Compare commits

..

1 Commits

1109 changed files with 597469 additions and 6614811 deletions

377
.github/dependabot.yml vendored
View File

@ -1,331 +1,52 @@
# DO NOT EDIT THIS FILE. This dependabot file was generated # To get started with Dependabot version updates, you'll need to specify which
# by https://github.com/KittyCAD/ciso Changes to this file should be addressed in # package ecosystems to update and where the package manifests are located.
# the ciso repository. # Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2 version: 2
updates: updates:
- package-ecosystem: github-actions - package-ecosystem: 'npm' # See documentation for possible values
directory: / directories:
schedule: - '/'
interval: weekly - '/packages/codemirror-lang-kcl/'
day: monday - '/packages/codemirror-lsp-client/'
time: '03:00' - '/rust/kcl-language-server/'
timezone: America/Los_Angeles schedule:
open-pull-requests-limit: 5 interval: weekly
groups: day: monday
security: reviewers:
applies-to: security-updates - franknoirot
update-types: - irev-dev
- major - package-ecosystem: 'github-actions' # See documentation for possible values
- minor directory: '/' # Location of package manifests
- patch schedule:
patch: interval: weekly
applies-to: version-updates day: monday
update-types: reviewers:
- patch - adamchalmers
major: - jessfraz
applies-to: version-updates - package-ecosystem: 'cargo' # See documentation for possible values
update-types: directory: '/rust/' # Location of package manifests
- major schedule:
minor: interval: weekly
applies-to: version-updates day: monday
update-types: reviewers:
- minor - adamchalmers
- patch - jessfraz
- package-ecosystem: cargo groups:
directory: /rust serde-dependencies:
schedule: patterns:
interval: weekly - "serde*"
day: monday wasm-bindgen-deps:
time: '03:00' patterns:
timezone: America/Los_Angeles - "wasm-bindgen*"
open-pull-requests-limit: 5 - package-ecosystem: "pip"
reviewers: directories:
- adamchalmers - "/public/kcl-samples/"
- franknoirot - "/rust/kcl-python-bindings/"
- irev-dev schedule:
- jessfraz interval: weekly
groups: day: monday
security: reviewers:
applies-to: security-updates - adamchalmers
update-types: - jessfraz
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: monday
time: '03:00'
timezone: America/Los_Angeles
open-pull-requests-limit: 5
reviewers:
- adamchalmers
- franknoirot
- irev-dev
- jessfraz
groups:
security:
applies-to: security-updates
update-types:
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: npm
directory: /rust/kcl-language-server
schedule:
interval: weekly
day: monday
time: '03:00'
timezone: America/Los_Angeles
open-pull-requests-limit: 5
reviewers:
- adamchalmers
- franknoirot
- irev-dev
- jessfraz
groups:
security:
applies-to: security-updates
update-types:
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: npm
directory: /packages/codemirror-lang-kcl
schedule:
interval: weekly
day: monday
time: '03:00'
timezone: America/Los_Angeles
open-pull-requests-limit: 5
reviewers:
- adamchalmers
- franknoirot
- irev-dev
- jessfraz
groups:
security:
applies-to: security-updates
update-types:
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: npm
directory: /packages/codemirror-lsp-client
schedule:
interval: weekly
day: monday
time: '03:00'
timezone: America/Los_Angeles
open-pull-requests-limit: 5
reviewers:
- adamchalmers
- franknoirot
- irev-dev
- jessfraz
groups:
security:
applies-to: security-updates
update-types:
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: npm
directory: /.github/actions/github-release
schedule:
interval: weekly
day: monday
time: '03:00'
timezone: America/Los_Angeles
open-pull-requests-limit: 5
reviewers:
- adamchalmers
- franknoirot
- irev-dev
- jessfraz
groups:
security:
applies-to: security-updates
update-types:
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: pip
directory: /public/kcl-samples
schedule:
interval: weekly
day: monday
time: '03:00'
timezone: America/Los_Angeles
open-pull-requests-limit: 5
reviewers:
- adamchalmers
- franknoirot
- irev-dev
- jessfraz
groups:
security:
applies-to: security-updates
update-types:
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: pip
directory: /rust/kcl-python-bindings
schedule:
interval: weekly
day: monday
time: '03:00'
timezone: America/Los_Angeles
open-pull-requests-limit: 5
reviewers:
- adamchalmers
- franknoirot
- irev-dev
- jessfraz
groups:
security:
applies-to: security-updates
update-types:
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: docker
directory: /.github/actions/github-release
schedule:
interval: weekly
day: monday
time: '03:00'
timezone: America/Los_Angeles
open-pull-requests-limit: 5
reviewers:
- adamchalmers
- franknoirot
- irev-dev
- jessfraz
groups:
security:
applies-to: security-updates
update-types:
- major
- minor
- patch
patch:
applies-to: version-updates
update-types:
- patch
major:
applies-to: version-updates
update-types:
- major
minor:
applies-to: version-updates
update-types:
- minor
- patch

View File

@ -16,21 +16,15 @@ jobs:
cache: 'yarn' cache: 'yarn'
- name: Install dependencies - name: Install dependencies
run: yarn run: yarn
- name: Use correct Rust toolchain - name: Setup Rust
shell: bash uses: dtolnay/rust-toolchain@stable
run: | - name: Cache wasm
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ uses: Swatinem/rust-cache@v2
- name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
cache: false # Configured below. workspaces: './rust'
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3 - uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
with: with:
tool: wasm-pack tool: wasm-pack
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: build wasm - name: build wasm
run: yarn build:wasm run: yarn build:wasm

View File

@ -33,63 +33,19 @@ jobs:
- run: yarn install - run: yarn install
- id: filter - name: Setup Rust
name: Check for Rust changes uses: dtolnay/rust-toolchain@stable
uses: dorny/paths-filter@v3
- uses: Swatinem/rust-cache@v2
with: with:
filters: | workspaces: './rust'
rust:
- 'rust/**'
- name: Download Wasm Cache
id: download-wasm
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: rust/kcl-wasm-lib/pkg
- name: Build WASM condition
id: wasm
run: |
set -euox pipefail
# Build wasm if this is a push to main or tag, there are Rust changes, or
# downloading from the wasm cache failed.
if [[ ${{github.event_name}} == 'push' || ${{steps.filter.outputs.rust}} == 'true' || ${{steps.download-wasm.outcome}} == 'failure' ]]; then
echo "should-build-wasm=true" >> $GITHUB_OUTPUT
else
echo "should-build-wasm=false" >> $GITHUB_OUTPUT
fi
- name: Use correct Rust toolchain
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
# TODO: see if we can fetch from main instead if no diff at rust
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3 - uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
with: with:
tool: wasm-pack tool: wasm-pack
- name: Rust Cache
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: Run build:wasm - name: Run build:wasm
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
run: "yarn build:wasm" run: "yarn build:wasm"
- name: Set nightly version, product name, release notes, and icons - name: Set nightly version, product name, release notes, and icons

View File

@ -1,62 +0,0 @@
on:
push:
branches:
- main
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-bench.yml
pull_request:
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-bench.yml
workflow_dispatch:
permissions:
contents: read
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo bench
jobs:
cargo-bench:
name: cargo bench
runs-on:
- runs-on=${{ github.run_id }}
- runner=32cpu-linux-x64
- extras=s3-cache
steps:
- uses: runs-on/action@v1
- uses: actions/checkout@v4
- 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: rust
- name: Install dependencies
run: |
cargo install cargo-criterion
cargo install cargo-codspeed
cd rust/kcl-lib
cargo add --dev codspeed-criterion-compat --rename criterion
- name: Build the benchmark target(s)
run: |
cd rust
cargo codspeed build --measurement-mode walltime
- name: Run the benchmarks
uses: CodSpeedHQ/action@v3
with:
working-directory: rust
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
mode: walltime
env:
KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN }}

View File

@ -22,7 +22,7 @@ jobs:
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
shell: bash shell: bash
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ cp --update=none rust/rust-toolchain.toml ./ || true
- name: Install rust - name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:

View File

@ -29,7 +29,7 @@ jobs:
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
shell: bash shell: bash
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ cp --update=none rust/rust-toolchain.toml ./ || true
- name: Install rust - name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:

View File

@ -31,7 +31,7 @@ jobs:
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
shell: bash shell: bash
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ cp --update=none rust/rust-toolchain.toml ./ || true
- name: Install rust - name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:

View File

@ -29,7 +29,7 @@ jobs:
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
shell: bash shell: bash
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ cp --update=none rust/rust-toolchain.toml ./ || true
- name: Install rust - name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:

View File

@ -18,19 +18,16 @@ permissions:
jobs: jobs:
conditions: path-changes:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
significant: ${{ steps.path-changes.outputs.significant }} significant: ${{ steps.path-changes.outputs.significant }}
should-run: ${{ steps.should-run.outputs.should-run }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Fetch the base branch - name: Fetch the base branch
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
run: git fetch origin ${{ github.base_ref }} --depth=1 run: git fetch origin ${{ github.base_ref }} --depth=1
- name: Check for path changes - name: Check for path changes
id: path-changes id: path-changes
shell: bash shell: bash
@ -50,341 +47,218 @@ jobs:
else else
echo "significant=false" >> $GITHUB_OUTPUT echo "significant=false" >> $GITHUB_OUTPUT
fi fi
- name: Display path changes
- name: Should run
id: should-run
shell: bash
run: |
set -euo pipefail
# We should run when this is a scheduled run or if there are
# significant changes in the diff.
if [[ ${{ github.event_name }} == 'schedule' || ${{ steps.path-changes.outputs.significant }} == 'true' ]]; then
echo "should-run=true" >> $GITHUB_OUTPUT
else
echo "should-run=false" >> $GITHUB_OUTPUT
fi
- name: Display conditions
shell: bash
run: | run: |
# For debugging purposes. # For debugging purposes.
set -euo pipefail
echo "significant: ${{ steps.path-changes.outputs.significant }}" echo "significant: ${{ steps.path-changes.outputs.significant }}"
echo "should-run: ${{ steps.should-run.outputs.should-run }}"
prepare-wasm:
# seperate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: conditions
steps:
- uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
- id: filter
if: needs.conditions.outputs.should-run == 'true'
name: Check for Rust changes
uses: dorny/paths-filter@v3
with:
filters: |
rust:
- 'rust/**'
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
if: needs.conditions.outputs.should-run == 'true'
run: yarn
- name: Download Wasm Cache
id: download-wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: rust/kcl-wasm-lib/pkg
- name: Build WASM condition
id: wasm
if: needs.conditions.outputs.should-run == 'true'
run: |
set -euox pipefail
# Build wasm if this is a scheduled run, there are Rust changes, or
# downloading from the wasm cache failed.
if [[ ${{github.event_name}} == 'schedule' || ${{steps.filter.outputs.rust}} == 'true' || ${{steps.download-wasm.outcome}} == 'failure' ]]; then
echo "should-build-wasm=true" >> $GITHUB_OUTPUT
else
echo "should-build-wasm=false" >> $GITHUB_OUTPUT
fi
- name: Use correct Rust toolchain
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
with:
tool: wasm-pack
- name: Rust Cache
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: Build Wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: yarn build:wasm
- uses: actions/upload-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
with:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
snapshots:
name: playwright:snapshots:ubuntu
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: [conditions, prepare-wasm]
steps:
- uses: actions/create-github-app-token@v1
if: needs.conditions.outputs.should-run == 'true'
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
if: needs.conditions.outputs.should-run == 'true'
with:
token: ${{ steps.app-token.outputs.token }}
- uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm
- name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
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
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: yarn
- name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
- name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
run: yarn playwright install --with-deps
- name: build web
if: needs.conditions.outputs.should-run == 'true'
run: yarn tronb:vite:dev
- name: Run ubuntu/chrome snapshots
if: needs.conditions.outputs.should-run == 'true'
uses: nick-fields/retry@v3.0.2
with:
shell: bash
command: yarn test:snapshots
timeout_minutes: 30
max_attempts: 3
env:
CI: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
with:
name: playwright-report-ubuntu-snapshot-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true
- name: check for changes
if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
if git status | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit -m "A snapshot a day keeps the bugs away! 📷🐛" || true
git push
git push origin ${{ github.head_ref }}
electron: electron:
needs: [conditions, prepare-wasm]
timeout-minutes: 60 timeout-minutes: 60
env: name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
name: playwright:electron:${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}:${{ matrix.shardIndex }}:${{ matrix.shardTotal }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# TODO: enable namespace-profile-windows-latest once available # TODO: enable self-hosted-windows-8-cores once available
os: os: [namespace-profile-ubuntu-8-cores, namespace-profile-macos-8-cores, windows-16-cores]
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
- namespace-profile-macos-8-cores
- windows-latest
shardIndex: [1, 2, 3, 4] shardIndex: [1, 2, 3, 4]
shardTotal: [4] shardTotal: [4]
# Disable macos and windows tests on hourly e2e tests since we only care
# about server side changes.
# Technique from https://github.com/joaomcteixeira/python-project-skeleton/pull/31/files
isScheduled:
- ${{ github.event_name == 'schedule' }}
exclude:
- os: namespace-profile-macos-8-cores
isScheduled: true
- os: windows-latest
isScheduled: true
# TODO: add ref here for main and latest release tag # TODO: add ref here for main and latest release tag
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: path-changes
if: ${{ needs.path-changes.outputs.significant == 'true' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/create-github-app-token@v1
if: needs.conditions.outputs.should-run == 'true' id: app-token
with:
- uses: actions/download-artifact@v4 app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
if: needs.conditions.outputs.should-run == 'true' private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
name: prepared-wasm owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
- name: Copy prepared wasm with:
if: needs.conditions.outputs.should-run == 'true' token: ${{ steps.app-token.outputs.token }}
run: | - id: filter
ls -R prepared-wasm name: Check for Rust changes
cp prepared-wasm/kcl_wasm_lib_bg.wasm public uses: dorny/paths-filter@v3
mkdir rust/kcl-wasm-lib/pkg with:
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg filters: |
rust:
- uses: actions/setup-node@v4 - 'rust/**'
if: needs.conditions.outputs.should-run == 'true' - uses: actions/setup-node@v4
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
- name: Install dependencies
- name: Install dependencies id: deps-install
id: deps-install shell: bash
if: needs.conditions.outputs.should-run == 'true' run: yarn
run: yarn - name: Cache Playwright Browsers
uses: actions/cache@v4
- name: Cache Playwright Browsers with:
uses: actions/cache@v4 path: |
with: ~/.cache/ms-playwright/
path: | key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
~/.cache/ms-playwright/ - name: Install Playwright Browsers
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }} shell: bash
run: yarn playwright install --with-deps
- name: Install Playwright Browsers - name: Download Wasm Cache
run: yarn playwright install --with-deps id: download-wasm
if: github.event_name != 'schedule' && steps.filter.outputs.rust == 'false'
- name: Build web uses: dawidd6/action-download-artifact@v7
if: needs.conditions.outputs.should-run == 'true' continue-on-error: true
run: yarn tronb:vite:dev with:
github_token: ${{secrets.GITHUB_TOKEN}}
- name: Install good sed name: wasm-bundle
if: startsWith(matrix.os, 'macos') workflow: build-and-store-wasm.yml
branch: main
path: rust/kcl-wasm-lib/pkg
- name: copy wasm blob
if: github.event_name != 'schedule' && steps.filter.outputs.rust == 'false'
shell: bash
run: cp rust/kcl-wasm-lib/pkg/kcl_wasm_lib_bg.wasm public
continue-on-error: true
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
with:
tool: wasm-pack
- name: Cache Wasm (because rust diff)
if: github.event_name == 'schedule' || steps.filter.outputs.rust == 'true'
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: OR Cache Wasm (because wasm cache failed)
if: steps.download-wasm.outcome == 'failure'
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: install good sed
if: ${{ startsWith(matrix.os, 'macos') }}
shell: bash
run: |
brew install gnu-sed
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
- name: Install vector
shell: bash
# TODO: figure out what to do with this, it's failing
if: false
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
chmod +x /tmp/vector.sh
/tmp/vector.sh -y -no-modify-path
mkdir -p /tmp/vector
cp .github/workflows/vector.toml /tmp/vector.toml
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
cat /tmp/vector.toml
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
- name: Build Wasm (because rust diff)
if: github.event_name == 'schedule' || steps.filter.outputs.rust == 'true'
shell: bash
run: yarn build:wasm
- name: OR Build Wasm (because wasm cache failed)
if: steps.download-wasm.outcome == 'failure'
shell: bash
run: yarn build:wasm
- name: build web
shell: bash
run: yarn tronb:vite:dev
- name: Run ubuntu/chrome snapshots
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
shell: bash
# TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
# but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
run: |
yarn test:snapshots
env:
CI: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
with:
name: playwright-report-snapshots-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true
- name: Clean up test-results
if: ${{ !cancelled() && (success() || failure()) }}
continue-on-error: true
run: rm -r test-results
- name: check for changes
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 && github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
if git status | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
if: steps.git-check.outputs.modified == 'true'
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit -m "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
git push
git push origin ${{ github.head_ref }}
# only upload artifacts if there's actually changes
- uses: actions/upload-artifact@v4
if: steps.git-check.outputs.modified == 'true'
with:
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
- uses: actions/download-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
continue-on-error: true
with:
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
- name: Run playwright/electron flow (with retries)
id: retry
if: ${{ !cancelled() && steps.deps-install.outcome == 'success' }}
uses: nick-fields/retry@v3.0.2
with:
shell: bash shell: bash
run: | command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
brew install gnu-sed timeout_minutes: 30
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH max_attempts: 25
env:
CI: true
FAIL_ON_CONSOLE_ERRORS: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
include-hidden-files: true
retention-days: 30
overwrite: true
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true
# TODO: Add back axiom logs
- uses: actions/download-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
continue-on-error: true
with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
- name: Run playwright/electron flow (with retries)
id: retry
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && steps.deps-install.outcome == 'success' }}
uses: nick-fields/retry@v3.0.2
with:
shell: bash
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{ env.OS_NAME }}
timeout_minutes: 45
max_attempts: 15
env:
CI: true
FAIL_ON_CONSOLE_ERRORS: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
include-hidden-files: true
retention-days: 30
overwrite: true
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
with:
name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true

View File

@ -5,7 +5,6 @@ on:
paths: paths:
- .github/workflows/generate-website-docs.yml - .github/workflows/generate-website-docs.yml
- 'docs/**' - 'docs/**'
- 'public/kcl-samples/**'
pull_request: pull_request:
paths: paths:
- .github/workflows/generate-website-docs.yml - .github/workflows/generate-website-docs.yml
@ -40,21 +39,9 @@ jobs:
# cleanup old # cleanup old
rm -rf documentation/content/pages/docs/kcl/*.md rm -rf documentation/content/pages/docs/kcl/*.md
rm -rf documentation/content/pages/docs/kcl/types rm -rf documentation/content/pages/docs/kcl/types
rm -rf documentation/content/pages/docs/kcl/settings
rm -rf documentation/content/pages/docs/kcl/consts
# move new # move new
mv -f docs/kcl/*.md documentation/content/pages/docs/kcl/ mv -f docs/kcl/*.md documentation/content/pages/docs/kcl/
mv -f docs/kcl/types documentation/content/pages/docs/kcl/ mv -f docs/kcl/types documentation/content/pages/docs/kcl/
mv -f docs/kcl/settings documentation/content/pages/docs/kcl/
mv -f docs/kcl/consts documentation/content/pages/docs/kcl/
- name: move kcl-samples
shell: bash
run: |
mkdir -p documentation/content/pages/docs/kcl-samples
# cleanup old
rm -rf documentation/content/pages/docs/kcl-samples/*
# move new
mv -f public/kcl-samples/* documentation/content/pages/docs/kcl-samples/
- name: commit the changes in the docs repo - name: commit the changes in the docs repo
shell: bash shell: bash
run: | run: |

View File

@ -159,13 +159,10 @@ jobs:
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
needs: [linux-x86_64, windows, macos, sdist] needs: [linux-x86_64, windows, macos, sdist]
steps: steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with:
path: rust/kcl-python-bindings
- name: Install the latest version of uv - name: Install the latest version of uv
uses: astral-sh/setup-uv@v5 uses: astral-sh/setup-uv@v5
- name: do uv things - name: Install codespell
run: | run: |
cd rust/kcl-python-bindings cd rust/kcl-python-bindings
uv venv .venv uv venv .venv

View File

@ -0,0 +1,52 @@
name: KCL Samples Manifest
permissions:
contents: write
pull-requests: write
on:
push:
branches:
- main
pull_request:
workflow_dispatch: # Allows manual triggering from the Actions tab
jobs:
generate-manifest:
runs-on: ubuntu-latest
steps:
# Checkout the repository
- name: Checkout repository
uses: actions/checkout@v4
# Set up Node.js
- name: Set up Node.js
uses: actions/setup-node@v4
# Run the script to generate the manifest.json
- name: Run manifest generation script
working-directory: public/kcl-samples
run: node generate-manifest.js
# Check if the manifest.json has changed
- name: Check for changes
id: check-for-changes
working-directory: public/kcl-samples
run: |
git diff --exit-code ./manifest.json || echo "changed=changes detected" >> "$GITHUB_OUTPUT"
# Stage and commit the changes if any were made
- name: Commit and push changes
if: ${{ success() && (steps.check-for-changes.outputs.changed == 'changes detected') }}
working-directory: public/kcl-samples
run: |
git add ./manifest.json
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit -m "Update manifest.json"
git push origin ${{ github.head_ref }}

View File

@ -0,0 +1,64 @@
on:
push:
branches:
- main
paths:
- 'public/kcl-samples/**'
# pull_request:
# paths:
# - .github/workflows/pr-kcl-samples-repo.yml
workflow_dispatch:
name: Create PR in kcl-samples repo
permissions:
contents: read
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
pr-kcl-samples-repo:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
# required
app-id: ${{ secrets.GH_ORG_APP_ID }}
private-key: ${{ secrets.GH_ORG_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
# Checkout the other repo since we want to update the files there.
- uses: actions/checkout@v4
with:
repository: 'KittyCAD/kcl-samples'
ref: next
path: kcl-samples-repo
token: ${{ steps.app-token.outputs.token }}
- name: Move files to repo work tree
shell: bash
run: |
rsync -av --delete \
--exclude='.git' \
--exclude='.github' \
--exclude='screenshots' \
--exclude='step' \
public/kcl-samples/ kcl-samples-repo/
- name: Commit the changes in the repo
shell: bash
run: |
set -euo pipefail
cd kcl-samples-repo
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
export NEW_BRANCH="update-from-modeling-app"
git switch -c "$NEW_BRANCH"
git add --all .
git commit -m "Updates from modeling-app repo" || exit 0
git push -f origin "$NEW_BRANCH"
gh pr create --title "Update from modeling-app" \
--body "Updating files from the modeling-app repo" \
--head "$NEW_BRANCH" \
--reviewer jtran \
--base next || true
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

5
.gitignore vendored
View File

@ -53,14 +53,15 @@ e2e/playwright/export-snapshots/*
/public/kcl-samples.zip /public/kcl-samples.zip
/public/kcl-samples/.github /public/kcl-samples/.github
/public/kcl-samples/screenshots/main.kcl /public/kcl-samples/screenshots
/public/kcl-samples/step/main.kcl /public/kcl-samples/step
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/blob-report/ /blob-report/
/playwright/.cache/ /playwright/.cache/
/src/lang/std/artifactMapCache /src/lang/std/artifactMapCache
## generated files ## generated files
src/**/*.typegen.ts src/**/*.typegen.ts

View File

@ -40,7 +40,7 @@ sketch001 = startSketchOn('XZ')
angle = angleToMatchLengthY(seg01, 15, %), angle = angleToMatchLengthY(seg01, 15, %),
length = 5 length = 5
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
extrusion = extrude(sketch001, length = 5) extrusion = extrude(sketch001, length = 5)

View File

@ -35,10 +35,10 @@ angledLine(
```js ```js
exampleSketch = startSketchOn(XZ) exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> yLine(endAbsolute = 15) |> yLineTo(15, %)
|> angledLine({ angle = 30, length = 15 }, %) |> angledLine({ angle = 30, length = 15 }, %)
|> line(end = [8, -10]) |> line(end = [8, -10])
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 10) example = extrude(exampleSketch, length = 10)

File diff suppressed because one or more lines are too long

View File

@ -37,7 +37,7 @@ sketch001 = startSketchOn('XZ')
angle = toDegrees(asin(0.5)), angle = toDegrees(asin(0.5)),
length = 20 length = 20
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
extrude001 = extrude(sketch001, length = 5) extrude001 = extrude(sketch001, length = 5)

View File

@ -37,7 +37,7 @@ sketch001 = startSketchOn('XZ')
angle = toDegrees(atan(1.25)), angle = toDegrees(atan(1.25)),
length = 20 length = 20
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
extrude001 = extrude(sketch001, length = 5) extrude001 = extrude(sketch001, length = 5)

View File

@ -41,7 +41,7 @@ sketch001 = startSketchOn('XZ')
angle = toDegrees(atan2(1.25, 2)), angle = toDegrees(atan2(1.25, 2)),
length = 20 length = 20
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
extrude001 = extrude(sketch001, length = 5) extrude001 = extrude(sketch001, length = 5)

View File

@ -35,7 +35,7 @@ sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(endAbsolute = [12, 10]) |> line(endAbsolute = [12, 10])
|> line(end = [ceil(7.02986), 0]) |> line(end = [ceil(7.02986), 0])
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
extrude001 = extrude(sketch001, length = 5) extrude001 = extrude(sketch001, length = 5)

File diff suppressed because one or more lines are too long

View File

@ -21,7 +21,7 @@ exampleSketch = startSketchOn("XZ")
angle = 30, angle = 30,
length = 2 * E ^ 2, length = 2 * E ^ 2,
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 10) example = extrude(exampleSketch, length = 10)

View File

@ -21,7 +21,7 @@ exampleSketch = startSketchOn("XZ")
angle = 50, angle = 50,
length = 10 * TAU, length = 10 * TAU,
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

View File

@ -1,25 +0,0 @@
---
title: "KCL Constants"
excerpt: "Documentation for the KCL constants."
layout: manual
---
## Table of Contents
### `std`
- [`HALF_TURN`](/docs/kcl/consts/std-HALF_TURN)
- [`QUARTER_TURN`](/docs/kcl/consts/std-QUARTER_TURN)
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-THREE_QUARTER_TURN)
- [`XY`](/docs/kcl/consts/std-XY)
- [`XZ`](/docs/kcl/consts/std-XZ)
- [`YZ`](/docs/kcl/consts/std-YZ)
- [`ZERO`](/docs/kcl/consts/std-ZERO)
### `std::math`
- [`E`](/docs/kcl/consts/std-math-E)
- [`PI`](/docs/kcl/consts/std-math-PI)
- [`TAU`](/docs/kcl/consts/std-math-TAU)

View File

@ -31,7 +31,7 @@ e(): number
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ angle = 30, length = 2 * e() ^ 2 }, %) |> angledLine({ angle = 30, length = 2 * e() ^ 2 }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 10) example = extrude(exampleSketch, length = 10)

View File

@ -6,13 +6,13 @@ layout: manual
Extend a 2-dimensional sketch through a third dimension in order to create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid. Extend a 2-dimensional sketch through a third dimension in order to create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid.
You can provide more than one sketch to extrude, and they will all be extruded in the same direction.
```js ```js
extrude( extrude(
sketches: [Sketch], sketchSet: SketchSet,
length: number, length: number,
): [Solid] ): SolidSet
``` ```
@ -20,12 +20,12 @@ extrude(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketch or sketches should be extruded | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes |
| `length` | [`number`](/docs/kcl/types/number) | How far to extrude the given sketches | Yes | | `length` | [`number`](/docs/kcl/types/number) | How far to extrude the given sketches | Yes |
### Returns ### Returns
[`[Solid]`](/docs/kcl/types/Solid) [`SolidSet`](/docs/kcl/types/SolidSet) - A solid or a group of solids.
### Examples ### Examples

View File

@ -35,7 +35,7 @@ sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(endAbsolute = [12, 10]) |> line(endAbsolute = [12, 10])
|> line(end = [floor(7.02986), 0]) |> line(end = [floor(7.02986), 0])
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
extrude001 = extrude(sketch001, length = 5) extrude001 = extrude(sketch001, length = 5)

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.
```js ```js
hole( hole(
holeSketch: [Sketch], holeSketch: SketchSet,
sketch: Sketch, sketch: Sketch,
): Sketch ): Sketch
``` ```
@ -20,7 +20,7 @@ hole(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `holeSketch` | [`[Sketch]`](/docs/kcl/types/Sketch) | | Yes | | `holeSketch` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes | | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
### Returns ### Returns

View File

@ -12,26 +12,25 @@ layout: manual
* [`Modules`](kcl/modules) * [`Modules`](kcl/modules)
* [`Settings`](kcl/settings) * [`Settings`](kcl/settings)
* [`Known Issues`](kcl/known-issues) * [`Known Issues`](kcl/known-issues)
* [`Constants`](kcl/consts)
### Standard library ### Standard library
* **Primitive types** * **Primitive types**
* [`bool`](kcl/types/bool) * [`bool`](kcl/bool)
* [`number`](kcl/types/number) * [`number`](kcl/number)
* [`string`](kcl/types/string) * [`string`](kcl/string)
* [`tag`](kcl/types/tag) * [`tag`](kcl/tag)
* **std** * **std**
* [`HALF_TURN`](kcl/consts/std-HALF_TURN) * [`HALF_TURN`](kcl/const_std-HALF_TURN)
* [`Plane`](kcl/types/Plane) * [`Plane`](kcl/Plane)
* [`QUARTER_TURN`](kcl/consts/std-QUARTER_TURN) * [`QUARTER_TURN`](kcl/const_std-QUARTER_TURN)
* [`Sketch`](kcl/types/Sketch) * [`Sketch`](kcl/Sketch)
* [`Solid`](kcl/types/Solid) * [`Solid`](kcl/Solid)
* [`THREE_QUARTER_TURN`](kcl/consts/std-THREE_QUARTER_TURN) * [`THREE_QUARTER_TURN`](kcl/const_std-THREE_QUARTER_TURN)
* [`XY`](kcl/consts/std-XY) * [`XY`](kcl/const_std-XY)
* [`XZ`](kcl/consts/std-XZ) * [`XZ`](kcl/const_std-XZ)
* [`YZ`](kcl/consts/std-YZ) * [`YZ`](kcl/const_std-YZ)
* [`ZERO`](kcl/consts/std-ZERO) * [`ZERO`](kcl/const_std-ZERO)
* [`abs`](kcl/abs) * [`abs`](kcl/abs)
* [`acos`](kcl/acos) * [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX) * [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
@ -132,12 +131,14 @@ layout: manual
* [`toRadians`](kcl/toRadians) * [`toRadians`](kcl/toRadians)
* [`translate`](kcl/translate) * [`translate`](kcl/translate)
* [`xLine`](kcl/xLine) * [`xLine`](kcl/xLine)
* [`xLineTo`](kcl/xLineTo)
* [`yLine`](kcl/yLine) * [`yLine`](kcl/yLine)
* [`yLineTo`](kcl/yLineTo)
* [`yd`](kcl/yd) * [`yd`](kcl/yd)
* **std::math** * **std::math**
* [`E`](kcl/consts/std-math-E) * [`E`](kcl/const_std-math-E)
* [`PI`](kcl/consts/std-math-PI) * [`PI`](kcl/const_std-math-PI)
* [`TAU`](kcl/consts/std-math-TAU) * [`TAU`](kcl/const_std-math-TAU)
* [`cos`](kcl/std-math-cos) * [`cos`](kcl/std-math-cos)
* [`sin`](kcl/std-math-sin) * [`sin`](kcl/std-math-sin)
* [`tan`](kcl/std-math-tan) * [`tan`](kcl/std-math-tan)

View File

@ -13,7 +13,9 @@ once fixed in engine will just start working here with no language changes.
If you see a red line around your model, it means this is happening. If you see a red line around your model, it means this is happening.
- **Import**: Right now you can import a file, even if that file has brep data - **Import**: Right now you can import a file, even if that file has brep data
you cannot edit it, after v1, the engine will account for this. you cannot edit it, after v1, the engine will account for this. You also cannot
currently move or transform the imported objects at all, once we have assemblies
this will work.
- **Fillets**: Fillets cannot intersect, you will get an error. Only simple fillet - **Fillets**: Fillets cannot intersect, you will get an error. Only simple fillet
cases work currently. cases work currently.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ Repeat a 2-dimensional sketch some number of times along a partial or complete c
```js ```js
patternCircular2d( patternCircular2d(
sketchSet: [Sketch], sketchSet: SketchSet,
instances: integer, instances: integer,
center: [number], center: [number],
arcDegrees: number, arcDegrees: number,
@ -24,7 +24,7 @@ patternCircular2d(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketchSet` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketch(es) to pattern | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `center` | [`[number]`](/docs/kcl/types/number) | The center about which to make the pattern. This is a 2D vector. | Yes | | `center` | [`[number]`](/docs/kcl/types/number) | The center about which to make the pattern. This is a 2D vector. | Yes |
| `arcDegrees` | [`number`](/docs/kcl/types/number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes | | `arcDegrees` | [`number`](/docs/kcl/types/number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |

View File

@ -10,7 +10,7 @@ Repeat a 3-dimensional solid some number of times along a partial or complete ci
```js ```js
patternCircular3d( patternCircular3d(
solids: [Solid], solidSet: SolidSet,
instances: integer, instances: integer,
axis: [number], axis: [number],
center: [number], center: [number],
@ -25,7 +25,7 @@ patternCircular3d(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solids` | [`[Solid]`](/docs/kcl/types/Solid) | Which solid(s) to pattern | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid(s) to pattern | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `axis` | [`[number]`](/docs/kcl/types/number) | The axis around which to make the pattern. This is a 3D vector | Yes | | `axis` | [`[number]`](/docs/kcl/types/number) | The axis around which to make the pattern. This is a 3D vector | Yes |
| `center` | [`[number]`](/docs/kcl/types/number) | The center about which to make the pattern. This is a 3D vector. | Yes | | `center` | [`[number]`](/docs/kcl/types/number) | The center about which to make the pattern. This is a 3D vector. | Yes |

View File

@ -10,7 +10,7 @@ Repeat a 2-dimensional sketch along some dimension, with a dynamic amount of dis
```js ```js
patternLinear2d( patternLinear2d(
sketches: [Sketch], sketchSet: SketchSet,
instances: integer, instances: integer,
distance: number, distance: number,
axis: [number], axis: [number],
@ -23,7 +23,7 @@ patternLinear2d(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | The sketch(es) to duplicate | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `distance` | [`number`](/docs/kcl/types/number) | Distance between each repetition. Also known as 'spacing'. | Yes | | `distance` | [`number`](/docs/kcl/types/number) | Distance between each repetition. Also known as 'spacing'. | Yes |
| `axis` | [`[number]`](/docs/kcl/types/number) | The axis of the pattern. A 2D vector. | Yes | | `axis` | [`[number]`](/docs/kcl/types/number) | The axis of the pattern. A 2D vector. | Yes |

View File

@ -10,7 +10,7 @@ Repeat a 3-dimensional solid along a linear path, with a dynamic amount of dista
```js ```js
patternLinear3d( patternLinear3d(
solids: [Solid], solidSet: SolidSet,
instances: integer, instances: integer,
distance: number, distance: number,
axis: [number], axis: [number],
@ -23,7 +23,7 @@ patternLinear3d(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solids` | [`[Solid]`](/docs/kcl/types/Solid) | The solid(s) to duplicate | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `distance` | [`number`](/docs/kcl/types/number) | Distance between each repetition. Also known as 'spacing'. | Yes | | `distance` | [`number`](/docs/kcl/types/number) | Distance between each repetition. Also known as 'spacing'. | Yes |
| `axis` | [`[number]`](/docs/kcl/types/number) | The axis of the pattern. A 2D vector. | Yes | | `axis` | [`[number]`](/docs/kcl/types/number) | The axis of the pattern. A 2D vector. | Yes |

View File

@ -36,7 +36,7 @@ The transform function returns a transform object. All properties of the object
```js ```js
patternTransform( patternTransform(
solids: [Solid], solidSet: SolidSet,
instances: integer, instances: integer,
transform: FunctionSource, transform: FunctionSource,
useOriginal?: bool, useOriginal?: bool,
@ -48,7 +48,7 @@ patternTransform(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solids` | [`[Solid]`](/docs/kcl/types/Solid) | The solid(s) to duplicate | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes | | `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `useOriginal` | [`bool`](/docs/kcl/types/bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | [`bool`](/docs/kcl/types/bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,7 @@ pow(
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ angle = 50, length = pow(5, 2) }, %) |> angledLine({ angle = 50, length = pow(5, 2) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -35,7 +35,7 @@ sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(endAbsolute = [12, 10]) |> line(endAbsolute = [12, 10])
|> line(end = [round(7.02986), 0]) |> line(end = [round(7.02986), 0])
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
extrude001 = extrude(sketch001, length = 5) extrude001 = extrude(sketch001, length = 5)

File diff suppressed because one or more lines are too long

View File

@ -1,31 +1,16 @@
--- ---
title: "KCL Settings" title: "KCL settings"
excerpt: "Documentation of settings for the KCL language and Zoo Modeling App." excerpt: "Documentation of settings for the KCL language and Zoo Modeling App."
layout: manual layout: manual
--- ---
# KCL Settings # Per-file settings
There are three levels of settings available in the KittyCAD modeling application:
1. [User Settings](/docs/kcl/settings/user): Global settings that apply to all projects, stored in `user.toml`
2. [Project Settings](/docs/kcl/settings/project): Settings specific to a project, stored in `project.toml`
3. Per-file Settings: Settings that apply to a single KCL file, specified using the `@settings` attribute
## Configuration Files
The KittyCAD modeling app uses TOML files for configuration:
* **User Settings**: `user.toml` - See [complete documentation](/docs/kcl/settings/user)
* **Project Settings**: `project.toml` - See [complete documentation](/docs/kcl/settings/project)
## Per-file settings
Settings which affect a single file are configured using the settings attribute. Settings which affect a single file are configured using the settings attribute.
This must be at the top of the KCL file (comments before the attribute are permitted). This must be at the top of the KCL file (comments before the attribute are permitted).
For example: E.g.,
```js ```
// The settings attribute. // The settings attribute.
@settings(defaultLengthUnit = in) @settings(defaultLengthUnit = in)

View File

@ -1,208 +0,0 @@
---
title: "Project Settings"
excerpt: "Project specific settings for the app. These live in `project.toml` in the base of the project directory. Updating the settings for the project in the app will update this file automatically. Do not edit this file manually, as it may be overwritten by the app. Manual edits can cause corruption of the settings file."
layout: manual
---
# Project Settings
Project specific settings for the app. These live in `project.toml` in the base of the project directory. Updating the settings for the project in the app will update this file automatically. Do not edit this file manually, as it may be overwritten by the app. Manual edits can cause corruption of the settings file.
## Project Configuration Structure
```toml
[settings.app]
# Set the appearance of the application
name = "My Awesome Project"
[settings.app.appearance]
# Use dark mode theme
theme = "dark"
# Set the app color to blue (240.0 = blue, 0.0 = red, 120.0 = green)
color = 240.0
[settings.modeling]
# Use inches as the default measurement unit
base_unit = "in"
```
## Available Settings
### settings
#### app
The settings for the modeling app.
**Default:** None
This setting has the following nested options:
##### appearance
The settings for the appearance of the app.
**Default:** None
This setting has further nested options. See the schema for full details.
##### onboarding_status
The onboarding status of the app.
**Default:** None
##### theme_color
The hue of the primary theme color for the app.
**Default:** None
##### enable_ssao
Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
**Default:** None
##### dismiss_web_banner
Permanently dismiss the banner warning to download the desktop app. This setting only applies to the web app. And is temporary until we have Linux support.
**Default:** None
##### stream_idle_mode
When the user is idle, and this is true, the stream will be torn down.
**Default:** None
##### allow_orbit_in_sketch_mode
When the user is idle, and this is true, the stream will be torn down.
**Default:** None
##### show_debug_panel
Whether to show the debug panel, which lets you see various states of the app to aid in development.
**Default:** None
##### named_views
Settings that affect the behavior of the command bar.
**Default:** None
#### modeling
Settings that affect the behavior while modeling.
**Default:** None
This setting has the following nested options:
##### base_unit
The default unit to use in modeling dimensions.
**Default:** None
##### highlight_edges
Highlight edges of 3D objects?
**Default:** None
##### show_debug_panel
Whether to show the debug panel, which lets you see various states of the app to aid in development. Remove this when we remove backwards compatibility with the old settings file.
**Default:** None
##### enable_ssao
Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
**Default:** None
#### text_editor
Settings that affect the behavior of the KCL text editor.
**Default:** None
This setting has the following nested options:
##### text_wrapping
Whether to wrap text in the editor or overflow with scroll.
**Default:** None
##### blinking_cursor
Whether to make the cursor blink in the editor.
**Default:** None
#### command_bar
Settings that affect the behavior of the command bar.
**Default:** None
This setting has the following nested options:
##### include_settings
Whether to include settings in the command bar.
**Default:** None
## Complete Example
```toml
[settings.app]
# Set the appearance of the application
name = "My Awesome Project"
[settings.app.appearance]
# Use dark mode theme
theme = "dark"
# Set the app color to blue (240.0 = blue, 0.0 = red, 120.0 = green)
color = 240.0
[settings.modeling]
# Use inches as the default measurement unit
base_unit = "in"
```

View File

@ -1,272 +0,0 @@
---
title: "User Settings"
excerpt: "User specific settings for the app. These live in `user.toml` in the app's configuration directory. Updating the settings in the app will update this file automatically. Do not edit this file manually, as it may be overwritten by the app. Manual edits can cause corruption of the settings file."
layout: manual
---
# User Settings
User specific settings for the app. These live in `user.toml` in the app's configuration directory. Updating the settings in the app will update this file automatically. Do not edit this file manually, as it may be overwritten by the app. Manual edits can cause corruption of the settings file.
## User Configuration Structure
```toml
[settings.app]
# Set the appearance of the application
[settings.app.appearance]
# Use dark mode theme
theme = "dark"
# Set the app color to blue (240.0 = blue, 0.0 = red, 120.0 = green)
color = 240.0
[settings.modeling]
# Use millimeters as the default measurement unit
base_unit = "mm"
[settings.text_editor]
# Disable text wrapping in the editor
text_wrapping = false
```
## Available Settings
### settings
#### app
The settings for the modeling app.
**Default:** None
This setting has the following nested options:
##### appearance
The settings for the appearance of the app.
**Default:** None
This setting has further nested options. See the schema for full details.
##### onboarding_status
The onboarding status of the app.
**Default:** None
##### project_directory
Backwards compatible project directory setting.
**Default:** None
##### theme
Backwards compatible theme setting.
**Default:** None
##### theme_color
The hue of the primary theme color for the app.
**Default:** None
##### enable_ssao
Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
**Default:** None
##### dismiss_web_banner
Permanently dismiss the banner warning to download the desktop app. This setting only applies to the web app. And is temporary until we have Linux support.
**Default:** None
##### stream_idle_mode
When the user is idle, and this is true, the stream will be torn down.
**Default:** None
##### allow_orbit_in_sketch_mode
When the user is idle, and this is true, the stream will be torn down.
**Default:** None
##### show_debug_panel
Whether to show the debug panel, which lets you see various states of the app to aid in development.
**Default:** None
#### modeling
Settings that affect the behavior while modeling.
**Default:** None
This setting has the following nested options:
##### base_unit
The default unit to use in modeling dimensions.
**Default:** None
##### camera_projection
The projection mode the camera should use while modeling.
**Default:** None
##### camera_orbit
The methodology the camera should use to orbit around the model.
**Default:** None
##### mouse_controls
The controls for how to navigate the 3D view.
**Possible values:** `zoo`, `onshape`, `trackpad_friendly`, `solidworks`, `nx`, `creo`, `autocad`
**Default:** None
##### highlight_edges
Highlight edges of 3D objects?
**Default:** None
##### show_debug_panel
Whether to show the debug panel, which lets you see various states of the app to aid in development. Remove this when we remove backwards compatibility with the old settings file.
**Default:** None
##### enable_ssao
Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
**Default:** None
##### show_scale_grid
Whether or not to show a scale grid in the 3D modeling view
**Default:** None
#### text_editor
Settings that affect the behavior of the KCL text editor.
**Default:** None
This setting has the following nested options:
##### text_wrapping
Whether to wrap text in the editor or overflow with scroll.
**Default:** None
##### blinking_cursor
Whether to make the cursor blink in the editor.
**Default:** None
#### project
Settings that affect the behavior of project management.
**Default:** None
This setting has the following nested options:
##### directory
The directory to save and load projects from.
**Default:** None
##### default_project_name
The default project name to use when creating a new project.
**Default:** None
#### command_bar
Settings that affect the behavior of the command bar.
**Default:** None
This setting has the following nested options:
##### include_settings
Whether to include settings in the command bar.
**Default:** None
## Complete Example
```toml
[settings.app]
# Set the appearance of the application
[settings.app.appearance]
# Use dark mode theme
theme = "dark"
# Set the app color to blue (240.0 = blue, 0.0 = red, 120.0 = green)
color = 240.0
[settings.modeling]
# Use millimeters as the default measurement unit
base_unit = "mm"
[settings.text_editor]
# Disable text wrapping in the editor
text_wrapping = false
```

View File

@ -10,10 +10,10 @@ Remove volume from a 3-dimensional shape such that a wall of the provided thickn
```js ```js
shell( shell(
solids: [Solid], solidSet: SolidSet,
thickness: number, thickness: number,
faces: [FaceTag], faces: [FaceTag],
): [Solid] ): SolidSet
``` ```
@ -21,13 +21,13 @@ shell(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solids` | [`[Solid]`](/docs/kcl/types/Solid) | Which solid (or solids) to shell out | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid (or solids) to shell out | Yes |
| `thickness` | [`number`](/docs/kcl/types/number) | The thickness of the shell | Yes | | `thickness` | [`number`](/docs/kcl/types/number) | The thickness of the shell | Yes |
| `faces` | [`[FaceTag]`](/docs/kcl/types/FaceTag) | The faces you want removed | Yes | | `faces` | [`[FaceTag]`](/docs/kcl/types/FaceTag) | The faces you want removed | Yes |
### Returns ### Returns
[`[Solid]`](/docs/kcl/types/Solid) [`SolidSet`](/docs/kcl/types/SolidSet) - A solid or a group of solids.
### Examples ### Examples

View File

@ -34,7 +34,7 @@ sqrt(num: number): number
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ angle = 50, length = sqrt(2500) }, %) |> angledLine({ angle = 50, length = sqrt(2500) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

View File

@ -137,9 +137,9 @@ a1 = startSketchOn({
}) })
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(end = [100.0, 0]) |> line(end = [100.0, 0])
|> yLine(length = -100.0) |> yLine(-100.0, %)
|> xLine(length = -100.0) |> xLine(-100.0, %)
|> yLine(length = 100.0) |> yLine(100.0, %)
|> close() |> close()
|> extrude(length = 3.14) |> extrude(length = 3.14)
``` ```

View File

@ -33,7 +33,7 @@ exampleSketch = startSketchOn("XZ")
angle = 30, angle = 30,
length = 3 / cos(toRadians(30)), length = 3 / cos(toRadians(30)),
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

View File

@ -33,7 +33,7 @@ exampleSketch = startSketchOn("XZ")
angle = 50, angle = 50,
length = 15 / sin(toDegrees(135)), length = 15 / sin(toDegrees(135)),
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

View File

@ -33,7 +33,7 @@ exampleSketch = startSketchOn("XZ")
angle = 50, angle = 50,
length = 50 * tan(1/2), length = 50 * tan(1/2),
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ tau(): number
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ angle = 50, length = 10 * tau() }, %) |> angledLine({ angle = 50, length = 10 * tau() }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

View File

@ -37,7 +37,7 @@ exampleSketch = startSketchOn("XZ")
angle = 50, angle = 50,
length = 70 * cos(toDegrees(pi() / 4)) length = 70 * cos(toDegrees(pi() / 4))
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

View File

@ -37,7 +37,7 @@ exampleSketch = startSketchOn("XZ")
angle = 50, angle = 50,
length = 70 * cos(toRadians(45)) length = 70 * cos(toRadians(45))
}, %) }, %)
|> yLine(endAbsolute = 0) |> yLineTo(0, %)
|> close() |> close()
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

File diff suppressed because one or more lines are too long

View File

@ -24,5 +24,6 @@ A face.
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -22,5 +22,6 @@ A helix.
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No | | `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -22,5 +22,6 @@ A helix.
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No | | `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -18,5 +18,6 @@ Data for an imported geometry.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No | | `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -25,6 +25,7 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Uuid`| | No | | `type` |enum: `Uuid`| | No |
| `value` |[`string`](/docs/kcl/types/string)| | No | | `value` |[`string`](/docs/kcl/types/string)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -41,6 +42,7 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Bool`| | No | | `type` |enum: `Bool`| | No |
| `value` |`boolean`| | No | | `value` |`boolean`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -58,6 +60,7 @@ Any KCL value.
| `type` |enum: `Number`| | No | | `type` |enum: `Number`| | No |
| `value` |[`number`](/docs/kcl/types/number)| | No | | `value` |[`number`](/docs/kcl/types/number)| | No |
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No | | `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -74,6 +77,7 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `String`| | No | | `type` |enum: `String`| | No |
| `value` |[`string`](/docs/kcl/types/string)| | No | | `value` |[`string`](/docs/kcl/types/string)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -90,22 +94,7 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `MixedArray`| | No | | `type` |enum: `MixedArray`| | No |
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No | | `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `HomArray`| | No |
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
---- ----
@ -122,6 +111,7 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Object`| | No | | `type` |enum: `Object`| | No |
| `value` |`object`| | No | | `value` |`object`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -138,6 +128,8 @@ Any KCL value.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`TagIdentifier`](/docs/kcl/types#tag-identifier)| | No | | `type` |enum: [`TagIdentifier`](/docs/kcl/types#tag-identifier)| | No |
| `value` |[`string`](/docs/kcl/types/string)| | No | | `value` |[`string`](/docs/kcl/types/string)| | No |
| `info` |[`TagEngineInfo`](/docs/kcl/types/TagEngineInfo)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -215,6 +207,22 @@ Any KCL value.
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Sketches`| | No |
| `value` |`[` [`Sketch`](/docs/kcl/types/Sketch) `]`| | No |
----
**Type:** `object`
## Properties ## Properties
| Property | Type | Description | Required | | Property | Type | Description | Required |
@ -231,6 +239,22 @@ Any KCL value.
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Solids`| | No |
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
----
**Type:** `object`
## Properties ## Properties
| Property | Type | Description | Required | | Property | Type | Description | Required |
@ -255,6 +279,7 @@ Data for an imported geometry.
| `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No | | `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No |
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No | | `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -270,6 +295,7 @@ Data for an imported geometry.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Function`| | No | | `type` |enum: `Function`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -286,6 +312,7 @@ Data for an imported geometry.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Module`| | No | | `type` |enum: `Module`| | No |
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No | | `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -301,6 +328,7 @@ Data for an imported geometry.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Type`| | No | | `type` |enum: `Type`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -317,6 +345,24 @@ Data for an imported geometry.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No | | `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application). | No | | `value` |[`KclNone`](/docs/kcl/types/KclNone)| KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application). | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Tombstone`| | No |
| `value` |`null`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----

View File

@ -126,30 +126,6 @@ A base path.
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No | | `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
----
A base path.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArcThreePoint`| | No |
| `p1` |`[number, number]`| Point 1 of the arc (base on the end of previous segment) | No |
| `p2` |`[number, number]`| Point 2 of the arc (interior kwarg) | No |
| `p3` |`[number, number]`| Point 3 of the arc (end kwarg) | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
---- ----
A path that is horizontal. A path that is horizontal.

View File

@ -0,0 +1,57 @@
---
title: "SketchSet"
excerpt: "A sketch or a group of sketches."
layout: manual
---
A sketch or a group of sketches.
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `sketch`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the sketch (this will change when the engine's reference to it changes). | No |
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
| `originalId` |[`string`](/docs/kcl/types/string)| | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
----
**Type:** `[object, array]`
`[` [`Sketch`](/docs/kcl/types/Sketch) `]`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `sketches`| | No |
----

View File

@ -32,6 +32,7 @@ A sketch type.
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
@ -56,6 +57,7 @@ A face.
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----

View File

@ -1,51 +0,0 @@
---
title: "SolidOrImportedGeometry"
excerpt: "Data for a solid or an imported geometry."
layout: manual
---
Data for a solid or an imported geometry.
**This schema accepts exactly one of the following:**
Data for an imported geometry.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `importedGeometry`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
----
**Type:** `[object, array]`
`[` [`Solid`](/docs/kcl/types/Solid) `]`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `solidSet`| | No |
----

View File

@ -0,0 +1,58 @@
---
title: "SolidSet"
excerpt: "A solid or a group of solids."
layout: manual
---
A solid or a group of solids.
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `solid`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the solid. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID of the solid. Unlike `id`, this doesn't change. | No |
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
| `height` |[`number`](/docs/kcl/types/number)| The height of the solid. | No |
| `startCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion start cap | No |
| `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
----
**Type:** `[object, array]`
`[` [`Solid`](/docs/kcl/types/Solid) `]`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `solids`| | No |
----

View File

@ -10,9 +10,8 @@ Draw a line relative to the current origin to a specified distance away from the
```js ```js
xLine( xLine(
length: number,
sketch: Sketch, sketch: Sketch,
length?: number,
endAbsolute?: number,
tag?: TagDeclarator, tag?: TagDeclarator,
): Sketch ): Sketch
``` ```
@ -22,10 +21,9 @@ xLine(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes | | `length` | [`number`](/docs/kcl/types/number) | | Yes |
| `length` | [`number`](/docs/kcl/types/number) | How far away along the X axis should this line go? Incompatible with `endAbsolute`. | No | | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
| `endAbsolute` | [`number`](/docs/kcl/types/number) | Which absolute X value should this line go to? Incompatible with `length`. | No | | [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Create a new tag which refers to this line | No |
### Returns ### Returns
@ -37,12 +35,12 @@ xLine(
```js ```js
exampleSketch = startSketchOn(XZ) exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> xLine(length = 15) |> xLine(15, %)
|> angledLine({ angle = 80, length = 15 }, %) |> angledLine({ angle = 80, length = 15 }, %)
|> line(end = [8, -10]) |> line(end = [8, -10])
|> xLine(length = 10) |> xLine(10, %)
|> angledLine({ angle = 120, length = 30 }, %) |> angledLine({ angle = 120, length = 30 }, %)
|> xLine(length = -15) |> xLine(-15, %)
|> close() |> close()
example = extrude(exampleSketch, length = 10) example = extrude(exampleSketch, length = 10)

51
docs/kcl/xLineTo.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -10,9 +10,8 @@ Draw a line relative to the current origin to a specified distance away from the
```js ```js
yLine( yLine(
length: number,
sketch: Sketch, sketch: Sketch,
length?: number,
endAbsolute?: number,
tag?: TagDeclarator, tag?: TagDeclarator,
): Sketch ): Sketch
``` ```
@ -22,10 +21,9 @@ yLine(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes | | `length` | [`number`](/docs/kcl/types/number) | | Yes |
| `length` | [`number`](/docs/kcl/types/number) | How far away along the Y axis should this line go? Incompatible with `endAbsolute`. | No | | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
| `endAbsolute` | [`number`](/docs/kcl/types/number) | Which absolute Y value should this line go to? Incompatible with `length`. | No | | [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Create a new tag which refers to this line | No |
### Returns ### Returns
@ -37,10 +35,10 @@ yLine(
```js ```js
exampleSketch = startSketchOn(XZ) exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> yLine(length = 15) |> yLine(15, %)
|> angledLine({ angle = 30, length = 15 }, %) |> angledLine({ angle = 30, length = 15 }, %)
|> line(end = [8, -10]) |> line(end = [8, -10])
|> yLine(length = -5) |> yLine(-5, %)
|> close() |> close()
example = extrude(exampleSketch, length = 10) example = extrude(exampleSketch, length = 10)

47
docs/kcl/yLineTo.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,4 @@
import { Page } from '@playwright/test' import { test, expect, Page } from './zoo-test'
import { test, expect } from './zoo-test'
import { import {
getUtils, getUtils,
TEST_COLORS, TEST_COLORS,
@ -66,7 +65,7 @@ async function doBasicSketch(
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect(u.codeLocator) await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001) .toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|> xLine(length = ${commonPoints.num1})`) |> xLine(${commonPoints.num1}, %)`)
} }
await page.waitForTimeout(500) await page.waitForTimeout(500)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
@ -75,8 +74,8 @@ async function doBasicSketch(
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${ .toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt commonPoints.startAt
}, sketch001) }, sketch001)
|> xLine(length = ${commonPoints.num1}) |> xLine(${commonPoints.num1}, %)
|> yLine(length = ${commonPoints.num1 + 0.01})`) |> yLine(${commonPoints.num1 + 0.01}, %)`)
} else { } else {
await page.waitForTimeout(500) await page.waitForTimeout(500)
} }
@ -87,9 +86,9 @@ async function doBasicSketch(
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${ .toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt commonPoints.startAt
}, sketch001) }, sketch001)
|> xLine(length = ${commonPoints.num1}) |> xLine(${commonPoints.num1}, %)
|> yLine(length = ${commonPoints.num1 + 0.01}) |> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(length = ${commonPoints.num2 * -1})`) |> xLine(${commonPoints.num2 * -1}, %)`)
} }
// deselect line tool // deselect line tool
@ -147,9 +146,9 @@ async function doBasicSketch(
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${ .toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt commonPoints.startAt
}, sketch001) }, sketch001)
|> xLine(length = ${commonPoints.num1}, tag = $seg01) |> xLine(${commonPoints.num1}, %, $seg01)
|> yLine(length = ${commonPoints.num1 + 0.01}) |> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(length = -segLen(seg01))`) |> xLine(-segLen(seg01), %)`)
} }
test.describe('Basic sketch', { tag: ['@skipWin'] }, () => { test.describe('Basic sketch', { tag: ['@skipWin'] }, () => {

View File

@ -1,5 +1,4 @@
import { Page } from '@playwright/test' import { test, expect, Page } from './zoo-test'
import { test, expect } from './zoo-test'
import { HomePageFixture } from './fixtures/homePageFixture' import { HomePageFixture } from './fixtures/homePageFixture'
import { getUtils } from './test-utils' import { getUtils } from './test-utils'
import { EngineCommand } from 'lang/std/artifactGraph' import { EngineCommand } from 'lang/std/artifactGraph'

View File

@ -16,7 +16,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line(end = [20, 0]) |> line(end = [20, 0])
|> line(end = [0, 20]) |> line(end = [0, 20])
|> xLine(length = -20) |> xLine(-20, %)
|> close() |> close()
` `
) )

View File

@ -10,11 +10,7 @@ import fsp from 'fs/promises'
test( test(
'export works on the first try', 'export works on the first try',
{ tag: ['@electron', '@skipLocalEngine'] }, { tag: ['@electron', '@skipLocalEngine'] },
async ({ page, context, scene, tronApp }, testInfo) => { async ({ page, context, scene }, testInfo) => {
if (!tronApp) {
fail()
}
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })]) await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
@ -90,7 +86,7 @@ test(
await expect(exportingToastMessage).not.toBeVisible() await expect(exportingToastMessage).not.toBeVisible()
const firstFileFullPath = path.resolve( const firstFileFullPath = path.resolve(
getPlaywrightDownloadDir(tronApp.projectDirName), getPlaywrightDownloadDir(page),
exportFileName exportFileName
) )
await test.step('Check the export size', async () => { await test.step('Check the export size', async () => {
@ -169,7 +165,7 @@ test(
])) ]))
const secondFileFullPath = path.resolve( const secondFileFullPath = path.resolve(
getPlaywrightDownloadDir(tronApp.projectDirName), getPlaywrightDownloadDir(page),
exportFileName exportFileName
) )
await test.step('Check the export size', async () => { await test.step('Check the export size', async () => {
@ -185,7 +181,7 @@ test(
}, },
{ timeout: 15_000 } { timeout: 15_000 }
) )
.toBeGreaterThan(70_000) .toBeGreaterThan(100_000)
}) })
}) })
} }

View File

@ -800,11 +800,11 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// finish line with comment // finish line with comment
await page.keyboard.press('Tab')
await page.waitForTimeout(100)
await page.keyboard.type('5') await page.keyboard.type('5')
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.keyboard.press('Tab') await page.keyboard.press('Tab')
await page.waitForTimeout(100)
await page.keyboard.press('Tab')
await page.keyboard.type(' // ') await page.keyboard.type(' // ')
// Since we need to parse the ast to know we are in a comment we gotta hang tight. // Since we need to parse the ast to know we are in a comment we gotta hang tight.
@ -817,7 +817,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ') .toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %) |> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`) |> xLine(5, %) // lin`)
// expect there to be no KCL errors // expect there to be no KCL errors
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
@ -873,11 +873,11 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('Tab') await page.keyboard.press('Tab')
// finish line with comment // finish line with comment
await page.keyboard.press('Tab')
await page.waitForTimeout(100)
await page.keyboard.type('5') await page.keyboard.type('5')
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.keyboard.press('Tab') await page.keyboard.press('Tab')
await page.waitForTimeout(100)
await page.keyboard.press('Tab')
await page.keyboard.type(' // ') await page.keyboard.type(' // ')
// Since we need to parse the ast to know we are in a comment we gotta hang tight. // Since we need to parse the ast to know we are in a comment we gotta hang tight.
@ -890,7 +890,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ') .toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %) |> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`) |> xLine(5, %) // lin`)
}) })
}) })
test('Can undo a click and point extrude with ctrl+z', async ({ test('Can undo a click and point extrude with ctrl+z', async ({

View File

@ -8,7 +8,7 @@ const FEATURE_TREE_EXAMPLE_CODE = `export fn timesFive(x) {
export fn triangle() { export fn triangle() {
return startSketchOn('XZ') return startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> xLine(length = 10) |> xLine(10, %)
|> line(end = [-10, -5]) |> line(end = [-10, -5])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
@ -28,7 +28,7 @@ plane001 = offsetPlane('XY', offset = 10)
sketch002 = startSketchOn(plane001) sketch002 = startSketchOn(plane001)
|> startProfileAt([-20, 0], %) |> startProfileAt([-20, 0], %)
|> line(end = [5, -15]) |> line(end = [5, -15])
|> xLine(length = -10) |> xLine(-10, %)
|> line(endAbsolute = [-40, 0]) |> line(endAbsolute = [-40, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
@ -274,6 +274,7 @@ test.describe('Feature Tree pane', () => {
currentArgKey: 'distance', currentArgKey: 'distance',
currentArgValue: initialInput, currentArgValue: initialInput,
headerArguments: { headerArguments: {
Selection: '1 face',
Distance: initialInput, Distance: initialInput,
}, },
highlightedHeaderArg: 'distance', highlightedHeaderArg: 'distance',
@ -290,6 +291,7 @@ test.describe('Feature Tree pane', () => {
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
Selection: '1 face',
// The calculated value is shown in the argument summary // The calculated value is shown in the argument summary
Distance: initialInput, Distance: initialInput,
}, },
@ -400,7 +402,7 @@ test.describe('Feature Tree pane', () => {
sketch001 = startSketchOn(plane001) sketch001 = startSketchOn(plane001)
profile001 = circle(sketch001, center = [0, 20], radius = 12) profile001 = circle(sketch001, center = [0, 20], radius = 12)
profile002 = startProfileAt([0, 7.25], sketch001) profile002 = startProfileAt([0, 7.25], sketch001)
|> xLine(length = 13.3) |> xLine(13.3, %)
profile003 = startProfileAt([0, -4.93], sketch001) profile003 = startProfileAt([0, -4.93], sketch001)
|> line(endAbsolute = [-5.56, 0])` |> line(endAbsolute = [-5.56, 0])`
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {

View File

@ -158,14 +158,11 @@ test.describe('when using the file tree to', () => {
await createNewFile('lee') await createNewFile('lee')
await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => { await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => {
await expect await expect(
.poll(() => page
page .locator('[data-testid="file-pane-scroll-container"] button')
.locator('[data-testid="file-pane-scroll-container"] button') .filter({ hasText: /lee[-]?[0-5]?/ })
.filter({ hasText: /lee[-]?[0-5]?/ }) ).toHaveCount(5)
.count()
)
.toEqual(5)
}) })
} }
) )

View File

@ -27,19 +27,28 @@ type CmdBarSerialised =
export class CmdBarFixture { export class CmdBarFixture {
public page: Page public page: Page
public cmdBarOpenBtn!: Locator
public cmdBarElement!: Locator get cmdBarOpenBtn() {
return this.page.getByTestId('command-bar-open-button')
}
get cmdBarElement() {
return this.page.getByTestId('command-bar')
}
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.cmdBarOpenBtn = this.page.getByTestId('command-bar-open-button')
this.cmdBarElement = this.page.getByTestId('command-bar')
} }
get currentArgumentInput() { get currentArgumentInput() {
return this.page.getByTestId('cmd-bar-arg-value') return this.page.getByTestId('cmd-bar-arg-value')
} }
// Put all selectors here because this method is re-run on fixture creation.
reConstruct = (page: Page) => {
this.page = page
}
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => { private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) { if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
return { stage: 'commandBarClosed' } return { stage: 'commandBarClosed' }

View File

@ -24,6 +24,11 @@ export class EditorFixture {
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.codeContent = page.locator('.cm-content[data-language="kcl"]') this.codeContent = page.locator('.cm-content[data-language="kcl"]')
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint') this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error') this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
@ -82,30 +87,6 @@ export class EditorFixture {
toContain: this._expectEditorToContain(), toContain: this._expectEditorToContain(),
not: { toContain: this._expectEditorToContain(true) }, not: { toContain: this._expectEditorToContain(true) },
} }
snapshot = async (options?: { timeout?: number; name?: string }) => {
const wasPaneOpen = await this.checkIfPaneIsOpen()
if (!wasPaneOpen) {
await this.openPane()
}
try {
// Use expect.poll to implement retry logic
await expect
.poll(
async () => {
const code = await this.codeContent.textContent()
return code || ''
},
{ timeout: options?.timeout || 5000 }
)
.toMatchSnapshot(options?.name || 'editor-content')
} finally {
// Reset pane state if needed
if (!wasPaneOpen) {
await this.closePane()
}
}
}
private _serialiseDiagnostics = async (): Promise<Array<string>> => { private _serialiseDiagnostics = async (): Promise<Array<string>> => {
const diagnostics = await this.diagnosticsGutterIcon.all() const diagnostics = await this.diagnosticsGutterIcon.all()
const diagnosticsContent: string[] = [] const diagnosticsContent: string[] = []

View File

@ -1,31 +1,13 @@
/* eslint-disable react-hooks/rules-of-hooks */
import type { import type {
BrowserContext, BrowserContext,
ElectronApplication, ElectronApplication,
Fixtures as PlaywrightFixtures,
TestInfo, TestInfo,
Page, Page,
} from '@playwright/test' } from '@playwright/test'
import { import { getUtils, setup, setupElectron } from '../test-utils'
_electron as electron,
PlaywrightTestArgs,
PlaywrightWorkerArgs,
} from '@playwright/test'
import * as TOML from '@iarna/toml'
import {
TEST_SETTINGS_KEY,
TEST_SETTINGS_CORRUPTED,
TEST_SETTINGS,
TEST_SETTINGS_DEFAULT_THEME,
} from '../storageStates'
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
import { getUtils, setup } from '../test-utils'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import fs from 'node:fs' import { join } from 'path'
import path from 'path'
import { CmdBarFixture } from './cmdBarFixture' import { CmdBarFixture } from './cmdBarFixture'
import { EditorFixture } from './editorFixture' import { EditorFixture } from './editorFixture'
import { ToolbarFixture } from './toolbarFixture' import { ToolbarFixture } from './toolbarFixture'
@ -41,7 +23,7 @@ export class AuthenticatedApp {
public readonly testInfo: TestInfo public readonly testInfo: TestInfo
public readonly viewPortSize = { width: 1200, height: 500 } public readonly viewPortSize = { width: 1200, height: 500 }
public electronApp: undefined | ElectronApplication public electronApp: undefined | ElectronApplication
public projectDirName: string = '' public dir: string = ''
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
this.context = context this.context = context
@ -64,7 +46,7 @@ export class AuthenticatedApp {
} }
getInputFile = (fileName: string) => { getInputFile = (fileName: string) => {
return fsp.readFile( return fsp.readFile(
path.join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName), join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName),
'utf-8' 'utf-8'
) )
} }
@ -77,326 +59,101 @@ export interface Fixtures {
scene: SceneFixture scene: SceneFixture
homePage: HomePageFixture homePage: HomePageFixture
} }
export class AuthenticatedTronApp {
public originalPage: Page
public page: Page
public browserContext: BrowserContext
public context: BrowserContext
public readonly testInfo: TestInfo
public electronApp: ElectronApplication | undefined
public readonly viewPortSize = { width: 1200, height: 500 }
public dir: string = ''
export class ElectronZoo { constructor(
public available: boolean = true browserContext: BrowserContext,
public electron!: ElectronApplication originalPage: Page,
public firstUrl = '' testInfo: TestInfo
public viewPortSize = { width: 1200, height: 500 } ) {
public projectDirName = '' this.page = originalPage
this.originalPage = originalPage
public page!: Page this.browserContext = browserContext
public context!: BrowserContext // Will be overwritten in the initializer
this.context = browserContext
constructor() {} this.testInfo = testInfo
}
// Help remote end by signaling we're done with the connection. async initialise(
// If it takes longer than 10s to stop, just resolve. arg: {
async makeAvailableAgain() { fixtures: Partial<Fixtures>
await this.page.evaluate(async () => { folderSetupFn?: (projectDirName: string) => Promise<void>
return new Promise((resolve) => { cleanProjectDir?: boolean
if (!window.engineCommandManager.engineConnection?.state?.type) { appSettings?: DeepPartial<Settings>
return resolve(undefined) } = { fixtures: {} }
} ) {
const { electronApp, page, context, dir } = await setupElectron({
window.engineCommandManager.tearDown() testInfo: this.testInfo,
folderSetupFn: arg.folderSetupFn,
// Keep polling (per js event tick) until state is Disconnected. cleanProjectDir: arg.cleanProjectDir,
const timeA = Date.now() appSettings: arg.appSettings,
const checkDisconnected = () => { viewport: this.viewPortSize,
// It's possible we never even created an engineConnection
// e.g. never left Projects view.
if (
window.engineCommandManager?.engineConnection?.state.type ===
'disconnected'
) {
return resolve(undefined)
}
if (Date.now() - timeA > 10000) {
return resolve(undefined)
}
setTimeout(checkDisconnected, 0)
}
checkDisconnected()
})
}) })
this.page = page
await this.context.tracing.stopChunk({ path: 'trace.zip' }) // These assignments "fix" some brokenness in the Playwright Workbench when
// running against electron applications.
// The timeline is still broken but failure screenshots work again.
this.context = context
// TODO: try to get this to work again for screenshots, but it messed with test ends when enabled
// Object.assign(this.browserContext, this.context)
// Only after cleanup we're ready. this.electronApp = electronApp
this.available = true this.dir = dir
// Easier to access throughout utils
this.page.dir = dir
// Setup localStorage, addCookies, reload
await setup(this.context, this.page, this.testInfo)
for (const key of unsafeTypedKeys(arg.fixtures)) {
const fixture = arg.fixtures[key]
if (
!fixture ||
fixture instanceof AuthenticatedApp ||
fixture instanceof AuthenticatedTronApp
)
continue
fixture.reConstruct(page)
}
} }
async createInstanceIfMissing(testInfo: TestInfo) { close = async () => {
// Create or otherwise clear the folder. await this.electronApp?.close?.()
this.projectDirName = testInfo.outputPath('electron-test-projects-dir')
// We need to expose this in order for some tests that require folder
// creation and some code below.
const that = this
const options = {
timeout: 120000,
args: ['.', '--no-sandbox'],
env: {
...process.env,
TEST_SETTINGS_FILE_KEY: this.projectDirName,
IS_PLAYWRIGHT: 'true',
},
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
? {
executablePath:
process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron',
}
: {}),
...(process.env.PLAYWRIGHT_RECORD_VIDEO
? {
recordVideo: {
dir: testInfo.snapshotPath(),
size: this.viewPortSize,
},
}
: {}),
}
// Do this once and then reuse window on subsequent calls.
if (!this.electron) {
this.electron = await electron.launch(options)
// Mac takes quite a long time to create the first window in CI.
// Turns out we can't trust firstWindow() either. So loop.
let timeoutId: ReturnType<typeof setTimeout>
const tryToGetWindowPage = () =>
new Promise((resolve) => {
const fn = () => {
this.page = this.electron.windows()[0]
timeoutId = setTimeout(() => {
if (this.page) {
clearTimeout(timeoutId)
return resolve(undefined)
}
fn()
}, 0)
}
fn()
})
await tryToGetWindowPage()
this.context = this.electron.context()
await this.context.tracing.start({ screenshots: true, snapshots: true })
}
await this.context.tracing.startChunk()
await setup(this.context, this.page, testInfo)
await this.cleanProjectDir()
// Create a consistent way to resize the page across electron and web.
// (lee) I had to do everything in the book to make electron change its
// damn window size. I succeeded in making it consistently and reliably
// do it after a whole afternoon.
this.page.setBodyDimensions = async function (dims: {
width: number
height: number
}) {
await this.setViewportSize(dims)
await that.electron?.evaluateHandle(async ({ app }, dims) => {
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
await app.resizeWindow(dims.width, dims.height)
}, dims)
return this.evaluate(async (dims: { width: number; height: number }) => {
await window.electron.resizeWindow(dims.width, dims.height)
window.document.body.style.width = dims.width + 'px'
window.document.body.style.height = dims.height + 'px'
window.document.documentElement.style.width = dims.width + 'px'
window.document.documentElement.style.height = dims.height + 'px'
}, dims)
}
await this.page.setBodyDimensions(this.viewPortSize)
this.context.folderSetupFn = async function (fn) {
return fn(that.projectDirName)
.then(() => that.page.reload())
.then(() => ({
dir: that.projectDirName,
}))
}
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = this.context.addInitScript
this.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await that.page.reload()
}
// No idea why we mix and match page and context's addInitScript but we do
const oldPageAddInitScript = this.page.addInitScript
this.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await that.page.reload()
}
if (!this.firstUrl) {
await this.page.getByText('Your Projects').count()
this.firstUrl = this.page.url()
}
// Due to the app controlling its own window context we need to inject new
// options and context here.
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
// await tronApp.electronApp.evaluate(({ app }) => {
// return app.reuseWindowForTest();
// });
await this.electron?.evaluate(({ app }, projectDirName) => {
// @ts-ignore can't declaration merge see main.ts
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
}, this.projectDirName)
// Always start at the root view
await this.page.goto(this.firstUrl)
// Force a hard reload, destroying the stream and other state
await this.page.reload()
}
async cleanProjectDir(appSettings?: DeepPartial<Settings>) {
try {
if (fs.existsSync(this.projectDirName)) {
await fsp.rm(this.projectDirName, { recursive: true })
}
} catch (e) {
console.error(e)
}
try {
await fsp.mkdir(this.projectDirName)
} catch (e) {
// Not a problem if it already exists.
}
const tempSettingsFilePath = path.join(
this.projectDirName,
SETTINGS_FILE_NAME
)
let settingsOverridesToml = ''
if (appSettings) {
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
project_directory: this.projectDirName,
...appSettings.app,
},
},
})
} else {
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
project_directory: this.projectDirName,
},
},
})
}
await fsp.writeFile(tempSettingsFilePath, settingsOverridesToml)
} }
debugPause = () =>
new Promise(() => {
console.log('UN-RESOLVING PROMISE')
})
} }
// If yee encounter this, please try to type it. export const fixtures = {
type FnUse = any cmdBar: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const fixturesForElectron = {
page: async (
{ tronApp }: { tronApp: ElectronZoo },
use: FnUse,
testInfo: TestInfo
) => {
await use(tronApp.page)
},
context: async (
{ tronApp }: { tronApp: ElectronZoo },
use: FnUse,
testInfo: TestInfo
) => {
await use(tronApp.context)
},
}
const fixturesForWeb = {
page: async (
{ page, context }: { page: Page; context: BrowserContext },
use: FnUse,
testInfo: TestInfo
) => {
page.setBodyDimensions = page.setViewportSize
// We do the same thing in ElectronZoo. addInitScript simply doesn't fire
// at the correct time, so we reload the page and it fires appropriately.
const oldPageAddInitScript = page.addInitScript
page.addInitScript = async function (...args) {
// @ts-expect-error
await oldPageAddInitScript.apply(this, args)
await page.reload()
}
const oldContextAddInitScript = context.addInitScript
context.addInitScript = async function (...args) {
// @ts-expect-error
await oldContextAddInitScript.apply(this, args)
await page.reload()
}
const webApp = new AuthenticatedApp(context, page, testInfo)
await webApp.initialise()
await use(page)
},
}
const fixturesBasedOnProcessEnvPlatform = {
cmdBar: async ({ page }: { page: Page }, use: FnUse) => {
await use(new CmdBarFixture(page)) await use(new CmdBarFixture(page))
}, },
editor: async ({ page }: { page: Page }, use: FnUse) => { editor: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new EditorFixture(page)) await use(new EditorFixture(page))
}, },
toolbar: async ({ page }: { page: Page }, use: FnUse) => { toolbar: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new ToolbarFixture(page)) await use(new ToolbarFixture(page))
}, },
scene: async ({ page }: { page: Page }, use: FnUse) => { scene: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new SceneFixture(page)) await use(new SceneFixture(page))
}, },
homePage: async ({ page }: { page: Page }, use: FnUse) => { homePage: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new HomePageFixture(page)) await use(new HomePageFixture(page))
}, },
} }
if (process.env.PLATFORM === 'web') {
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForWeb)
} else {
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForElectron)
}
export { fixturesBasedOnProcessEnvPlatform }

View File

@ -27,6 +27,10 @@ export class HomePageFixture {
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.projectSection = this.page.getByTestId('home-section') this.projectSection = this.page.getByTestId('home-section')
@ -92,12 +96,8 @@ export class HomePageFixture {
} }
} }
projectsLoaded = async () => {
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
}
createAndGoToProject = async (projectTitle = 'project-$nnn') => { createAndGoToProject = async (projectTitle = 'project-$nnn') => {
await this.projectsLoaded() await expect(this.projectSection).not.toHaveText('Loading your Projects...')
await this.projectButtonNew.click() await this.projectButtonNew.click()
await this.projectTextName.click() await this.projectTextName.click()
await this.projectTextName.fill(projectTitle) await this.projectTextName.fill(projectTitle)

View File

@ -53,12 +53,7 @@ export class SceneFixture {
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.streamWrapper = page.getByTestId('stream') this.reConstruct(page)
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
} }
private _serialiseScene = async (): Promise<SceneSerialised> => { private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo() const camera = await this.getCameraInfo()
@ -77,6 +72,17 @@ export class SceneFixture {
.toEqual(expected) .toEqual(expected)
} }
reConstruct = (page: Page) => {
this.page = page
this.streamWrapper = page.getByTestId('stream')
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
}
makeMouseHelpers = ( makeMouseHelpers = (
x: number, x: number,
y: number, y: number,
@ -247,7 +253,7 @@ export class SceneFixture {
await u.openDebugPanel() await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel() await u.clearAndCloseDebugPanel()
await this.waitForExecutionDone() await this.waitForExecutionDone()
await expect(this.startEditSketchBtn).not.toBeDisabled() await expect(this.startEditSketchBtn).not.toBeDisabled()

View File

@ -8,7 +8,6 @@ import {
} from '../test-utils' } from '../test-utils'
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes' import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants' import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
import { ToolbarModeName } from 'lib/toolbar'
export class ToolbarFixture { export class ToolbarFixture {
public page: Page public page: Page
@ -29,6 +28,7 @@ export class ToolbarFixture {
rectangleBtn!: Locator rectangleBtn!: Locator
lengthConstraintBtn!: Locator lengthConstraintBtn!: Locator
exitSketchBtn!: Locator exitSketchBtn!: Locator
editSketchBtn!: Locator
fileTreeBtn!: Locator fileTreeBtn!: Locator
createFileBtn!: Locator createFileBtn!: Locator
fileCreateToast!: Locator fileCreateToast!: Locator
@ -38,12 +38,13 @@ export class ToolbarFixture {
featureTreeId = 'feature-tree' as const featureTreeId = 'feature-tree' as const
/** The pane element for the Feature Tree */ /** The pane element for the Feature Tree */
featureTreePane!: Locator featureTreePane!: Locator
gizmo!: Locator
gizmoDisabled!: Locator
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.extrudeButton = page.getByTestId('extrude') this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft') this.loftButton = page.getByTestId('loft')
this.sweepButton = page.getByTestId('sweep') this.sweepButton = page.getByTestId('sweep')
@ -60,6 +61,7 @@ export class ToolbarFixture {
this.rectangleBtn = page.getByTestId('corner-rectangle') this.rectangleBtn = page.getByTestId('corner-rectangle')
this.lengthConstraintBtn = page.getByTestId('constraint-length') this.lengthConstraintBtn = page.getByTestId('constraint-length')
this.exitSketchBtn = page.getByTestId('sketch-exit') this.exitSketchBtn = page.getByTestId('sketch-exit')
this.editSketchBtn = page.locator('[name="Edit Sketch"]')
this.fileTreeBtn = page.locator('[id="files-button-holder"]') this.fileTreeBtn = page.locator('[id="files-button-holder"]')
this.createFileBtn = page.getByTestId('create-file-button') this.createFileBtn = page.getByTestId('create-file-button')
this.treeInputField = page.getByTestId('tree-input-field') this.treeInputField = page.getByTestId('tree-input-field')
@ -67,17 +69,6 @@ export class ToolbarFixture {
this.filePane = page.locator('#files-pane') this.filePane = page.locator('#files-pane')
this.featureTreePane = page.locator('#feature-tree-pane') this.featureTreePane = page.locator('#feature-tree-pane')
this.fileCreateToast = page.getByText('Successfully created') this.fileCreateToast = page.getByText('Successfully created')
// Note to test writers: having two locators like this is preferable to one
// which changes another el property because it means our test "signal" is
// completely decoupled from the elements themselves. It means the same
// element or two different elements can represent these states.
this.gizmo = page.getByTestId('gizmo')
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
}
get editSketchBtn() {
return this.page.locator('[name="Edit Sketch"]')
} }
get logoLink() { get logoLink() {
@ -93,18 +84,6 @@ export class ToolbarFixture {
startSketchPlaneSelection = async () => startSketchPlaneSelection = async () =>
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500) doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
waitUntilSketchingReady = async () => {
await expect(this.gizmoDisabled).toBeVisible()
}
startSketchThenCallbackThenWaitUntilReady = async (
cb: () => Promise<void>
) => {
await this.startSketchBtn.click()
await cb()
await this.waitUntilSketchingReady()
}
exitSketch = async () => { exitSketch = async () => {
await this.exitSketchBtn.click() await this.exitSketchBtn.click()
await expect( await expect(
@ -121,15 +100,6 @@ export class ToolbarFixture {
// this is for the engine animation, as it takes 500ms to complete // this is for the engine animation, as it takes 500ms to complete
await this.page.waitForTimeout(600) await this.page.waitForTimeout(600)
} }
private _getMode = () =>
this.page.locator('[data-current-mode]').getAttribute('data-current-mode')
expectToolbarMode = {
toBe: (mode: ToolbarModeName) => expect.poll(this._getMode).toEqual(mode),
not: {
toBe: (mode: ToolbarModeName) =>
expect.poll(this._getMode).not.toEqual(mode),
},
}
private _serialiseFileTree = async () => { private _serialiseFileTree = async () => {
return this.page return this.page
@ -186,22 +156,6 @@ export class ToolbarFixture {
).toBeVisible() ).toBeVisible()
await this.page.getByTestId('dropdown-circle-three-points').click() await this.page.getByTestId('dropdown-circle-three-points').click()
} }
selectArc = async () => {
await this.page
.getByRole('button', { name: 'caret down Tangential Arc:' })
.click()
await expect(this.page.getByTestId('dropdown-arc')).toBeVisible()
await this.page.getByTestId('dropdown-arc').click()
}
selectThreePointArc = async () => {
await this.page
.getByRole('button', { name: 'caret down Tangential Arc:' })
.click()
await expect(
this.page.getByTestId('dropdown-three-point-arc')
).toBeVisible()
await this.page.getByTestId('dropdown-three-point-arc').click()
}
async closePane(paneId: SidebarType) { async closePane(paneId: SidebarType) {
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX) return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)

View File

@ -21,54 +21,58 @@ import { expectPixelColor } from './fixtures/sceneFixture'
// we must set it to empty for the tests where we want to see the onboarding immediately. // we must set it to empty for the tests where we want to see the onboarding immediately.
test.describe('Onboarding tests', () => { test.describe('Onboarding tests', () => {
test('Onboarding code is shown in the editor', async ({ test(
page, 'Onboarding code is shown in the editor',
homePage, {
tronApp, appSettings: {
}) => { app: {
if (!tronApp) { onboarding_status: '',
fail() },
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
}, },
}) cleanProjectDir: true,
},
async ({ page, homePage }) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
const u = await getUtils(page) // Test that the onboarding pane loaded
await page.setBodyDimensions({ width: 1200, height: 500 }) await expect(
await homePage.goToModelingScene() page.getByText('Welcome to Modeling App! This')
).toBeVisible()
// Test that the onboarding pane loaded // Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible() await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
// Test that the onboarding pane loaded // *and* that the code is shown in the editor
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible() await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
// *and* that the code is shown in the editor // Make sure the model loaded
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') const XYPlanePoint = { x: 774, y: 116 } as const
const modelColor: [number, number, number] = [45, 45, 45]
// Make sure the model loaded await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
const XYPlanePoint = { x: 774, y: 116 } as const expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(
const modelColor: [number, number, number] = [45, 45, 45] 8
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) )
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8) }
}) )
test( test(
'Desktop: fresh onboarding executes and loads', 'Desktop: fresh onboarding executes and loads',
{ {
tag: '@electron', tag: '@electron',
}, appSettings: {
async ({ page, tronApp }) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
onboarding_status: '', onboarding_status: '',
}, },
}) },
cleanProjectDir: true,
},
async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 } const viewportSize = { width: 1200, height: 500 }
@ -103,235 +107,223 @@ test.describe('Onboarding tests', () => {
} }
) )
test('Code resets after confirmation', async ({ test(
context, 'Code resets after confirmation',
page, {
homePage, cleanProjectDir: true,
tronApp, },
scene, async ({ context, page, homePage }) => {
cmdBar, const initialCode = `sketch001 = startSketchOn('XZ')`
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir()
const initialCode = `sketch001 = startSketchOn('XZ')` // Load the page up with some code so we see the confirmation warning
// when we go to replay onboarding
await context.addInitScript((code) => {
localStorage.setItem('persistCode', code)
}, initialCode)
// Load the page up with some code so we see the confirmation warning await page.setBodyDimensions({ width: 1200, height: 500 })
// when we go to replay onboarding await homePage.goToModelingScene()
await page.addInitScript((code) => {
localStorage.setItem('persistCode', code)
}, initialCode)
await page.setBodyDimensions({ width: 1200, height: 500 }) // Replay the onboarding
await homePage.goToModelingScene() await page.getByRole('link', { name: 'Settings' }).last().click()
await scene.connectionEstablished() const replayButton = page.getByRole('button', {
name: 'Replay onboarding',
})
await expect(replayButton).toBeVisible()
await replayButton.click()
// Replay the onboarding // Ensure we see the warning, and that the code has not yet updated
await page.getByRole('link', { name: 'Settings' }).last().click() await expect(page.getByText('Would you like to create')).toBeVisible()
const replayButton = page.getByRole('button', { await expect(page.locator('.cm-content')).toHaveText(initialCode)
name: 'Replay onboarding',
})
await expect(replayButton).toBeVisible()
await replayButton.click()
// Ensure we see the warning, and that the code has not yet updated const nextButton = page.getByTestId('onboarding-next')
await expect(page.getByText('Would you like to create')).toBeVisible()
await expect(page.locator('.cm-content')).toHaveText(initialCode)
const nextButton = page.getByTestId('onboarding-next')
await nextButton.hover()
await nextButton.click()
// Ensure we see the introduction and that the code has been reset
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
// There used to be old code here that checked if we stored the reset
// code into localStorage but that isn't the case on desktop. It gets
// saved to the file system, which we have other tests for.
})
test('Click through each onboarding step and back', async ({
context,
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
},
})
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings }) => {
// Give no initial code, so that the onboarding start is shown immediately
localStorage.setItem('persistCode', '')
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_START,
}),
}
)
await page.setBodyDimensions({ width: 1200, height: 1080 })
await homePage.goToModelingScene()
// Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
const nextButton = page.getByTestId('onboarding-next')
const prevButton = page.getByTestId('onboarding-prev')
while ((await nextButton.innerText()) !== 'Finish') {
await nextButton.hover() await nextButton.hover()
await nextButton.click() await nextButton.click()
}
while ((await prevButton.innerText()) !== 'Dismiss') { // Ensure we see the introduction and that the code has been reset
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
// There used to be old code here that checked if we stored the reset
// code into localStorage but that isn't the case on desktop. It gets
// saved to the file system, which we have other tests for.
}
)
test(
'Click through each onboarding step and back',
{
appSettings: {
app: {
onboarding_status: '',
},
},
},
async ({ context, page, homePage }) => {
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings }) => {
// Give no initial code, so that the onboarding start is shown immediately
localStorage.setItem('persistCode', '')
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_START,
}),
}
)
await page.setBodyDimensions({ width: 1200, height: 1080 })
await homePage.goToModelingScene()
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
const nextButton = page.getByTestId('onboarding-next')
const prevButton = page.getByTestId('onboarding-prev')
while ((await nextButton.innerText()) !== 'Finish') {
await nextButton.hover()
await nextButton.click()
}
while ((await prevButton.innerText()) !== 'Dismiss') {
await prevButton.hover()
await prevButton.click()
}
// Dismiss the onboarding
await prevButton.hover() await prevButton.hover()
await prevButton.click() await prevButton.click()
// Test that the onboarding pane is gone
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect.poll(() => page.url()).not.toContain('/onboarding')
} }
)
// Dismiss the onboarding test(
await prevButton.hover() 'Onboarding redirects and code updating',
await prevButton.click() {
appSettings: {
app: {
onboarding_status: '/export',
},
},
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
const originalCode = 'sigmaAllow = 15000'
// Test that the onboarding pane is gone // Override beforeEach test setup
await expect(page.getByTestId('onboarding-content')).not.toBeVisible() await context.addInitScript(
await expect.poll(() => page.url()).not.toContain('/onboarding') async ({ settingsKey, settings }) => {
}) // Give some initial code, so we can test that it's cleared
localStorage.setItem('persistCode', originalCode)
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
}),
}
)
test('Onboarding redirects and code updating', async ({ await page.setBodyDimensions({ width: 1200, height: 500 })
context, await homePage.goToModelingScene()
page,
homePage, // Test that the redirect happened
tronApp, await expect.poll(() => page.url()).toContain('/onboarding/export')
}) => {
if (!tronApp) { // Test that you come back to this page when you refresh
fail() await page.reload()
await expect.poll(() => page.url()).toContain('/onboarding/export')
// Test that the code changes when you advance to the next step
await page.getByTestId('onboarding-next').hover()
await page.getByTestId('onboarding-next').click()
// Test that the onboarding pane loaded
const title = page.locator('[data-testid="onboarding-content"]')
await expect(title).toBeAttached()
await expect(page.locator('.cm-content')).not.toHaveText(originalCode)
// Test that the code is not empty when you click on the next step
await page.locator('[data-testid="onboarding-next"]').hover()
await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText(/.+/)
} }
await tronApp.cleanProjectDir({ )
app: {
onboarding_status: '/export', test(
'Onboarding code gets reset to demo on Interactive Numbers step',
{
appSettings: {
app: {
onboarding_status: '/parametric-modeling',
},
}, },
}) cleanProjectDir: true,
},
const originalCode = 'sigmaAllow = 15000' async ({ page, homePage }) => {
const u = await getUtils(page)
const badCode = `// This is bad code we shouldn't see`
// Override beforeEach test setup await page.setBodyDimensions({ width: 1200, height: 1080 })
await context.addInitScript( await homePage.goToModelingScene()
async ({ settingsKey, settings }) => {
// Give some initial code, so we can test that it's cleared
localStorage.setItem('persistCode', originalCode)
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
}),
}
)
await page.setBodyDimensions({ width: 1200, height: 500 }) await expect
await homePage.goToModelingScene() .poll(() => page.url())
.toContain(onboardingPaths.PARAMETRIC_MODELING)
// Test that the redirect happened const bracketNoNewLines = bracket.replace(/\n/g, '')
await expect.poll(() => page.url()).toContain('/onboarding/export')
// Test that you come back to this page when you refresh // Check the code got reset on load
await page.reload() await expect(page.locator('#code-pane')).toBeVisible()
await expect.poll(() => page.url()).toContain('/onboarding/export') await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
timeout: 10_000,
})
// Test that the code changes when you advance to the next step // Mess with the code again
await page.getByTestId('onboarding-next').hover() await u.codeLocator.selectText()
await page.getByTestId('onboarding-next').click() await u.codeLocator.fill(badCode)
await expect(u.codeLocator).toHaveText(badCode)
// Test that the onboarding pane loaded // Click to the next step
const title = page.locator('[data-testid="onboarding-content"]') await page.locator('[data-testid="onboarding-next"]').hover()
await expect(title).toBeAttached() await page.locator('[data-testid="onboarding-next"]').click()
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
waitUntil: 'domcontentloaded',
})
await expect(page.locator('.cm-content')).not.toHaveText(originalCode) // Check that the code has been reset
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
// Test that the code is not empty when you click on the next step
await page.locator('[data-testid="onboarding-next"]').hover()
await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText(/.+/)
})
test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
} }
await tronApp.cleanProjectDir({ )
app: {
onboarding_status: '/parametric-modeling',
},
})
const u = await getUtils(page)
const badCode = `// This is bad code we shouldn't see`
await page.setBodyDimensions({ width: 1200, height: 1080 })
await homePage.goToModelingScene()
await expect
.poll(() => page.url())
.toContain(onboardingPaths.PARAMETRIC_MODELING)
const bracketNoNewLines = bracket.replace(/\n/g, '')
// Check the code got reset on load
await expect(page.locator('#code-pane')).toBeVisible()
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
timeout: 10_000,
})
// Mess with the code again
await u.codeLocator.selectText()
await u.codeLocator.fill(badCode)
await expect(u.codeLocator).toHaveText(badCode)
// Click to the next step
await page.locator('[data-testid="onboarding-next"]').hover()
await page.locator('[data-testid="onboarding-next"]').click()
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
waitUntil: 'domcontentloaded',
})
// Check that the code has been reset
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
})
// (lee) The two avatar tests are weird because even on main, we don't have // (lee) The two avatar tests are weird because even on main, we don't have
// anything to do with the avatar inside the onboarding test. Due to the // anything to do with the avatar inside the onboarding test. Due to the
// low impact of an avatar not showing I'm changing this to fixme. // low impact of an avatar not showing I'm changing this to fixme.
test.fixme( test.fixme(
'Avatar text updates depending on image load success', 'Avatar text updates depending on image load success',
async ({ context, page, homePage, tronApp }) => { {
if (!tronApp) { appSettings: {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
onboarding_status: '', onboarding_status: '',
}, },
}) },
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
// Override beforeEach test setup // Override beforeEach test setup
await context.addInitScript( await context.addInitScript(
async ({ settingsKey, settings }) => { async ({ settingsKey, settings }) => {
@ -396,16 +388,15 @@ test.describe('Onboarding tests', () => {
test.fixme( test.fixme(
"Avatar text doesn't mention avatar when no avatar", "Avatar text doesn't mention avatar when no avatar",
async ({ context, page, homePage, tronApp }) => { {
if (!tronApp) { appSettings: {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
onboarding_status: '', onboarding_status: '',
}, },
}) },
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
// Override beforeEach test setup // Override beforeEach test setup
await context.addInitScript( await context.addInitScript(
async ({ settingsKey, settings }) => { async ({ settingsKey, settings }) => {
@ -453,17 +444,15 @@ test.describe('Onboarding tests', () => {
test.fixme( test.fixme(
'Restarting onboarding on desktop takes one attempt', 'Restarting onboarding on desktop takes one attempt',
async ({ context, page, tronApp }) => { {
if (!tronApp) { appSettings: {
fail()
}
await tronApp.cleanProjectDir({
app: { app: {
onboarding_status: 'dismissed', onboarding_status: 'dismissed',
}, },
}) },
cleanProjectDir: true,
},
async ({ context, page }) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate') const routerTemplateDir = join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true }) await fsp.mkdir(routerTemplateDir, { recursive: true })

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