Compare commits

...

22 Commits

Author SHA1 Message Date
dac3c0c224 Delete test that is covering a basic assumption 2025-05-13 12:46:28 -04:00
99cb6a6179 [Fix] Created the safest navigate/load/open/please sir i want some more for a .kcl file (#6867)
* fix: clear scene and bust cache if rust panics

* Update onboarding following @jgomez720

* chore: hopefully made a safe navigate to kcl file to call executeAST without a race condition

* chore: hopefully made a safe navigate to kcl file to call executeAST without a race condition

* fix: clean up

* fix: FUCK

* fix: FUCK 2.0

* fix: oh boi

* fix: oh boi

* fix: idk man

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix: take main on this, do not need a single line from my testing code

* fix: more PR cleanup from all of the testing code

* fix: trying to clean up more, ope this has a lot of other code

* fix: PR clean up

* fix: trying to get a clean branch, I had multiple other branches in here ope

* fix: more cleanup

* fix: another one

* fix: fixed the comment to be accurate

* fix: removed confusing comment

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 15:35:29 +00:00
13dbfdfaa4 SystemIOMachine: Toast default error instead of empty string (#6898)
pierremtb/issue6820-add-error-instead-of-empty-string
2025-05-13 10:28:24 -05:00
8603f5c53a Default to the feature tree, code, and file panes open (#6901)
Only include the file pane on desktop. This should encourage users to
try point-and-click and feature tree workflows. After #6629 is
completed, we should default to the code pane being closed.
2025-05-13 15:25:27 +00:00
d5cc9e8386 Check both public and internal KCL samples (#6889) 2025-05-13 09:21:53 -04:00
533e17466b Update text-to-cad success toast to tell what file got made (#6882)
Closes #5820 and provides a bit of other polish to make this toast look
a bit better.
2025-05-13 12:26:29 +00:00
01c7b69f50 Function types (#6891)
* Change Fn to fn for function types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Support args and return types in function types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Use fancy function types in the docs

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 14:59:23 +12:00
47feae3bd9 Register modeling commands as disabled, enable without needing to re-open the command palette (#6886)
* Allow adding and removing commands from any command bar state

* Allow commands to be configured disabled in the combobox

* Set up modeling commands to toggle `disabled` based on network status, instead of filtering

* Fix tsc
2025-05-13 01:18:50 +00:00
e7ecd655c4 Add docs on arrays and ranges (#6890)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 13:06:07 +12:00
d9e538c6ea Fix docs for object properties (#6892)
Jon removed support for obj["a"] a while ago in #6529 aka
2e754f2a11
2025-05-13 00:56:50 +00:00
e297f8286f KCL: Fix incorrect error messages (#6883)
**Problem:**

KCL's xLine and yLine functions were telling users to supply either "end" or "endAbsolute" arguments. But "end" is not a valid argument for xLine/yLine, it's actually "length" instead.

**Cause:**

xLine/yLine were using a helper function designed for `line` KCL stdlib functions, which do use `end`. 

**Solution**

Add a new param to the helper function, indicating what the label should be for relative lines. "end" for `line` calls, `length` for x/yline
2025-05-13 00:26:43 +00:00
b3bc90bbe4 Fix lower right version background in dark mode and update snapshots (#6888)
* pierremtb/adhoc/fix-version-bg-dark-mode

* Update snapshots
2025-05-12 23:46:52 +00:00
95a02cbcd7 Check all nested KCL samples (#6880) 2025-05-12 18:43:51 -04:00
a049768f1c Move some more functions to be declared in KCL (#6856)
* Move the leg functions to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Move array functions to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Move clone to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Add a function type

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 08:29:38 +12:00
818d9a0d77 Release link and updater toast touchups on Nightly (#6881)
pierremtb/adhoc/clean-up-nightly-on-merge-more
2025-05-12 20:20:12 +00:00
1a8f80a7dc #6793 Fix file, folder renaming issues (#6869)
* fix going into rename mode for files with parents

* lastDirectoryClicked is not used

* fix file/folder rename bugs: renaming within folders

* Turn form into div to fix issue of child renaming continues into renaming the parent folder when hitting enter

* ContextMenu stopPropagation not needed anymore, maybe because of form refactor

* ContextMenu IS needed actually, with multiple nested folders

* make lint happy

* Update src/components/ContextMenu.tsx

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* re-add <form> instead of <div> for file renaming

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-05-12 21:59:10 +02:00
566c9eaf10 Spell out shortcut in project search bar (#6872)
dumb little zero-thought bit of polish.
2025-05-12 19:36:05 +00:00
e6485c2da1 Clean up Nightly release version & notes (#6877)
* pierremtb/adhoc/clean-up-nightly-on-merge

* To revert: test to upload builds at test/nightly

* Remove + suffix trim that's not needed anymore

* Revert "To revert: test to upload builds at test/nightly"

This reverts commit b0549d426f.
2025-05-12 14:21:09 -04:00
0479edd36a Remove unused GitHub Actions workflow for old onboarding (#6873) 2025-05-12 17:30:51 +00:00
87c1e92134 Deploy Nightly on merge to main (#6871)
* Deploy Nightly on merge to main

* Forgot one block

* Remove .github/workflows/tag-nightly.yml

* Remove scripts/get-nightly-changelog.sh

* Update .github/workflows/build-apps.yml

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-12 12:30:01 -04:00
8f950ac1b0 Always run end-to-end tests (#6870) 2025-05-12 12:23:09 -04:00
78e4f43708 Fix kcl errors indicator not scaling with multi-digit count (#6864)
Fixes #6847
2025-05-12 11:36:09 -04:00
128 changed files with 1526 additions and 36239 deletions

View File

@ -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'

View File

@ -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,
});
}

View File

@ -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/

View File

@ -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:

View File

@ -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

View File

@ -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
View 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.

View File

@ -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)

View File

@ -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`

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@ -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)
```

View File

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

View File

@ -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)

View File

@ -1,38 +0,0 @@
---
title: "legAngX"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for x."
layout: manual
---
Compute the angle of the given leg for x.
```kcl
legAngX(
hypotenuse: number,
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)
```

View File

@ -1,38 +0,0 @@
---
title: "legAngY"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for y."
layout: manual
---
Compute the angle of the given leg for y.
```kcl
legAngY(
hypotenuse: number,
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)
```

View File

@ -1,38 +0,0 @@
---
title: "legLen"
subtitle: "Function in std::math"
excerpt: "Compute the length of the given leg."
layout: manual
---
Compute the length of the given leg.
```kcl
legLen(
hypotenuse: number,
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)
```

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -43,7 +43,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `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

View File

@ -29,7 +29,7 @@ If you want to apply the transform in global space, set `global` to `true`. The
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `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

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `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

View 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.

View 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.

View File

@ -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,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -905,7 +905,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter')
// 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()

View File

@ -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
View File

@ -1959,6 +1959,7 @@ dependencies = [
"url",
"uuid",
"validator",
"walkdir",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",

View File

@ -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 }

View File

@ -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) {

View File

@ -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();
}

View File

@ -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})"#);
}

View File

@ -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,
}
}
}

View File

@ -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());

View File

@ -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")]
@ -3285,7 +3337,7 @@ impl fmt::Display for Type {
} else {
write!(f, ",")?;
}
write!(f, "{}: ", p.identifier.name)?;
write!(f, " {}:", p.identifier.name)?;
if let Some(ty) = &p.type_ {
write!(f, " {}", ty.inner)?;
}

View File

@ -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));
}
}
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)
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));
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])

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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,

View File

@ -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()],
))
}

View File

@ -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,
}

View File

@ -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,

View File

@ -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() {

View File

@ -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] {}

View File

@ -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) {}

View File

@ -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 {}

View File

@ -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

View File

@ -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
· ╰── 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
╰────

View File

@ -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
╰────

View File

@ -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": []
}
]

View File

@ -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": []
}
]

View File

@ -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

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

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