Compare commits
22 Commits
pierremtb/
...
delete-net
Author | SHA1 | Date | |
---|---|---|---|
dac3c0c224 | |||
99cb6a6179 | |||
13dbfdfaa4 | |||
8603f5c53a | |||
d5cc9e8386 | |||
533e17466b | |||
01c7b69f50 | |||
47feae3bd9 | |||
e7ecd655c4 | |||
d9e538c6ea | |||
e297f8286f | |||
b3bc90bbe4 | |||
95a02cbcd7 | |||
a049768f1c | |||
818d9a0d77 | |||
1a8f80a7dc | |||
566c9eaf10 | |||
e6485c2da1 | |||
0479edd36a | |||
87c1e92134 | |||
8f950ac1b0 | |||
78e4f43708 |
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'
|
||||
|
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
@ -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/
|
||||
|
8
.github/workflows/static-analysis.yml
vendored
@ -213,7 +213,13 @@ jobs:
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: npm run playwright install chromium --with-deps
|
||||
|
||||
- name: run unit tests for kcl samples
|
||||
- name: Download internal KCL samples
|
||||
run: git clone --depth=1 https://x-access-token:${{ secrets.GH_PAT_KCL_SAMPLES_INTERNAL }}@github.com/KittyCAD/kcl-samples-internal public/kcl-samples/internal
|
||||
|
||||
- name: Regenerate KCL samples manifest
|
||||
run: cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
|
||||
|
||||
- name: Check public and internal KCL samples
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: npm run test:unit:kcl-samples
|
||||
env:
|
||||
|
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
@ -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)
|
||||
|
32
docs/kcl-lang/arrays.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: "Arrays and ranges"
|
||||
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Arrays are sequences of values.
|
||||
|
||||
Arrays can be written out as *array literals* using a sequence of expressions surrounded by square brackets, e.g., `['hello', 'world']` is an array of strings, `[x, x + 1, x + 2]` is an array of numbers (assuming `x` is a number), `[]` is an empty array, and `['hello', 42, true]` is a mixed array.
|
||||
|
||||
A value in an array can be accessed by indexing using square brackets where the index is a number, for example, `arr[0]`, `arr[42]`, `arr[i]` (where `arr` is an array and `i` is a (whole) number).
|
||||
|
||||
There are some useful functions for working with arrays in the standard library, see [std::array](/docs/kcl-std/modules/std-array) for details.
|
||||
|
||||
## Array types
|
||||
|
||||
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
|
||||
|
||||
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
|
||||
|
||||
## Ranges
|
||||
|
||||
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples:
|
||||
|
||||
```kcl,norun
|
||||
[0..3] // [0, 1, 2, 3]
|
||||
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
|
||||
x = 2
|
||||
[x..x+1] // [2, 3]
|
||||
```
|
||||
|
||||
The units of the start and end numbers must be the same and the result inherits those units.
|
@ -14,6 +14,7 @@ things in a more tutorial fashion. See also our documentation of the [standard l
|
||||
* [Values and types](/docs/kcl-lang/types)
|
||||
* [Numeric types and units](/docs/kcl-lang/numeric)
|
||||
* [Functions](/docs/kcl-lang/functions)
|
||||
* [Arrays and ranges](/docs/kcl-lang/arrays)
|
||||
* [Projects and modules](/docs/kcl-lang/modules)
|
||||
* [Attributes](/docs/kcl-lang/attributes)
|
||||
* [Importing geometry from other CAD systems](/docs/kcl-lang/foreign-imports)
|
||||
|
@ -19,18 +19,6 @@ myBool = false
|
||||
|
||||
Currently you cannot redeclare a constant.
|
||||
|
||||
## Arrays
|
||||
|
||||
An array is defined with `[]` braces. What is inside the brackets can
|
||||
be of any type. For example, the following is completely valid:
|
||||
|
||||
```
|
||||
myArray = ["thing", 2, false]
|
||||
```
|
||||
|
||||
If you want to get a value from an array you can use the index like so:
|
||||
`myArray[0]`.
|
||||
|
||||
|
||||
## Objects
|
||||
|
||||
@ -40,8 +28,8 @@ An object is defined with `{}` braces. Here is an example object:
|
||||
myObj = { a = 0, b = "thing" }
|
||||
```
|
||||
|
||||
We support two different ways of getting properties from objects, you can call
|
||||
`myObj.a` or `myObj["a"]` both work.
|
||||
To get the property of an object, you can call `myObj.a`, which in the above
|
||||
example returns 0.
|
||||
|
||||
## `ImportedGeometry`
|
||||
|
||||
|
@ -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
|
||||
|
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
@ -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
@ -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)
|
||||
```
|
||||
|
||||
|
||||
|
@ -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,12 +140,13 @@ 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)
|
||||
* [`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)
|
||||
* [`any`](/docs/kcl-std/types/std-types-any)
|
||||
* [`bool`](/docs/kcl-std/types/std-types-bool)
|
||||
* [`fn`](/docs/kcl-std/types/std-types-fn)
|
||||
* [`number`](/docs/kcl-std/types/std-types-number)
|
||||
* [`string`](/docs/kcl-std/types/std-types-string)
|
||||
* [`tag`](/docs/kcl-std/types/std-types-tag)
|
||||
|
@ -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)
|
||||
|
@ -18,6 +18,7 @@ Types can (optionally) be used to describe a function's arguments and returned v
|
||||
* [`Edge`](/docs/kcl-std/types/std-types-Edge)
|
||||
* [`Face`](/docs/kcl-std/types/std-types-Face)
|
||||
* [`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)
|
||||
@ -25,6 +26,7 @@ Types can (optionally) be used to describe a function's arguments and returned v
|
||||
* [`Solid`](/docs/kcl-std/types/std-types-Solid)
|
||||
* [`any`](/docs/kcl-std/types/std-types-any)
|
||||
* [`bool`](/docs/kcl-std/types/std-types-bool)
|
||||
* [`fn`](/docs/kcl-std/types/std-types-fn)
|
||||
* [`number`](/docs/kcl-std/types/std-types-number)
|
||||
* [`string`](/docs/kcl-std/types/std-types-string)
|
||||
* [`tag`](/docs/kcl-std/types/std-types-tag)
|
||||
|
@ -37,7 +37,7 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui
|
||||
* [`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
|
||||
|
34924
docs/kcl-std/std.json
@ -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-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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -549,48 +549,6 @@ extrude002 = extrude(profile002, length = 150)
|
||||
})
|
||||
})
|
||||
|
||||
test(
|
||||
`Network health indicator only appears in modeling view`,
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Locators
|
||||
const projectsHeading = page.getByRole('heading', {
|
||||
name: 'Projects',
|
||||
})
|
||||
const projectLink = page.getByRole('link', { name: 'bracket' })
|
||||
const networkHealthIndicator = page.getByTestId('network-toggle')
|
||||
|
||||
await test.step('Check the home page', async () => {
|
||||
await expect(projectsHeading).toBeVisible()
|
||||
await expect(projectLink).toBeVisible()
|
||||
await expect(networkHealthIndicator).not.toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Open the project', async () => {
|
||||
await projectLink.click()
|
||||
})
|
||||
|
||||
await test.step('Check the modeling view', async () => {
|
||||
await expect(networkHealthIndicator).toBeVisible()
|
||||
await expect(networkHealthIndicator).toContainText('Problem')
|
||||
await u.waitForPageLoad()
|
||||
await expect(networkHealthIndicator).toContainText('Connected')
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(`View gizmo stays visible even when zoomed out all the way`, async ({
|
||||
page,
|
||||
homePage,
|
||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
@ -905,7 +905,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Go into the project that was created from Text to CAD
|
||||
await page.getByText(projectName).click()
|
||||
await homePage.openProject(projectName)
|
||||
|
||||
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||
@ -951,7 +951,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Go into the project that was created from Text to CAD
|
||||
await page.getByText(projectName).click()
|
||||
await homePage.openProject(projectName)
|
||||
|
||||
await page.getByRole('button', { name: 'Accept' }).click()
|
||||
|
||||
|
@ -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",
|
||||
|
1
rust/Cargo.lock
generated
@ -1959,6 +1959,7 @@ dependencies = [
|
||||
"url",
|
||||
"uuid",
|
||||
"validator",
|
||||
"walkdir",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
|
@ -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 }
|
||||
|
@ -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"),
|
||||
@ -674,6 +674,8 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
|
||||
|
||||
if fmt_for_text && ty.starts_with("number") {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)")
|
||||
} else if fmt_for_text && ty.starts_with("fn") {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-fn)")
|
||||
} else if fmt_for_text && SPECIAL_TYPES.contains(&ty) {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})")
|
||||
} else if fmt_for_text && DECLARED_TYPES.contains(&ty) {
|
||||
|
@ -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")]
|
||||
@ -534,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" {
|
||||
@ -702,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() {
|
||||
@ -1008,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]
|
||||
@ -1130,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})"#);
|
||||
}
|
||||
|
||||
|
@ -182,6 +182,8 @@ impl RuntimeType {
|
||||
}
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
||||
@ -363,6 +365,7 @@ pub enum PrimitiveType {
|
||||
Axis2d,
|
||||
Axis3d,
|
||||
ImportedGeometry,
|
||||
Function,
|
||||
}
|
||||
|
||||
impl PrimitiveType {
|
||||
@ -382,6 +385,7 @@ 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(),
|
||||
}
|
||||
@ -418,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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1184,6 +1189,10 @@ 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()),
|
||||
@ -1372,12 +1381,10 @@ impl KclValue {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use sha2::{Digest as DigestTrait, Sha256};
|
||||
|
||||
use crate::parsing::ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryPart, BodyItem,
|
||||
CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, Identifier, IfExpression,
|
||||
ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Literal,
|
||||
CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, FunctionType, Identifier,
|
||||
IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Literal,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, ObjectExpression, ObjectProperty, Parameter,
|
||||
PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, TagDeclarator, Type, TypeDeclaration,
|
||||
UnaryExpression, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
@ -232,12 +232,29 @@ impl PrimitiveType {
|
||||
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(f) => hasher.update(f.compute_digest()),
|
||||
}
|
||||
|
||||
hasher.finalize().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionType {
|
||||
compute_digest!(|slf, hasher| {
|
||||
if let Some(u) = &mut slf.unnamed_arg {
|
||||
hasher.update(u.compute_digest());
|
||||
}
|
||||
slf.named_args.iter_mut().for_each(|(a, t)| {
|
||||
a.compute_digest();
|
||||
t.compute_digest();
|
||||
});
|
||||
if let Some(r) = &mut slf.return_type {
|
||||
hasher.update(r.compute_digest());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl Parameter {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.identifier.compute_digest());
|
||||
|
@ -3197,6 +3197,10 @@ pub enum PrimitiveType {
|
||||
Boolean,
|
||||
/// A tag.
|
||||
Tag,
|
||||
/// Imported from other CAD system.
|
||||
ImportedGeometry,
|
||||
/// `fn`, type of functions.
|
||||
Function(FunctionType),
|
||||
/// An identifier used as a type (not really a primitive type, but whatever).
|
||||
Named(Node<Identifier>),
|
||||
}
|
||||
@ -3210,6 +3214,7 @@ impl PrimitiveType {
|
||||
("tag", None) => Some(PrimitiveType::Tag),
|
||||
("number", None) => Some(PrimitiveType::Number(NumericSuffix::None)),
|
||||
("number", Some(s)) => Some(PrimitiveType::Number(s)),
|
||||
("ImportedGeometry", None) => Some(PrimitiveType::ImportedGeometry),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -3229,11 +3234,58 @@ 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(t) => {
|
||||
write!(f, "fn")?;
|
||||
if t.unnamed_arg.is_some() || !t.named_args.is_empty() || t.return_type.is_some() {
|
||||
write!(f, "(")?;
|
||||
if let Some(u) = &t.unnamed_arg {
|
||||
write!(f, "{u}")?;
|
||||
if !t.named_args.is_empty() {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
for (i, (a, t)) in t.named_args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}: {t}", a.name)?;
|
||||
}
|
||||
write!(f, ")")?;
|
||||
if let Some(r) = &t.return_type {
|
||||
write!(f, ": {r}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
PrimitiveType::Named(n) => write!(f, "{}", n.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct FunctionType {
|
||||
pub unnamed_arg: Option<BoxNode<Type>>,
|
||||
pub named_args: Vec<(Node<Identifier>, Node<Type>)>,
|
||||
pub return_type: Option<BoxNode<Type>>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
impl FunctionType {
|
||||
pub fn empty_fn_type() -> Self {
|
||||
FunctionType {
|
||||
unnamed_arg: None,
|
||||
named_args: Vec::new(),
|
||||
return_type: None,
|
||||
digest: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
|
@ -25,11 +25,11 @@ use crate::{
|
||||
ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
|
||||
BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
|
||||
FunctionExpression, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility,
|
||||
LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeList,
|
||||
NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression,
|
||||
PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type, TypeDeclaration,
|
||||
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
FunctionExpression, FunctionType, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement,
|
||||
ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name,
|
||||
Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter,
|
||||
PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type,
|
||||
TypeDeclaration, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
},
|
||||
math::BinaryExpressionToken,
|
||||
token::{Token, TokenSlice, TokenType},
|
||||
@ -1261,7 +1261,7 @@ fn function_decl(i: &mut TokenSlice) -> PResult<Node<FunctionExpression>> {
|
||||
fn return_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
|
||||
colon(i)?;
|
||||
ignore_whitespace(i);
|
||||
argument_type(i)
|
||||
type_(i)
|
||||
}
|
||||
|
||||
let open = open_paren(i)?;
|
||||
@ -2013,7 +2013,7 @@ fn expression_but_not_pipe(i: &mut TokenSlice) -> PResult<Expr> {
|
||||
.context(expected("a KCL value"))
|
||||
.parse_next(i)?;
|
||||
|
||||
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?;
|
||||
let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
|
||||
if let Some((_, _, ty)) = ty {
|
||||
expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
|
||||
}
|
||||
@ -2083,7 +2083,7 @@ fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
|
||||
))
|
||||
.parse_next(i)?;
|
||||
|
||||
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?;
|
||||
let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
|
||||
if let Some((_, _, ty)) = ty {
|
||||
expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
|
||||
}
|
||||
@ -2233,7 +2233,21 @@ fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> {
|
||||
let start = visibility_token.map(|t| t.start).unwrap_or_else(|| decl_token.start);
|
||||
whitespace(i)?;
|
||||
|
||||
let name = identifier(i)?;
|
||||
let name = alt((
|
||||
fun.map(|t| {
|
||||
Node::new(
|
||||
Identifier {
|
||||
name: "fn".to_owned(),
|
||||
digest: None,
|
||||
},
|
||||
t.start,
|
||||
t.end,
|
||||
t.module_id,
|
||||
)
|
||||
}),
|
||||
identifier,
|
||||
))
|
||||
.parse_next(i)?;
|
||||
let mut end = name.end;
|
||||
|
||||
let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() {
|
||||
@ -2253,7 +2267,7 @@ fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> {
|
||||
ignore_whitespace(i);
|
||||
equals(i)?;
|
||||
ignore_whitespace(i);
|
||||
let ty = argument_type(i)?;
|
||||
let ty = type_(i)?;
|
||||
|
||||
ParseContext::warn(CompilationError::err(
|
||||
ty.as_source_range(),
|
||||
@ -2755,12 +2769,8 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// 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}`
|
||||
fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
|
||||
/// Parse a type in various positions.
|
||||
fn type_(i: &mut TokenSlice) -> PResult<Node<Type>> {
|
||||
let type_ = alt((
|
||||
// Object types
|
||||
// TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`.
|
||||
@ -2800,14 +2810,69 @@ fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
|
||||
}
|
||||
|
||||
fn primitive_type(i: &mut TokenSlice) -> PResult<Node<PrimitiveType>> {
|
||||
let ident = identifier(i)?;
|
||||
alt((
|
||||
// A function type: `fn` (`(` type?, (id: type,)* `)` (`:` type)?)?
|
||||
(
|
||||
fun,
|
||||
opt((
|
||||
// `(` type?, (id: type,)* `)`
|
||||
delimited(
|
||||
open_paren,
|
||||
opt(alt((
|
||||
// type, (id: type,)+
|
||||
(
|
||||
type_,
|
||||
comma,
|
||||
opt(whitespace),
|
||||
separated(
|
||||
1..,
|
||||
(identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
|
||||
comma_sep,
|
||||
),
|
||||
)
|
||||
.map(|(t, _, _, args)| (Some(t), args)),
|
||||
// (id: type,)+
|
||||
separated(
|
||||
1..,
|
||||
(identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
|
||||
comma_sep,
|
||||
)
|
||||
.map(|args| (None, args)),
|
||||
// type
|
||||
type_.map(|t| (Some(t), Vec::new())),
|
||||
))),
|
||||
close_paren,
|
||||
),
|
||||
// `:` type
|
||||
opt((colon, opt(whitespace), type_)),
|
||||
)),
|
||||
)
|
||||
.map(|(t, tys)| {
|
||||
let mut ft = FunctionType::empty_fn_type();
|
||||
|
||||
let suffix = opt(delimited(open_paren, uom_for_type, close_paren)).parse_next(i)?;
|
||||
if let Some((args, ret)) = tys {
|
||||
if let Some((unnamed, named)) = args {
|
||||
if let Some(unnamed) = unnamed {
|
||||
ft.unnamed_arg = Some(Box::new(unnamed));
|
||||
}
|
||||
ft.named_args = named;
|
||||
}
|
||||
if let Some((_, _, ty)) = ret {
|
||||
ft.return_type = Some(Box::new(ty));
|
||||
}
|
||||
}
|
||||
|
||||
Node::new(PrimitiveType::Function(ft), t.start, t.end, t.module_id)
|
||||
}),
|
||||
// A named type, possibly with a numeric suffix.
|
||||
(identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
|
||||
let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
|
||||
result.inner = PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
|
||||
|
||||
Ok(result)
|
||||
result.inner =
|
||||
PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
|
||||
result
|
||||
}),
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
|
||||
@ -2817,7 +2882,7 @@ fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
|
||||
}
|
||||
|
||||
open_bracket(i)?;
|
||||
let ty = argument_type(i)?;
|
||||
let ty = type_(i)?;
|
||||
let len = opt((
|
||||
semi_colon,
|
||||
opt_whitespace,
|
||||
@ -2905,7 +2970,7 @@ fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> {
|
||||
any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
|
||||
opt(question_mark),
|
||||
opt(whitespace),
|
||||
opt((colon, opt(whitespace), argument_type).map(|tup| tup.2)),
|
||||
opt((colon, opt(whitespace), type_).map(|tup| tup.2)),
|
||||
opt(whitespace),
|
||||
opt((equals, opt(whitespace), literal).map(|(_, _, literal)| literal)),
|
||||
)
|
||||
@ -4777,6 +4842,20 @@ let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
|
||||
assert_no_err(some_program_string);
|
||||
}
|
||||
#[test]
|
||||
fn parse_function_types() {
|
||||
let code = r#"foo = x: fn
|
||||
foo = x: fn(number)
|
||||
fn foo(x: fn(): number): fn { return 0 }
|
||||
fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { return 0 }
|
||||
type fn
|
||||
type foo = fn
|
||||
type foo = fn(a: string, b: { f: fn(): any })
|
||||
type foo = fn([fn])
|
||||
type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
|
||||
"#;
|
||||
assert_no_err(code);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_tag_starting_with_bang() {
|
||||
let some_program_string = r#"startSketchOn(XY)
|
||||
|> startProfile(at = [0, 0])
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use indexmap::IndexMap;
|
||||
use kcl_derive_docs::stdlib;
|
||||
|
||||
use super::{
|
||||
args::{Arg, KwArgs},
|
||||
@ -27,46 +26,6 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
})
|
||||
}
|
||||
|
||||
/// 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,
|
||||
@ -118,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,
|
||||
@ -285,28 +154,8 @@ pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
Ok(KclValue::HomArray { value: new_array, ty })
|
||||
}
|
||||
|
||||
/// 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"]
|
||||
}]
|
||||
fn inner_push(mut array: Vec<KclValue>, item: KclValue) -> Vec<KclValue> {
|
||||
array.push(item);
|
||||
|
||||
array
|
||||
}
|
||||
|
||||
@ -322,30 +171,9 @@ pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
};
|
||||
|
||||
let new_array = inner_pop(values, &args)?;
|
||||
|
||||
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"]
|
||||
}]
|
||||
fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, KclError> {
|
||||
if array.is_empty() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
|
@ -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,
|
||||
|
@ -231,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,
|
||||
}
|
||||
|
@ -278,6 +278,7 @@ async fn inner_line(
|
||||
end_absolute,
|
||||
end,
|
||||
tag,
|
||||
relative_name: "end",
|
||||
},
|
||||
exec_state,
|
||||
args,
|
||||
@ -290,6 +291,7 @@ struct StraightLineParams {
|
||||
end_absolute: Option<[TyF64; 2]>,
|
||||
end: Option<[TyF64; 2]>,
|
||||
tag: Option<TagNode>,
|
||||
relative_name: &'static str,
|
||||
}
|
||||
|
||||
impl StraightLineParams {
|
||||
@ -299,6 +301,7 @@ impl StraightLineParams {
|
||||
tag,
|
||||
end: Some(p),
|
||||
end_absolute: None,
|
||||
relative_name: "end",
|
||||
}
|
||||
}
|
||||
fn absolute(p: [TyF64; 2], sketch: Sketch, tag: Option<TagNode>) -> Self {
|
||||
@ -307,6 +310,7 @@ impl StraightLineParams {
|
||||
tag,
|
||||
end: None,
|
||||
end_absolute: Some(p),
|
||||
relative_name: "end",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -317,6 +321,7 @@ async fn straight_line(
|
||||
end,
|
||||
end_absolute,
|
||||
tag,
|
||||
relative_name,
|
||||
}: StraightLineParams,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
@ -335,7 +340,7 @@ async fn straight_line(
|
||||
(None, None) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: vec![args.source_range],
|
||||
message: "You must supply either `end` or `endAbsolute` arguments".to_owned(),
|
||||
message: format!("You must supply either `{relative_name}` or `endAbsolute` arguments"),
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -447,6 +452,7 @@ async fn inner_x_line(
|
||||
end_absolute: end_absolute.map(|x| [x, from.into_y()]),
|
||||
end: length.map(|x| [x, TyF64::new(0.0, NumericType::mm())]),
|
||||
tag,
|
||||
relative_name: "length",
|
||||
},
|
||||
exec_state,
|
||||
args,
|
||||
@ -512,6 +518,7 @@ async fn inner_y_line(
|
||||
end_absolute: end_absolute.map(|y| [from.into_x(), y]),
|
||||
end: length.map(|y| [TyF64::new(0.0, NumericType::mm()), y]),
|
||||
tag,
|
||||
relative_name: "length",
|
||||
},
|
||||
exec_state,
|
||||
args,
|
||||
|
@ -2575,6 +2575,27 @@ sketch002 = startSketchOn({
|
||||
assert_eq!(actual, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recast_function_types() {
|
||||
let input = r#"foo = x: fn
|
||||
foo = x: fn(number)
|
||||
fn foo(x: fn(): number): fn {
|
||||
return 0
|
||||
}
|
||||
fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn {
|
||||
return 0
|
||||
}
|
||||
type fn
|
||||
type foo = fn
|
||||
type foo = fn(a: string, b: { f: fn(): any })
|
||||
type foo = fn([fn])
|
||||
type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
|
||||
"#;
|
||||
let ast = crate::parsing::top_level_parse(input).unwrap();
|
||||
let actual = ast.recast(&FormatOptions::new(), 0);
|
||||
assert_eq!(actual, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unparse_call_inside_function_args_multiple_lines() {
|
||||
let input = r#"fn foo() {
|
||||
|
@ -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): any,
|
||||
): [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, accum: any): any,
|
||||
): [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] {}
|
||||
|
@ -436,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) {}
|
||||
|
@ -251,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,6 +163,14 @@ 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 is abstract
|
||||
|
@ -4,11 +4,22 @@ description: Error from executing argument_error.kcl
|
||||
---
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: This function expected the input argument to be of type
|
||||
│ Vec<KclValue> but it's actually of type Function
|
||||
╭─[5:5]
|
||||
× semantic: f requires a value with type `fn(any): any`, but found array
|
||||
│ (list)
|
||||
╭─[5:1]
|
||||
4 │
|
||||
5 │ map(f, f = [0, 1])
|
||||
· ┬
|
||||
· ─────────┬────────┬
|
||||
· │ ╰── tests/argument_error/input.kcl
|
||||
· ╰── tests/argument_error/input.kcl
|
||||
╰────
|
||||
╰─▶ KCL Semantic error
|
||||
|
||||
× semantic: f requires a value with type `fn(any): any`, but found
|
||||
│ array (list)
|
||||
╭─[5:12]
|
||||
4 │
|
||||
5 │ map(f, f = [0, 1])
|
||||
· ───┬──
|
||||
· ╰── tests/argument_error/input.kcl
|
||||
╰────
|
||||
|
@ -4,10 +4,22 @@ description: Error from executing array_elem_pop_empty_fail.kcl
|
||||
---
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: Cannot pop from an empty array
|
||||
× semantic: The input argument of `std::array::pop` requires a value with
|
||||
│ type `[any; 1+]`, but found array (list)
|
||||
╭─[2:8]
|
||||
1 │ arr = []
|
||||
2 │ fail = pop(arr)
|
||||
· ────┬───
|
||||
· ────┬───┬
|
||||
· │ ╰── tests/array_elem_pop_empty_fail/input.kcl
|
||||
· ╰── tests/array_elem_pop_empty_fail/input.kcl
|
||||
╰────
|
||||
╰─▶ KCL Semantic error
|
||||
|
||||
× semantic: The input argument of `std::array::pop` requires a value
|
||||
│ with type `[any; 1+]`, but found array (list)
|
||||
╭─[2:12]
|
||||
1 │ arr = []
|
||||
2 │ fail = pop(arr)
|
||||
· ─┬─
|
||||
· ╰── tests/array_elem_pop_empty_fail/input.kcl
|
||||
╰────
|
||||
|
@ -105,10 +105,8 @@ description: Operations executed clone_w_fillets.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"labeledArgs": {},
|
||||
"type": "KclStdLibCall",
|
||||
"name": "clone",
|
||||
"sourceRange": [],
|
||||
"type": "StdLibCall",
|
||||
"unlabeledArg": {
|
||||
"value": {
|
||||
"type": "Solid",
|
||||
@ -117,6 +115,8 @@ description: Operations executed clone_w_fillets.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"labeledArgs": {},
|
||||
"sourceRange": []
|
||||
}
|
||||
]
|
||||
|
@ -98,10 +98,8 @@ description: Operations executed clone_w_shell.kcl
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"labeledArgs": {},
|
||||
"type": "KclStdLibCall",
|
||||
"name": "clone",
|
||||
"sourceRange": [],
|
||||
"type": "StdLibCall",
|
||||
"unlabeledArg": {
|
||||
"value": {
|
||||
"type": "Solid",
|
||||
@ -110,6 +108,8 @@ description: Operations executed clone_w_shell.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"labeledArgs": {},
|
||||
"sourceRange": []
|
||||
}
|
||||
]
|
||||
|
@ -678,7 +678,7 @@ flowchart LR
|
||||
99 --- 245
|
||||
99 --- 293
|
||||
106 --- 193
|
||||
106 x--> 206
|
||||
106 x--> 207
|
||||
106 --- 272
|
||||
106 --- 321
|
||||
126 --- 194
|
||||
@ -1012,7 +1012,7 @@ flowchart LR
|
||||
233 <--x 205
|
||||
234 <--x 205
|
||||
235 <--x 205
|
||||
272 <--x 207
|
||||
272 <--x 206
|
||||
254 <--x 213
|
||||
255 <--x 213
|
||||
256 <--x 213
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |