Compare commits
31 Commits
jtran/fmt-
...
marijnh/si
Author | SHA1 | Date | |
---|---|---|---|
aa0b97de3d | |||
95a02cbcd7 | |||
a049768f1c | |||
818d9a0d77 | |||
1a8f80a7dc | |||
566c9eaf10 | |||
e6485c2da1 | |||
0479edd36a | |||
87c1e92134 | |||
8f950ac1b0 | |||
78e4f43708 | |||
270436f404 | |||
57e61632a9 | |||
884191b8bb | |||
21b92f5f13 | |||
5599a75dbd | |||
3a06ae6e34 | |||
22857d77e9 | |||
1a325d0b29 | |||
1240b23080 | |||
8445080d7a | |||
bbe89f56a7 | |||
86e8bcfe0b | |||
21ccf129d6 | |||
dfc4b7d0c5 | |||
17b1120a27 | |||
2b509a515b | |||
97594b9a9e | |||
c65190a158 | |||
24a241c05a | |||
9265af9115 |
40
.github/ci-cd-scripts/upload-results.sh
vendored
40
.github/ci-cd-scripts/upload-results.sh
vendored
@ -1,13 +1,41 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
|
||||
COMMIT="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
|
||||
if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
curl --request POST \
|
||||
project="https://github.com/KittyCAD/modeling-app"
|
||||
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}" \
|
||||
--form "project=https://github.com/KittyCAD/modeling-app" \
|
||||
--form "branch=${BRANCH}" \
|
||||
--form "commit=${COMMIT}" \
|
||||
--form "project=${project}" \
|
||||
--form "branch=${branch}" \
|
||||
--form "commit=${commit}" \
|
||||
--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
|
||||
|
||||
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
|
||||
|
21
.github/workflows/build-apps.yml
vendored
21
.github/workflows/build-apps.yml
vendored
@ -7,11 +7,10 @@ on:
|
||||
- main
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
- 'nightly-v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
env:
|
||||
IS_RELEASE: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }}
|
||||
IS_NIGHTLY: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'nightly-v') }}
|
||||
IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@ -95,7 +94,9 @@ jobs:
|
||||
- name: Set nightly version, product name, release notes, and icons
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
run: |
|
||||
export VERSION=${GITHUB_REF_NAME#nightly-v}
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
DATE=$(date +'%-y.%-m.%-d')
|
||||
export VERSION=$DATE-main.$COMMIT
|
||||
npm run files:set-version
|
||||
npm run files:flip-to-nightly
|
||||
|
||||
@ -306,7 +307,8 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
# Equivalent to IS_RELEASE || IS_NIGHTLY (but we can't access those env vars here)
|
||||
if: ${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
|
||||
env:
|
||||
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||
VERSION: ${{ format('v{0}', needs.prepare-files.outputs.version) }}
|
||||
@ -412,17 +414,6 @@ jobs:
|
||||
- name: List artifacts
|
||||
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
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
uses: 'google-github-actions/auth@v2.1.8'
|
||||
|
2
.github/workflows/cargo-test.yml
vendored
2
.github/workflows/cargo-test.yml
vendored
@ -188,6 +188,8 @@ jobs:
|
||||
env:
|
||||
TAB_API_URL: ${{ secrets.TAB_API_URL }}
|
||||
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run-wasm-tests:
|
||||
name: Run wasm tests
|
||||
strategy:
|
||||
|
47
.github/workflows/check-exampleKcl.yml
vendored
47
.github/workflows/check-exampleKcl.yml
vendored
@ -1,47 +0,0 @@
|
||||
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,
|
||||
});
|
||||
}
|
114
.github/workflows/e2e-tests.yml
vendored
114
.github/workflows/e2e-tests.yml
vendored
@ -18,73 +18,13 @@ permissions:
|
||||
|
||||
jobs:
|
||||
|
||||
conditions:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
significant: ${{ steps.path-changes.outputs.significant }}
|
||||
should-run: ${{ steps.should-run.outputs.should-run }}
|
||||
|
||||
steps:
|
||||
- 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
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
name: Check for Rust changes
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
@ -93,18 +33,16 @@ jobs:
|
||||
- 'rust/**'
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: npm install
|
||||
|
||||
- name: Download Wasm Cache
|
||||
id: download-wasm
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
|
||||
if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
@ -116,7 +54,6 @@ jobs:
|
||||
|
||||
- name: Build WASM condition
|
||||
id: wasm
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: |
|
||||
set -euox pipefail
|
||||
# Build wasm if this is a scheduled run, there are Rust changes, or
|
||||
@ -128,35 +65,34 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Use correct Rust toolchain
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
|
||||
|
||||
- name: Install rust
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
cache: false # Configured below.
|
||||
|
||||
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
with:
|
||||
tool: wasm-pack
|
||||
|
||||
- name: Rust Cache
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './rust'
|
||||
|
||||
- name: Build Wasm
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||
shell: bash
|
||||
run: npm run build:wasm
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
with:
|
||||
name: prepared-wasm
|
||||
path: |
|
||||
@ -165,10 +101,9 @@ jobs:
|
||||
snapshots:
|
||||
name: playwright:snapshots:ubuntu
|
||||
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
|
||||
needs: [conditions, prepare-wasm]
|
||||
needs: [prepare-wasm]
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
|
||||
@ -176,16 +111,13 @@ jobs:
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
name: prepared-wasm
|
||||
|
||||
- name: Copy prepared wasm
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: |
|
||||
ls -R prepared-wasm
|
||||
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
|
||||
@ -193,18 +125,15 @@ jobs:
|
||||
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
id: deps-install
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: npm install
|
||||
|
||||
- name: Cache Playwright Browsers
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
@ -212,15 +141,12 @@ jobs:
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: npm run playwright install --with-deps
|
||||
|
||||
- name: build web
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: npm run tronb:vite:dev
|
||||
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
with:
|
||||
shell: bash
|
||||
@ -236,7 +162,7 @@ jobs:
|
||||
TARGET: web
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-ubuntu-snapshot-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
@ -245,7 +171,7 @@ jobs:
|
||||
overwrite: true
|
||||
|
||||
- name: Check for changes
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }}
|
||||
if: ${{ github.ref != 'refs/heads/main' }}
|
||||
shell: bash
|
||||
id: git-check
|
||||
run: |
|
||||
@ -257,7 +183,7 @@ jobs:
|
||||
|
||||
- name: Commit changes, if any
|
||||
# TODO: find a more reliable way to detect visual changes
|
||||
if: ${{ false && needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
|
||||
if: ${{ false && steps.git-check.outputs.modified == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
|
||||
@ -272,7 +198,7 @@ jobs:
|
||||
git push origin ${{ github.head_ref }}
|
||||
|
||||
electron:
|
||||
needs: [conditions, prepare-wasm]
|
||||
needs: [prepare-wasm]
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
|
||||
@ -315,14 +241,11 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
name: prepared-wasm
|
||||
|
||||
- name: Copy prepared wasm
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: |
|
||||
ls -R prepared-wasm
|
||||
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
|
||||
@ -330,18 +253,15 @@ jobs:
|
||||
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
id: deps-install
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: npm install
|
||||
|
||||
- name: Cache Playwright Browsers
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
@ -349,22 +269,20 @@ jobs:
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: npm run playwright install --with-deps
|
||||
|
||||
- name: Build web
|
||||
if: needs.conditions.outputs.should-run == 'true'
|
||||
run: npm run tronb:vite:dev
|
||||
|
||||
- name: Start Vector
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && !contains(matrix.os, 'windows') }}
|
||||
if: ${{ !contains(matrix.os, 'windows') }}
|
||||
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
|
||||
env:
|
||||
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
|
||||
OS_NAME: ${{ env.OS_NAME }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
@ -372,7 +290,7 @@ jobs:
|
||||
|
||||
- name: Run playwright/electron flow (with retries)
|
||||
id: retry
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && steps.deps-install.outcome == 'success' }}
|
||||
if: ${{ !cancelled() && steps.deps-install.outcome == 'success' }}
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
with:
|
||||
shell: bash
|
||||
@ -389,7 +307,7 @@ jobs:
|
||||
TARGET: desktop
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
@ -398,7 +316,7 @@ jobs:
|
||||
overwrite: true
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
|
39
.github/workflows/tag-nightly.yml
vendored
39
.github/workflows/tag-nightly.yml
vendored
@ -1,39 +0,0 @@
|
||||
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
|
10
Makefile
10
Makefile
@ -23,6 +23,7 @@ endif
|
||||
install: node_modules/.package-lock.json $(CARGO) $(WASM_PACK) ## Install dependencies
|
||||
|
||||
node_modules/.package-lock.json: package.json package-lock.json
|
||||
npm prune
|
||||
npm install
|
||||
|
||||
$(CARGO):
|
||||
@ -43,15 +44,15 @@ endif
|
||||
# BUILD
|
||||
|
||||
CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*)
|
||||
KCL_SOURCES := $(wildcard public/kcl-samples/**/*.kcl)
|
||||
RUST_SOURCES := $(wildcard rust/**/*.rs)
|
||||
|
||||
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
|
||||
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
|
||||
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
|
||||
|
||||
|
||||
.PHONY: build
|
||||
build: install public/kcl_wasm_lib_bg.wasm .vite/build/main.js
|
||||
build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js
|
||||
|
||||
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES)
|
||||
ifdef WINDOWS
|
||||
@ -60,6 +61,9 @@ else
|
||||
npm run build:wasm:dev
|
||||
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)
|
||||
npm run tronb:vite:dev
|
||||
|
||||
@ -107,8 +111,10 @@ test: test-unit test-e2e
|
||||
.PHONY: test-unit
|
||||
test-unit: install ## Run the unit tests
|
||||
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 )
|
||||
npm run test:unit
|
||||
npm run test:unit:kcl-samples
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e: test-e2e-$(TARGET)
|
||||
|
99
docs/kcl-lang/foreign-imports.md
Normal file
99
docs/kcl-lang/foreign-imports.md
Normal file
@ -0,0 +1,99 @@
|
||||
---
|
||||
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 deep‑dive for foreign‑file imports
|
||||
|
||||
Parallelized foreign‑file 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 kick‑starts 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 IO‑heavy work into its own module so it can render in parallel while `main.kcl` continues.
|
||||
|
||||
#### Future improvements
|
||||
|
||||
Upcoming releases will auto‑analyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.
|
||||
|
@ -5,7 +5,7 @@ layout: manual
|
||||
---
|
||||
|
||||
This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
|
||||
things in a more tutorial fashion.
|
||||
things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std).
|
||||
|
||||
## Topics
|
||||
|
||||
@ -14,7 +14,8 @@ things in a more tutorial fashion.
|
||||
* [Values and types](/docs/kcl-lang/types)
|
||||
* [Numeric types and units](/docs/kcl-lang/numeric)
|
||||
* [Functions](/docs/kcl-lang/functions)
|
||||
* [Projects, modules, and imports](/docs/kcl-lang/modules)
|
||||
* [Projects and modules](/docs/kcl-lang/modules)
|
||||
* [Attributes](/docs/kcl-lang/attributes)
|
||||
* [Importing geometry from other CAD systems](/docs/kcl-lang/foreign-imports)
|
||||
* [Settings](/docs/kcl-lang/settings)
|
||||
* [Known Issues](/docs/kcl-lang/known-issues)
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "Modules"
|
||||
title: "Projects and modules"
|
||||
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
|
||||
layout: manual
|
||||
---
|
||||
@ -264,102 +264,3 @@ cube
|
||||
clone(cube)
|
||||
|> 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 deep‑dive for foreign‑file imports
|
||||
|
||||
Parallelized foreign‑file 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 kick‑starts 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 IO‑heavy work into its own module so it can render in parallel while `main.kcl` continues.
|
||||
|
||||
#### Future improvements
|
||||
|
||||
Upcoming releases will auto‑analyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.
|
||||
|
||||
|
||||
|
@ -28,6 +28,8 @@ 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).
|
||||
|
||||
Using just `number` means accepting any kind of number, even where the units are unknown by KCL.
|
||||
|
||||
|
||||
## Function calls
|
||||
|
||||
|
@ -22,14 +22,14 @@ This will work on any solid, including extruded solids, revolved solids, and she
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `roughness` | [`number`](/docs/kcl-std/types/std-types-number) | Roughness of the new material, a percentage like 95.7. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
[`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid or an imported geometry.
|
||||
[`[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.
|
||||
|
||||
|
||||
### Examples
|
||||
|
@ -11,7 +11,13 @@ The value of `pi`, Archimedes’ constant (π).
|
||||
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
|
||||
|
||||
|
@ -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 |
|
||||
| `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 | No |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
38
docs/kcl-std/functions/std-math-legAngX.md
Normal file
38
docs/kcl-std/functions/std-math-legAngX.md
Normal 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(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)
|
||||
```
|
||||
|
||||
|
||||
|
38
docs/kcl-std/functions/std-math-legAngY.md
Normal file
38
docs/kcl-std/functions/std-math-legAngY.md
Normal 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(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)
|
||||
```
|
||||
|
||||
|
||||
|
38
docs/kcl-std/functions/std-math-legLen.md
Normal file
38
docs/kcl-std/functions/std-math-legLen.md
Normal 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(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)
|
||||
```
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ layout: manual
|
||||
Convert a number to centimeters from its current units.
|
||||
|
||||
```kcl
|
||||
units::toCentimeters(@num: number(cm)): number(cm)
|
||||
units::toCentimeters(@num: number(Length)): number(cm)
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ units::toCentimeters(@num: number(cm)): number(cm)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | [`number(cm)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -8,7 +8,7 @@ layout: manual
|
||||
Converts a number to degrees from its current units.
|
||||
|
||||
```kcl
|
||||
units::toDegrees(@num: number(deg)): number(deg)
|
||||
units::toDegrees(@num: number(Angle)): number(deg)
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ units::toDegrees(@num: number(deg)): number(deg)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | [`number(deg)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
| `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -8,7 +8,7 @@ layout: manual
|
||||
Convert a number to feet from its current units.
|
||||
|
||||
```kcl
|
||||
units::toFeet(@num: number(ft)): number(ft)
|
||||
units::toFeet(@num: number(Length)): number(ft)
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ units::toFeet(@num: number(ft)): number(ft)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | [`number(ft)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -8,7 +8,7 @@ layout: manual
|
||||
Convert a number to inches from its current units.
|
||||
|
||||
```kcl
|
||||
units::toInches(@num: number(in)): number(in)
|
||||
units::toInches(@num: number(Length)): number(in)
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ units::toInches(@num: number(in)): number(in)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | [`number(in)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -8,7 +8,7 @@ layout: manual
|
||||
Convert a number to meters from its current units.
|
||||
|
||||
```kcl
|
||||
units::toMeters(@num: number(m)): number(m)
|
||||
units::toMeters(@num: number(Length)): number(m)
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ units::toMeters(@num: number(m)): number(m)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | [`number(m)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -8,7 +8,7 @@ layout: manual
|
||||
Convert a number to millimeters from its current units.
|
||||
|
||||
```kcl
|
||||
units::toMillimeters(@num: number(mm)): number(mm)
|
||||
units::toMillimeters(@num: number(Length)): number(mm)
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ units::toMillimeters(@num: number(mm)): number(mm)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | [`number(mm)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -8,7 +8,7 @@ layout: manual
|
||||
Converts a number to radians from its current units.
|
||||
|
||||
```kcl
|
||||
units::toRadians(@num: number(rad)): number(rad)
|
||||
units::toRadians(@num: number(Angle)): number(rad)
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ units::toRadians(@num: number(rad)): number(rad)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | [`number(rad)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
| `num` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -8,7 +8,7 @@ layout: manual
|
||||
Converts a number to yards from its current units.
|
||||
|
||||
```kcl
|
||||
units::toYards(@num: number(yd)): number(yd)
|
||||
units::toYards(@num: number(Length)): number(yd)
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ units::toYards(@num: number(yd)): number(yd)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | [`number(yd)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
| `num` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | A number. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -12,15 +12,15 @@ layout: manual
|
||||
* [`appearance`](/docs/kcl-std/appearance)
|
||||
* [`assert`](/docs/kcl-std/assert)
|
||||
* [`assertIs`](/docs/kcl-std/assertIs)
|
||||
* [`clone`](/docs/kcl-std/clone)
|
||||
* [`clone`](/docs/kcl-std/functions/std-clone)
|
||||
* [`helix`](/docs/kcl-std/functions/std-helix)
|
||||
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
|
||||
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
|
||||
* [**std::array**](/docs/kcl-std/modules/std-array)
|
||||
* [`map`](/docs/kcl-std/map)
|
||||
* [`pop`](/docs/kcl-std/pop)
|
||||
* [`push`](/docs/kcl-std/push)
|
||||
* [`reduce`](/docs/kcl-std/reduce)
|
||||
* [`map`](/docs/kcl-std/functions/std-array-map)
|
||||
* [`pop`](/docs/kcl-std/functions/std-array-pop)
|
||||
* [`push`](/docs/kcl-std/functions/std-array-push)
|
||||
* [`reduce`](/docs/kcl-std/functions/std-array-reduce)
|
||||
* [**std::math**](/docs/kcl-std/modules/std-math)
|
||||
* [`abs`](/docs/kcl-std/functions/std-math-abs)
|
||||
* [`acos`](/docs/kcl-std/functions/std-math-acos)
|
||||
@ -30,9 +30,9 @@ layout: manual
|
||||
* [`ceil`](/docs/kcl-std/functions/std-math-ceil)
|
||||
* [`cos`](/docs/kcl-std/functions/std-math-cos)
|
||||
* [`floor`](/docs/kcl-std/functions/std-math-floor)
|
||||
* [`legAngX`](/docs/kcl-std/legAngX)
|
||||
* [`legAngY`](/docs/kcl-std/legAngY)
|
||||
* [`legLen`](/docs/kcl-std/legLen)
|
||||
* [`legAngX`](/docs/kcl-std/functions/std-math-legAngX)
|
||||
* [`legAngY`](/docs/kcl-std/functions/std-math-legAngY)
|
||||
* [`legLen`](/docs/kcl-std/functions/std-math-legLen)
|
||||
* [`ln`](/docs/kcl-std/functions/std-math-ln)
|
||||
* [`log`](/docs/kcl-std/functions/std-math-log)
|
||||
* [`log10`](/docs/kcl-std/functions/std-math-log10)
|
||||
@ -140,7 +140,8 @@ See also the [types overview](/docs/kcl-lang/types)
|
||||
|
||||
* [**Primitive types**](/docs/kcl-lang/types)
|
||||
* [`End`](/docs/kcl-lang/types#End)
|
||||
* [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry)
|
||||
* [`Fn`](/docs/kcl-std/types/std-types-Fn)
|
||||
* [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry)
|
||||
* [`Start`](/docs/kcl-lang/types#Start)
|
||||
* [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator)
|
||||
* [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier)
|
||||
|
@ -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,
|
||||
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)
|
||||
```
|
||||
|
||||
|
||||
|
@ -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,
|
||||
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)
|
||||
```
|
||||
|
||||
|
||||
|
@ -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,
|
||||
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)
|
||||
```
|
||||
|
||||
|
||||
|
@ -12,8 +12,8 @@ Functions for manipulating arrays of values.
|
||||
|
||||
## Functions and constants
|
||||
|
||||
* [`map`](/docs/kcl-std/map)
|
||||
* [`pop`](/docs/kcl-std/pop)
|
||||
* [`push`](/docs/kcl-std/push)
|
||||
* [`reduce`](/docs/kcl-std/reduce)
|
||||
* [`map`](/docs/kcl-std/functions/std-array-map)
|
||||
* [`pop`](/docs/kcl-std/functions/std-array-pop)
|
||||
* [`push`](/docs/kcl-std/functions/std-array-push)
|
||||
* [`reduce`](/docs/kcl-std/functions/std-array-reduce)
|
||||
|
||||
|
@ -23,9 +23,9 @@ Functions for mathematical operations and some useful constants.
|
||||
* [`ceil`](/docs/kcl-std/functions/std-math-ceil)
|
||||
* [`cos`](/docs/kcl-std/functions/std-math-cos)
|
||||
* [`floor`](/docs/kcl-std/functions/std-math-floor)
|
||||
* [`legAngX`](/docs/kcl-std/legAngX)
|
||||
* [`legAngY`](/docs/kcl-std/legAngY)
|
||||
* [`legLen`](/docs/kcl-std/legLen)
|
||||
* [`legAngX`](/docs/kcl-std/functions/std-math-legAngX)
|
||||
* [`legAngY`](/docs/kcl-std/functions/std-math-legAngY)
|
||||
* [`legLen`](/docs/kcl-std/functions/std-math-legLen)
|
||||
* [`ln`](/docs/kcl-std/functions/std-math-ln)
|
||||
* [`log`](/docs/kcl-std/functions/std-math-log)
|
||||
* [`log10`](/docs/kcl-std/functions/std-math-log10)
|
||||
|
@ -17,7 +17,9 @@ Types can (optionally) be used to describe a function's arguments and returned v
|
||||
* [`Axis3d`](/docs/kcl-std/types/std-types-Axis3d)
|
||||
* [`Edge`](/docs/kcl-std/types/std-types-Edge)
|
||||
* [`Face`](/docs/kcl-std/types/std-types-Face)
|
||||
* [`Fn`](/docs/kcl-std/types/std-types-Fn)
|
||||
* [`Helix`](/docs/kcl-std/types/std-types-Helix)
|
||||
* [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry)
|
||||
* [`Plane`](/docs/kcl-std/types/std-types-Plane)
|
||||
* [`Point2d`](/docs/kcl-std/types/std-types-Point2d)
|
||||
* [`Point3d`](/docs/kcl-std/types/std-types-Point3d)
|
||||
|
@ -11,6 +11,8 @@ Contains frequently used constants, functions for interacting with the KittyCAD
|
||||
|
||||
The standard library is organised into modules (listed below), but most things are always available in KCL programs.
|
||||
|
||||
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
|
||||
|
||||
## Modules
|
||||
|
||||
* [`array`](/docs/kcl-std/modules/std-array)
|
||||
@ -35,7 +37,7 @@ The standard library is organised into modules (listed below), but most things a
|
||||
* [`appearance`](/docs/kcl-std/appearance)
|
||||
* [`assert`](/docs/kcl-std/assert)
|
||||
* [`assertIs`](/docs/kcl-std/assertIs)
|
||||
* [`clone`](/docs/kcl-std/clone)
|
||||
* [`clone`](/docs/kcl-std/functions/std-clone)
|
||||
* [`helix`](/docs/kcl-std/functions/std-helix)
|
||||
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
|
||||
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
|
||||
|
@ -43,7 +43,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an
|
||||
|
||||
| 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-lang/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-std/types/std-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 |
|
||||
| `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 |
|
||||
@ -53,7 +53,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an
|
||||
|
||||
### Returns
|
||||
|
||||
[`[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.
|
||||
[`[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.
|
||||
|
||||
|
||||
### Examples
|
||||
|
@ -29,7 +29,7 @@ If you want to apply the transform in global space, set `global` to `true`. The
|
||||
|
||||
| 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-lang/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-std/types/std-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 |
|
||||
| `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 |
|
||||
@ -37,7 +37,7 @@ If you want to apply the transform in global space, set `global` to `true`. The
|
||||
|
||||
### Returns
|
||||
|
||||
[`[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.
|
||||
[`[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.
|
||||
|
||||
|
||||
### Examples
|
||||
|
35694
docs/kcl-std/std.json
35694
docs/kcl-std/std.json
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro
|
||||
|
||||
| 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-lang/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-std/types/std-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 |
|
||||
| `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 |
|
||||
@ -33,7 +33,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro
|
||||
|
||||
### Returns
|
||||
|
||||
[`[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.
|
||||
[`[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.
|
||||
|
||||
|
||||
### Examples
|
||||
|
13
docs/kcl-std/types/std-types-Fn.md
Normal file
13
docs/kcl-std/types/std-types-Fn.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
title: "Fn"
|
||||
subtitle: "Type in std::types"
|
||||
excerpt: "The type of any function in KCL."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
The type of any function in KCL.
|
||||
|
||||
|
||||
|
||||
|
||||
|
13
docs/kcl-std/types/std-types-ImportedGeometry.md
Normal file
13
docs/kcl-std/types/std-types-ImportedGeometry.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -7,8 +7,25 @@ layout: manual
|
||||
|
||||
An abstract plane.
|
||||
|
||||
A plane has a position and orientation in space defined by its origin and axes. A plane can be used
|
||||
to sketch on.
|
||||
A plane has a position and orientation in space defined by its origin and axes. A plane is abstract
|
||||
in the sense that it is not part of the objects being drawn. A plane can be used 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.
|
||||
|
||||
|
||||
|
||||
|
@ -841,6 +841,40 @@ washer = extrude(washerSketch, length = thicknessMax)`
|
||||
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) {
|
||||
|
@ -107,7 +107,6 @@
|
||||
"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: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\"",
|
||||
"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",
|
||||
@ -123,7 +122,6 @@
|
||||
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
||||
"postinstall": "electron-rebuild",
|
||||
"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",
|
||||
"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",
|
||||
|
@ -15,10 +15,6 @@ export default function lspGoToDefinitionExt(
|
||||
{
|
||||
key: 'F12',
|
||||
run: (view) => {
|
||||
if (!plugin) {
|
||||
return false
|
||||
}
|
||||
|
||||
const value = view.plugin(plugin)
|
||||
if (!value) return false
|
||||
|
||||
|
@ -47,7 +47,7 @@ import {
|
||||
import { isArray } from '../lib/utils'
|
||||
import lspGoToDefinitionExt from './go-to-definition'
|
||||
import lspRenameExt from './rename'
|
||||
import lspSignatureHelpExt from './signature-help'
|
||||
import { lspSignatureHelpExt, setSignatureTooltip } from './signature-help'
|
||||
|
||||
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
|
||||
export const docPathFacet = Facet.define<string, string>({
|
||||
@ -695,6 +695,8 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
|
||||
// Create the tooltip container
|
||||
const dom = this.createTooltipContainer()
|
||||
dom.className =
|
||||
'documentation hover-tooltip cm-tooltip cm-signature-tooltip'
|
||||
|
||||
// Get active signature
|
||||
const activeSignatureIndex = result.activeSignature ?? 0
|
||||
@ -769,42 +771,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
)
|
||||
|
||||
if (tooltip) {
|
||||
// Create and show the tooltip manually
|
||||
const { pos: tooltipPos, create } = tooltip
|
||||
const tooltipView = create(view)
|
||||
|
||||
const tooltipElement = document.createElement('div')
|
||||
tooltipElement.className =
|
||||
'documentation hover-tooltip cm-tooltip cm-signature-tooltip'
|
||||
tooltipElement.style.position = 'absolute'
|
||||
tooltipElement.style.zIndex = '99999999'
|
||||
|
||||
tooltipElement.appendChild(tooltipView.dom)
|
||||
|
||||
// Position the tooltip
|
||||
const coords = view.coordsAtPos(tooltipPos)
|
||||
if (coords) {
|
||||
tooltipElement.style.left = `${coords.left}px`
|
||||
tooltipElement.style.top = `${coords.bottom + 5}px`
|
||||
|
||||
// Add to DOM
|
||||
document.body.appendChild(tooltipElement)
|
||||
|
||||
// Remove after a delay or on editor changes
|
||||
setTimeout(() => {
|
||||
removeTooltip() // Use the function that also cleans up event listeners
|
||||
}, 10000) // Show for 10 seconds
|
||||
|
||||
// Also remove on any user input
|
||||
const removeTooltip = () => {
|
||||
tooltipElement.remove()
|
||||
view.dom.removeEventListener('keydown', removeTooltip)
|
||||
view.dom.removeEventListener('mousedown', removeTooltip)
|
||||
}
|
||||
|
||||
view.dom.addEventListener('keydown', removeTooltip)
|
||||
view.dom.addEventListener('mousedown', removeTooltip)
|
||||
}
|
||||
view.dispatch({ effects: setSignatureTooltip.of(tooltip) })
|
||||
}
|
||||
}
|
||||
|
||||
@ -1034,15 +1001,8 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
continue
|
||||
}
|
||||
|
||||
// Sort edits in reverse order to avoid position shifts
|
||||
const sortedEdits = docChange.edits.sort((a, b) => {
|
||||
const posA = posToOffset(view.state.doc, a.range.start)
|
||||
const posB = posToOffset(view.state.doc, b.range.start)
|
||||
return (posB ?? 0) - (posA ?? 0)
|
||||
})
|
||||
|
||||
// Create a single transaction with all changes
|
||||
const changes = sortedEdits.map((edit) => ({
|
||||
const changes = docChange.edits.map((edit) => ({
|
||||
from: posToOffset(view.state.doc, edit.range.start) ?? 0,
|
||||
to: posToOffset(view.state.doc, edit.range.end) ?? 0,
|
||||
insert: edit.newText,
|
||||
@ -1070,15 +1030,8 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
continue
|
||||
}
|
||||
|
||||
// Sort changes in reverse order to avoid position shifts
|
||||
const sortedChanges = changes.sort((a, b) => {
|
||||
const posA = posToOffset(view.state.doc, a.range.start)
|
||||
const posB = posToOffset(view.state.doc, b.range.start)
|
||||
return (posB ?? 0) - (posA ?? 0)
|
||||
})
|
||||
|
||||
// Create a single transaction with all changes
|
||||
const changeSpecs = sortedChanges.map((change) => ({
|
||||
const changeSpecs = changes.map((change) => ({
|
||||
from: posToOffset(view.state.doc, change.range.start) ?? 0,
|
||||
to: posToOffset(view.state.doc, change.range.end) ?? 0,
|
||||
insert: change.newText,
|
||||
|
@ -15,8 +15,6 @@ export default function lspRenameExt(
|
||||
{
|
||||
key: 'F2',
|
||||
run: (view) => {
|
||||
if (!plugin) return false
|
||||
|
||||
const value = view.plugin(plugin)
|
||||
if (!value) return false
|
||||
|
||||
|
@ -1,12 +1,68 @@
|
||||
import type { Extension } from '@codemirror/state'
|
||||
import { Prec } from '@codemirror/state'
|
||||
import type { ViewPlugin } from '@codemirror/view'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { keymap } from '@codemirror/view'
|
||||
import {
|
||||
type EditorState,
|
||||
type Extension,
|
||||
Prec,
|
||||
StateEffect,
|
||||
StateField,
|
||||
} from '@codemirror/state'
|
||||
import type { Tooltip } from '@codemirror/view'
|
||||
import { type ViewPlugin, showTooltip } from '@codemirror/view'
|
||||
import { EditorView, keymap } from '@codemirror/view'
|
||||
import { syntaxTree } from '@codemirror/language'
|
||||
import { type SyntaxNode } from '@lezer/common'
|
||||
|
||||
import type { LanguageServerPlugin } from './lsp'
|
||||
|
||||
export default function lspSignatureHelpExt(
|
||||
export const setSignatureTooltip = StateEffect.define<Tooltip | null>()
|
||||
|
||||
function findParenthesized(
|
||||
state: EditorState,
|
||||
pos: number,
|
||||
side: 1 | 0 | -1 = 0
|
||||
) {
|
||||
let context: SyntaxNode | null = syntaxTree(state).resolveInner(pos, side)
|
||||
while (context) {
|
||||
const open = context.firstChild
|
||||
if (
|
||||
open &&
|
||||
open.from == context.from &&
|
||||
open.to == context.from + 1 &&
|
||||
state.doc.sliceString(open.from, open.to) == '('
|
||||
)
|
||||
break
|
||||
context = context.parent
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
const signatureTooltip = StateField.define<Tooltip | null>({
|
||||
create: () => null,
|
||||
update(value, tr) {
|
||||
for (let effect of tr.effects) {
|
||||
if (effect.is(setSignatureTooltip)) return effect.value
|
||||
}
|
||||
if (!value) return null
|
||||
if (tr.selection) {
|
||||
let parens = findParenthesized(tr.state, tr.selection.main.head)
|
||||
if (!parens || parens.from != value.pos) return null
|
||||
}
|
||||
return tr.docChanged
|
||||
? { ...value, pos: tr.changes.mapPos(value.pos) }
|
||||
: value
|
||||
},
|
||||
provide: (f) => [
|
||||
showTooltip.from(f),
|
||||
EditorView.domEventHandlers({
|
||||
blur: (_, view) => {
|
||||
if (view.state.field(f)) {
|
||||
view.dispatch({ effects: setSignatureTooltip.of(null) })
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export function lspSignatureHelpExt(
|
||||
plugin: ViewPlugin<LanguageServerPlugin>
|
||||
): Extension {
|
||||
return [
|
||||
@ -15,16 +71,16 @@ export default function lspSignatureHelpExt(
|
||||
{
|
||||
key: 'Mod-Shift-Space',
|
||||
run: (view) => {
|
||||
if (!plugin) {
|
||||
return false
|
||||
}
|
||||
|
||||
const value = view.plugin(plugin)
|
||||
if (!value) return false
|
||||
|
||||
const pos = view.state.selection.main.head
|
||||
const parens = findParenthesized(
|
||||
view.state,
|
||||
view.state.selection.main.head
|
||||
)
|
||||
if (!parens) return false
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
value.showSignatureHelpTooltip(view, pos)
|
||||
value.showSignatureHelpTooltip(view, parens.from)
|
||||
return true
|
||||
},
|
||||
},
|
||||
@ -32,17 +88,10 @@ export default function lspSignatureHelpExt(
|
||||
),
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
EditorView.updateListener.of(async (update) => {
|
||||
if (!(plugin && update.docChanged)) return
|
||||
if (!update.docChanged) return
|
||||
|
||||
// Make sure this is a valid user typing event.
|
||||
let isRelevant = false
|
||||
for (const tr of update.transactions) {
|
||||
if (tr.isUserEvent('input')) {
|
||||
isRelevant = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRelevant) {
|
||||
if (!update.transactions.some((tr) => tr.isUserEvent('input'))) {
|
||||
// We only want signature help on user events.
|
||||
return
|
||||
}
|
||||
@ -59,18 +108,16 @@ export default function lspSignatureHelpExt(
|
||||
|
||||
// Check if changes include trigger characters
|
||||
const changes = update.changes
|
||||
let shouldTrigger = false
|
||||
let triggerPos = -1
|
||||
|
||||
changes.iterChanges((_fromA, _toA, _fromB, toB, inserted) => {
|
||||
if (shouldTrigger) return // Skip if already found a trigger
|
||||
if (triggerPos >= 0) return // Skip if already found a trigger
|
||||
|
||||
const text = inserted.toString()
|
||||
if (!text) return
|
||||
|
||||
for (const char of triggerChars) {
|
||||
if (text.includes(char)) {
|
||||
shouldTrigger = true
|
||||
triggerPos = toB
|
||||
triggerCharacter = char
|
||||
break
|
||||
@ -78,13 +125,17 @@ export default function lspSignatureHelpExt(
|
||||
}
|
||||
})
|
||||
|
||||
if (shouldTrigger && triggerPos >= 0) {
|
||||
await value.showSignatureHelpTooltip(
|
||||
update.view,
|
||||
triggerPos,
|
||||
triggerCharacter
|
||||
)
|
||||
if (triggerPos >= 0) {
|
||||
const parens = findParenthesized(update.view.state, triggerPos, -1)
|
||||
if (parens) {
|
||||
await value.showSignatureHelpTooltip(
|
||||
update.view,
|
||||
parens.from,
|
||||
triggerCharacter
|
||||
)
|
||||
}
|
||||
}
|
||||
}),
|
||||
signatureTooltip,
|
||||
]
|
||||
}
|
||||
|
25
rust/Cargo.lock
generated
25
rust/Cargo.lock
generated
@ -1815,7 +1815,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-bumper"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1826,7 +1826,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-derive-docs"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
@ -1845,7 +1845,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-directory-test-macro"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1854,7 +1854,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-language-server"
|
||||
version = "0.2.68"
|
||||
version = "0.2.69"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1875,7 +1875,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-language-server-release"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1895,7 +1895,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.68"
|
||||
version = "0.2.69"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@ -1959,6 +1959,7 @@ dependencies = [
|
||||
"url",
|
||||
"uuid",
|
||||
"validator",
|
||||
"walkdir",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
@ -1970,7 +1971,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-python-bindings"
|
||||
version = "0.3.68"
|
||||
version = "0.3.69"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"kcl-lib",
|
||||
@ -1985,7 +1986,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hyper 0.14.32",
|
||||
@ -1998,7 +1999,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-to-core"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -2012,7 +2013,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-wasm-lib"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bson",
|
||||
@ -2042,9 +2043,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.3.36"
|
||||
version = "0.3.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a345fd2a4cb16205f32bd1aa41715045830c59d78c59927fca6580e2a651ac9"
|
||||
checksum = "b48a9698d68c791df76aa020b596c324177a614e38ff3dd67eedd04db76e222f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -35,7 +35,7 @@ console_error_panic_hook = "0.1.7"
|
||||
dashmap = { version = "6.1.0" }
|
||||
http = "1"
|
||||
indexmap = "2.9.0"
|
||||
kittycad = { version = "0.3.36", default-features = false, features = ["js", "requests"] }
|
||||
kittycad = { version = "0.3.37", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-cmds = { version = "0.2.117", features = ["ts-rs", "websocket"] }
|
||||
lazy_static = "1.5.0"
|
||||
miette = "7.5.0"
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
[package]
|
||||
name = "kcl-bumper"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/KittyCAD/modeling-api"
|
||||
rust-version = "1.76"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-directory-test-macro"
|
||||
description = "A tool for generating tests from a directory of kcl files"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "kcl-language-server-release"
|
||||
version = "0.1.68"
|
||||
version = "0.1.69"
|
||||
edition = "2021"
|
||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
|
||||
publish = false
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "kcl-language-server"
|
||||
description = "A language server for KCL."
|
||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
|
||||
version = "0.2.68"
|
||||
version = "0.2.69"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language implementation and tools"
|
||||
version = "0.2.68"
|
||||
version = "0.2.69"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
@ -85,6 +85,7 @@ tynm = "0.1.10"
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
uuid = { workspace = true, features = ["v4", "v5", "js", "serde"] }
|
||||
validator = { version = "0.20.0", features = ["derive"] }
|
||||
walkdir = "2.5.0"
|
||||
web-time = "1.1"
|
||||
winnow = "=0.6.24"
|
||||
zip = { workspace = true }
|
||||
|
@ -3,6 +3,7 @@
|
||||
use kcl_lib::{bust_cache, ExecError, ExecOutcome};
|
||||
use kcmc::{each_cmd as mcmd, ModelingCmd};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Variation<'a> {
|
||||
@ -247,8 +248,11 @@ extrude(profile001, length = 100)"#
|
||||
)
|
||||
.await;
|
||||
|
||||
result.first().unwrap();
|
||||
result.last().unwrap();
|
||||
let first = result.first().unwrap();
|
||||
let last = 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")]
|
||||
@ -550,3 +554,64 @@ extrude(profile001, length = 100)
|
||||
"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");
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
After Width: | Height: | Size: 109 KiB |
@ -16,7 +16,7 @@ use crate::{
|
||||
};
|
||||
|
||||
// Types with special handling.
|
||||
const SPECIAL_TYPES: [&str; 5] = ["TagDeclarator", "TagIdentifier", "Start", "End", "ImportedGeometry"];
|
||||
const SPECIAL_TYPES: [&str; 4] = ["TagDeclarator", "TagIdentifier", "Start", "End"];
|
||||
|
||||
const TYPE_REWRITES: [(&str, &str); 11] = [
|
||||
("TagNode", "TagDeclarator"),
|
||||
@ -380,6 +380,21 @@ fn generate_function_from_kcl(
|
||||
.enumerate()
|
||||
.filter_map(|(index, example)| generate_example(index, &example.0, &example.1, &example_name))
|
||||
.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!({
|
||||
"name": function.preferred_name,
|
||||
@ -389,14 +404,7 @@ fn generate_function_from_kcl(
|
||||
"deprecated": function.properties.deprecated,
|
||||
"fn_signature": function.preferred_name.clone() + &function.fn_signature(),
|
||||
"examples": examples,
|
||||
"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<_>>(),
|
||||
"args": args,
|
||||
"return_value": function.return_type.as_ref().map(|t| {
|
||||
json!({
|
||||
"type_": t,
|
||||
|
@ -626,6 +626,8 @@ impl FnData {
|
||||
pub(super) fn to_autocomplete_snippet(&self) -> String {
|
||||
if self.name == "loft" {
|
||||
return "loft([${0:sketch000}, ${1:sketch001}])".to_owned();
|
||||
} else if self.name == "clone" {
|
||||
return "clone(${0:part001})".to_owned();
|
||||
} else if self.name == "hole" {
|
||||
return "hole(${0:holeSketch}, ${1:%})".to_owned();
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ use std::{
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_doc::ModData;
|
||||
use parse_display::Display;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::lsp_types::{
|
||||
@ -18,15 +19,27 @@ use tower_lsp::lsp_types::{
|
||||
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
execution::{types::NumericType, Sketch},
|
||||
std::Primitive,
|
||||
};
|
||||
use crate::execution::{types::NumericType, Sketch};
|
||||
|
||||
// These types are declared in (KCL) std.
|
||||
const DECLARED_TYPES: [&str; 15] = [
|
||||
"any", "number", "string", "tag", "bool", "Sketch", "Solid", "Plane", "Helix", "Face", "Edge", "Point2d",
|
||||
"Point3d", "Axis2d", "Axis3d",
|
||||
const DECLARED_TYPES: [&str; 17] = [
|
||||
"any",
|
||||
"number",
|
||||
"string",
|
||||
"tag",
|
||||
"bool",
|
||||
"Sketch",
|
||||
"Solid",
|
||||
"Plane",
|
||||
"Helix",
|
||||
"Face",
|
||||
"Edge",
|
||||
"Point2d",
|
||||
"Point3d",
|
||||
"Axis2d",
|
||||
"Axis3d",
|
||||
"ImportedGeometry",
|
||||
"Fn",
|
||||
];
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@ -38,6 +51,21 @@ 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)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -167,6 +195,7 @@ impl StdLibFnArg {
|
||||
pub fn description(&self, kcl_std: Option<&ModData>) -> Option<String> {
|
||||
// Check if we explicitly gave this stdlib arg a description.
|
||||
if !self.description.is_empty() {
|
||||
assert!(!self.description.contains('\n'), "Arg docs will get truncated");
|
||||
return Some(self.description.clone());
|
||||
}
|
||||
|
||||
@ -533,8 +562,6 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
|
||||
fn to_autocomplete_snippet(&self) -> Result<String> {
|
||||
if self.name() == "loft" {
|
||||
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" {
|
||||
return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string());
|
||||
} else if self.name() == "subtract" {
|
||||
@ -701,7 +728,7 @@ pub fn get_description_string_from_schema(schema: &schemars::schema::RootSchema)
|
||||
None
|
||||
}
|
||||
|
||||
pub fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> {
|
||||
fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> {
|
||||
match schema {
|
||||
schemars::schema::Schema::Object(o) => {
|
||||
if o.enum_values.is_some() {
|
||||
@ -1007,9 +1034,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_map() {
|
||||
let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map);
|
||||
let snippet = map_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"map(${0:[0..9]})"#);
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(map_fn) = data.find_by_name("map").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = map_fn.to_autocomplete_snippet();
|
||||
assert_eq!(snippet, r#"map()"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1129,8 +1159,11 @@ mod tests {
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_clone() {
|
||||
let clone_fn: Box<dyn StdLibFn> = Box::new(crate::std::clone::Clone);
|
||||
let snippet = clone_fn.to_autocomplete_snippet().unwrap();
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(clone_fn) = data.find_by_name("clone").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = clone_fn.to_autocomplete_snippet();
|
||||
assert_eq!(snippet, r#"clone(${0:part001})"#);
|
||||
}
|
||||
|
||||
|
@ -559,7 +559,7 @@ impl From<KclError> for pyo3::PyErr {
|
||||
}
|
||||
|
||||
/// An error which occurred during parsing, etc.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS, PartialEq, Eq)]
|
||||
#[ts(export)]
|
||||
pub struct CompilationError {
|
||||
#[serde(rename = "sourceRange")]
|
||||
|
@ -118,7 +118,6 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
|
||||
// We know they have the same imports because the ast is the same.
|
||||
// If we have no imports, we can skip this.
|
||||
if !old.ast.has_import_statements() {
|
||||
println!("No imports, no need to check.");
|
||||
return CacheResult::NoAction(reapply_settings);
|
||||
}
|
||||
|
||||
|
@ -278,7 +278,7 @@ impl From<&KclValue> for OpKclValue {
|
||||
ty: ty.clone(),
|
||||
},
|
||||
KclValue::String { value, .. } => Self::String { value: value.clone() },
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
let value = value.iter().map(Self::from).collect();
|
||||
Self::Array { value }
|
||||
}
|
||||
|
@ -16,12 +16,13 @@ use crate::{
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo,
|
||||
TagIdentifier,
|
||||
},
|
||||
fmt,
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
|
||||
CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, ObjectExpression,
|
||||
PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
||||
BinaryPart, BodyItem, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector,
|
||||
ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef,
|
||||
ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
std::{
|
||||
@ -707,17 +708,25 @@ impl ExecutorContext {
|
||||
// TODO this lets us use the label as a variable name, but not as a tag in most cases
|
||||
result
|
||||
}
|
||||
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())?
|
||||
}
|
||||
Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
|
||||
};
|
||||
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(
|
||||
value: &KclValue,
|
||||
ty: &Node<Type>,
|
||||
@ -758,6 +767,7 @@ impl BinaryPart {
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
|
||||
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
|
||||
BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -867,11 +877,7 @@ impl Node<MemberExpression> {
|
||||
source_ranges: vec![self.clone().into()],
|
||||
}))
|
||||
}
|
||||
(
|
||||
KclValue::MixedArray { value: arr, .. } | KclValue::HomArray { value: arr, .. },
|
||||
Property::UInt(index),
|
||||
_,
|
||||
) => {
|
||||
(KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
|
||||
let value_of_arr = arr.get(index);
|
||||
if let Some(value) = value_of_arr {
|
||||
Ok(value.to_owned())
|
||||
@ -882,7 +888,7 @@ impl Node<MemberExpression> {
|
||||
}))
|
||||
}
|
||||
}
|
||||
(KclValue::MixedArray { .. } | KclValue::HomArray { .. }, p, _) => {
|
||||
(KclValue::HomArray { .. }, p, _) => {
|
||||
let t = p.type_name();
|
||||
let article = article_for(t);
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
@ -1170,7 +1176,7 @@ impl Node<UnaryExpression> {
|
||||
};
|
||||
|
||||
let direction = match direction {
|
||||
KclValue::MixedArray { value: values, meta } => {
|
||||
KclValue::Tuple { value: values, meta } => {
|
||||
let values = values
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
@ -1183,7 +1189,7 @@ impl Node<UnaryExpression> {
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
KclValue::MixedArray {
|
||||
KclValue::Tuple {
|
||||
value: values,
|
||||
meta: meta.clone(),
|
||||
}
|
||||
@ -1551,7 +1557,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
||||
}
|
||||
}
|
||||
}
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
for v in value {
|
||||
update_memory_for_tags_of_geometry(v, exec_state)?;
|
||||
}
|
||||
@ -1595,9 +1601,9 @@ impl Node<ArrayExpression> {
|
||||
results.push(value);
|
||||
}
|
||||
|
||||
Ok(KclValue::MixedArray {
|
||||
Ok(KclValue::HomArray {
|
||||
value: results,
|
||||
meta: vec![self.into()],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Any),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1606,7 +1612,7 @@ impl Node<ArrayRangeExpression> {
|
||||
#[async_recursion]
|
||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
let metadata = Metadata::from(&self.start_element);
|
||||
let start = ctx
|
||||
let start_val = ctx
|
||||
.execute_expr(
|
||||
&self.start_element,
|
||||
exec_state,
|
||||
@ -1615,19 +1621,30 @@ impl Node<ArrayRangeExpression> {
|
||||
StatementKind::Expression,
|
||||
)
|
||||
.await?;
|
||||
let start = start.as_int().ok_or(KclError::Semantic(KclErrorDetails {
|
||||
let (start, start_ty) = start_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: vec![self.into()],
|
||||
message: format!("Expected int but found {}", start.human_friendly_type()),
|
||||
message: format!("Expected int but found {}", start_val.human_friendly_type()),
|
||||
}))?;
|
||||
let metadata = Metadata::from(&self.end_element);
|
||||
let end = ctx
|
||||
let end_val = ctx
|
||||
.execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
|
||||
.await?;
|
||||
let end = end.as_int().ok_or(KclError::Semantic(KclErrorDetails {
|
||||
let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: vec![self.into()],
|
||||
message: format!("Expected int but found {}", end.human_friendly_type()),
|
||||
message: format!("Expected int but found {}", end_val.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 {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: vec![self.into()],
|
||||
@ -1644,16 +1661,17 @@ impl Node<ArrayRangeExpression> {
|
||||
let meta = vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}];
|
||||
Ok(KclValue::MixedArray {
|
||||
|
||||
Ok(KclValue::HomArray {
|
||||
value: range
|
||||
.into_iter()
|
||||
.map(|num| KclValue::Number {
|
||||
value: num as f64,
|
||||
ty: NumericType::count(),
|
||||
ty: start_ty.clone(),
|
||||
meta: meta.clone(),
|
||||
})
|
||||
.collect(),
|
||||
meta,
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(start_ty)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1868,7 +1886,7 @@ fn type_check_params_kw(
|
||||
arg.value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
|
||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|e| {
|
||||
@ -1946,7 +1964,8 @@ fn type_check_params_kw(
|
||||
arg.value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
|
||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range)
|
||||
.map_err(|e| KclError::Semantic(e.into()))?,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|_| {
|
||||
@ -2125,7 +2144,8 @@ impl FunctionSource {
|
||||
arg.value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
|
||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range)
|
||||
.map_err(|e| KclError::Semantic(e.into()))?,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|_| {
|
||||
@ -2235,9 +2255,10 @@ mod test {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
exec::UnitType,
|
||||
execution::{memory::Stack, parse_execute, ContextType},
|
||||
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
|
||||
ExecutorSettings,
|
||||
ExecutorSettings, UnitLen,
|
||||
};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2391,6 +2412,7 @@ p = {
|
||||
yAxis = { x = 0, y = 1, z = 0 },
|
||||
zAxis = { x = 0, y = 0, z = 1 }
|
||||
}: Plane
|
||||
arr1 = [42]: [number(cm)]
|
||||
"#;
|
||||
|
||||
let result = parse_execute(program).await.unwrap();
|
||||
@ -2401,6 +2423,24 @@ p = {
|
||||
.unwrap(),
|
||||
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#"
|
||||
a = 42: string
|
||||
@ -2419,6 +2459,28 @@ a = 42: Plane
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.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")]
|
||||
@ -2638,4 +2700,19 @@ sketch001 = startSketchOn(XY)
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -1239,6 +1239,20 @@ impl Path {
|
||||
[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.
|
||||
pub fn length(&self) -> TyF64 {
|
||||
let n = match self {
|
||||
|
@ -48,7 +48,7 @@ pub enum KclValue {
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
MixedArray {
|
||||
Tuple {
|
||||
value: Vec<KclValue>,
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
@ -197,7 +197,7 @@ impl From<KclValue> for Vec<SourceRange> {
|
||||
KclValue::Bool { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Number { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::String { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::MixedArray { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
|
||||
KclValue::Object { 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::String { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Uuid { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::MixedArray { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Tuple { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
|
||||
KclValue::Object { 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::Number { meta, .. } => meta.clone(),
|
||||
KclValue::String { value: _, meta } => meta.clone(),
|
||||
KclValue::MixedArray { value: _, meta } => meta.clone(),
|
||||
KclValue::Tuple { value: _, meta } => meta.clone(),
|
||||
KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
|
||||
KclValue::Object { value: _, meta } => meta.clone(),
|
||||
KclValue::TagIdentifier(x) => x.meta.clone(),
|
||||
@ -307,7 +307,7 @@ impl KclValue {
|
||||
} => "number(Angle)",
|
||||
KclValue::Number { .. } => "number",
|
||||
KclValue::String { .. } => "string (text)",
|
||||
KclValue::MixedArray { .. } => "mixed array (list)",
|
||||
KclValue::Tuple { .. } => "tuple (list)",
|
||||
KclValue::HomArray { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::Module { .. } => "module",
|
||||
@ -373,7 +373,7 @@ impl KclValue {
|
||||
|
||||
/// Put the point into a KCL value.
|
||||
pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
|
||||
Self::MixedArray {
|
||||
Self::Tuple {
|
||||
value: vec![
|
||||
Self::Number {
|
||||
value: p[0],
|
||||
@ -404,6 +404,13 @@ 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> {
|
||||
if let KclValue::Object { value, meta: _ } = &self {
|
||||
Some(value)
|
||||
@ -430,7 +437,7 @@ impl KclValue {
|
||||
|
||||
pub fn as_array(&self) -> Option<&[KclValue]> {
|
||||
match self {
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => Some(value),
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -602,7 +609,7 @@ impl KclValue {
|
||||
KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
|
||||
KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
|
||||
// TODO better Array and Object stringification
|
||||
KclValue::MixedArray { .. } => Some("[...]".to_owned()),
|
||||
KclValue::Tuple { .. } => Some("[...]".to_owned()),
|
||||
KclValue::HomArray { .. } => Some("[...]".to_owned()),
|
||||
KclValue::Object { .. } => Some("{ ... }".to_owned()),
|
||||
KclValue::Module { .. }
|
||||
|
@ -64,7 +64,7 @@ pub mod typed_path;
|
||||
pub(crate) mod types;
|
||||
|
||||
/// Outcome of executing a program. This is used in TS.
|
||||
#[derive(Debug, Clone, Serialize, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Serialize, ts_rs::TS, PartialEq)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExecOutcome {
|
||||
@ -385,6 +385,7 @@ impl ExecutorContext {
|
||||
let (ws, _headers) = client
|
||||
.modeling()
|
||||
.commands_ws(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
if settings.enable_ssao {
|
||||
@ -653,14 +654,22 @@ impl ExecutorContext {
|
||||
keys.sort();
|
||||
for key in keys {
|
||||
let (_, id, _, _) = &new_universe[key];
|
||||
let old_source = old_state.get_source(*id);
|
||||
let new_source = new_exec_state.get_source(*id);
|
||||
if old_source != new_source {
|
||||
clear_scene = true;
|
||||
break;
|
||||
if let (Some(source0), Some(source1)) =
|
||||
(old_state.get_source(*id), new_exec_state.get_source(*id))
|
||||
{
|
||||
if source0.source != source1.source {
|
||||
clear_scene = true;
|
||||
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,
|
||||
crate::Program {
|
||||
@ -1932,7 +1941,7 @@ a = []
|
||||
notArray = !a";
|
||||
assert_eq!(
|
||||
parse_execute(code5).await.unwrap_err().message(),
|
||||
"Cannot apply unary operator ! to non-boolean value: mixed array (list)",
|
||||
"Cannot apply unary operator ! to non-boolean value: array (list)",
|
||||
);
|
||||
|
||||
let code6 = "
|
||||
@ -2244,7 +2253,7 @@ w = f() + f()
|
||||
|> line(end = [0, 0])
|
||||
|> close()
|
||||
}
|
||||
|
||||
|
||||
sketch = startSketchOn(XY)
|
||||
|> startProfile(at = [0,0])
|
||||
|> line(end = [0, 10])
|
||||
|
@ -28,6 +28,10 @@ pub enum RuntimeType {
|
||||
}
|
||||
|
||||
impl RuntimeType {
|
||||
pub fn any() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::Any)
|
||||
}
|
||||
|
||||
pub fn edge() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::Edge)
|
||||
}
|
||||
@ -72,6 +76,10 @@ impl RuntimeType {
|
||||
RuntimeType::Primitive(PrimitiveType::Tag)
|
||||
}
|
||||
|
||||
pub fn tag_identifier() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::TagId)
|
||||
}
|
||||
|
||||
pub fn bool() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean)
|
||||
}
|
||||
@ -162,13 +170,20 @@ impl RuntimeType {
|
||||
source_range: SourceRange,
|
||||
) -> Result<Self, CompilationError> {
|
||||
Ok(match value {
|
||||
AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
|
||||
AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
|
||||
AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
|
||||
AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number(
|
||||
NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
|
||||
)),
|
||||
AstPrimitiveType::Number(suffix) => {
|
||||
let ty = match suffix {
|
||||
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::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
|
||||
AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
|
||||
AstPrimitiveType::Function => RuntimeType::Primitive(PrimitiveType::Function),
|
||||
})
|
||||
}
|
||||
|
||||
@ -203,7 +218,7 @@ impl RuntimeType {
|
||||
.collect::<Vec<_>>()
|
||||
.join(" or "),
|
||||
RuntimeType::Tuple(tys) => format!(
|
||||
"an array with values of types ({})",
|
||||
"a tuple with values of types ({})",
|
||||
tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
|
||||
),
|
||||
RuntimeType::Object(_) => format!("an object with fields {}", self),
|
||||
@ -215,6 +230,7 @@ impl RuntimeType {
|
||||
use RuntimeType::*;
|
||||
|
||||
match (self, sup) {
|
||||
(_, Primitive(PrimitiveType::Any)) => true,
|
||||
(Primitive(t1), Primitive(t2)) => t1.subtype(t2),
|
||||
(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)),
|
||||
@ -265,7 +281,7 @@ impl RuntimeType {
|
||||
.map(|t| t.display_multiple())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" or "),
|
||||
RuntimeType::Tuple(_) => "arrays".to_owned(),
|
||||
RuntimeType::Tuple(_) => "tuples".to_owned(),
|
||||
RuntimeType::Object(_) => format!("objects with fields {self}"),
|
||||
}
|
||||
}
|
||||
@ -282,7 +298,7 @@ impl fmt::Display for RuntimeType {
|
||||
},
|
||||
RuntimeType::Tuple(ts) => write!(
|
||||
f,
|
||||
"[{}]",
|
||||
"({})",
|
||||
ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
|
||||
),
|
||||
RuntimeType::Union(ts) => write!(
|
||||
@ -333,10 +349,13 @@ impl ArrayLen {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PrimitiveType {
|
||||
Any,
|
||||
Number(NumericType),
|
||||
String,
|
||||
Boolean,
|
||||
Tag,
|
||||
// Annoyingly some functions only want a TagIdentifier, not any kind of tag.
|
||||
TagId,
|
||||
Sketch,
|
||||
Solid,
|
||||
Plane,
|
||||
@ -346,11 +365,13 @@ pub enum PrimitiveType {
|
||||
Axis2d,
|
||||
Axis3d,
|
||||
ImportedGeometry,
|
||||
Function,
|
||||
}
|
||||
|
||||
impl PrimitiveType {
|
||||
fn display_multiple(&self) -> String {
|
||||
match self {
|
||||
PrimitiveType::Any => "any values".to_owned(),
|
||||
PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
|
||||
PrimitiveType::Number(_) => "numbers".to_owned(),
|
||||
PrimitiveType::String => "strings".to_owned(),
|
||||
@ -364,13 +385,17 @@ impl PrimitiveType {
|
||||
PrimitiveType::Axis2d => "2d axes".to_owned(),
|
||||
PrimitiveType::Axis3d => "3d axes".to_owned(),
|
||||
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
|
||||
PrimitiveType::Function => "functions".to_owned(),
|
||||
PrimitiveType::Tag => "tags".to_owned(),
|
||||
PrimitiveType::TagId => "tag identifiers".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn subtype(&self, other: &PrimitiveType) -> bool {
|
||||
match (self, other) {
|
||||
(_, PrimitiveType::Any) => true,
|
||||
(PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
|
||||
(PrimitiveType::TagId, PrimitiveType::Tag) => true,
|
||||
(t1, t2) => t1 == t2,
|
||||
}
|
||||
}
|
||||
@ -379,6 +404,7 @@ impl PrimitiveType {
|
||||
impl fmt::Display for PrimitiveType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PrimitiveType::Any => write!(f, "any"),
|
||||
PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
|
||||
PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
|
||||
PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number(default units)"),
|
||||
@ -386,6 +412,7 @@ impl fmt::Display for PrimitiveType {
|
||||
PrimitiveType::String => write!(f, "string"),
|
||||
PrimitiveType::Boolean => write!(f, "bool"),
|
||||
PrimitiveType::Tag => write!(f, "tag"),
|
||||
PrimitiveType::TagId => write!(f, "tag identifier"),
|
||||
PrimitiveType::Sketch => write!(f, "Sketch"),
|
||||
PrimitiveType::Solid => write!(f, "Solid"),
|
||||
PrimitiveType::Plane => write!(f, "Plane"),
|
||||
@ -395,6 +422,7 @@ impl fmt::Display for PrimitiveType {
|
||||
PrimitiveType::Axis3d => write!(f, "Axis3d"),
|
||||
PrimitiveType::Helix => write!(f, "Helix"),
|
||||
PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
|
||||
PrimitiveType::Function => write!(f, "function"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -663,6 +691,7 @@ impl NumericType {
|
||||
// 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(val.into()),
|
||||
|
||||
(Any, _) => Ok(KclValue::Number {
|
||||
value: *value,
|
||||
ty: self.clone(),
|
||||
@ -994,9 +1023,9 @@ impl KclValue {
|
||||
self_ty.subtype(ty)
|
||||
}
|
||||
|
||||
/// Coerce `self` to a new value which has `ty` as it's closest supertype.
|
||||
/// Coerce `self` to a new value which has `ty` as its closest supertype.
|
||||
///
|
||||
/// If the result is Some, then:
|
||||
/// If the result is Ok, then:
|
||||
/// - result.principal_type().unwrap().subtype(ty)
|
||||
///
|
||||
/// If self.principal_type() == ty then result == self
|
||||
@ -1016,10 +1045,11 @@ impl KclValue {
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<KclValue, CoercionError> {
|
||||
let value = match self {
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
|
||||
_ => self,
|
||||
};
|
||||
match ty {
|
||||
PrimitiveType::Any => Ok(value.clone()),
|
||||
PrimitiveType::Number(ty) => ty.coerce(value),
|
||||
PrimitiveType::String => match value {
|
||||
KclValue::String { .. } => Ok(value.clone()),
|
||||
@ -1159,6 +1189,14 @@ impl KclValue {
|
||||
KclValue::ImportedGeometry { .. } => Ok(value.clone()),
|
||||
_ => 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 {
|
||||
KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
|
||||
Ok(value.clone())
|
||||
@ -1178,41 +1216,49 @@ impl KclValue {
|
||||
exec_state: &mut ExecState,
|
||||
allow_shrink: bool,
|
||||
) -> 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 {
|
||||
KclValue::HomArray { value, ty: aty } => {
|
||||
KclValue::HomArray { value, ty: aty, .. } => {
|
||||
let satisfied_len = len.satisfied(value.len(), allow_shrink);
|
||||
|
||||
if aty.subtype(ty) {
|
||||
len.satisfied(value.len(), allow_shrink)
|
||||
// If the element type is a subtype of the target type and
|
||||
// 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 {
|
||||
value: value[..len].to_vec(),
|
||||
ty: aty.clone(),
|
||||
})
|
||||
.ok_or(self.into())
|
||||
} else {
|
||||
Err(self.into())
|
||||
.ok_or(self.into());
|
||||
}
|
||||
}
|
||||
KclValue::MixedArray { value, .. } => {
|
||||
// Check if we have a nested homogeneous array that we can flatten.
|
||||
|
||||
// Ignore the array type, and coerce the elements of the array.
|
||||
if let Some(satisfied_len) = satisfied_len {
|
||||
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();
|
||||
for item in value {
|
||||
if let KclValue::HomArray {
|
||||
ty: inner_ty,
|
||||
value: inner_value,
|
||||
} = item
|
||||
{
|
||||
if inner_ty.subtype(ty) {
|
||||
values.extend(inner_value.iter().cloned());
|
||||
} else {
|
||||
values.push(item.clone());
|
||||
if let KclValue::HomArray { value: inner_value, .. } = item {
|
||||
// Flatten elements.
|
||||
for item in inner_value {
|
||||
values.push(item.coerce(ty, exec_state)?);
|
||||
}
|
||||
} else {
|
||||
values.push(item.clone());
|
||||
values.push(item.coerce(ty, exec_state)?);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1220,9 +1266,22 @@ impl KclValue {
|
||||
.satisfied(values.len(), allow_shrink)
|
||||
.ok_or(CoercionError::from(self))?;
|
||||
|
||||
let value = values[..len]
|
||||
assert!(len <= 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()
|
||||
.map(|v| v.coerce(ty, exec_state))
|
||||
.map(|item| item.coerce(ty, exec_state))
|
||||
.take(len)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(KclValue::HomArray { value, ty: ty.clone() })
|
||||
@ -1231,28 +1290,32 @@ impl KclValue {
|
||||
value: Vec::new(),
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
|
||||
match self {
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
|
||||
let mut result = Vec::new();
|
||||
for (i, t) in tys.iter().enumerate() {
|
||||
result.push(value[i].coerce(t, exec_state)?);
|
||||
}
|
||||
|
||||
Ok(KclValue::MixedArray {
|
||||
Ok(KclValue::Tuple {
|
||||
value: result,
|
||||
meta: Vec::new(),
|
||||
})
|
||||
}
|
||||
KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::MixedArray {
|
||||
KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
|
||||
value: Vec::new(),
|
||||
meta: meta.clone(),
|
||||
}),
|
||||
value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::MixedArray {
|
||||
value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::Tuple {
|
||||
value: vec![value.clone()],
|
||||
meta: Vec::new(),
|
||||
}),
|
||||
@ -1312,18 +1375,16 @@ impl KclValue {
|
||||
KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
|
||||
KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
|
||||
KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
|
||||
KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
|
||||
KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
|
||||
value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
|
||||
)),
|
||||
KclValue::HomArray { ty, value, .. } => {
|
||||
Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
|
||||
}
|
||||
KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => {
|
||||
Some(RuntimeType::Primitive(PrimitiveType::Tag))
|
||||
}
|
||||
KclValue::Function { .. } | KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => {
|
||||
None
|
||||
}
|
||||
KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TagId)),
|
||||
KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
|
||||
KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
|
||||
KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1348,7 +1409,7 @@ mod test {
|
||||
value: "hello".to_owned(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::MixedArray {
|
||||
KclValue::Tuple {
|
||||
value: Vec::new(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
@ -1417,45 +1478,67 @@ mod test {
|
||||
let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
|
||||
let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
|
||||
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&aty,
|
||||
&KclValue::HomArray {
|
||||
value: vec![v.clone()],
|
||||
ty: ty.clone(),
|
||||
},
|
||||
&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,
|
||||
);
|
||||
match v {
|
||||
KclValue::Tuple { .. } | KclValue::HomArray { .. } => {
|
||||
// These will not get wrapped if possible.
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&aty,
|
||||
&KclValue::HomArray {
|
||||
value: vec![],
|
||||
ty: ty.clone(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
// Coercing an empty tuple or array to an array of length 1
|
||||
// should fail.
|
||||
v.coerce(&aty1, &mut exec_state).unwrap_err();
|
||||
// Coercing an empty tuple or array to an array that's
|
||||
// non-empty should fail.
|
||||
v.coerce(&aty0, &mut exec_state).unwrap_err();
|
||||
}
|
||||
_ => {
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&aty,
|
||||
&KclValue::HomArray {
|
||||
value: vec![v.clone()],
|
||||
ty: ty.clone(),
|
||||
},
|
||||
&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
|
||||
let tty = RuntimeType::Tuple(vec![ty.clone()]);
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&tty,
|
||||
&KclValue::MixedArray {
|
||||
value: vec![v.clone()],
|
||||
meta: Vec::new(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
// Tuple subtype
|
||||
let tty = RuntimeType::Tuple(vec![ty.clone()]);
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&tty,
|
||||
&KclValue::Tuple {
|
||||
value: vec![v.clone()],
|
||||
meta: Vec::new(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for v in &values[1..] {
|
||||
@ -1503,7 +1586,7 @@ mod test {
|
||||
assert_coerce_results(
|
||||
&none,
|
||||
&tty,
|
||||
&KclValue::MixedArray {
|
||||
&KclValue::Tuple {
|
||||
value: Vec::new(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
@ -1634,7 +1717,7 @@ mod test {
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
};
|
||||
let mixed1 = KclValue::MixedArray {
|
||||
let mixed1 = KclValue::Tuple {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
@ -1649,7 +1732,7 @@ mod test {
|
||||
],
|
||||
meta: Vec::new(),
|
||||
};
|
||||
let mixed2 = KclValue::MixedArray {
|
||||
let mixed2 = KclValue::Tuple {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
@ -1739,7 +1822,7 @@ mod test {
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
};
|
||||
let mixed0 = KclValue::MixedArray {
|
||||
let mixed0 = KclValue::Tuple {
|
||||
value: vec![],
|
||||
meta: Vec::new(),
|
||||
};
|
||||
@ -2156,7 +2239,7 @@ d = cos(30)
|
||||
async fn coerce_nested_array() {
|
||||
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
|
||||
|
||||
let mixed1 = KclValue::MixedArray {
|
||||
let mixed1 = KclValue::HomArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
@ -2184,7 +2267,7 @@ d = cos(30)
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
},
|
||||
],
|
||||
meta: Vec::new(),
|
||||
ty: RuntimeType::any(),
|
||||
};
|
||||
|
||||
// Principal types
|
||||
|
@ -150,6 +150,7 @@ impl BinaryPart {
|
||||
unary_expression.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) => {
|
||||
member_expression.get_hover_value_for_position(pos, code, opts)
|
||||
}
|
||||
|
@ -162,6 +162,7 @@ impl BinaryPart {
|
||||
BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
|
||||
BinaryPart::MemberExpression(me) => me.compute_digest(),
|
||||
BinaryPart::IfExpression(e) => e.compute_digest(),
|
||||
BinaryPart::AscribedExpression(e) => e.compute_digest(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,11 +226,14 @@ impl PrimitiveType {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
let mut hasher = Sha256::new();
|
||||
match self {
|
||||
PrimitiveType::Any => hasher.update(b"any"),
|
||||
PrimitiveType::Named(id) => hasher.update(id.compute_digest()),
|
||||
PrimitiveType::String => hasher.update(b"string"),
|
||||
PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()),
|
||||
PrimitiveType::Boolean => hasher.update(b"bool"),
|
||||
PrimitiveType::Tag => hasher.update(b"tag"),
|
||||
PrimitiveType::ImportedGeometry => hasher.update(b"ImportedGeometry"),
|
||||
PrimitiveType::Function => hasher.update(b"Fn"),
|
||||
}
|
||||
|
||||
hasher.finalize().into()
|
||||
|
@ -52,6 +52,7 @@ impl BinaryPart {
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
|
||||
BinaryPart::IfExpression(e) => e.module_id,
|
||||
BinaryPart::AscribedExpression(e) => e.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1215,6 +1215,7 @@ impl From<&BinaryPart> for Expr {
|
||||
BinaryPart::UnaryExpression(unary_expression) => Expr::UnaryExpression(unary_expression.clone()),
|
||||
BinaryPart::MemberExpression(member_expression) => Expr::MemberExpression(member_expression.clone()),
|
||||
BinaryPart::IfExpression(e) => Expr::IfExpression(e.clone()),
|
||||
BinaryPart::AscribedExpression(e) => Expr::AscribedExpression(e.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1281,6 +1282,7 @@ pub enum BinaryPart {
|
||||
UnaryExpression(BoxNode<UnaryExpression>),
|
||||
MemberExpression(BoxNode<MemberExpression>),
|
||||
IfExpression(BoxNode<IfExpression>),
|
||||
AscribedExpression(BoxNode<AscribedExpression>),
|
||||
}
|
||||
|
||||
impl From<BinaryPart> for SourceRange {
|
||||
@ -1306,6 +1308,7 @@ impl BinaryPart {
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||
BinaryPart::IfExpression(e) => e.get_constraint_level(),
|
||||
BinaryPart::AscribedExpression(e) => e.expr.get_constraint_level(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1324,6 +1327,7 @@ impl BinaryPart {
|
||||
}
|
||||
BinaryPart::MemberExpression(_) => {}
|
||||
BinaryPart::IfExpression(e) => e.replace_value(source_range, new_value),
|
||||
BinaryPart::AscribedExpression(e) => e.expr.replace_value(source_range, new_value),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1336,6 +1340,7 @@ impl BinaryPart {
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start,
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.start,
|
||||
BinaryPart::IfExpression(e) => e.start,
|
||||
BinaryPart::AscribedExpression(e) => e.start,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1348,6 +1353,7 @@ impl BinaryPart {
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end,
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.end,
|
||||
BinaryPart::IfExpression(e) => e.end,
|
||||
BinaryPart::AscribedExpression(e) => e.end,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1369,6 +1375,7 @@ impl BinaryPart {
|
||||
member_expression.rename_identifiers(old_name, new_name)
|
||||
}
|
||||
BinaryPart::IfExpression(ref mut if_expression) => if_expression.rename_identifiers(old_name, new_name),
|
||||
BinaryPart::AscribedExpression(ref mut e) => e.expr.rename_identifiers(old_name, new_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3179,6 +3186,8 @@ impl PipeExpression {
|
||||
#[ts(export)]
|
||||
#[serde(tag = "p_type")]
|
||||
pub enum PrimitiveType {
|
||||
/// The super type of all other types.
|
||||
Any,
|
||||
/// A string type.
|
||||
String,
|
||||
/// A number type.
|
||||
@ -3188,6 +3197,10 @@ pub enum PrimitiveType {
|
||||
Boolean,
|
||||
/// A tag.
|
||||
Tag,
|
||||
/// Imported from other CAD system.
|
||||
ImportedGeometry,
|
||||
/// `Fn`, type of functions.
|
||||
Function,
|
||||
/// An identifier used as a type (not really a primitive type, but whatever).
|
||||
Named(Node<Identifier>),
|
||||
}
|
||||
@ -3195,11 +3208,14 @@ pub enum PrimitiveType {
|
||||
impl PrimitiveType {
|
||||
pub fn primitive_from_str(s: &str, suffix: Option<NumericSuffix>) -> Option<Self> {
|
||||
match (s, suffix) {
|
||||
("any", None) => Some(PrimitiveType::Any),
|
||||
("string", None) => Some(PrimitiveType::String),
|
||||
("bool", None) => Some(PrimitiveType::Boolean),
|
||||
("tag", None) => Some(PrimitiveType::Tag),
|
||||
("number", None) => Some(PrimitiveType::Number(NumericSuffix::None)),
|
||||
("number", Some(s)) => Some(PrimitiveType::Number(s)),
|
||||
("ImportedGeometry", None) => Some(PrimitiveType::ImportedGeometry),
|
||||
("Fn", None) => Some(PrimitiveType::Function),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -3208,6 +3224,7 @@ impl PrimitiveType {
|
||||
impl fmt::Display for PrimitiveType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PrimitiveType::Any => write!(f, "any"),
|
||||
PrimitiveType::Number(suffix) => {
|
||||
write!(f, "number")?;
|
||||
if *suffix != NumericSuffix::None {
|
||||
@ -3218,6 +3235,8 @@ impl fmt::Display for PrimitiveType {
|
||||
PrimitiveType::String => write!(f, "string"),
|
||||
PrimitiveType::Boolean => write!(f, "bool"),
|
||||
PrimitiveType::Tag => write!(f, "tag"),
|
||||
PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
|
||||
PrimitiveType::Function => write!(f, "Fn"),
|
||||
PrimitiveType::Named(n) => write!(f, "{}", n.name),
|
||||
}
|
||||
}
|
||||
|
@ -582,6 +582,26 @@ fn binary_operator(i: &mut TokenSlice) -> PResult<BinaryOperator> {
|
||||
"<=" => BinaryOperator::Lte,
|
||||
"|" => BinaryOperator::Or,
|
||||
"&" => BinaryOperator::And,
|
||||
"||" => {
|
||||
ParseContext::err(
|
||||
CompilationError::err(
|
||||
token.as_source_range(),
|
||||
"`||` is not an operator, did you mean to use `|`?",
|
||||
)
|
||||
.with_suggestion("Replace `||` with `|`", "|", None, Tag::None),
|
||||
);
|
||||
BinaryOperator::Or
|
||||
}
|
||||
"&&" => {
|
||||
ParseContext::err(
|
||||
CompilationError::err(
|
||||
token.as_source_range(),
|
||||
"`&&` is not an operator, did you mean to use `&`?",
|
||||
)
|
||||
.with_suggestion("Replace `&&` with `&`", "&", None, Tag::None),
|
||||
);
|
||||
BinaryOperator::And
|
||||
}
|
||||
_ => {
|
||||
return Err(CompilationError::fatal(
|
||||
token.as_source_range(),
|
||||
@ -611,8 +631,7 @@ fn operand(i: &mut TokenSlice) -> PResult<BinaryPart> {
|
||||
| Expr::ArrayExpression(_)
|
||||
| Expr::ArrayRangeExpression(_)
|
||||
| Expr::ObjectExpression(_)
|
||||
| Expr::LabelledExpression(..)
|
||||
| Expr::AscribedExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)),
|
||||
| Expr::LabelledExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)),
|
||||
Expr::None(_) => {
|
||||
return Err(CompilationError::fatal(
|
||||
source_range,
|
||||
@ -638,6 +657,7 @@ fn operand(i: &mut TokenSlice) -> PResult<BinaryPart> {
|
||||
Expr::CallExpressionKw(x) => BinaryPart::CallExpressionKw(x),
|
||||
Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
|
||||
Expr::IfExpression(x) => BinaryPart::IfExpression(x),
|
||||
Expr::AscribedExpression(x) => BinaryPart::AscribedExpression(x),
|
||||
};
|
||||
Ok(expr)
|
||||
})
|
||||
@ -2048,7 +2068,7 @@ fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> PResult<Expr> {
|
||||
}
|
||||
|
||||
fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
|
||||
alt((
|
||||
let mut expr = alt((
|
||||
unary_expression.map(Box::new).map(Expr::UnaryExpression),
|
||||
bool_value.map(Expr::Literal),
|
||||
member_expression.map(Box::new).map(Expr::MemberExpression),
|
||||
@ -2061,7 +2081,14 @@ fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
|
||||
.context(expected(
|
||||
"a KCL value which can be used as an argument/operand to an operator",
|
||||
))
|
||||
.parse_next(i)
|
||||
.parse_next(i)?;
|
||||
|
||||
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?;
|
||||
if let Some((_, _, ty)) = ty {
|
||||
expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// Parse an item visibility specifier, e.g. export.
|
||||
@ -2730,9 +2757,9 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
|
||||
|
||||
/// A type of a function argument.
|
||||
/// This can be:
|
||||
/// - a primitive type, e.g. 'number' or 'string' or 'bool'
|
||||
/// - an array type, e.g. 'number[]' or 'string[]' or 'bool[]'
|
||||
/// - an object type, e.g. '{x: number, y: number}' or '{name: string, age: number}'
|
||||
/// - a primitive type, e.g. `number` or `string` or `bool`
|
||||
/// - an array type, e.g. `[number]` or `[string]` or `[bool]`
|
||||
/// - an object type, e.g. `{x: number, y: number}` or `{name: string, age: number}`
|
||||
fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
|
||||
let type_ = alt((
|
||||
// Object types
|
||||
@ -4511,6 +4538,13 @@ export fn cos(num: number(rad)): number(_) {}"#;
|
||||
assert_eq!(errs.len(), 3, "found: {errs:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_double_and() {
|
||||
let (_, errs) = assert_no_fatal("foo = true && false");
|
||||
assert_eq!(errs.len(), 1, "found: {errs:#?}");
|
||||
assert!(errs[0].message.contains("`&&`") && errs[0].message.contains("`&`") && errs[0].suggestion.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_type_ascription() {
|
||||
let (_, errs) = assert_no_fatal("a + b: number");
|
||||
|
@ -34,14 +34,12 @@ lazy_static! {
|
||||
set.insert("true", TokenType::Keyword);
|
||||
set.insert("false", TokenType::Keyword);
|
||||
set.insert("nil", TokenType::Keyword);
|
||||
// This isn't a type because brackets are used for the type.
|
||||
set.insert("array", TokenType::Keyword);
|
||||
set.insert("and", TokenType::Keyword);
|
||||
set.insert("or", TokenType::Keyword);
|
||||
set.insert("not", TokenType::Keyword);
|
||||
set.insert("var", TokenType::Keyword);
|
||||
set.insert("const", TokenType::Keyword);
|
||||
// "import" is special because of import().
|
||||
set.insert("import", TokenType::Keyword);
|
||||
set.insert("export", TokenType::Keyword);
|
||||
set.insert("type", TokenType::Keyword);
|
||||
set.insert("interface", TokenType::Keyword);
|
||||
@ -181,7 +179,7 @@ fn word(i: &mut Input<'_>) -> PResult<Token> {
|
||||
|
||||
fn operator(i: &mut Input<'_>) -> PResult<Token> {
|
||||
let (value, range) = alt((
|
||||
">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "^", "|", "&",
|
||||
">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "^", "||", "&&", "|", "&",
|
||||
))
|
||||
.with_span()
|
||||
.parse_next(i)?;
|
||||
|
@ -363,6 +363,27 @@ mod cube_with_error {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod any_type {
|
||||
const TEST_NAME: &str = "any_type";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod artifact_graph_example_code1 {
|
||||
const TEST_NAME: &str = "artifact_graph_example_code1";
|
||||
|
||||
@ -573,6 +594,48 @@ mod array_range_negative_expr {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod array_range_with_units {
|
||||
const TEST_NAME: &str = "array_range_with_units";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod array_range_mismatch_units {
|
||||
const TEST_NAME: &str = "array_range_mismatch_units";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod sketch_in_object {
|
||||
const TEST_NAME: &str = "sketch_in_object";
|
||||
|
||||
@ -1144,6 +1207,27 @@ mod array_elem_push_fail {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod array_push_item_wrong_type {
|
||||
const TEST_NAME: &str = "array_push_item_wrong_type";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod sketch_on_face {
|
||||
const TEST_NAME: &str = "sketch_on_face";
|
||||
|
||||
|
@ -8,6 +8,7 @@ use std::{
|
||||
use anyhow::Result;
|
||||
use fnv::FnvHashSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::Test;
|
||||
|
||||
@ -250,19 +251,12 @@ fn get_kcl_metadata(project_path: &Path, files: &[String]) -> Option<KclMetadata
|
||||
let title = lines[0].trim_start_matches(COMMENT_PREFIX).trim().to_string();
|
||||
let description = lines[1].trim_start_matches(COMMENT_PREFIX).trim().to_string();
|
||||
|
||||
// Get the path components
|
||||
let path_components: Vec<String> = full_path_to_primary_kcl
|
||||
.components()
|
||||
.map(|comp| comp.as_os_str().to_string_lossy().to_string())
|
||||
.collect();
|
||||
|
||||
// Get the last two path components
|
||||
let len = path_components.len();
|
||||
let path_from_project_dir = if len >= 2 {
|
||||
format!("{}/{}", path_components[len - 2], path_components[len - 1])
|
||||
} else {
|
||||
primary_kcl_file.clone()
|
||||
};
|
||||
// Get the relative path from the project directory to the primary KCL file
|
||||
let path_from_project_dir = full_path_to_primary_kcl
|
||||
.strip_prefix(INPUTS_DIR.as_path())
|
||||
.unwrap_or(&full_path_to_primary_kcl)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let mut files = files.to_vec();
|
||||
files.sort();
|
||||
@ -281,21 +275,23 @@ fn get_kcl_metadata(project_path: &Path, files: &[String]) -> Option<KclMetadata
|
||||
fn generate_kcl_manifest(dir: &Path) -> Result<()> {
|
||||
let mut manifest = Vec::new();
|
||||
|
||||
// Collect all directory entries first and sort them by name for consistent ordering
|
||||
let mut entries: Vec<_> = fs::read_dir(dir)?
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| e.path().is_dir())
|
||||
// Collect all directory entries first
|
||||
let mut entries: Vec<_> = WalkDir::new(dir)
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.collect();
|
||||
|
||||
// Sort directories by name for consistent ordering
|
||||
entries.sort_by_key(|a| a.file_name());
|
||||
entries.sort_by_key(|a| a.file_name().to_string_lossy().to_string());
|
||||
|
||||
// Loop through all directories and add to manifest if KCL sample
|
||||
for entry in entries {
|
||||
let project_path = entry.path();
|
||||
let path = entry.path();
|
||||
|
||||
if project_path.is_dir() {
|
||||
if path.is_dir() {
|
||||
// Get all .kcl files in the directory
|
||||
let files: Vec<String> = fs::read_dir(&project_path)?
|
||||
let files: Vec<String> = fs::read_dir(path)?
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| {
|
||||
if let Some(ext) = e.path().extension() {
|
||||
@ -311,7 +307,7 @@ fn generate_kcl_manifest(dir: &Path) -> Result<()> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(metadata) = get_kcl_metadata(&project_path, &files) {
|
||||
if let Some(metadata) = get_kcl_metadata(path, &files) {
|
||||
manifest.push(metadata);
|
||||
}
|
||||
}
|
||||
|
@ -408,13 +408,10 @@ impl Args {
|
||||
})?;
|
||||
|
||||
T::from_kcl_val(&arg).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
source_ranges: vec![self.source_range],
|
||||
message: format!(
|
||||
"This function expected the input argument to be {}",
|
||||
ty.human_friendly_type(),
|
||||
),
|
||||
})
|
||||
message: "Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}".to_owned(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -560,24 +557,23 @@ impl Args {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn make_user_val_from_point(&self, p: [TyF64; 2]) -> Result<KclValue, KclError> {
|
||||
pub(crate) fn make_kcl_val_from_point(&self, p: [f64; 2], ty: NumericType) -> Result<KclValue, KclError> {
|
||||
let meta = Metadata {
|
||||
source_range: self.source_range,
|
||||
};
|
||||
let x = KclValue::Number {
|
||||
value: p[0].n,
|
||||
value: p[0],
|
||||
meta: vec![meta],
|
||||
ty: p[0].ty.clone(),
|
||||
ty: ty.clone(),
|
||||
};
|
||||
let y = KclValue::Number {
|
||||
value: p[1].n,
|
||||
value: p[1],
|
||||
meta: vec![meta],
|
||||
ty: p[1].ty.clone(),
|
||||
ty: ty.clone(),
|
||||
};
|
||||
Ok(KclValue::MixedArray {
|
||||
value: vec![x, y],
|
||||
meta: vec![meta],
|
||||
})
|
||||
let ty = RuntimeType::Primitive(PrimitiveType::Number(ty));
|
||||
|
||||
Ok(KclValue::HomArray { value: vec![x, y], ty })
|
||||
}
|
||||
|
||||
pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
|
||||
@ -799,7 +795,7 @@ impl<'a> FromKclValue<'a> for Vec<TagIdentifier> {
|
||||
let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
|
||||
Some(tags)
|
||||
}
|
||||
KclValue::MixedArray { value, .. } => {
|
||||
KclValue::Tuple { value, .. } => {
|
||||
let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
|
||||
Some(tags)
|
||||
}
|
||||
@ -1139,8 +1135,11 @@ impl_from_kcl_for_vec!(TyF64);
|
||||
|
||||
impl<'a> FromKclValue<'a> for SourceRange {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let KclValue::MixedArray { value, meta: _ } = arg else {
|
||||
return None;
|
||||
let value = match arg {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if value.len() != 3 {
|
||||
return None;
|
||||
@ -1337,7 +1336,7 @@ impl<'a> FromKclValue<'a> for TyF64 {
|
||||
impl<'a> FromKclValue<'a> for [TyF64; 2] {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
match arg {
|
||||
KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
|
||||
KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
|
||||
if value.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
@ -1354,7 +1353,7 @@ impl<'a> FromKclValue<'a> for [TyF64; 2] {
|
||||
impl<'a> FromKclValue<'a> for [TyF64; 3] {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
match arg {
|
||||
KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
|
||||
KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
|
||||
if value.len() != 3 {
|
||||
return None;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use indexmap::IndexMap;
|
||||
use kcl_derive_docs::stdlib;
|
||||
|
||||
use super::{
|
||||
args::{Arg, KwArgs},
|
||||
@ -9,6 +8,7 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
kcl_value::{FunctionSource, KclValue},
|
||||
types::RuntimeType,
|
||||
ExecState,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
@ -19,51 +19,13 @@ use crate::{
|
||||
pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
|
||||
let f: &FunctionSource = args.get_kw_arg("f")?;
|
||||
let meta = vec![args.source_range.into()];
|
||||
let new_array = inner_map(array, f, exec_state, &args).await?;
|
||||
Ok(KclValue::MixedArray { value: new_array, meta })
|
||||
Ok(KclValue::HomArray {
|
||||
value: new_array,
|
||||
ty: RuntimeType::any(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply a function to every element of a list.
|
||||
///
|
||||
/// Given a list like `[a, b, c]`, and a function like `f`, returns
|
||||
/// `[f(a), f(b), f(c)]`
|
||||
/// ```no_run
|
||||
/// r = 10 // radius
|
||||
/// fn drawCircle(@id) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> circle( center= [id * 2 * r, 0], radius= r)
|
||||
/// }
|
||||
///
|
||||
/// // Call `drawCircle`, passing in each element of the array.
|
||||
/// // The outputs from each `drawCircle` form a new array,
|
||||
/// // which is the return value from `map`.
|
||||
/// circles = map(
|
||||
/// [1..3],
|
||||
/// f = drawCircle
|
||||
/// )
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// r = 10 // radius
|
||||
/// // Call `map`, using an anonymous function instead of a named one.
|
||||
/// circles = map(
|
||||
/// [1..3],
|
||||
/// f = fn(@id) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> circle( center= [id * 2 * r, 0], radius= r)
|
||||
/// }
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "map",
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
array = { docs = "Input array. The output array is this input array, but every element has had the function `f` run on it." },
|
||||
f = { docs = "A function. The output array is just the input array, but `f` has been run on every item." },
|
||||
},
|
||||
tags = ["array"]
|
||||
}]
|
||||
async fn inner_map<'a>(
|
||||
array: Vec<KclValue>,
|
||||
f: &'a FunctionSource,
|
||||
@ -115,96 +77,6 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
inner_reduce(array, initial, f, exec_state, &args).await
|
||||
}
|
||||
|
||||
/// Take a starting value. Then, for each element of an array, calculate the next value,
|
||||
/// using the previous value and the element.
|
||||
/// ```no_run
|
||||
/// // This function adds two numbers.
|
||||
/// fn add(@a, accum) { return a + accum }
|
||||
///
|
||||
/// // This function adds an array of numbers.
|
||||
/// // It uses the `reduce` function, to call the `add` function on every
|
||||
/// // element of the `arr` parameter. The starting value is 0.
|
||||
/// fn sum(@arr) { return reduce(arr, initial = 0, f = add) }
|
||||
///
|
||||
/// /*
|
||||
/// The above is basically like this pseudo-code:
|
||||
/// fn sum(arr):
|
||||
/// sumSoFar = 0
|
||||
/// for i in arr:
|
||||
/// sumSoFar = add(i, sumSoFar)
|
||||
/// return sumSoFar
|
||||
/// */
|
||||
///
|
||||
/// // We use `assert` to check that our `sum` function gives the
|
||||
/// // expected result. It's good to check your work!
|
||||
/// assert(sum([1, 2, 3]), isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// // This example works just like the previous example above, but it uses
|
||||
/// // an anonymous `add` function as its parameter, instead of declaring a
|
||||
/// // named function outside.
|
||||
/// arr = [1, 2, 3]
|
||||
/// sum = reduce(arr, initial = 0, f = fn (@i, accum) { return i + accum })
|
||||
///
|
||||
/// // We use `assert` to check that our `sum` function gives the
|
||||
/// // expected result. It's good to check your work!
|
||||
/// assert(sum, isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// // Declare a function that sketches a decagon.
|
||||
/// fn decagon(@radius) {
|
||||
/// // Each side of the decagon is turned this many radians from the previous angle.
|
||||
/// stepAngle = ((1/10) * TAU): number(rad)
|
||||
///
|
||||
/// // Start the decagon sketch at this point.
|
||||
/// startOfDecagonSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [(cos(0)*radius), (sin(0) * radius)])
|
||||
///
|
||||
/// // Use a `reduce` to draw the remaining decagon sides.
|
||||
/// // For each number in the array 1..10, run the given function,
|
||||
/// // which takes a partially-sketched decagon and adds one more edge to it.
|
||||
/// fullDecagon = reduce([1..10], initial = startOfDecagonSketch, f = fn(@i, accum) {
|
||||
/// // Draw one edge of the decagon.
|
||||
/// x = cos(stepAngle * i) * radius
|
||||
/// y = sin(stepAngle * i) * radius
|
||||
/// return line(accum, end = [x, y])
|
||||
/// })
|
||||
///
|
||||
/// return fullDecagon
|
||||
///
|
||||
/// }
|
||||
///
|
||||
/// /*
|
||||
/// The `decagon` above is basically like this pseudo-code:
|
||||
/// fn decagon(radius):
|
||||
/// stepAngle = ((1/10) * TAU): number(rad)
|
||||
/// plane = startSketchOn(XY)
|
||||
/// startOfDecagonSketch = startProfile(plane, at = [(cos(0)*radius), (sin(0) * radius)])
|
||||
///
|
||||
/// // Here's the reduce part.
|
||||
/// partialDecagon = startOfDecagonSketch
|
||||
/// for i in [1..10]:
|
||||
/// x = cos(stepAngle * i) * radius
|
||||
/// y = sin(stepAngle * i) * radius
|
||||
/// partialDecagon = line(partialDecagon, end = [x, y])
|
||||
/// fullDecagon = partialDecagon // it's now full
|
||||
/// return fullDecagon
|
||||
/// */
|
||||
///
|
||||
/// // Use the `decagon` function declared above, to sketch a decagon with radius 5.
|
||||
/// decagon(5.0) |> close()
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "reduce",
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
array = { docs = "Each element of this array gets run through the function `f`, combined with the previous output from `f`, and then used for the next run." },
|
||||
initial = { docs = "The first time `f` is run, it will be called with the first item of `array` and this initial starting value."},
|
||||
f = { docs = "Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`." },
|
||||
},
|
||||
tags = ["array"]
|
||||
}]
|
||||
async fn inner_reduce<'a>(
|
||||
array: Vec<KclValue>,
|
||||
initial: KclValue,
|
||||
@ -257,70 +129,52 @@ async fn call_reduce_closure(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Append an element to the end of an array.
|
||||
///
|
||||
/// Returns a new array with the element appended.
|
||||
///
|
||||
/// ```no_run
|
||||
/// arr = [1, 2, 3]
|
||||
/// new_arr = push(arr, item = 4)
|
||||
/// assert(new_arr[3], isEqualTo = 4, tolerance = 0.1, error = "4 was added to the end of the array")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "push",
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
array = { docs = "The array which you're adding a new item to." },
|
||||
item = { docs = "The new item to add to the array" },
|
||||
},
|
||||
tags = ["array"]
|
||||
}]
|
||||
async fn inner_push(mut array: Vec<KclValue>, item: KclValue, args: &Args) -> Result<KclValue, KclError> {
|
||||
array.push(item);
|
||||
Ok(KclValue::MixedArray {
|
||||
value: array,
|
||||
meta: vec![args.source_range.into()],
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
// Extract the array and the element from the arguments
|
||||
let val: KclValue = args.get_unlabeled_kw_arg("array")?;
|
||||
let item = args.get_kw_arg("item")?;
|
||||
let array = args.get_unlabeled_kw_arg("array")?;
|
||||
let item: KclValue = args.get_kw_arg("item")?;
|
||||
|
||||
let meta = vec![args.source_range];
|
||||
let KclValue::MixedArray { value: array, meta: _ } = val else {
|
||||
let actual_type = val.human_friendly_type();
|
||||
let KclValue::HomArray { value: values, ty } = array else {
|
||||
let meta = vec![args.source_range];
|
||||
let actual_type = array.human_friendly_type();
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: meta,
|
||||
message: format!("You can't push to a value of type {actual_type}, only an array"),
|
||||
}));
|
||||
};
|
||||
inner_push(array, item, &args).await
|
||||
let ty = if item.has_type(&ty) {
|
||||
ty
|
||||
} else {
|
||||
// The user pushed an item with a type that differs from the array's
|
||||
// element type.
|
||||
RuntimeType::any()
|
||||
};
|
||||
|
||||
let new_array = inner_push(values, item);
|
||||
|
||||
Ok(KclValue::HomArray { value: new_array, ty })
|
||||
}
|
||||
|
||||
/// Remove the last element from an array.
|
||||
///
|
||||
/// Returns a new array with the last element removed.
|
||||
///
|
||||
/// ```no_run
|
||||
/// arr = [1, 2, 3, 4]
|
||||
/// new_arr = pop(arr)
|
||||
/// assert(new_arr[0], isEqualTo = 1, tolerance = 0.00001, error = "1 is the first element of the array")
|
||||
/// assert(new_arr[1], isEqualTo = 2, tolerance = 0.00001, error = "2 is the second element of the array")
|
||||
/// assert(new_arr[2], isEqualTo = 3, tolerance = 0.00001, error = "3 is the third element of the array")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "pop",
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
array = { docs = "The array to pop from. Must not be empty."},
|
||||
},
|
||||
tags = ["array"]
|
||||
}]
|
||||
async fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<KclValue, KclError> {
|
||||
fn inner_push(mut array: Vec<KclValue>, item: KclValue) -> Vec<KclValue> {
|
||||
array.push(item);
|
||||
array
|
||||
}
|
||||
|
||||
pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let array = args.get_unlabeled_kw_arg("array")?;
|
||||
let KclValue::HomArray { value: values, ty } = array else {
|
||||
let meta = vec![args.source_range];
|
||||
let actual_type = array.human_friendly_type();
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: meta,
|
||||
message: format!("You can't pop from a value of type {actual_type}, only an array"),
|
||||
}));
|
||||
};
|
||||
|
||||
let new_array = inner_pop(values, &args)?;
|
||||
Ok(KclValue::HomArray { value: new_array, ty })
|
||||
}
|
||||
|
||||
fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, KclError> {
|
||||
if array.is_empty() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Cannot pop from an empty array".to_string(),
|
||||
@ -331,24 +185,5 @@ async fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<KclValue, KclErr
|
||||
// Create a new array with all elements except the last one
|
||||
let new_array = array[..array.len() - 1].to_vec();
|
||||
|
||||
Ok(KclValue::MixedArray {
|
||||
value: new_array,
|
||||
meta: vec![args.source_range.into()],
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
// Extract the array from the arguments
|
||||
let val = args.get_unlabeled_kw_arg("array")?;
|
||||
|
||||
let meta = vec![args.source_range];
|
||||
let KclValue::MixedArray { value: array, meta: _ } = val else {
|
||||
let actual_type = val.human_friendly_type();
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: meta,
|
||||
message: format!("You can't pop from a value of type {actual_type}, only an array"),
|
||||
}));
|
||||
};
|
||||
|
||||
inner_pop(array, &args).await
|
||||
Ok(new_array)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
ok_response::{output::EntityGetAllChildUuids, OkModelingCmdResponse},
|
||||
@ -41,240 +40,6 @@ pub async fn clone(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
Ok(cloned.into())
|
||||
}
|
||||
|
||||
/// Clone a sketch or solid.
|
||||
///
|
||||
/// This works essentially like a copy-paste operation. It creates a perfect replica
|
||||
/// at that point in time that you can manipulate individually afterwards.
|
||||
///
|
||||
/// This doesn't really have much utility unless you need the equivalent of a double
|
||||
/// instance pattern with zero transformations.
|
||||
///
|
||||
/// Really only use this function if YOU ARE SURE you need it. In most cases you
|
||||
/// do not need clone and using a pattern with `instance = 2` is more appropriate.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Clone a basic sketch and move it and extrude it.
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// clonedSketch = clone(exampleSketch)
|
||||
/// |> scale(
|
||||
/// x = 1.0,
|
||||
/// y = 1.0,
|
||||
/// z = 2.5,
|
||||
/// )
|
||||
/// |> translate(
|
||||
/// x = 15.0,
|
||||
/// y = 0,
|
||||
/// z = 0,
|
||||
/// )
|
||||
/// |> extrude(length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Clone a basic solid and move it.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// myPart = extrude(exampleSketch, length = 5)
|
||||
/// clonedPart = clone(myPart)
|
||||
/// |> translate(
|
||||
/// x = 25.0,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Translate and rotate a cloned sketch to create a loft.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-10, 10])
|
||||
/// |> xLine(length = 20)
|
||||
/// |> yLine(length = -20)
|
||||
/// |> xLine(length = -20)
|
||||
/// |> close()
|
||||
///
|
||||
/// sketch002 = clone(sketch001)
|
||||
/// |> translate(x = 0, y = 0, z = 20)
|
||||
/// |> rotate(axis = [0, 0, 1.0], angle = 45)
|
||||
///
|
||||
/// loft([sketch001, sketch002])
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Translate a cloned solid. Fillet only the clone.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-10, 10])
|
||||
/// |> xLine(length = 20)
|
||||
/// |> yLine(length = -20)
|
||||
/// |> xLine(length = -20, tag = $filletTag)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
///
|
||||
/// sketch002 = clone(sketch001)
|
||||
/// |> translate(x = 0, y = 0, z = 20)
|
||||
/// |> fillet(
|
||||
/// radius = 2,
|
||||
/// tags = [getNextAdjacentEdge(filletTag)],
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // You can reuse the tags from the original geometry with the cloned geometry.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10], tag = $sketchingFace)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// sketch002 = clone(sketch001)
|
||||
/// |> translate(x = 10, y = 20, z = 0)
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
/// startSketchOn(sketch002, face = sketchingFace)
|
||||
/// |> startProfile(at = [1, 1])
|
||||
/// |> line(end = [8, 0])
|
||||
/// |> line(end = [0, 8])
|
||||
/// |> line(end = [-8, 0])
|
||||
/// |> close(tag = $sketchingFace002)
|
||||
/// |> extrude(length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // You can also use the tags from the original geometry to fillet the cloned geometry.
|
||||
///
|
||||
/// width = 20
|
||||
/// length = 10
|
||||
/// thickness = 1
|
||||
/// filletRadius = 2
|
||||
///
|
||||
/// mountingPlateSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-width/2, -length/2])
|
||||
/// |> line(endAbsolute = [width/2, -length/2], tag = $edge1)
|
||||
/// |> line(endAbsolute = [width/2, length/2], tag = $edge2)
|
||||
/// |> line(endAbsolute = [-width/2, length/2], tag = $edge3)
|
||||
/// |> close(tag = $edge4)
|
||||
///
|
||||
/// mountingPlate = extrude(mountingPlateSketch, length = thickness)
|
||||
///
|
||||
/// clonedMountingPlate = clone(mountingPlate)
|
||||
/// |> fillet(
|
||||
/// radius = filletRadius,
|
||||
/// tags = [
|
||||
/// getNextAdjacentEdge(edge1),
|
||||
/// getNextAdjacentEdge(edge2),
|
||||
/// getNextAdjacentEdge(edge3),
|
||||
/// getNextAdjacentEdge(edge4)
|
||||
/// ],
|
||||
/// )
|
||||
/// |> translate(x = 0, y = 50, z = 0)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a spring by sweeping around a helix path from a cloned sketch.
|
||||
///
|
||||
/// // Create a helix around the Z axis.
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 4,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = Z,
|
||||
/// )
|
||||
///
|
||||
///
|
||||
/// springSketch = startSketchOn(YZ)
|
||||
/// |> circle( center = [0, 0], radius = 1)
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// sweepedSpring = clone(springSketch)
|
||||
/// |> translate(x=100)
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // A donut shape from a cloned sketch.
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> circle( center = [15, 0], radius = 5 )
|
||||
///
|
||||
/// sketch002 = clone(sketch001)
|
||||
/// |> translate( z = 30)
|
||||
/// |> revolve(
|
||||
/// angle = 360,
|
||||
/// axis = Y,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Sketch on the end of a revolved face by tagging the end face.
|
||||
/// // This shows the cloned geometry will have the same tags as the original geometry.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [4, 12])
|
||||
/// |> line(end = [2, 0])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [4, -6])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [-3.75, -4.5])
|
||||
/// |> line(end = [0, -5.5])
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example001 = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01)
|
||||
///
|
||||
/// // example002 = clone(example001)
|
||||
/// // |> translate(x = 0, y = 20, z = 0)
|
||||
///
|
||||
/// // Sketch on the cloned face.
|
||||
/// // exampleSketch002 = startSketchOn(example002, face = end01)
|
||||
/// // |> startProfile(at = [4.5, -5])
|
||||
/// // |> line(end = [0, 5])
|
||||
/// // |> line(end = [5, 0])
|
||||
/// // |> line(end = [0, -5])
|
||||
/// // |> close()
|
||||
///
|
||||
/// // example003 = extrude(exampleSketch002, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Clone an imported model.
|
||||
///
|
||||
/// import "tests/inputs/cube.sldprt" as cube
|
||||
///
|
||||
/// myCube = cube
|
||||
///
|
||||
/// clonedCube = clone(myCube)
|
||||
/// |> translate(
|
||||
/// x = 1020,
|
||||
/// )
|
||||
/// |> appearance(
|
||||
/// color = "#ff0000",
|
||||
/// metalness = 50,
|
||||
/// roughness = 50
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "clone",
|
||||
feature_tree_operation = true,
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
geometry = { docs = "The sketch, solid, or imported geometry to be cloned" },
|
||||
}
|
||||
}]
|
||||
async fn inner_clone(
|
||||
geometry: GeometryWithImportedGeometry,
|
||||
exec_state: &mut ExecState,
|
||||
|
@ -14,7 +14,7 @@ use crate::{
|
||||
|
||||
/// Get the opposite edge to the edge given.
|
||||
pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
|
||||
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
|
||||
let edge = inner_get_opposite_edge(input_edge, exec_state, args.clone()).await?;
|
||||
Ok(KclValue::Uuid {
|
||||
@ -98,7 +98,7 @@ async fn inner_get_opposite_edge(
|
||||
|
||||
/// Get the next adjacent edge to the edge given.
|
||||
pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
|
||||
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
|
||||
let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
|
||||
Ok(KclValue::Uuid {
|
||||
@ -191,7 +191,7 @@ async fn inner_get_next_adjacent_edge(
|
||||
|
||||
/// Get the previous adjacent edge to the edge given.
|
||||
pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
|
||||
let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
|
||||
let edge = inner_get_previous_adjacent_edge(input_edge, exec_state, args.clone()).await?;
|
||||
Ok(KclValue::Uuid {
|
||||
|
@ -152,8 +152,7 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
args = {
|
||||
sketches = { docs = "Which sketch or sketches should be extruded"},
|
||||
length = { docs = "How far to extrude the given sketches"},
|
||||
symmetric = { docs = "If true, the extrusion will happen symmetrically around the sketch. Otherwise, the
|
||||
extrusion will happen on only one side of the sketch." },
|
||||
symmetric = { docs = "If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch." },
|
||||
bidirectional_length = { docs = "If specified, will also extrude in the opposite direction to 'distance' to the specified distance. If 'symmetric' is true, this value is ignored."},
|
||||
tag_start = { docs = "A named tag for the face at the start of the extrusion, i.e. the original sketch" },
|
||||
tag_end = { docs = "A named tag for the face at the end of the extrusion, i.e. the new face created by extruding the original sketch" },
|
||||
|
@ -3,7 +3,7 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
types::{ArrayLen, NumericType, RuntimeType},
|
||||
ExecState, KclValue,
|
||||
@ -54,6 +54,17 @@ pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
/// Compute the square root of a number.
|
||||
pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
|
||||
|
||||
if input.n < 0.0 {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: vec![args.source_range],
|
||||
message: format!(
|
||||
"Attempt to take square root (`sqrt`) of a number less than zero ({})",
|
||||
input.n
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
let result = input.n.sqrt();
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
|
||||
@ -220,3 +231,38 @@ pub async fn ln(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclE
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
|
||||
}
|
||||
|
||||
/// Compute the length of the given leg.
|
||||
pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
|
||||
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
|
||||
let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
|
||||
let result = (hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt();
|
||||
Ok(KclValue::from_number_with_type(result, ty, vec![args.into()]))
|
||||
}
|
||||
|
||||
/// Compute the angle of the given leg for x.
|
||||
pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
|
||||
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
|
||||
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
|
||||
let result = (leg.min(hypotenuse) / hypotenuse).acos().to_degrees();
|
||||
Ok(KclValue::from_number_with_type(
|
||||
result,
|
||||
NumericType::degrees(),
|
||||
vec![args.into()],
|
||||
))
|
||||
}
|
||||
|
||||
/// Compute the angle of the given leg for y.
|
||||
pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
|
||||
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
|
||||
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
|
||||
let result = (leg.min(hypotenuse) / hypotenuse).asin().to_degrees();
|
||||
Ok(KclValue::from_number_with_type(
|
||||
result,
|
||||
NumericType::degrees(),
|
||||
vec![args.into()],
|
||||
))
|
||||
}
|
||||
|
@ -28,21 +28,13 @@ pub mod utils;
|
||||
|
||||
use anyhow::Result;
|
||||
pub use args::Args;
|
||||
use args::TyF64;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use lazy_static::lazy_static;
|
||||
use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::KclError,
|
||||
execution::{
|
||||
types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitType},
|
||||
ExecState, KclValue,
|
||||
},
|
||||
execution::{types::PrimitiveType, ExecState, KclValue},
|
||||
parsing::ast::types::Name,
|
||||
};
|
||||
|
||||
@ -53,9 +45,6 @@ pub type StdFn = fn(
|
||||
|
||||
lazy_static! {
|
||||
static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
|
||||
Box::new(LegLen),
|
||||
Box::new(LegAngX),
|
||||
Box::new(LegAngY),
|
||||
Box::new(crate::std::appearance::Appearance),
|
||||
Box::new(crate::std::extrude::Extrude),
|
||||
Box::new(crate::std::segment::SegEnd),
|
||||
@ -87,17 +76,12 @@ lazy_static! {
|
||||
Box::new(crate::std::sketch::TangentialArc),
|
||||
Box::new(crate::std::sketch::BezierCurve),
|
||||
Box::new(crate::std::sketch::Subtract2D),
|
||||
Box::new(crate::std::clone::Clone),
|
||||
Box::new(crate::std::patterns::PatternLinear2D),
|
||||
Box::new(crate::std::patterns::PatternLinear3D),
|
||||
Box::new(crate::std::patterns::PatternCircular2D),
|
||||
Box::new(crate::std::patterns::PatternCircular3D),
|
||||
Box::new(crate::std::patterns::PatternTransform),
|
||||
Box::new(crate::std::patterns::PatternTransform2D),
|
||||
Box::new(crate::std::array::Reduce),
|
||||
Box::new(crate::std::array::Map),
|
||||
Box::new(crate::std::array::Push),
|
||||
Box::new(crate::std::array::Pop),
|
||||
Box::new(crate::std::edge::GetOppositeEdge),
|
||||
Box::new(crate::std::edge::GetNextAdjacentEdge),
|
||||
Box::new(crate::std::edge::GetPreviousAdjacentEdge),
|
||||
@ -149,84 +133,96 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
match (path, fn_name) {
|
||||
("math", "cos") => (
|
||||
|e, a| Box::pin(crate::std::math::cos(e, a)),
|
||||
StdFnProps::default("std::cos"),
|
||||
StdFnProps::default("std::math::cos"),
|
||||
),
|
||||
("math", "sin") => (
|
||||
|e, a| Box::pin(crate::std::math::sin(e, a)),
|
||||
StdFnProps::default("std::sin"),
|
||||
StdFnProps::default("std::math::sin"),
|
||||
),
|
||||
("math", "tan") => (
|
||||
|e, a| Box::pin(crate::std::math::tan(e, a)),
|
||||
StdFnProps::default("std::tan"),
|
||||
StdFnProps::default("std::math::tan"),
|
||||
),
|
||||
("math", "acos") => (
|
||||
|e, a| Box::pin(crate::std::math::acos(e, a)),
|
||||
StdFnProps::default("std::acos"),
|
||||
StdFnProps::default("std::math::acos"),
|
||||
),
|
||||
("math", "asin") => (
|
||||
|e, a| Box::pin(crate::std::math::asin(e, a)),
|
||||
StdFnProps::default("std::asin"),
|
||||
StdFnProps::default("std::math::asin"),
|
||||
),
|
||||
("math", "atan") => (
|
||||
|e, a| Box::pin(crate::std::math::atan(e, a)),
|
||||
StdFnProps::default("std::atan"),
|
||||
StdFnProps::default("std::math::atan"),
|
||||
),
|
||||
("math", "atan2") => (
|
||||
|e, a| Box::pin(crate::std::math::atan2(e, a)),
|
||||
StdFnProps::default("std::atan2"),
|
||||
StdFnProps::default("std::math::atan2"),
|
||||
),
|
||||
("math", "sqrt") => (
|
||||
|e, a| Box::pin(crate::std::math::sqrt(e, a)),
|
||||
StdFnProps::default("std::sqrt"),
|
||||
StdFnProps::default("std::math::sqrt"),
|
||||
),
|
||||
|
||||
("math", "abs") => (
|
||||
|e, a| Box::pin(crate::std::math::abs(e, a)),
|
||||
StdFnProps::default("std::abs"),
|
||||
StdFnProps::default("std::math::abs"),
|
||||
),
|
||||
("math", "rem") => (
|
||||
|e, a| Box::pin(crate::std::math::rem(e, a)),
|
||||
StdFnProps::default("std::rem"),
|
||||
StdFnProps::default("std::math::rem"),
|
||||
),
|
||||
("math", "round") => (
|
||||
|e, a| Box::pin(crate::std::math::round(e, a)),
|
||||
StdFnProps::default("std::round"),
|
||||
StdFnProps::default("std::math::round"),
|
||||
),
|
||||
("math", "floor") => (
|
||||
|e, a| Box::pin(crate::std::math::floor(e, a)),
|
||||
StdFnProps::default("std::floor"),
|
||||
StdFnProps::default("std::math::floor"),
|
||||
),
|
||||
("math", "ceil") => (
|
||||
|e, a| Box::pin(crate::std::math::ceil(e, a)),
|
||||
StdFnProps::default("std::ceil"),
|
||||
StdFnProps::default("std::math::ceil"),
|
||||
),
|
||||
("math", "min") => (
|
||||
|e, a| Box::pin(crate::std::math::min(e, a)),
|
||||
StdFnProps::default("std::min"),
|
||||
StdFnProps::default("std::math::min"),
|
||||
),
|
||||
("math", "max") => (
|
||||
|e, a| Box::pin(crate::std::math::max(e, a)),
|
||||
StdFnProps::default("std::max"),
|
||||
StdFnProps::default("std::math::max"),
|
||||
),
|
||||
("math", "pow") => (
|
||||
|e, a| Box::pin(crate::std::math::pow(e, a)),
|
||||
StdFnProps::default("std::pow"),
|
||||
StdFnProps::default("std::math::pow"),
|
||||
),
|
||||
("math", "log") => (
|
||||
|e, a| Box::pin(crate::std::math::log(e, a)),
|
||||
StdFnProps::default("std::log"),
|
||||
StdFnProps::default("std::math::log"),
|
||||
),
|
||||
("math", "log2") => (
|
||||
|e, a| Box::pin(crate::std::math::log2(e, a)),
|
||||
StdFnProps::default("std::log2"),
|
||||
StdFnProps::default("std::math::log2"),
|
||||
),
|
||||
("math", "log10") => (
|
||||
|e, a| Box::pin(crate::std::math::log10(e, a)),
|
||||
StdFnProps::default("std::log10"),
|
||||
StdFnProps::default("std::math::log10"),
|
||||
),
|
||||
("math", "ln") => (
|
||||
|e, a| Box::pin(crate::std::math::ln(e, a)),
|
||||
StdFnProps::default("std::ln"),
|
||||
StdFnProps::default("std::math::ln"),
|
||||
),
|
||||
("math", "legLen") => (
|
||||
|e, a| Box::pin(crate::std::math::leg_length(e, a)),
|
||||
StdFnProps::default("std::math::legLen"),
|
||||
),
|
||||
("math", "legAngX") => (
|
||||
|e, a| Box::pin(crate::std::math::leg_angle_x(e, a)),
|
||||
StdFnProps::default("std::math::legAngX"),
|
||||
),
|
||||
("math", "legAngY") => (
|
||||
|e, a| Box::pin(crate::std::math::leg_angle_y(e, a)),
|
||||
StdFnProps::default("std::math::legAngY"),
|
||||
),
|
||||
("sketch", "circle") => (
|
||||
|e, a| Box::pin(crate::std::shapes::circle(e, a)),
|
||||
@ -264,6 +260,26 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
|e, a| Box::pin(crate::std::shell::hollow(e, a)),
|
||||
StdFnProps::default("std::solid::hollow").include_in_feature_tree(),
|
||||
),
|
||||
("array", "map") => (
|
||||
|e, a| Box::pin(crate::std::array::map(e, a)),
|
||||
StdFnProps::default("std::array::map"),
|
||||
),
|
||||
("array", "reduce") => (
|
||||
|e, a| Box::pin(crate::std::array::reduce(e, a)),
|
||||
StdFnProps::default("std::array::reduce"),
|
||||
),
|
||||
("array", "push") => (
|
||||
|e, a| Box::pin(crate::std::array::push(e, a)),
|
||||
StdFnProps::default("std::array::push"),
|
||||
),
|
||||
("array", "pop") => (
|
||||
|e, a| Box::pin(crate::std::array::pop(e, a)),
|
||||
StdFnProps::default("std::array::pop"),
|
||||
),
|
||||
("prelude", "clone") => (
|
||||
|e, a| Box::pin(crate::std::clone::clone(e, a)),
|
||||
StdFnProps::default("std::clone").include_in_feature_tree(),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -341,110 +357,3 @@ pub enum FunctionKind {
|
||||
|
||||
/// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`].
|
||||
const DEFAULT_TOLERANCE: f64 = 0.0000001;
|
||||
|
||||
/// Compute the length of the given leg.
|
||||
pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
|
||||
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
|
||||
let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
|
||||
let result = inner_leg_length(hypotenuse, leg);
|
||||
Ok(KclValue::from_number_with_type(result, ty, vec![args.into()]))
|
||||
}
|
||||
|
||||
/// Compute the length of the given leg.
|
||||
///
|
||||
/// ```kcl,no_run
|
||||
/// legLen(hypotenuse = 5, leg = 3)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "legLen",
|
||||
keywords = true,
|
||||
unlabeled_first = false,
|
||||
args = {
|
||||
hypotenuse = { docs = "The length of the triangle's hypotenuse" },
|
||||
leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
|
||||
},
|
||||
tags = ["math"],
|
||||
}]
|
||||
fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 {
|
||||
(hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt()
|
||||
}
|
||||
|
||||
/// Compute the angle of the given leg for x.
|
||||
pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
|
||||
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
|
||||
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
|
||||
let result = inner_leg_angle_x(hypotenuse, leg);
|
||||
Ok(KclValue::from_number_with_type(
|
||||
result,
|
||||
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
|
||||
vec![args.into()],
|
||||
))
|
||||
}
|
||||
|
||||
/// Compute the angle of the given leg for x.
|
||||
///
|
||||
/// ```kcl,no_run
|
||||
/// legAngX(hypotenuse = 5, leg = 3)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "legAngX",
|
||||
keywords = true,
|
||||
unlabeled_first = false,
|
||||
args = {
|
||||
hypotenuse = { docs = "The length of the triangle's hypotenuse" },
|
||||
leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
|
||||
},
|
||||
tags = ["math"],
|
||||
}]
|
||||
fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 {
|
||||
(leg.min(hypotenuse) / hypotenuse).acos().to_degrees()
|
||||
}
|
||||
|
||||
/// Compute the angle of the given leg for y.
|
||||
pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
|
||||
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
|
||||
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
|
||||
let result = inner_leg_angle_y(hypotenuse, leg);
|
||||
Ok(KclValue::from_number_with_type(
|
||||
result,
|
||||
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
|
||||
vec![args.into()],
|
||||
))
|
||||
}
|
||||
|
||||
/// Compute the angle of the given leg for y.
|
||||
///
|
||||
/// ```kcl,no_run
|
||||
/// legAngY(hypotenuse = 5, leg = 3)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "legAngY",
|
||||
keywords = true,
|
||||
unlabeled_first = false,
|
||||
args = {
|
||||
hypotenuse = { docs = "The length of the triangle's hypotenuse" },
|
||||
leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
|
||||
},
|
||||
tags = ["math"],
|
||||
}]
|
||||
fn inner_leg_angle_y(hypotenuse: f64, leg: f64) -> f64 {
|
||||
(leg.min(hypotenuse) / hypotenuse).asin().to_degrees()
|
||||
}
|
||||
|
||||
/// The primitive types that can be used in a KCL file.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[display(style = "lowercase")]
|
||||
pub enum Primitive {
|
||||
/// A boolean value.
|
||||
Bool,
|
||||
/// A number value.
|
||||
Number,
|
||||
/// A string value.
|
||||
String,
|
||||
/// A uuid value.
|
||||
Uuid,
|
||||
}
|
||||
|
@ -452,7 +452,7 @@ async fn make_transform<T: GeometryTrait>(
|
||||
})?;
|
||||
let transforms = match transform_fn_return {
|
||||
KclValue::Object { value, meta: _ } => vec![value],
|
||||
KclValue::MixedArray { value, meta: _ } => {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
let transforms: Vec<_> = value
|
||||
.into_iter()
|
||||
.map(|val| {
|
||||
@ -671,12 +671,44 @@ impl GeometryTrait for Solid {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::execution::types::NumericType;
|
||||
use crate::execution::types::{NumericType, PrimitiveType};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_array_to_point3d() {
|
||||
let mut exec_state = ExecState::new(&ExecutorContext::new_mock().await);
|
||||
let input = KclValue::MixedArray {
|
||||
let input = KclValue::HomArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 1.1,
|
||||
meta: Default::default(),
|
||||
ty: NumericType::mm(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 2.2,
|
||||
meta: Default::default(),
|
||||
ty: NumericType::mm(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 3.3,
|
||||
meta: Default::default(),
|
||||
ty: NumericType::mm(),
|
||||
},
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
|
||||
};
|
||||
let expected = [
|
||||
TyF64::new(1.1, NumericType::mm()),
|
||||
TyF64::new(2.2, NumericType::mm()),
|
||||
TyF64::new(3.3, NumericType::mm()),
|
||||
];
|
||||
let actual = array_to_point3d(&input, Vec::new(), &mut exec_state);
|
||||
assert_eq!(actual.unwrap(), expected);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_tuple_to_point3d() {
|
||||
let mut exec_state = ExecState::new(&ExecutorContext::new_mock().await);
|
||||
let input = KclValue::Tuple {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 1.1,
|
||||
|
@ -17,9 +17,9 @@ use crate::{
|
||||
/// Returns the point at the end of the given segment.
|
||||
pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let result = inner_segment_end(&tag, exec_state, args.clone())?;
|
||||
let pt = inner_segment_end(&tag, exec_state, args.clone())?;
|
||||
|
||||
args.make_user_val_from_point(result)
|
||||
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
|
||||
}
|
||||
|
||||
/// Compute the ending point of the provided line segment.
|
||||
@ -64,8 +64,11 @@ fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
let (p, ty) = path.end_point_components();
|
||||
// Docs generation isn't smart enough to handle ([f64; 2], NumericType).
|
||||
let point = [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)];
|
||||
|
||||
Ok(path.get_to().clone())
|
||||
Ok(point)
|
||||
}
|
||||
|
||||
/// Returns the segment end of x.
|
||||
@ -156,9 +159,9 @@ fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
/// Returns the point at the start of the given segment.
|
||||
pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let result = inner_segment_start(&tag, exec_state, args.clone())?;
|
||||
let pt = inner_segment_start(&tag, exec_state, args.clone())?;
|
||||
|
||||
args.make_user_val_from_point(result)
|
||||
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
|
||||
}
|
||||
|
||||
/// Compute the starting point of the provided line segment.
|
||||
@ -203,8 +206,11 @@ fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
source_ranges: vec![args.source_range],
|
||||
})
|
||||
})?;
|
||||
let (p, ty) = path.start_point_components();
|
||||
// Docs generation isn't smart enough to handle ([f64; 2], NumericType).
|
||||
let point = [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)];
|
||||
|
||||
Ok(path.get_from().to_owned())
|
||||
Ok(point)
|
||||
}
|
||||
|
||||
/// Returns the segment start of x.
|
||||
|
@ -2,11 +2,11 @@ use std::fmt::Write;
|
||||
|
||||
use crate::parsing::{
|
||||
ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
|
||||
CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression, IfExpression,
|
||||
ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue,
|
||||
MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter, PipeExpression,
|
||||
Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind,
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
||||
BinaryPart, BodyItem, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression,
|
||||
IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
|
||||
PipeExpression, Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind,
|
||||
},
|
||||
deprecation, DeprecationKind, PIPE_OPERATOR,
|
||||
};
|
||||
@ -308,18 +308,7 @@ impl Expr {
|
||||
result += &e.label.name;
|
||||
result
|
||||
}
|
||||
Expr::AscribedExpression(e) => {
|
||||
let mut result = e.expr.recast(options, indentation_level, ctxt);
|
||||
if matches!(
|
||||
e.expr,
|
||||
Expr::BinaryExpression(..) | Expr::PipeExpression(..) | Expr::UnaryExpression(..)
|
||||
) {
|
||||
result = format!("({result})");
|
||||
}
|
||||
result += ": ";
|
||||
result += &e.ty.to_string();
|
||||
result
|
||||
}
|
||||
Expr::AscribedExpression(e) => e.recast(options, indentation_level, ctxt),
|
||||
Expr::None(_) => {
|
||||
unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
|
||||
}
|
||||
@ -327,6 +316,21 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
|
||||
impl AscribedExpression {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
|
||||
let mut result = self.expr.recast(options, indentation_level, ctxt);
|
||||
if matches!(
|
||||
self.expr,
|
||||
Expr::BinaryExpression(..) | Expr::PipeExpression(..) | Expr::UnaryExpression(..)
|
||||
) {
|
||||
result = format!("({result})");
|
||||
}
|
||||
result += ": ";
|
||||
result += &self.ty.to_string();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryPart {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||
match &self {
|
||||
@ -345,6 +349,7 @@ impl BinaryPart {
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.recast(),
|
||||
BinaryPart::IfExpression(e) => e.recast(options, indentation_level, ExprContext::Other),
|
||||
BinaryPart::AscribedExpression(e) => e.recast(options, indentation_level, ExprContext::Other),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -722,6 +727,7 @@ impl UnaryExpression {
|
||||
| BinaryPart::Name(_)
|
||||
| BinaryPart::MemberExpression(_)
|
||||
| BinaryPart::IfExpression(_)
|
||||
| BinaryPart::AscribedExpression(_)
|
||||
| BinaryPart::CallExpressionKw(_) => {
|
||||
format!("{}{}", &self.operator, self.argument.recast(options, 0))
|
||||
}
|
||||
|
@ -221,6 +221,7 @@ impl<'tree> From<&'tree types::BinaryPart> for Node<'tree> {
|
||||
types::BinaryPart::UnaryExpression(ue) => ue.as_ref().into(),
|
||||
types::BinaryPart::MemberExpression(me) => me.as_ref().into(),
|
||||
types::BinaryPart::IfExpression(e) => e.as_ref().into(),
|
||||
types::BinaryPart::AscribedExpression(e) => e.as_ref().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,3 +2,169 @@
|
||||
|
||||
@no_std
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
/// Apply a function to every element of a list.
|
||||
///
|
||||
/// Given a list like `[a, b, c]`, and a function like `f`, returns
|
||||
/// `[f(a), f(b), f(c)]`
|
||||
///
|
||||
/// ```kcl
|
||||
/// r = 10 // radius
|
||||
/// fn drawCircle(@id) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> circle( center= [id * 2 * r, 0], radius= r)
|
||||
/// }
|
||||
///
|
||||
/// // Call `drawCircle`, passing in each element of the array.
|
||||
/// // The outputs from each `drawCircle` form a new array,
|
||||
/// // which is the return value from `map`.
|
||||
/// circles = map(
|
||||
/// [1..3],
|
||||
/// f = drawCircle
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// r = 10 // radius
|
||||
/// // Call `map`, using an anonymous function instead of a named one.
|
||||
/// circles = map(
|
||||
/// [1..3],
|
||||
/// f = fn(@id) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> circle( center= [id * 2 * r, 0], radius= r)
|
||||
/// }
|
||||
/// )
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn map(
|
||||
/// Input array. The output array is this input array, but every element has had the function `f` run on it.
|
||||
@array: [any],
|
||||
/// A function. The output array is just the input array, but `f` has been run on every item.
|
||||
f: Fn,
|
||||
): [any] {}
|
||||
|
||||
/// Take a starting value. Then, for each element of an array, calculate the next value,
|
||||
/// using the previous value and the element.
|
||||
///
|
||||
/// ```kcl
|
||||
/// // This function adds two numbers.
|
||||
/// fn add(@a, accum) { return a + accum }
|
||||
///
|
||||
/// // This function adds an array of numbers.
|
||||
/// // It uses the `reduce` function, to call the `add` function on every
|
||||
/// // element of the `arr` parameter. The starting value is 0.
|
||||
/// fn sum(@arr) { return reduce(arr, initial = 0, f = add) }
|
||||
///
|
||||
/// /*
|
||||
/// The above is basically like this pseudo-code:
|
||||
/// fn sum(arr):
|
||||
/// sumSoFar = 0
|
||||
/// for i in arr:
|
||||
/// sumSoFar = add(i, sumSoFar)
|
||||
/// return sumSoFar
|
||||
/// */
|
||||
///
|
||||
/// // We use `assert` to check that our `sum` function gives the
|
||||
/// // expected result. It's good to check your work!
|
||||
/// assert(sum([1, 2, 3]), isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // This example works just like the previous example above, but it uses
|
||||
/// // an anonymous `add` function as its parameter, instead of declaring a
|
||||
/// // named function outside.
|
||||
/// arr = [1, 2, 3]
|
||||
/// sum = reduce(arr, initial = 0, f = fn (@i, accum) { return i + accum })
|
||||
///
|
||||
/// // We use `assert` to check that our `sum` function gives the
|
||||
/// // expected result. It's good to check your work!
|
||||
/// assert(sum, isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // Declare a function that sketches a decagon.
|
||||
/// fn decagon(@radius) {
|
||||
/// // Each side of the decagon is turned this many radians from the previous angle.
|
||||
/// stepAngle = ((1/10) * TAU): number(rad)
|
||||
///
|
||||
/// // Start the decagon sketch at this point.
|
||||
/// startOfDecagonSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [(cos(0)*radius), (sin(0) * radius)])
|
||||
///
|
||||
/// // Use a `reduce` to draw the remaining decagon sides.
|
||||
/// // For each number in the array 1..10, run the given function,
|
||||
/// // which takes a partially-sketched decagon and adds one more edge to it.
|
||||
/// fullDecagon = reduce([1..10], initial = startOfDecagonSketch, f = fn(@i, accum) {
|
||||
/// // Draw one edge of the decagon.
|
||||
/// x = cos(stepAngle * i) * radius
|
||||
/// y = sin(stepAngle * i) * radius
|
||||
/// return line(accum, end = [x, y])
|
||||
/// })
|
||||
///
|
||||
/// return fullDecagon
|
||||
///
|
||||
/// }
|
||||
///
|
||||
/// /*
|
||||
/// The `decagon` above is basically like this pseudo-code:
|
||||
/// fn decagon(radius):
|
||||
/// stepAngle = ((1/10) * TAU): number(rad)
|
||||
/// plane = startSketchOn(XY)
|
||||
/// startOfDecagonSketch = startProfile(plane, at = [(cos(0)*radius), (sin(0) * radius)])
|
||||
///
|
||||
/// // Here's the reduce part.
|
||||
/// partialDecagon = startOfDecagonSketch
|
||||
/// for i in [1..10]:
|
||||
/// x = cos(stepAngle * i) * radius
|
||||
/// y = sin(stepAngle * i) * radius
|
||||
/// partialDecagon = line(partialDecagon, end = [x, y])
|
||||
/// fullDecagon = partialDecagon // it's now full
|
||||
/// return fullDecagon
|
||||
/// */
|
||||
///
|
||||
/// // Use the `decagon` function declared above, to sketch a decagon with radius 5.
|
||||
/// decagon(5.0) |> close()
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn reduce(
|
||||
/// Each element of this array gets run through the function `f`, combined with the previous output from `f`, and then used for the next run.
|
||||
@array: [any],
|
||||
/// The first time `f` is run, it will be called with the first item of `array` and this initial starting value.
|
||||
initial: any,
|
||||
/// Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`.
|
||||
f: Fn,
|
||||
): [any] {}
|
||||
|
||||
/// Append an element to the end of an array.
|
||||
///
|
||||
/// Returns a new array with the element appended.
|
||||
///
|
||||
/// ```kcl
|
||||
/// arr = [1, 2, 3]
|
||||
/// new_arr = push(arr, item = 4)
|
||||
/// assert(new_arr[3], isEqualTo = 4, tolerance = 0.1, error = "4 was added to the end of the array")
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn push(
|
||||
/// The array which you're adding a new item to.
|
||||
@array: [any],
|
||||
/// The new item to add to the array
|
||||
item: any,
|
||||
): [any; 1+] {}
|
||||
|
||||
/// Remove the last element from an array.
|
||||
///
|
||||
/// Returns a new array with the last element removed.
|
||||
///
|
||||
/// ```kcl
|
||||
/// arr = [1, 2, 3, 4]
|
||||
/// new_arr = pop(arr)
|
||||
/// assert(new_arr[0], isEqualTo = 1, tolerance = 0.00001, error = "1 is the first element of the array")
|
||||
/// assert(new_arr[1], isEqualTo = 2, tolerance = 0.00001, error = "2 is the second element of the array")
|
||||
/// assert(new_arr[2], isEqualTo = 3, tolerance = 0.00001, error = "3 is the third element of the array")
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn pop(
|
||||
/// The array to pop from. Must not be empty.
|
||||
@array: [any; 1+],
|
||||
): [any] {}
|
||||
|
@ -7,6 +7,14 @@ import Point2d from "std::types"
|
||||
|
||||
/// The value of `pi`, Archimedes’ constant (π).
|
||||
///
|
||||
/// `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.
|
||||
///
|
||||
/// ```
|
||||
/// circumference = 70
|
||||
///
|
||||
@ -428,3 +436,42 @@ export fn log10(@input: number): number {}
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn ln(@input: number): number {}
|
||||
|
||||
/// Compute the length of the given leg.
|
||||
///
|
||||
/// ```kcl,no_run
|
||||
/// legLen(hypotenuse = 5, leg = 3)
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn legLen(
|
||||
/// The length of the triangle's hypotenuse.
|
||||
hypotenuse: number(Length),
|
||||
/// The length of one of the triangle's legs (i.e. non-hypotenuse side).
|
||||
leg: number(Length),
|
||||
): number(deg) {}
|
||||
|
||||
/// Compute the angle of the given leg for x.
|
||||
///
|
||||
/// ```kcl,no_run
|
||||
/// legAngX(hypotenuse = 5, leg = 3)
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn legAngX(
|
||||
/// The length of the triangle's hypotenuse.
|
||||
hypotenuse: number(Length),
|
||||
/// The length of one of the triangle's legs (i.e. non-hypotenuse side).
|
||||
leg: number(Length),
|
||||
): number(deg) {}
|
||||
|
||||
/// Compute the angle of the given leg for y.
|
||||
///
|
||||
/// ```kcl,no_run
|
||||
/// legAngY(hypotenuse = 5, leg = 3)
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn legAngY(
|
||||
/// The length of the triangle's hypotenuse.
|
||||
hypotenuse: number(Length),
|
||||
/// The length of one of the triangle's legs (i.e. non-hypotenuse side).
|
||||
leg: number(Length),
|
||||
): number(deg) {}
|
||||
|
@ -5,6 +5,8 @@
|
||||
///
|
||||
/// 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]().
|
||||
|
||||
@no_std
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
@ -249,3 +251,234 @@ export fn offsetPlane(
|
||||
/// Distance from the standard plane this new plane will be created at.
|
||||
offset: number(Length),
|
||||
): Plane {}
|
||||
|
||||
/// Clone a sketch or solid.
|
||||
///
|
||||
/// This works essentially like a copy-paste operation. It creates a perfect replica
|
||||
/// at that point in time that you can manipulate individually afterwards.
|
||||
///
|
||||
/// This doesn't really have much utility unless you need the equivalent of a double
|
||||
/// instance pattern with zero transformations.
|
||||
///
|
||||
/// Really only use this function if YOU ARE SURE you need it. In most cases you
|
||||
/// do not need clone and using a pattern with `instance = 2` is more appropriate.
|
||||
///
|
||||
/// ```kcl
|
||||
/// // Clone a basic sketch and move it and extrude it.
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// clonedSketch = clone(exampleSketch)
|
||||
/// |> scale(
|
||||
/// x = 1.0,
|
||||
/// y = 1.0,
|
||||
/// z = 2.5,
|
||||
/// )
|
||||
/// |> translate(
|
||||
/// x = 15.0,
|
||||
/// y = 0,
|
||||
/// z = 0,
|
||||
/// )
|
||||
/// |> extrude(length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // Clone a basic solid and move it.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// myPart = extrude(exampleSketch, length = 5)
|
||||
/// clonedPart = clone(myPart)
|
||||
/// |> translate(
|
||||
/// x = 25.0,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // Translate and rotate a cloned sketch to create a loft.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-10, 10])
|
||||
/// |> xLine(length = 20)
|
||||
/// |> yLine(length = -20)
|
||||
/// |> xLine(length = -20)
|
||||
/// |> close()
|
||||
///
|
||||
/// sketch002 = clone(sketch001)
|
||||
/// |> translate(x = 0, y = 0, z = 20)
|
||||
/// |> rotate(axis = [0, 0, 1.0], angle = 45)
|
||||
///
|
||||
/// loft([sketch001, sketch002])
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // Translate a cloned solid. Fillet only the clone.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-10, 10])
|
||||
/// |> xLine(length = 20)
|
||||
/// |> yLine(length = -20)
|
||||
/// |> xLine(length = -20, tag = $filletTag)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
///
|
||||
/// sketch002 = clone(sketch001)
|
||||
/// |> translate(x = 0, y = 0, z = 20)
|
||||
/// |> fillet(
|
||||
/// radius = 2,
|
||||
/// tags = [getNextAdjacentEdge(filletTag)],
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // You can reuse the tags from the original geometry with the cloned geometry.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10], tag = $sketchingFace)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// sketch002 = clone(sketch001)
|
||||
/// |> translate(x = 10, y = 20, z = 0)
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
/// startSketchOn(sketch002, face = sketchingFace)
|
||||
/// |> startProfile(at = [1, 1])
|
||||
/// |> line(end = [8, 0])
|
||||
/// |> line(end = [0, 8])
|
||||
/// |> line(end = [-8, 0])
|
||||
/// |> close(tag = $sketchingFace002)
|
||||
/// |> extrude(length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // You can also use the tags from the original geometry to fillet the cloned geometry.
|
||||
///
|
||||
/// width = 20
|
||||
/// length = 10
|
||||
/// thickness = 1
|
||||
/// filletRadius = 2
|
||||
///
|
||||
/// mountingPlateSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-width/2, -length/2])
|
||||
/// |> line(endAbsolute = [width/2, -length/2], tag = $edge1)
|
||||
/// |> line(endAbsolute = [width/2, length/2], tag = $edge2)
|
||||
/// |> line(endAbsolute = [-width/2, length/2], tag = $edge3)
|
||||
/// |> close(tag = $edge4)
|
||||
///
|
||||
/// mountingPlate = extrude(mountingPlateSketch, length = thickness)
|
||||
///
|
||||
/// clonedMountingPlate = clone(mountingPlate)
|
||||
/// |> fillet(
|
||||
/// radius = filletRadius,
|
||||
/// tags = [
|
||||
/// getNextAdjacentEdge(edge1),
|
||||
/// getNextAdjacentEdge(edge2),
|
||||
/// getNextAdjacentEdge(edge3),
|
||||
/// getNextAdjacentEdge(edge4)
|
||||
/// ],
|
||||
/// )
|
||||
/// |> translate(x = 0, y = 50, z = 0)
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // Create a spring by sweeping around a helix path from a cloned sketch.
|
||||
///
|
||||
/// // Create a helix around the Z axis.
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 4,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = Z,
|
||||
/// )
|
||||
///
|
||||
///
|
||||
/// springSketch = startSketchOn(YZ)
|
||||
/// |> circle( center = [0, 0], radius = 1)
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// sweepedSpring = clone(springSketch)
|
||||
/// |> translate(x=100)
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // A donut shape from a cloned sketch.
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> circle( center = [15, 0], radius = 5 )
|
||||
///
|
||||
/// sketch002 = clone(sketch001)
|
||||
/// |> translate( z = 30)
|
||||
/// |> revolve(
|
||||
/// angle = 360,
|
||||
/// axis = Y,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // Sketch on the end of a revolved face by tagging the end face.
|
||||
/// // This shows the cloned geometry will have the same tags as the original geometry.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [4, 12])
|
||||
/// |> line(end = [2, 0])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [4, -6])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [-3.75, -4.5])
|
||||
/// |> line(end = [0, -5.5])
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example001 = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01)
|
||||
///
|
||||
/// // example002 = clone(example001)
|
||||
/// // |> translate(x = 0, y = 20, z = 0)
|
||||
///
|
||||
/// // Sketch on the cloned face.
|
||||
/// // exampleSketch002 = startSketchOn(example002, face = end01)
|
||||
/// // |> startProfile(at = [4.5, -5])
|
||||
/// // |> line(end = [0, 5])
|
||||
/// // |> line(end = [5, 0])
|
||||
/// // |> line(end = [0, -5])
|
||||
/// // |> close()
|
||||
///
|
||||
/// // example003 = extrude(exampleSketch002, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```kcl
|
||||
/// // Clone an imported model.
|
||||
///
|
||||
/// import "tests/inputs/cube.sldprt" as cube
|
||||
///
|
||||
/// myCube = cube
|
||||
///
|
||||
/// clonedCube = clone(myCube)
|
||||
/// |> translate(
|
||||
/// x = 1020,
|
||||
/// )
|
||||
/// |> appearance(
|
||||
/// color = "#ff0000",
|
||||
/// metalness = 50,
|
||||
/// roughness = 50
|
||||
/// )
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn clone(
|
||||
/// The sketch, solid, or imported geometry to be cloned.
|
||||
@geometry: Sketch | Solid | ImportedGeometry,
|
||||
): Sketch | Solid | ImportedGeometry {}
|
||||
|
@ -163,10 +163,35 @@ export type string
|
||||
@(impl = primitive)
|
||||
export type tag
|
||||
|
||||
/// Represents geometry which is defined using some other CAD system and imported into KCL.
|
||||
@(impl = primitive)
|
||||
export type ImportedGeometry
|
||||
|
||||
/// The type of any function in KCL.
|
||||
@(impl = primitive)
|
||||
export type Fn
|
||||
|
||||
/// An abstract plane.
|
||||
///
|
||||
/// A plane has a position and orientation in space defined by its origin and axes. A plane can be used
|
||||
/// to sketch on.
|
||||
/// A plane has a position and orientation in space defined by its origin and axes. A plane is abstract
|
||||
/// in the sense that it is not part of the objects being drawn. A plane can be used 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.,
|
||||
///
|
||||
/// ```kcl,inline,norun
|
||||
/// 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.
|
||||
@(impl = std_rust)
|
||||
export type Plane
|
||||
|
||||
|
@ -11,32 +11,32 @@
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
/// Convert a number to millimeters from its current units.
|
||||
export fn toMillimeters(@num: number(mm)): number(mm) {
|
||||
export fn toMillimeters(@num: number(Length)): number(mm) {
|
||||
return num
|
||||
}
|
||||
|
||||
/// Convert a number to centimeters from its current units.
|
||||
export fn toCentimeters(@num: number(cm)): number(cm) {
|
||||
export fn toCentimeters(@num: number(Length)): number(cm) {
|
||||
return num
|
||||
}
|
||||
|
||||
/// Convert a number to meters from its current units.
|
||||
export fn toMeters(@num: number(m)): number(m) {
|
||||
export fn toMeters(@num: number(Length)): number(m) {
|
||||
return num
|
||||
}
|
||||
|
||||
/// Convert a number to inches from its current units.
|
||||
export fn toInches(@num: number(in)): number(in) {
|
||||
export fn toInches(@num: number(Length)): number(in) {
|
||||
return num
|
||||
}
|
||||
|
||||
/// Convert a number to feet from its current units.
|
||||
export fn toFeet(@num: number(ft)): number(ft) {
|
||||
export fn toFeet(@num: number(Length)): number(ft) {
|
||||
return num
|
||||
}
|
||||
|
||||
/// Converts a number to yards from its current units.
|
||||
export fn toYards(@num: number(yd)): number(yd) {
|
||||
export fn toYards(@num: number(Length)): number(yd) {
|
||||
return num
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ export fn toYards(@num: number(yd)): number(yd) {
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
export fn toRadians(@num: number(rad)): number(rad) {
|
||||
export fn toRadians(@num: number(Angle)): number(rad) {
|
||||
return num
|
||||
}
|
||||
|
||||
@ -72,6 +72,6 @@ export fn toRadians(@num: number(rad)): number(rad) {
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
export fn toDegrees(@num: number(deg)): number(deg) {
|
||||
export fn toDegrees(@num: number(Angle)): number(deg) {
|
||||
return num
|
||||
}
|
||||
|
32
rust/kcl-lib/tests/any_type/artifact_commands.snap
Normal file
32
rust/kcl-lib/tests/any_type/artifact_commands.snap
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact commands any_type.kcl
|
||||
---
|
||||
[
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "edge_lines_visible",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "object_visible",
|
||||
"object_id": "[uuid]",
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "object_visible",
|
||||
"object_id": "[uuid]",
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart any_type.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,3 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user