Compare commits
89 Commits
pierremtb/
...
codex/upda
Author | SHA1 | Date | |
---|---|---|---|
5135badef7 | |||
416de9a9fb | |||
da65426ddc | |||
585b485852 | |||
e85f16ff9c | |||
e7d2289a14 | |||
d35531758d | |||
729e0a7949 | |||
620b7401aa | |||
e3e67b00d5 | |||
49d4f8e5c3 | |||
47b159c605 | |||
c7b086fa69 | |||
203db79204 | |||
48a4fd8373 | |||
17eb84325f | |||
ebf048478d | |||
28a8cd2421 | |||
1506de92f5 | |||
8a03413643 | |||
f59b806a88 | |||
23a0085c78 | |||
a280a8c3f0 | |||
11620dfa6b | |||
f6e26e0bab | |||
f6b3a55cbf | |||
74939e5cd6 | |||
9906c9947a | |||
48d6a21f0a | |||
dd6a980915 | |||
2516df3a39 | |||
52125f0566 | |||
e489222b6a | |||
d93a57d7bf | |||
d34aea345b | |||
0b6102b0ac | |||
9e0873ed84 | |||
8587eb5fea | |||
b898c27e74 | |||
3026866a16 | |||
92fc294eae | |||
21e967ea7f | |||
3f00e7186c | |||
d3a4fd8b55 | |||
2be7107cca | |||
94f194a984 | |||
4fe880a970 | |||
8f5fbfc273 | |||
e660f52bb0 | |||
d74fdd9369 | |||
334145f0be | |||
c24073b6ae | |||
078b7f3bf7 | |||
3d65676ccb | |||
ce566fb6e5 | |||
b23fc9f623 | |||
5c2dfb8e40 | |||
0e341d7863 | |||
6a03ff9596 | |||
d7bd0c937d | |||
d3b2483f4f | |||
7838b7c9fd | |||
130ecf1f88 | |||
550d8b3753 | |||
696222a070 | |||
edb424988d | |||
1e487ef3bd | |||
811ef3e72d | |||
980e3c4bc2 | |||
78b6854c6b | |||
068b9129cf | |||
05fba3c50c | |||
7944a4ce41 | |||
4640f1a3ad | |||
cd79059d97 | |||
2d95e19048 | |||
d047587bc1 | |||
128e1093fb | |||
afdc305e3e | |||
4bc5439996 | |||
be14022f97 | |||
eda78ef5ae | |||
0cc833e687 | |||
33e83747f3 | |||
c3a8fc6d93 | |||
8ff84e269c | |||
99cb6a6179 | |||
13dbfdfaa4 | |||
8603f5c53a |
57
.github/workflows/build-apps.yml
vendored
@ -13,7 +13,7 @@ env:
|
||||
IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
@ -123,18 +123,6 @@ 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]
|
||||
@ -259,49 +247,6 @@ 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
|
||||
|
56
.github/workflows/build-wasm.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
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/*
|
50
.github/workflows/cargo-test.yml
vendored
@ -155,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.
|
||||
@ -190,6 +190,54 @@ 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 }}
|
||||
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}}
|
||||
run-wasm-tests:
|
||||
name: Run wasm tests
|
||||
strategy:
|
||||
|
38
.github/workflows/e2e-tests.yml
vendored
@ -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: Rust Cache
|
||||
- name: Use 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,20 +133,17 @@ jobs:
|
||||
id: deps-install
|
||||
run: npm install
|
||||
|
||||
- name: Cache Playwright Browsers
|
||||
- name: Cache browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright/
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
- name: Install browsers
|
||||
run: npm run playwright install --with-deps
|
||||
|
||||
- name: build web
|
||||
run: npm run tronb:vite:dev
|
||||
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
- name: Capture snapshots
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
with:
|
||||
shell: bash
|
||||
@ -170,7 +167,7 @@ jobs:
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
- name: Check for changes
|
||||
- name: Check diff
|
||||
if: ${{ github.ref != 'refs/heads/main' }}
|
||||
shell: bash
|
||||
id: git-check
|
||||
@ -181,9 +178,8 @@ jobs:
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Commit changes, if any
|
||||
# TODO: find a more reliable way to detect visual changes
|
||||
if: ${{ false && steps.git-check.outputs.modified == 'true' }}
|
||||
- name: Commit changes
|
||||
if: ${{ steps.git-check.outputs.modified == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
|
||||
@ -193,7 +189,7 @@ jobs:
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
git commit -m "A snapshot a day keeps the bugs away! 📷🐛" || true
|
||||
git commit --message "Update snapshots" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
|
||||
@ -234,10 +230,16 @@ jobs:
|
||||
shardTotal: 8
|
||||
- os: namespace-profile-macos-8-cores
|
||||
shardIndex: 1
|
||||
shardTotal: 1
|
||||
shardTotal: 2
|
||||
- os: namespace-profile-macos-8-cores
|
||||
shardIndex: 2
|
||||
shardTotal: 2
|
||||
- os: windows-latest-8-cores
|
||||
shardIndex: 1
|
||||
shardTotal: 1
|
||||
shardTotal: 2
|
||||
- os: windows-latest-8-cores
|
||||
shardIndex: 2
|
||||
shardTotal: 2
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
99
.github/workflows/kcl-language-server.yml
vendored
@ -21,14 +21,11 @@ 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
|
||||
@ -38,10 +35,9 @@ 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: vscode tests
|
||||
name: kcl-language-server (vscode tests)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -77,22 +73,20 @@ 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
|
||||
@ -105,41 +99,33 @@ jobs:
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
code-target: darwin-arm64
|
||||
|
||||
name: build-release (${{ matrix.target }})
|
||||
name: kcl-language-server 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
|
||||
@ -164,64 +150,53 @@ 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: build-release (x86_64-unknown-linux-musl)
|
||||
name: kcl-language-server build-release (x86_64-unknown-linux-musl)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RA_TARGET: x86_64-unknown-linux-musl
|
||||
@ -231,7 +206,6 @@ jobs:
|
||||
image: alpine:latest
|
||||
volumes:
|
||||
- /usr/local/cargo/registry:/usr/local/cargo/registry
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@ -245,55 +219,46 @@ 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: publish
|
||||
name: kcl-language-server (publish)
|
||||
runs-on: ubuntu-latest
|
||||
needs: ["build-release", "build-release-x86_64-unknown-linux-musl"]
|
||||
if: startsWith(github.event.ref, 'refs/tags')
|
||||
@ -301,22 +266,17 @@ 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
|
||||
@ -344,33 +304,29 @@ 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:
|
||||
@ -385,15 +341,12 @@ 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
|
||||
|
32
.github/workflows/kcl-python-bindings.yml
vendored
@ -4,7 +4,6 @@
|
||||
# maturin generate-ci github
|
||||
#
|
||||
name: kcl-python-bindings
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@ -27,16 +26,14 @@ 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
|
||||
@ -58,8 +55,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:
|
||||
@ -84,8 +81,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:
|
||||
@ -110,8 +107,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
|
||||
@ -127,8 +124,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
|
||||
@ -136,10 +133,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:
|
||||
@ -151,7 +148,6 @@ jobs:
|
||||
with:
|
||||
name: wheels-sdist
|
||||
path: rust/kcl-python-bindings/dist
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
@ -168,11 +164,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:
|
||||
|
167
.github/workflows/static-analysis.yml
vendored
@ -28,53 +28,7 @@ jobs:
|
||||
- run: npm run fmt:check
|
||||
|
||||
npm-build-wasm:
|
||||
# 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/*
|
||||
uses: ./.github/workflows/build-wasm.yml
|
||||
|
||||
npm-tsc:
|
||||
runs-on: ubuntu-latest
|
||||
@ -173,122 +127,3 @@ 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 }}
|
||||
|
124
.github/workflows/unit-tests.yml
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
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
@ -58,6 +58,8 @@ 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/
|
||||
|
@ -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 as well as updater-test artifacts.
|
||||
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files.
|
||||
|
||||
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,13 +142,10 @@ 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.
|
||||
|
||||
##### 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).
|
||||
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.
|
||||
|
||||
```
|
||||
# Windows (PowerShell)
|
||||
|
1
Makefile
@ -114,7 +114,6 @@ 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)
|
||||
|
@ -34,7 +34,7 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
|
||||
- WebSockets (via [KittyCAD TS client](https://github.com/KittyCAD/kittycad.ts))
|
||||
- Code Editor
|
||||
- [CodeMirror](https://codemirror.net/)
|
||||
- Custom WASM LSP Server
|
||||
- [Custom WASM LSP Server](https://github.com/KittyCAD/modeling-app/tree/main/rust/kcl-lib/src/lsp/kcl)
|
||||
- Modeling
|
||||
- [KittyCAD TypeScript client](https://github.com/KittyCAD/kittycad.ts)
|
||||
|
||||
|
@ -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,6 +29,7 @@ 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 = [
|
||||
|
16
docs/kcl-std/consts/std-sweep-SKETCH_PLANE.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
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'
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
16
docs/kcl-std/consts/std-sweep-TRAJECTORY.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
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'
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -128,6 +128,9 @@ 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)
|
||||
|
17
docs/kcl-std/modules/std-sweep.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
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)
|
||||
|
@ -19,6 +19,7 @@ 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)
|
||||
|
@ -134,8 +134,6 @@ 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', {
|
||||
@ -158,10 +156,14 @@ extrude001 = extrude(sketch001, length = 5)`
|
||||
await expect(
|
||||
page
|
||||
.getByText(
|
||||
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
|
||||
'Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis'
|
||||
)
|
||||
.first()
|
||||
).toBeVisible()
|
||||
|
||||
// Make sure ApiError is not on the page.
|
||||
// This ensures we didn't nest the json
|
||||
await expect(page.getByText('ApiError')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
|
||||
@ -179,7 +181,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')
|
||||
|
@ -58,12 +58,6 @@ test(
|
||||
await expect(submitButton).toBeVisible()
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Look out for the toast message
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Expect it to succeed
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
@ -71,8 +65,9 @@ test(
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
await page.waitForTimeout(1_000)
|
||||
const count = await successToastMessage.count()
|
||||
await expect(count).toBeGreaterThanOrEqual(1)
|
||||
|
||||
// Check for the exported file
|
||||
const firstFileFullPath = path.resolve(
|
||||
@ -141,7 +136,9 @@ test(
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
await page.waitForTimeout(1_000)
|
||||
const count = await successToastMessage.count()
|
||||
await expect(count).toBeGreaterThanOrEqual(1)
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Check for the exported file=
|
||||
|
@ -1533,7 +1533,6 @@ sketch001 = startSketchOn(XZ)
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await scene.connectionEstablished()
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.DARK_MODE_BKGD,
|
||||
|
@ -155,6 +155,12 @@ export class CmdBarFixture {
|
||||
}
|
||||
}
|
||||
|
||||
closeCmdBar = async () => {
|
||||
const cmdBarCloseBtn = this.page.getByTestId('command-bar-close-button')
|
||||
await cmdBarCloseBtn.click()
|
||||
await expect(this.cmdBarElement).not.toBeVisible()
|
||||
}
|
||||
|
||||
get cmdSearchInput() {
|
||||
return this.page.getByTestId('cmd-bar-search')
|
||||
}
|
||||
@ -298,4 +304,27 @@ export class CmdBarFixture {
|
||||
`Monitoring text-to-cad API requests. Output will be saved to: ${outputPath}`
|
||||
)
|
||||
}
|
||||
|
||||
async toBeOpened() {
|
||||
// Check that the command bar is opened
|
||||
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
|
||||
}
|
||||
|
||||
async expectArgValue(value: string) {
|
||||
// Check the placeholder project name exists
|
||||
const actualArgument = await this.cmdBarElement
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.inputValue()
|
||||
const expectedArgument = value
|
||||
expect(actualArgument).toBe(expectedArgument)
|
||||
}
|
||||
|
||||
async expectCommandName(value: string) {
|
||||
// Check the placeholder project name exists
|
||||
const actual = await this.cmdBarElement
|
||||
.getByTestId('command-name')
|
||||
.textContent()
|
||||
const expected = value
|
||||
expect(actual).toBe(expected)
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ export class HomePageFixture {
|
||||
projectTextName!: Locator
|
||||
sortByDateBtn!: Locator
|
||||
sortByNameBtn!: Locator
|
||||
appHeader!: Locator
|
||||
tutorialBtn!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
@ -44,6 +45,7 @@ export class HomePageFixture {
|
||||
|
||||
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||
this.appHeader = this.page.getByTestId('app-header')
|
||||
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
|
||||
}
|
||||
|
||||
@ -125,4 +127,11 @@ export class HomePageFixture {
|
||||
|
||||
await this.createAndGoToProject(name)
|
||||
}
|
||||
|
||||
isNativeFileMenuCreated = async () => {
|
||||
await expect(this.appHeader).toHaveAttribute(
|
||||
'data-native-file-menu',
|
||||
'true'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ export class SceneFixture {
|
||||
public networkToggleConnected!: Locator
|
||||
public engineConnectionsSpinner!: Locator
|
||||
public startEditSketchBtn!: Locator
|
||||
public appHeader!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -57,6 +58,7 @@ export class SceneFixture {
|
||||
this.startEditSketchBtn = page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
||||
this.appHeader = this.page.getByTestId('app-header')
|
||||
}
|
||||
private _serialiseScene = async (): Promise<SceneSerialised> => {
|
||||
const camera = await this.getCameraInfo()
|
||||
@ -280,6 +282,13 @@ export class SceneFixture {
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
await buttonToTest.click()
|
||||
}
|
||||
|
||||
isNativeFileMenuCreated = async () => {
|
||||
await expect(this.appHeader).toHaveAttribute(
|
||||
'data-native-file-menu',
|
||||
'true'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function isColourArray(
|
||||
|
@ -6,7 +6,6 @@ test.describe('Onboarding tests', () => {
|
||||
homePage,
|
||||
toolbar,
|
||||
editor,
|
||||
scene,
|
||||
tronApp,
|
||||
}) => {
|
||||
if (!tronApp) {
|
||||
@ -62,7 +61,6 @@ 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 () => {
|
||||
@ -132,9 +130,7 @@ 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')
|
||||
})
|
||||
@ -162,13 +158,10 @@ 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')
|
||||
})
|
||||
|
@ -1931,50 +1931,37 @@ sketch002 = startSketchOn(XZ)
|
||||
})
|
||||
})
|
||||
|
||||
test(`Sweep point-and-click failing validation`, async ({
|
||||
test(`Sweep point-and-click helix`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
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])
|
||||
`
|
||||
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)'
|
||||
|
||||
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: 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 test.step(`Add sweep through the command bar flow`, async () => {
|
||||
await toolbar.openPane('feature-tree')
|
||||
await toolbar.sweepButton.click()
|
||||
await expect
|
||||
.poll(() => page.getByText('Please select one').count())
|
||||
.toBe(1)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
currentArgKey: 'sketches',
|
||||
@ -1987,7 +1974,8 @@ sketch002 = startSketchOn(XZ)
|
||||
highlightedHeaderArg: 'sketches',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await clickOnSketch1()
|
||||
await editor.scrollToText(circleCode)
|
||||
await page.getByText(circleCode).click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
@ -2001,11 +1989,39 @@ sketch002 = startSketchOn(XZ)
|
||||
highlightedHeaderArg: 'path',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await clickOnSketch2()
|
||||
const helix = await toolbar.getFeatureTreeOperation('Helix', 0)
|
||||
await helix.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
currentArgKey: 'path',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(
|
||||
page.getByText('Unable to sweep with the current selection. Reason:')
|
||||
).toBeVisible()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
headerArguments: {
|
||||
Sketches: '1 face',
|
||||
Path: '1 helix',
|
||||
Sectional: '',
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await editor.expectEditor.toContain(sweepDeclaration)
|
||||
})
|
||||
|
||||
await test.step('Delete sweep via feature tree selection', async () => {
|
||||
const sweep = await toolbar.getFeatureTreeOperation('Sweep', 0)
|
||||
await sweep.click()
|
||||
await page.keyboard.press('Delete')
|
||||
await editor.expectEditor.not.toContain(sweepDeclaration)
|
||||
})
|
||||
})
|
||||
|
||||
@ -3627,67 +3643,6 @@ 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,
|
||||
@ -4943,4 +4898,34 @@ 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 })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1818,7 +1818,6 @@ test(
|
||||
'extrude-custom-plane.kcl',
|
||||
'extrude-inside-fn-with-tags.kcl',
|
||||
'fillet-and-shell.kcl',
|
||||
'fillet_duplicate_tags.kcl',
|
||||
'focusrite_scarlett_mounting_bracket.kcl',
|
||||
'function_sketch.kcl',
|
||||
'function_sketch_with_position.kcl',
|
||||
@ -1826,7 +1825,6 @@ test(
|
||||
'helix_defaults.kcl',
|
||||
'helix_defaults_negative_extrude.kcl',
|
||||
'helix_with_length.kcl',
|
||||
'i_shape.kcl',
|
||||
'kittycad_svg.kcl',
|
||||
'lego.kcl',
|
||||
'lsystem.kcl',
|
||||
|
@ -99,6 +99,8 @@ 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')
|
||||
|
@ -88,6 +88,8 @@ 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')
|
||||
@ -165,6 +167,8 @@ 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')
|
||||
|
@ -19,11 +19,12 @@ 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',
|
||||
@ -40,7 +41,8 @@ test.describe('Regression tests', () => {
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await scene.connectionEstablished()
|
||||
// await u.waitForPageLoad()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
@ -51,8 +53,11 @@ test.describe('Regression tests', () => {
|
||||
// the close doesn't work
|
||||
// when https://github.com/KittyCAD/modeling-app/issues/3268 is closed
|
||||
// this test will need updating
|
||||
const crypticErrorText = `ApiError`
|
||||
const crypticErrorText = `Cannot close a path that is non-planar or with duplicate vertices.
|
||||
Internal engine error on request`
|
||||
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||
// Ensure we didn't nest the json.
|
||||
await expect(page.getByText('ApiError')).not.toBeVisible()
|
||||
})
|
||||
test('user should not have to press down twice in cmdbar', async ({
|
||||
page,
|
||||
@ -185,8 +190,8 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
page.locator('.pretty-json-container >> text=myVar:"67')
|
||||
).toBeVisible()
|
||||
})
|
||||
test('ProgramMemory can be serialised', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
test('ProgramMemory can be serialised', async ({ page, homePage, scene }) => {
|
||||
// const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -211,11 +216,12 @@ 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 u.waitForPageLoad()
|
||||
await scene.connectionEstablished()
|
||||
|
||||
// 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) => {
|
||||
@ -229,6 +235,7 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -247,11 +254,10 @@ 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,
|
||||
@ -329,10 +335,10 @@ extrude002 = extrude(profile002, length = 150)
|
||||
)
|
||||
|
||||
const websocketPromise = page.waitForEvent('websocket')
|
||||
await toolbar.closePane('code')
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await toolbar.closePane('code')
|
||||
const websocket = await websocketPromise
|
||||
|
||||
await scene.connectionEstablished()
|
||||
@ -452,12 +458,10 @@ 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.
|
||||
@ -465,11 +469,13 @@ extrude002 = extrude(profile002, length = 150)
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
await page.waitForTimeout(1_000)
|
||||
const count = await successToastMessage.count()
|
||||
await expect(count).toBeGreaterThanOrEqual(1)
|
||||
}
|
||||
)
|
||||
// We updated this test such that you can have multiple exports going at once.
|
||||
@ -545,14 +551,15 @@ extrude002 = extrude(profile002, length = 150)
|
||||
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
||||
])
|
||||
|
||||
await expect(successToastMessage).toHaveCount(2)
|
||||
const count = await successToastMessage.count()
|
||||
await expect(count).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
test(
|
||||
`Network health indicator only appears in modeling view`,
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
async ({ context, page }) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
@ -561,9 +568,7 @@ extrude002 = extrude(profile002, length = 150)
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Locators
|
||||
const projectsHeading = page.getByRole('heading', {
|
||||
@ -583,10 +588,8 @@ extrude002 = extrude(profile002, length = 150)
|
||||
})
|
||||
|
||||
await test.step('Check the modeling view', async () => {
|
||||
await expect(projectsHeading).not.toBeVisible()
|
||||
await expect(networkHealthIndicator).toBeVisible()
|
||||
await expect(networkHealthIndicator).toContainText('Problem')
|
||||
await u.waitForPageLoad()
|
||||
await expect(networkHealthIndicator).toContainText('Connected')
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -1365,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
|
||||
}
|
||||
|
||||
const railTop = in2mm(.748)
|
||||
const railSide = in2mm(.024)
|
||||
const railBaseWidth = in2mm(.612)
|
||||
const railWideWidth = in2mm(.835)
|
||||
const railBaseLength = in2mm(.200)
|
||||
const railClampable = in2mm(.200)
|
||||
railTop = in2mm(.748)
|
||||
railSide = in2mm(.024)
|
||||
railBaseWidth = in2mm(.612)
|
||||
railWideWidth = in2mm(.835)
|
||||
railBaseLength = in2mm(.200)
|
||||
railClampable = in2mm(.200)
|
||||
|
||||
const rail = startSketchOn(XZ)
|
||||
rail = startSketchOn(XZ)
|
||||
|> startProfile(at = [-railTop / 2, railClampable + railBaseLength])
|
||||
|> line(endAbsolute = [
|
||||
railTop / 2,
|
||||
@ -3496,6 +3496,69 @@ profile001 = startProfile(sketch001, at = [-102.72, 237.44])
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
// Ensure feature tree is not showing previous file's content when switching to a file with KCL errors.
|
||||
test('Feature tree shows correct sketch count per file', async ({
|
||||
context,
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Setup project with files.
|
||||
const GOOD_KCL = `sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfile(sketch001, at = [220.81, 253.8])
|
||||
|> line(end = [132.84, -151.31])
|
||||
|> line(end = [25.51, 167.15])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch002 = startSketchOn(XZ)
|
||||
profile002 = startProfile(sketch002, at = [158.35, -70.82])
|
||||
|> line(end = [73.9, -152.19])
|
||||
|> line(end = [85.33, 135.48])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()`
|
||||
|
||||
const ERROR_KCL = `sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfile(sketch001, at = [127.56, 179.02])
|
||||
|> line(end = [132.84, -112.6])
|
||||
|> line(end = [85.33, 234.01])
|
||||
|> line(enfd = [-137.23, -54.55])`
|
||||
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const projectDir = path.join(dir, 'multi-file-sketch-test')
|
||||
await fs.mkdir(projectDir, { recursive: true })
|
||||
await Promise.all([
|
||||
fs.writeFile(path.join(projectDir, 'good.kcl'), GOOD_KCL, 'utf-8'),
|
||||
fs.writeFile(path.join(projectDir, 'error.kcl'), ERROR_KCL, 'utf-8'),
|
||||
])
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 800 })
|
||||
|
||||
await homePage.openProject('multi-file-sketch-test')
|
||||
await scene.connectionEstablished()
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await toolbar.openFeatureTreePane()
|
||||
await toolbar.openPane('files')
|
||||
|
||||
await toolbar.openFile('good.kcl')
|
||||
|
||||
await expect(
|
||||
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
|
||||
).toHaveCount(2)
|
||||
|
||||
await toolbar.openFile('error.kcl')
|
||||
|
||||
await expect(
|
||||
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
|
||||
).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('adding a syntax error, recovers after fixing', async ({
|
||||
page,
|
||||
homePage,
|
||||
|
@ -1,4 +1,3 @@
|
||||
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'
|
||||
@ -9,6 +8,7 @@ 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 }) => {
|
||||
test('theme persists', async ({ page, context, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -784,7 +784,7 @@ test('theme persists', async ({ page, context }) => {
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// await page.getByRole('link', { name: 'Settings Settings (tooltip)' }).click()
|
||||
@ -812,7 +812,7 @@ test('theme persists', async ({ page, context }) => {
|
||||
// Disconnect and reconnect to check the theme persists through a reload
|
||||
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Offline')
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
|
||||
// simulate network up
|
||||
await u.emulateNetworkConditions({
|
||||
@ -873,6 +873,50 @@ 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,
|
||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 47 KiB |
@ -1,236 +1,257 @@
|
||||
import type { EngineCommand } from '@src/lang/std/artifactGraph'
|
||||
import { uuidv4 } from '@src/lib/utils'
|
||||
|
||||
import { commonPoints, getUtils } from '@e2e/playwright/test-utils'
|
||||
import {
|
||||
commonPoints,
|
||||
getUtils,
|
||||
TEST_COLORS,
|
||||
circleMove,
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
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 })
|
||||
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)')
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// This is how we wait until the stream is online
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
|
||||
const networkWidget = page.locator('[data-testid="network-toggle"]')
|
||||
await expect(networkWidget).toBeVisible()
|
||||
await networkWidget.hover()
|
||||
// This is how we wait until the stream is online
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
const networkPopover = page.locator('[data-testid="network-popover"]')
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
const networkWidget = page.locator('[data-testid="network-toggle"]')
|
||||
await expect(networkWidget).toBeVisible()
|
||||
await networkWidget.hover()
|
||||
|
||||
// (First check) Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
const networkPopover = page.locator('[data-testid="network-popover"]')
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
// (First check) Expect the network to be up
|
||||
await expect(
|
||||
networkToggleConnectedText.or(networkToggleWeakText)
|
||||
).toBeVisible()
|
||||
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).toBeVisible()
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
|
||||
// Click off the modal.
|
||||
await page.mouse.click(100, 100)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).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,
|
||||
})
|
||||
// Click off the modal.
|
||||
await page.mouse.click(100, 100)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
// 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,
|
||||
})
|
||||
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).toBeVisible()
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
|
||||
// Click off the modal.
|
||||
await page.mouse.click(0, 0)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).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,
|
||||
})
|
||||
// Click off the modal.
|
||||
await page.mouse.click(0, 0)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
// 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,
|
||||
})
|
||||
|
||||
// (Second check) expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
}
|
||||
)
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
test(
|
||||
'Engine disconnect & reconnect in sketch mode',
|
||||
{ tag: '@skipLocalEngine' },
|
||||
async ({ page, homePage, toolbar, scene, cmdBar }) => {
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
// (Second check) expect the network to be up
|
||||
await expect(
|
||||
networkToggleConnectedText.or(networkToggleWeakText)
|
||||
).toBeVisible()
|
||||
}
|
||||
)
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
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)')
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
|
||||
await u.openDebugPanel()
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
await u.openDebugPanel()
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn(XZ)`
|
||||
)
|
||||
await u.closeDebugPanel()
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)`
|
||||
)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
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)
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
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 expect(
|
||||
page.locator('.cm-content')
|
||||
).toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfile(sketch001, at = ${commonPoints.startAt})
|
||||
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})
|
||||
|> xLine(length = ${commonPoints.num1})`)
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
// Expect the network to be up
|
||||
await networkToggle.hover()
|
||||
await expect(
|
||||
networkToggleConnectedText.or(networkToggleWeakText)
|
||||
).toBeVisible()
|
||||
|
||||
// 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 expect(networkToggle).toContainText('Problem')
|
||||
// Expect the network to be down
|
||||
await networkToggle.hover()
|
||||
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 expect(networkToggle).toContainText('Connected')
|
||||
await scene.settled(cmdBar)
|
||||
// Expect the network to be up
|
||||
await networkToggle.hover()
|
||||
await expect(
|
||||
networkToggleConnectedText.or(networkToggleWeakText)
|
||||
).toBeVisible()
|
||||
|
||||
// Click off the code pane.
|
||||
await page.mouse.click(100, 100)
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
// select a line
|
||||
await page
|
||||
.getByText(`startProfile(sketch001, at = ${commonPoints.startAt})`)
|
||||
.click()
|
||||
// Click off the code pane.
|
||||
await page.mouse.click(100, 100)
|
||||
|
||||
// enter sketch again
|
||||
await toolbar.editSketch()
|
||||
// select a line
|
||||
await page
|
||||
.getByText(`startProfile(sketch001, at = ${commonPoints.startAt})`)
|
||||
.click()
|
||||
|
||||
// Click the line tool
|
||||
await page
|
||||
.getByRole('button', { name: 'line Line', exact: true })
|
||||
.click()
|
||||
// enter sketch again
|
||||
await toolbar.editSketch()
|
||||
|
||||
await page.waitForTimeout(150)
|
||||
// Click the line tool
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
|
||||
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)
|
||||
await page.waitForTimeout(150)
|
||||
|
||||
// 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)
|
||||
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)
|
||||
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(`sketch001 = startSketchOn(XZ)
|
||||
await expect
|
||||
.poll(u.normalisedEditorCode)
|
||||
.toBe(`@settings(defaultLengthUnit = in)
|
||||
|
||||
|
||||
sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfile(sketch001, at = [12.34, -12.34])
|
||||
|> xLine(length = 12.34)
|
||||
|> line(end = [-12.34, 12.34])
|
||||
@ -238,22 +259,105 @@ 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()
|
||||
// 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()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -21,6 +21,7 @@ export const token = process.env.token || ''
|
||||
|
||||
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
|
||||
|
||||
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
|
||||
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
|
||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
|
||||
import { test } from '@e2e/playwright/zoo-test'
|
||||
@ -43,6 +44,8 @@ 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],
|
||||
@ -548,11 +551,6 @@ 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')
|
||||
@ -809,8 +807,6 @@ export const doExport = async (
|
||||
|
||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||
|
||||
// This usually happens immediately after. If we're too slow we don't
|
||||
// catch it.
|
||||
await expect(page.getByText('Exported successfully')).toBeVisible()
|
||||
|
||||
if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') {
|
||||
@ -1151,3 +1147,77 @@ export function perProjectSettingsToToml(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return TOML.stringify(settings as any)
|
||||
}
|
||||
|
||||
export async function clickElectronNativeMenuById(
|
||||
tronApp: ElectronZoo,
|
||||
menuId: string
|
||||
) {
|
||||
const clickWasTriggered = await tronApp.electron.evaluate(
|
||||
async ({ app }, menuId) => {
|
||||
if (!app || !app.applicationMenu) {
|
||||
return false
|
||||
}
|
||||
const menu = app.applicationMenu.getMenuItemById(menuId)
|
||||
if (!menu) return false
|
||||
menu.click()
|
||||
return true
|
||||
},
|
||||
menuId
|
||||
)
|
||||
expect(clickWasTriggered).toBe(true)
|
||||
}
|
||||
|
||||
export async function findElectronNativeMenuById(
|
||||
tronApp: ElectronZoo,
|
||||
menuId: string
|
||||
) {
|
||||
const found = await tronApp.electron.evaluate(async ({ app }, menuId) => {
|
||||
if (!app || !app.applicationMenu) {
|
||||
return false
|
||||
}
|
||||
const menu = app.applicationMenu.getMenuItemById(menuId)
|
||||
if (!menu) return false
|
||||
return true
|
||||
}, menuId)
|
||||
expect(found).toBe(true)
|
||||
}
|
||||
|
||||
export async function openSettingsExpectText(page: Page, text: string) {
|
||||
const settings = page.getByTestId('settings-dialog-panel')
|
||||
await expect(settings).toBeVisible()
|
||||
// You are viewing the user tab
|
||||
const actualText = settings.getByText(text)
|
||||
await expect(actualText).toBeVisible()
|
||||
}
|
||||
|
||||
export async function openSettingsExpectLocator(page: Page, selector: string) {
|
||||
const settings = page.getByTestId('settings-dialog-panel')
|
||||
await expect(settings).toBeVisible()
|
||||
// You are viewing the keybindings tab
|
||||
const settingsLocator = settings.locator(selector)
|
||||
await expect(settingsLocator).toBeVisible()
|
||||
}
|
||||
|
||||
/**
|
||||
* A developer helper function to make playwright send all the console logs to stdout
|
||||
* Call this within your E2E test and pass in the page or the tronApp to get as many
|
||||
* logs piped to stdout for debugging
|
||||
*/
|
||||
export async function enableConsoleLogEverything({
|
||||
page,
|
||||
tronApp,
|
||||
}: { page?: Page; tronApp?: ElectronZoo }) {
|
||||
page?.on('console', (msg) => {
|
||||
console.log(`[Page-log]: ${msg.text()}`)
|
||||
})
|
||||
|
||||
tronApp?.electron.on('window', async (electronPage) => {
|
||||
electronPage.on('console', (msg) => {
|
||||
console.log(`[Renderer] ${msg.type()}: ${msg.text()}`)
|
||||
})
|
||||
})
|
||||
|
||||
tronApp?.electron.on('console', (msg) => {
|
||||
console.log(`[Main] ${msg.type()}: ${msg.text()}`)
|
||||
})
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ test.describe('Testing loading external models', () => {
|
||||
|
||||
// Locators and constants
|
||||
const newSample = {
|
||||
file: 'parametric-bearing-pillow-block' + FILE_EXT,
|
||||
title: 'Parametric Bearing Pillow Block',
|
||||
file: 'pillow-block-bearing' + FILE_EXT,
|
||||
title: 'Pillow Block Bearing',
|
||||
}
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const samplesCommandOption = page.getByRole('option', {
|
||||
@ -100,9 +100,9 @@ test.describe('Testing loading external models', () => {
|
||||
|
||||
// Locators and constants
|
||||
const sampleOne = {
|
||||
file: 'parametric-bearing-pillow-block' + FILE_EXT,
|
||||
title: 'Parametric Bearing Pillow Block',
|
||||
file1: 'parametric-bearing-pillow-block-1' + FILE_EXT,
|
||||
file: 'ball-bearing' + FILE_EXT,
|
||||
title: 'Ball Bearing',
|
||||
file1: 'ball-bearing-1' + FILE_EXT,
|
||||
}
|
||||
const projectCard = page.getByRole('link', { name: 'bracket' })
|
||||
const overwriteWarning = page.getByText(
|
||||
|
@ -74,7 +74,7 @@
|
||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||
ELECTRON_OVERRIDE_DIST_PATH =
|
||||
if pkgs.stdenv.isDarwin
|
||||
then "${pkgs.electron}/Applications"
|
||||
then "${pkgs.electron}/Applications/Electron.app/Contents/MacOS/"
|
||||
else "${pkgs.electron}/bin";
|
||||
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
|
||||
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";
|
||||
|
@ -94,7 +94,6 @@
|
||||
"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 &",
|
||||
@ -130,15 +129,14 @@
|
||||
"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 **/kclSamples.test.ts --exclude **/jest-component-unit-tests/*",
|
||||
"test:unit": "vitest run --mode development --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:kcl-samples:local": "npm run simpleserver:bg && npm run test:unit:kcl-samples; kill-port 3000"
|
||||
"test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
@ -31,14 +31,24 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](ball-bearing/main.kcl)
|
||||
#### [bench](bench/main.kcl) ([screenshot](screenshots/bench.png))
|
||||
[](bench/main.kcl)
|
||||
#### [bone-plate](bone-plate/main.kcl) ([screenshot](screenshots/bone-plate.png))
|
||||
[](bone-plate/main.kcl)
|
||||
#### [bottle](bottle/main.kcl) ([screenshot](screenshots/bottle.png))
|
||||
[](bottle/main.kcl)
|
||||
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
|
||||
[](bracket/main.kcl)
|
||||
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))
|
||||
[](car-wheel-assembly/main.kcl)
|
||||
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
|
||||
[](cold-plate/main.kcl)
|
||||
#### [color-cube](color-cube/main.kcl) ([screenshot](screenshots/color-cube.png))
|
||||
[](color-cube/main.kcl)
|
||||
#### [counterdrilled-weldment](counterdrilled-weldment/main.kcl) ([screenshot](screenshots/counterdrilled-weldment.png))
|
||||
[](counterdrilled-weldment/main.kcl)
|
||||
#### [countersunk-plate](countersunk-plate/main.kcl) ([screenshot](screenshots/countersunk-plate.png))
|
||||
[](countersunk-plate/main.kcl)
|
||||
#### [cpu-cooler](cpu-cooler/main.kcl) ([screenshot](screenshots/cpu-cooler.png))
|
||||
[](cpu-cooler/main.kcl)
|
||||
#### [cycloidal-gear](cycloidal-gear/main.kcl) ([screenshot](screenshots/cycloidal-gear.png))
|
||||
[](cycloidal-gear/main.kcl)
|
||||
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
|
||||
@ -55,8 +65,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](food-service-spatula/main.kcl)
|
||||
#### [french-press](french-press/main.kcl) ([screenshot](screenshots/french-press.png))
|
||||
[](french-press/main.kcl)
|
||||
#### [gear](gear/main.kcl) ([screenshot](screenshots/gear.png))
|
||||
[](gear/main.kcl)
|
||||
#### [gear-rack](gear-rack/main.kcl) ([screenshot](screenshots/gear-rack.png))
|
||||
[](gear-rack/main.kcl)
|
||||
#### [gridfinity-baseplate](gridfinity-baseplate/main.kcl) ([screenshot](screenshots/gridfinity-baseplate.png))
|
||||
@ -67,6 +75,18 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](gridfinity-bins/main.kcl)
|
||||
#### [gridfinity-bins-stacking-lip](gridfinity-bins-stacking-lip/main.kcl) ([screenshot](screenshots/gridfinity-bins-stacking-lip.png))
|
||||
[](gridfinity-bins-stacking-lip/main.kcl)
|
||||
#### [hammer](hammer/main.kcl) ([screenshot](screenshots/hammer.png))
|
||||
[](hammer/main.kcl)
|
||||
#### [helical-gear](helical-gear/main.kcl) ([screenshot](screenshots/helical-gear.png))
|
||||
[](helical-gear/main.kcl)
|
||||
#### [helical-planetary-gearset](helical-planetary-gearset/main.kcl) ([screenshot](screenshots/helical-planetary-gearset.png))
|
||||
[](helical-planetary-gearset/main.kcl)
|
||||
#### [helium-tank](helium-tank/main.kcl) ([screenshot](screenshots/helium-tank.png))
|
||||
[](helium-tank/main.kcl)
|
||||
#### [herringbone-gear](herringbone-gear/main.kcl) ([screenshot](screenshots/herringbone-gear.png))
|
||||
[](herringbone-gear/main.kcl)
|
||||
#### [herringbone-planetary-gearset](herringbone-planetary-gearset/main.kcl) ([screenshot](screenshots/herringbone-planetary-gearset.png))
|
||||
[](herringbone-planetary-gearset/main.kcl)
|
||||
#### [hex-nut](hex-nut/main.kcl) ([screenshot](screenshots/hex-nut.png))
|
||||
[](hex-nut/main.kcl)
|
||||
#### [i-beam](i-beam/main.kcl) ([screenshot](screenshots/i-beam.png))
|
||||
@ -83,8 +103,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](mounting-plate/main.kcl)
|
||||
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
|
||||
[](multi-axis-robot/main.kcl)
|
||||
#### [parametric-bearing-pillow-block](parametric-bearing-pillow-block/main.kcl) ([screenshot](screenshots/parametric-bearing-pillow-block.png))
|
||||
[](parametric-bearing-pillow-block/main.kcl)
|
||||
#### [pillow-block-bearing](pillow-block-bearing/main.kcl) ([screenshot](screenshots/pillow-block-bearing.png))
|
||||
[](pillow-block-bearing/main.kcl)
|
||||
#### [pipe](pipe/main.kcl) ([screenshot](screenshots/pipe.png))
|
||||
[](pipe/main.kcl)
|
||||
#### [pipe-flange-assembly](pipe-flange-assembly/main.kcl) ([screenshot](screenshots/pipe-flange-assembly.png))
|
||||
@ -93,6 +113,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](pipe-with-bend/main.kcl)
|
||||
#### [poopy-shoe](poopy-shoe/main.kcl) ([screenshot](screenshots/poopy-shoe.png))
|
||||
[](poopy-shoe/main.kcl)
|
||||
#### [prosthetic-hip](prosthetic-hip/main.kcl) ([screenshot](screenshots/prosthetic-hip.png))
|
||||
[](prosthetic-hip/main.kcl)
|
||||
#### [router-template-cross-bar](router-template-cross-bar/main.kcl) ([screenshot](screenshots/router-template-cross-bar.png))
|
||||
[](router-template-cross-bar/main.kcl)
|
||||
#### [router-template-slate](router-template-slate/main.kcl) ([screenshot](screenshots/router-template-slate.png))
|
||||
@ -101,10 +123,20 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](sheet-metal-bracket/main.kcl)
|
||||
#### [socket-head-cap-screw](socket-head-cap-screw/main.kcl) ([screenshot](screenshots/socket-head-cap-screw.png))
|
||||
[](socket-head-cap-screw/main.kcl)
|
||||
#### [spur-gear](spur-gear/main.kcl) ([screenshot](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/main.kcl)
|
||||
#### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png))
|
||||
[](surgical-drill-guide/main.kcl)
|
||||
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
|
||||
[](tooling-nest-block/main.kcl)
|
||||
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
|
||||
[](utility-sink/main.kcl)
|
||||
#### [walkie-talkie](walkie-talkie/main.kcl) ([screenshot](screenshots/walkie-talkie.png))
|
||||
[](walkie-talkie/main.kcl)
|
||||
#### [washer](washer/main.kcl) ([screenshot](screenshots/washer.png))
|
||||
[](washer/main.kcl)
|
||||
#### [wing-spar](wing-spar/main.kcl) ([screenshot](screenshots/wing-spar.png))
|
||||
[](wing-spar/main.kcl)
|
||||
|
||||
|
@ -57,9 +57,9 @@ fn connectorSketch(@plane, start) {
|
||||
|
||||
export fn connector(@plane, length) {
|
||||
connectorSketch(plane, start = [-12, 8])
|
||||
|> extrude(length = length)
|
||||
|> extrude(length)
|
||||
connectorSketch(plane, start = [16, 8])
|
||||
|> extrude(length = length)
|
||||
|> extrude(length)
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ fn seatSlatSketch(@plane) {
|
||||
|
||||
export fn seatSlats(@plane, length) {
|
||||
seatSlatSketch(plane)
|
||||
|> extrude(length = length)
|
||||
|> extrude(length)
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ fn backSlatsSketch(@plane) {
|
||||
|
||||
export fn backSlats(@plane, length) {
|
||||
b = backSlatsSketch(plane)
|
||||
|> extrude(length = length)
|
||||
|> extrude(length)
|
||||
return b
|
||||
}
|
||||
|
||||
|
55
public/kcl-samples/bone-plate/main.kcl
Normal file
@ -0,0 +1,55 @@
|
||||
// Bone Plate
|
||||
// A bone plate is a medical device used in orthopedics to stabilize and fix bone fractures during the healing process. They are typically made of stainless steel or titanium and are secured to the bone with screws. Bone plates come in various types, including locking, compression, and bridge plates, each with specific applications
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Define parameters
|
||||
boltSize = 4.5
|
||||
|
||||
// Revolve the profile of a compression plate designed to fit a bone
|
||||
plateRevolve = startSketchOn(YZ)
|
||||
|> startProfile(at = [22.9, 0])
|
||||
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|
||||
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|
||||
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|
||||
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|
||||
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|
||||
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|
||||
|> arc(angleStart = 180, angleEnd = 174, radius = 170)
|
||||
|> tangentialArc(endAbsolute = [41.8, 91.88])
|
||||
|> tangentialArc(endAbsolute = [56.92, 117.08], tag = $seg01)
|
||||
|> angledLine(angle = tangentToEnd(seg01), length = 23.16)
|
||||
|> tangentialArc(endAbsolute = [60.93, 140.44], tag = $seg02)
|
||||
|> angledLine(angle = tangentToEnd(seg02), length = 25.65)
|
||||
|> tangentialArc(endAbsolute = [48.35, 85.53])
|
||||
|> tangentialArc(endAbsolute = [35.2, 67.73], tag = $seg03)
|
||||
|> angledLine(angle = tangentToEnd(seg03), length = 49.06)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(axis = Y, angle = 65, symmetric = true)
|
||||
|
||||
// Create a hole sketch with the size and location of each bolt hole
|
||||
holeSketch = startSketchOn(XZ)
|
||||
hole01 = circle(holeSketch, center = [0, 12.25], radius = boltSize / 2)
|
||||
|> extrude(length = -100)
|
||||
hole02 = circle(holeSketch, center = [0, 29.5], radius = boltSize / 2)
|
||||
|> extrude(length = -100)
|
||||
hole03 = circle(holeSketch, center = [0, 46.25], radius = boltSize / 2)
|
||||
|> extrude(length = -100)
|
||||
hole04 = circle(holeSketch, center = [0, 77], radius = boltSize / 2)
|
||||
|> extrude(length = -100)
|
||||
hole05 = circle(holeSketch, center = [0, 100], radius = boltSize / 2)
|
||||
|> extrude(length = -100)
|
||||
hole06 = circle(holeSketch, center = [0, 130], radius = boltSize / 2)
|
||||
|> extrude(length = -100)
|
||||
hole07 = circle(holeSketch, center = [-20, 130], radius = boltSize / 2)
|
||||
|> extrude(length = -100)
|
||||
hole08 = circle(holeSketch, center = [20, 130], radius = boltSize / 2)
|
||||
|> extrude(length = -100)
|
||||
|
||||
// Cut each guiding clearance hole from the bone plate
|
||||
solid001 = subtract([plateRevolve], tools = union([hole01, hole02]))
|
||||
solid002 = subtract([solid001], tools = union([hole03, hole04]))
|
||||
solid003 = subtract([solid002], tools = union([hole05, hole06]))
|
||||
solid004 = subtract([solid003], tools = union([hole07, hole08]))
|
67
public/kcl-samples/cold-plate/main.kcl
Normal file
@ -0,0 +1,67 @@
|
||||
// Cold Plate
|
||||
// A cold plate is a thermal management device used to remove heat from a device or component, typically by transferring heat to a liquid coolant that flows through the plate. It's a conductive cooling solution, commonly made of materials like aluminum or copper, with internal channels or tubes for the coolant
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Define parameters
|
||||
tubeDiameter = 5 / 8
|
||||
wallThickness = 0.080
|
||||
bendRadius = 1
|
||||
|
||||
// Create the cold plate with indentions to secure each pass of the brazed copper tube
|
||||
coldPlate = startSketchOn(YZ)
|
||||
|> startProfile(at = [0, tubeDiameter * 2])
|
||||
|> xLine(length = bendRadius - (tubeDiameter / 2))
|
||||
|> yLine(length = -tubeDiameter)
|
||||
|> tangentialArc(angle = 180, radius = tubeDiameter / 2)
|
||||
|> yLine(length = tubeDiameter)
|
||||
|> xLine(length = bendRadius * 2 - tubeDiameter, tag = $seg07)
|
||||
|> yLine(length = -tubeDiameter, tag = $seg09)
|
||||
|> tangentialArc(angle = 180, radius = tubeDiameter / 2)
|
||||
|> yLine(length = tubeDiameter, tag = $seg08)
|
||||
|> xLine(length = bendRadius - (tubeDiameter / 2))
|
||||
|> angledLine(angle = -77, length = tubeDiameter / 3)
|
||||
|> tangentialArc(angle = 77, radius = tubeDiameter, tag = $seg01)
|
||||
|> angledLine(angle = tangentToEnd(seg01), length = 1)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> xLine(endAbsolute = 0)
|
||||
|> mirror2d(axis = Y)
|
||||
|> close()
|
||||
|> extrude(length = 10, symmetric = true)
|
||||
|
||||
// Sketch the path for the copper tube to follow
|
||||
copperTubePath = startSketchOn(offsetPlane(XY, offset = tubeDiameter))
|
||||
|> startProfile(at = [-7.35, -bendRadius * 3])
|
||||
|> xLine(length = 14.13, tag = $seg05)
|
||||
|> tangentialArc(angle = 180, radius = bendRadius, tag = $seg02)
|
||||
|> angledLine(angle = tangentToEnd(seg02), length = 13.02, tag = $seg06)
|
||||
|> tangentialArc(angle = -180, radius = bendRadius, tag = $seg03)
|
||||
|> angledLine(angle = tangentToEnd(seg03), length = segLen(seg06))
|
||||
|> tangentialArc(angle = 180, radius = bendRadius, tag = $seg04)
|
||||
|> angledLine(angle = tangentToEnd(seg04), length = segLen(seg05))
|
||||
|
||||
// Create the profile for the inner and outer diameter of the hollow copper tube
|
||||
tubeWall = startSketchOn(offsetPlane(YZ, offset = -7.35))
|
||||
|> circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2)
|
||||
|> subtract2d(%, tool = circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2 - wallThickness))
|
||||
|> sweep(path = copperTubePath)
|
||||
|> appearance(color = "#b81b0a")
|
||||
|
||||
// Model a brazed cap to cover each tube. Constrain the caps using the walls of the plate
|
||||
brazedCap = startSketchOn(YZ)
|
||||
|> startProfile(at = segEnd(seg07))
|
||||
|> arc(interiorAbsolute = [bendRadius * 3, tubeDiameter * 1.85], endAbsolute = segEnd(seg08))
|
||||
|> yLine(endAbsolute = segStartY(seg08))
|
||||
|> arc(
|
||||
interiorAbsolute = [
|
||||
bendRadius * 3,
|
||||
segEndY(seg09) + tubeDiameter / 2
|
||||
],
|
||||
endAbsolute = segEnd(seg09),
|
||||
)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = 10, symmetric = true)
|
||||
|> patternLinear3d(instances = 4, distance = bendRadius * 2, axis = [0, -1, 0])
|
||||
|> appearance(color = "#6b261e")
|
120
public/kcl-samples/counterdrilled-weldment/main.kcl
Normal file
@ -0,0 +1,120 @@
|
||||
// Counterdrilled Weldment
|
||||
// A metal weldment consisting of a counterdrilled plate, a centrally mounted housing tube, and four structural support fins.
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Define parameters
|
||||
boltSpacingX = 5
|
||||
boltSpacingY = 3
|
||||
boltDiameter = 1 / 4
|
||||
counterdrillDiameter = 7 / 16
|
||||
counterdrillDepth = 3 / 16
|
||||
tubeInnerDiameter = 1 + 1 / 4
|
||||
tubeThickness = 0.115
|
||||
tubeHeight = 2
|
||||
stockThickness = .5
|
||||
|
||||
// Calculate the dimensions of the block using the specified bolt spacing. The size of the block can be defined by adding a multiple of the counterdrill diameter to the bolt spacing
|
||||
blockLength = boltSpacingX + boltDiameter * 6
|
||||
blockWidth = boltSpacingY + boltDiameter * 6
|
||||
|
||||
// Draw the base plate
|
||||
plateSketch = startSketchOn(XY)
|
||||
|> startProfile(at = [-blockLength / 2, -blockWidth / 2])
|
||||
|> angledLine(angle = 0, length = blockLength, tag = $rectangleSegmentA001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = blockWidth, tag = $rectangleSegmentB001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|
||||
|> close()
|
||||
|> subtract2d(tool = circle(center = [0, 0], radius = tubeInnerDiameter / 2))
|
||||
plateBody = extrude(plateSketch, length = stockThickness)
|
||||
|> chamfer(
|
||||
length = boltDiameter * 2,
|
||||
tags = [
|
||||
getNextAdjacentEdge(rectangleSegmentB001),
|
||||
getNextAdjacentEdge(rectangleSegmentA001),
|
||||
getNextAdjacentEdge(rectangleSegmentC001),
|
||||
getNextAdjacentEdge(rectangleSegmentD001)
|
||||
],
|
||||
)
|
||||
|
||||
// Define hole positions
|
||||
holePositions = [
|
||||
[-boltSpacingX / 2, -boltSpacingY / 2],
|
||||
[-boltSpacingX / 2, boltSpacingY / 2],
|
||||
[boltSpacingX / 2, -boltSpacingY / 2],
|
||||
[boltSpacingX / 2, boltSpacingY / 2]
|
||||
]
|
||||
|
||||
// Function to create a counterdrilled hole
|
||||
fn counterdrill(@holePosition) {
|
||||
cbdrill = startSketchOn(plateBody, face = END)
|
||||
|> circle(center = holePosition, radius = counterdrillDiameter / 2)
|
||||
|> extrude(length = -counterdrillDepth)
|
||||
cbBolt = startSketchOn(cbdrill, face = START)
|
||||
|> circle(center = holePosition, radius = boltDiameter / 2, tag = $hole01)
|
||||
|> extrude(length = -stockThickness + counterdrillDepth)
|
||||
// Use a chamfer to create a 90-degree counterdrill edge
|
||||
|> chamfer(length = (counterdrillDiameter - boltDiameter) / 2 * sqrt(2), tags = [hole01])
|
||||
return { }
|
||||
}
|
||||
|
||||
// Place a counterdrilled hole at each bolt hole position
|
||||
map(holePositions, f = counterdrill)
|
||||
|
||||
// Drill a small pin hole in the side of the tube
|
||||
pinhole = startSketchOn(YZ)
|
||||
|> circle(center = [0, 2.2], radius = 0.125)
|
||||
|> extrude(length = -10)
|
||||
|
||||
// Model the central tube and subtract the pin hole
|
||||
centralTube = startSketchOn(offsetPlane(XY, offset = stockThickness))
|
||||
|> circle(center = [0, 0], radius = tubeInnerDiameter / 2 + tubeThickness)
|
||||
|> subtract2d(tool = circle(center = [0, 0], radius = tubeInnerDiameter / 2))
|
||||
|> extrude(length = tubeHeight)
|
||||
|> subtract(tools = [pinhole])
|
||||
|
||||
// Create a function to create a fin which spans from the central tube to the bolt hole
|
||||
fn fin(@i) {
|
||||
diagPlane = {
|
||||
origin = [0.0, 0.0, 0.0],
|
||||
xAxis = [
|
||||
boltSpacingX / 2 * i,
|
||||
boltSpacingY / 2,
|
||||
0.0
|
||||
],
|
||||
yAxis = [0.0, 0.0, 1.0]
|
||||
}
|
||||
|
||||
finSketch = startSketchOn(diagPlane)
|
||||
|> startProfile(at = [
|
||||
tubeInnerDiameter / 2 + tubeThickness,
|
||||
stockThickness
|
||||
])
|
||||
|> xLine(endAbsolute = sqrt((boltSpacingX / 2) ^ 2 + (boltSpacingY / 2) ^ 2) - counterdrillDiameter)
|
||||
|> yLine(length = 0.15)
|
||||
|> line(endAbsolute = [
|
||||
profileStartX(%) + 0.15,
|
||||
stockThickness + tubeHeight * .8
|
||||
])
|
||||
|> xLine(length = -0.15)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = tubeThickness, symmetric = true)
|
||||
|
||||
// Use a circular pattern to create an identical fin on the opposite side
|
||||
otherFin = patternCircular3d(
|
||||
finSketch,
|
||||
instances = 2,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
return { }
|
||||
}
|
||||
|
||||
// Place a pair of support fins along each diagonal axis of the bolt pattern
|
||||
fin(1)
|
||||
fin(-1)
|
50
public/kcl-samples/countersunk-plate/main.kcl
Normal file
@ -0,0 +1,50 @@
|
||||
// Plate with countersunk holes
|
||||
// A small mounting plate with a countersunk hole at each end
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Define parameters
|
||||
boltSpacing = 5
|
||||
boltDiameter = 1 / 4
|
||||
centerHoleDiameter = 1 + 3 / 4
|
||||
plateThickness = 0.375
|
||||
|
||||
// Check that the plate is thick enough to countersink a hole
|
||||
// assertGreaterThan(plateThickness, boltDiameter, "This plate is not thick enough for the necessary countersink dimensions")
|
||||
|
||||
// A bit of math to calculate the tangent line between the two diameters
|
||||
r1 = centerHoleDiameter / 2 * 1.5 + .35
|
||||
r2 = boltDiameter * 2 + .25
|
||||
d = boltSpacing / 2
|
||||
tangentAngle = asin((r1 - r2) / d)
|
||||
tangentLength = (r1 - r2) / tan(tangentAngle)
|
||||
|
||||
plateBody = startSketchOn(XY)
|
||||
// Use polar coordinates to start the sketch at the tangent point of the larger radius
|
||||
|> startProfile(at = polar(angle = 90 - tangentAngle, length = r1))
|
||||
|> angledLine(angle = -tangentAngle, length = tangentLength)
|
||||
|> tangentialArc(radius = r2, angle = (tangentAngle - 90) * 2)
|
||||
|> angledLine(angle = tangentAngle, length = -tangentLength)
|
||||
|> tangentialArc(radius = r1, angle = -tangentAngle * 2)
|
||||
|> angledLine(angle = -tangentAngle, length = -tangentLength)
|
||||
|> tangentialArc(radius = r2, angle = (tangentAngle - 90) * 2)
|
||||
|> angledLine(angle = tangentAngle, length = tangentLength)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> subtract2d(tool = circle(center = [0, 0], radius = centerHoleDiameter / 2 * 1.5))
|
||||
|> extrude(%, length = plateThickness)
|
||||
|
||||
// Function to create a countersunk hole
|
||||
fn countersink(@holePosition) {
|
||||
startSketchOn(plateBody, face = END)
|
||||
|> circle(center = [holePosition, 0], radius = boltDiameter / 2, tag = $hole01)
|
||||
|> extrude(length = -plateThickness)
|
||||
// Use a chamfer to create a 90-degree countersink
|
||||
|> chamfer(length = boltDiameter, tags = [hole01])
|
||||
return { }
|
||||
}
|
||||
|
||||
// Place a countersunk hole at each bolt hole position
|
||||
countersink(-boltSpacing / 2)
|
||||
countersink(boltSpacing / 2)
|
153
public/kcl-samples/cpu-cooler/fan-housing.kcl
Normal file
@ -0,0 +1,153 @@
|
||||
// Fan Housing
|
||||
// The plastic housing that contains the fan and the motor
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Import parameters
|
||||
import * from "parameters.kcl"
|
||||
|
||||
// Model the housing which holds the motor, the fan, and the mounting provisions
|
||||
// Bottom mounting face
|
||||
bottomFaceSketch = startSketchOn(YZ)
|
||||
|> startProfile(at = [-fanSize / 2, -fanSize / 2])
|
||||
|> angledLine(angle = 0, length = fanSize, tag = $rectangleSegmentA001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = fanSize, tag = $rectangleSegmentB001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|
||||
|> close()
|
||||
|> subtract2d(tool = circle(center = [0, 0], radius = 4))
|
||||
|> subtract2d(tool = circle(
|
||||
center = [
|
||||
mountingHoleSpacing / 2,
|
||||
mountingHoleSpacing / 2
|
||||
],
|
||||
radius = mountingHoleSize / 2,
|
||||
))
|
||||
|> subtract2d(tool = circle(
|
||||
center = [
|
||||
-mountingHoleSpacing / 2,
|
||||
mountingHoleSpacing / 2
|
||||
],
|
||||
radius = mountingHoleSize / 2,
|
||||
))
|
||||
|> subtract2d(tool = circle(
|
||||
center = [
|
||||
mountingHoleSpacing / 2,
|
||||
-mountingHoleSpacing / 2
|
||||
],
|
||||
radius = mountingHoleSize / 2,
|
||||
))
|
||||
|> subtract2d(tool = circle(
|
||||
center = [
|
||||
-mountingHoleSpacing / 2,
|
||||
-mountingHoleSpacing / 2
|
||||
],
|
||||
radius = mountingHoleSize / 2,
|
||||
))
|
||||
|> extrude(length = 4)
|
||||
|
||||
// Add large openings to the bottom face to allow airflow through the fan
|
||||
airflowPattern = startSketchOn(bottomFaceSketch, face = END)
|
||||
|> startProfile(at = [fanSize * 7 / 25, -fanSize * 9 / 25])
|
||||
|> angledLine(angle = 140, length = fanSize * 12 / 25, tag = $seg01)
|
||||
|> tangentialArc(radius = fanSize * 1 / 50, angle = 90)
|
||||
|> angledLine(angle = -130, length = fanSize * 8 / 25)
|
||||
|> tangentialArc(radius = fanSize * 1 / 50, angle = 90)
|
||||
|> angledLine(angle = segAng(seg01) + 180, length = fanSize * 2 / 25)
|
||||
|> tangentialArc(radius = fanSize * 8 / 25, angle = 40)
|
||||
|> xLine(length = fanSize * 3 / 25)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternCircular2d(
|
||||
instances = 4,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> extrude(length = -4)
|
||||
|
||||
// Create the middle segment of the fan housing body
|
||||
housingMiddleLength = fanSize / 3
|
||||
housingMiddleRadius = fanSize / 3 - 1
|
||||
bodyMiddle = startSketchOn(bottomFaceSketch, face = END)
|
||||
|> startProfile(at = [
|
||||
housingMiddleLength / 2,
|
||||
-housingMiddleLength / 2 - housingMiddleRadius
|
||||
])
|
||||
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|
||||
|> yLine(length = housingMiddleLength)
|
||||
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|
||||
|> xLine(length = -housingMiddleLength)
|
||||
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|
||||
|> yLine(length = -housingMiddleLength)
|
||||
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> extrude(length = fanHeight - 4 - 4)
|
||||
|
||||
// Cut a hole in the body to accommodate the fan
|
||||
bodyFanHole = startSketchOn(bodyMiddle, face = END)
|
||||
|> circle(center = [0, 0], radius = fanSize * 23 / 50)
|
||||
|> extrude(length = -(fanHeight - 4 - 4))
|
||||
|
||||
// Top mounting face. Cut a hole in the face to accommodate the fan
|
||||
topFaceSketch = startSketchOn(bodyMiddle, face = END)
|
||||
topHoles = startProfile(topFaceSketch, at = [-fanSize / 2, -fanSize / 2])
|
||||
|> angledLine(angle = 0, length = fanSize, tag = $rectangleSegmentA002)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA002) + 90, length = fanSize, tag = $rectangleSegmentB002)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002), tag = $rectangleSegmentC002)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD002)
|
||||
|> close()
|
||||
|> subtract2d(tool = circle(center = [0, 0], radius = fanSize * 23 / 50))
|
||||
|> subtract2d(tool = circle(
|
||||
center = [
|
||||
mountingHoleSpacing / 2,
|
||||
mountingHoleSpacing / 2
|
||||
],
|
||||
radius = mountingHoleSize / 2,
|
||||
))
|
||||
|> subtract2d(tool = circle(
|
||||
center = [
|
||||
-mountingHoleSpacing / 2,
|
||||
mountingHoleSpacing / 2
|
||||
],
|
||||
radius = mountingHoleSize / 2,
|
||||
))
|
||||
|> subtract2d(tool = circle(
|
||||
center = [
|
||||
mountingHoleSpacing / 2,
|
||||
-mountingHoleSpacing / 2
|
||||
],
|
||||
radius = mountingHoleSize / 2,
|
||||
))
|
||||
|> subtract2d(tool = circle(
|
||||
center = [
|
||||
-mountingHoleSpacing / 2,
|
||||
-mountingHoleSpacing / 2
|
||||
],
|
||||
radius = mountingHoleSize / 2,
|
||||
))
|
||||
|> extrude(length = 4)
|
||||
|
||||
// Create a housing for the electric motor to sit
|
||||
motorHousing = startSketchOn(bottomFaceSketch, face = END)
|
||||
|> circle(center = [0, 0], radius = 11.2)
|
||||
|> extrude(length = 16)
|
||||
|
||||
startSketchOn(motorHousing, face = END)
|
||||
|> circle(center = [0, 0], radius = 10)
|
||||
|> extrude(length = -16)
|
||||
|> appearance(color = "#a55e2c")
|
||||
|> fillet(
|
||||
radius = abs(fanSize - mountingHoleSpacing) / 2,
|
||||
tags = [
|
||||
getNextAdjacentEdge(rectangleSegmentA001),
|
||||
getNextAdjacentEdge(rectangleSegmentB001),
|
||||
getNextAdjacentEdge(rectangleSegmentC001),
|
||||
getNextAdjacentEdge(rectangleSegmentD001),
|
||||
getNextAdjacentEdge(rectangleSegmentA002),
|
||||
getNextAdjacentEdge(rectangleSegmentB002),
|
||||
getNextAdjacentEdge(rectangleSegmentC002),
|
||||
getNextAdjacentEdge(rectangleSegmentD002)
|
||||
],
|
||||
)
|
71
public/kcl-samples/cpu-cooler/fan.kcl
Normal file
@ -0,0 +1,71 @@
|
||||
// Fan
|
||||
// Spinning axial fan that moves airflow
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Import parameters
|
||||
import * from "parameters.kcl"
|
||||
|
||||
// Model the center of the fan
|
||||
fanCenter = startSketchOn(YZ)
|
||||
|> circle(center = [0, 0], radius = fanHeight / 2, tag = $centerBend)
|
||||
|> extrude(%, length = fanHeight)
|
||||
|> fillet(radius = 1.5, tags = [getOppositeEdge(centerBend)])
|
||||
|
||||
// Create a function for a lofted fan blade cross section that rotates about the center hub of the fan
|
||||
fn fanBlade(offsetHeight, startAngle) {
|
||||
fanBlade = startSketchOn(offsetPlane(YZ, offset = offsetHeight))
|
||||
|> startProfile(at = [
|
||||
15 * cos(startAngle),
|
||||
15 * sin(startAngle)
|
||||
])
|
||||
|> arc(angleStart = startAngle, angleEnd = startAngle + 14, radius = 15)
|
||||
|> arc(
|
||||
endAbsolute = [
|
||||
fanSize * 22 / 50 * cos(startAngle - 20),
|
||||
fanSize * 22 / 50 * sin(startAngle - 20)
|
||||
],
|
||||
interiorAbsolute = [
|
||||
fanSize * 11 / 50 * cos(startAngle + 3),
|
||||
fanSize * 11 / 50 * sin(startAngle + 3)
|
||||
],
|
||||
)
|
||||
|> arc(
|
||||
endAbsolute = [
|
||||
fanSize * 22 / 50 * cos(startAngle - 24),
|
||||
fanSize * 22 / 50 * sin(startAngle - 24)
|
||||
],
|
||||
interiorAbsolute = [
|
||||
fanSize * 22 / 50 * cos(startAngle - 22),
|
||||
fanSize * 22 / 50 * sin(startAngle - 22)
|
||||
],
|
||||
)
|
||||
|> arc(
|
||||
endAbsolute = [profileStartX(%), profileStartY(%)],
|
||||
interiorAbsolute = [
|
||||
fanSize * 11 / 50 * cos(startAngle - 5),
|
||||
fanSize * 11 / 50 * sin(startAngle - 5)
|
||||
],
|
||||
)
|
||||
|> close()
|
||||
return fanBlade
|
||||
}
|
||||
|
||||
// Loft the fan blade cross sections into a single blade, then pattern them about the fan center
|
||||
crossSections = [
|
||||
fanBlade(offsetHeight = 4.5, startAngle = 50),
|
||||
fanBlade(offsetHeight = (fanHeight - 2 - 4) / 2, startAngle = 30),
|
||||
fanBlade(offsetHeight = fanHeight - 2, startAngle = 0)
|
||||
]
|
||||
bladeLoft = loft(crossSections)
|
||||
|> patternCircular3d(
|
||||
instances = 9,
|
||||
axis = [1, 0, 0],
|
||||
center = [0, 0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
[fanCenter, bladeLoft]
|
||||
|> appearance(color = "#110803")
|
171
public/kcl-samples/cpu-cooler/heat-sink.kcl
Normal file
@ -0,0 +1,171 @@
|
||||
// Heat Sink
|
||||
// Conductive metal device made from brazed tubes and fins
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Import parameters
|
||||
import * from "parameters.kcl"
|
||||
|
||||
// Draw the sweep path for the outermost tubes
|
||||
endTubePath = startSketchOn(offsetPlane(YZ, offset = -20))
|
||||
|> startProfile(at = [fanSize / 4, fanSize + 38])
|
||||
|> yLine(endAbsolute = bendRadius + 10, tag = $seg01)
|
||||
|> tangentialArc(radius = bendRadius, angle = -90)
|
||||
|> xLine(endAbsolute = 0, tag = $seg02)
|
||||
|> xLine(length = -segLen(seg02))
|
||||
|> tangentialArc(radius = bendRadius, angle = -90)
|
||||
|> yLine(length = segLen(seg01))
|
||||
|
||||
// Sweep and translate the outermost tube on each end
|
||||
endTube = startSketchOn(offsetPlane(XY, offset = fanSize + 38))
|
||||
|> circle(center = [-20, fanSize / 4], radius = 3)
|
||||
|> subtract2d(tool = circle(center = [-20, fanSize / 4], radius = 2.5))
|
||||
|> sweep(path = endTubePath)
|
||||
|> patternCircular3d(
|
||||
%,
|
||||
instances = 2,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = false,
|
||||
)
|
||||
|
||||
// Draw the sweep path for the 4 interior tubes
|
||||
centerTubePath = startSketchOn(offsetPlane(YZ, offset = -4))
|
||||
|> startProfile(at = [fanSize / 2.67, fanSize + 38])
|
||||
|> yLine(endAbsolute = bendRadius + 15 + 10)
|
||||
|> tangentialArc(radius = bendRadius, angle = -45)
|
||||
|> angledLine(angle = -135, lengthY = 15)
|
||||
|> tangentialArc(radius = bendRadius, angle = -45)
|
||||
|> xLine(endAbsolute = 0, tag = $seg03)
|
||||
|> xLine(length = -segLen(seg03))
|
||||
|> tangentialArc(radius = bendRadius, angle = -155)
|
||||
|> tangentialArc(radius = bendRadius, angle = 65)
|
||||
|> yLine(endAbsolute = fanSize + 38)
|
||||
|
||||
// Draw the profile and sweep the 4 interior tubes
|
||||
centerTube = startSketchOn(offsetPlane(XY, offset = fanSize + 38))
|
||||
|> circle(center = [-4, fanSize / 2.67], radius = 3)
|
||||
|> subtract2d(tool = circle(center = [-4, fanSize / 2.67], radius = 2.5))
|
||||
|> sweep(path = centerTubePath)
|
||||
|> patternCircular3d(
|
||||
%,
|
||||
instances = 2,
|
||||
axis = [0, 0, 1],
|
||||
center = [-4 * 2, 0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> patternLinear3d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = 4 * 4,
|
||||
axis = [1, 0, 0],
|
||||
)
|
||||
|
||||
// Draw a heat fin with built-in clips to secure the mounting wire. Pattern the fin upwards by the height of the fan
|
||||
heatFins = startSketchOn(offsetPlane(XY, offset = 45))
|
||||
|> startProfile(at = [0, -fanSize / 2])
|
||||
|> xLine(length = 9)
|
||||
|> angledLine(angle = -60, length = 2.5, tag = $seg04)
|
||||
|> xLine(length = 0.75)
|
||||
|> arc(interiorAbsolute = [lastSegX(%) + 1, lastSegY(%) + 1.2], endAbsolute = [lastSegX(%) + 2, lastSegY(%)])
|
||||
|> xLine(length = 0.75)
|
||||
|> angledLine(angle = 60, length = segLen(seg04))
|
||||
|> xLine(endAbsolute = heatSinkDepth / 2 - 3)
|
||||
|> tangentialArc(angle = 90, radius = 3)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> mirror2d(axis = X)
|
||||
|> mirror2d(axis = Y)
|
||||
|> close()
|
||||
|> extrude(length = 1)
|
||||
|> patternLinear3d(
|
||||
%,
|
||||
instances = 31,
|
||||
distance = (fanSize - 10) / 30,
|
||||
axis = [0, 0, 1],
|
||||
)
|
||||
|
||||
// Create the mounting base for the CPU cooler. The base should consist of two pieces that secure around each of the tubes at the bottom
|
||||
coolerBase = startSketchOn(-XZ)
|
||||
baseLower = startProfile(coolerBase, at = [0, 10])
|
||||
|> xLine(length = -0.9)
|
||||
|> arc(angleStart = 0, angleEnd = -180, radius = 3.1)
|
||||
|> xLine(length = -1.8)
|
||||
|> arc(angleStart = 0, angleEnd = -180, radius = 3)
|
||||
|> xLine(length = -1.8)
|
||||
|> arc(angleStart = 0, angleEnd = -180, radius = 3)
|
||||
|> xLine(length = -1.8)
|
||||
|> xLine(length = -2)
|
||||
|> yLine(length = -10)
|
||||
|> xLine(endAbsolute = 0)
|
||||
|> mirror2d(axis = Y)
|
||||
|> extrude(length = 2 * segLen(seg02), symmetric = true)
|
||||
|
||||
baseUpper = startProfile(coolerBase, at = [0, 10])
|
||||
|> xLine(length = -0.9)
|
||||
|> arc(angleStart = 0, angleEnd = 180, radius = 3.1)
|
||||
|> xLine(length = -1.8)
|
||||
|> arc(angleStart = 0, angleEnd = 180, radius = 3)
|
||||
|> xLine(length = -1.8)
|
||||
|> arc(angleStart = 0, angleEnd = 180, radius = 3)
|
||||
|> xLine(length = -1.8)
|
||||
|> xLine(length = -1)
|
||||
|> yLine(length = 4)
|
||||
|> tangentialArc(angle = -90, radius = 2)
|
||||
|> xLine(endAbsolute = 0)
|
||||
|> mirror2d(axis = Y)
|
||||
|> extrude(length = 2 * segLen(seg02) * 3 / 4, symmetric = true)
|
||||
|
||||
// Create a flexible mounting bracket to secure the heat sink to the motherboard once adhered
|
||||
mountingBracket = startSketchOn(XZ)
|
||||
|> startProfile(at = [-10, 16])
|
||||
|> xLine(length = -20)
|
||||
|> tangentialArc(angle = 20, radius = bendRadius)
|
||||
|> angledLine(angle = -160, length = 14, tag = $seg09)
|
||||
|> tangentialArc(angle = -30, radius = bendRadius + sheetThickness)
|
||||
|> angledLine(angle = 170, length = 21.5, tag = $seg04Q)
|
||||
|> angledLine(angle = 170 - 90, length = sheetThickness, tag = $seg08)
|
||||
|> angledLine(angle = segAng(seg04Q) + 180, length = segLen(seg04Q), tag = $seg05E)
|
||||
|> tangentialArc(angle = 30, radius = bendRadius)
|
||||
|> angledLine(angle = segAng(seg09) + 180, length = segLen(seg09))
|
||||
|> tangentialArc(angle = -20, radius = bendRadius + sheetThickness)
|
||||
|> xLine(endAbsolute = profileStartX(%))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg07)
|
||||
|> close()
|
||||
|> extrude(
|
||||
length = 16,
|
||||
symmetric = true,
|
||||
tagEnd = $capEnd001,
|
||||
tagStart = $capStart001,
|
||||
)
|
||||
|> fillet(
|
||||
radius = 2,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg07, capEnd001]),
|
||||
getCommonEdge(faces = [seg08, capEnd001]),
|
||||
getCommonEdge(faces = [seg08, capStart001]),
|
||||
getCommonEdge(faces = [seg07, capStart001])
|
||||
],
|
||||
)
|
||||
|
||||
// Create a clearance hole in the bracket for an M3 screw and countersink the hole
|
||||
thruHole = startSketchOn(mountingBracket, face = seg05E)
|
||||
|> circle(center = [70, 0], radius = 3.4 / 2, tag = $seg06E)
|
||||
|> extrude(length = -sheetThickness)
|
||||
|> chamfer(
|
||||
length = sheetThickness * 0.75,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg05E, seg06E])
|
||||
],
|
||||
)
|
||||
|
||||
// Duplicate the bracket to the other side of the heat sink base
|
||||
|> patternCircular3d(
|
||||
instances = 2,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
51
public/kcl-samples/cpu-cooler/main.kcl
Normal file
@ -0,0 +1,51 @@
|
||||
// CPU Cooler
|
||||
// A CPU cooler is a device designed to dissipate heat generated by the CPU of a computer. They consist of a brazed heat sink made from aluminum or copper alloys, and one or two axial fans to move airflow across the heat sink.
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Import all parts and parameters into assembly file
|
||||
import * from "parameters.kcl"
|
||||
import "fan-housing.kcl" as fanHousing
|
||||
import "motor.kcl" as motor
|
||||
import "fan.kcl" as fan
|
||||
import "heat-sink.kcl" as heatSink
|
||||
import "mounting-wire.kcl" as mountingWire
|
||||
import "removable-sticker.kcl" as removableSticker
|
||||
|
||||
// Produce the model for each imported part
|
||||
heatSink
|
||||
|
||||
fn translatePart(part) {
|
||||
part
|
||||
|> translate(x = heatSinkDepth / 2, z = 40 + fanSize / 2)
|
||||
|> patternLinear3d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = heatSinkDepth + fanHeight,
|
||||
axis = [-1, 0, 0],
|
||||
)
|
||||
return { }
|
||||
}
|
||||
translatePart(part = fanHousing)
|
||||
translatePart(part = motor)
|
||||
translatePart(part = fan)
|
||||
|
||||
mountingWire
|
||||
|> patternCircular3d(
|
||||
%,
|
||||
instances = 2,
|
||||
axis = [0, 1, 0],
|
||||
center = [0, 0, 40 + fanSize / 2],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> patternCircular3d(
|
||||
%,
|
||||
instances = 2,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
removableSticker
|
21
public/kcl-samples/cpu-cooler/motor.kcl
Normal file
@ -0,0 +1,21 @@
|
||||
// Motor
|
||||
// A small electric motor to power the fan
|
||||
|
||||
// Set Units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Import Parameters
|
||||
import * from "parameters.kcl"
|
||||
|
||||
// Model the motor body and stem
|
||||
startPlane = offsetPlane(YZ, offset = 4)
|
||||
motorBody = startSketchOn(startPlane)
|
||||
|> circle(center = [0, 0], radius = 10, tag = $seg04)
|
||||
|> extrude(length = 17)
|
||||
|> fillet(radius = 2, tags = [getOppositeEdge(seg04), seg04])
|
||||
|> appearance(color = "#021b55")
|
||||
motorStem = startSketchOn(offsetPlane(YZ, offset = 21))
|
||||
|> circle(center = [0, 0], radius = 1)
|
||||
|> extrude(length = 3.8)
|
||||
|> appearance(color = "#cbcccd")
|
||||
[motorBody, motorStem]
|
79
public/kcl-samples/cpu-cooler/mounting-wire.kcl
Normal file
@ -0,0 +1,79 @@
|
||||
// Mounting Wire
|
||||
// The flexible metal wire used to clip the fans onto the heat sink
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Import parameters
|
||||
import * from "parameters.kcl"
|
||||
|
||||
// Draw the XZ component of the mounting wire path
|
||||
upperArm = startSketchOn(offsetPlane(XZ, offset = fanSize / 2 + 2))
|
||||
|> startProfile(at = [-12, 40 + fanSize / 2])
|
||||
|> yLine(length = 7)
|
||||
|> tangentialArc(radius = 2, angle = 90)
|
||||
|> xLine(length = -9)
|
||||
|> tangentialArc(radius = 2, angle = -90)
|
||||
|> yLine(length = 14)
|
||||
|> tangentialArc(radius = 2, angle = 90)
|
||||
|> xLine(length = -9)
|
||||
|> tangentialArc(radius = 2, angle = -80)
|
||||
|> angledLine(angle = 100, endAbsoluteY = 40 + fanSize / 2 + mountingHoleSpacing / 2 - 1.5)
|
||||
|> tangentialArc(radius = 2, angle = 80, tag = $seg07)
|
||||
|
||||
// Draw the XZ component of the mounting wire path
|
||||
lowerArm = startSketchOn(offsetPlane(XZ, offset = fanSize / 2 + 2))
|
||||
|> startProfile(at = [-12, 40 + fanSize / 2])
|
||||
|> yLine(length = -7)
|
||||
|> tangentialArc(radius = 2, angle = -90)
|
||||
|> xLine(length = -9)
|
||||
|> tangentialArc(radius = 2, angle = 90)
|
||||
|> yLine(length = -14)
|
||||
|> tangentialArc(radius = 2, angle = -90)
|
||||
|> xLine(length = -9)
|
||||
|> tangentialArc(radius = 2, angle = 80)
|
||||
|> angledLine(angle = -100, endAbsoluteY = 40 + fanSize / 2 - (mountingHoleSpacing / 2) + 1.5)
|
||||
|> tangentialArc(radius = 2, angle = -80, tag = $seg08)
|
||||
|
||||
// Create the profile of the mounting wire and sweep along the XZ path
|
||||
wireProfile = startSketchOn(offsetPlane(XY, offset = 40 + fanSize / 2))
|
||||
sweepUpperArm = circle(wireProfile, center = [-12, -fanSize / 2 - 2], radius = 1)
|
||||
|> sweep(%, path = upperArm)
|
||||
|
||||
sweepLowerArm = circle(wireProfile, center = [-12, -fanSize / 2 - 2], radius = 1)
|
||||
|> sweep(%, path = lowerArm)
|
||||
|
||||
// Draw the XY components of the mounting wire path
|
||||
upperHook = startSketchOn(offsetPlane(XY, offset = segEndY(seg07)))
|
||||
|> startProfile(at = [segEndX(seg07), -fanSize / 2 - 2])
|
||||
|> xLine(endAbsolute = -heatSinkDepth / 2 - fanHeight)
|
||||
|> tangentialArc(radius = 2, angle = -90)
|
||||
|> yLine(endAbsolute = -mountingHoleSpacing / 2 - 2)
|
||||
|> tangentialArc(radius = 2, angle = -90)
|
||||
|> xLine(length = fanHeight / 3)
|
||||
|
||||
// Draw the XY components of the mounting wire path
|
||||
lowerHook = startSketchOn(offsetPlane(XY, offset = segEndY(seg08)))
|
||||
|> startProfile(at = [segEndX(seg07), -fanSize / 2 - 2])
|
||||
|> xLine(endAbsolute = -heatSinkDepth / 2 - fanHeight)
|
||||
|> tangentialArc(radius = 2, angle = -90)
|
||||
|> yLine(endAbsolute = -mountingHoleSpacing / 2 - 2)
|
||||
|> tangentialArc(radius = 2, angle = -90)
|
||||
|> xLine(length = fanHeight / 3)
|
||||
|
||||
// Sweep the wire profile around the hook-shaped segments of the mounting wire
|
||||
hookProfile = startSketchOn(offsetPlane(YZ, offset = segEndX(seg07)))
|
||||
sweepUpperHook = circle(hookProfile, center = [-fanSize / 2 - 2, segEndY(seg07)], radius = 1)
|
||||
|> sweep(%, path = upperHook)
|
||||
|
||||
sweepLowerHook = circle(hookProfile, center = [-fanSize / 2 - 2, segEndY(seg08)], radius = 1)
|
||||
|> sweep(%, path = lowerHook)
|
||||
|
||||
// Union each piece of the wire into a single continuous sweep
|
||||
[
|
||||
sweepLowerArm,
|
||||
sweepLowerHook,
|
||||
sweepUpperArm,
|
||||
sweepUpperHook
|
||||
]
|
||||
|> appearance(color = "#0d0d0d")
|
13
public/kcl-samples/cpu-cooler/parameters.kcl
Normal file
@ -0,0 +1,13 @@
|
||||
// Global parameters for the CPU cooler
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Define Parameters
|
||||
export fanSize = 120
|
||||
export fanHeight = 25
|
||||
export mountingHoleSpacing = 105
|
||||
export mountingHoleSize = 4.5
|
||||
export bendRadius = 15
|
||||
export sheetThickness = 2.125
|
||||
export heatSinkDepth = 55
|
25
public/kcl-samples/cpu-cooler/removable-sticker.kcl
Normal file
@ -0,0 +1,25 @@
|
||||
// Removable Sticker
|
||||
// Protective sticker to be removed before adhering the heat sink to the CPU
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Create a simple body to represent the removable warning sticker. Brightly color the sticker so that the user will not forget to remove it before installing the device
|
||||
removableSticker = startSketchOn(-XY)
|
||||
|> startProfile(at = [-12, -12])
|
||||
|> angledLine(angle = 0, length = 24, tag = $rectangleSegmentA001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = 24, tag = $rectangleSegmentB001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|
||||
|> close()
|
||||
|> extrude(length = .3)
|
||||
|> appearance(color = "#021b55")
|
||||
|> chamfer(
|
||||
length = 3,
|
||||
tags = [
|
||||
getNextAdjacentEdge(rectangleSegmentA001),
|
||||
getNextAdjacentEdge(rectangleSegmentB001),
|
||||
getNextAdjacentEdge(rectangleSegmentC001),
|
||||
getNextAdjacentEdge(rectangleSegmentD001)
|
||||
],
|
||||
)
|
@ -15,7 +15,7 @@ holeDia = 4
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> startProfile(at = [0, 0])
|
||||
|> angledLine(angle = 0, length = width, tag = $rectangleSegmentA001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = length, tag = $rectangleSegmentB001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length, tag = $rectangleSegmentB001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|
||||
|> close()
|
||||
@ -74,7 +74,7 @@ function001([
|
||||
sketch003 = startSketchOn(XY)
|
||||
|> startProfile(at = [width * 1.2, 0])
|
||||
|> angledLine(angle = 0, length = width, tag = $rectangleSegmentA002)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = length, tag = $rectangleSegmentB002)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length, tag = $rectangleSegmentB002)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC002)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD002)
|
||||
|> close()
|
||||
|
@ -1,112 +0,0 @@
|
||||
// Spur Gear
|
||||
// A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear.
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in, kclVersion = 1.0)
|
||||
|
||||
// Define parameters
|
||||
nTeeth = 21
|
||||
module = 0.5
|
||||
pitchDiameter = module * nTeeth
|
||||
pressureAngle = 20
|
||||
addendum = module
|
||||
deddendum = 1.25 * module
|
||||
baseDiameter = pitchDiameter * cos(pressureAngle)
|
||||
tipDiameter = pitchDiameter + 2 * module
|
||||
gearHeight = 3
|
||||
|
||||
// Interpolate points along the involute curve
|
||||
cmo = 101
|
||||
rs = map(
|
||||
[0..cmo],
|
||||
f = fn(@i) {
|
||||
return baseDiameter / 2 + i / cmo * (tipDiameter - baseDiameter) / 2
|
||||
},
|
||||
)
|
||||
|
||||
// Calculate operating pressure angle
|
||||
angles = map(
|
||||
rs,
|
||||
f = fn(@r) {
|
||||
return units::toDegrees(acos(baseDiameter / 2 / r))
|
||||
},
|
||||
)
|
||||
|
||||
// Calculate the involute function
|
||||
invas = map(
|
||||
angles,
|
||||
f = fn(@a) {
|
||||
return tan(a) - units::toRadians(a)
|
||||
},
|
||||
)
|
||||
|
||||
// Map the involute curve
|
||||
xs = map(
|
||||
[0..cmo],
|
||||
f = fn(@i) {
|
||||
return rs[i] * cos(invas[i]: number(rad))
|
||||
},
|
||||
)
|
||||
|
||||
ys = map(
|
||||
[0..cmo],
|
||||
f = fn(@i) {
|
||||
return rs[i] * sin(invas[i]: number(rad))
|
||||
},
|
||||
)
|
||||
|
||||
// Extrude the gear body
|
||||
body = startSketchOn(XY)
|
||||
|> circle(center = [0, 0], radius = baseDiameter / 2)
|
||||
|> extrude(length = gearHeight)
|
||||
|
||||
toothAngle = 360 / nTeeth / 1.5
|
||||
|
||||
// Plot the involute curve
|
||||
fn leftInvolute(@i, accum) {
|
||||
j = 100 - i // iterate backwards
|
||||
return line(accum, endAbsolute = [xs[j], ys[j]])
|
||||
}
|
||||
|
||||
fn rightInvolute(@i, accum) {
|
||||
x = rs[i] * cos(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
|
||||
y = -rs[i] * sin(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
|
||||
return line(accum, endAbsolute = [x, y])
|
||||
}
|
||||
|
||||
// Draw gear teeth
|
||||
start = startSketchOn(XY)
|
||||
|> startProfile(at = [xs[101], ys[101]])
|
||||
teeth = reduce([0..100], initial = start, f = leftInvolute)
|
||||
|> arc(angleStart = 0, angleEnd = toothAngle, radius = baseDiameter / 2)
|
||||
|> reduce([1..101], initial = %, f = rightInvolute)
|
||||
|> close()
|
||||
|> extrude(length = gearHeight)
|
||||
|> patternCircular3d(
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
instances = nTeeth,
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
// Define the constants of the keyway and the bore hole
|
||||
keywayWidth = 0.250
|
||||
keywayDepth = keywayWidth / 2
|
||||
holeDiam = 2
|
||||
holeRadius = 1
|
||||
startAngle = asin(keywayWidth / 2 / holeRadius)
|
||||
|
||||
// Sketch the keyway and center hole and extrude
|
||||
keyWay = startSketchOn(body, face = END)
|
||||
|> startProfile(at = [
|
||||
holeRadius * cos(startAngle),
|
||||
holeRadius * sin(startAngle)
|
||||
])
|
||||
|> xLine(length = keywayDepth)
|
||||
|> yLine(length = -keywayWidth)
|
||||
|> xLine(length = -keywayDepth)
|
||||
|> arc(angleStart = -1 * units::toDegrees(startAngle) + 360, angleEnd = 180, radius = holeRadius)
|
||||
|> arc(angleStart = 180, angleEnd = units::toDegrees(startAngle), radius = holeRadius)
|
||||
|> close()
|
||||
|> extrude(length = -gearHeight)
|
118
public/kcl-samples/hammer/main.kcl
Normal file
@ -0,0 +1,118 @@
|
||||
// Claw Hammer
|
||||
// Often used in construction, a claw hammer is a levered metal hand tool that is used to strike and extract nails
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Sketch the side profile of the hammer head
|
||||
headSideProfile = startSketchOn(XZ)
|
||||
|> startProfile(at = [0.33, 11.26])
|
||||
|> yLine(length = 0.1)
|
||||
|> tangentialArc(endAbsolute = [0.95, 11.92])
|
||||
|> tangentialArc(endAbsolute = [2.72, 11.26], tag = $seg01)
|
||||
|> angledLine(angle = tangentToEnd(seg01) + 90, length = .2)
|
||||
|> angledLine(angle = tangentToEnd(seg01) - 10, length = -0.5)
|
||||
|> tangentialArc(endAbsolute = [-0.91, 12.78], tag = $seg03)
|
||||
|> tangentialArc(endAbsolute = [-1.67, 12.85])
|
||||
|> xLine(length = -.25)
|
||||
|> tangentialArc(angle = 90, radius = .05)
|
||||
|> yLine(length = -1.125, tag = $seg02)
|
||||
|> tangentialArc(angle = 90, radius = .05)
|
||||
|> xLine(length = .25, tag = $seg04)
|
||||
|> angledLine(angle = 23, length = 0.1)
|
||||
|> tangentialArc(endAbsolute = [-0.33, profileStartY(%)])
|
||||
|> xLine(endAbsolute = profileStartX(%))
|
||||
|> close()
|
||||
|> extrude(length = 3, symmetric = true)
|
||||
|
||||
// Sketch the top profile of the hammer head
|
||||
headTopProfile = startSketchOn(offsetPlane(XY, offset = 13))
|
||||
leftSideCut = startProfile(headTopProfile, at = [-4, -1.6])
|
||||
|> line(endAbsolute = [segEndX(seg02), -segLen(seg02) / 2])
|
||||
|> arc(
|
||||
%,
|
||||
angleStart = 180,
|
||||
angleEnd = 270,
|
||||
radius = .05,
|
||||
)
|
||||
|> xLine(endAbsolute = segEndX(seg04))
|
||||
|> arc(interiorAbsolute = [segEndX(seg03) - .1, lastSegY(%) + .03], endAbsolute = [segEndX(seg03), lastSegY(%)])
|
||||
|> tangentialArc(endAbsolute = [3.39, -1.15])
|
||||
|> yLine(endAbsolute = profileStartY(%))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = -14)
|
||||
|
||||
rearCut = startProfile(headTopProfile, at = [3.39, -0.56])
|
||||
|> angledLine(angle = 177, length = 0.1)
|
||||
|> tangentialArc(endAbsolute = [1.86, -0.37])
|
||||
|> tangentialArc(endAbsolute = [lastSegX(%), -lastSegY(%)])
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), -profileStartY(%)])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = -14)
|
||||
|
||||
rightSideCut = startProfile(headTopProfile, at = [-4, 1.6])
|
||||
|> line(endAbsolute = [segEndX(seg02), segLen(seg02) / 2])
|
||||
|> arc(
|
||||
%,
|
||||
angleStart = -180,
|
||||
angleEnd = -270,
|
||||
radius = .05,
|
||||
)
|
||||
|> xLine(endAbsolute = segEndX(seg04))
|
||||
|> arc(interiorAbsolute = [segEndX(seg03) - .1, lastSegY(%) - .03], endAbsolute = [segEndX(seg03), lastSegY(%)])
|
||||
|> tangentialArc(endAbsolute = [3.39, 1.15])
|
||||
|> yLine(endAbsolute = profileStartY(%))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = -14)
|
||||
|
||||
// Subtract the top profiles from the side profile to create a CSG hammer shape
|
||||
firstProfiles = subtract(
|
||||
[headSideProfile],
|
||||
tools = [
|
||||
union([
|
||||
leftSideCut,
|
||||
union([rearCut, rightSideCut])
|
||||
])
|
||||
],
|
||||
)
|
||||
|
||||
// Extrude a polygon through the center of the hammer head to create the mounting hole for the handle
|
||||
handleHole = startSketchOn(XY)
|
||||
|> polygon(
|
||||
%,
|
||||
radius = .28,
|
||||
numSides = 10,
|
||||
center = [0, 0],
|
||||
)
|
||||
|> extrude(length = 14)
|
||||
|
||||
// Add an additional fillet feature to support the handle, and union it to the rest of the head
|
||||
baseSupport = startSketchOn(offsetPlane(XY, offset = 11.5))
|
||||
|> circle(center = [0, 0], radius = .45, tag = $seg05)
|
||||
|> extrude(length = 1, tagStart = $capStart001)
|
||||
|> fillet(
|
||||
radius = .05,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg05, capStart001])
|
||||
],
|
||||
)
|
||||
|
||||
// Union all pieces into a single solid, then cut the handle hole
|
||||
hammerHead = union([firstProfiles, baseSupport])
|
||||
|> subtract(tools = [handleHole])
|
||||
|
||||
// Draw a profile for the handle, then revolve around the center axis
|
||||
handleSketch = startSketchOn(XZ)
|
||||
|> startProfile(at = [0.01, 0])
|
||||
|> xLine(length = 1.125 / 2)
|
||||
|> tangentialArc(angle = 90, radius = 0.05)
|
||||
|> tangentialArc(endAbsolute = [0.38, 12.8 / 1.612])
|
||||
|> tangentialArc(endAbsolute = [0.28, 12.8])
|
||||
|> xLine(endAbsolute = profileStartX(%))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
handle = revolve(handleSketch, angle = 360, axis = Y)
|
||||
|> appearance(color = "#f14f04")
|
99
public/kcl-samples/helical-gear/main.kcl
Normal file
@ -0,0 +1,99 @@
|
||||
// Helical Gear
|
||||
// A helical gear is a type of cylindrical gear where the teeth are slanted at an angle relative to the axis of rotation. This greatly reduces noise and wear when transmitting torque across meshed spinning gears
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Define a function to create a helical gear
|
||||
fn helicalGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
|
||||
// Calculate gear parameters
|
||||
pitchDiameter = module * nTeeth
|
||||
addendum = module
|
||||
deddendum = 1.25 * module
|
||||
baseDiameter = pitchDiameter * cos(pressureAngle)
|
||||
tipDiameter = pitchDiameter + 2 * module
|
||||
|
||||
// Define the constants of the keyway and the bore hole
|
||||
keywayWidth = 2
|
||||
keywayDepth = keywayWidth / 2
|
||||
holeDiam = 7
|
||||
holeRadius = holeDiam / 2
|
||||
startAngle = asin(keywayWidth / 2 / holeRadius)
|
||||
|
||||
// Sketch the keyway and center hole
|
||||
holeWithKeyway = startSketchOn(XY)
|
||||
|> startProfile(at = [
|
||||
holeRadius * cos(startAngle),
|
||||
holeRadius * sin(startAngle)
|
||||
])
|
||||
|> xLine(length = keywayDepth)
|
||||
|> yLine(length = -keywayWidth)
|
||||
|> xLine(length = -keywayDepth)
|
||||
|> arc(angleStart = -1 * startAngle + 360, angleEnd = 180, radius = holeRadius)
|
||||
|> arc(angleStart = 180, angleEnd = startAngle, radius = holeRadius)
|
||||
|> close()
|
||||
|
||||
// Define a function to create a rotated gear sketch on an offset plane
|
||||
fn helicalGearSketch(offsetHeight) {
|
||||
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
|
||||
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
|
||||
|
||||
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
|
||||
helicalGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = helixCalc,
|
||||
tag = $seg01,
|
||||
)
|
||||
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
|
||||
reverse = true,
|
||||
)
|
||||
|
||||
// Position the end line of the sketch at the start of the next tooth
|
||||
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
|
||||
|
||||
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|
||||
|> patternCircular2d(
|
||||
%,
|
||||
instances = nTeeth,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> close()
|
||||
|> subtract2d(tool = holeWithKeyway)
|
||||
return helicalGearSketch
|
||||
}
|
||||
|
||||
// Draw a gear sketch on the base plane
|
||||
gearSketch001 = helicalGearSketch(offsetHeight = 0)
|
||||
|
||||
// Draw a rotated gear sketch on a middle interstitial plane
|
||||
gearSketch002 = helicalGearSketch(offsetHeight = gearHeight / 2)
|
||||
|
||||
// Draw a rotated gear sketch at the gear height offset plane
|
||||
gearSketch003 = helicalGearSketch(offsetHeight = gearHeight)
|
||||
|
||||
// Loft each rotated gear sketch together to form a helical gear
|
||||
helicalGear = loft([
|
||||
gearSketch001,
|
||||
gearSketch002,
|
||||
gearSketch003
|
||||
])
|
||||
|
||||
return helicalGear
|
||||
}
|
||||
|
||||
helicalGear(
|
||||
nTeeth = 21,
|
||||
module = 2,
|
||||
pressureAngle = 20,
|
||||
helixAngle = 35,
|
||||
gearHeight = 7,
|
||||
)
|
197
public/kcl-samples/helical-planetary-gearset/main.kcl
Normal file
@ -0,0 +1,197 @@
|
||||
// Helical Planetary Gearset
|
||||
// A helical planetary gearset is a type of planetary gear system where the teeth of the sun gear, planet gears, and/or ring gear are helical rather than straight. This design allows for smoother, quieter operation, greater load-carrying capacity, and more flexible shaft alignment.
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Define a function to create a helical gear
|
||||
fn helicalGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
|
||||
// Calculate gear parameters
|
||||
pitchDiameter = module * nTeeth
|
||||
addendum = module
|
||||
deddendum = 1.25 * module
|
||||
baseDiameter = pitchDiameter * cos(pressureAngle)
|
||||
tipDiameter = pitchDiameter + 2 * module
|
||||
|
||||
// Define the constants of the keyway and the bore hole
|
||||
keywayWidth = 1
|
||||
keywayDepth = keywayWidth / 2
|
||||
holeDiam = 7
|
||||
holeRadius = holeDiam / 2
|
||||
startAngle = asin(keywayWidth / 2 / holeRadius)
|
||||
|
||||
// Sketch the keyway and center hole
|
||||
holeWithKeyway = startSketchOn(XY)
|
||||
|> startProfile(at = [
|
||||
holeRadius * cos(startAngle),
|
||||
holeRadius * sin(startAngle)
|
||||
])
|
||||
|> xLine(length = keywayDepth)
|
||||
|> yLine(length = -keywayWidth)
|
||||
|> xLine(length = -keywayDepth)
|
||||
|> arc(angleStart = -1 * startAngle + 360, angleEnd = 180, radius = holeRadius)
|
||||
|> arc(angleStart = 180, angleEnd = startAngle, radius = holeRadius)
|
||||
|> close()
|
||||
|
||||
// Define a function to create a rotated gear sketch on an offset plane
|
||||
fn helicalGearSketch(offsetHeight) {
|
||||
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
|
||||
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
|
||||
|
||||
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
|
||||
helicalGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = helixCalc,
|
||||
tag = $seg01,
|
||||
)
|
||||
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
|
||||
reverse = true,
|
||||
)
|
||||
|
||||
// Position the end line of the sketch at the start of the next tooth
|
||||
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
|
||||
|
||||
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|
||||
|> patternCircular2d(
|
||||
%,
|
||||
instances = nTeeth,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> close()
|
||||
|> subtract2d(tool = holeWithKeyway)
|
||||
return helicalGearSketch
|
||||
}
|
||||
|
||||
// Draw a gear sketch on the base plane
|
||||
gearSketch001 = helicalGearSketch(offsetHeight = 0)
|
||||
|
||||
// Draw a rotated gear sketch on a middle interstitial plane
|
||||
gearSketch002 = helicalGearSketch(offsetHeight = gearHeight / 2)
|
||||
|
||||
// Draw a rotated gear sketch at the gear height offset plane
|
||||
gearSketch003 = helicalGearSketch(offsetHeight = gearHeight)
|
||||
|
||||
// Loft each rotated gear sketch together to form a helical gear
|
||||
helicalGear = loft([
|
||||
gearSketch001,
|
||||
gearSketch002,
|
||||
gearSketch003
|
||||
])
|
||||
|
||||
return helicalGear
|
||||
}
|
||||
|
||||
// Define a function to create a ring gear
|
||||
fn ringGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
|
||||
// Calculate gear parameters
|
||||
pitchDiameter = module * nTeeth
|
||||
addendum = module
|
||||
deddendum = 1.25 * module
|
||||
baseDiameter = pitchDiameter * cos(pressureAngle)
|
||||
tipDiameter = pitchDiameter + 2 * module
|
||||
|
||||
// Define a function to create a rotated gear sketch on an offset plane
|
||||
fn ringGearSketch(offsetHeight) {
|
||||
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
|
||||
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
|
||||
|
||||
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
|
||||
ringTeeth = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = helixCalc,
|
||||
tag = $seg01,
|
||||
)
|
||||
|> line(endAbsolute = polar(angle = 200 / nTeeth + helixCalc, length = tipDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
|
||||
reverse = true,
|
||||
)
|
||||
|
||||
// Position the end line of the sketch at the start of the next tooth
|
||||
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
|
||||
|
||||
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|
||||
|> patternCircular2d(
|
||||
%,
|
||||
instances = nTeeth,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> close()
|
||||
|
||||
// Create a circular body that is larger than the tip diameter of the gear, then subtract the gear profile from the body
|
||||
ringGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||
|> circle(center = [0, 0], radius = tipDiameter / 1.85)
|
||||
|> subtract2d(tool = ringTeeth)
|
||||
return ringGearSketch
|
||||
}
|
||||
|
||||
// Draw a gear sketch on the base plane
|
||||
gearSketch001 = ringGearSketch(offsetHeight = 0)
|
||||
|
||||
// Draw a rotated gear sketch on a middle interstitial plane
|
||||
gearSketch002 = ringGearSketch(offsetHeight = gearHeight / 2)
|
||||
|
||||
// Draw a rotated gear sketch at the gear height offset plane
|
||||
gearSketch003 = ringGearSketch(offsetHeight = gearHeight)
|
||||
|
||||
// Loft each rotated gear sketch together to form a ring gear
|
||||
ringGear = loft([
|
||||
gearSketch001,
|
||||
gearSketch002,
|
||||
gearSketch003
|
||||
])
|
||||
|
||||
return ringGear
|
||||
}
|
||||
|
||||
// Create the outer ring gear for the planetary gearset
|
||||
ringGear(
|
||||
nTeeth = 42,
|
||||
module = 1.5,
|
||||
pressureAngle = 14,
|
||||
helixAngle = -25,
|
||||
gearHeight = 5,
|
||||
)
|
||||
|
||||
// Create a central sun gear using a small helical gear
|
||||
helicalGear(
|
||||
nTeeth = 12,
|
||||
module = 1.5,
|
||||
pressureAngle = 14,
|
||||
helixAngle = 25,
|
||||
gearHeight = 5,
|
||||
)
|
||||
|
||||
// Create the helical planet gears
|
||||
numPlanetGears = 3
|
||||
helicalGear(
|
||||
nTeeth = 12,
|
||||
module = 1.5,
|
||||
pressureAngle = 14,
|
||||
helixAngle = -25,
|
||||
gearHeight = 5,
|
||||
)
|
||||
|> translate(y = (12 + 12) / 2 * 1.5 + 2.7)
|
||||
|> patternCircular3d(
|
||||
instances = numPlanetGears,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = false,
|
||||
)
|
123
public/kcl-samples/helium-tank/main.kcl
Normal file
@ -0,0 +1,123 @@
|
||||
// Helium Tank
|
||||
// A helium tank is a portable pressure vessel used to store and dispense helium gas for a variety of commercial and entertainment purposes
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Define parameters
|
||||
tankHeight = 2.5ft
|
||||
tankDiameter = 9
|
||||
wallThickness = 0.125
|
||||
portDiameter = 1.25
|
||||
bracketThickness = 0.090
|
||||
boltSize = 1 / 4
|
||||
|
||||
// Sketch the perimeter of the gas tank- inside and out, then revolve around the vertical axis.
|
||||
tankSketch = startSketchOn(YZ)
|
||||
|> startProfile(at = [portDiameter / 2, tankHeight])
|
||||
|> yLine(length = -0.6)
|
||||
|> xLine(length = 0.1)
|
||||
|> tangentialArc(angle = -110, radius = 0.1)
|
||||
|> tangentialArc(angle = 40, radius = 0.6)
|
||||
|> tangentialArc(angle = -110, radius = 0.1)
|
||||
|> tangentialArc(angle = 180, radius = 0.1)
|
||||
|> tangentialArc(angle = -90, radius = tankDiameter / 2 - lastSegX(%), tag = $seg01)
|
||||
|> angledLine(angle = tangentToEnd(seg01), endAbsoluteY = 1.5, tag = $seg09)
|
||||
|> tangentialArc(angle = -90, radius = 2, tag = $seg02)
|
||||
|> angledLine(angle = tangentToEnd(seg02), endAbsoluteX = 0.001, tag = $seg08)
|
||||
|> yLine(length = wallThickness)
|
||||
|> xLine(length = segLen(seg08))
|
||||
|> tangentialArc(angle = 90, radius = 2 - wallThickness)
|
||||
|> yLine(length = segLen(seg09))
|
||||
|> tangentialArc(angle = 90, radius = tankDiameter / 2 - wallThickness - 1.3)
|
||||
|> xLine(endAbsolute = profileStartX(%) - .1)
|
||||
|> yLine(endAbsolute = profileStartY(%))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Only revolving to 220deg so that the interior of the tank is visible. It should ultimately be closed at 360deg
|
||||
tankRevolve = revolve(tankSketch, angle = 220, axis = Y)
|
||||
|
||||
// Model the brass valve on top of the tank port
|
||||
valveBody = startSketchOn(offsetPlane(XY, offset = tankHeight - 0.5))
|
||||
|> circle(center = [0, 0], radius = portDiameter / 1.9, tag = $seg03)
|
||||
|> extrude(length = 1.5, tagEnd = $capEnd001)
|
||||
|> fillet(
|
||||
radius = 0.1,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg03, capEnd001])
|
||||
],
|
||||
)
|
||||
|
||||
// Model the outlet port of the valve, then union it all together
|
||||
valvePort = startSketchOn(YZ)
|
||||
|> circle(center = [0, tankHeight + 0.3], radius = portDiameter / 3)
|
||||
|> subtract2d(tool = circle(center = [0, tankHeight + 0.3], radius = portDiameter / 3.25))
|
||||
|> extrude(length = 1.3)
|
||||
valve = union([valveBody, valvePort])
|
||||
|> appearance(color = "#9a4618")
|
||||
|
||||
// Sketch the offset profile of the mounting bracket
|
||||
bracketOffsetProfile = startSketchOn(offsetPlane(XY, offset = tankHeight * 0.67))
|
||||
|> startProfile(at = [0, tankDiameter / 2 + wallThickness])
|
||||
|> xLine(length = -0.1)
|
||||
|> tangentialArc(angle = 35, radius = tankDiameter / 2 + wallThickness)
|
||||
|> tangentialArc(angle = -135, radius = 0.25 - wallThickness, tag = $seg06)
|
||||
|> angledLine(angle = tangentToEnd(seg06), length = tankDiameter / 7)
|
||||
|> tangentialArc(angle = -80, radius = 0.25 - wallThickness, tag = $seg07)
|
||||
|> angledLine(angle = tangentToEnd(seg07), endAbsoluteX = 0)
|
||||
|> mirror2d(axis = Y)
|
||||
|> close()
|
||||
|
||||
// Sketch the outer perimeter of the offset bracket, then subtract the inner offset to create a constant thickness sheet metal hoop
|
||||
bracketProfile = startSketchOn(offsetPlane(XY, offset = tankHeight * 0.67))
|
||||
|> startProfile(at = [0, tankDiameter / 2])
|
||||
|> xLine(length = -0.1)
|
||||
|> tangentialArc(angle = 35, radius = tankDiameter / 2)
|
||||
|> tangentialArc(angle = -135, radius = 0.25, tag = $seg04)
|
||||
|> angledLine(angle = tangentToEnd(seg04), length = tankDiameter / 7)
|
||||
|> tangentialArc(angle = -80, radius = 0.25, tag = $seg05)
|
||||
|> angledLine(angle = tangentToEnd(seg05), endAbsoluteX = 0)
|
||||
|> mirror2d(axis = Y)
|
||||
|> close()
|
||||
|> subtract2d(tool = bracketOffsetProfile)
|
||||
|> extrude(length = 1, symmetric = true)
|
||||
|
||||
// Cut holes in the bracket for a mounting strap
|
||||
strapSleeve = startSketchOn(offsetPlane(XY, offset = tankHeight * 0.67))
|
||||
|> circle(center = [0, .125 / 2], radius = 4.75)
|
||||
|> subtract2d(tool = circle(center = [0, .125 / 2], radius = 4.65))
|
||||
|> extrude(length = .8, symmetric = true)
|
||||
bracketSleeve = subtract([bracketProfile], tools = [strapSleeve])
|
||||
|
||||
// Create holes in the bracket for anchor mounts to secure the bracket to a wall
|
||||
mountingHoles = startSketchOn(offsetPlane(XZ, offset = -tankDiameter / 1.9))
|
||||
|> circle(center = [tankDiameter / 4.5, tankHeight * 0.67], radius = boltSize / 2)
|
||||
|> extrude(length = -5)
|
||||
|
||||
bracket = subtract(
|
||||
[bracketSleeve],
|
||||
tools = union(patternLinear3d(
|
||||
mountingHoles,
|
||||
instances = 2,
|
||||
distance = tankDiameter / 2.25,
|
||||
axis = [-1, 0, 0],
|
||||
)),
|
||||
)
|
||||
|> appearance(color = "#cd0404")
|
||||
|
||||
// Model a circular strap to secure the tank to the bracket
|
||||
mountingStrap = startSketchOn(offsetPlane(XY, offset = tankHeight * 0.67))
|
||||
|> circle(center = [0, .155 / 2], radius = tankDiameter / 1.9)
|
||||
|> subtract2d(tool = circle(center = [0, .155 / 2], radius = tankDiameter / 1.9 - 0.1))
|
||||
|> extrude(length = .75, symmetric = true)
|
||||
|> appearance(color = "#210d03")
|
||||
|
||||
// Create a second instance of the bracket and strap at a lower point on the tank
|
||||
[bracket, mountingStrap]
|
||||
|> patternLinear3d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = tankHeight * 0.33,
|
||||
axis = [0, 0, -1],
|
||||
)
|
84
public/kcl-samples/herringbone-gear/main.kcl
Normal file
@ -0,0 +1,84 @@
|
||||
// Herringbone Gear
|
||||
// A herringbone, or double-helical gear, is a cylindrical gear type with angled teeth in opposing directions. This allows the quietness and smoothness of a helical gear, without applying a directional load while turning
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Define a function to create a herringbone gear
|
||||
fn herringboneGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
|
||||
// Calculate gear parameters
|
||||
pitchDiameter = module * nTeeth
|
||||
addendum = module
|
||||
deddendum = 1.25 * module
|
||||
baseDiameter = pitchDiameter * cos(pressureAngle)
|
||||
tipDiameter = pitchDiameter + 2 * module
|
||||
|
||||
// Define a function to create a rotated gear sketch on an offset plane
|
||||
fn herringboneGearSketch(offsetHeight) {
|
||||
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
|
||||
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
|
||||
|
||||
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
|
||||
herringboneGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = helixCalc,
|
||||
tag = $seg01,
|
||||
)
|
||||
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
|
||||
reverse = true,
|
||||
)
|
||||
|
||||
// Position the end line of the sketch at the start of the next tooth
|
||||
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
|
||||
|
||||
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|
||||
|> patternCircular2d(
|
||||
%,
|
||||
instances = nTeeth,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> close()
|
||||
// Create a center hole with an 8mm diameter
|
||||
|> subtract2d(tool = circle(center = [0, 0], radius = 4))
|
||||
return herringboneGearSketch
|
||||
}
|
||||
|
||||
// Draw a gear sketch on the base plane
|
||||
gearSketch001 = herringboneGearSketch(offsetHeight = 0)
|
||||
|
||||
// Draw a gear sketch that has been rotated by the helix angle
|
||||
gearSketch002 = herringboneGearSketch(offsetHeight = gearHeight / 2)
|
||||
|
||||
// Draw a gear sketch at the total gear height that reverses the angle direction
|
||||
gearSketch003 = clone(gearSketch001)
|
||||
|> translate(z = gearHeight)
|
||||
|
||||
// Loft each rotated gear sketch together to form a herringbone gear
|
||||
herringboneGear = loft(
|
||||
[
|
||||
gearSketch001,
|
||||
gearSketch002,
|
||||
gearSketch003
|
||||
],
|
||||
vDegree = 1,
|
||||
)
|
||||
|
||||
return herringboneGear
|
||||
}
|
||||
|
||||
herringboneGear(
|
||||
nTeeth = 25,
|
||||
module = 1,
|
||||
pressureAngle = 14,
|
||||
helixAngle = 40,
|
||||
gearHeight = 8,
|
||||
)
|
186
public/kcl-samples/herringbone-planetary-gearset/main.kcl
Normal file
@ -0,0 +1,186 @@
|
||||
// Herringbone Planetary Gearset
|
||||
// A herringbone planetary gearset is a type of planetary gear system where the teeth of the sun gear, planet gears, and/or ring gear are herringbone rather than straight. This design allows for smoother, quieter operation, greater load-carrying capacity, and more flexible shaft alignment.
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Define a function to create a herringbone gear
|
||||
fn herringboneGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
|
||||
// Calculate gear parameters
|
||||
pitchDiameter = module * nTeeth
|
||||
addendum = module
|
||||
deddendum = 1.25 * module
|
||||
baseDiameter = pitchDiameter * cos(pressureAngle)
|
||||
tipDiameter = pitchDiameter + 2 * module
|
||||
|
||||
// Define a function to create a rotated gear sketch on an offset plane
|
||||
fn herringboneGearSketch(offsetHeight) {
|
||||
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
|
||||
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
|
||||
|
||||
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
|
||||
herringboneGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = helixCalc,
|
||||
tag = $seg01,
|
||||
)
|
||||
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
|
||||
reverse = true,
|
||||
)
|
||||
|
||||
// Position the end line of the sketch at the start of the next tooth
|
||||
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
|
||||
|
||||
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|
||||
|> patternCircular2d(
|
||||
%,
|
||||
instances = nTeeth,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> close()
|
||||
// Create a center hole with an 8mm diameter
|
||||
|> subtract2d(tool = circle(center = [0, 0], radius = 4))
|
||||
return herringboneGearSketch
|
||||
}
|
||||
|
||||
// Draw a gear sketch on the base plane
|
||||
gearSketch001 = herringboneGearSketch(offsetHeight = 0)
|
||||
|
||||
// Draw a gear sketch that has been rotated by the helix angle
|
||||
gearSketch002 = herringboneGearSketch(offsetHeight = gearHeight / 2)
|
||||
|
||||
// Draw a gear sketch at the total gear height that reverses the angle direction
|
||||
gearSketch003 = clone(gearSketch001)
|
||||
|> translate(z = gearHeight)
|
||||
|
||||
// Loft each rotated gear sketch together to form a herringbone gear
|
||||
herringboneGear = loft(
|
||||
[
|
||||
gearSketch001,
|
||||
gearSketch002,
|
||||
gearSketch003
|
||||
],
|
||||
vDegree = 1,
|
||||
)
|
||||
|
||||
return herringboneGear
|
||||
}
|
||||
|
||||
// Define a function to create a ring gear
|
||||
fn ringGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
|
||||
// Calculate gear parameters
|
||||
pitchDiameter = module * nTeeth
|
||||
addendum = module
|
||||
deddendum = 1.25 * module
|
||||
baseDiameter = pitchDiameter * cos(pressureAngle)
|
||||
tipDiameter = pitchDiameter + 2 * module
|
||||
|
||||
// Define a function to create a rotated gear sketch on an offset plane
|
||||
fn ringGearSketch(offsetHeight) {
|
||||
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
|
||||
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
|
||||
|
||||
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
|
||||
ringTeeth = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = helixCalc,
|
||||
tag = $seg01,
|
||||
)
|
||||
|> line(endAbsolute = polar(angle = 220 / nTeeth + helixCalc, length = tipDiameter / 2))
|
||||
|> involuteCircular(
|
||||
startRadius = baseDiameter / 2,
|
||||
endRadius = tipDiameter / 2,
|
||||
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
|
||||
reverse = true,
|
||||
)
|
||||
|
||||
// Position the end line of the sketch at the start of the next tooth
|
||||
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
|
||||
|
||||
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|
||||
|> patternCircular2d(
|
||||
%,
|
||||
instances = nTeeth,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|> close()
|
||||
|
||||
// Create a circular body that is larger than the tip diameter of the gear, then subtract the gear profile from the body
|
||||
ringGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|
||||
|> circle(center = [0, 0], radius = tipDiameter / 1.8)
|
||||
|> subtract2d(tool = ringTeeth)
|
||||
return ringGearSketch
|
||||
}
|
||||
|
||||
// Draw a gear sketch on the base plane
|
||||
gearSketch001 = ringGearSketch(offsetHeight = 0)
|
||||
|
||||
// Draw a rotated gear sketch on a middle interstitial plane
|
||||
gearSketch002 = ringGearSketch(offsetHeight = gearHeight / 2)
|
||||
|
||||
// Draw a gear sketch at the total gear height that reverses the angle direction
|
||||
gearSketch003 = clone(gearSketch001)
|
||||
|> translate(z = gearHeight)
|
||||
|
||||
// Loft each rotated gear sketch together to form a ring gear
|
||||
ringGear = loft(
|
||||
[
|
||||
gearSketch001,
|
||||
gearSketch002,
|
||||
gearSketch003
|
||||
],
|
||||
vDegree = 1,
|
||||
)
|
||||
|
||||
return ringGear
|
||||
}
|
||||
|
||||
// Create the outer ring gear for the planetary gearset
|
||||
ringGear(
|
||||
nTeeth = 58,
|
||||
module = 1.5,
|
||||
pressureAngle = 14,
|
||||
helixAngle = -35,
|
||||
gearHeight = 8,
|
||||
)
|
||||
|
||||
// Create a central sun gear using a small herringbone gear
|
||||
herringboneGear(
|
||||
nTeeth = 18,
|
||||
module = 1.5,
|
||||
pressureAngle = 14,
|
||||
helixAngle = 35,
|
||||
gearHeight = 8,
|
||||
)
|
||||
|
||||
// Create the herringbone planet gears
|
||||
numPlanetGears = 4
|
||||
herringboneGear(
|
||||
nTeeth = 18,
|
||||
module = 1.5,
|
||||
pressureAngle = 14,
|
||||
helixAngle = -35,
|
||||
gearHeight = 8,
|
||||
)
|
||||
|> translate(y = 18 * 1.5 + 1.95)
|
||||
|> patternCircular3d(
|
||||
instances = numPlanetGears,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = false,
|
||||
)
|
@ -44,6 +44,16 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "bone-plate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Bone Plate",
|
||||
"description": "A bone plate is a medical device used in orthopedics to stabilize and fix bone fractures during the healing process. They are typically made of stainless steel or titanium and are secured to the bone with screws. Bone plates come in various types, including locking, compression, and bridge plates, each with specific applications",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "bottle/main.kcl",
|
||||
@ -80,6 +90,16 @@
|
||||
"parameters.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "cold-plate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Cold Plate",
|
||||
"description": "A cold plate is a thermal management device used to remove heat from a device or component, typically by transferring heat to a liquid coolant that flows through the plate. It's a conductive cooling solution, commonly made of materials like aluminum or copper, with internal channels or tubes for the coolant",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "color-cube/main.kcl",
|
||||
@ -90,6 +110,43 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "counterdrilled-weldment/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Counterdrilled Weldment",
|
||||
"description": "A metal weldment consisting of a counterdrilled plate, a centrally mounted housing tube, and four structural support fins.",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "countersunk-plate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Plate with countersunk holes",
|
||||
"description": "A small mounting plate with a countersunk hole at each end",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "cpu-cooler/main.kcl",
|
||||
"multipleFiles": true,
|
||||
"title": "CPU Cooler",
|
||||
"description": "A CPU cooler is a device designed to dissipate heat generated by the CPU of a computer. They consist of a brazed heat sink made from aluminum or copper alloys, and one or two axial fans to move airflow across the heat sink.",
|
||||
"files": [
|
||||
"fan-housing.kcl",
|
||||
"fan.kcl",
|
||||
"heat-sink.kcl",
|
||||
"main.kcl",
|
||||
"motor.kcl",
|
||||
"mounting-wire.kcl",
|
||||
"parameters.kcl",
|
||||
"removable-sticker.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
|
||||
@ -170,16 +227,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "gear/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Spur Gear",
|
||||
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear.",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "gear-rack/main.kcl",
|
||||
@ -230,6 +277,66 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "hammer/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Claw Hammer",
|
||||
"description": "Often used in construction, a claw hammer is a levered metal hand tool that is used to strike and extract nails",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "helical-gear/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Helical Gear",
|
||||
"description": "A helical gear is a type of cylindrical gear where the teeth are slanted at an angle relative to the axis of rotation. This greatly reduces noise and wear when transmitting torque across meshed spinning gears",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "helical-planetary-gearset/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Helical Planetary Gearset",
|
||||
"description": "A helical planetary gearset is a type of planetary gear system where the teeth of the sun gear, planet gears, and/or ring gear are helical rather than straight. This design allows for smoother, quieter operation, greater load-carrying capacity, and more flexible shaft alignment.",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "helium-tank/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Helium Tank",
|
||||
"description": "A helium tank is a portable pressure vessel used to store and dispense helium gas for a variety of commercial and entertainment purposes",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "herringbone-gear/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Herringbone Gear",
|
||||
"description": "A herringbone, or double-helical gear, is a cylindrical gear type with angled teeth in opposing directions. This allows the quietness and smoothness of a helical gear, without applying a directional load while turning",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "herringbone-planetary-gearset/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Herringbone Planetary Gearset",
|
||||
"description": "A herringbone planetary gearset is a type of planetary gear system where the teeth of the sun gear, planet gears, and/or ring gear are herringbone rather than straight. This design allows for smoother, quieter operation, greater load-carrying capacity, and more flexible shaft alignment.",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "hex-nut/main.kcl",
|
||||
@ -317,12 +424,15 @@
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "parametric-bearing-pillow-block/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Parametric Bearing Pillow Block",
|
||||
"pathFromProjectDirectoryToFirstFile": "pillow-block-bearing/main.kcl",
|
||||
"multipleFiles": true,
|
||||
"title": "Pillow Block Bearing",
|
||||
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads.",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
"ball-bearing.kcl",
|
||||
"block.kcl",
|
||||
"main.kcl",
|
||||
"parameters.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -372,6 +482,16 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "prosthetic-hip/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Prosthetic Hip",
|
||||
"description": "A prosthetic hip is a surgically implanted ball-and-socket intended to replace a damaged or worn hip joint",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "router-template-cross-bar/main.kcl",
|
||||
@ -412,6 +532,46 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "spur-gear/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Spur Gear",
|
||||
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear.",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "spur-reduction-gearset/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Spur Reduction Gearset",
|
||||
"description": "A pair of spur gears meshed together, with an equal module and different number of teeth",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "surgical-drill-guide/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Surgical Drill Guide",
|
||||
"description": "A surgical drill guide is a tool used in medical procedures to assist in drilling holes to a desired depth, ensuring proper orientation and minimizing intraosseal pressure",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "tooling-nest-block/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Tooling Nest Block",
|
||||
"description": "A tooling nest block is a block-shaped tool made from high-carbon steel. It features an assortment of conical or hemispherical indentions, which are used to form or shape metal, particularly in crafting bells or jewelry",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "utility-sink/main.kcl",
|
||||
@ -449,5 +609,15 @@
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "wing-spar/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Wing Spar",
|
||||
"description": "In a fixed-wing aircraft, the spar is often the main structural member of the wing, running spanwise at right angles (or thereabouts depending on wing sweep) to the fuselage. The spar carries flight loads and the weight of the wings while on the ground. Other structural and forming members such as ribs may be attached to the spars",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
}
|
||||
]
|
@ -1,51 +0,0 @@
|
||||
// Parametric Bearing Pillow Block
|
||||
// A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads.
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in, kclVersion = 1.0)
|
||||
|
||||
// Define parameters
|
||||
length = 6
|
||||
width = 4
|
||||
height = 1
|
||||
cbDepth = .25
|
||||
cbDia = .7
|
||||
holeDia = .375
|
||||
padding = 1.5
|
||||
bearingDia = 3
|
||||
|
||||
// Sketch the block body
|
||||
body = startSketchOn(XY)
|
||||
|> startProfile(at = [-width / 2, -length / 2])
|
||||
|> line(endAbsolute = [width / 2, -length / 2])
|
||||
|> line(endAbsolute = [width / 2, length / 2])
|
||||
|> line(endAbsolute = [-width / 2, length / 2])
|
||||
|> close()
|
||||
|> extrude(length = height)
|
||||
counterBoreHoles = startSketchOn(body, face = END)
|
||||
|> circle(
|
||||
center = [
|
||||
-(width / 2 - (padding / 2)),
|
||||
-(length / 2 - (padding / 2))
|
||||
],
|
||||
radius = cbDia / 2,
|
||||
)
|
||||
|> patternLinear2d(instances = 2, distance = length - padding, axis = [0, 1])
|
||||
|> patternLinear2d(instances = 2, distance = width - padding, axis = [1, 0])
|
||||
|> extrude(%, length = -cbDepth)
|
||||
|
||||
boltHoles = startSketchOn(body, face = START)
|
||||
|> circle(
|
||||
center = [
|
||||
-(width / 2 - (padding / 2)),
|
||||
-(length / 2 - (padding / 2))
|
||||
],
|
||||
radius = holeDia / 2,
|
||||
)
|
||||
|> patternLinear2d(instances = 2, distance = length - padding, axis = [0, 1])
|
||||
|> patternLinear2d(instances = 2, distance = width - padding, axis = [1, 0])
|
||||
|> extrude(length = -height + cbDepth)
|
||||
|
||||
centerHole = startSketchOn(body, face = END)
|
||||
|> circle(center = [0, 0], radius = bearingDia / 2)
|
||||
|> extrude(length = -height)
|
101
public/kcl-samples/pillow-block-bearing/ball-bearing.kcl
Normal file
@ -0,0 +1,101 @@
|
||||
// Pillow Block Bearing
|
||||
// The ball bearing for the pillow block bearing assembly
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Import Parameters
|
||||
import * from "parameters.kcl"
|
||||
|
||||
// Create the sketch of one of the balls. The ball diameter is sized as a fraction of the difference between inner and outer radius of the bearing
|
||||
ballsSketch = startSketchOn(offsetPlane(XY, offset = stockThickness / 2))
|
||||
|> startProfile(at = [bearingBoreDiameter / 2 + 0.1, 0.001])
|
||||
|> arc(angleEnd = 0, angleStart = 180, radius = sphereDia / 2)
|
||||
|> close()
|
||||
|
||||
// Revolve the ball to make a sphere and pattern around the inside wall
|
||||
balls = revolve(ballsSketch, axis = X)
|
||||
|> patternCircular3d(
|
||||
arcDegrees = 360,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
instances = 16,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
// Create the sketch for the chain around the balls
|
||||
chainSketch = startSketchOn(offsetPlane(XY, offset = stockThickness / 2))
|
||||
|> startProfile(at = [
|
||||
bearingBoreDiameter / 2 + 0.1 + sphereDia / 2 - (chainWidth / 2),
|
||||
0.125 * sin(60deg)
|
||||
])
|
||||
|> arc(angleEnd = 60, angleStart = 120, radius = sphereDia / 2)
|
||||
|> line(end = [0, chainThickness])
|
||||
|> line(end = [-chainWidth, 0])
|
||||
|> close()
|
||||
|
||||
// Revolve the chain sketch
|
||||
chainHead = revolve(chainSketch, axis = X)
|
||||
|> patternCircular3d(
|
||||
arcDegrees = 360,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
instances = 16,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
// Create the sketch for the links in between the chains
|
||||
linkSketch = startSketchOn(XZ)
|
||||
|> circle(
|
||||
center = [
|
||||
bearingBoreDiameter / 2 + 0.1 + sphereDia / 2,
|
||||
stockThickness / 2
|
||||
],
|
||||
radius = linkDiameter / 2,
|
||||
)
|
||||
|
||||
// Create the walls of the bearing
|
||||
bearingBody = startSketchOn(XZ)
|
||||
bearingUpper = startProfile(
|
||||
bearingBody,
|
||||
at = [
|
||||
bearingOuterDiameter / 2 - .07,
|
||||
stockThickness
|
||||
],
|
||||
)
|
||||
|> angledLine(angle = -91, length = 0.05)
|
||||
|> xLine(length = -(bearingOuterDiameter / 2 - (bearingBoreDiameter / 2)) + .145)
|
||||
|> yLine(endAbsolute = 0.105)
|
||||
|> xLine(length = -0.025)
|
||||
|> angledLine(angle = 91, endAbsoluteY = profileStartY(%))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|> appearance(%, color = "#121212")
|
||||
|
||||
bearingLower = startProfile(bearingBody, at = [bearingBoreDiameter / 2, 0.025])
|
||||
|> xLine(length = 0.05)
|
||||
|> angledLine(angle = 75, length = 0.04, tag = $seg01)
|
||||
|> xLine(length = 0.05)
|
||||
|> angledLine(angle = -75, length = segLen(seg01))
|
||||
|> xLine(endAbsolute = bearingOuterDiameter / 2)
|
||||
|> yLine(length = stockThickness)
|
||||
|> xLine(length = -0.07)
|
||||
|> angledLine(angle = -91, endAbsoluteY = profileStartY(%) + .075)
|
||||
|> xLine(endAbsolute = profileStartX(%) + .05)
|
||||
|> angledLine(angle = 91, endAbsoluteY = stockThickness * 1.25)
|
||||
|> xLine(endAbsolute = profileStartX(%))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|> appearance(%, color = "#f0f0f0")
|
||||
|
||||
// Revolve the link sketch
|
||||
revolve(linkSketch, axis = Y, angle = 360 / 16)
|
||||
|> patternCircular3d(
|
||||
arcDegrees = 360,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
instances = 16,
|
||||
rotateDuplicates = true,
|
||||
)
|
56
public/kcl-samples/pillow-block-bearing/block.kcl
Normal file
@ -0,0 +1,56 @@
|
||||
// Pillow Block Bearing
|
||||
// The machined block for the pillow block bearing assembly. The block is dimensioned using the bolt pattern spacing, and each bolt hole includes a counterbore
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Import Parameters
|
||||
import * from "parameters.kcl"
|
||||
|
||||
// Calculate the dimensions of the block using the specified bolt spacing. The size of the block can be defined by adding a multiple of the counterbore diameter to the bolt spacing
|
||||
blockLength = boltSpacingX + counterboreDiameter + boltDiameter
|
||||
blockWidth = boltSpacingY + counterboreDiameter + boltDiameter
|
||||
|
||||
// Draw the base plate
|
||||
plateSketch = startSketchOn(XY)
|
||||
|> startProfile(at = [-blockLength / 2, -blockWidth / 2])
|
||||
|> angledLine(angle = 0, length = blockLength, tag = $rectangleSegmentA001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = blockWidth, tag = $rectangleSegmentB001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|
||||
|> close()
|
||||
|> subtract2d(tool = circle(center = [0, 0], radius = bearingOuterDiameter / 2))
|
||||
plateBody = extrude(plateSketch, length = stockThickness)
|
||||
|> appearance(%, color = "#1e62eb")
|
||||
|> fillet(
|
||||
radius = boltDiameter * 1 / 3,
|
||||
tags = [
|
||||
getNextAdjacentEdge(rectangleSegmentB001),
|
||||
getNextAdjacentEdge(rectangleSegmentA001),
|
||||
getNextAdjacentEdge(rectangleSegmentC001),
|
||||
getNextAdjacentEdge(rectangleSegmentD001)
|
||||
],
|
||||
)
|
||||
|
||||
// Define hole positions
|
||||
holePositions = [
|
||||
[-boltSpacingX / 2, -boltSpacingY / 2],
|
||||
[-boltSpacingX / 2, boltSpacingY / 2],
|
||||
[boltSpacingX / 2, -boltSpacingY / 2],
|
||||
[boltSpacingX / 2, boltSpacingY / 2]
|
||||
]
|
||||
|
||||
// Function to create a counterbored hole
|
||||
fn counterbore(@holePosition) {
|
||||
cbBore = startSketchOn(plateBody, face = END)
|
||||
|> circle(center = holePosition, radius = counterboreDiameter / 2)
|
||||
|> extrude(length = -counterboreDepth)
|
||||
cbBolt = startSketchOn(cbBore, face = START)
|
||||
|> circle(center = holePosition, radius = boltDiameter / 2, tag = $hole01)
|
||||
|> extrude(length = -stockThickness + counterboreDepth)
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
// Place a counterbored hole at each bolt hole position
|
||||
map(holePositions, f = counterbore)
|
14
public/kcl-samples/pillow-block-bearing/main.kcl
Normal file
@ -0,0 +1,14 @@
|
||||
// Pillow Block Bearing
|
||||
// A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads.
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Import parts and parameters
|
||||
import * from "parameters.kcl"
|
||||
import "ball-bearing.kcl" as ballBearing
|
||||
import "block.kcl" as block
|
||||
|
||||
// Render each part
|
||||
ballBearing
|
||||
block
|
18
public/kcl-samples/pillow-block-bearing/parameters.kcl
Normal file
@ -0,0 +1,18 @@
|
||||
// Global parameters for the pillow block bearing
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
// Export parameters
|
||||
export boltSpacingX = 5
|
||||
export boltSpacingY = 3
|
||||
export boltDiameter = 3 / 8
|
||||
export counterboreDiameter = 3 / 4
|
||||
export counterboreDepth = 3 / 16
|
||||
export stockThickness = .5
|
||||
export bearingBoreDiameter = 1 + 3 / 4
|
||||
export bearingOuterDiameter = bearingBoreDiameter * 1.5
|
||||
export sphereDia = (bearingOuterDiameter - bearingBoreDiameter) / 4
|
||||
export chainWidth = sphereDia / 2
|
||||
export chainThickness = sphereDia / 8
|
||||
export linkDiameter = sphereDia / 4
|
176
public/kcl-samples/prosthetic-hip/main.kcl
Normal file
@ -0,0 +1,176 @@
|
||||
// Prosthetic Hip
|
||||
// A prosthetic hip is a surgically implanted ball-and-socket intended to replace a damaged or worn hip joint
|
||||
|
||||
// Set units
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Create the femur using a series of lofts. Draw the profile for the first loft on the XY plane.
|
||||
l1 = 1
|
||||
r1 = 3
|
||||
stemLoftProfile1 = startSketchOn(XY)
|
||||
|> startProfile(at = [-3, -l1 / 2])
|
||||
|> yLine(length = l1, tag = $seg01)
|
||||
|> tangentialArc(angle = -120, radius = r1)
|
||||
|> angledLine(angle = -30, length = segLen(seg01))
|
||||
|> tangentialArc(angle = -120, radius = r1)
|
||||
|> angledLine(angle = 30, length = -segLen(seg01))
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Draw the second profile for the lofted femur
|
||||
l2 = 19
|
||||
r2 = 3
|
||||
stemLoftProfile2 = startSketchOn(offsetPlane(XY, offset = 75))
|
||||
|> startProfile(at = [-8, -l2 / 2])
|
||||
|> yLine(length = l2, tag = $seg02)
|
||||
|> tangentialArc(angle = -120, radius = r2)
|
||||
|> angledLine(angle = -30, length = segLen(seg02))
|
||||
|> tangentialArc(angle = -120, radius = r2)
|
||||
|> angledLine(angle = 30, length = -segLen(seg02))
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Draw the third profile for the lofted femur
|
||||
p3Z = 110
|
||||
p3A = 25
|
||||
plane003 = {
|
||||
origin = [0, 0.0, p3Z],
|
||||
xAxis = [cos(p3A), 0, sin(p3A)],
|
||||
yAxis = [0.0, 1.0, 0.0]
|
||||
}
|
||||
l3 = 32
|
||||
r3 = 4
|
||||
stemLoftProfile3 = startSketchOn(plane003)
|
||||
|> startProfile(at = [-15.5, -l3 / 2])
|
||||
|> yLine(length = l3, tag = $seg03)
|
||||
|> tangentialArc(angle = -120, radius = r3)
|
||||
|> angledLine(angle = -30, length = segLen(seg03))
|
||||
|> tangentialArc(angle = -120, radius = r3)
|
||||
|> angledLine(angle = 30, length = -segLen(seg03))
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Draw the fourth profile for the lofted femur
|
||||
p4Z = 130
|
||||
p4A = 36.5
|
||||
plane004 = {
|
||||
origin = [0, 0.0, p4Z],
|
||||
xAxis = [cos(p4A), 0, sin(p4A)],
|
||||
yAxis = [0.0, 1.0, 0.0]
|
||||
}
|
||||
l4 = 16
|
||||
r4 = 5
|
||||
stemLoftProfile4 = startSketchOn(plane004)
|
||||
|> startProfile(at = [-23, -l4 / 2])
|
||||
|> yLine(length = l4, tag = $seg04)
|
||||
|> tangentialArc(angle = -120, radius = r4)
|
||||
|> angledLine(angle = -30, length = segLen(seg04))
|
||||
|> tangentialArc(angle = -120, radius = r4)
|
||||
|> angledLine(angle = 30, length = -segLen(seg04))
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Draw the first profile for the femoral stem
|
||||
p5Z = 140
|
||||
p5A = 36.5
|
||||
plane005 = {
|
||||
origin = [0, 0.0, p5Z],
|
||||
xAxis = [cos(p5A), 0, sin(p5A)],
|
||||
yAxis = [0.0, 1.0, 0.0]
|
||||
}
|
||||
l5 = 1.6
|
||||
r5 = 1.6
|
||||
stemLoftProfile5 = startSketchOn(plane005)
|
||||
|> startProfile(at = [-19.5, -l5 / 2])
|
||||
|> yLine(length = l5, tag = $seg05)
|
||||
|> tangentialArc(angle = -120, radius = r5)
|
||||
|> angledLine(angle = -30, length = segLen(seg05))
|
||||
|> tangentialArc(angle = -120, radius = r5)
|
||||
|> angledLine(angle = 30, length = -segLen(seg05))
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Draw the second profile for the femoral stem
|
||||
p6Z = 145
|
||||
p6A = 36.5
|
||||
plane006 = {
|
||||
origin = [0, 0.0, p6Z],
|
||||
xAxis = [cos(p6A), 0, sin(p6A)],
|
||||
yAxis = [0.0, 1.0, 0.0]
|
||||
}
|
||||
l6 = 1
|
||||
r6 = 3
|
||||
stemLoftProfile6 = startSketchOn(plane006)
|
||||
|> startProfile(at = [-23.4, -l6 / 2])
|
||||
|> yLine(length = l6, tag = $seg06)
|
||||
|> tangentialArc(angle = -120, radius = r6)
|
||||
|> angledLine(angle = -30, length = segLen(seg06))
|
||||
|> tangentialArc(angle = -120, radius = r6)
|
||||
|> angledLine(angle = 30, length = -segLen(seg06))
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Draw the third profile for the femoral stem
|
||||
stemTab = clone(stemLoftProfile6)
|
||||
|> extrude(%, length = 6)
|
||||
|
||||
// Loft the femur using all profiles in sequence
|
||||
|
||||
femur = loft([
|
||||
stemLoftProfile1,
|
||||
stemLoftProfile2,
|
||||
stemLoftProfile3,
|
||||
stemLoftProfile4
|
||||
])
|
||||
|
||||
// Loft the femoral stem
|
||||
femoralStem = loft([
|
||||
clone(stemLoftProfile4),
|
||||
stemLoftProfile5,
|
||||
stemLoftProfile6
|
||||
])
|
||||
|
||||
// Revolve a hollow socket to represent the femoral head
|
||||
femoralHead = startSketchOn(XZ)
|
||||
|> startProfile(at = [4, 0])
|
||||
|> xLine(length = 1.1)
|
||||
|> tangentialArc(angle = 90, radius = 3)
|
||||
|> tangentialArc(angle = 90, radius = 8)
|
||||
|> yLine(length = -1)
|
||||
|> tangentialArc(angle = 90, radius = .1)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|> translate(x = -16.1, z = 133)
|
||||
|> rotate(pitch = -36.5)
|
||||
|> appearance(color = "#d64398")
|
||||
|
||||
// Place a polyethylene cap over the femoral head
|
||||
polyethyleneInsert = startSketchOn(XZ)
|
||||
|> startProfile(at = [8.36, 3])
|
||||
|> xLine(length = 0.5)
|
||||
|> yLine(length = .1)
|
||||
|> tangentialArc(endAbsolute = [0.1, 12.55])
|
||||
|> yLine(length = -0.85)
|
||||
|> xLine(length = 0.58)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|> translate(x = -16.1, z = 133)
|
||||
|> rotate(pitch = -36.5)
|
||||
|> appearance(color = "#3cadd3")
|
||||
|
||||
// Place a ceramic or metal shell over the cap
|
||||
acetabularShell = startSketchOn(XZ)
|
||||
|> startProfile(at = [8.84, 4.7])
|
||||
|> xLine(length = 1)
|
||||
|> yLine(length = .5)
|
||||
|> tangentialArc(endAbsolute = [0.1, 14])
|
||||
|> yLine(endAbsolute = 12.56)
|
||||
|> xLine(length = 0.1)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|> translate(x = -16.1, z = 133)
|
||||
|> rotate(pitch = -36.5)
|
||||
|> appearance(color = "#a55e2c")
|
BIN
public/kcl-samples/screenshots/bone-plate.png
Normal file
After Width: | Height: | Size: 126 KiB |