Compare commits

..

2 Commits

Author SHA1 Message Date
a74771c50d Change to only check formatting, not do it 2025-05-11 14:17:53 -04:00
cb330b99f0 Change biome fmt to be pre-push 2025-05-11 14:14:58 -04:00
240 changed files with 39893 additions and 13103 deletions

View File

@ -1,41 +1,13 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
exit 0 COMMIT="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
fi
project="https://github.com/KittyCAD/modeling-app" curl --request POST \
branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
echo "Uploading batch results"
curl --silent --request POST \
--header "X-API-Key: ${TAB_API_KEY}" \ --header "X-API-Key: ${TAB_API_KEY}" \
--form "project=${project}" \ --form "project=https://github.com/KittyCAD/modeling-app" \
--form "branch=${branch}" \ --form "branch=${BRANCH}" \
--form "commit=${commit}" \ --form "commit=${COMMIT}" \
--form "tests=@test-results/junit.xml" \ --form "tests=@test-results/junit.xml" \
--form "CI_COMMIT_SHA=${CI_COMMIT_SHA:-}" \
--form "CI_PR_NUMBER=${CI_PR_NUMBER:-}" \
--form "GITHUB_BASE_REF=${GITHUB_BASE_REF:-}" \
--form "GITHUB_EVENT_NAME=${GITHUB_EVENT_NAME:-}" \
--form "GITHUB_HEAD_REF=${GITHUB_HEAD_REF:-}" \
--form "GITHUB_REF_NAME=${GITHUB_REF_NAME:-}" \
--form "GITHUB_REF=${GITHUB_REF:-}" \
--form "GITHUB_SHA=${GITHUB_SHA:-}" \
--form "GITHUB_WORKFLOW=${GITHUB_WORKFLOW:-}" \
--form "RUNNER_ARCH=${RUNNER_ARCH:-}" \
${TAB_API_URL}/api/results/bulk ${TAB_API_URL}/api/results/bulk
echo
echo "Sharing updated report"
curl --silent --request POST \
--header "Content-Type: application/json" \
--header "X-API-Key: ${TAB_API_KEY}" \
--data "{
\"project\": \"${project}\",
\"branch\": \"${branch}\",
\"commit\": \"${commit}\"
}" \
${TAB_API_URL}/api/share

View File

@ -7,10 +7,11 @@ on:
- main - main
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+'
- 'nightly-v[0-9]+.[0-9]+.[0-9]+'
env: env:
IS_RELEASE: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} IS_RELEASE: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }}
IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} IS_NIGHTLY: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'nightly-v') }}
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -94,9 +95,7 @@ jobs:
- name: Set nightly version, product name, release notes, and icons - name: Set nightly version, product name, release notes, and icons
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
run: | run: |
COMMIT=$(git rev-parse --short HEAD) export VERSION=${GITHUB_REF_NAME#nightly-v}
DATE=$(date +'%-y.%-m.%-d')
export VERSION=$DATE-main.$COMMIT
npm run files:set-version npm run files:set-version
npm run files:flip-to-nightly npm run files:flip-to-nightly
@ -307,8 +306,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions: permissions:
contents: write contents: write
# Equivalent to IS_RELEASE || IS_NIGHTLY (but we can't access those env vars here) if: ${{ github.ref_type == 'tag' }}
if: ${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
env: env:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ format('v{0}', needs.prepare-files.outputs.version) }} VERSION: ${{ format('v{0}', needs.prepare-files.outputs.version) }}
@ -414,6 +412,17 @@ jobs:
- name: List artifacts - name: List artifacts
run: "ls -R out" run: "ls -R out"
- name: Set more complete nightly release notes
if: ${{ env.IS_NIGHTLY == 'true' }}
run: |
# Note: preferred going this way instead of a full clone in the checkout step,
# see https://github.com/actions/checkout/issues/1471
git fetch --prune --unshallow --tags
export TAG="nightly-${VERSION}"
export PREVIOUS_TAG=$(git tag --list --sort=-committerdate "nightly-v[0-9]*" | head -n2 | tail -n1)
export NOTES=$(./scripts/get-nightly-changelog.sh)
npm run files:set-notes
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
uses: 'google-github-actions/auth@v2.1.8' uses: 'google-github-actions/auth@v2.1.8'

View File

@ -188,8 +188,6 @@ jobs:
env: env:
TAB_API_URL: ${{ secrets.TAB_API_URL }} TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
run-wasm-tests: run-wasm-tests:
name: Run wasm tests name: Run wasm tests
strategy: strategy:

47
.github/workflows/check-exampleKcl.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Check Onboarding KCL
on:
pull_request:
types: [opened, synchronize]
paths:
- 'src/lib/exampleKcl.ts'
- 'public/kcl-samples/bracket/main.kcl'
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const message = '`public/kcl-samples/bracket/main.kcl` or `src/lib/exampleKcl.ts` has been updated in this PR, please review and update the `src/routes/onboarding`, if needed.';
const issue_number = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number
});
const commentExists = comments.some(comment => comment.body === message);
if (!commentExists) {
// Post a comment on the PR
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: message,
});
}

View File

@ -18,13 +18,73 @@ permissions:
jobs: jobs:
prepare-wasm: conditions:
# separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner runs-on: ubuntu-latest
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64 outputs:
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
if: ${{ github.event_name == 'pull_request' }}
run: git fetch origin ${{ github.base_ref }} --depth=1
- name: Check for path changes
id: path-changes
shell: bash
run: |
set -euo pipefail
# Manual runs or push should run all tests.
if [[ ${{ github.event_name }} != 'pull_request' ]]; then
echo "significant=true" >> $GITHUB_OUTPUT
exit 0
fi
changed_files=$(git diff --name-only origin/${{ github.base_ref }})
echo "$changed_files"
if grep -Evq '^README.md|^public/kcl-samples/|^rust/kcl-lib/tests/kcl_samples/' <<< "$changed_files" ; then
echo "significant=true" >> $GITHUB_OUTPUT
else
echo "significant=false" >> $GITHUB_OUTPUT
fi
- 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: |
# For debugging purposes
set -euo pipefail
echo "GITHUB_REF: $GITHUB_REF"
echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF"
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
echo "significant: ${{ steps.path-changes.outputs.significant }}"
echo "should-run: ${{ steps.should-run.outputs.should-run }}"
prepare-wasm:
# separate 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 - id: filter
if: needs.conditions.outputs.should-run == 'true'
name: Check for Rust changes name: Check for Rust changes
uses: dorny/paths-filter@v3 uses: dorny/paths-filter@v3
with: with:
@ -33,16 +93,18 @@ jobs:
- 'rust/**' - 'rust/**'
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
if: needs.conditions.outputs.should-run == 'true'
run: npm install run: npm install
- name: Download Wasm Cache - name: Download Wasm Cache
id: download-wasm id: download-wasm
if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }} if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7 uses: dawidd6/action-download-artifact@v7
continue-on-error: true continue-on-error: true
with: with:
@ -54,6 +116,7 @@ jobs:
- name: Build WASM condition - name: Build WASM condition
id: wasm id: wasm
if: needs.conditions.outputs.should-run == 'true'
run: | run: |
set -euox pipefail set -euox pipefail
# Build wasm if this is a scheduled run, there are Rust changes, or # Build wasm if this is a scheduled run, there are Rust changes, or
@ -65,34 +128,35 @@ jobs:
fi fi
- name: Use correct Rust toolchain - name: Use correct Rust toolchain
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash shell: bash
run: | run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ [ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust - name: Install rust
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
cache: false # Configured below. cache: false # Configured below.
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc - uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
with: with:
tool: wasm-pack tool: wasm-pack
- name: Rust Cache - name: Rust Cache
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
workspaces: './rust' workspaces: './rust'
- name: Build Wasm - name: Build Wasm
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash shell: bash
run: npm run build:wasm run: npm run build:wasm
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
name: prepared-wasm name: prepared-wasm
path: | path: |
@ -101,9 +165,10 @@ jobs:
snapshots: snapshots:
name: playwright:snapshots:ubuntu name: playwright:snapshots:ubuntu
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64 runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: [prepare-wasm] needs: [conditions, prepare-wasm]
steps: steps:
- uses: actions/create-github-app-token@v1 - uses: actions/create-github-app-token@v1
if: needs.conditions.outputs.should-run == 'true'
id: app-token id: app-token
with: with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }} app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
@ -111,13 +176,16 @@ jobs:
owner: ${{ github.repository_owner }} owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4 - uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
token: ${{ steps.app-token.outputs.token }} token: ${{ steps.app-token.outputs.token }}
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm name: prepared-wasm
- name: Copy prepared wasm - name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
run: | run: |
ls -R prepared-wasm ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -125,15 +193,18 @@ jobs:
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
id: deps-install id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: npm install run: npm install
- name: Cache Playwright Browsers - name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
@ -141,12 +212,15 @@ jobs:
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
run: npm run playwright install --with-deps run: npm run playwright install --with-deps
- name: build web - name: build web
if: needs.conditions.outputs.should-run == 'true'
run: npm run tronb:vite:dev run: npm run tronb:vite:dev
- name: Run ubuntu/chrome snapshots - name: Run ubuntu/chrome snapshots
if: needs.conditions.outputs.should-run == 'true'
uses: nick-fields/retry@v3.0.2 uses: nick-fields/retry@v3.0.2
with: with:
shell: bash shell: bash
@ -162,7 +236,7 @@ jobs:
TARGET: web TARGET: web
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }} if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
with: with:
name: playwright-report-ubuntu-snapshot-${{ github.sha }} name: playwright-report-ubuntu-snapshot-${{ github.sha }}
path: playwright-report/ path: playwright-report/
@ -171,7 +245,7 @@ jobs:
overwrite: true overwrite: true
- name: Check for changes - name: Check for changes
if: ${{ github.ref != 'refs/heads/main' }} if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }}
shell: bash shell: bash
id: git-check id: git-check
run: | run: |
@ -183,7 +257,7 @@ jobs:
- name: Commit changes, if any - name: Commit changes, if any
# TODO: find a more reliable way to detect visual changes # TODO: find a more reliable way to detect visual changes
if: ${{ false && steps.git-check.outputs.modified == 'true' }} if: ${{ false && needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
shell: bash shell: bash
run: | run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
@ -198,7 +272,7 @@ jobs:
git push origin ${{ github.head_ref }} git push origin ${{ github.head_ref }}
electron: electron:
needs: [prepare-wasm] needs: [conditions, prepare-wasm]
timeout-minutes: 60 timeout-minutes: 60
env: env:
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }} OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
@ -241,11 +315,14 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm name: prepared-wasm
- name: Copy prepared wasm - name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
run: | run: |
ls -R prepared-wasm ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -253,15 +330,18 @@ jobs:
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
id: deps-install id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: npm install run: npm install
- name: Cache Playwright Browsers - name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
@ -269,20 +349,22 @@ jobs:
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Browsers - name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
run: npm run playwright install --with-deps run: npm run playwright install --with-deps
- name: Build web - name: Build web
if: needs.conditions.outputs.should-run == 'true'
run: npm run tronb:vite:dev run: npm run tronb:vite:dev
- name: Start Vector - name: Start Vector
if: ${{ !contains(matrix.os, 'windows') }} if: ${{ needs.conditions.outputs.should-run == 'true' && !contains(matrix.os, 'windows') }}
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
env: env:
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }} GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
OS_NAME: ${{ env.OS_NAME }} OS_NAME: ${{ env.OS_NAME }}
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }} if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
continue-on-error: true continue-on-error: true
with: with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
@ -290,7 +372,7 @@ jobs:
- name: Run playwright/electron flow (with retries) - name: Run playwright/electron flow (with retries)
id: retry id: retry
if: ${{ !cancelled() && steps.deps-install.outcome == 'success' }} if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && steps.deps-install.outcome == 'success' }}
uses: nick-fields/retry@v3.0.2 uses: nick-fields/retry@v3.0.2
with: with:
shell: bash shell: bash
@ -307,7 +389,7 @@ jobs:
TARGET: desktop TARGET: desktop
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
with: with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/ path: test-results/
@ -316,7 +398,7 @@ jobs:
overwrite: true overwrite: true
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
with: with:
name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/

View File

@ -213,13 +213,7 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run playwright install chromium --with-deps run: npm run playwright install chromium --with-deps
- name: Download internal KCL samples - name: run unit tests for kcl samples
run: git clone --depth=1 https://x-access-token:${{ secrets.GH_PAT_KCL_SAMPLES_INTERNAL }}@github.com/KittyCAD/kcl-samples-internal public/kcl-samples/internal
- name: Regenerate KCL samples manifest
run: cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
- name: Check public and internal KCL samples
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run test:unit:kcl-samples run: npm run test:unit:kcl-samples
env: env:

39
.github/workflows/tag-nightly.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: tag-nightly
permissions:
contents: write
on:
schedule:
- cron: '0 4 * * *'
# Daily at 04:00 AM UTC
# Will checkout the last commit from the default branch (main as of 2023-10-04)
jobs:
tag-nightly:
runs-on: ubuntu-22.04
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- run: npm install
- name: Push tag
run: |
VERSION_NO_V=$(date +'%-y.%-m.%-d')
TAG="nightly-v$VERSION_NO_V"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git tag $TAG
git push origin tag $TAG

View File

@ -1 +0,0 @@
npm run fmt

1
.husky/pre-push Executable file
View File

@ -0,0 +1 @@
npm run fmt:check

View File

@ -23,7 +23,6 @@ endif
install: node_modules/.package-lock.json $(CARGO) $(WASM_PACK) ## Install dependencies install: node_modules/.package-lock.json $(CARGO) $(WASM_PACK) ## Install dependencies
node_modules/.package-lock.json: package.json package-lock.json node_modules/.package-lock.json: package.json package-lock.json
npm prune
npm install npm install
$(CARGO): $(CARGO):
@ -44,15 +43,15 @@ endif
# BUILD # BUILD
CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*) CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*)
KCL_SOURCES := $(wildcard public/kcl-samples/**/*.kcl)
RUST_SOURCES := $(wildcard rust/**/*.rs) RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx) REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts) TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx) VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
.PHONY: build .PHONY: build
build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js build: install public/kcl_wasm_lib_bg.wasm .vite/build/main.js
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES) public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES)
ifdef WINDOWS ifdef WINDOWS
@ -61,9 +60,6 @@ else
npm run build:wasm:dev npm run build:wasm:dev
endif endif
public/kcl-samples/manifest.json: $(KCL_SOURCES)
cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
.vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES) .vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
npm run tronb:vite:dev npm run tronb:vite:dev
@ -111,10 +107,8 @@ test: test-unit test-e2e
.PHONY: test-unit .PHONY: test-unit
test-unit: install ## Run the unit tests test-unit: install ## Run the unit tests
npm run test:rust npm run test:rust
npm run test:unit:components
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 ) @ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
npm run test:unit npm run test:unit
npm run test:unit:kcl-samples
.PHONY: test-e2e .PHONY: test-e2e
test-e2e: test-e2e-$(TARGET) test-e2e: test-e2e-$(TARGET)

View File

@ -1,32 +0,0 @@
---
title: "Arrays and ranges"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
Arrays are sequences of values.
Arrays can be written out as *array literals* using a sequence of expressions surrounded by square brackets, e.g., `['hello', 'world']` is an array of strings, `[x, x + 1, x + 2]` is an array of numbers (assuming `x` is a number), `[]` is an empty array, and `['hello', 42, true]` is a mixed array.
A value in an array can be accessed by indexing using square brackets where the index is a number, for example, `arr[0]`, `arr[42]`, `arr[i]` (where `arr` is an array and `i` is a (whole) number).
There are some useful functions for working with arrays in the standard library, see [std::array](/docs/kcl-std/modules/std-array) for details.
## Array types
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
## Ranges
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples:
```kcl,norun
[0..3] // [0, 1, 2, 3]
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
x = 2
[x..x+1] // [2, 3]
```
The units of the start and end numbers must be the same and the result inherits those units.

View File

@ -1,99 +0,0 @@
---
title: "Importing geometry from other CAD systems"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
`import` can also be used to import files from other CAD systems. The format of the statement is the
same as for KCL files. You can only import the whole file, not items from it. E.g.,
```norun
import "tests/inputs/cube.obj"
// Use `cube` just like a KCL object.
```
```kcl
import "tests/inputs/cube.sldprt" as cube
// Use `cube` just like a KCL object.
```
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
unit of measurement is millimeters. Alternatively you may specify the unit
by using an attribute. Likewise, you can also specify a coordinate system. E.g.,
```kcl
@(lengthUnit = ft, coords = opengl)
import "tests/inputs/cube.obj"
```
When importing a GLTF file, the bin file will be imported as well.
Import paths are relative to the current project directory. Imports currently only work when
using the native Design Studio, not in the browser.
### Supported values
File formats: `fbx`, `gltf`/`glb`, `obj`+, `ply`+, `sldprt`, `step`/`stp`, `stl`+. (Those marked with a
'+' support customising the length unit and coordinate system).
Length units: `mm` (the default), `cm`, `m`, `inch`, `ft`, `yd`.
Coordinate systems:
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
- `opengl`, forward: +Z, up: +Y, handedness: right
- `vulkan`, forward: +Z, up: -Y, handedness: left
---
## Performance deepdive for foreignfile imports
Parallelized foreignfile imports now let you overlap file reads, initialization,
and rendering. To maximize throughput, you need to understand the three distinct
stages—reading, initializing (background render start), and invocation (blocking)
—and structure your code to defer blocking operations until the end.
### Foreign import execution stages
1. **Import (Read / Initialization) Stage**
```kcl
import "tests/inputs/cube.step" as cube
```
- Reads the file from disk and makes its API available.
- Starts engine rendering but **does not block** your script.
- This kickstarts the render pipeline while you keep executing other code.
2. **Invocation (Blocking) Stage**
```kcl
import "tests/inputs/cube.step" as cube
cube
|> translate(z=10) // ← blocks here only
```
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
### Best practices
#### 1. Defer blocking calls
```kcl
import "tests/inputs/cube.step" as cube // 1) Read / Background render starts
// --- perform other operations and calculations here ---
cube
|> translate(z=10) // 2) Blocks only here
```
#### 2. Split heavy work into separate modules
Place computationally expensive or IOheavy work into its own module so it can render in parallel while `main.kcl` continues.
#### Future improvements
Upcoming releases will autoanalyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.

View File

@ -5,7 +5,7 @@ layout: manual
--- ---
This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std). things in a more tutorial fashion.
## Topics ## Topics
@ -14,9 +14,7 @@ things in a more tutorial fashion. See also our documentation of the [standard l
* [Values and types](/docs/kcl-lang/types) * [Values and types](/docs/kcl-lang/types)
* [Numeric types and units](/docs/kcl-lang/numeric) * [Numeric types and units](/docs/kcl-lang/numeric)
* [Functions](/docs/kcl-lang/functions) * [Functions](/docs/kcl-lang/functions)
* [Arrays and ranges](/docs/kcl-lang/arrays) * [Projects, modules, and imports](/docs/kcl-lang/modules)
* [Projects and modules](/docs/kcl-lang/modules)
* [Attributes](/docs/kcl-lang/attributes) * [Attributes](/docs/kcl-lang/attributes)
* [Importing geometry from other CAD systems](/docs/kcl-lang/foreign-imports)
* [Settings](/docs/kcl-lang/settings) * [Settings](/docs/kcl-lang/settings)
* [Known Issues](/docs/kcl-lang/known-issues) * [Known Issues](/docs/kcl-lang/known-issues)

View File

@ -1,5 +1,5 @@
--- ---
title: "Projects and modules" title: "Modules"
excerpt: "Documentation of the KCL language for the Zoo Design Studio." excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual layout: manual
--- ---
@ -264,3 +264,102 @@ cube
clone(cube) clone(cube)
|> translate(x=20) |> translate(x=20)
``` ```
---
## Importing files from other CAD systems
`import` can also be used to import files from other CAD systems. The format of the statement is the
same as for KCL files. You can only import the whole file, not items from it. E.g.,
```norun
import "tests/inputs/cube.obj"
// Use `cube` just like a KCL object.
```
```kcl
import "tests/inputs/cube.sldprt" as cube
// Use `cube` just like a KCL object.
```
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
unit of measurement is millimeters. Alternatively you may specify the unit
by using an attribute. Likewise, you can also specify a coordinate system. E.g.,
```kcl
@(lengthUnit = ft, coords = opengl)
import "tests/inputs/cube.obj"
```
When importing a GLTF file, the bin file will be imported as well.
Import paths are relative to the current project directory. Imports currently only work when
using the native Design Studio, not in the browser.
### Supported values
File formats: `fbx`, `gltf`/`glb`, `obj`+, `ply`+, `sldprt`, `step`/`stp`, `stl`+. (Those marked with a
'+' support customising the length unit and coordinate system).
Length units: `mm` (the default), `cm`, `m`, `inch`, `ft`, `yd`.
Coordinate systems:
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
- `opengl`, forward: +Z, up: +Y, handedness: right
- `vulkan`, forward: +Z, up: -Y, handedness: left
---
## Performance deepdive for foreignfile imports
Parallelized foreignfile imports now let you overlap file reads, initialization,
and rendering. To maximize throughput, you need to understand the three distinct
stages—reading, initializing (background render start), and invocation (blocking)
—and structure your code to defer blocking operations until the end.
### Foreign import execution stages
1. **Import (Read / Initialization) Stage**
```kcl
import "tests/inputs/cube.step" as cube
```
- Reads the file from disk and makes its API available.
- Starts engine rendering but **does not block** your script.
- This kickstarts the render pipeline while you keep executing other code.
2. **Invocation (Blocking) Stage**
```kcl
import "tests/inputs/cube.step" as cube
cube
|> translate(z=10) // ← blocks here only
```
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
### Best practices
#### 1. Defer blocking calls
```kcl
import "tests/inputs/cube.step" as cube // 1) Read / Background render starts
// --- perform other operations and calculations here ---
cube
|> translate(z=10) // 2) Blocks only here
```
#### 2. Split heavy work into separate modules
Place computationally expensive or IOheavy work into its own module so it can render in parallel while `main.kcl` continues.
#### Future improvements
Upcoming releases will autoanalyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.

View File

@ -28,8 +28,6 @@ Any of the suffixes described above can be used meaning that values with that ty
You can also use `number(Length)`, `number(Angle)`, or `number(Count)`. These types mean a number with any length, angle, or unitless (count) units, respectively (note that `number(_)` and `number(Count)` are equivalent since there is only one kind of unitless-ness). You can also use `number(Length)`, `number(Angle)`, or `number(Count)`. These types mean a number with any length, angle, or unitless (count) units, respectively (note that `number(_)` and `number(Count)` are equivalent since there is only one kind of unitless-ness).
Using just `number` means accepting any kind of number, even where the units are unknown by KCL.
## Function calls ## Function calls

View File

@ -19,6 +19,18 @@ myBool = false
Currently you cannot redeclare a constant. Currently you cannot redeclare a constant.
## Arrays
An array is defined with `[]` braces. What is inside the brackets can
be of any type. For example, the following is completely valid:
```
myArray = ["thing", 2, false]
```
If you want to get a value from an array you can use the index like so:
`myArray[0]`.
## Objects ## Objects
@ -28,8 +40,8 @@ An object is defined with `{}` braces. Here is an example object:
myObj = { a = 0, b = "thing" } myObj = { a = 0, b = "thing" }
``` ```
To get the property of an object, you can call `myObj.a`, which in the above We support two different ways of getting properties from objects, you can call
example returns 0. `myObj.a` or `myObj["a"]` both work.
## `ImportedGeometry` ## `ImportedGeometry`

View File

@ -22,14 +22,14 @@ This will work on any solid, including extruded solids, revolved solids, and she
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid(s) whose appearance is being set | Yes | | `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid(s) whose appearance is being set | Yes |
| `color` | [`string`](/docs/kcl-std/types/std-types-string) | Color of the new material, a hex string like '#ff0000' | Yes | | `color` | [`string`](/docs/kcl-std/types/std-types-string) | Color of the new material, a hex string like '#ff0000' | Yes |
| `metalness` | [`number`](/docs/kcl-std/types/std-types-number) | Metalness of the new material, a percentage like 95.7. | No | | `metalness` | [`number`](/docs/kcl-std/types/std-types-number) | Metalness of the new material, a percentage like 95.7. | No |
| `roughness` | [`number`](/docs/kcl-std/types/std-types-number) | Roughness of the new material, a percentage like 95.7. | No | | `roughness` | [`number`](/docs/kcl-std/types/std-types-number) | Roughness of the new material, a percentage like 95.7. | No |
### Returns ### Returns
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid or an imported geometry. [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid or an imported geometry.
### Examples ### Examples

File diff suppressed because one or more lines are too long

View File

@ -11,13 +11,7 @@ The value of `pi`, Archimedes constant (π).
PI: number(_?) = 3.14159265358979323846264338327950288_? PI: number(_?) = 3.14159265358979323846264338327950288_?
``` ```
`PI` is a number and is technically a ratio, so you might expect it to have type `number(_)`.
However, `PI` is nearly always used for converting between different units - usually degrees to or
from radians. Therefore, `PI` is treated a bit specially by KCL and always has unknown units. This
means that if you use `PI`, you will need to give KCL some extra information about the units of numbers.
Usually you should use type ascription on the result of calculations, e.g., `(2 * PI): number(rad)`.
You might prefer to use `units::toRadians` or `units::toDegrees` to convert between angles with
different units.
### Examples ### Examples

View File

@ -26,7 +26,7 @@ You can provide more than one sketch to extrude, and they will all be extruded i
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch or sketches should be extruded | Yes | | `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch or sketches should be extruded | Yes |
| `length` | [`number`](/docs/kcl-std/types/std-types-number) | How far to extrude the given sketches | Yes | | `length` | [`number`](/docs/kcl-std/types/std-types-number) | How far to extrude the given sketches | Yes |
| `symmetric` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch. | No | | `symmetric` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the extrusion will happen symmetrically around the sketch. Otherwise, the | No |
| `bidirectionalLength` | [`number`](/docs/kcl-std/types/std-types-number) | If specified, will also extrude in the opposite direction to 'distance' to the specified distance. If 'symmetric' is true, this value is ignored. | No | | `bidirectionalLength` | [`number`](/docs/kcl-std/types/std-types-number) | If specified, will also extrude in the opposite direction to 'distance' to the specified distance. If 'symmetric' is true, this value is ignored. | No |
| `tagStart` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the start of the extrusion, i.e. the original sketch | No | | `tagStart` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the start of the extrusion, i.e. the original sketch | No |
| `tagEnd` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the end of the extrusion, i.e. the new face created by extruding the original sketch | No | | `tagEnd` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the end of the extrusion, i.e. the new face created by extruding the original sketch | No |

View File

@ -1,38 +0,0 @@
---
title: "legAngX"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for x."
layout: manual
---
Compute the angle of the given leg for x.
```kcl
legAngX(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngX(hypotenuse = 5, leg = 3)
```

View File

@ -1,38 +0,0 @@
---
title: "legAngY"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for y."
layout: manual
---
Compute the angle of the given leg for y.
```kcl
legAngY(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngY(hypotenuse = 5, leg = 3)
```

View File

@ -1,38 +0,0 @@
---
title: "legLen"
subtitle: "Function in std::math"
excerpt: "Compute the length of the given leg."
layout: manual
---
Compute the length of the given leg.
```kcl
legLen(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legLen(hypotenuse = 5, leg = 3)
```

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to centimeters from its current units. Convert a number to centimeters from its current units.
```kcl ```kcl
units::toCentimeters(@num: number(Length)): number(cm) units::toCentimeters(@num: number(cm)): number(cm)
``` ```
@ -17,7 +17,7 @@ units::toCentimeters(@num: number(Length)): number(cm)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(cm)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Converts a number to degrees from its current units. Converts a number to degrees from its current units.
```kcl ```kcl
units::toDegrees(@num: number(Angle)): number(deg) units::toDegrees(@num: number(deg)): number(deg)
``` ```
@ -17,7 +17,7 @@ units::toDegrees(@num: number(Angle)): number(deg)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(deg)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to feet from its current units. Convert a number to feet from its current units.
```kcl ```kcl
units::toFeet(@num: number(Length)): number(ft) units::toFeet(@num: number(ft)): number(ft)
``` ```
@ -17,7 +17,7 @@ units::toFeet(@num: number(Length)): number(ft)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(ft)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to inches from its current units. Convert a number to inches from its current units.
```kcl ```kcl
units::toInches(@num: number(Length)): number(in) units::toInches(@num: number(in)): number(in)
``` ```
@ -17,7 +17,7 @@ units::toInches(@num: number(Length)): number(in)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(in)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to meters from its current units. Convert a number to meters from its current units.
```kcl ```kcl
units::toMeters(@num: number(Length)): number(m) units::toMeters(@num: number(m)): number(m)
``` ```
@ -17,7 +17,7 @@ units::toMeters(@num: number(Length)): number(m)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(m)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Convert a number to millimeters from its current units. Convert a number to millimeters from its current units.
```kcl ```kcl
units::toMillimeters(@num: number(Length)): number(mm) units::toMillimeters(@num: number(mm)): number(mm)
``` ```
@ -17,7 +17,7 @@ units::toMillimeters(@num: number(Length)): number(mm)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(mm)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Converts a number to radians from its current units. Converts a number to radians from its current units.
```kcl ```kcl
units::toRadians(@num: number(Angle)): number(rad) units::toRadians(@num: number(rad)): number(rad)
``` ```
@ -17,7 +17,7 @@ units::toRadians(@num: number(Angle)): number(rad)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(rad)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Converts a number to yards from its current units. Converts a number to yards from its current units.
```kcl ```kcl
units::toYards(@num: number(Length)): number(yd) units::toYards(@num: number(yd)): number(yd)
``` ```
@ -17,7 +17,7 @@ units::toYards(@num: number(Length)): number(yd)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes | | `num` | [`number(yd)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
### Returns ### Returns

View File

@ -12,15 +12,15 @@ layout: manual
* [`appearance`](/docs/kcl-std/appearance) * [`appearance`](/docs/kcl-std/appearance)
* [`assert`](/docs/kcl-std/assert) * [`assert`](/docs/kcl-std/assert)
* [`assertIs`](/docs/kcl-std/assertIs) * [`assertIs`](/docs/kcl-std/assertIs)
* [`clone`](/docs/kcl-std/functions/std-clone) * [`clone`](/docs/kcl-std/clone)
* [`helix`](/docs/kcl-std/functions/std-helix) * [`helix`](/docs/kcl-std/functions/std-helix)
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane) * [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d) * [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
* [**std::array**](/docs/kcl-std/modules/std-array) * [**std::array**](/docs/kcl-std/modules/std-array)
* [`map`](/docs/kcl-std/functions/std-array-map) * [`map`](/docs/kcl-std/map)
* [`pop`](/docs/kcl-std/functions/std-array-pop) * [`pop`](/docs/kcl-std/pop)
* [`push`](/docs/kcl-std/functions/std-array-push) * [`push`](/docs/kcl-std/push)
* [`reduce`](/docs/kcl-std/functions/std-array-reduce) * [`reduce`](/docs/kcl-std/reduce)
* [**std::math**](/docs/kcl-std/modules/std-math) * [**std::math**](/docs/kcl-std/modules/std-math)
* [`abs`](/docs/kcl-std/functions/std-math-abs) * [`abs`](/docs/kcl-std/functions/std-math-abs)
* [`acos`](/docs/kcl-std/functions/std-math-acos) * [`acos`](/docs/kcl-std/functions/std-math-acos)
@ -30,9 +30,9 @@ layout: manual
* [`ceil`](/docs/kcl-std/functions/std-math-ceil) * [`ceil`](/docs/kcl-std/functions/std-math-ceil)
* [`cos`](/docs/kcl-std/functions/std-math-cos) * [`cos`](/docs/kcl-std/functions/std-math-cos)
* [`floor`](/docs/kcl-std/functions/std-math-floor) * [`floor`](/docs/kcl-std/functions/std-math-floor)
* [`legAngX`](/docs/kcl-std/functions/std-math-legAngX) * [`legAngX`](/docs/kcl-std/legAngX)
* [`legAngY`](/docs/kcl-std/functions/std-math-legAngY) * [`legAngY`](/docs/kcl-std/legAngY)
* [`legLen`](/docs/kcl-std/functions/std-math-legLen) * [`legLen`](/docs/kcl-std/legLen)
* [`ln`](/docs/kcl-std/functions/std-math-ln) * [`ln`](/docs/kcl-std/functions/std-math-ln)
* [`log`](/docs/kcl-std/functions/std-math-log) * [`log`](/docs/kcl-std/functions/std-math-log)
* [`log10`](/docs/kcl-std/functions/std-math-log10) * [`log10`](/docs/kcl-std/functions/std-math-log10)
@ -140,13 +140,12 @@ See also the [types overview](/docs/kcl-lang/types)
* [**Primitive types**](/docs/kcl-lang/types) * [**Primitive types**](/docs/kcl-lang/types)
* [`End`](/docs/kcl-lang/types#End) * [`End`](/docs/kcl-lang/types#End)
* [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) * [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry)
* [`Start`](/docs/kcl-lang/types#Start) * [`Start`](/docs/kcl-lang/types#Start)
* [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) * [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator)
* [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) * [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier)
* [`any`](/docs/kcl-std/types/std-types-any) * [`any`](/docs/kcl-std/types/std-types-any)
* [`bool`](/docs/kcl-std/types/std-types-bool) * [`bool`](/docs/kcl-std/types/std-types-bool)
* [`fn`](/docs/kcl-std/types/std-types-fn)
* [`number`](/docs/kcl-std/types/std-types-number) * [`number`](/docs/kcl-std/types/std-types-number)
* [`string`](/docs/kcl-std/types/std-types-string) * [`string`](/docs/kcl-std/types/std-types-string)
* [`tag`](/docs/kcl-std/types/std-types-tag) * [`tag`](/docs/kcl-std/types/std-types-tag)

38
docs/kcl-std/legAngX.md Normal file
View File

@ -0,0 +1,38 @@
---
title: "legAngX"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for x."
layout: manual
---
Compute the angle of the given leg for x.
```kcl
legAngX(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngX(hypotenuse = 5, leg = 3)
```

38
docs/kcl-std/legAngY.md Normal file
View File

@ -0,0 +1,38 @@
---
title: "legAngY"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for y."
layout: manual
---
Compute the angle of the given leg for y.
```kcl
legAngY(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngY(hypotenuse = 5, leg = 3)
```

38
docs/kcl-std/legLen.md Normal file
View File

@ -0,0 +1,38 @@
---
title: "legLen"
subtitle: "Function in std::math"
excerpt: "Compute the length of the given leg."
layout: manual
---
Compute the length of the given leg.
```kcl
legLen(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legLen(hypotenuse = 5, leg = 3)
```

File diff suppressed because one or more lines are too long

View File

@ -12,8 +12,8 @@ Functions for manipulating arrays of values.
## Functions and constants ## Functions and constants
* [`map`](/docs/kcl-std/functions/std-array-map) * [`map`](/docs/kcl-std/map)
* [`pop`](/docs/kcl-std/functions/std-array-pop) * [`pop`](/docs/kcl-std/pop)
* [`push`](/docs/kcl-std/functions/std-array-push) * [`push`](/docs/kcl-std/push)
* [`reduce`](/docs/kcl-std/functions/std-array-reduce) * [`reduce`](/docs/kcl-std/reduce)

View File

@ -23,9 +23,9 @@ Functions for mathematical operations and some useful constants.
* [`ceil`](/docs/kcl-std/functions/std-math-ceil) * [`ceil`](/docs/kcl-std/functions/std-math-ceil)
* [`cos`](/docs/kcl-std/functions/std-math-cos) * [`cos`](/docs/kcl-std/functions/std-math-cos)
* [`floor`](/docs/kcl-std/functions/std-math-floor) * [`floor`](/docs/kcl-std/functions/std-math-floor)
* [`legAngX`](/docs/kcl-std/functions/std-math-legAngX) * [`legAngX`](/docs/kcl-std/legAngX)
* [`legAngY`](/docs/kcl-std/functions/std-math-legAngY) * [`legAngY`](/docs/kcl-std/legAngY)
* [`legLen`](/docs/kcl-std/functions/std-math-legLen) * [`legLen`](/docs/kcl-std/legLen)
* [`ln`](/docs/kcl-std/functions/std-math-ln) * [`ln`](/docs/kcl-std/functions/std-math-ln)
* [`log`](/docs/kcl-std/functions/std-math-log) * [`log`](/docs/kcl-std/functions/std-math-log)
* [`log10`](/docs/kcl-std/functions/std-math-log10) * [`log10`](/docs/kcl-std/functions/std-math-log10)

View File

@ -18,7 +18,6 @@ Types can (optionally) be used to describe a function's arguments and returned v
* [`Edge`](/docs/kcl-std/types/std-types-Edge) * [`Edge`](/docs/kcl-std/types/std-types-Edge)
* [`Face`](/docs/kcl-std/types/std-types-Face) * [`Face`](/docs/kcl-std/types/std-types-Face)
* [`Helix`](/docs/kcl-std/types/std-types-Helix) * [`Helix`](/docs/kcl-std/types/std-types-Helix)
* [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry)
* [`Plane`](/docs/kcl-std/types/std-types-Plane) * [`Plane`](/docs/kcl-std/types/std-types-Plane)
* [`Point2d`](/docs/kcl-std/types/std-types-Point2d) * [`Point2d`](/docs/kcl-std/types/std-types-Point2d)
* [`Point3d`](/docs/kcl-std/types/std-types-Point3d) * [`Point3d`](/docs/kcl-std/types/std-types-Point3d)
@ -26,7 +25,6 @@ Types can (optionally) be used to describe a function's arguments and returned v
* [`Solid`](/docs/kcl-std/types/std-types-Solid) * [`Solid`](/docs/kcl-std/types/std-types-Solid)
* [`any`](/docs/kcl-std/types/std-types-any) * [`any`](/docs/kcl-std/types/std-types-any)
* [`bool`](/docs/kcl-std/types/std-types-bool) * [`bool`](/docs/kcl-std/types/std-types-bool)
* [`fn`](/docs/kcl-std/types/std-types-fn)
* [`number`](/docs/kcl-std/types/std-types-number) * [`number`](/docs/kcl-std/types/std-types-number)
* [`string`](/docs/kcl-std/types/std-types-string) * [`string`](/docs/kcl-std/types/std-types-string)
* [`tag`](/docs/kcl-std/types/std-types-tag) * [`tag`](/docs/kcl-std/types/std-types-tag)

View File

@ -11,8 +11,6 @@ Contains frequently used constants, functions for interacting with the KittyCAD
The standard library is organised into modules (listed below), but most things are always available in KCL programs. The standard library is organised into modules (listed below), but most things are always available in KCL programs.
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
## Modules ## Modules
* [`array`](/docs/kcl-std/modules/std-array) * [`array`](/docs/kcl-std/modules/std-array)
@ -37,7 +35,7 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui
* [`appearance`](/docs/kcl-std/appearance) * [`appearance`](/docs/kcl-std/appearance)
* [`assert`](/docs/kcl-std/assert) * [`assert`](/docs/kcl-std/assert)
* [`assertIs`](/docs/kcl-std/assertIs) * [`assertIs`](/docs/kcl-std/assertIs)
* [`clone`](/docs/kcl-std/functions/std-clone) * [`clone`](/docs/kcl-std/clone)
* [`helix`](/docs/kcl-std/functions/std-helix) * [`helix`](/docs/kcl-std/functions/std-helix)
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane) * [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d) * [`patternLinear2d`](/docs/kcl-std/patternLinear2d)

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

@ -43,7 +43,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to rotate. | Yes | | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to rotate. | Yes |
| `roll` | [`number`](/docs/kcl-std/types/std-types-number) | The roll angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | `roll` | [`number`](/docs/kcl-std/types/std-types-number) | The roll angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No |
| `pitch` | [`number`](/docs/kcl-std/types/std-types-number) | The pitch angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | `pitch` | [`number`](/docs/kcl-std/types/std-types-number) | The pitch angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No |
| `yaw` | [`number`](/docs/kcl-std/types/std-types-number) | The yaw angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | `yaw` | [`number`](/docs/kcl-std/types/std-types-number) | The yaw angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No |
@ -53,7 +53,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an
### Returns ### Returns
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry. [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples ### Examples

View File

@ -29,7 +29,7 @@ If you want to apply the transform in global space, set `global` to `true`. The
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to scale. | Yes | | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to scale. | Yes |
| `x` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the x axis. Default is 1 if not provided. | No | | `x` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the x axis. Default is 1 if not provided. | No |
| `y` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the y axis. Default is 1 if not provided. | No | | `y` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the y axis. Default is 1 if not provided. | No |
| `z` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the z axis. Default is 1 if not provided. | No | | `z` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the z axis. Default is 1 if not provided. | No |
@ -37,7 +37,7 @@ If you want to apply the transform in global space, set `global` to `true`. The
### Returns ### Returns
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry. [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples ### Examples

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to move. | Yes | | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to move. | Yes |
| `x` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the x axis. Defaults to 0 if not provided. | No | | `x` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the x axis. Defaults to 0 if not provided. | No |
| `y` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the y axis. Defaults to 0 if not provided. | No | | `y` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the y axis. Defaults to 0 if not provided. | No |
| `z` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the z axis. Defaults to 0 if not provided. | No | | `z` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the z axis. Defaults to 0 if not provided. | No |
@ -33,7 +33,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro
### Returns ### Returns
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry. [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples ### Examples

View File

@ -1,13 +0,0 @@
---
title: "ImportedGeometry"
subtitle: "Type in std::types"
excerpt: "Represents geometry which is defined using some other CAD system and imported into KCL."
layout: manual
---
Represents geometry which is defined using some other CAD system and imported into KCL.

View File

@ -7,25 +7,8 @@ layout: manual
An abstract plane. An abstract plane.
A plane has a position and orientation in space defined by its origin and axes. A plane is abstract A plane has a position and orientation in space defined by its origin and axes. A plane can be used
in the sense that it is not part of the objects being drawn. A plane can be used to sketch on. to sketch on.
A plane can be created in several ways:
- you can use one of the default planes, e.g., `XY`.
- you can use `offsetPlane` to create a new plane offset from an existing one, e.g., `offsetPlane(XY, offset = 150)`.
- you can use negation to create a plane from an existing one which is identical but has an opposite normal
e.g., `-XY`.
- you can define an entirely custom plane, e.g.,
```js
myXY = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
}
```
Any object with appropriate `origin`, `xAxis`, and `yAxis` fields can be used as a plane.

View File

@ -1,13 +0,0 @@
---
title: "fn"
subtitle: "Type in std::types"
excerpt: "The type of any function in KCL."
layout: manual
---
The type of any function in KCL.

View File

@ -549,6 +549,48 @@ extrude002 = extrude(profile002, length = 150)
}) })
}) })
test(
`Network health indicator only appears in modeling view`,
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page)
// Locators
const projectsHeading = page.getByRole('heading', {
name: 'Projects',
})
const projectLink = page.getByRole('link', { name: 'bracket' })
const networkHealthIndicator = page.getByTestId('network-toggle')
await test.step('Check the home page', async () => {
await expect(projectsHeading).toBeVisible()
await expect(projectLink).toBeVisible()
await expect(networkHealthIndicator).not.toBeVisible()
})
await test.step('Open the project', async () => {
await projectLink.click()
})
await test.step('Check the modeling view', async () => {
await expect(networkHealthIndicator).toBeVisible()
await expect(networkHealthIndicator).toContainText('Problem')
await u.waitForPageLoad()
await expect(networkHealthIndicator).toContainText('Connected')
})
}
)
test(`View gizmo stays visible even when zoomed out all the way`, async ({ test(`View gizmo stays visible even when zoomed out all the way`, async ({
page, page,
homePage, homePage,
@ -799,40 +841,6 @@ washer = extrude(washerSketch, length = thicknessMax)`
await editor.expectEditor.toContain('@settings(defaultLengthUnit = yd)') await editor.expectEditor.toContain('@settings(defaultLengthUnit = yd)')
}) })
}) })
test('Exiting existing sketch without editing should not delete it', async ({
page,
editor,
homePage,
context,
toolbar,
scene,
cmdBar,
}) => {
await context.folderSetupFn(async (dir) => {
const testDir = path.join(dir, 'test')
await fsp.mkdir(testDir, { recursive: true })
await fsp.writeFile(
path.join(testDir, 'main.kcl'),
`s1 = startSketchOn(XY)
|> startProfile(at = [0, 25])
|> xLine(endAbsolute = -15 + 1.5)
s2 = startSketchOn(XY)
|> startProfile(at = [25, 0])
|> yLine(endAbsolute = -15 + 1.5)`,
'utf-8'
)
})
await homePage.openProject('test')
await scene.settled(cmdBar)
await toolbar.waitForFeatureTreeToBeBuilt()
await toolbar.editSketch(1)
await page.waitForTimeout(1000) // Just hang out for a second
await toolbar.exitSketch()
await editor.expectEditor.toContain('s2 = startSketchOn(XY)')
})
}) })
async function clickExportButton(page: Page) { async function clickExportButton(page: Page) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -905,7 +905,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Go into the project that was created from Text to CAD // Go into the project that was created from Text to CAD
await homePage.openProject(projectName) await page.getByText(projectName).click()
await expect(page.getByTestId('app-header-project-name')).toBeVisible() await expect(page.getByTestId('app-header-project-name')).toBeVisible()
await expect(page.getByTestId('app-header-project-name')).toContainText( await expect(page.getByTestId('app-header-project-name')).toContainText(
@ -951,7 +951,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Go into the project that was created from Text to CAD // Go into the project that was created from Text to CAD
await homePage.openProject(projectName) await page.getByText(projectName).click()
await page.getByRole('button', { name: 'Accept' }).click() await page.getByRole('button', { name: 'Accept' }).click()

View File

@ -107,6 +107,7 @@
"check": "biome check ./src ./e2e ./packages/codemirror-lsp-client/src ./rust/kcl-language-server/client/src", "check": "biome check ./src ./e2e ./packages/codemirror-lsp-client/src ./rust/kcl-language-server/client/src",
"fetch:wasm": "./scripts/get-latest-wasm-bundle.sh", "fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
"fetch:wasm:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/get-latest-wasm-bundle.ps1", "fetch:wasm:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/get-latest-wasm-bundle.ps1",
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"", "remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"",
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src", "lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src", "lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
@ -122,6 +123,7 @@
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly", "files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "electron-rebuild", "postinstall": "electron-rebuild",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts", "generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
"tron:start": "electron-forge start", "tron:start": "electron-forge start",
"chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert=@snapshot", "chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert=@snapshot",
"tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development", "tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development",

25
rust/Cargo.lock generated
View File

@ -1815,7 +1815,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.69" version = "0.1.68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1826,7 +1826,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-derive-docs" name = "kcl-derive-docs"
version = "0.1.69" version = "0.1.68"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -1845,7 +1845,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
version = "0.1.69" version = "0.1.68"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1854,7 +1854,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server" name = "kcl-language-server"
version = "0.2.69" version = "0.2.68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1875,7 +1875,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.69" version = "0.1.68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1895,7 +1895,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.69" version = "0.2.68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1959,7 +1959,6 @@ dependencies = [
"url", "url",
"uuid", "uuid",
"validator", "validator",
"walkdir",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
@ -1971,7 +1970,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-python-bindings" name = "kcl-python-bindings"
version = "0.3.69" version = "0.3.68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kcl-lib", "kcl-lib",
@ -1986,7 +1985,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.69" version = "0.1.68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.32", "hyper 0.14.32",
@ -1999,7 +1998,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-to-core" name = "kcl-to-core"
version = "0.1.69" version = "0.1.68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -2013,7 +2012,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-wasm-lib" name = "kcl-wasm-lib"
version = "0.1.69" version = "0.1.68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bson", "bson",
@ -2043,9 +2042,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad" name = "kittycad"
version = "0.3.37" version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b48a9698d68c791df76aa020b596c324177a614e38ff3dd67eedd04db76e222f" checksum = "0a345fd2a4cb16205f32bd1aa41715045830c59d78c59927fca6580e2a651ac9"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",

View File

@ -35,7 +35,7 @@ console_error_panic_hook = "0.1.7"
dashmap = { version = "6.1.0" } dashmap = { version = "6.1.0" }
http = "1" http = "1"
indexmap = "2.9.0" indexmap = "2.9.0"
kittycad = { version = "0.3.37", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.3.36", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.117", features = ["ts-rs", "websocket"] } kittycad-modeling-cmds = { version = "0.2.117", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0" lazy_static = "1.5.0"
miette = "7.5.0" miette = "7.5.0"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.69" version = "0.1.68"
edition = "2021" edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api" repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76" rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-derive-docs" name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.69" version = "0.1.68"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files" description = "A tool for generating tests from a directory of kcl files"
version = "0.1.69" version = "0.1.68"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.69" version = "0.1.68"
edition = "2021" edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false publish = false

View File

@ -2,7 +2,7 @@
name = "kcl-language-server" name = "kcl-language-server"
description = "A language server for KCL." description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.69" version = "0.2.68"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language implementation and tools" description = "KittyCAD Language implementation and tools"
version = "0.2.69" version = "0.2.68"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"
@ -85,7 +85,6 @@ tynm = "0.1.10"
url = { version = "2.5.4", features = ["serde"] } url = { version = "2.5.4", features = ["serde"] }
uuid = { workspace = true, features = ["v4", "v5", "js", "serde"] } uuid = { workspace = true, features = ["v4", "v5", "js", "serde"] }
validator = { version = "0.20.0", features = ["derive"] } validator = { version = "0.20.0", features = ["derive"] }
walkdir = "2.5.0"
web-time = "1.1" web-time = "1.1"
winnow = "=0.6.24" winnow = "=0.6.24"
zip = { workspace = true } zip = { workspace = true }

View File

@ -3,7 +3,6 @@
use kcl_lib::{bust_cache, ExecError, ExecOutcome}; use kcl_lib::{bust_cache, ExecError, ExecOutcome};
use kcmc::{each_cmd as mcmd, ModelingCmd}; use kcmc::{each_cmd as mcmd, ModelingCmd};
use kittycad_modeling_cmds as kcmc; use kittycad_modeling_cmds as kcmc;
use pretty_assertions::assert_eq;
#[derive(Debug)] #[derive(Debug)]
struct Variation<'a> { struct Variation<'a> {
@ -248,11 +247,8 @@ extrude(profile001, length = 100)"#
) )
.await; .await;
let first = result.first().unwrap(); result.first().unwrap();
let last = result.last().unwrap(); result.last().unwrap();
assert!(first.1 == last.1, "The images should be the same");
assert_eq!(first.2, last.2, "The outcomes should be the same");
} }
#[cfg(feature = "artifact-graph")] #[cfg(feature = "artifact-graph")]
@ -554,64 +550,3 @@ extrude(profile001, length = 100)
"The outcomes artifact graphs should be different" "The outcomes artifact graphs should be different"
); );
} }
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_cache_multi_file_same_code_dont_reexecute_settings_only_change() {
let code = r#"import "toBeImported.kcl" as importedCube
importedCube
sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [-134.53, -56.17])
|> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(profile001, length = 100)
sketch003 = startSketchOn(extrude001, face = seg02)
sketch002 = startSketchOn(extrude001, face = seg01)
"#;
let other_file = (
std::path::PathBuf::from("toBeImported.kcl"),
r#"sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [281.54, 305.81])
|> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude(profile001, length = 100)"#
.to_string(),
);
let result = cache_test(
"multi_file_same_code_dont_reexecute_settings_only_change",
vec![
Variation {
code,
other_files: vec![other_file.clone()],
settings: &kcl_lib::ExecutorSettings {
show_grid: false,
..Default::default()
},
},
Variation {
code,
other_files: vec![other_file],
settings: &kcl_lib::ExecutorSettings {
show_grid: true,
..Default::default()
},
},
],
)
.await;
let first = result.first().unwrap();
let last = result.last().unwrap();
assert!(first.1 != last.1, "The images should be different for the grid");
assert_eq!(first.2, last.2, "The outcomes should be the same");
}

View File

@ -16,7 +16,7 @@ use crate::{
}; };
// Types with special handling. // Types with special handling.
const SPECIAL_TYPES: [&str; 4] = ["TagDeclarator", "TagIdentifier", "Start", "End"]; const SPECIAL_TYPES: [&str; 5] = ["TagDeclarator", "TagIdentifier", "Start", "End", "ImportedGeometry"];
const TYPE_REWRITES: [(&str, &str); 11] = [ const TYPE_REWRITES: [(&str, &str); 11] = [
("TagNode", "TagDeclarator"), ("TagNode", "TagDeclarator"),
@ -380,21 +380,6 @@ fn generate_function_from_kcl(
.enumerate() .enumerate()
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name)) .filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name))
.collect(); .collect();
let args = function.args.iter().map(|arg| {
let docs = arg.docs.clone();
if let Some(docs) = &docs {
// We deliberately truncate to one line in the template so that if we are using the docs
// from the type, then we only take the summary. However, if there's a newline in the
// arg docs, then they would get truncated unintentionally.
assert!(!docs.contains('\n'), "Arg docs will get truncated");
};
json!({
"name": arg.name,
"type_": arg.ty,
"description": docs.or_else(|| arg.ty.as_ref().and_then(|t| super::docs_for_type(t, kcl_std))).unwrap_or_default(),
"required": arg.kind.required(),
})
}).collect::<Vec<_>>();
let data = json!({ let data = json!({
"name": function.preferred_name, "name": function.preferred_name,
@ -404,7 +389,14 @@ fn generate_function_from_kcl(
"deprecated": function.properties.deprecated, "deprecated": function.properties.deprecated,
"fn_signature": function.preferred_name.clone() + &function.fn_signature(), "fn_signature": function.preferred_name.clone() + &function.fn_signature(),
"examples": examples, "examples": examples,
"args": args, "args": function.args.iter().map(|arg| {
json!({
"name": arg.name,
"type_": arg.ty,
"description": arg.docs.clone().or_else(|| arg.ty.as_ref().and_then(|t| super::docs_for_type(t, kcl_std))).unwrap_or_default(),
"required": arg.kind.required(),
})
}).collect::<Vec<_>>(),
"return_value": function.return_type.as_ref().map(|t| { "return_value": function.return_type.as_ref().map(|t| {
json!({ json!({
"type_": t, "type_": t,
@ -674,8 +666,6 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
if fmt_for_text && ty.starts_with("number") { if fmt_for_text && ty.starts_with("number") {
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)") format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)")
} else if fmt_for_text && ty.starts_with("fn") {
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-fn)")
} else if fmt_for_text && SPECIAL_TYPES.contains(&ty) { } else if fmt_for_text && SPECIAL_TYPES.contains(&ty) {
format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})") format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})")
} else if fmt_for_text && DECLARED_TYPES.contains(&ty) { } else if fmt_for_text && DECLARED_TYPES.contains(&ty) {

View File

@ -626,8 +626,6 @@ impl FnData {
pub(super) fn to_autocomplete_snippet(&self) -> String { pub(super) fn to_autocomplete_snippet(&self) -> String {
if self.name == "loft" { if self.name == "loft" {
return "loft([${0:sketch000}, ${1:sketch001}])".to_owned(); return "loft([${0:sketch000}, ${1:sketch001}])".to_owned();
} else if self.name == "clone" {
return "clone(${0:part001})".to_owned();
} else if self.name == "hole" { } else if self.name == "hole" {
return "hole(${0:holeSketch}, ${1:%})".to_owned(); return "hole(${0:holeSketch}, ${1:%})".to_owned();
} }

View File

@ -11,7 +11,6 @@ use std::{
use anyhow::Result; use anyhow::Result;
use kcl_doc::ModData; use kcl_doc::ModData;
use parse_display::Display;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{ use tower_lsp::lsp_types::{
@ -19,27 +18,15 @@ use tower_lsp::lsp_types::{
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation, MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
}; };
use crate::execution::{types::NumericType, Sketch}; use crate::{
execution::{types::NumericType, Sketch},
std::Primitive,
};
// These types are declared in (KCL) std. // These types are declared in (KCL) std.
const DECLARED_TYPES: [&str; 17] = [ const DECLARED_TYPES: [&str; 15] = [
"any", "any", "number", "string", "tag", "bool", "Sketch", "Solid", "Plane", "Helix", "Face", "Edge", "Point2d",
"number", "Point3d", "Axis2d", "Axis3d",
"string",
"tag",
"bool",
"Sketch",
"Solid",
"Plane",
"Helix",
"Face",
"Edge",
"Point2d",
"Point3d",
"Axis2d",
"Axis3d",
"ImportedGeometry",
"fn",
]; ];
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -51,21 +38,6 @@ lazy_static::lazy_static! {
}; };
} }
/// The primitive types that can be used in a KCL file.
#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema, Display)]
#[serde(rename_all = "lowercase")]
#[display(style = "lowercase")]
enum Primitive {
/// A boolean value.
Bool,
/// A number value.
Number,
/// A string value.
String,
/// A uuid value.
Uuid,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -195,7 +167,6 @@ impl StdLibFnArg {
pub fn description(&self, kcl_std: Option<&ModData>) -> Option<String> { pub fn description(&self, kcl_std: Option<&ModData>) -> Option<String> {
// Check if we explicitly gave this stdlib arg a description. // Check if we explicitly gave this stdlib arg a description.
if !self.description.is_empty() { if !self.description.is_empty() {
assert!(!self.description.contains('\n'), "Arg docs will get truncated");
return Some(self.description.clone()); return Some(self.description.clone());
} }
@ -562,6 +533,8 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
fn to_autocomplete_snippet(&self) -> Result<String> { fn to_autocomplete_snippet(&self) -> Result<String> {
if self.name() == "loft" { if self.name() == "loft" {
return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string()); return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string());
} else if self.name() == "clone" {
return Ok("clone(${0:part001})".to_string());
} else if self.name() == "union" { } else if self.name() == "union" {
return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string()); return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string());
} else if self.name() == "subtract" { } else if self.name() == "subtract" {
@ -728,7 +701,7 @@ pub fn get_description_string_from_schema(schema: &schemars::schema::RootSchema)
None None
} }
fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> { pub fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> {
match schema { match schema {
schemars::schema::Schema::Object(o) => { schemars::schema::Schema::Object(o) => {
if o.enum_values.is_some() { if o.enum_values.is_some() {
@ -1034,12 +1007,9 @@ mod tests {
#[test] #[test]
fn get_autocomplete_snippet_map() { fn get_autocomplete_snippet_map() {
let data = kcl_doc::walk_prelude(); let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map);
let DocData::Fn(map_fn) = data.find_by_name("map").unwrap() else { let snippet = map_fn.to_autocomplete_snippet().unwrap();
panic!(); assert_eq!(snippet, r#"map(${0:[0..9]})"#);
};
let snippet = map_fn.to_autocomplete_snippet();
assert_eq!(snippet, r#"map()"#);
} }
#[test] #[test]
@ -1159,11 +1129,8 @@ mod tests {
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)] #[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_clone() { fn get_autocomplete_snippet_clone() {
let data = kcl_doc::walk_prelude(); let clone_fn: Box<dyn StdLibFn> = Box::new(crate::std::clone::Clone);
let DocData::Fn(clone_fn) = data.find_by_name("clone").unwrap() else { let snippet = clone_fn.to_autocomplete_snippet().unwrap();
panic!();
};
let snippet = clone_fn.to_autocomplete_snippet();
assert_eq!(snippet, r#"clone(${0:part001})"#); assert_eq!(snippet, r#"clone(${0:part001})"#);
} }

View File

@ -559,7 +559,7 @@ impl From<KclError> for pyo3::PyErr {
} }
/// An error which occurred during parsing, etc. /// An error which occurred during parsing, etc.
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
#[ts(export)] #[ts(export)]
pub struct CompilationError { pub struct CompilationError {
#[serde(rename = "sourceRange")] #[serde(rename = "sourceRange")]

View File

@ -118,6 +118,7 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
// We know they have the same imports because the ast is the same. // We know they have the same imports because the ast is the same.
// If we have no imports, we can skip this. // If we have no imports, we can skip this.
if !old.ast.has_import_statements() { if !old.ast.has_import_statements() {
println!("No imports, no need to check.");
return CacheResult::NoAction(reapply_settings); return CacheResult::NoAction(reapply_settings);
} }

View File

@ -278,7 +278,7 @@ impl From<&KclValue> for OpKclValue {
ty: ty.clone(), ty: ty.clone(),
}, },
KclValue::String { value, .. } => Self::String { value: value.clone() }, KclValue::String { value, .. } => Self::String { value: value.clone() },
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => { KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
let value = value.iter().map(Self::from).collect(); let value = value.iter().map(Self::from).collect();
Self::Array { value } Self::Array { value }
} }

View File

@ -16,13 +16,12 @@ use crate::{
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo, BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo,
TagIdentifier, TagIdentifier,
}, },
fmt,
modules::{ModuleId, ModulePath, ModuleRepr}, modules::{ModuleId, ModulePath, ModuleRepr},
parsing::ast::types::{ parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator, Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
BinaryPart, BodyItem, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility,
ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, ObjectExpression,
ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
}, },
source_range::SourceRange, source_range::SourceRange,
std::{ std::{
@ -708,25 +707,17 @@ impl ExecutorContext {
// TODO this lets us use the label as a variable name, but not as a tag in most cases // TODO this lets us use the label as a variable name, but not as a tag in most cases
result result
} }
Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?, Expr::AscribedExpression(expr) => {
let result = self
.execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
.await?;
apply_ascription(&result, &expr.ty, exec_state, expr.into())?
}
}; };
Ok(item) Ok(item)
} }
} }
impl Node<AscribedExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
let metadata = Metadata {
source_range: SourceRange::from(self),
};
let result = ctx
.execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
.await?;
apply_ascription(&result, &self.ty, exec_state, self.into())
}
}
fn apply_ascription( fn apply_ascription(
value: &KclValue, value: &KclValue,
ty: &Node<Type>, ty: &Node<Type>,
@ -767,7 +758,6 @@ impl BinaryPart {
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await, BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state), BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await, BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
} }
} }
} }
@ -877,7 +867,11 @@ impl Node<MemberExpression> {
source_ranges: vec![self.clone().into()], source_ranges: vec![self.clone().into()],
})) }))
} }
(KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => { (
KclValue::MixedArray { value: arr, .. } | KclValue::HomArray { value: arr, .. },
Property::UInt(index),
_,
) => {
let value_of_arr = arr.get(index); let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr { if let Some(value) = value_of_arr {
Ok(value.to_owned()) Ok(value.to_owned())
@ -888,7 +882,7 @@ impl Node<MemberExpression> {
})) }))
} }
} }
(KclValue::HomArray { .. }, p, _) => { (KclValue::MixedArray { .. } | KclValue::HomArray { .. }, p, _) => {
let t = p.type_name(); let t = p.type_name();
let article = article_for(t); let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
@ -1176,7 +1170,7 @@ impl Node<UnaryExpression> {
}; };
let direction = match direction { let direction = match direction {
KclValue::Tuple { value: values, meta } => { KclValue::MixedArray { value: values, meta } => {
let values = values let values = values
.iter() .iter()
.map(|v| match v { .map(|v| match v {
@ -1189,7 +1183,7 @@ impl Node<UnaryExpression> {
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
KclValue::Tuple { KclValue::MixedArray {
value: values, value: values,
meta: meta.clone(), meta: meta.clone(),
} }
@ -1557,7 +1551,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
} }
} }
} }
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => { KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
for v in value { for v in value {
update_memory_for_tags_of_geometry(v, exec_state)?; update_memory_for_tags_of_geometry(v, exec_state)?;
} }
@ -1601,9 +1595,9 @@ impl Node<ArrayExpression> {
results.push(value); results.push(value);
} }
Ok(KclValue::HomArray { Ok(KclValue::MixedArray {
value: results, value: results,
ty: RuntimeType::Primitive(PrimitiveType::Any), meta: vec![self.into()],
}) })
} }
} }
@ -1612,7 +1606,7 @@ impl Node<ArrayRangeExpression> {
#[async_recursion] #[async_recursion]
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> { pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
let metadata = Metadata::from(&self.start_element); let metadata = Metadata::from(&self.start_element);
let start_val = ctx let start = ctx
.execute_expr( .execute_expr(
&self.start_element, &self.start_element,
exec_state, exec_state,
@ -1621,30 +1615,19 @@ impl Node<ArrayRangeExpression> {
StatementKind::Expression, StatementKind::Expression,
) )
.await?; .await?;
let (start, start_ty) = start_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails { let start = start.as_int().ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()], source_ranges: vec![self.into()],
message: format!("Expected int but found {}", start_val.human_friendly_type()), message: format!("Expected int but found {}", start.human_friendly_type()),
}))?; }))?;
let metadata = Metadata::from(&self.end_element); let metadata = Metadata::from(&self.end_element);
let end_val = ctx let end = ctx
.execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression) .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
.await?; .await?;
let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails { let end = end.as_int().ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()], source_ranges: vec![self.into()],
message: format!("Expected int but found {}", end_val.human_friendly_type()), message: format!("Expected int but found {}", end.human_friendly_type()),
}))?; }))?;
if start_ty != end_ty {
let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
let start = fmt::human_display_number(start.n, start.ty);
let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
let end = fmt::human_display_number(end.n, end.ty);
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: format!("Range start and end must be of the same type, but found {start} and {end}"),
}));
}
if end < start { if end < start {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()], source_ranges: vec![self.into()],
@ -1661,17 +1644,16 @@ impl Node<ArrayRangeExpression> {
let meta = vec![Metadata { let meta = vec![Metadata {
source_range: self.into(), source_range: self.into(),
}]; }];
Ok(KclValue::MixedArray {
Ok(KclValue::HomArray {
value: range value: range
.into_iter() .into_iter()
.map(|num| KclValue::Number { .map(|num| KclValue::Number {
value: num as f64, value: num as f64,
ty: start_ty.clone(), ty: NumericType::count(),
meta: meta.clone(), meta: meta.clone(),
}) })
.collect(), .collect(),
ty: RuntimeType::Primitive(PrimitiveType::Number(start_ty)), meta,
}) })
} }
} }
@ -1886,7 +1868,7 @@ fn type_check_params_kw(
arg.value = arg arg.value = arg
.value .value
.coerce( .coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?, &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
exec_state, exec_state,
) )
.map_err(|e| { .map_err(|e| {
@ -1964,8 +1946,7 @@ fn type_check_params_kw(
arg.value = arg arg.value = arg
.value .value
.coerce( .coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range) &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
.map_err(|e| KclError::Semantic(e.into()))?,
exec_state, exec_state,
) )
.map_err(|_| { .map_err(|_| {
@ -2144,8 +2125,7 @@ impl FunctionSource {
arg.value = arg arg.value = arg
.value .value
.coerce( .coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range) &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
.map_err(|e| KclError::Semantic(e.into()))?,
exec_state, exec_state,
) )
.map_err(|_| { .map_err(|_| {
@ -2255,10 +2235,9 @@ mod test {
use super::*; use super::*;
use crate::{ use crate::{
exec::UnitType,
execution::{memory::Stack, parse_execute, ContextType}, execution::{memory::Stack, parse_execute, ContextType},
parsing::ast::types::{DefaultParamVal, Identifier, Parameter}, parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
ExecutorSettings, UnitLen, ExecutorSettings,
}; };
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2412,7 +2391,6 @@ p = {
yAxis = { x = 0, y = 1, z = 0 }, yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 } zAxis = { x = 0, y = 0, z = 1 }
}: Plane }: Plane
arr1 = [42]: [number(cm)]
"#; "#;
let result = parse_execute(program).await.unwrap(); let result = parse_execute(program).await.unwrap();
@ -2423,24 +2401,6 @@ arr1 = [42]: [number(cm)]
.unwrap(), .unwrap(),
KclValue::Plane { .. } KclValue::Plane { .. }
)); ));
let arr1 = mem
.memory
.get_from("arr1", result.mem_env, SourceRange::default(), 0)
.unwrap();
if let KclValue::HomArray { value, ty } = arr1 {
assert_eq!(value.len(), 1, "Expected Vec with specific length: found {:?}", value);
assert_eq!(*ty, RuntimeType::known_length(UnitLen::Cm));
// Compare, ignoring meta.
if let KclValue::Number { value, ty, .. } = &value[0] {
// Converted from mm to cm.
assert_eq!(*value, 4.2);
assert_eq!(*ty, NumericType::Known(UnitType::Length(UnitLen::Cm)));
} else {
panic!("Expected a number; found {:?}", value[0]);
}
} else {
panic!("Expected HomArray; found {arr1:?}");
}
let program = r#" let program = r#"
a = 42: string a = 42: string
@ -2459,28 +2419,6 @@ a = 42: Plane
.unwrap_err() .unwrap_err()
.to_string() .to_string()
.contains("could not coerce number value to type Plane")); .contains("could not coerce number value to type Plane"));
let program = r#"
arr = [0]: [string]
"#;
let result = parse_execute(program).await;
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce array (list) value to type [string]"),
"Expected error but found {err:?}"
);
let program = r#"
mixedArr = [0, "a"]: [number(mm)]
"#;
let result = parse_execute(program).await;
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce array (list) value to type [number(mm)]"),
"Expected error but found {err:?}"
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2700,19 +2638,4 @@ sketch001 = startSketchOn(XY)
parse_execute(ast).await.unwrap(); parse_execute(ast).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread")]
async fn ascription_in_binop() {
let ast = r#"foo = tan(0): number(rad) - 4deg"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn neg_sqrt() {
let ast = r#"bad = sqrt(-2)"#;
let e = parse_execute(ast).await.unwrap_err();
// Make sure we get a useful error message and not an engine error.
assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
}
} }

View File

@ -1239,20 +1239,6 @@ impl Path {
[TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)] [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]
} }
/// The path segment start point and its type.
pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
let p = &self.get_base().from;
let ty: NumericType = self.get_base().units.into();
(*p, ty)
}
/// The path segment end point and its type.
pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
let p = &self.get_base().to;
let ty: NumericType = self.get_base().units.into();
(*p, ty)
}
/// Length of this path segment, in cartesian plane. /// Length of this path segment, in cartesian plane.
pub fn length(&self) -> TyF64 { pub fn length(&self) -> TyF64 {
let n = match self { let n = match self {

View File

@ -48,7 +48,7 @@ pub enum KclValue {
#[serde(skip)] #[serde(skip)]
meta: Vec<Metadata>, meta: Vec<Metadata>,
}, },
Tuple { MixedArray {
value: Vec<KclValue>, value: Vec<KclValue>,
#[serde(skip)] #[serde(skip)]
meta: Vec<Metadata>, meta: Vec<Metadata>,
@ -197,7 +197,7 @@ impl From<KclValue> for Vec<SourceRange> {
KclValue::Bool { meta, .. } => to_vec_sr(&meta), KclValue::Bool { meta, .. } => to_vec_sr(&meta),
KclValue::Number { meta, .. } => to_vec_sr(&meta), KclValue::Number { meta, .. } => to_vec_sr(&meta),
KclValue::String { meta, .. } => to_vec_sr(&meta), KclValue::String { meta, .. } => to_vec_sr(&meta),
KclValue::Tuple { meta, .. } => to_vec_sr(&meta), KclValue::MixedArray { meta, .. } => to_vec_sr(&meta),
KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(), KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
KclValue::Object { meta, .. } => to_vec_sr(&meta), KclValue::Object { meta, .. } => to_vec_sr(&meta),
KclValue::Module { meta, .. } => to_vec_sr(&meta), KclValue::Module { meta, .. } => to_vec_sr(&meta),
@ -228,7 +228,7 @@ impl From<&KclValue> for Vec<SourceRange> {
KclValue::Number { meta, .. } => to_vec_sr(meta), KclValue::Number { meta, .. } => to_vec_sr(meta),
KclValue::String { meta, .. } => to_vec_sr(meta), KclValue::String { meta, .. } => to_vec_sr(meta),
KclValue::Uuid { meta, .. } => to_vec_sr(meta), KclValue::Uuid { meta, .. } => to_vec_sr(meta),
KclValue::Tuple { meta, .. } => to_vec_sr(meta), KclValue::MixedArray { meta, .. } => to_vec_sr(meta),
KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(), KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
KclValue::Object { meta, .. } => to_vec_sr(meta), KclValue::Object { meta, .. } => to_vec_sr(meta),
KclValue::Module { meta, .. } => to_vec_sr(meta), KclValue::Module { meta, .. } => to_vec_sr(meta),
@ -252,7 +252,7 @@ impl KclValue {
KclValue::Bool { value: _, meta } => meta.clone(), KclValue::Bool { value: _, meta } => meta.clone(),
KclValue::Number { meta, .. } => meta.clone(), KclValue::Number { meta, .. } => meta.clone(),
KclValue::String { value: _, meta } => meta.clone(), KclValue::String { value: _, meta } => meta.clone(),
KclValue::Tuple { value: _, meta } => meta.clone(), KclValue::MixedArray { value: _, meta } => meta.clone(),
KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(), KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
KclValue::Object { value: _, meta } => meta.clone(), KclValue::Object { value: _, meta } => meta.clone(),
KclValue::TagIdentifier(x) => x.meta.clone(), KclValue::TagIdentifier(x) => x.meta.clone(),
@ -307,7 +307,7 @@ impl KclValue {
} => "number(Angle)", } => "number(Angle)",
KclValue::Number { .. } => "number", KclValue::Number { .. } => "number",
KclValue::String { .. } => "string (text)", KclValue::String { .. } => "string (text)",
KclValue::Tuple { .. } => "tuple (list)", KclValue::MixedArray { .. } => "mixed array (list)",
KclValue::HomArray { .. } => "array (list)", KclValue::HomArray { .. } => "array (list)",
KclValue::Object { .. } => "object", KclValue::Object { .. } => "object",
KclValue::Module { .. } => "module", KclValue::Module { .. } => "module",
@ -373,7 +373,7 @@ impl KclValue {
/// Put the point into a KCL value. /// Put the point into a KCL value.
pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self { pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
Self::Tuple { Self::MixedArray {
value: vec![ value: vec![
Self::Number { Self::Number {
value: p[0], value: p[0],
@ -404,13 +404,6 @@ impl KclValue {
} }
} }
pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
match self {
KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, ty.clone())),
_ => None,
}
}
pub fn as_object(&self) -> Option<&KclObjectFields> { pub fn as_object(&self) -> Option<&KclObjectFields> {
if let KclValue::Object { value, meta: _ } = &self { if let KclValue::Object { value, meta: _ } = &self {
Some(value) Some(value)
@ -437,7 +430,7 @@ impl KclValue {
pub fn as_array(&self) -> Option<&[KclValue]> { pub fn as_array(&self) -> Option<&[KclValue]> {
match self { match self {
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => Some(value), KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => Some(value),
_ => None, _ => None,
} }
} }
@ -609,7 +602,7 @@ impl KclValue {
KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)), KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)), KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
// TODO better Array and Object stringification // TODO better Array and Object stringification
KclValue::Tuple { .. } => Some("[...]".to_owned()), KclValue::MixedArray { .. } => Some("[...]".to_owned()),
KclValue::HomArray { .. } => Some("[...]".to_owned()), KclValue::HomArray { .. } => Some("[...]".to_owned()),
KclValue::Object { .. } => Some("{ ... }".to_owned()), KclValue::Object { .. } => Some("{ ... }".to_owned()),
KclValue::Module { .. } KclValue::Module { .. }

View File

@ -64,7 +64,7 @@ pub mod typed_path;
pub(crate) mod types; pub(crate) mod types;
/// Outcome of executing a program. This is used in TS. /// Outcome of executing a program. This is used in TS.
#[derive(Debug, Clone, Serialize, ts_rs::TS, PartialEq)] #[derive(Debug, Clone, Serialize, ts_rs::TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ExecOutcome { pub struct ExecOutcome {
@ -385,7 +385,6 @@ impl ExecutorContext {
let (ws, _headers) = client let (ws, _headers) = client
.modeling() .modeling()
.commands_ws( .commands_ws(
None,
None, None,
None, None,
if settings.enable_ssao { if settings.enable_ssao {
@ -654,22 +653,14 @@ impl ExecutorContext {
keys.sort(); keys.sort();
for key in keys { for key in keys {
let (_, id, _, _) = &new_universe[key]; let (_, id, _, _) = &new_universe[key];
if let (Some(source0), Some(source1)) = let old_source = old_state.get_source(*id);
(old_state.get_source(*id), new_exec_state.get_source(*id)) let new_source = new_exec_state.get_source(*id);
{ if old_source != new_source {
if source0.source != source1.source { clear_scene = true;
clear_scene = true; break;
break;
}
} }
} }
if !clear_scene {
// Return early we don't need to clear the scene.
let outcome = old_state.to_exec_outcome(result_env).await;
return Ok(outcome);
}
( (
clear_scene, clear_scene,
crate::Program { crate::Program {
@ -1941,7 +1932,7 @@ a = []
notArray = !a"; notArray = !a";
assert_eq!( assert_eq!(
parse_execute(code5).await.unwrap_err().message(), parse_execute(code5).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: array (list)", "Cannot apply unary operator ! to non-boolean value: mixed array (list)",
); );
let code6 = " let code6 = "
@ -2253,7 +2244,7 @@ w = f() + f()
|> line(end = [0, 0]) |> line(end = [0, 0])
|> close() |> close()
} }
sketch = startSketchOn(XY) sketch = startSketchOn(XY)
|> startProfile(at = [0,0]) |> startProfile(at = [0,0])
|> line(end = [0, 10]) |> line(end = [0, 10])

View File

@ -28,10 +28,6 @@ pub enum RuntimeType {
} }
impl RuntimeType { impl RuntimeType {
pub fn any() -> Self {
RuntimeType::Primitive(PrimitiveType::Any)
}
pub fn edge() -> Self { pub fn edge() -> Self {
RuntimeType::Primitive(PrimitiveType::Edge) RuntimeType::Primitive(PrimitiveType::Edge)
} }
@ -76,10 +72,6 @@ impl RuntimeType {
RuntimeType::Primitive(PrimitiveType::Tag) RuntimeType::Primitive(PrimitiveType::Tag)
} }
pub fn tag_identifier() -> Self {
RuntimeType::Primitive(PrimitiveType::TagId)
}
pub fn bool() -> Self { pub fn bool() -> Self {
RuntimeType::Primitive(PrimitiveType::Boolean) RuntimeType::Primitive(PrimitiveType::Boolean)
} }
@ -170,20 +162,13 @@ impl RuntimeType {
source_range: SourceRange, source_range: SourceRange,
) -> Result<Self, CompilationError> { ) -> Result<Self, CompilationError> {
Ok(match value { Ok(match value {
AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String), AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean), AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
AstPrimitiveType::Number(suffix) => { AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number(
let ty = match suffix { NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
NumericSuffix::None => NumericType::Any, )),
_ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
};
RuntimeType::Primitive(PrimitiveType::Number(ty))
}
AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?, AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?,
AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag), AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
}) })
} }
@ -218,7 +203,7 @@ impl RuntimeType {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" or "), .join(" or "),
RuntimeType::Tuple(tys) => format!( RuntimeType::Tuple(tys) => format!(
"a tuple with values of types ({})", "an array with values of types ({})",
tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ") tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
), ),
RuntimeType::Object(_) => format!("an object with fields {}", self), RuntimeType::Object(_) => format!("an object with fields {}", self),
@ -230,7 +215,6 @@ impl RuntimeType {
use RuntimeType::*; use RuntimeType::*;
match (self, sup) { match (self, sup) {
(_, Primitive(PrimitiveType::Any)) => true,
(Primitive(t1), Primitive(t2)) => t1.subtype(t2), (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
(Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2), (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
(Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)), (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
@ -281,7 +265,7 @@ impl RuntimeType {
.map(|t| t.display_multiple()) .map(|t| t.display_multiple())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" or "), .join(" or "),
RuntimeType::Tuple(_) => "tuples".to_owned(), RuntimeType::Tuple(_) => "arrays".to_owned(),
RuntimeType::Object(_) => format!("objects with fields {self}"), RuntimeType::Object(_) => format!("objects with fields {self}"),
} }
} }
@ -298,7 +282,7 @@ impl fmt::Display for RuntimeType {
}, },
RuntimeType::Tuple(ts) => write!( RuntimeType::Tuple(ts) => write!(
f, f,
"({})", "[{}]",
ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ") ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
), ),
RuntimeType::Union(ts) => write!( RuntimeType::Union(ts) => write!(
@ -349,13 +333,10 @@ impl ArrayLen {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum PrimitiveType { pub enum PrimitiveType {
Any,
Number(NumericType), Number(NumericType),
String, String,
Boolean, Boolean,
Tag, Tag,
// Annoyingly some functions only want a TagIdentifier, not any kind of tag.
TagId,
Sketch, Sketch,
Solid, Solid,
Plane, Plane,
@ -365,13 +346,11 @@ pub enum PrimitiveType {
Axis2d, Axis2d,
Axis3d, Axis3d,
ImportedGeometry, ImportedGeometry,
Function,
} }
impl PrimitiveType { impl PrimitiveType {
fn display_multiple(&self) -> String { fn display_multiple(&self) -> String {
match self { match self {
PrimitiveType::Any => "any values".to_owned(),
PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"), PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
PrimitiveType::Number(_) => "numbers".to_owned(), PrimitiveType::Number(_) => "numbers".to_owned(),
PrimitiveType::String => "strings".to_owned(), PrimitiveType::String => "strings".to_owned(),
@ -385,17 +364,13 @@ impl PrimitiveType {
PrimitiveType::Axis2d => "2d axes".to_owned(), PrimitiveType::Axis2d => "2d axes".to_owned(),
PrimitiveType::Axis3d => "3d axes".to_owned(), PrimitiveType::Axis3d => "3d axes".to_owned(),
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(), PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
PrimitiveType::Function => "functions".to_owned(),
PrimitiveType::Tag => "tags".to_owned(), PrimitiveType::Tag => "tags".to_owned(),
PrimitiveType::TagId => "tag identifiers".to_owned(),
} }
} }
fn subtype(&self, other: &PrimitiveType) -> bool { fn subtype(&self, other: &PrimitiveType) -> bool {
match (self, other) { match (self, other) {
(_, PrimitiveType::Any) => true,
(PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2), (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
(PrimitiveType::TagId, PrimitiveType::Tag) => true,
(t1, t2) => t1 == t2, (t1, t2) => t1 == t2,
} }
} }
@ -404,7 +379,6 @@ impl PrimitiveType {
impl fmt::Display for PrimitiveType { impl fmt::Display for PrimitiveType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
PrimitiveType::Any => write!(f, "any"),
PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"), PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"), PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number(default units)"), PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number(default units)"),
@ -412,7 +386,6 @@ impl fmt::Display for PrimitiveType {
PrimitiveType::String => write!(f, "string"), PrimitiveType::String => write!(f, "string"),
PrimitiveType::Boolean => write!(f, "bool"), PrimitiveType::Boolean => write!(f, "bool"),
PrimitiveType::Tag => write!(f, "tag"), PrimitiveType::Tag => write!(f, "tag"),
PrimitiveType::TagId => write!(f, "tag identifier"),
PrimitiveType::Sketch => write!(f, "Sketch"), PrimitiveType::Sketch => write!(f, "Sketch"),
PrimitiveType::Solid => write!(f, "Solid"), PrimitiveType::Solid => write!(f, "Solid"),
PrimitiveType::Plane => write!(f, "Plane"), PrimitiveType::Plane => write!(f, "Plane"),
@ -422,7 +395,6 @@ impl fmt::Display for PrimitiveType {
PrimitiveType::Axis3d => write!(f, "Axis3d"), PrimitiveType::Axis3d => write!(f, "Axis3d"),
PrimitiveType::Helix => write!(f, "Helix"), PrimitiveType::Helix => write!(f, "Helix"),
PrimitiveType::ImportedGeometry => write!(f, "imported geometry"), PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
PrimitiveType::Function => write!(f, "function"),
} }
} }
} }
@ -691,7 +663,6 @@ impl NumericType {
// We don't have enough information to coerce. // We don't have enough information to coerce.
(Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))), (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
(_, Unknown) => Err(val.into()), (_, Unknown) => Err(val.into()),
(Any, _) => Ok(KclValue::Number { (Any, _) => Ok(KclValue::Number {
value: *value, value: *value,
ty: self.clone(), ty: self.clone(),
@ -1023,9 +994,9 @@ impl KclValue {
self_ty.subtype(ty) self_ty.subtype(ty)
} }
/// Coerce `self` to a new value which has `ty` as its closest supertype. /// Coerce `self` to a new value which has `ty` as it's closest supertype.
/// ///
/// If the result is Ok, then: /// If the result is Some, then:
/// - result.principal_type().unwrap().subtype(ty) /// - result.principal_type().unwrap().subtype(ty)
/// ///
/// If self.principal_type() == ty then result == self /// If self.principal_type() == ty then result == self
@ -1045,11 +1016,10 @@ impl KclValue {
exec_state: &mut ExecState, exec_state: &mut ExecState,
) -> Result<KclValue, CoercionError> { ) -> Result<KclValue, CoercionError> {
let value = match self { let value = match self {
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0], KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
_ => self, _ => self,
}; };
match ty { match ty {
PrimitiveType::Any => Ok(value.clone()),
PrimitiveType::Number(ty) => ty.coerce(value), PrimitiveType::Number(ty) => ty.coerce(value),
PrimitiveType::String => match value { PrimitiveType::String => match value {
KclValue::String { .. } => Ok(value.clone()), KclValue::String { .. } => Ok(value.clone()),
@ -1189,14 +1159,6 @@ impl KclValue {
KclValue::ImportedGeometry { .. } => Ok(value.clone()), KclValue::ImportedGeometry { .. } => Ok(value.clone()),
_ => Err(self.into()), _ => Err(self.into()),
}, },
PrimitiveType::Function => match value {
KclValue::Function { .. } => Ok(value.clone()),
_ => Err(self.into()),
},
PrimitiveType::TagId => match value {
KclValue::TagIdentifier { .. } => Ok(value.clone()),
_ => Err(self.into()),
},
PrimitiveType::Tag => match value { PrimitiveType::Tag => match value {
KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => { KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
Ok(value.clone()) Ok(value.clone())
@ -1216,49 +1178,41 @@ impl KclValue {
exec_state: &mut ExecState, exec_state: &mut ExecState,
allow_shrink: bool, allow_shrink: bool,
) -> Result<KclValue, CoercionError> { ) -> Result<KclValue, CoercionError> {
if len.satisfied(1, false).is_some() && self.has_type(ty) {
return Ok(KclValue::HomArray {
value: vec![self.clone()],
ty: ty.clone(),
});
}
match self { match self {
KclValue::HomArray { value, ty: aty, .. } => { KclValue::HomArray { value, ty: aty } => {
let satisfied_len = len.satisfied(value.len(), allow_shrink);
if aty.subtype(ty) { if aty.subtype(ty) {
// If the element type is a subtype of the target type and len.satisfied(value.len(), allow_shrink)
// the length constraint is satisfied, we can just return
// the values unchanged, only adjusting the length. The new
// array element type should preserve its type because the
// target type oftentimes includes an unknown type as a way
// to say that the caller doesn't care.
return satisfied_len
.map(|len| KclValue::HomArray { .map(|len| KclValue::HomArray {
value: value[..len].to_vec(), value: value[..len].to_vec(),
ty: aty.clone(), ty: aty.clone(),
}) })
.ok_or(self.into()); .ok_or(self.into())
} else {
Err(self.into())
} }
}
// Ignore the array type, and coerce the elements of the array. KclValue::MixedArray { value, .. } => {
if let Some(satisfied_len) = satisfied_len { // Check if we have a nested homogeneous array that we can flatten.
let value_result = value
.iter()
.take(satisfied_len)
.map(|v| v.coerce(ty, exec_state))
.collect::<Result<Vec<_>, _>>();
if let Ok(value) = value_result {
// We were able to coerce all the elements.
return Ok(KclValue::HomArray { value, ty: ty.clone() });
}
}
// As a last resort, try to flatten the array.
let mut values = Vec::new(); let mut values = Vec::new();
for item in value { for item in value {
if let KclValue::HomArray { value: inner_value, .. } = item { if let KclValue::HomArray {
// Flatten elements. ty: inner_ty,
for item in inner_value { value: inner_value,
values.push(item.coerce(ty, exec_state)?); } = item
{
if inner_ty.subtype(ty) {
values.extend(inner_value.iter().cloned());
} else {
values.push(item.clone());
} }
} else { } else {
values.push(item.coerce(ty, exec_state)?); values.push(item.clone());
} }
} }
@ -1266,22 +1220,9 @@ impl KclValue {
.satisfied(values.len(), allow_shrink) .satisfied(values.len(), allow_shrink)
.ok_or(CoercionError::from(self))?; .ok_or(CoercionError::from(self))?;
assert!(len <= values.len()); let value = values[..len]
values.truncate(len);
Ok(KclValue::HomArray {
value: values,
ty: ty.clone(),
})
}
KclValue::Tuple { value, .. } => {
let len = len
.satisfied(value.len(), allow_shrink)
.ok_or(CoercionError::from(self))?;
let value = value
.iter() .iter()
.map(|item| item.coerce(ty, exec_state)) .map(|v| v.coerce(ty, exec_state))
.take(len)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Ok(KclValue::HomArray { value, ty: ty.clone() }) Ok(KclValue::HomArray { value, ty: ty.clone() })
@ -1290,32 +1231,28 @@ impl KclValue {
value: Vec::new(), value: Vec::new(),
ty: ty.clone(), ty: ty.clone(),
}), }),
_ if len.satisfied(1, false).is_some() => Ok(KclValue::HomArray {
value: vec![self.coerce(ty, exec_state)?],
ty: ty.clone(),
}),
_ => Err(self.into()), _ => Err(self.into()),
} }
} }
fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> { fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
match self { match self {
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => { KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
let mut result = Vec::new(); let mut result = Vec::new();
for (i, t) in tys.iter().enumerate() { for (i, t) in tys.iter().enumerate() {
result.push(value[i].coerce(t, exec_state)?); result.push(value[i].coerce(t, exec_state)?);
} }
Ok(KclValue::Tuple { Ok(KclValue::MixedArray {
value: result, value: result,
meta: Vec::new(), meta: Vec::new(),
}) })
} }
KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple { KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::MixedArray {
value: Vec::new(), value: Vec::new(),
meta: meta.clone(), meta: meta.clone(),
}), }),
value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::Tuple { value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::MixedArray {
value: vec![value.clone()], value: vec![value.clone()],
meta: Vec::new(), meta: Vec::new(),
}), }),
@ -1375,16 +1312,18 @@ impl KclValue {
KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)), KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)), KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)), KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple( KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?, value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
)), )),
KclValue::HomArray { ty, value, .. } => { KclValue::HomArray { ty, value, .. } => {
Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len()))) Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
} }
KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TagId)), KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => {
KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Tag)), Some(RuntimeType::Primitive(PrimitiveType::Tag))
KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)), }
KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None, KclValue::Function { .. } | KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => {
None
}
} }
} }
} }
@ -1409,7 +1348,7 @@ mod test {
value: "hello".to_owned(), value: "hello".to_owned(),
meta: Vec::new(), meta: Vec::new(),
}, },
KclValue::Tuple { KclValue::MixedArray {
value: Vec::new(), value: Vec::new(),
meta: Vec::new(), meta: Vec::new(),
}, },
@ -1478,67 +1417,45 @@ mod test {
let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1)); let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty); let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
match v { assert_coerce_results(
KclValue::Tuple { .. } | KclValue::HomArray { .. } => { v,
// These will not get wrapped if possible. &aty,
assert_coerce_results( &KclValue::HomArray {
v, value: vec![v.clone()],
&aty, ty: ty.clone(),
&KclValue::HomArray { },
value: vec![], &mut exec_state,
ty: ty.clone(), );
}, assert_coerce_results(
&mut exec_state, v,
); &aty1,
// Coercing an empty tuple or array to an array of length 1 &KclValue::HomArray {
// should fail. value: vec![v.clone()],
v.coerce(&aty1, &mut exec_state).unwrap_err(); ty: ty.clone(),
// Coercing an empty tuple or array to an array that's },
// non-empty should fail. &mut exec_state,
v.coerce(&aty0, &mut exec_state).unwrap_err(); );
} assert_coerce_results(
_ => { v,
assert_coerce_results( &aty0,
v, &KclValue::HomArray {
&aty, value: vec![v.clone()],
&KclValue::HomArray { ty: ty.clone(),
value: vec![v.clone()], },
ty: ty.clone(), &mut exec_state,
}, );
&mut exec_state,
);
assert_coerce_results(
v,
&aty1,
&KclValue::HomArray {
value: vec![v.clone()],
ty: ty.clone(),
},
&mut exec_state,
);
assert_coerce_results(
v,
&aty0,
&KclValue::HomArray {
value: vec![v.clone()],
ty: ty.clone(),
},
&mut exec_state,
);
// Tuple subtype // Tuple subtype
let tty = RuntimeType::Tuple(vec![ty.clone()]); let tty = RuntimeType::Tuple(vec![ty.clone()]);
assert_coerce_results( assert_coerce_results(
v, v,
&tty, &tty,
&KclValue::Tuple { &KclValue::MixedArray {
value: vec![v.clone()], value: vec![v.clone()],
meta: Vec::new(), meta: Vec::new(),
}, },
&mut exec_state, &mut exec_state,
); );
}
}
} }
for v in &values[1..] { for v in &values[1..] {
@ -1586,7 +1503,7 @@ mod test {
assert_coerce_results( assert_coerce_results(
&none, &none,
&tty, &tty,
&KclValue::Tuple { &KclValue::MixedArray {
value: Vec::new(), value: Vec::new(),
meta: Vec::new(), meta: Vec::new(),
}, },
@ -1717,7 +1634,7 @@ mod test {
], ],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
}; };
let mixed1 = KclValue::Tuple { let mixed1 = KclValue::MixedArray {
value: vec![ value: vec![
KclValue::Number { KclValue::Number {
value: 0.0, value: 0.0,
@ -1732,7 +1649,7 @@ mod test {
], ],
meta: Vec::new(), meta: Vec::new(),
}; };
let mixed2 = KclValue::Tuple { let mixed2 = KclValue::MixedArray {
value: vec![ value: vec![
KclValue::Number { KclValue::Number {
value: 0.0, value: 0.0,
@ -1822,7 +1739,7 @@ mod test {
], ],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
}; };
let mixed0 = KclValue::Tuple { let mixed0 = KclValue::MixedArray {
value: vec![], value: vec![],
meta: Vec::new(), meta: Vec::new(),
}; };
@ -2239,7 +2156,7 @@ d = cos(30)
async fn coerce_nested_array() { async fn coerce_nested_array() {
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await); let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
let mixed1 = KclValue::HomArray { let mixed1 = KclValue::MixedArray {
value: vec![ value: vec![
KclValue::Number { KclValue::Number {
value: 0.0, value: 0.0,
@ -2267,7 +2184,7 @@ d = cos(30)
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())), ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
}, },
], ],
ty: RuntimeType::any(), meta: Vec::new(),
}; };
// Principal types // Principal types

View File

@ -150,7 +150,6 @@ impl BinaryPart {
unary_expression.get_hover_value_for_position(pos, code, opts) unary_expression.get_hover_value_for_position(pos, code, opts)
} }
BinaryPart::IfExpression(e) => e.get_hover_value_for_position(pos, code, opts), BinaryPart::IfExpression(e) => e.get_hover_value_for_position(pos, code, opts),
BinaryPart::AscribedExpression(e) => e.expr.get_hover_value_for_position(pos, code, opts),
BinaryPart::MemberExpression(member_expression) => { BinaryPart::MemberExpression(member_expression) => {
member_expression.get_hover_value_for_position(pos, code, opts) member_expression.get_hover_value_for_position(pos, code, opts)
} }

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