Compare commits

...

25 Commits

Author SHA1 Message Date
2a415012d7 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-25 18:04:37 +00:00
d39dd5697c A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-25 17:57:59 +00:00
c9f7275bc4 pierremtb/adhoc/commit-rust-analyzer-linked-projects 2025-02-25 12:51:44 -05:00
4297dc43ae Disable pw object.assign context (#5498) 2025-02-25 16:49:58 +00:00
935b4ee7f5 KCL: All keyword arguments should use camelCase (#5481)
Closes #5355
2025-02-25 10:29:59 -06:00
2523242bb1 Get rid of failing cache on build-apps' setup-node (#5490) 2025-02-25 09:53:18 -05:00
309e4fadf0 Backspace as Delete only on macOS (#5453)
* Backspace as Delete only on macOS

* Call isDesktop first

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-25 09:41:25 -05:00
842054de09 refactor codeToIdSelections (#5432)
* add test for original range to artifact conversion

* try naieve refactor

* types clean up

* typo

* break function into smaller functions

* optimizations

* better comments

* camera test tweak

* fmt

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* overflow fix

* update binary search to ignore end ranges

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* break binary search into sub function

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-25 12:51:25 +00:00
af146284b6 Share instances of the std prelude (#5448)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-25 16:10:06 +13:00
5ac40e6849 Use Rust 1.85 (#5441) 2025-02-24 18:04:43 -06:00
d3e0625e8a Add edit flow for Helix (#5394)
* WIP: Add edit flow for Helix
Fixes #5392

* Clean upp

* Add e2e test step
2025-02-24 18:44:13 -05:00
484da28374 Rename AI-related buttons to reduce confusion (#5475)
Just renaming the titles for now; there are lots of other places we
refer to "text-to-cad" and "prompt-to-edit" that we will will want to
follow up on eventually I'm sure. This is a user fix with the least
chance of introducing bugs.
2025-02-24 18:14:00 +00:00
fa9e6ccea9 Bring back Playwright-Electron Workbench's on-failure screenshot + new PLAYWRIGHT_RECORD_VIDEO (#5464)
* Fix playwright-electron screenshots on failure and add PLAYWRIGHT_RECORD_VIDEO

* yarn tsc
2025-02-24 13:09:39 -05:00
77fbb68419 Improve dev experience on windows (#5335)
* Add files:flip-to-nightly:windows

* Add fetch:wasm:windows script

* Add install:tools:windows

* Update install:tools:windows

* Remove wasm-pack and improve README instruction

* yarn.lock

* Update to Powershell 7, fixes left and right

* Contd clean up

* install:wasm-pack fix and cleanup

* Fix vercel (hopefully)

* Attempt 2 for Vercel

* Attempt 3 for Vercel

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Attempt 5

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Another fix

* Try with install:wasm-pack:cargo

* Decouple wasm-pack install and build:wasm

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Use install:wasm-pack:sh where possible

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* @jtran's comments

* Back to cargo

* Use binary install of wasm-pack through taiki-e/install-action

* Fix QL suggestions

* Add PS7 link

* Fix build:wasm:windows

* Fix flip-files-to-nightly after PS7 upgrade

* Pin taiki-e/install-action@v2.48.20

* Try with taiki-e/install-action@2dbeb927f5

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-24 11:19:09 -05:00
253a7910b0 Fix settings loading actions (#5469)
Due to a lapse in understanding between `Set all settings` event and
`setAllSettings` action, these were missing from slots where user and
project settings are loaded. We want to fire off these effectful actions
when those settings are updated so that we can, for example, get the
opposite theme for sketch mode properly.
2025-02-24 10:23:10 -05:00
88c6be810f Bump nick-fields/retry from 3.0.0 to 3.0.1 (#5466)
Bumps [nick-fields/retry](https://github.com/nick-fields/retry) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/nick-fields/retry/releases)
- [Changelog](https://github.com/nick-fields/retry/blob/master/.releaserc.js)
- [Commits](https://github.com/nick-fields/retry/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: nick-fields/retry
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 08:32:30 -05:00
2860926571 Bump tailwindcss from 3.4.7 to 3.4.17 (#5468)
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) from 3.4.7 to 3.4.17.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v3.4.17/packages/tailwindcss)

---
updated-dependencies:
- dependency-name: tailwindcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 05:43:05 -05:00
fb81c02877 Bump electron-updater from 6.5.0 to 6.6.0 (#5467)
Bumps [electron-updater](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-updater) from 6.5.0 to 6.6.0.
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/electron-updater@6.6.0/packages/electron-updater)

---
updated-dependencies:
- dependency-name: electron-updater
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 05:41:48 -05:00
d78648f0f8 Bump @codemirror/language from 6.10.3 to 6.10.8 (#5402)
* Bump @codemirror/language from 6.10.3 to 6.10.8

Bumps [@codemirror/language](https://github.com/codemirror/language) from 6.10.3 to 6.10.8.
- [Changelog](https://github.com/codemirror/language/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/language/compare/6.10.3...6.10.8)

---
updated-dependencies:
- dependency-name: "@codemirror/language"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2025-02-22 14:36:36 -05:00
fe31769be5 update github action to only post comment once (#5454)
* update github action

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-22 14:21:02 -05:00
388371b05d External retry of the playwright step instead of --last-failed (#5465)
* External retry

* Trigger CI

* More external attemps, less internal retries

* Trigger CI

* Trigger CI

* Clean up for PR

* Add --retries=2 to cargo test
2025-02-22 12:59:19 -05:00
9db69007e5 Initiate connection when we receive SDP and don't connect when null (#5451)
* Initiate connection when we receive SDP and don't connect when null

Sometimes clients were gathering ice candidates faster than we returning
the SDP answer, which meant we tried to parse a null as the remote
description.

Clean up tsc error and add log on timeout

* Add fallback for windows CI

WIP

* If we get sdp answer just connect

* typo

* Fmt

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
2025-02-22 12:38:09 -05:00
f2a6492ab7 Set apperance in feature tree context menu (#5439)
* Revert "Revert multi-profile (#4812)"

This reverts commit efe8089b08.

* fix poor 1000ms wait UX

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* trigger CI

* Add Rust side artifacts for startSketchOn face or plane (#4834)

* Add Rust side artifacts for startSketchOn face or plane

* move ast digging

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>

* lint

* lint

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-16-cores)

* trigger CI

* chore: disabled file watcher which prevents faster file write (#4835)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* partial fixes

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Trigger CI

* Fix up all the tests

* Fix partial execution

* wip

* WIP

* wip

* rust changes to make three point confrom to same as others since we're not ready with name params yet

* most of the fix for 3 point circle

* get overlays working for circle three point

* fmt

* fix types

* cargo fmt

* add face codef ref for walls and caps

* fix sketch on face after updates to rust side artifact graph

* some things needed for multi-profile tests

* bad attempts at fixing rust

* more

* more

* fix rust

* more rust fixes

* overlay fix

* remove duplicate test

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* lint and typing

* maybe fix a unit test

* small thing

* WIP: Add Delete right click menu item to Feature Tree
Copying code around
Fixes #5090

* I don't know why it works

* WIP

* fix circ dep

* fix unit test

* fix some tests

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Working deletion machine loo

* Working helix deletion

* Extend deletion to more things

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* fix sweep point-and-click test

* fix more tests and add a fix me

* fix more tests

* fix electron specific test

* tsc

* more test tweaks

* update docs

* commint snaps?

* is clippy happy now?

* clippy again

* test works now without me changing anything big-fixed-itself

* small bug

* make three point have cross hair to make it consistent with othe rtools

* fix up state diagram

* fmt

* add draft point for first click of three point circ

* 1 test for three point circle

* 2 test for three point circle

* clean up

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* remove bad doc comment

* remove test skip

* remove onboarding test changes

* Update src/lang/modifyAst.ts

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

* Update output from simulation tests

* Fix to use correct source ranges

This also reduces cloning.

* Change back to skipping face cap none and both

* Update output after changing back to skipping none and both

* Fix clippy warning

* fix profile start snap bug

* WIP: migrate to actor

* add path ids to cap

* fix going into edit sketch

* make other startSketchOn's work

* fix snapshot test

* explain function name

* Update src/lib/rectangleTool.ts

Co-authored-by: Frank Noirot <frank@zoo.dev>

* rename error

* remove file tree from diff

* Update src/clientSideScene/segments.ts

Co-authored-by: Frank Noirot <frank@zoo.dev>

* nit

* Continue actor migration

* Prevent double write to KCL code on revolve

* Clean up

* Update output after adding cap-to-path graph edge

* Clean up

* Update machine diag

* Update context menu hotkey class

* Fix edit/select sketch-on-cap via feature tree

* clean up for face codeRef

* fix changing tools part way through circle/rect tools

* fix delete of circle profile

* fix close profiles

* fix closing profile bug (tangentArcTo being ignored)

* remove stale comment

* Delete paths associated with sketch when the sketch plane is deleted

* Add support for deleting sketches on caps (not walls)

* get delet working for walls

* make delet of extrusions work for multi profile

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Delete the sketch statement too on the cap and wall cases

* Don't write to file in `split-sketch-pipe-if-needed` unless necessary

* Don't wait for file write to complete within `updateEditorWithAstAndWriteToFile`
It is already debounced internally. If we await it, we will have to wait for a debounced timeout

* Fix bad conflict resolution

* Fix a few things post merge

* Add guard back, fixing tests

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Add e2e test

* WIP: Add Set apperance right click menu item to Feature Tree
Fixes #5372

* Working cheap implementation

* Unset appearance via Default option

* More colors

* Add basic test

* Add test

* Lint

* 🔪 them timers

* Increase color matching threshold on appearance test

* Fix colors in e2e

* Move Set apperance down in the menu

* Revert "Move Set apperance down in the menu"

This reverts commit eb1d2e2c1c.

* Attempt at fixing dual extrude for role option search in test

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: 49lf <ircsurfer33@gmail.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
2025-02-22 13:09:54 +00:00
3ddce116e5 Warn when calling deprecated functions (#5447)
* Warn when calling deprecated std functions

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

* Refactor function values

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-22 20:16:29 +13:00
57f7d022ca Allow using a different source range for suggestions than the error message (#5442)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-22 18:55:44 +13:00
192 changed files with 3495 additions and 22953 deletions

View File

@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
fi fi
retry=1 retry=1
max_retrys=5 max_retrys=1
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues # retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
while [[ $retry -le $max_retrys ]]; do while [[ $retry -le $max_retrys ]]; do

View File

@ -22,6 +22,9 @@ jobs:
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
workspaces: './src/wasm-lib' workspaces: './src/wasm-lib'
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- name: build wasm - name: build wasm
run: yarn build:wasm run: yarn build:wasm

View File

@ -32,7 +32,6 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install - run: yarn install
@ -44,6 +43,10 @@ jobs:
workspaces: './src/wasm-lib' workspaces: './src/wasm-lib'
# TODO: see if we can fetch from main instead if no diff at src/wasm-lib # TODO: see if we can fetch from main instead if no diff at src/wasm-lib
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- name: Run build:wasm - name: Run build:wasm
run: "yarn build:wasm" run: "yarn build:wasm"
@ -120,15 +123,13 @@ jobs:
cp prepared-files/assets/icon.ico assets/icon.ico cp prepared-files/assets/icon.ico assets/icon.ico
cp prepared-files/assets/icon.png assets/icon.png cp prepared-files/assets/icon.png assets/icon.png
- name: Sync node version and setup cache - uses: actions/setup-node@v4
uses: actions/setup-node@v4
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' # Set this to npm, yarn or pnpm.
- name: yarn install - name: yarn install
# Windows is picky sometimes and fails on fetch. Step takes about ~30s # Windows is picky sometimes and fails on fetch. Step takes about ~30s
uses: nick-fields/retry@v3.0.0 uses: nick-fields/retry@v3.0.1
with: with:
timeout_minutes: 2 timeout_minutes: 2
max_attempts: 3 max_attempts: 3
@ -179,7 +180,7 @@ jobs:
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
DEBUG: "electron-notarize*" DEBUG: "electron-notarize*"
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures # TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
uses: nick-fields/retry@v3.0.0 uses: nick-fields/retry@v3.0.1
with: with:
timeout_minutes: 10 timeout_minutes: 10
max_attempts: 3 max_attempts: 3
@ -240,7 +241,7 @@ jobs:
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
DEBUG: "electron-notarize*" DEBUG: "electron-notarize*"
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures # TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
uses: nick-fields/retry@v3.0.0 uses: nick-fields/retry@v3.0.1
with: with:
timeout_minutes: 10 timeout_minutes: 10
max_attempts: 3 max_attempts: 3

View File

@ -46,7 +46,7 @@ jobs:
shell: bash shell: bash
run: |- run: |-
cd "${{ matrix.dir }}" cd "${{ matrix.dir }}"
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --retries=2 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
env: env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000 RUST_MIN_STACK: 10485760000

View File

@ -27,10 +27,20 @@ jobs:
const owner = context.repo.owner; const owner = context.repo.owner;
const repo = context.repo.repo; const repo = context.repo.repo;
// Post a comment on the PR const { data: comments } = await github.rest.issues.listComments({
await github.rest.issues.createComment({
owner, owner,
repo, repo,
issue_number, issue_number
body: message,
}); });
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

@ -89,6 +89,9 @@ jobs:
continue-on-error: true continue-on-error: true
- name: Setup Rust - name: Setup Rust
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- name: Cache Wasm (because rust diff) - name: Cache Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true' if: needs.check-rust-changes.outputs.rust-changed == 'true'
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
@ -200,9 +203,11 @@ jobs:
- name: Run playwright/electron flow (with retries) - name: Run playwright/electron flow (with retries)
id: retry id: retry
if: ${{ !cancelled() && (success() || failure()) }} if: ${{ !cancelled() && (success() || failure()) }}
shell: bash uses: nick-fields/retry@v3.0.1
run: | with:
.github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}} command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
timeout_minutes: 30
max_attempts: 25
env: env:
CI: true CI: true
FAIL_ON_CONSOLE_ERRORS: true FAIL_ON_CONSOLE_ERRORS: true

View File

@ -37,6 +37,9 @@ jobs:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
- run: yarn install - run: yarn install
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- run: yarn build:wasm - run: yarn build:wasm
yarn-tsc: yarn-tsc:
@ -53,6 +56,9 @@ jobs:
with: with:
workspaces: './src/wasm-lib' workspaces: './src/wasm-lib'
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- run: yarn build:wasm - run: yarn build:wasm
- run: yarn tsc - run: yarn tsc
@ -92,6 +98,9 @@ jobs:
cache: 'yarn' cache: 'yarn'
- run: yarn install - run: yarn install
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- run: yarn build:wasm - run: yarn build:wasm
- run: yarn simpleserver:bg - run: yarn simpleserver:bg
@ -118,6 +127,9 @@ jobs:
cache: 'yarn' cache: 'yarn'
- run: yarn install - run: yarn install
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- run: yarn build:wasm - run: yarn build:wasm
- run: yarn simpleserver:bg - run: yarn simpleserver:bg

2
.gitignore vendored
View File

@ -24,7 +24,7 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
.idea .idea
.vscode # .vscode
.helix .helix
src/wasm-lib/.idea src/wasm-lib/.idea
src/wasm-lib/.vscode src/wasm-lib/.vscode

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"rust-analyzer.linkedProjects": [
"src/wasm-lib/Cargo.toml"
]
}

View File

@ -48,22 +48,49 @@ We recommend downloading the latest application binary from [our Releases page](
## Running a development build ## Running a development build
First, [install Rust via `rustup`](https://www.rust-lang.org/tools/install). This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. We always use the latest stable version of Rust, so you may need to run `rustup update stable`. Then, run: Install a node version manager such as [fnm](https://github.com/Schniz/fnm?tab=readme-ov-#installation).
On Windows, it's also recommended to [upgrade your PowerShell version](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5#winget), we're using 7.
Then in the repo run the following to install and use the node version specified in `.nvmrc`. You might need to specify your processor architecture with `--arch arm64` or `--arch x64` if it's not autodetected.
```
fnm install --corepack-enabled
fnm use
```
Install the NPM dependencies with:
``` ```
yarn install yarn install
``` ```
followed by: This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. We have package scripts to run rustup, see `package.json` for reference:
``` ```
# macOS/Linux
yarn install:rust
yarn install:wasm-pack:sh
# Windows
yarn install:rust:windows
yarn install:wasm-pack:cargo
```
Then to build the WASM layer, run:
```
# macOS/Linux
yarn build:wasm yarn build:wasm
# Windows
yarn build:wasm:windows
``` ```
or if you have the gh cli installed Or if you have the `gh` cli installed and want to download the latest main wasm bundle. Note that on Windows, you need to associate .ps1 files with PowerShell, which can be done via the right click menu, selecting `C:\Program Files\PowerShell\7\pwsh.exe`, and you can install tools like `gh` via `yarn install:tools:windows`.
``` ```
./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle # macOS/Linux
yarn fetch:wasm
# Windows
yarn fetch:wasm:windows
``` ```
That will build the WASM binary and put in the `public` dir (though gitignored). That will build the WASM binary and put in the `public` dir (though gitignored).
@ -74,7 +101,7 @@ Finally, to run the web app only, run:
yarn start yarn start
``` ```
If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens of course, then navigate to localhost:3000 again. Note that navigating to `localhost:3000/signin` removes your token so you will need to set the token again. If you're not a Zoo employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens of course, then navigate to localhost:3000 again. Note that navigating to `localhost:3000/signin` removes your token so you will need to set the token again.
### Development environment variables ### Development environment variables
@ -101,7 +128,7 @@ This will start the application and hot-reload on changes.
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows). Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
To build with electron-builder, run `yarn tronb:package:dev` (or `yarn tronb:package:prod` to point to the .env.production variables) To package the app for your platform with electron-builder, run `yarn tronb:package:dev` (or `yarn tronb:package:prod` to point to the .env.production variables)
## Checking out commits / Bisecting ## Checking out commits / Bisecting

View File

@ -9,7 +9,7 @@ Set the appearance of a solid. This only works on solids, not sketches or indivi
This will work on any solid, including extruded solids, revolved solids, and shelled solids. This will work on any solid, including extruded solids, revolved solids, and shelled solids.
```js ```js
appearance(solid_set: SolidSet, color: String, metalness?: number, roughness?: number) -> SolidSet appearance(solidSet: SolidSet, color: String, metalness?: number, roughness?: number) -> SolidSet
``` ```
@ -17,7 +17,7 @@ appearance(solid_set: SolidSet, color: String, metalness?: number, roughness?: n
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) whose appearance is being set | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) whose appearance is being set | Yes |
| `color` | `String` | Color of the new material, a hex string like '#ff0000' | Yes | | `color` | `String` | Color of the new material, a hex string like '#ff0000' | Yes |
| `metalness` | `number` | Metalness of the new material, a percentage like 95.7. | No | | `metalness` | `number` | Metalness of the new material, a percentage like 95.7. | No |
| `roughness` | `number` | Roughness of the new material, a percentage like 95.7. | No | | `roughness` | `number` | Roughness of the new material, a percentage like 95.7. | No |

View File

@ -9,7 +9,7 @@ Construct a 2-dimensional circle, of the specified radius, centered at
the provided (x, y) origin point. the provided (x, y) origin point.
```js ```js
circle(data: CircleData, sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch circle(data: CircleData, sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
``` ```
@ -18,7 +18,7 @@ circle(data: CircleData, sketch_surface_or_group: SketchOrSurface, tag?: TagDecl
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `data` | [`CircleData`](/docs/kcl/types/CircleData) | Data for drawing an circle | Yes | | `data` | [`CircleData`](/docs/kcl/types/CircleData) | Data for drawing an circle | Yes |
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes | | `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No | | `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Construct a circle derived from 3 points.
```js ```js
circleThreePoint(p1: [number], p2: [number], p3: [number], sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch circleThreePoint(p1: [number], p2: [number], p3: [number], sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
``` ```
@ -20,7 +20,7 @@ circleThreePoint(p1: [number], p2: [number], p3: [number], sketch_surface_or_gro
| `p1` | `[number]` | 1st point to derive the circle. | Yes | | `p1` | `[number]` | 1st point to derive the circle. | Yes |
| `p2` | `[number]` | 2nd point to derive the circle. | Yes | | `p2` | `[number]` | 2nd point to derive the circle. | Yes |
| `p3` | `[number]` | 3rd point to derive the circle. | Yes | | `p3` | `[number]` | 3rd point to derive the circle. | Yes |
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes | | `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Identifier for the circle to reference elsewhere. | No | | `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Identifier for the circle to reference elsewhere. | No |
### Returns ### Returns

View File

@ -1,15 +0,0 @@
---
title: "std::prelude::HALF_TURN"
excerpt: ""
layout: manual
---
```js
std::prelude::HALF_TURN: number(deg) = 180deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::prelude::QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::prelude::QUARTER_TURN: number(deg) = 90deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::prelude::THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::prelude::THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::prelude::ZERO"
excerpt: ""
layout: manual
---
```js
std::prelude::ZERO: number = 0
```

View File

@ -0,0 +1,15 @@
---
title: "std::HALF_TURN"
excerpt: ""
layout: manual
---
```js
std::HALF_TURN: number(deg) = 180deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::QUARTER_TURN: number(deg) = 90deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::ZERO"
excerpt: ""
layout: manual
---
```js
std::ZERO: number = 0
```

View File

@ -9,7 +9,7 @@ Extend a 2-dimensional sketch through a third dimension in order to
create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid. create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid.
```js ```js
extrude(sketch_set: SketchSet, length: number) -> SolidSet extrude(sketchSet: SketchSet, length: number) -> SolidSet
``` ```
@ -17,7 +17,7 @@ extrude(sketch_set: SketchSet, length: number) -> SolidSet
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes |
| `length` | `number` | How far to extrude the given sketches | Yes | | `length` | `number` | How far to extrude the given sketches | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Create a helix.
```js ```js
helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis: Axis3dOrEdgeReference, length?: number) -> HelixValue helix(revolutions: number, angleStart: number, ccw?: bool, radius: number, axis: Axis3dOrEdgeReference, length?: number) -> HelixValue
``` ```
@ -18,7 +18,7 @@ helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `revolutions` | `number` | Number of revolutions. | Yes | | `revolutions` | `number` | Number of revolutions. | Yes |
| `angle_start` | `number` | Start angle (in degrees). | Yes | | `angleStart` | `number` | Start angle (in degrees). | Yes |
| `ccw` | `bool` | Is the helix rotation counter clockwise? The default is `false`. | No | | `ccw` | `bool` | Is the helix rotation counter clockwise? The default is `false`. | No |
| `radius` | `number` | Radius of the helix. | Yes | | `radius` | `number` | Radius of the helix. | Yes |
| `axis` | [`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference) | Axis to use for the helix. | Yes | | `axis` | [`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference) | Axis to use for the helix. | Yes |

View File

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

View File

@ -15,7 +15,7 @@ For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit
Note: The import command currently only works when using the native Modeling App. Note: The import command currently only works when using the native Modeling App.
```js ```js
import(file_path: String, options?: ImportFormat) -> ImportedGeometry import(filePath: String, options?: ImportFormat) -> ImportedGeometry
``` ```
@ -23,7 +23,7 @@ import(file_path: String, options?: ImportFormat) -> ImportedGeometry
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `file_path` | `String` | | Yes | | `filePath` | `String` | | Yes |
| `options` | [`ImportFormat`](/docs/kcl/types/ImportFormat) | Import format specifier | No | | `options` | [`ImportFormat`](/docs/kcl/types/ImportFormat) | Import format specifier | No |
### Returns ### Returns

View File

@ -10,6 +10,10 @@ layout: manual
* [Modules](kcl/modules) * [Modules](kcl/modules)
* [Known Issues](kcl/KNOWN-ISSUES) * [Known Issues](kcl/KNOWN-ISSUES)
* **`std`** * **`std`**
* [`HALF_TURN`](kcl/const_std-HALF_TURN)
* [`QUARTER_TURN`](kcl/const_std-QUARTER_TURN)
* [`THREE_QUARTER_TURN`](kcl/const_std-THREE_QUARTER_TURN)
* [`ZERO`](kcl/const_std-ZERO)
* [`abs`](kcl/abs) * [`abs`](kcl/abs)
* [`acos`](kcl/acos) * [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX) * [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
@ -118,8 +122,3 @@ layout: manual
* [`cos`](kcl/std-math-cos) * [`cos`](kcl/std-math-cos)
* [`sin`](kcl/std-math-sin) * [`sin`](kcl/std-math-sin)
* [`tan`](kcl/std-math-tan) * [`tan`](kcl/std-math-tan)
* **`std::prelude`**
* [`HALF_TURN`](kcl/const_std-prelude-HALF_TURN)
* [`QUARTER_TURN`](kcl/const_std-prelude-QUARTER_TURN)
* [`THREE_QUARTER_TURN`](kcl/const_std-prelude-THREE_QUARTER_TURN)
* [`ZERO`](kcl/const_std-prelude-ZERO)

View File

@ -9,7 +9,7 @@ Extend the current sketch with a new straight line.
```js ```js
line(sketch: Sketch, end_absolute?: [number], end?: [number], tag?: TagDeclarator) -> Sketch line(sketch: Sketch, endAbsolute?: [number], end?: [number], tag?: TagDeclarator) -> Sketch
``` ```
@ -18,7 +18,7 @@ line(sketch: Sketch, end_absolute?: [number], end?: [number], tag?: TagDeclarato
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes | | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes |
| `end_absolute` | `[number]` | Which absolute point should this line go to? Incompatible with `end`. | No | | `endAbsolute` | `[number]` | Which absolute point should this line go to? Incompatible with `end`. | No |
| `end` | `[number]` | How far away (along the X and Y axes) should this line go? Incompatible with `endAbsolute`. | No | | `end` | `[number]` | How far away (along the X and Y axes) should this line go? Incompatible with `endAbsolute`. | No |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Create a new tag which refers to this line | No | | `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Create a new tag which refers to this line | No |

View File

@ -9,7 +9,7 @@ Create a 3D surface or solid by interpolating between two or more sketches.
The sketches need to closed and on the same plane. The sketches need to closed and on the same plane.
```js ```js
loft(sketches: [Sketch], v_degree: NonZeroU32, bez_approximate_rational: bool, base_curve_index?: integer, tolerance?: number) -> Solid loft(sketches: [Sketch], vDegree: NonZeroU32, bezApproximateRational: bool, baseCurveIndex?: integer, tolerance?: number) -> Solid
``` ```
@ -18,9 +18,9 @@ loft(sketches: [Sketch], v_degree: NonZeroU32, bez_approximate_rational: bool, b
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketches to loft. Must include at least 2 sketches. | Yes | | `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketches to loft. Must include at least 2 sketches. | Yes |
| `v_degree` | `NonZeroU32` | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified. | Yes | | `vDegree` | `NonZeroU32` | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified. | Yes |
| `bez_approximate_rational` | `bool` | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary. | Yes | | `bezApproximateRational` | `bool` | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary. | Yes |
| `base_curve_index` | `integer` | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No | | `baseCurveIndex` | `integer` | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No |
| `tolerance` | `number` | Tolerance for the loft operation. | No | | `tolerance` | `number` | Tolerance for the loft operation. | No |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ 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)]` Given a list like `[a, b, c]`, and a function like `f`, returns `[f(a), f(b), f(c)]`
```js ```js
map(array: [KclValue], map_fn: FunctionParam) -> [KclValue] map(array: [KclValue], mapFn: FunctionSource) -> [KclValue]
``` ```
@ -18,7 +18,7 @@ map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes | | `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `map_fn` | `FunctionParam` | | Yes | | `mapFn` | `FunctionSource` | | Yes |
### Returns ### Returns

View File

@ -11,7 +11,7 @@ Only works on unclosed sketches for now.
Mirror occurs around a local sketch axis rather than a global axis. Mirror occurs around a local sketch axis rather than a global axis.
```js ```js
mirror2d(data: Mirror2dData, sketch_set: SketchSet) -> [Sketch] mirror2d(data: Mirror2dData, sketchSet: SketchSet) -> [Sketch]
``` ```
@ -20,7 +20,7 @@ mirror2d(data: Mirror2dData, sketch_set: SketchSet) -> [Sketch]
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `data` | [`Mirror2dData`](/docs/kcl/types/Mirror2dData) | Data for a mirror. | Yes | | `data` | [`Mirror2dData`](/docs/kcl/types/Mirror2dData) | Data for a mirror. | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch some number of times along a partial or
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained. complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
```js ```js
patternCircular2d(sketch_set: SketchSet, instances: integer, center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Sketch] patternCircular2d(sketchSet: SketchSet, instances: integer, center: [number], arcDegrees: number, rotateDuplicates: bool, useOriginal?: bool) -> [Sketch]
``` ```
@ -17,12 +17,12 @@ patternCircular2d(sketch_set: SketchSet, instances: integer, center: [number], a
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `center` | `[number]` | The center about which to make the pattern. This is a 2D vector. | Yes | | `center` | `[number]` | The center about which to make the pattern. This is a 2D vector. | Yes |
| `arc_degrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes | | `arcDegrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
| `rotate_duplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes | | `rotateDuplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Repeat a 3-dimensional solid some number of times along a partial or
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained. complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
```js ```js
patternCircular3d(solid_set: SolidSet, instances: integer, axis: [number], center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Solid] patternCircular3d(solidSet: SolidSet, instances: integer, axis: [number], center: [number], arcDegrees: number, rotateDuplicates: bool, useOriginal?: bool) -> [Solid]
``` ```
@ -17,13 +17,13 @@ patternCircular3d(solid_set: SolidSet, instances: integer, axis: [number], cente
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid(s) to pattern | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid(s) to pattern | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `axis` | `[number]` | The axis around which to make the pattern. This is a 3D vector | Yes | | `axis` | `[number]` | The axis around which to make the pattern. This is a 3D vector | Yes |
| `center` | `[number]` | The center about which to make the pattern. This is a 3D vector. | Yes | | `center` | `[number]` | The center about which to make the pattern. This is a 3D vector. | Yes |
| `arc_degrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes | | `arcDegrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
| `rotate_duplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes | | `rotateDuplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch along some dimension, with a dynamic amount
of distance between each repetition, some specified number of times. of distance between each repetition, some specified number of times.
```js ```js
patternLinear2d(sketch_set: SketchSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Sketch] patternLinear2d(sketchSet: SketchSet, instances: integer, distance: number, axis: [number], useOriginal?: bool) -> [Sketch]
``` ```
@ -17,11 +17,11 @@ patternLinear2d(sketch_set: SketchSet, instances: integer, distance: number, axi
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes | | `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes |
| `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes | | `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Repeat a 3-dimensional solid along a linear path, with a dynamic amount
of distance between each repetition, some specified number of times. of distance between each repetition, some specified number of times.
```js ```js
patternLinear3d(solid_set: SolidSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Solid] patternLinear3d(solidSet: SolidSet, instances: integer, distance: number, axis: [number], useOriginal?: bool) -> [Solid]
``` ```
@ -17,11 +17,11 @@ patternLinear3d(solid_set: SolidSet, instances: integer, distance: number, axis:
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes | | `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes |
| `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes | | `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns

View File

@ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local") - `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
```js ```js
patternTransform(solid_set: SolidSet, instances: integer, transform: FunctionParam, use_original?: bool) -> [Solid] patternTransform(solidSet: SolidSet, instances: integer, transform: FunctionSource, useOriginal?: bool) -> [Solid]
``` ```
@ -43,10 +43,10 @@ patternTransform(solid_set: SolidSet, instances: integer, transform: FunctionPar
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | `FunctionParam` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes | | `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
```js ```js
patternTransform2d(sketch_set: SketchSet, instances: integer, transform: FunctionParam, use_original?: bool) -> [Sketch] patternTransform2d(sketchSet: SketchSet, instances: integer, transform: FunctionSource, useOriginal?: bool) -> [Sketch]
``` ```
@ -17,10 +17,10 @@ patternTransform2d(sketch_set: SketchSet, instances: integer, transform: Functio
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes | | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | `FunctionParam` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes | | `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Create a regular polygon with the specified number of sides that is either inscr
```js ```js
polygon(data: PolygonData, sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch polygon(data: PolygonData, sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
``` ```
@ -18,7 +18,7 @@ polygon(data: PolygonData, sketch_surface_or_group: SketchOrSurface, tag?: TagDe
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `data` | [`PolygonData`](/docs/kcl/types/PolygonData) | Data for drawing a polygon | Yes | | `data` | [`PolygonData`](/docs/kcl/types/PolygonData) | Data for drawing a polygon | Yes |
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes | | `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No | | `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Take a starting value. Then, for each element of an array, calculate the next va
using the previous value and the element. using the previous value and the element.
```js ```js
reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue reduce(array: [KclValue], start: KclValue, reduceFn: FunctionSource) -> KclValue
``` ```
@ -19,7 +19,7 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes | | `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `start` | [`KclValue`](/docs/kcl/types/KclValue) | Any KCL value. | Yes | | `start` | [`KclValue`](/docs/kcl/types/KclValue) | Any KCL value. | Yes |
| `reduce_fn` | `FunctionParam` | | Yes | | `reduceFn` | `FunctionSource` | | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Remove volume from a 3-dimensional shape such that a wall of the
provided thickness remains, taking volume starting at the provided face, leaving it open in that direction. provided thickness remains, taking volume starting at the provided face, leaving it open in that direction.
```js ```js
shell(solid_set: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet shell(solidSet: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet
``` ```
@ -17,7 +17,7 @@ shell(solid_set: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid (or solids) to shell out | Yes | | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid (or solids) to shell out | Yes |
| `thickness` | `number` | The thickness of the shell | Yes | | `thickness` | `number` | The thickness of the shell | Yes |
| `faces` | [`[FaceTag]`](/docs/kcl/types/FaceTag) | The faces you want removed | Yes | | `faces` | [`[FaceTag]`](/docs/kcl/types/FaceTag) | The faces you want removed | Yes |

View File

@ -9,7 +9,7 @@ Start a new profile at a given point.
```js ```js
startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: TagDeclarator) -> Sketch startProfileAt(to: [number], sketchSurface: SketchSurface, tag?: TagDeclarator) -> Sketch
``` ```
@ -18,7 +18,7 @@ startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: TagDeclarator)
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `to` | `[number]` | | Yes | | `to` | `[number]` | | Yes |
| `sketch_surface` | [`SketchSurface`](/docs/kcl/types/SketchSurface) | A sketch type. | Yes | | `sketchSurface` | [`SketchSurface`](/docs/kcl/types/SketchSurface) | A sketch type. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No | | `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
### Returns ### Returns

View File

@ -38863,7 +38863,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "solid_set", "name": "solidSet",
"type": "SolidSet", "type": "SolidSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -70936,7 +70936,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "sketch_surface_or_group", "name": "sketchSurfaceOrGroup",
"type": "SketchOrSurface", "type": "SketchOrSurface",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -75887,7 +75887,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "sketch_surface_or_group", "name": "sketchSurfaceOrGroup",
"type": "SketchOrSurface", "type": "SketchOrSurface",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -85796,7 +85796,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "sketch_set", "name": "sketchSet",
"type": "SketchSet", "type": "SketchSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -104094,7 +104094,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "angle_start", "name": "angleStart",
"type": "number", "type": "number",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -110158,7 +110158,7 @@
"keywordArguments": false, "keywordArguments": false,
"args": [ "args": [
{ {
"name": "hole_sketch", "name": "holeSketch",
"type": "SketchSet", "type": "SketchSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -118587,7 +118587,7 @@
"keywordArguments": false, "keywordArguments": false,
"args": [ "args": [
{ {
"name": "file_path", "name": "filePath",
"type": "String", "type": "String",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -124304,7 +124304,7 @@
"labelRequired": false "labelRequired": false
}, },
{ {
"name": "end_absolute", "name": "endAbsolute",
"type": "[number]", "type": "[number]",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -132449,7 +132449,7 @@
"labelRequired": false "labelRequired": false
}, },
{ {
"name": "v_degree", "name": "vDegree",
"type": "NonZeroU32", "type": "NonZeroU32",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -134051,7 +134051,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "bez_approximate_rational", "name": "bezApproximateRational",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -135651,7 +135651,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "base_curve_index", "name": "baseCurveIndex",
"type": "integer", "type": "integer",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -141138,14 +141138,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -143279,28 +143271,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -143331,11 +143301,11 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "map_fn", "name": "mapFn",
"type": "FunctionParam", "type": "FunctionSource",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "FunctionParam", "title": "FunctionSource",
"type": "null", "type": "null",
"definitions": { "definitions": {
"KclValue": { "KclValue": {
@ -143756,14 +143726,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -145897,28 +145859,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -146378,14 +146318,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -148519,28 +148451,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -149897,7 +149807,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "sketch_set", "name": "sketchSet",
"type": "SketchSet", "type": "SketchSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -153793,7 +153703,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "sketch_set", "name": "sketchSet",
"type": "SketchSet", "type": "SketchSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -158703,7 +158613,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "arc_degrees", "name": "arcDegrees",
"type": "number", "type": "number",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -160304,7 +160214,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "rotate_duplicates", "name": "rotateDuplicates",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -161904,7 +161814,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "use_original", "name": "useOriginal",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -165120,7 +165030,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "solid_set", "name": "solidSet",
"type": "SolidSet", "type": "SolidSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -171640,7 +171550,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "arc_degrees", "name": "arcDegrees",
"type": "number", "type": "number",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -173241,7 +173151,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "rotate_duplicates", "name": "rotateDuplicates",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -174841,7 +174751,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "use_original", "name": "useOriginal",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -178057,7 +177967,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "sketch_set", "name": "sketchSet",
"type": "SketchSet", "type": "SketchSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -184568,7 +184478,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "use_original", "name": "useOriginal",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -187784,7 +187694,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "solid_set", "name": "solidSet",
"type": "SolidSet", "type": "SolidSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -194299,7 +194209,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "use_original", "name": "useOriginal",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -197515,7 +197425,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "solid_set", "name": "solidSet",
"type": "SolidSet", "type": "SolidSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -200824,10 +200734,10 @@
}, },
{ {
"name": "transform", "name": "transform",
"type": "FunctionParam", "type": "FunctionSource",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "FunctionParam", "title": "FunctionSource",
"type": "null", "type": "null",
"definitions": { "definitions": {
"ArtifactId": { "ArtifactId": {
@ -202423,7 +202333,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "use_original", "name": "useOriginal",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -205644,7 +205554,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "sketch_set", "name": "sketchSet",
"type": "SketchSet", "type": "SketchSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -208949,10 +208859,10 @@
}, },
{ {
"name": "transform", "name": "transform",
"type": "FunctionParam", "type": "FunctionSource",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "FunctionParam", "title": "FunctionSource",
"type": "null", "type": "null",
"definitions": { "definitions": {
"Path": { "Path": {
@ -210548,7 +210458,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "use_original", "name": "useOriginal",
"type": "bool", "type": "bool",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -214272,7 +214182,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "sketch_surface_or_group", "name": "sketchSurfaceOrGroup",
"type": "SketchOrSurface", "type": "SketchOrSurface",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -219594,14 +219504,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -221735,28 +221637,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -222211,14 +222091,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -223102,14 +222974,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -224863,28 +224727,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -230504,14 +230346,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -232645,28 +232479,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -233119,14 +232931,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -233630,14 +233434,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -235771,28 +235567,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -236246,14 +236020,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -237137,14 +236903,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -238898,28 +238656,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -239391,14 +239127,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -241532,28 +241260,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -242006,14 +241712,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -242517,14 +242215,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -244658,28 +244348,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -244710,11 +244378,11 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "reduce_fn", "name": "reduceFn",
"type": "FunctionParam", "type": "FunctionSource",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "FunctionParam", "title": "FunctionSource",
"type": "null", "type": "null",
"definitions": { "definitions": {
"KclValue": { "KclValue": {
@ -245135,14 +244803,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -247276,28 +246936,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -247751,14 +247389,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -248642,14 +248272,6 @@
"Function" "Function"
] ]
}, },
"memory": {
"allOf": [
{
"$ref": "#/components/schemas/EnvironmentRef"
}
],
"nullable": true
},
"__meta": { "__meta": {
"type": "array", "type": "array",
"items": { "items": {
@ -250403,28 +250025,6 @@
} }
} }
}, },
"EnvironmentRef": {
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
"type": "array",
"items": [
{
"type": "integer",
"format": "uint",
"minimum": 0.0
},
{
"$ref": "#/components/schemas/SnapshotRef"
}
],
"maxItems": 2,
"minItems": 2
},
"SnapshotRef": {
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"ModuleId": { "ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.", "description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer", "type": "integer",
@ -264274,7 +263874,7 @@
"keywordArguments": true, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "solid_set", "name": "solidSet",
"type": "SolidSet", "type": "SolidSet",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -271003,7 +270603,7 @@
"labelRequired": true "labelRequired": true
}, },
{ {
"name": "sketch_surface", "name": "sketchSurface",
"type": "SketchSurface", "type": "SketchSurface",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",

View File

@ -295,7 +295,6 @@ Data for an imported geometry.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Function`| | No | | `type` |enum: `Function`| | No |
| `memory` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -219,7 +219,11 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
} }
) )
test('Can extrude from the command bar', async ({ page, homePage }) => { test('Can extrude from the command bar', async ({
page,
homePage,
cmdBar,
}) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -254,7 +258,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
await expect(cmdSearchBar).toBeVisible() await expect(cmdSearchBar).toBeVisible()
// Search for extrude command and choose it // Search for extrude command and choose it
await page.getByRole('option', { name: 'Extrude' }).click() await cmdBar.cmdOptions.getByText('Extrude').click()
// Assert that we're on the selection step // Assert that we're on the selection step
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled() await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()

View File

@ -59,18 +59,25 @@ export interface Fixtures {
homePage: HomePageFixture homePage: HomePageFixture
} }
export class AuthenticatedTronApp { export class AuthenticatedTronApp {
public readonly _page: Page public originalPage: Page
public page: Page public page: Page
public browserContext: BrowserContext
public context: BrowserContext public context: BrowserContext
public readonly testInfo: TestInfo public readonly testInfo: TestInfo
public electronApp: ElectronApplication | undefined public electronApp: ElectronApplication | undefined
public readonly viewPortSize = { width: 1200, height: 500 } public readonly viewPortSize = { width: 1200, height: 500 }
public dir: string = '' public dir: string = ''
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { constructor(
this._page = page browserContext: BrowserContext,
this.page = page originalPage: Page,
this.context = context testInfo: TestInfo
) {
this.page = originalPage
this.originalPage = originalPage
this.browserContext = browserContext
// Will be overwritten in the initializer
this.context = browserContext
this.testInfo = testInfo this.testInfo = testInfo
} }
async initialise( async initialise(
@ -86,9 +93,17 @@ export class AuthenticatedTronApp {
folderSetupFn: arg.folderSetupFn, folderSetupFn: arg.folderSetupFn,
cleanProjectDir: arg.cleanProjectDir, cleanProjectDir: arg.cleanProjectDir,
appSettings: arg.appSettings, appSettings: arg.appSettings,
viewport: this.viewPortSize,
}) })
this.page = page this.page = page
// These assignments "fix" some brokenness in the Playwright Workbench when
// running against electron applications.
// The timeline is still broken but failure screenshots work again.
this.context = context this.context = context
// TODO: try to get this to work again for screenshots, but it messed with test ends when enabled
// Object.assign(this.browserContext, this.context)
this.electronApp = electronApp this.electronApp = electronApp
this.dir = dir this.dir = dir

View File

@ -1064,7 +1064,7 @@ openSketch = startSketchOn('XY')
0 0
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await scene.expectPixelColor([50, 51, 96], testPoint, 15) await scene.expectPixelColor([50, 51, 96], testPoint, 15)
}) })
}) })
@ -1125,11 +1125,53 @@ openSketch = startSketchOn('XY')
await scene.expectPixelColor([250, 250, 250], testPoint, 15) await scene.expectPixelColor([250, 250, 250], testPoint, 15)
}) })
await test.step('Delete offset plane via feature tree selection', async () => { await test.step(`Edit helix through the feature tree`, async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.dblclick()
const initialInput = '5'
const newInput = '50'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'length',
currentArgValue: initialInput,
headerArguments: {
AngleStart: '360',
Axis: 'X',
CounterClockWise: '',
Length: initialInput,
Radius: '5',
Revolutions: '1',
},
highlightedHeaderArg: 'length',
})
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '360',
Axis: 'X',
CounterClockWise: '',
Length: newInput,
Radius: '5',
Revolutions: '1',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await toolbar.closeFeatureTreePane()
await editor.openPane()
await editor.expectEditor.toContain('length = ' + newInput)
})
await test.step('Delete helix via feature tree selection', async () => {
await editor.closePane() await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0) const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
// Red plane is back // Red plane is back
await scene.expectPixelColor([96, 52, 52], testPoint, 15) await scene.expectPixelColor([96, 52, 52], testPoint, 15)
}) })
@ -1221,7 +1263,7 @@ openSketch = startSketchOn('XY')
await editor.closePane() await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0) const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await scene.expectPixelColor([254, 254, 254], testPoint, 15) await scene.expectPixelColor([254, 254, 254], testPoint, 15)
}) })
}) })
@ -1264,7 +1306,7 @@ loft001 = loft([sketch001, sketch002])
await expect(page.locator('.cm-activeLine')).toHaveText(` await expect(page.locator('.cm-activeLine')).toHaveText(`
|> circle({ center = [0, 0], radius = 30 }, %) |> circle({ center = [0, 0], radius = 30 }, %)
`) `)
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
// Check for sketch 1 // Check for sketch 1
await scene.expectPixelColor([254, 254, 254], testPoint, 15) await scene.expectPixelColor([254, 254, 254], testPoint, 15)
}) })
@ -1275,7 +1317,7 @@ loft001 = loft([sketch001, sketch002])
await expect(page.locator('.cm-activeLine')).toHaveText(` await expect(page.locator('.cm-activeLine')).toHaveText(`
|> circle({ center = [0, 0], radius = 20 }, %) |> circle({ center = [0, 0], radius = 20 }, %)
`) `)
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
// Check for plane001 // Check for plane001
await scene.expectPixelColor([228, 228, 228], testPoint, 15) await scene.expectPixelColor([228, 228, 228], testPoint, 15)
}) })
@ -1286,7 +1328,7 @@ loft001 = loft([sketch001, sketch002])
await expect(page.locator('.cm-activeLine')).toHaveText(` await expect(page.locator('.cm-activeLine')).toHaveText(`
plane001 = offsetPlane('XZ', offset = 50) plane001 = offsetPlane('XZ', offset = 50)
`) `)
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
// Check for sketch 1 // Check for sketch 1
await scene.expectPixelColor([254, 254, 254], testPoint, 15) await scene.expectPixelColor([254, 254, 254], testPoint, 15)
}) })
@ -1380,7 +1422,7 @@ sketch002 = startSketchOn('XZ')
await page.waitForTimeout(500) await page.waitForTimeout(500)
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0) const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0)
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await page.waitForTimeout(500)
await toolbar.closePane('feature-tree') await toolbar.closePane('feature-tree')
await scene.expectPixelColor([53, 53, 53], testPoint, 15) await scene.expectPixelColor([53, 53, 53], testPoint, 15)
@ -1686,7 +1728,7 @@ extrude001 = extrude(sketch001, length = -12)
1 1
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await page.waitForTimeout(500)
await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted
await editor.expectEditor.not.toContain(secondFilletDeclaration) await editor.expectEditor.not.toContain(secondFilletDeclaration)
@ -1788,7 +1830,7 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
0 0
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await page.waitForTimeout(500)
}) })
await test.step('Verify piped fillet is deleted but other fillets are not (in the editor)', async () => { await test.step('Verify piped fillet is deleted but other fillets are not (in the editor)', async () => {
@ -1818,7 +1860,7 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
1 1
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await page.waitForTimeout(500)
}) })
await test.step('Verify non-piped fillet is deleted but other two fillets are not (in the editor)', async () => { await test.step('Verify non-piped fillet is deleted but other two fillets are not (in the editor)', async () => {
@ -2057,7 +2099,7 @@ extrude001 = extrude(sketch001, length = -12)
1 1
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await page.waitForTimeout(500)
await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted
await scene.expectPixelColor(chamferColor, firstEdgeLocation, 15) // stayed await scene.expectPixelColor(chamferColor, firstEdgeLocation, 15) // stayed
@ -2160,7 +2202,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
0 0
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await page.waitForTimeout(500)
}) })
await test.step('Verify piped chamfer is deleted but other chamfers are not (in the editor)', async () => { await test.step('Verify piped chamfer is deleted but other chamfers are not (in the editor)', async () => {
@ -2192,7 +2234,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
1 1
) )
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await page.waitForTimeout(500) await page.waitForTimeout(500)
}) })
await test.step('Verify non-piped chamfer is deleted but other two chamfers are not (in the editor)', async () => { await test.step('Verify non-piped chamfer is deleted but other two chamfers are not (in the editor)', async () => {
@ -2396,7 +2438,7 @@ extrude001 = extrude(sketch001, length = 40)
await editor.closePane() await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0) const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await scene.expectPixelColor([99, 99, 99], testPoint, 15) await scene.expectPixelColor([99, 99, 99], testPoint, 15)
}) })
}) })
@ -2535,7 +2577,7 @@ profile001 = startProfileAt([-20, 20], sketch001)
const deleteOperation = async (operationButton: Locator) => { const deleteOperation = async (operationButton: Locator) => {
if (shouldUseKeyboard) { if (shouldUseKeyboard) {
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
} else { } else {
await operationButton.click({ button: 'right' }) await operationButton.click({ button: 'right' })
const editButton = page.getByTestId('context-menu-delete') const editButton = page.getByTestId('context-menu-delete')
@ -2796,4 +2838,107 @@ radius = 8.69
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy() expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
}) })
}) })
test(`Set appearance`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
profile001 = circle({
center = [0, 0],
radius = 100
}, sketch001)
extrude001 = extrude(profile001, length = 100)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 500, y: 250 }
const initialColor: [number, number, number] = [135, 135, 135]
await test.step(`Confirm extrude exists with default appearance`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor(initialColor, testPoint, 15)
})
async function setApperanceAndCheck(
option: string,
hex: string,
shapeColor: [number, number, number]
) {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Extrude',
0
)
await operationButton.click({ button: 'right' })
const menuButton = page.getByTestId('context-menu-set-appearance')
await menuButton.click()
await cmdBar.expectState({
commandName: 'Appearance',
currentArgKey: 'color',
currentArgValue: '',
headerArguments: {
Color: '',
},
highlightedHeaderArg: 'color',
stage: 'arguments',
})
const item = page.getByText(option, { exact: true })
await item.click()
await cmdBar.expectState({
commandName: 'Appearance',
headerArguments: {
Color: hex,
},
stage: 'review',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await scene.expectPixelColor(shapeColor, testPoint, 40)
await toolbar.openPane('code')
if (hex === 'default') {
const anyAppearanceDeclaration = `|> appearance(`
await editor.expectEditor.not.toContain(anyAppearanceDeclaration)
} else {
const declaration = `|> appearance(%, color = '${hex}')`
await editor.expectEditor.toContain(declaration)
// TODO: fix selection range after appearance update
// await editor.expectState({
// diagnostics: [],
// activeLines: [declaration],
// highlightedCode: '',
// })
}
await toolbar.closePane('code')
}
await test.step(`Go through the Set Appearance flow for all options`, async () => {
await setApperanceAndCheck('Red', '#FF0000', [180, 0, 0])
await setApperanceAndCheck('Green', '#00FF00', [0, 180, 0])
await setApperanceAndCheck('Blue', '#0000FF', [0, 0, 180])
await setApperanceAndCheck('Turquoise', '#00FFFF', [0, 180, 180])
await setApperanceAndCheck('Purple', '#FF00FF', [180, 0, 180])
await setApperanceAndCheck('Yellow', '#FFFF00', [180, 180, 0])
await setApperanceAndCheck('Black', '#000000', [0, 0, 0])
await setApperanceAndCheck('Dark Grey', '#080808', [10, 10, 10])
await setApperanceAndCheck('Light Grey', '#D3D3D3', [190, 190, 190])
await setApperanceAndCheck('White', '#FFFFFF', [200, 200, 200])
await setApperanceAndCheck(
'Default (clear appearance)',
'default',
initialColor
)
})
})
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 129 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: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -937,11 +937,16 @@ export async function setupElectron({
testInfo, testInfo,
cleanProjectDir = true, cleanProjectDir = true,
appSettings, appSettings,
viewport,
}: { }: {
testInfo: TestInfo testInfo: TestInfo
folderSetupFn?: (projectDirName: string) => Promise<void> folderSetupFn?: (projectDirName: string) => Promise<void>
cleanProjectDir?: boolean cleanProjectDir?: boolean
appSettings?: Partial<SaveSettingsPayload> appSettings?: Partial<SaveSettingsPayload>
viewport: {
width: number
height: number
}
}): Promise<{ }): Promise<{
electronApp: ElectronApplication electronApp: ElectronApplication
context: BrowserContext context: BrowserContext
@ -972,6 +977,14 @@ export async function setupElectron({
...(process.env.ELECTRON_OVERRIDE_DIST_PATH ...(process.env.ELECTRON_OVERRIDE_DIST_PATH
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' } ? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
: {}), : {}),
...(process.env.PLAYWRIGHT_RECORD_VIDEO
? {
recordVideo: {
dir: testInfo.snapshotPath(),
size: viewport,
},
}
: {}),
} }
// Do this once and then reuse window on subsequent calls. // Do this once and then reuse window on subsequent calls.

View File

@ -94,6 +94,8 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
await bakeInRetries(async () => { await bakeInRetries(async () => {
await page.mouse.move(700, 200) await page.mouse.move(700, 200)
await page.mouse.down({ button: 'right' }) await page.mouse.down({ button: 'right' })
await page.waitForTimeout(100)
const appLogoBBox = await page.getByTestId('app-logo').boundingBox() const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
expect(appLogoBBox).not.toBeNull() expect(appLogoBBox).not.toBeNull()
if (!appLogoBBox) throw new Error('app logo not found') if (!appLogoBBox) throw new Error('app logo not found')
@ -101,7 +103,9 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
appLogoBBox.x + appLogoBBox.width / 2, appLogoBBox.x + appLogoBBox.width / 2,
appLogoBBox.y + appLogoBBox.height / 2 appLogoBBox.y + appLogoBBox.height / 2
) )
await page.waitForTimeout(100)
await page.mouse.move(600, 303) await page.mouse.move(600, 303)
await page.waitForTimeout(100)
await page.mouse.up({ button: 'right' }) await page.mouse.up({ button: 'right' })
}, [4, -10.5, -120]) }, [4, -10.5, -120])

View File

@ -382,7 +382,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
'|> line(end = [0, -pipeLength])' '|> line(end = [0, -pipeLength])'
) )
await u.clearCommandLogs() await u.clearCommandLogs()
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000) await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await page.waitForTimeout(200) await page.waitForTimeout(200)
@ -439,7 +439,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
'|> startProfileAt([23.24, 136.52], %)' '|> startProfileAt([23.24, 136.52], %)'
) )
await u.clearCommandLogs() await u.clearCommandLogs()
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000) await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await page.waitForTimeout(200) await page.waitForTimeout(200)
await expect(u.codeLocator).not.toContainText(`sketch005 = startSketchOn({`) await expect(u.codeLocator).not.toContainText(`sketch005 = startSketchOn({`)
@ -453,7 +453,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
' |> line(end = [20.91, -28.61])' ' |> line(end = [20.91, -28.61])'
) )
await u.clearCommandLogs() await u.clearCommandLogs()
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000) await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await page.waitForTimeout(200) await page.waitForTimeout(200)
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet) await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
@ -518,7 +518,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
'|> line(end = [170.36, -121.61], tag = $seg01)' '|> line(end = [170.36, -121.61], tag = $seg01)'
) )
await u.clearCommandLogs() await u.clearCommandLogs()
await page.keyboard.press('Backspace') await page.keyboard.press('Delete')
await expect(page.getByText('Unable to delete selection')).toBeVisible() await expect(page.getByText('Unable to delete selection')).toBeVisible()
} }

View File

@ -68,13 +68,6 @@ type PWFunction = (
let firstUrl = '' let firstUrl = ''
// The below error is due to the extreme type spaghetti going on. playwright/
// types/test.d.ts does not export 2 functions (below is one of them) but tsc
// is trying to use a interface name it can't see.
// e2e/playwright/zoo-test.ts:64:14 - error TS4023: Exported variable 'test' has
// or is using name 'TestFunction' from external module
// "/home/lee/Code/Zoo/modeling-app/dirty2/node_modules/playwright/types/test"
// but cannot be named.
export const test = ( export const test = (
desc: string, desc: string,
objOrFn: PWFunction | TestDetails, objOrFn: PWFunction | TestDetails,

View File

@ -14,7 +14,7 @@
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.17.0", "@codemirror/autocomplete": "^6.17.0",
"@codemirror/commands": "^6.6.0", "@codemirror/commands": "^6.6.0",
"@codemirror/language": "^6.10.3", "@codemirror/language": "^6.10.8",
"@codemirror/lint": "^6.8.4", "@codemirror/lint": "^6.8.4",
"@codemirror/search": "^6.5.6", "@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
@ -40,7 +40,7 @@
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"diff": "^7.0.0", "diff": "^7.0.0",
"electron-updater": "^6.5.0", "electron-updater": "^6.6.0",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"html2canvas-pro": "^1.5.8", "html2canvas-pro": "^1.5.8",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
@ -69,10 +69,15 @@
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"scripts": { "scripts": {
"install:rust": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y",
"install:rust:windows": "winget install Microsoft.VisualStudio.2022.Community --silent --override \"--wait --quiet --add ProductLang En-us --add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended\" && winget install Rustlang.Rustup",
"install:wasm-pack:sh": ". $HOME/.cargo/env && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y",
"install:wasm-pack:cargo": "cargo install wasm-pack",
"install:tools:windows": "winget install jqlang.jq MikeFarah.yq GitHub.cli",
"start": "vite --port=3000 --host=0.0.0.0", "start": "vite --port=3000 --host=0.0.0.0",
"start:prod": "vite preview --port=3000", "start:prod": "vite preview --port=3000",
"serve": "vite serve --port=3000", "serve": "vite serve --port=3000",
"build": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source \"$HOME/.cargo/env\" && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y && yarn build:wasm && vite build", "build": "yarn install:rust && . $HOME/.cargo/env && yarn install:wasm-pack:sh && yarn build:wasm && vite build",
"build:local": "vite build", "build:local": "vite build",
"build:both": "vite build", "build:both": "vite build",
"build:both:local": "yarn build:wasm && vite build", "build:both:local": "yarn build:wasm && vite build",
@ -84,11 +89,13 @@
"simpleserver:stop": "kill-port 3000", "simpleserver:stop": "kill-port 3000",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages", "fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages", "fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh", "fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
"fetch:wasm:windows": "./scripts/get-latest-wasm-bundle.ps1",
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/next/manifest.json", "fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/next/manifest.json",
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt", "build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt", "build:wasm:nocopy": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings",
"build:wasm": "yarn build:wasm:nocopy && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm:windows": "yarn install:wasm-pack:cargo && yarn build:wasm:nocopy && copy src\\wasm-lib\\pkg\\wasm_lib_bg.wasm public && yarn fmt",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"", "remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings", "wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src", "lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
@ -96,6 +103,7 @@
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", "files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh", "files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh", "files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
"files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1",
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh", "files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly", "files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild", "postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
@ -198,7 +206,7 @@
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"setimmediate": "^1.0.5", "setimmediate": "^1.0.5",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.17",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.23.0", "typescript-eslint": "^8.23.0",
@ -207,7 +215,6 @@
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.1", "vitest": "^1.6.1",
"vitest-webgl-canvas-mock": "^1.1.0", "vitest-webgl-canvas-mock": "^1.1.0",
"wasm-pack": "^0.13.1",
"ws": "^8.17.0", "ws": "^8.17.0",
"yarn": "^1.22.22" "yarn": "^1.22.22"
}, },

View File

@ -0,0 +1,20 @@
$VERSION=$(Get-Date -Format "yy.M.d")
$COMMIT=$(git rev-parse --short HEAD)
$PRODUCT_NAME="Zoo Modeling App (Nightly)"
# package.json
yq -i '.version = env(VERSION)' -p=json -o=json package.json
yq -i '.productName = env(PRODUCT_NAME)' -p=json -o=json package.json
yq -i '.name = "zoo-modeling-app-nightly"' -p=json -o=json package.json
# electron-builder.yml
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/nightly"' electron-builder.yml
yq -i '.appId = "dev.zoo.modeling-app-nightly"' electron-builder.yml
yq -i '.nsis.include = "./scripts/installer-nightly.nsh"' electron-builder.yml
# Release notes
echo "Nightly build $VERSION (commit $COMMIT)" > release-notes.md
# icons
cp assets/icon-nightly.png assets/icon.png
cp assets/icon-nightly.ico assets/icon.ico

View File

@ -0,0 +1,13 @@
$REPO_OWNER = "KittyCAD"
$REPO_NAME = "modeling-app"
$WORKFLOW_NAME = "build-and-store-wasm.yml"
$ARTIFACT_NAME = "wasm-bundle"
# Fetch the latest completed workflow run ID for the specified workflow
$RUN_ID = (gh run list -w $WORKFLOW_NAME --repo $REPO_OWNER/$REPO_NAME --limit 1 --json databaseId -s completed --jq 'first | .databaseId') -join [Environment]::NewLine
$PKG_PATH="./src/wasm-lib/pkg/"
rm -r $PKG_PATH/*
gh run download $RUN_ID --repo $REPO_OWNER/$REPO_NAME --name $ARTIFACT_NAME --dir $PKG_PATH
cp $PKG_PATH/wasm_lib_bg.wasm public
echo "latest wasm copied to public folder"

View File

View File

@ -133,9 +133,11 @@ function DisplayObj({
}} }}
onClick={(e) => { onClick={(e) => {
const range = topLevelRange(obj?.start || 0, obj.end || 0) const range = topLevelRange(obj?.start || 0, obj.end || 0)
const idInfo = codeToIdSelections([ const idInfo = codeToIdSelections(
{ codeRef: codeRefFromRange(range, kclManager.ast) }, [{ codeRef: codeRefFromRange(range, kclManager.ast) }],
])[0] engineCommandManager.artifactGraph,
engineCommandManager.artifactIndex
)[0]
const artifact = engineCommandManager.artifactGraph.get( const artifact = engineCommandManager.artifactGraph.get(
idInfo?.id || '' idInfo?.id || ''
) )

View File

@ -110,6 +110,7 @@ import { commandBarActor } from 'machines/commandBarMachine'
import { useToken } from 'machines/appMachine' import { useToken } from 'machines/appMachine'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils' import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { useSettings } from 'machines/appMachine' import { useSettings } from 'machines/appMachine'
import { isDesktop } from 'lib/isDesktop'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -802,7 +803,7 @@ export const ModelingMachineProvider = ({
engineCommandManager.artifactGraph engineCommandManager.artifactGraph
) )
if (err(plane)) return Promise.reject(plane) if (err(plane)) return Promise.reject(plane)
// if the user selected a segment, make sure we enter the right sketch as there can be multiple on a plan // if the user selected a segment, make sure we enter the right sketch as there can be multiple on a plane
// but still works if the user selected a plane/face by defaulting to the first path // but still works if the user selected a plane/face by defaulting to the first path
const mainPath = const mainPath =
artifact?.type === 'segment' || artifact?.type === 'solid2d' artifact?.type === 'segment' || artifact?.type === 'solid2d'
@ -1717,8 +1718,12 @@ export const ModelingMachineProvider = ({
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
}, [allowOrbitInSketchMode]) }, [allowOrbitInSketchMode])
// Allow using the delete key to delete solids // Allow using the delete key to delete solids. Backspace only on macOS as Windows and Linux have dedicated Delete
useHotkeys(['backspace', 'delete', 'del'], () => { const deleteKeys =
isDesktop() && window.electron.os.isMac
? ['backspace', 'delete', 'del']
: ['delete', 'del']
useHotkeys(deleteKeys, () => {
modelingSend({ type: 'Delete selection' }) modelingSend({ type: 'Delete selection' })
}) })

View File

@ -324,6 +324,18 @@ const OperationItem = (props: {
} }
} }
function enterAppearanceFlow() {
if (props.item.type === 'StdLibCall') {
props.send({
type: 'enterAppearanceFlow',
data: {
targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
currentOperation: props.item,
},
})
}
}
function deleteOperation() { function deleteOperation() {
if ( if (
props.item.type === 'StdLibCall' || props.item.type === 'StdLibCall' ||
@ -380,6 +392,13 @@ const OperationItem = (props: {
: []), : []),
...(props.item.type === 'StdLibCall' ...(props.item.type === 'StdLibCall'
? [ ? [
<ContextMenuItem
disabled={!stdLibMap[props.item.name]?.supportsAppearance}
onClick={enterAppearanceFlow}
data-testid="context-menu-set-appearance"
>
Set appearance
</ContextMenuItem>,
<ContextMenuItem <ContextMenuItem
disabled={!stdLibMap[props.item.name]?.prepareToEdit} disabled={!stdLibMap[props.item.name]?.prepareToEdit}
onClick={enterEditFlow} onClick={enterEditFlow}

View File

@ -33,7 +33,7 @@ describe('processMemory', () => {
const output = processMemory(execState.variables) const output = processMemory(execState.variables)
expect(output.myVar).toEqual(5) expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3) expect(output.otherVar).toEqual(3)
expect(output.myFn).toEqual('__function(a)__') expect(output.myFn).toEqual('__function__')
expect(output.theExtrude).toEqual([ expect(output.theExtrude).toEqual([
{ {
type: 'extrudePlane', type: 'extrudePlane',

View File

@ -107,9 +107,7 @@ export const processMemory = (variables: VariableMap) => {
} }
//@ts-ignore //@ts-ignore
} else if (val.type === 'Function') { } else if (val.type === 'Function') {
processedMemory[key] = `__function(${(val as any)?.expression?.params processedMemory[key] = `__function__`
?.map?.(({ identifier }: any) => identifier?.name || '')
.join(', ')})__`
} }
} }
return processedMemory return processedMemory

View File

@ -374,6 +374,7 @@ export default class EditorManager {
selectionRanges: this._selectionRanges, selectionRanges: this._selectionRanges,
isShiftDown: this._isShiftDown, isShiftDown: this._isShiftDown,
ast: kclManager.ast, ast: kclManager.ast,
artifactGraph: engineCommandManager.artifactGraph,
}) })
if (!eventInfo) { if (!eventInfo) {

View File

@ -285,7 +285,11 @@ export function complilationErrorsToDiagnostics(
name: suggestion.title, name: suggestion.title,
apply: (view: EditorView, from: number, to: number) => { apply: (view: EditorView, from: number, to: number) => {
view.dispatch({ view.dispatch({
changes: { from, to, insert: suggestion.insert }, changes: {
from: suggestion.source_range[0],
to: suggestion.source_range[1],
insert: suggestion.insert,
},
}) })
}, },
}, },

View File

@ -717,6 +717,8 @@ export function addHelix({
radius, radius,
axis, axis,
length, length,
insertIndex,
variableName,
}: { }: {
node: Node<Program> node: Node<Program>
revolutions: Expr revolutions: Expr
@ -725,9 +727,12 @@ export function addHelix({
radius: Expr radius: Expr
axis: string axis: string
length: Expr length: Expr
insertIndex?: number
variableName?: string
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } { }): { modifiedAst: Node<Program>; pathToNode: PathToNode } {
const modifiedAst = structuredClone(node) const modifiedAst = structuredClone(node)
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.HELIX) const name =
variableName ?? findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.HELIX)
const variable = createVariableDeclaration( const variable = createVariableDeclaration(
name, name,
createCallExpressionStdLibKw( createCallExpressionStdLibKw(
@ -744,12 +749,20 @@ export function addHelix({
) )
) )
// TODO: figure out smart insertion than just appending at the end const insertAt =
insertIndex !== undefined
? insertIndex
: modifiedAst.body.length
? modifiedAst.body.length
: 0
modifiedAst.body.length
? modifiedAst.body.splice(insertAt, 0, variable)
: modifiedAst.body.push(variable)
const argIndex = 0 const argIndex = 0
modifiedAst.body.push(variable)
const pathToNode: PathToNode = [ const pathToNode: PathToNode = [
['body', ''], ['body', ''],
[modifiedAst.body.length - 1, 'index'], [insertAt, 'index'],
['declaration', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpressionKw'], ['arguments', 'CallExpressionKw'],

View File

@ -397,10 +397,10 @@ export function getEdgeTagCall(
return tagCall return tagCall
} }
function locateExtrudeDeclarator( export function locateExtrudeDeclarator(
node: Program, node: Program,
pathToExtrudeNode: PathToNode pathToExtrudeNode: PathToNode
): { extrudeDeclarator: VariableDeclarator } | Error { ): { extrudeDeclarator: VariableDeclarator; shallowPath: PathToNode } | Error {
const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>( const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>(
node, node,
pathToExtrudeNode, pathToExtrudeNode,
@ -427,7 +427,7 @@ function locateExtrudeDeclarator(
return new Error('Extrude must be a PipeExpression or CallExpression') return new Error('Extrude must be a PipeExpression or CallExpression')
} }
return { extrudeDeclarator } return { extrudeDeclarator, shallowPath: nodeOfExtrudeCall.shallowPath }
} }
function getPathToNodeOfEdgeTreatmentLiteral( function getPathToNodeOfEdgeTreatmentLiteral(

View File

@ -0,0 +1,70 @@
import { PathToNode, Program } from 'lang/wasm'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { locateExtrudeDeclarator } from './addEdgeTreatment'
import { err } from 'lib/trap'
import {
createCallExpressionStdLibKw,
createLabeledArg,
createLiteral,
createPipeExpression,
} from 'lang/modifyAst'
import { createPipeSubstitution } from 'lang/modifyAst'
import { COMMAND_APPEARANCE_COLOR_DEFAULT } from 'lib/commandBarConfigs/modelingCommandConfig'
export function setAppearance({
ast,
nodeToEdit,
color,
}: {
ast: Node<Program>
nodeToEdit: PathToNode
color: string
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
const modifiedAst = structuredClone(ast)
// Locate the call (not necessarily an extrude here)
const result = locateExtrudeDeclarator(modifiedAst, nodeToEdit)
if (err(result)) {
return result
}
const declarator = result.extrudeDeclarator
const call = createCallExpressionStdLibKw(
'appearance',
createPipeSubstitution(),
[createLabeledArg('color', createLiteral(color))]
)
// Modify the expression
if (
declarator.init.type === 'CallExpression' ||
declarator.init.type === 'CallExpressionKw'
) {
// 1. case when no appearance exists, mutate in place
declarator.init = createPipeExpression([declarator.init, call])
} else if (declarator.init.type === 'PipeExpression') {
// 2. case when appearance exists or extrude in sketch pipe
const existingIndex = declarator.init.body.findIndex(
(v) =>
v.type === 'CallExpressionKw' &&
v.callee.type === 'Identifier' &&
v.callee.name === 'appearance'
)
if (existingIndex > -1) {
if (color === COMMAND_APPEARANCE_COLOR_DEFAULT) {
// Special case of unsetting the appearance aka deleting the node
declarator.init.body.splice(existingIndex, 1)
} else {
declarator.init.body[existingIndex] = call
}
} else {
declarator.init.body.push(call)
}
} else {
return new Error('Unsupported operation type.')
}
return {
modifiedAst,
pathToNode: result.shallowPath,
}
}

View File

@ -31,6 +31,8 @@ import { markOnce } from 'lib/performance'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import { DefaultPlaneStr } from 'lib/planes' import { DefaultPlaneStr } from 'lib/planes'
import { defaultPlaneStrToKey } from 'lib/planes' import { defaultPlaneStrToKey } from 'lib/planes'
import { buildArtifactIndex } from 'lib/artifactIndex'
import { ArtifactIndex } from 'lib/artifactIndex'
// TODO(paultag): This ought to be tweakable. // TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 5_000 const pingIntervalMs = 5_000
@ -240,6 +242,20 @@ export enum EngineConnectionEvents {
NewTrack = 'new-track', // (track: NewTrackArgs) => void NewTrack = 'new-track', // (track: NewTrackArgs) => void
} }
function toRTCSessionDescriptionInit(
desc: Models['RtcSessionDescription_type']
): RTCSessionDescriptionInit | undefined {
if (desc.type === 'unspecified') {
console.error('Invalid SDP answer: type is "unspecified".')
return undefined
}
return {
sdp: desc.sdp,
// Force the type to be one of the valid RTCSdpType values
type: desc.type as RTCSdpType,
}
}
// EngineConnection encapsulates the connection(s) to the Engine // EngineConnection encapsulates the connection(s) to the Engine
// for the EngineCommandManager; namely, the underlying WebSocket // for the EngineCommandManager; namely, the underlying WebSocket
// and WebRTC connections. // and WebRTC connections.
@ -250,7 +266,7 @@ class EngineConnection extends EventTarget {
mediaStream?: MediaStream mediaStream?: MediaStream
idleMode: boolean = false idleMode: boolean = false
promise?: Promise<void> promise?: Promise<void>
sdpAnswer?: Models['RtcSessionDescription_type'] sdpAnswer?: RTCSessionDescriptionInit
triggeredStart = false triggeredStart = false
onIceCandidate = function ( onIceCandidate = function (
@ -549,6 +565,50 @@ class EngineConnection extends EventTarget {
this.disconnectAll() this.disconnectAll()
} }
initiateConnectionExclusive(): boolean {
// Only run if:
// - A peer connection exists,
// - ICE gathering is complete,
// - We have an SDP answer,
// - And we havent already triggered this connection.
if (!this.pc || this.triggeredStart || !this.sdpAnswer) {
return false
}
this.triggeredStart = true
// Transition to the connecting state
this.state = {
type: EngineConnectionStateType.Connecting,
value: { type: ConnectingType.WebRTCConnecting },
}
// Attempt to set the remote description to initiate connection
this.pc
.setRemoteDescription(this.sdpAnswer)
.then(() => {
// Update state once the remote description has been set
this.state = {
type: EngineConnectionStateType.Connecting,
value: { type: ConnectingType.SetRemoteDescription },
}
})
.catch((error: Error) => {
console.error('Failed to set remote description:', error)
this.state = {
type: EngineConnectionStateType.Disconnecting,
value: {
type: DisconnectingType.Error,
value: {
error: ConnectionError.LocalDescriptionInvalid,
context: error,
},
},
}
this.disconnectAll()
})
return true
}
/** /**
* Attempts to connect to the Engine over a WebSocket, and * Attempts to connect to the Engine over a WebSocket, and
* establish the WebRTC connections. * establish the WebRTC connections.
@ -588,38 +648,13 @@ class EngineConnection extends EventTarget {
}, },
} }
const initiateConnectingExclusive = () => {
if (that.triggeredStart) return
that.triggeredStart = true
// Start connecting.
that.state = {
type: EngineConnectionStateType.Connecting,
value: {
type: ConnectingType.WebRTCConnecting,
},
}
// As soon as this is set, RTCPeerConnection tries to
// establish a connection.
// @ts-expect-error: Have to ignore because dom.ts doesn't have the right type
void that.pc?.setRemoteDescription(that.sdpAnswer)
that.state = {
type: EngineConnectionStateType.Connecting,
value: {
type: ConnectingType.SetRemoteDescription,
},
}
}
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => { this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
console.log('icecandidate', event.candidate) console.log('icecandidate', event.candidate)
// This is null when the ICE gathering state is done. // This is null when the ICE gathering state is done.
// Windows ONLY uses this to signal it's done! // Windows ONLY uses this to signal it's done!
if (event.candidate === null) { if (event.candidate === null) {
initiateConnectingExclusive() that.initiateConnectionExclusive()
return return
} }
@ -643,7 +678,9 @@ class EngineConnection extends EventTarget {
// Sometimes the remote end doesn't report the end of candidates. // Sometimes the remote end doesn't report the end of candidates.
// They have 3 seconds to. // They have 3 seconds to.
setTimeout(() => { setTimeout(() => {
initiateConnectingExclusive() if (that.initiateConnectionExclusive()) {
console.warn('connected after 3 second delay')
}
}, 3000) }, 3000)
} }
this.pc?.addEventListener?.('icecandidate', this.onIceCandidate) this.pc?.addEventListener?.('icecandidate', this.onIceCandidate)
@ -653,7 +690,7 @@ class EngineConnection extends EventTarget {
console.log('icegatheringstatechange', this.iceGatheringState) console.log('icegatheringstatechange', this.iceGatheringState)
if (this.iceGatheringState !== 'complete') return if (this.iceGatheringState !== 'complete') return
initiateConnectingExclusive() that.initiateConnectionExclusive()
} }
) )
@ -1192,8 +1229,11 @@ class EngineConnection extends EventTarget {
}, },
} }
this.sdpAnswer = answer this.sdpAnswer = toRTCSessionDescriptionInit(answer)
// We might have received this after ice candidates finish
// Make sure we attempt to connect when we do.
this.initiateConnectionExclusive()
break break
case 'trickle_ice': case 'trickle_ice':
@ -1369,6 +1409,7 @@ export class EngineCommandManager extends EventTarget {
* see: src/lang/std/artifactGraph-README.md for a full explanation. * see: src/lang/std/artifactGraph-README.md for a full explanation.
*/ */
artifactGraph: ArtifactGraph = new Map() artifactGraph: ArtifactGraph = new Map()
artifactIndex: ArtifactIndex = []
/** /**
* The pendingCommands object is a map of the commands that have been sent to the engine that are still waiting on a reply * The pendingCommands object is a map of the commands that have been sent to the engine that are still waiting on a reply
*/ */
@ -2146,6 +2187,7 @@ export class EngineCommandManager extends EventTarget {
} }
updateArtifactGraph(execStateArtifactGraph: ExecState['artifactGraph']) { updateArtifactGraph(execStateArtifactGraph: ExecState['artifactGraph']) {
this.artifactGraph = execStateArtifactGraph this.artifactGraph = execStateArtifactGraph
this.artifactIndex = buildArtifactIndex(execStateArtifactGraph)
// TODO check if these still need to be deferred once e2e tests are working again. // TODO check if these still need to be deferred once e2e tests are working again.
if (this.artifactGraph.size) { if (this.artifactGraph.size) {
this.deferredArtifactEmptied(null) this.deferredArtifactEmptied(null)

29
src/lib/artifactIndex.ts Normal file
View File

@ -0,0 +1,29 @@
import { ArtifactGraph, ArtifactId, SourceRange, Artifact } from 'lang/wasm'
import { getFaceCodeRef } from 'lang/std/artifactGraph'
// Index artifacts in an ordered list for binary search
export type ArtifactEntry = { artifact: Artifact; id: ArtifactId }
/** Index artifacts by their codeRef range, ordered by start position */
export type ArtifactIndex = Array<{
range: SourceRange
entry: ArtifactEntry
}>
/** Creates an array of artifacts, only those with codeRefs, orders them by start range,
* to be used later by binary search */
export function buildArtifactIndex(
artifactGraph: ArtifactGraph
): ArtifactIndex {
const index: ArtifactIndex = []
Array.from(artifactGraph).forEach(([id, artifact]) => {
const codeRef = getFaceCodeRef(artifact)
if (!codeRef?.range) return
const entry = { artifact, id }
index.push({ range: codeRef.range, entry })
})
// Sort by start position for binary search
return index.sort((a, b) => a.range[0] - b.range[0])
}

View File

@ -3,7 +3,11 @@ import { angleLengthInfo } from 'components/Toolbar/setAngleLength'
import { transformAstSketchLines } from 'lang/std/sketchcombos' import { transformAstSketchLines } from 'lang/std/sketchcombos'
import { PathToNode } from 'lang/wasm' import { PathToNode } from 'lang/wasm'
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes' import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants' import {
KCL_DEFAULT_LENGTH,
KCL_DEFAULT_DEGREE,
KCL_DEFAULT_COLOR,
} from 'lib/constants'
import { components } from 'lib/machine-api' import { components } from 'lib/machine-api'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
@ -28,6 +32,8 @@ export const EXTRUSION_RESULTS = [
'intersect', 'intersect',
] as const ] as const
export const COMMAND_APPEARANCE_COLOR_DEFAULT = 'default'
export type ModelingCommandSchema = { export type ModelingCommandSchema = {
'Enter sketch': {} 'Enter sketch': {}
Export: { Export: {
@ -77,6 +83,9 @@ export type ModelingCommandSchema = {
distance: KclCommandValue distance: KclCommandValue
} }
Helix: { Helix: {
// Enables editing workflow
nodeToEdit?: PathToNode
// KCL stdlib arguments
revolutions: KclCommandValue revolutions: KclCommandValue
angleStart: KclCommandValue angleStart: KclCommandValue
counterClockWise: boolean counterClockWise: boolean
@ -107,6 +116,10 @@ export type ModelingCommandSchema = {
selection: Selections selection: Selections
} }
'Delete selection': {} 'Delete selection': {}
Appearance: {
nodeToEdit?: PathToNode
color: string
}
} }
export const modelingMachineCommandConfig: StateMachineCommandSetConfig< export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
@ -462,6 +475,13 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
status: 'development', status: 'development',
needsReview: true, needsReview: true,
args: { args: {
nodeToEdit: {
description:
'Path to the node in the AST to edit. Never shown to the user.',
skip: true,
inputType: 'text',
required: false,
},
revolutions: { revolutions: {
inputType: 'kcl', inputType: 'kcl',
defaultValue: '1', defaultValue: '1',
@ -477,9 +497,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
counterClockWise: { counterClockWise: {
inputType: 'options', inputType: 'options',
required: true, required: true,
defaultValue: false,
options: [ options: [
{ name: 'True', isCurrent: false, value: true }, { name: 'False', value: false },
{ name: 'False', isCurrent: true, value: false }, { name: 'True', value: true },
], ],
}, },
radius: { radius: {
@ -490,10 +511,11 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
axis: { axis: {
inputType: 'options', inputType: 'options',
required: true, required: true,
defaultValue: 'X',
options: [ options: [
{ name: 'X Axis', isCurrent: true, value: 'X' }, { name: 'X Axis', value: 'X' },
{ name: 'Y Axis', isCurrent: false, value: 'Y' }, { name: 'Y Axis', value: 'Y' },
{ name: 'Z Axis', isCurrent: false, value: 'Z' }, { name: 'Z Axis', value: 'Z' },
], ],
}, },
length: { length: {
@ -664,4 +686,40 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
}, },
}, },
Appearance: {
description:
'Set the appearance of a solid. This only works on solids, not sketches or individual paths.',
icon: 'extrude',
needsReview: true,
args: {
nodeToEdit: {
description:
'Path to the node in the AST to edit. Never shown to the user.',
skip: true,
inputType: 'text',
required: false,
},
color: {
inputType: 'options',
required: true,
options: [
{ name: 'Red', value: '#FF0000' },
{ name: 'Green', value: '#00FF00' },
{ name: 'Blue', value: '#0000FF' },
{ name: 'Turquoise', value: '#00FFFF' },
{ name: 'Purple', value: '#FF00FF' },
{ name: 'Yellow', value: '#FFFF00' },
{ name: 'Black', value: '#000000' },
{ name: 'Dark Grey', value: '#080808' },
{ name: 'Light Grey', value: '#D3D3D3' },
{ name: 'White', value: '#FFFFFF' },
{
name: 'Default (clear appearance)',
value: COMMAND_APPEARANCE_COLOR_DEFAULT,
},
],
},
// Add more fields
},
},
} }

View File

@ -66,6 +66,9 @@ export const KCL_DEFAULT_LENGTH = `5`
/** The default KCL degree expression */ /** The default KCL degree expression */
export const KCL_DEFAULT_DEGREE = `360` export const KCL_DEFAULT_DEGREE = `360`
/** The default KCL color expression */
export const KCL_DEFAULT_COLOR = `#3c73ff`
/** localStorage key for the playwright test-specific app settings file */ /** localStorage key for the playwright test-specific app settings file */
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings' export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'

View File

@ -33,6 +33,7 @@ interface StdLibCallInfo {
| ExecuteCommandEventPayload | ExecuteCommandEventPayload
| PrepareToEditCallback | PrepareToEditCallback
| PrepareToEditFailurePayload | PrepareToEditFailurePayload
supportsAppearance?: boolean
} }
/** /**
@ -190,6 +191,114 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
} }
} }
const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
const baseCommand = {
name: 'Helix',
groupId: 'modeling',
}
if (operation.type !== 'StdLibCall' || !operation.labeledArgs) {
return baseCommand
}
// TODO: find a way to loop over the arguments while keeping it safe
// revolutions kcl arg
if (
!('revolutions' in operation.labeledArgs) ||
!operation.labeledArgs.revolutions
)
return baseCommand
const revolutions = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.revolutions.sourceRange[0],
operation.labeledArgs.revolutions.sourceRange[1]
),
{}
)
if (err(revolutions) || 'errors' in revolutions) return baseCommand
// angleStart kcl arg
if (
!('angleStart' in operation.labeledArgs) ||
!operation.labeledArgs.angleStart
)
return baseCommand
const angleStart = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.angleStart.sourceRange[0],
operation.labeledArgs.angleStart.sourceRange[1]
),
{}
)
if (err(angleStart) || 'errors' in angleStart) return baseCommand
// counterClockWise options boolean arg
if (
!('counterClockWise' in operation.labeledArgs) ||
!operation.labeledArgs.counterClockWise
)
return baseCommand
const counterClockWise =
codeManager.code.slice(
operation.labeledArgs.counterClockWise.sourceRange[0],
operation.labeledArgs.counterClockWise.sourceRange[1]
) === 'true'
// radius kcl arg
if (!('radius' in operation.labeledArgs) || !operation.labeledArgs.radius)
return baseCommand
const radius = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.radius.sourceRange[0],
operation.labeledArgs.radius.sourceRange[1]
),
{}
)
if (err(radius) || 'errors' in radius) return baseCommand
// axis options string arg
if (!('axis' in operation.labeledArgs) || !operation.labeledArgs.axis)
return baseCommand
const axis = codeManager.code
.slice(
operation.labeledArgs.axis.sourceRange[0],
operation.labeledArgs.axis.sourceRange[1]
)
.replaceAll("'", '') // TODO: fix this crap
// length kcl arg
if (!('length' in operation.labeledArgs) || !operation.labeledArgs.length)
return baseCommand
const length = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.length.sourceRange[0],
operation.labeledArgs.length.sourceRange[1]
),
{}
)
if (err(length) || 'errors' in length) return baseCommand
// Assemble the default argument values for the Offset Plane command,
// with `nodeToEdit` set, which will let the Offset Plane actor know
// to edit the node that corresponds to the StdLibCall.
const argDefaultValues: ModelingCommandSchema['Helix'] = {
revolutions,
angleStart,
counterClockWise,
radius,
axis,
length,
nodeToEdit: getNodePathFromSourceRange(
kclManager.ast,
sourceRangeFromRust(operation.sourceRange)
),
}
return {
...baseCommand,
argDefaultValues,
}
}
/** /**
* A map of standard library calls to their corresponding information * A map of standard library calls to their corresponding information
* for use in the feature tree UI. * for use in the feature tree UI.
@ -204,6 +313,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
label: 'Extrude', label: 'Extrude',
icon: 'extrude', icon: 'extrude',
prepareToEdit: prepareToEditExtrude, prepareToEdit: prepareToEditExtrude,
supportsAppearance: true,
}, },
fillet: { fillet: {
label: 'Fillet', label: 'Fillet',
@ -212,6 +322,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
helix: { helix: {
label: 'Helix', label: 'Helix',
icon: 'helix', icon: 'helix',
prepareToEdit: prepareToEditHelix,
}, },
hole: { hole: {
label: 'Hole', label: 'Hole',
@ -228,6 +339,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
loft: { loft: {
label: 'Loft', label: 'Loft',
icon: 'loft', icon: 'loft',
supportsAppearance: true,
}, },
offsetPlane: { offsetPlane: {
label: 'Offset Plane', label: 'Offset Plane',
@ -253,10 +365,12 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
revolve: { revolve: {
label: 'Revolve', label: 'Revolve',
icon: 'revolve', icon: 'revolve',
supportsAppearance: true,
}, },
shell: { shell: {
label: 'Shell', label: 'Shell',
icon: 'shell', icon: 'shell',
supportsAppearance: true,
}, },
startSketchOn: { startSketchOn: {
label: 'Sketch', label: 'Sketch',
@ -280,6 +394,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
sweep: { sweep: {
label: 'Sweep', label: 'Sweep',
icon: 'sweep', icon: 'sweep',
supportsAppearance: true,
}, },
} }
@ -432,3 +547,37 @@ export async function enterEditFlow({
'Feature tree editing not yet supported for this operation. Please edit in the code editor.' 'Feature tree editing not yet supported for this operation. Please edit in the code editor.'
) )
} }
export async function enterAppearanceFlow({
operation,
artifact,
}: EnterEditFlowProps): Promise<Error | CommandBarMachineEvent> {
if (operation.type !== 'StdLibCall') {
return new Error(
'Appearance setting not yet supported for user-defined functions. Please edit in the code editor.'
)
}
const stdLibInfo = stdLibMap[operation.name]
if (stdLibInfo && stdLibInfo.supportsAppearance) {
const argDefaultValues = {
nodeToEdit: getNodePathFromSourceRange(
kclManager.ast,
sourceRangeFromRust(operation.sourceRange)
),
}
console.log('argDefaultValues', argDefaultValues)
return {
type: 'Find and select command',
data: {
name: 'Appearance',
groupId: 'modeling',
argDefaultValues,
},
}
}
return new Error(
'Appearance setting not yet supported for this operation. Please edit in the code editor.'
)
}

1298
src/lib/selections.test.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ import {
Expr, Expr,
defaultSourceRange, defaultSourceRange,
topLevelRange, topLevelRange,
ArtifactGraph,
} from 'lang/wasm' } from 'lang/wasm'
import { ModelingMachineEvent } from 'machines/modelingMachine' import { ModelingMachineEvent } from 'machines/modelingMachine'
import { isNonNullable, uuidv4 } from 'lib/utils' import { isNonNullable, uuidv4 } from 'lib/utils'
@ -31,19 +32,13 @@ import { PathToNodeMap } from 'lang/std/sketchcombos'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { import {
Artifact, Artifact,
getArtifactOfTypes,
getArtifactsOfTypes,
getCapCodeRef,
getSweepEdgeCodeRef,
getSolid2dCodeRef,
getWallCodeRef,
CodeRef, CodeRef,
getCodeRefsByArtifactId, getCodeRefsByArtifactId,
ArtifactId, ArtifactId,
getFaceCodeRef,
} from 'lang/std/artifactGraph' } from 'lang/std/artifactGraph'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { DefaultPlaneStr } from './planes' import { DefaultPlaneStr } from './planes'
import { ArtifactEntry, ArtifactIndex } from './artifactIndex'
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b' export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01' export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
@ -54,38 +49,7 @@ export type DefaultPlaneSelection = {
id: string id: string
} }
/** @deprecated Use {@link Artifact} instead. */
type Selection__old =
| {
type:
| 'default'
| 'line-end'
| 'line-mid'
| 'extrude-wall'
| 'solid2d'
| 'start-cap'
| 'end-cap'
| 'point'
| 'edge'
| 'adjacent-edge'
| 'line'
| 'arc'
| 'all'
range: SourceRange
}
| {
type: 'opposite-edgeCut' | 'adjacent-edgeCut' | 'base-edgeCut'
range: SourceRange
// TODO this is a temporary measure that well be made redundant with: https://github.com/KittyCAD/modeling-app/pull/3836
secondaryRange: SourceRange
}
export type NonCodeSelection = Axis | DefaultPlaneSelection export type NonCodeSelection = Axis | DefaultPlaneSelection
/** @deprecated Use {@link Selection} instead. */
export type Selections__old = {
otherSelections: NonCodeSelection[]
codeBasedSelections: Selection__old[]
}
export interface Selection { export interface Selection {
artifact?: Artifact artifact?: Artifact
codeRef: CodeRef codeRef: CodeRef
@ -95,76 +59,6 @@ export type Selections = {
graphSelections: Array<Selection> graphSelections: Array<Selection>
} }
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
* this function should only be used for backwards compatibility with old functions.
*/
function convertSelectionToOld(selection: Selection): Selection__old | null {
// return {} as Selection__old
// TODO implementation
const _artifact = selection.artifact
if (_artifact?.type === 'solid2d') {
const codeRef = getSolid2dCodeRef(
_artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return null
return { range: codeRef.range, type: 'solid2d' }
}
if (_artifact?.type === 'cap') {
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
if (err(codeRef)) return null
return {
range: codeRef.range,
type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
}
}
if (_artifact?.type === 'wall') {
const codeRef = getWallCodeRef(
_artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return null
return { range: codeRef.range, type: 'extrude-wall' }
}
if (_artifact?.type === 'segment' || _artifact?.type === 'path') {
return { range: _artifact.codeRef.range, type: 'default' }
}
if (_artifact?.type === 'sweepEdge') {
const codeRef = getSweepEdgeCodeRef(
_artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return null
if (_artifact?.subType === 'adjacent') {
return { range: codeRef.range, type: 'adjacent-edge' }
}
return { range: codeRef.range, type: 'edge' }
}
if (_artifact?.type === 'edgeCut') {
const codeRef = _artifact.codeRef
return { range: codeRef.range, type: 'default' }
}
if (selection?.codeRef?.range) {
return { range: selection.codeRef.range, type: 'default' }
}
return null
}
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
* this function should only be used for backwards compatibility with old functions.
*/
export function convertSelectionsToOld(selection: Selections): Selections__old {
const selections: Selection__old[] = []
for (const artifact of selection.graphSelections) {
const converted = convertSelectionToOld(artifact)
if (converted) selections.push(converted)
}
const selectionsOld: Selections__old = {
otherSelections: selection.otherSelections,
codeBasedSelections: selections,
}
return selectionsOld
}
export async function getEventForSelectWithPoint({ export async function getEventForSelectWithPoint({
data, data,
}: Extract< }: Extract<
@ -310,7 +204,6 @@ export function handleSelectionBatch({
selections.graphSelections.forEach(({ artifact }) => { selections.graphSelections.forEach(({ artifact }) => {
artifact?.id && artifact?.id &&
selectionToEngine.push({ selectionToEngine.push({
type: 'default',
id: artifact?.id, id: artifact?.id,
range: range:
getCodeRefsByArtifactId( getCodeRefsByArtifactId(
@ -350,7 +243,6 @@ export function handleSelectionBatch({
} }
type SelectionToEngine = { type SelectionToEngine = {
type: Selection__old['type']
id?: string id?: string
range: SourceRange range: SourceRange
} }
@ -360,11 +252,13 @@ export function processCodeMirrorRanges({
selectionRanges, selectionRanges,
isShiftDown, isShiftDown,
ast, ast,
artifactGraph,
}: { }: {
codeMirrorRanges: readonly SelectionRange[] codeMirrorRanges: readonly SelectionRange[]
selectionRanges: Selections selectionRanges: Selections
isShiftDown: boolean isShiftDown: boolean
ast: Program ast: Program
artifactGraph: ArtifactGraph
}): null | { }): null | {
modelingEvent: ModelingMachineEvent modelingEvent: ModelingMachineEvent
engineEvents: Models['WebSocketRequest_type'][] engineEvents: Models['WebSocketRequest_type'][]
@ -392,8 +286,11 @@ export function processCodeMirrorRanges({
}, },
} }
}) })
const idBasedSelections: SelectionToEngine[] = const idBasedSelections: SelectionToEngine[] = codeToIdSelections(
codeToIdSelections(codeBasedSelections) codeBasedSelections,
artifactGraph,
engineCommandManager.artifactIndex
)
const selections: Selection[] = [] const selections: Selection[] = []
for (const { id, range } of idBasedSelections) { for (const { id, range } of idBasedSelections) {
if (!id) { if (!id) {
@ -406,11 +303,8 @@ export function processCodeMirrorRanges({
}) })
continue continue
} }
const artifact = engineCommandManager.artifactGraph.get(id) const artifact = artifactGraph.get(id)
const codeRefs = getCodeRefsByArtifactId( const codeRefs = getCodeRefsByArtifactId(id, artifactGraph)
id,
engineCommandManager.artifactGraph
)
if (artifact && codeRefs) { if (artifact && codeRefs) {
selections.push({ artifact, codeRef: codeRefs[0] }) selections.push({ artifact, codeRef: codeRefs[0] })
} else if (codeRefs) { } else if (codeRefs) {
@ -601,234 +495,150 @@ export function canSubmitSelectionArg(
) )
} }
export function codeToIdSelections( /**
selections: Selection[] * Find the index of the last range where range[0] < targetStart
): SelectionToEngine[] { * This is used as a starting point for linear search of overlapping ranges
const selectionsOld = convertSelectionsToOld({ * @param index The sorted array of ranges to search through
graphSelections: selections, * @param targetStart The start position to compare against
otherSelections: [], * @returns The index of the last range where range[0] < targetStart
}).codeBasedSelections */
return selectionsOld export function findLastRangeStartingBefore(
.flatMap((selection): null | SelectionToEngine[] => { index: ArtifactIndex,
const { type } = selection targetStart: number
// TODO #868: loops over all artifacts will become inefficient at a large scale ): number {
const overlappingEntries = Array.from(engineCommandManager.artifactGraph) let left = 0
.map(([id, artifact]) => { let right = index.length - 1
const codeRef = getFaceCodeRef(artifact) let lastValidIndex = 0
if (!codeRef) return null
return isOverlap(codeRef.range, selection.range)
? {
artifact,
selection,
id,
}
: null
})
.filter(isNonNullable)
/** TODO refactor while (left <= right) {
* selections in our app is a sourceRange plus some metadata const mid = left + Math.floor((right - left) / 2)
* The metadata is just a union type string of different types of artifacts or 3d features 'extrude-wall' 'segment' etc const midRange = index[mid].range
* Because the source range is not enough to figure out what the user selected, so here we're using filtering through all the artifacts
* to find something that matches both the source range and the metadata.
*
* What we should migrate to is just storing what the user selected by what it matched in the artifactGraph it will simply the below a lot.
*
* In the case of a user moving the cursor them, we will still need to figure out what artifact from the graph matches best, but we will just need sane defaults
* and most of the time we can expect the user to be clicking in the 3d scene instead.
*/
let bestCandidate:
| {
id: ArtifactId
artifact: unknown
selection: Selection__old
}
| undefined
overlappingEntries.forEach((entry) => {
// TODO probably need to remove much of the `type === 'xyz'` below
if (type === 'default' && entry.artifact.type === 'segment') {
bestCandidate = entry
return
}
if (entry.artifact.type === 'path') {
const artifact = engineCommandManager.artifactGraph.get(
entry.artifact.solid2dId || ''
)
if (artifact?.type !== 'solid2d') {
bestCandidate = {
artifact: entry.artifact,
selection,
id: entry.id,
}
}
if (!entry.artifact.solid2dId) {
console.error(
'Expected PathArtifact to have solid2dId, but none found'
)
return
}
bestCandidate = {
artifact: artifact,
selection,
id: entry.artifact.solid2dId,
}
}
if (entry.artifact.type === 'plane') {
bestCandidate = {
artifact: entry.artifact,
selection,
id: entry.id,
}
}
if (entry.artifact.type === 'cap') {
bestCandidate = {
artifact: entry.artifact,
selection,
id: entry.id,
}
}
if (entry.artifact.type === 'wall') {
bestCandidate = {
artifact: entry.artifact,
selection,
id: entry.id,
}
}
if (type === 'extrude-wall' && entry.artifact.type === 'segment') {
if (!entry.artifact.surfaceId) return
const wall = engineCommandManager.artifactGraph.get(
entry.artifact.surfaceId
)
if (wall?.type !== 'wall') return
bestCandidate = {
artifact: wall,
selection,
id: entry.artifact.surfaceId,
}
return
}
if (type === 'edge' && entry.artifact.type === 'segment') {
const edges = getArtifactsOfTypes(
{ keys: entry.artifact.edgeIds, types: ['sweepEdge'] },
engineCommandManager.artifactGraph
)
const edge = [...edges].find(([_, edge]) => edge.type === 'sweepEdge')
if (!edge) return
bestCandidate = {
artifact: edge[1],
selection,
id: edge[0],
}
}
if (type === 'adjacent-edge' && entry.artifact.type === 'segment') {
const edges = getArtifactsOfTypes(
{ keys: entry.artifact.edgeIds, types: ['sweepEdge'] },
engineCommandManager.artifactGraph
)
const edge = [...edges].find(
([_, edge]) =>
edge.type === 'sweepEdge' && edge.subType === 'adjacent'
)
if (!edge) return
bestCandidate = {
artifact: edge[1],
selection,
id: edge[0],
}
}
if (
(type === 'end-cap' || type === 'start-cap') &&
entry.artifact.type === 'path'
) {
if (!entry.artifact.sweepId) return
const extrusion = getArtifactOfTypes(
{
key: entry.artifact.sweepId,
types: ['sweep'],
},
engineCommandManager.artifactGraph
)
if (err(extrusion)) return
const caps = getArtifactsOfTypes(
{ keys: extrusion.surfaceIds, types: ['cap'] },
engineCommandManager.artifactGraph
)
const cap = [...caps].find(
([_, cap]) => cap.subType === (type === 'end-cap' ? 'end' : 'start')
)
if (!cap) return
bestCandidate = {
artifact: entry.artifact,
selection,
id: cap[0],
}
return
}
if (entry.artifact.type === 'edgeCut') {
const consumedEdge = getArtifactOfTypes(
{
key: entry.artifact.consumedEdgeId,
types: ['segment', 'sweepEdge'],
},
engineCommandManager.artifactGraph
)
if (err(consumedEdge)) return
if (
consumedEdge.type === 'segment' &&
type === 'base-edgeCut' &&
isOverlap(
consumedEdge.codeRef.range,
selection.secondaryRange || [0, 0]
)
) {
bestCandidate = {
artifact: entry.artifact,
selection,
id: entry.id,
}
} else if (
consumedEdge.type === 'sweepEdge' &&
((type === 'adjacent-edgeCut' &&
consumedEdge.subType === 'adjacent') ||
(type === 'opposite-edgeCut' &&
consumedEdge.subType === 'opposite'))
) {
const seg = getArtifactOfTypes(
{ key: consumedEdge.segId, types: ['segment'] },
engineCommandManager.artifactGraph
)
if (err(seg)) return
if (
isOverlap(seg.codeRef.range, selection.secondaryRange || [0, 0])
) {
bestCandidate = {
artifact: entry.artifact,
selection,
id: entry.id,
}
}
}
}
if (entry.artifact.type === 'sweep') { if (midRange[0] < targetStart) {
bestCandidate = { // This range starts before our selection, look in right half for later ones
artifact: entry.artifact, lastValidIndex = mid
selection, left = mid + 1
id: entry.id, } else {
} // This range starts at or after our selection, look in left half
} right = mid - 1
}) }
}
if (bestCandidate) { return lastValidIndex
return [ }
{
type, function findOverlappingArtifactsFromIndex(
id: bestCandidate.id, selection: Selection,
range: bestCandidate.selection.range, index: ArtifactIndex
}, ): ArtifactEntry[] {
] if (!selection.codeRef?.range) {
console.warn('Selection missing code reference range')
return []
}
const selectionRange = selection.codeRef.range
const results: ArtifactEntry[] = []
// Binary search to find the last range where range[0] < selectionRange[0]
// This search does not take into consideration the end range, so it's possible
// the index it finds dose not have any overlap (depending on the end range)
// but it's main purpose is to act as a starting point for the linear part of the search
// so a tiny loss in efficiency is acceptable to keep the code simple
const startIndex = findLastRangeStartingBefore(index, selectionRange[0])
// Check all potential overlaps from the found position
for (let i = startIndex; i < index.length; i++) {
const { range, entry } = index[i]
// Stop if we've gone past possible overlaps
if (range[0] > selectionRange[1]) break
if (isOverlap(range, selectionRange)) {
results.push(entry)
}
}
return results
}
function getBestCandidate(
entries: ArtifactEntry[],
artifactGraph: ArtifactGraph
): ArtifactEntry | undefined {
if (!entries.length) {
return undefined
}
for (const entry of entries) {
// Segments take precedence
if (entry.artifact.type === 'segment') {
return entry
}
// Handle paths and their solid2d references
if (entry.artifact.type === 'path') {
const solid2dId = entry.artifact.solid2dId
if (!solid2dId) {
return entry
} }
return [selection] const solid2d = artifactGraph.get(solid2dId)
if (solid2d?.type === 'solid2d') {
return { id: solid2dId, artifact: solid2d }
}
continue
}
// Other valid artifact types
if (['plane', 'cap', 'wall', 'sweep'].includes(entry.artifact.type)) {
return entry
}
}
return undefined
}
function createSelectionToEngine(
selection: Selection,
candidateId?: ArtifactId
): SelectionToEngine {
return {
...(candidateId && { id: candidateId }),
range: selection.codeRef.range,
}
}
export function codeToIdSelections(
selections: Selection[],
artifactGraph: ArtifactGraph,
artifactIndex: ArtifactIndex
): SelectionToEngine[] {
if (!selections?.length) {
return []
}
if (!artifactGraph) {
console.warn('Artifact graph is missing or empty')
return selections.map((selection) => createSelectionToEngine(selection))
}
return selections
.flatMap((selection): SelectionToEngine[] => {
if (!selection) {
console.warn('Null or undefined selection encountered')
return []
}
// Direct artifact case
if (selection.artifact?.id) {
return [createSelectionToEngine(selection, selection.artifact.id)]
}
// Find matching artifacts by code range overlap
const overlappingEntries = findOverlappingArtifactsFromIndex(
selection,
artifactIndex
)
const bestCandidate = getBestCandidate(overlappingEntries, artifactGraph)
return [createSelectionToEngine(selection, bestCandidate?.id)]
}) })
.filter(isNonNullable) .filter(isNonNullable)
} }

View File

@ -312,7 +312,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}), }),
icon: 'sparkles', icon: 'sparkles',
status: 'available', status: 'available',
title: 'Text-to-CAD', title: 'Create with AI',
description: 'Generate geometry from a text prompt.', description: 'Generate geometry from a text prompt.',
links: [ links: [
{ {
@ -330,7 +330,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}), }),
icon: 'sparkles', icon: 'sparkles',
status: 'available', status: 'available',
title: 'Prompt-to-Edit', title: 'Edit with AI',
description: 'Edit geometry based on a text prompt.', description: 'Edit geometry based on a text prompt.',
links: [], links: [],
}, },

View File

@ -5,7 +5,6 @@ import {
CommandArgumentWithName, CommandArgumentWithName,
KclCommandValue, KclCommandValue,
} from 'lib/commandTypes' } from 'lib/commandTypes'
import { Selections__old } from 'lib/selections'
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils' import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
@ -16,7 +15,6 @@ export type CommandBarContext = {
commands: Command[] commands: Command[]
selectedCommand?: Command selectedCommand?: Command
currentArgument?: CommandArgument<unknown> & { name: string } currentArgument?: CommandArgument<unknown> & { name: string }
selectionRanges: Selections__old
argumentsToSubmit: { [x: string]: unknown } argumentsToSubmit: { [x: string]: unknown }
machineManager: MachineManager machineManager: MachineManager
} }

File diff suppressed because one or more lines are too long

View File

@ -88,6 +88,7 @@ import {
import { getPathsFromPlaneArtifact } from 'lang/std/artifactGraph' import { getPathsFromPlaneArtifact } from 'lang/std/artifactGraph'
import { createProfileStartHandle } from 'clientSideScene/segments' import { createProfileStartHandle } from 'clientSideScene/segments'
import { DRAFT_POINT } from 'clientSideScene/sceneInfra' import { DRAFT_POINT } from 'clientSideScene/sceneInfra'
import { setAppearance } from 'lang/modifyAst/setAppearance'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY' export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
@ -314,6 +315,7 @@ export type ModelingMachineEvent =
type: 'Delete selection' type: 'Delete selection'
data: ModelingCommandSchema['Delete selection'] data: ModelingCommandSchema['Delete selection']
} }
| { type: 'Appearance'; data: ModelingCommandSchema['Appearance'] }
| { | {
type: 'Add rectangle origin' type: 'Add rectangle origin'
data: [x: number, y: number] data: [x: number, y: number]
@ -1799,8 +1801,32 @@ export const modelingMachine = setup({
radius, radius,
axis, axis,
length, length,
nodeToEdit,
} = input } = input
let opInsertIndex: number | undefined = undefined
let opVariableName: string | undefined = undefined
// If this is an edit flow, first we're going to remove the old one
if (nodeToEdit && typeof nodeToEdit[1][0] === 'number') {
// Extract the old name from the node to edit
const oldNode = getNodeFromPath<VariableDeclaration>(
ast,
nodeToEdit,
'VariableDeclaration'
)
if (err(oldNode)) {
console.error('Error extracting plane name')
} else {
opVariableName = oldNode.node.declaration.id.name
}
const newBody = [...ast.body]
newBody.splice(nodeToEdit[1][0], 1)
ast.body = newBody
opInsertIndex = nodeToEdit[1][0]
}
for (const variable of [revolutions, angleStart, radius, length]) { for (const variable of [revolutions, angleStart, radius, length]) {
// Insert the variable if it exists // Insert the variable if it exists
if ( if (
@ -1831,6 +1857,8 @@ export const modelingMachine = setup({
radius: valueOrVariable(radius), radius: valueOrVariable(radius),
axis, axis,
length: valueOrVariable(length), length: valueOrVariable(length),
insertIndex: opInsertIndex,
variableName: opVariableName,
}) })
const updateAstResult = await kclManager.updateAst( const updateAstResult = await kclManager.updateAst(
@ -2172,6 +2200,47 @@ export const modelingMachine = setup({
}) })
} }
), ),
appearanceAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Appearance'] | undefined
}) => {
if (!input) return new Error('No input provided')
// Extract inputs
const ast = kclManager.ast
const { color, nodeToEdit } = input
if (!(nodeToEdit && typeof nodeToEdit[1][0] === 'number')) {
return new Error('Appearance is only an edit flow')
}
const result = setAppearance({
ast,
nodeToEdit,
color,
})
if (err(result)) {
return err(result)
}
const updateAstResult = await kclManager.updateAst(
result.modifiedAst,
true,
{
focusPath: [result.pathToNode],
}
)
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
}
),
}, },
// end actors // end actors
}).createMachine({ }).createMachine({
@ -2267,6 +2336,11 @@ export const modelingMachine = setup({
}, },
'Prompt-to-edit': 'Applying Prompt-to-edit', 'Prompt-to-edit': 'Applying Prompt-to-edit',
Appearance: {
target: 'Applying appearance',
reenter: true,
},
}, },
entry: 'reset client scene mouse handlers', entry: 'reset client scene mouse handlers',
@ -3389,6 +3463,19 @@ export const modelingMachine = setup({
}, },
}, },
}, },
'Applying appearance': {
invoke: {
src: 'appearanceAstMod',
id: 'appearanceAstMod',
input: ({ event }) => {
if (event.type !== 'Appearance') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
}, },
initial: 'idle', initial: 'idle',

View File

@ -538,7 +538,16 @@ export const settingsMachine = setup({
src: 'loadUserSettings', src: 'loadUserSettings',
onDone: { onDone: {
target: 'idle', target: 'idle',
actions: 'setAllSettings', actions: [
'setAllSettings',
'setThemeClass',
'setEngineTheme',
'setClientSideSceneUnits',
'setThemeColor',
'setClientTheme',
'setAllowOrbitInSketchMode',
'sendThemeToWatcher',
],
}, },
onError: { onError: {
target: 'idle', target: 'idle',
@ -561,8 +570,14 @@ export const settingsMachine = setup({
target: 'idle', target: 'idle',
actions: [ actions: [
'setAllSettings', 'setAllSettings',
'setThemeClass',
'setEngineTheme',
'setClientSideSceneUnits',
'setThemeColor', 'setThemeColor',
'Execute AST', 'Execute AST',
'setClientTheme',
'setAllowOrbitInSketchMode',
'sendThemeToWatcher',
sendTo('registerCommands', { type: 'update' }), sendTo('registerCommands', { type: 'update' }),
], ],
}, },

View File

@ -122,9 +122,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.95" version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -1858,9 +1858,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.97" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c37ad10b8a2afdcd1852d027f123cf4e38864ea93e0fda5c7ee1e8a49af49fb" checksum = "828a0c74476533e6258ea7dd70cfc7d63a5df4b37753d30ef198e0689eaac4eb"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -3245,9 +3245,9 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.217" version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -3263,9 +3263,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.217" version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3285,9 +3285,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.138" version = "1.0.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
dependencies = [ dependencies = [
"indexmap 2.7.1", "indexmap 2.7.1",
"itoa", "itoa",
@ -4208,9 +4208,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.13.1" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1"
dependencies = [ dependencies = [
"getrandom 0.3.1", "getrandom 0.3.1",
"js-sys", "js-sys",

View File

@ -80,7 +80,7 @@ members = [
[workspace.dependencies] [workspace.dependencies]
http = "1" http = "1"
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.97", features = [ kittycad-modeling-cmds = { version = "0.2.99", features = [
"ts-rs", "ts-rs",
"websocket", "websocket",
] } ] }

View File

@ -9,7 +9,7 @@ mod unbox;
use std::collections::HashMap; use std::collections::HashMap;
use convert_case::Casing; use convert_case::Casing;
use inflector::Inflector; use inflector::{cases::camelcase::to_camel_case, Inflector};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use quote::{format_ident, quote, quote_spanned, ToTokens}; use quote::{format_ident, quote, quote_spanned, ToTokens};
use regex::Regex; use regex::Regex;
@ -326,13 +326,14 @@ fn do_stdlib_inner(
}; };
let include_in_snippet = required || arg_meta.map(|arg| arg.include_in_snippet).unwrap_or_default(); let include_in_snippet = required || arg_meta.map(|arg| arg.include_in_snippet).unwrap_or_default();
let label_required = !(i == 0 && metadata.unlabeled_first); let label_required = !(i == 0 && metadata.unlabeled_first);
let camel_case_arg_name = to_camel_case(&arg_name);
if ty_string != "ExecState" && ty_string != "Args" { if ty_string != "ExecState" && ty_string != "Args" {
let schema = quote! { let schema = quote! {
#docs_crate::cleanup_number_tuples_root(generator.root_schema_for::<#ty_ident>()) #docs_crate::cleanup_number_tuples_root(generator.root_schema_for::<#ty_ident>())
}; };
arg_types.push(quote! { arg_types.push(quote! {
#docs_crate::StdLibFnArg { #docs_crate::StdLibFnArg {
name: #arg_name.to_string(), name: #camel_case_arg_name.to_string(),
type_: #ty_string.to_string(), type_: #ty_string.to_string(),
schema: #schema, schema: #schema,
required: #required, required: #required,

View File

@ -457,7 +457,7 @@ fn generate_const_from_kcl(cnst: &ConstData, file_name: String) -> Result<()> {
}); });
let output = hbs.render("const", &data)?; let output = hbs.render("const", &data)?;
expectorate::assert_contents(format!("../../../docs/kcl/const_{}.md", file_name), &output); expectorate::assert_contents(format!("../../../docs/kcl/{}.md", file_name), &output);
Ok(()) Ok(())
} }

View File

@ -49,12 +49,15 @@ impl CollectionVisitor {
} }
} }
crate::parsing::ast::types::BodyItem::VariableDeclaration(var) if !var.visibility.is_default() => { crate::parsing::ast::types::BodyItem::VariableDeclaration(var) if !var.visibility.is_default() => {
let qual_name = if self.name == "prelude" {
"std::".to_owned()
} else {
format!("std::{}::", self.name)
};
let mut dd = match var.kind { let mut dd = match var.kind {
// TODO metadata for args // TODO metadata for args
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, format!("std::{}::", self.name))), VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name)),
VariableKind::Const => { VariableKind::Const => DocData::Const(ConstData::from_ast(var, qual_name)),
DocData::Const(ConstData::from_ast(var, format!("std::{}::", self.name)))
}
}; };
// FIXME this association of metadata with items is pretty flaky. // FIXME this association of metadata with items is pretty flaky.
@ -359,6 +362,7 @@ impl FnData {
} }
} }
#[allow(clippy::literal_string_with_formatting_args)]
fn to_autocomplete_snippet(&self) -> String { fn to_autocomplete_snippet(&self) -> String {
if self.name == "loft" { if self.name == "loft" {
return "loft([${0:sketch000}, ${1:sketch001}])${}".to_owned(); return "loft([${0:sketch000}, ${1:sketch001}])${}".to_owned();

View File

@ -473,6 +473,7 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
}) })
} }
#[allow(clippy::literal_string_with_formatting_args)]
fn to_autocomplete_snippet(&self) -> Result<String> { fn to_autocomplete_snippet(&self) -> Result<String> {
if self.name() == "loft" { if self.name() == "loft" {
return Ok("loft([${0:sketch000}, ${1:sketch001}])${}".to_string()); return Ok("loft([${0:sketch000}, ${1:sketch001}])${}".to_string());
@ -926,6 +927,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_line() { fn get_autocomplete_snippet_line() {
let line_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Line); let line_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Line);
let snippet = line_fn.to_autocomplete_snippet().unwrap(); let snippet = line_fn.to_autocomplete_snippet().unwrap();
@ -933,6 +935,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_extrude() { fn get_autocomplete_snippet_extrude() {
let extrude_fn: Box<dyn StdLibFn> = Box::new(crate::std::extrude::Extrude); let extrude_fn: Box<dyn StdLibFn> = Box::new(crate::std::extrude::Extrude);
let snippet = extrude_fn.to_autocomplete_snippet().unwrap(); let snippet = extrude_fn.to_autocomplete_snippet().unwrap();
@ -940,6 +943,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_fillet() { fn get_autocomplete_snippet_fillet() {
let fillet_fn: Box<dyn StdLibFn> = Box::new(crate::std::fillet::Fillet); let fillet_fn: Box<dyn StdLibFn> = Box::new(crate::std::fillet::Fillet);
let snippet = fillet_fn.to_autocomplete_snippet().unwrap(); let snippet = fillet_fn.to_autocomplete_snippet().unwrap();
@ -957,13 +961,14 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_pattern_circular_3d() { fn get_autocomplete_snippet_pattern_circular_3d() {
// We test this one specifically because it has ints and floats and strings. // We test this one specifically because it has ints and floats and strings.
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternCircular3D); let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternCircular3D);
let snippet = pattern_fn.to_autocomplete_snippet().unwrap(); let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!( assert_eq!(
snippet, snippet,
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arc_degrees = ${8:3.14}, rotate_duplicates = ${9:false})${}"# r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})${}"#
); );
} }
@ -980,6 +985,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_circle() { fn get_autocomplete_snippet_circle() {
let circle_fn: Box<dyn StdLibFn> = Box::new(crate::std::shapes::Circle); let circle_fn: Box<dyn StdLibFn> = Box::new(crate::std::shapes::Circle);
let snippet = circle_fn.to_autocomplete_snippet().unwrap(); let snippet = circle_fn.to_autocomplete_snippet().unwrap();
@ -993,6 +999,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_arc() { fn get_autocomplete_snippet_arc() {
let arc_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Arc); let arc_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Arc);
let snippet = arc_fn.to_autocomplete_snippet().unwrap(); let snippet = arc_fn.to_autocomplete_snippet().unwrap();
@ -1014,6 +1021,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_pattern_linear_2d() { fn get_autocomplete_snippet_pattern_linear_2d() {
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D); let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
let snippet = pattern_fn.to_autocomplete_snippet().unwrap(); let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
@ -1034,6 +1042,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_loft() { fn get_autocomplete_snippet_loft() {
let loft_fn: Box<dyn StdLibFn> = Box::new(crate::std::loft::Loft); let loft_fn: Box<dyn StdLibFn> = Box::new(crate::std::loft::Loft);
let snippet = loft_fn.to_autocomplete_snippet().unwrap(); let snippet = loft_fn.to_autocomplete_snippet().unwrap();
@ -1041,6 +1050,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_sweep() { fn get_autocomplete_snippet_sweep() {
let sweep_fn: Box<dyn StdLibFn> = Box::new(crate::std::sweep::Sweep); let sweep_fn: Box<dyn StdLibFn> = Box::new(crate::std::sweep::Sweep);
let snippet = sweep_fn.to_autocomplete_snippet().unwrap(); let snippet = sweep_fn.to_autocomplete_snippet().unwrap();
@ -1048,6 +1058,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_hole() { fn get_autocomplete_snippet_hole() {
let hole_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Hole); let hole_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Hole);
let snippet = hole_fn.to_autocomplete_snippet().unwrap(); let snippet = hole_fn.to_autocomplete_snippet().unwrap();
@ -1055,16 +1066,18 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_helix() { fn get_autocomplete_snippet_helix() {
let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::Helix); let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::Helix);
let snippet = helix_fn.to_autocomplete_snippet().unwrap(); let snippet = helix_fn.to_autocomplete_snippet().unwrap();
assert_eq!( assert_eq!(
snippet, snippet,
r#"helix(revolutions = ${0:3.14}, angle_start = ${1:3.14}, radius = ${2:3.14}, axis = ${3:"X"}, length = ${4:3.14})${}"# r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:"X"}, length = ${4:3.14})${}"#
); );
} }
#[test] #[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_helix_revolutions() { fn get_autocomplete_snippet_helix_revolutions() {
let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::HelixRevolutions); let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::HelixRevolutions);
let snippet = helix_fn.to_autocomplete_snippet().unwrap(); let snippet = helix_fn.to_autocomplete_snippet().unwrap();

View File

@ -402,12 +402,15 @@ impl CompilationError {
self, self,
suggestion_title: impl ToString, suggestion_title: impl ToString,
suggestion_insert: impl ToString, suggestion_insert: impl ToString,
// Will use the error source range if none is supplied
source_range: Option<SourceRange>,
tag: Tag, tag: Tag,
) -> CompilationError { ) -> CompilationError {
CompilationError { CompilationError {
suggestion: Some(Suggestion { suggestion: Some(Suggestion {
title: suggestion_title.to_string(), title: suggestion_title.to_string(),
insert: suggestion_insert.to_string(), insert: suggestion_insert.to_string(),
source_range: source_range.unwrap_or(self.source_range),
}), }),
tag, tag,
..self ..self
@ -419,9 +422,9 @@ impl CompilationError {
let suggestion = self.suggestion.as_ref()?; let suggestion = self.suggestion.as_ref()?;
Some(format!( Some(format!(
"{}{}{}", "{}{}{}",
&src[0..self.source_range.start()], &src[0..suggestion.source_range.start()],
suggestion.insert, suggestion.insert,
&src[self.source_range.end()..] &src[suggestion.source_range.end()..]
)) ))
} }
} }
@ -465,4 +468,5 @@ pub enum Tag {
pub struct Suggestion { pub struct Suggestion {
pub title: String, pub title: String,
pub insert: String, pub insert: String,
pub source_range: SourceRange,
} }

View File

@ -15,7 +15,7 @@ pub(super) const SIGNIFICANT_ATTRS: [&str; 2] = [SETTINGS, NO_PRELUDE];
pub(crate) const SETTINGS: &str = "settings"; pub(crate) const SETTINGS: &str = "settings";
pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit"; pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit"; pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
pub(super) const NO_PRELUDE: &str = "no_prelude"; pub(super) const NO_PRELUDE: &str = "no_std";
pub(super) const IMPORT_FORMAT: &str = "format"; pub(super) const IMPORT_FORMAT: &str = "format";
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"]; pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];

View File

@ -6,7 +6,7 @@ use itertools::{EitherOrBoth, Itertools};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use crate::{ use crate::{
execution::{annotations, memory::ProgramMemory, ExecState, ExecutorSettings}, execution::{annotations, memory::ProgramMemory, EnvironmentRef, ExecState, ExecutorSettings},
parsing::ast::types::{Annotation, Node, Program}, parsing::ast::types::{Annotation, Node, Program},
walk::Node as WalkNode, walk::Node as WalkNode,
}; };
@ -65,6 +65,7 @@ pub struct OldAstState {
pub exec_state: ExecState, pub exec_state: ExecState,
/// The last settings used for execution. /// The last settings used for execution.
pub settings: crate::execution::ExecutorSettings, pub settings: crate::execution::ExecutorSettings,
pub result_env: EnvironmentRef,
} }
/// The result of a cache check. /// The result of a cache check.
@ -267,7 +268,7 @@ firstSketch = startSketchOn('XY')
// Remove the end face for the extrusion. // Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25)"#; shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
let (program, ctx, _) = parse_execute(new).await.unwrap(); let (program, _, ctx, _) = parse_execute(new).await.unwrap();
let result = get_changed_program( let result = get_changed_program(
CacheInformation { CacheInformation {
@ -310,7 +311,7 @@ firstSketch = startSketchOn('XY')
// Remove the end face for the extrusion. // Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25)"#; shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
let (program_old, ctx, _) = parse_execute(old).await.unwrap(); let (program_old, _, ctx, _) = parse_execute(old).await.unwrap();
let program_new = crate::Program::parse_no_errs(new).unwrap(); let program_new = crate::Program::parse_no_errs(new).unwrap();
@ -355,7 +356,7 @@ firstSketch = startSketchOn('XY')
// Remove the end face for the extrusion. // Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25)"#; shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
let (program, ctx, _) = parse_execute(old).await.unwrap(); let (program, _, ctx, _) = parse_execute(old).await.unwrap();
let program_new = crate::Program::parse_no_errs(new).unwrap(); let program_new = crate::Program::parse_no_errs(new).unwrap();
@ -404,7 +405,7 @@ firstSketch = startSketchOn('XY')
// Remove the end face for the extrusion. // Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25)"#; shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
let (program, ctx, _) = parse_execute(old).await.unwrap(); let (program, _, ctx, _) = parse_execute(old).await.unwrap();
let program_new = crate::Program::parse_no_errs(new).unwrap(); let program_new = crate::Program::parse_no_errs(new).unwrap();
@ -438,7 +439,7 @@ firstSketch = startSketchOn('XY')
// Remove the end face for the extrusion. // Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25)"#; shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
let (program, mut ctx, _) = parse_execute(new).await.unwrap(); let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
// Change the settings to cm. // Change the settings to cm.
ctx.settings.units = crate::UnitLength::Cm; ctx.settings.units = crate::UnitLength::Cm;
@ -480,7 +481,7 @@ firstSketch = startSketchOn('XY')
// Remove the end face for the extrusion. // Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25)"#; shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
let (program, mut ctx, _) = parse_execute(new).await.unwrap(); let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
// Change the settings. // Change the settings.
ctx.settings.show_grid = !ctx.settings.show_grid; ctx.settings.show_grid = !ctx.settings.show_grid;
@ -515,7 +516,7 @@ firstSketch = startSketchOn('XY')
// Remove the end face for the extrusion. // Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25)"#; shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
let (program, mut ctx, _) = parse_execute(new).await.unwrap(); let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
// Change the settings. // Change the settings.
ctx.settings.highlight_edges = !ctx.settings.highlight_edges; ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
@ -582,7 +583,7 @@ startSketchOn('XY')
startSketchOn('XY') startSketchOn('XY')
"#; "#;
let (program, ctx, _) = parse_execute(old_code).await.unwrap(); let (program, _, ctx, _) = parse_execute(old_code).await.unwrap();
let mut new_program = crate::Program::parse_no_errs(new_code).unwrap(); let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
new_program.compute_digest(); new_program.compute_digest();

View File

@ -1,7 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use schemars::JsonSchema;
use crate::{ use crate::{
engine::ExecutionKind, engine::ExecutionKind,
@ -9,7 +8,7 @@ use crate::{
execution::{ execution::{
annotations, annotations,
cad_op::{OpArg, OpKclValue, Operation}, cad_op::{OpArg, OpKclValue, Operation},
kcl_value::NumericType, kcl_value::{FunctionSource, NumericType},
memory, memory,
state::ModuleState, state::ModuleState,
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, TagEngineInfo, TagIdentifier, BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, TagEngineInfo, TagIdentifier,
@ -54,7 +53,7 @@ impl ExecutorContext {
let mut no_prelude = false; let mut no_prelude = false;
for annotation in annotations { for annotation in annotations {
if annotation.name() == Some(annotations::SETTINGS) { if annotation.name() == Some(annotations::SETTINGS) {
if matches!(body_type, BodyType::Root(_)) { if matches!(body_type, BodyType::Root) {
let old_units = exec_state.length_unit(); let old_units = exec_state.length_unit();
exec_state.mod_local.settings.update_from_annotation(annotation)?; exec_state.mod_local.settings.update_from_annotation(annotation)?;
let new_units = exec_state.length_unit(); let new_units = exec_state.length_unit();
@ -70,12 +69,12 @@ impl ExecutorContext {
)); ));
} }
} else if annotation.name() == Some(annotations::NO_PRELUDE) { } else if annotation.name() == Some(annotations::NO_PRELUDE) {
if matches!(body_type, BodyType::Root(_)) { if matches!(body_type, BodyType::Root) {
no_prelude = true; no_prelude = true;
} else { } else {
exec_state.err(CompilationError::err( exec_state.err(CompilationError::err(
annotation.as_source_range(), annotation.as_source_range(),
"Prelude can only be skipped at the top level scope of a file", "The standard library can only be skipped at the top level scope of a file",
)); ));
} }
} else { } else {
@ -88,51 +87,56 @@ impl ExecutorContext {
Ok(no_prelude) Ok(no_prelude)
} }
pub(super) async fn exec_module_body(
&self,
program: &Node<Program>,
exec_state: &mut ExecState,
exec_kind: ExecutionKind,
preserve_mem: bool,
) -> Result<(Option<KclValue>, EnvironmentRef), KclError> {
let old_units = exec_state.length_unit();
let no_prelude = self
.handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
.await?;
if !preserve_mem {
exec_state.mut_memory().push_new_root_env(!no_prelude);
}
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
let result = self
.exec_block(program, exec_state, crate::execution::BodyType::Root)
.await;
let new_units = exec_state.length_unit();
let env_ref = if preserve_mem {
exec_state.mut_memory().pop_and_preserve_env()
} else {
exec_state.mut_memory().pop_env()
};
if !exec_kind.is_isolated() && new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?;
}
self.engine.replace_execution_kind(original_execution).await;
result.map(|result| (result, env_ref))
}
/// Execute an AST's program. /// Execute an AST's program.
#[async_recursion] #[async_recursion]
pub(super) async fn exec_program<'a>( pub(super) async fn exec_block<'a>(
&'a self, &'a self,
program: NodeRef<'a, crate::parsing::ast::types::Program>, program: NodeRef<'a, crate::parsing::ast::types::Program>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
body_type: BodyType, body_type: BodyType,
) -> Result<Option<KclValue>, KclError> { ) -> Result<Option<KclValue>, KclError> {
let no_prelude = self
.handle_annotations(program.inner_attrs.iter(), body_type, exec_state)
.await?;
if !no_prelude && body_type == BodyType::Root(true) {
// Import std::prelude
let prelude_range = SourceRange::from(program).start_as_range();
let id = self
.open_module(
&ImportPath::Std {
path: vec!["std".to_owned(), "prelude".to_owned()],
},
&[],
exec_state,
prelude_range,
)
.await?;
let (module_memory, module_exports) = self
.exec_module_for_items(id, exec_state, ExecutionKind::Isolated, prelude_range)
.await
.unwrap();
for name in module_exports {
let item = exec_state
.memory()
.get_from(&name, module_memory, prelude_range)
.cloned()
.unwrap();
exec_state.mut_memory().add(name, item, prelude_range)?;
}
}
let mut last_expr = None; let mut last_expr = None;
// Iterate over the body of the program. // Iterate over the body of the program.
for statement in &program.body { for statement in &program.body {
match statement { match statement {
BodyItem::ImportStatement(import_stmt) => { BodyItem::ImportStatement(import_stmt) => {
if !matches!(body_type, BodyType::Root(_)) { if !matches!(body_type, BodyType::Root) {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: "Imports are only supported at the top-level of a file.".to_owned(), message: "Imports are only supported at the top-level of a file.".to_owned(),
source_ranges: vec![import_stmt.into()], source_ranges: vec![import_stmt.into()],
@ -263,7 +267,7 @@ impl ExecutorContext {
BodyItem::ReturnStatement(return_statement) => { BodyItem::ReturnStatement(return_statement) => {
let metadata = Metadata::from(return_statement); let metadata = Metadata::from(return_statement);
if matches!(body_type, BodyType::Root(_)) { if matches!(body_type, BodyType::Root) {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: "Cannot return from outside a function.".to_owned(), message: "Cannot return from outside a function.".to_owned(),
source_ranges: vec![metadata.source_range], source_ranges: vec![metadata.source_range],
@ -293,7 +297,7 @@ impl ExecutorContext {
} }
} }
if matches!(body_type, BodyType::Root(_)) { if matches!(body_type, BodyType::Root) {
// Flush the batch queue. // Flush the batch queue.
self.engine self.engine
.flush_batch( .flush_batch(
@ -308,7 +312,7 @@ impl ExecutorContext {
Ok(last_expr) Ok(last_expr)
} }
async fn open_module( pub(super) async fn open_module(
&self, &self,
path: &ImportPath, path: &ImportPath,
attrs: &[Node<Annotation>], attrs: &[Node<Annotation>],
@ -358,7 +362,7 @@ impl ExecutorContext {
} }
} }
async fn exec_module_for_items( pub(super) async fn exec_module_for_items(
&self, &self,
module_id: ModuleId, module_id: ModuleId,
exec_state: &mut ExecState, exec_state: &mut ExecState,
@ -425,25 +429,14 @@ impl ExecutorContext {
exec_kind: ExecutionKind, exec_kind: ExecutionKind,
source_range: SourceRange, source_range: SourceRange,
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> { ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
let old_units = exec_state.length_unit();
let mut local_state = ModuleState::new(&self.settings, path.std_path());
exec_state.global.mod_loader.enter_module(path); exec_state.global.mod_loader.enter_module(path);
let mut local_state = ModuleState::new(&self.settings, path.std_path());
std::mem::swap(&mut exec_state.mod_local, &mut local_state); std::mem::swap(&mut exec_state.mod_local, &mut local_state);
exec_state.mut_memory().push_new_root_env();
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
let result = self let result = self.exec_module_body(program, exec_state, exec_kind, false).await;
.exec_program(program, exec_state, crate::execution::BodyType::Root(true))
.await;
let new_units = exec_state.length_unit();
std::mem::swap(&mut exec_state.mod_local, &mut local_state); std::mem::swap(&mut exec_state.mod_local, &mut local_state);
let env_ref = exec_state.mut_memory().pop_env();
exec_state.global.mod_loader.leave_module(path); exec_state.global.mod_loader.leave_module(path);
if !exec_kind.is_isolated() && new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?;
}
self.engine.replace_execution_kind(original_execution).await;
result result
.map_err(|err| { .map_err(|err| {
@ -461,7 +454,7 @@ impl ExecutorContext {
}) })
} }
}) })
.map(|result| (result, env_ref, local_state.module_exports)) .map(|(val, env)| (val, env, local_state.module_exports))
} }
#[async_recursion] #[async_recursion]
@ -520,11 +513,10 @@ impl ExecutorContext {
if rust_impl { if rust_impl {
if let Some(std_path) = &exec_state.mod_local.settings.std_path { if let Some(std_path) = &exec_state.mod_local.settings.std_path {
let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
KclValue::Function { KclValue::Function {
expression: function_expression.clone(), value: FunctionSource::Std { func, props },
meta: vec![metadata.to_owned()], meta: vec![metadata.to_owned()],
func: Some(crate::std::std_fn(std_path, statement_kind.expect_name())),
memory: None,
} }
} else { } else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
@ -534,14 +526,15 @@ impl ExecutorContext {
})); }));
} }
} else { } else {
// Cloning memory here is crucial for semantics so that we close // Snapshotting memory here is crucial for semantics so that we close
// over variables. Variables defined lexically later shouldn't // over variables. Variables defined lexically later shouldn't
// be available to the function body. // be available to the function body.
KclValue::Function { KclValue::Function {
expression: function_expression.clone(), value: FunctionSource::User {
ast: function_expression.clone(),
memory: exec_state.mut_memory().snapshot(),
},
meta: vec![metadata.to_owned()], meta: vec![metadata.to_owned()],
func: None,
memory: Some(exec_state.mut_memory().snapshot()),
} }
} }
} }
@ -996,6 +989,12 @@ impl Node<CallExpressionKw> {
); );
match ctx.stdlib.get_either(fn_name) { match ctx.stdlib.get_either(fn_name) {
FunctionKind::Core(func) => { FunctionKind::Core(func) => {
if func.deprecated() {
exec_state.warn(CompilationError::err(
self.callee.as_source_range(),
format!("`{fn_name}` is deprecated, see the docs for a recommended replacement"),
));
}
let op = if func.feature_tree_operation() { let op = if func.feature_tree_operation() {
let op_labeled_args = args let op_labeled_args = args
.kw_args .kw_args
@ -1120,6 +1119,12 @@ impl Node<CallExpression> {
match ctx.stdlib.get_either(fn_name) { match ctx.stdlib.get_either(fn_name) {
FunctionKind::Core(func) => { FunctionKind::Core(func) => {
if func.deprecated() {
exec_state.warn(CompilationError::err(
self.callee.as_source_range(),
format!("`{fn_name}` is deprecated, see the docs for a recommended replacement"),
));
}
let op = if func.feature_tree_operation() { let op = if func.feature_tree_operation() {
let op_labeled_args = func let op_labeled_args = func
.args(false) .args(false)
@ -1207,7 +1212,7 @@ impl Node<CallExpression> {
source_ranges = meta.iter().map(|m| m.source_range).collect(); source_ranges = meta.iter().map(|m| m.source_range).collect();
}; };
KclError::UndefinedValue(KclErrorDetails { KclError::UndefinedValue(KclErrorDetails {
message: format!("Result of user-defined function {} is undefined", fn_name), message: format!("Result of function {} is undefined", fn_name),
source_ranges, source_ranges,
}) })
})?; })?;
@ -1472,7 +1477,7 @@ impl Node<IfExpression> {
.await? .await?
.get_bool()?; .get_bool()?;
if cond { if cond {
let block_result = ctx.exec_program(&self.then_val, exec_state, BodyType::Block).await?; let block_result = ctx.exec_block(&self.then_val, exec_state, BodyType::Block).await?;
// Block must end in an expression, so this has to be Some. // Block must end in an expression, so this has to be Some.
// Enforced by the parser. // Enforced by the parser.
// See https://github.com/KittyCAD/modeling-app/issues/4015 // See https://github.com/KittyCAD/modeling-app/issues/4015
@ -1492,7 +1497,7 @@ impl Node<IfExpression> {
.await? .await?
.get_bool()?; .get_bool()?;
if cond { if cond {
let block_result = ctx.exec_program(&else_if.then_val, exec_state, BodyType::Block).await?; let block_result = ctx.exec_block(&else_if.then_val, exec_state, BodyType::Block).await?;
// Block must end in an expression, so this has to be Some. // Block must end in an expression, so this has to be Some.
// Enforced by the parser. // Enforced by the parser.
// See https://github.com/KittyCAD/modeling-app/issues/4015 // See https://github.com/KittyCAD/modeling-app/issues/4015
@ -1501,7 +1506,7 @@ impl Node<IfExpression> {
} }
// Run the final `else` branch. // Run the final `else` branch.
ctx.exec_program(&self.final_else, exec_state, BodyType::Block) ctx.exec_block(&self.final_else, exec_state, BodyType::Block)
.await .await
.map(|expr| expr.unwrap()) .map(|expr| expr.unwrap())
} }
@ -1734,7 +1739,7 @@ pub(crate) async fn call_user_defined_function(
// Execute the function body using the memory we just created. // Execute the function body using the memory we just created.
let result = ctx let result = ctx
.exec_program(&function_expression.body, exec_state, BodyType::Block) .exec_block(&function_expression.body, exec_state, BodyType::Block)
.await; .await;
let result = result.map(|_| { let result = result.map(|_| {
exec_state exec_state
@ -1767,7 +1772,7 @@ pub(crate) async fn call_user_defined_function_kw(
// Execute the function body using the memory we just created. // Execute the function body using the memory we just created.
let result = ctx let result = ctx
.exec_program(&function_expression.body, exec_state, BodyType::Block) .exec_block(&function_expression.body, exec_state, BodyType::Block)
.await; .await;
let result = result.map(|_| { let result = result.map(|_| {
exec_state exec_state
@ -1782,48 +1787,42 @@ pub(crate) async fn call_user_defined_function_kw(
result result
} }
/// A function being used as a parameter into a stdlib function. This is a impl FunctionSource {
/// closure, plus everything needed to execute it.
pub struct FunctionParam<'a> {
pub inner: Option<&'a crate::std::StdFn>,
pub memory: Option<EnvironmentRef>,
pub fn_expr: crate::parsing::ast::types::BoxNode<FunctionExpression>,
pub ctx: ExecutorContext,
}
impl FunctionParam<'_> {
pub async fn call( pub async fn call(
&self, &self,
exec_state: &mut ExecState, exec_state: &mut ExecState,
ctx: &ExecutorContext,
args: Vec<Arg>, args: Vec<Arg>,
source_range: SourceRange, source_range: SourceRange,
) -> Result<Option<KclValue>, KclError> { ) -> Result<Option<KclValue>, KclError> {
if let Some(inner) = self.inner { match self {
let args = crate::std::Args::new( FunctionSource::Std { func, props } => {
args, if props.deprecated {
source_range, exec_state.warn(CompilationError::err(
self.ctx.clone(), source_range,
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic), format!(
); "`{}` is deprecated, see the docs for a recommended replacement",
props.name
),
));
}
let args = crate::std::Args::new(
args,
source_range,
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
);
inner(exec_state, args).await.map(Some) func(exec_state, args).await.map(Some)
} else { }
call_user_defined_function(args, self.memory.unwrap(), self.fn_expr.as_ref(), exec_state, &self.ctx).await FunctionSource::User { ast, memory } => {
call_user_defined_function(args, *memory, ast, exec_state, ctx).await
}
FunctionSource::None => unreachable!(),
} }
} }
} }
impl JsonSchema for FunctionParam<'_> {
fn schema_name() -> String {
"FunctionParam".to_owned()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
// TODO: Actually generate a reasonable schema.
gen.subschema_for::<()>()
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -1868,6 +1867,7 @@ mod test {
} }
fn additional_program_memory(items: &[(String, KclValue)]) -> ProgramMemory { fn additional_program_memory(items: &[(String, KclValue)]) -> ProgramMemory {
let mut program_memory = ProgramMemory::new(); let mut program_memory = ProgramMemory::new();
program_memory.init_for_tests();
for (name, item) in items { for (name, item) in items {
program_memory program_memory
.add(name.clone(), item.clone(), SourceRange::default()) .add(name.clone(), item.clone(), SourceRange::default())
@ -1877,7 +1877,7 @@ mod test {
} }
// Declare the test cases. // Declare the test cases.
for (test_name, params, args, expected) in [ for (test_name, params, args, expected) in [
("empty", Vec::new(), Vec::new(), Ok(ProgramMemory::new())), ("empty", Vec::new(), Vec::new(), Ok(additional_program_memory(&[]))),
( (
"all params required, and all given, should be OK", "all params required, and all given, should be OK",
vec![req_param("x")], vec![req_param("x")],
@ -1945,6 +1945,7 @@ mod test {
}); });
let args = args.into_iter().map(Arg::synthetic).collect(); let args = args.into_iter().map(Arg::synthetic).collect();
let mut exec_state = ExecState::new(&Default::default()); let mut exec_state = ExecState::new(&Default::default());
exec_state.mut_memory().init_for_tests();
let actual = assign_args_to_params(func_expr, args, &mut exec_state).map(|_| exec_state.global.memory); let actual = assign_args_to_params(func_expr, args, &mut exec_state).map(|_| exec_state.global.memory);
assert_eq!( assert_eq!(
actual, expected, actual, expected,

View File

@ -4,7 +4,7 @@ use anyhow::Result;
use kcmc::{ use kcmc::{
coord::{System, KITTYCAD}, coord::{System, KITTYCAD},
each_cmd as mcmd, each_cmd as mcmd,
format::InputFormat, format::InputFormat3d,
ok_response::OkModelingCmdResponse, ok_response::OkModelingCmdResponse,
shared::FileImportFormat, shared::FileImportFormat,
units::UnitLength, units::UnitLength,
@ -32,7 +32,7 @@ pub const ZOO_COORD_SYSTEM: System = *KITTYCAD;
pub async fn import_foreign( pub async fn import_foreign(
file_path: &Path, file_path: &Path,
format: Option<InputFormat>, format: Option<InputFormat3d>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
ctxt: &ExecutorContext, ctxt: &ExecutorContext,
source_range: SourceRange, source_range: SourceRange,
@ -98,7 +98,7 @@ pub async fn import_foreign(
// In the case of a gltf importing a bin file we need to handle that! and figure out where the // In the case of a gltf importing a bin file we need to handle that! and figure out where the
// file is relative to our current file. // file is relative to our current file.
if let InputFormat::Gltf(..) = format { if let InputFormat3d::Gltf(..) = format {
// Check if the file is a binary gltf file, in that case we don't need to import the bin // Check if the file is a binary gltf file, in that case we don't need to import the bin
// file. // file.
if !file_contents.starts_with(b"glTF") { if !file_contents.starts_with(b"glTF") {
@ -158,7 +158,7 @@ pub(super) fn format_from_annotations(
annotations: &[Node<Annotation>], annotations: &[Node<Annotation>],
path: &Path, path: &Path,
import_source_range: SourceRange, import_source_range: SourceRange,
) -> Result<Option<InputFormat>, KclError> { ) -> Result<Option<InputFormat3d>, KclError> {
if annotations.is_empty() { if annotations.is_empty() {
return Ok(None); return Ok(None);
} }
@ -220,7 +220,7 @@ pub(super) fn format_from_annotations(
Ok(Some(result)) Ok(Some(result))
} }
fn set_coords(fmt: &mut InputFormat, coords_str: &str, source_range: SourceRange) -> Result<(), KclError> { fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRange) -> Result<(), KclError> {
let mut coords = None; let mut coords = None;
for (name, val) in annotations::IMPORT_COORDS_VALUES { for (name, val) in annotations::IMPORT_COORDS_VALUES {
if coords_str == name { if coords_str == name {
@ -243,9 +243,9 @@ fn set_coords(fmt: &mut InputFormat, coords_str: &str, source_range: SourceRange
}; };
match fmt { match fmt {
InputFormat::Obj(opts) => opts.coords = coords, InputFormat3d::Obj(opts) => opts.coords = coords,
InputFormat::Ply(opts) => opts.coords = coords, InputFormat3d::Ply(opts) => opts.coords = coords,
InputFormat::Stl(opts) => opts.coords = coords, InputFormat3d::Stl(opts) => opts.coords = coords,
_ => { _ => {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
@ -260,13 +260,13 @@ fn set_coords(fmt: &mut InputFormat, coords_str: &str, source_range: SourceRange
Ok(()) Ok(())
} }
fn set_length_unit(fmt: &mut InputFormat, units_str: &str, source_range: SourceRange) -> Result<(), KclError> { fn set_length_unit(fmt: &mut InputFormat3d, units_str: &str, source_range: SourceRange) -> Result<(), KclError> {
let units = UnitLen::from_str(units_str, source_range)?; let units = UnitLen::from_str(units_str, source_range)?;
match fmt { match fmt {
InputFormat::Obj(opts) => opts.units = units.into(), InputFormat3d::Obj(opts) => opts.units = units.into(),
InputFormat::Ply(opts) => opts.units = units.into(), InputFormat3d::Ply(opts) => opts.units = units.into(),
InputFormat::Stl(opts) => opts.units = units.into(), InputFormat3d::Stl(opts) => opts.units = units.into(),
_ => { _ => {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
@ -320,7 +320,7 @@ pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) ->
} }
/// Get the source format from the extension. /// Get the source format from the extension.
fn get_import_format_from_extension(ext: &str) -> Result<InputFormat> { fn get_import_format_from_extension(ext: &str) -> Result<InputFormat3d> {
let format = match FileImportFormat::from_str(ext) { let format = match FileImportFormat::from_str(ext) {
Ok(format) => format, Ok(format) => format,
Err(_) => { Err(_) => {
@ -343,44 +343,44 @@ fn get_import_format_from_extension(ext: &str) -> Result<InputFormat> {
// * Up: +Z // * Up: +Z
// * Handedness: Right // * Handedness: Right
match format { match format {
FileImportFormat::Step => Ok(InputFormat::Step(kcmc::format::step::import::Options { FileImportFormat::Step => Ok(InputFormat3d::Step(kcmc::format::step::import::Options {
split_closed_faces: false, split_closed_faces: false,
})), })),
FileImportFormat::Stl => Ok(InputFormat::Stl(kcmc::format::stl::import::Options { FileImportFormat::Stl => Ok(InputFormat3d::Stl(kcmc::format::stl::import::Options {
coords: ZOO_COORD_SYSTEM, coords: ZOO_COORD_SYSTEM,
units: ul, units: ul,
})), })),
FileImportFormat::Obj => Ok(InputFormat::Obj(kcmc::format::obj::import::Options { FileImportFormat::Obj => Ok(InputFormat3d::Obj(kcmc::format::obj::import::Options {
coords: ZOO_COORD_SYSTEM, coords: ZOO_COORD_SYSTEM,
units: ul, units: ul,
})), })),
FileImportFormat::Gltf => Ok(InputFormat::Gltf(kcmc::format::gltf::import::Options {})), FileImportFormat::Gltf => Ok(InputFormat3d::Gltf(kcmc::format::gltf::import::Options {})),
FileImportFormat::Ply => Ok(InputFormat::Ply(kcmc::format::ply::import::Options { FileImportFormat::Ply => Ok(InputFormat3d::Ply(kcmc::format::ply::import::Options {
coords: ZOO_COORD_SYSTEM, coords: ZOO_COORD_SYSTEM,
units: ul, units: ul,
})), })),
FileImportFormat::Fbx => Ok(InputFormat::Fbx(kcmc::format::fbx::import::Options {})), FileImportFormat::Fbx => Ok(InputFormat3d::Fbx(kcmc::format::fbx::import::Options {})),
FileImportFormat::Sldprt => Ok(InputFormat::Sldprt(kcmc::format::sldprt::import::Options { FileImportFormat::Sldprt => Ok(InputFormat3d::Sldprt(kcmc::format::sldprt::import::Options {
split_closed_faces: false, split_closed_faces: false,
})), })),
} }
} }
fn validate_extension_format(ext: InputFormat, given: InputFormat) -> Result<()> { fn validate_extension_format(ext: InputFormat3d, given: InputFormat3d) -> Result<()> {
if let InputFormat::Stl(_) = ext { if let InputFormat3d::Stl(_) = ext {
if let InputFormat::Stl(_) = given { if let InputFormat3d::Stl(_) = given {
return Ok(()); return Ok(());
} }
} }
if let InputFormat::Obj(_) = ext { if let InputFormat3d::Obj(_) = ext {
if let InputFormat::Obj(_) = given { if let InputFormat3d::Obj(_) = given {
return Ok(()); return Ok(());
} }
} }
if let InputFormat::Ply(_) = ext { if let InputFormat3d::Ply(_) = ext {
if let InputFormat::Ply(_) = given { if let InputFormat3d::Ply(_) = given {
return Ok(()); return Ok(());
} }
} }
@ -396,15 +396,15 @@ fn validate_extension_format(ext: InputFormat, given: InputFormat) -> Result<()>
) )
} }
fn get_name_of_format(type_: InputFormat) -> &'static str { fn get_name_of_format(type_: InputFormat3d) -> &'static str {
match type_ { match type_ {
InputFormat::Fbx(_) => "fbx", InputFormat3d::Fbx(_) => "fbx",
InputFormat::Gltf(_) => "gltf", InputFormat3d::Gltf(_) => "gltf",
InputFormat::Obj(_) => "obj", InputFormat3d::Obj(_) => "obj",
InputFormat::Ply(_) => "ply", InputFormat3d::Ply(_) => "ply",
InputFormat::Sldprt(_) => "sldprt", InputFormat3d::Sldprt(_) => "sldprt",
InputFormat::Step(_) => "step", InputFormat3d::Step(_) => "step",
InputFormat::Stl(_) => "stl", InputFormat3d::Stl(_) => "stl",
} }
} }
@ -430,7 +430,7 @@ mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
fmt, fmt,
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {}) InputFormat3d::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
); );
// format, no options // format, no options
@ -442,7 +442,7 @@ mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
fmt, fmt,
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {}) InputFormat3d::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
); );
// format, no extension (wouldn't parse but might some day) // format, no extension (wouldn't parse but might some day)
@ -451,7 +451,7 @@ mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
fmt, fmt,
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {}) InputFormat3d::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
); );
// format, options // format, options
@ -463,7 +463,7 @@ mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
fmt, fmt,
InputFormat::Obj(kittycad_modeling_cmds::format::obj::import::Options { InputFormat3d::Obj(kittycad_modeling_cmds::format::obj::import::Options {
coords: *kittycad_modeling_cmds::coord::VULKAN, coords: *kittycad_modeling_cmds::coord::VULKAN,
units: kittycad_modeling_cmds::units::UnitLength::Feet, units: kittycad_modeling_cmds::units::UnitLength::Feet,
}) })
@ -478,7 +478,7 @@ mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
fmt, fmt,
InputFormat::Obj(kittycad_modeling_cmds::format::obj::import::Options { InputFormat3d::Obj(kittycad_modeling_cmds::format::obj::import::Options {
coords: *kittycad_modeling_cmds::coord::VULKAN, coords: *kittycad_modeling_cmds::coord::VULKAN,
units: kittycad_modeling_cmds::units::UnitLength::Feet, units: kittycad_modeling_cmds::units::UnitLength::Feet,
}) })

View File

@ -17,8 +17,8 @@ use crate::{
}, },
token::NumericSuffix, token::NumericSuffix,
}, },
std::{args::Arg, FnAsArg}, std::{args::Arg, StdFnProps},
KclError, ModuleId, SourceRange, CompilationError, KclError, ModuleId, SourceRange,
}; };
pub type KclObjectFields = HashMap<String, KclValue>; pub type KclObjectFields = HashMap<String, KclValue>;
@ -86,11 +86,7 @@ pub enum KclValue {
#[ts(skip)] #[ts(skip)]
Function { Function {
#[serde(skip)] #[serde(skip)]
func: Option<crate::std::StdFn>, value: FunctionSource,
#[schemars(skip)]
expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
// Invariant: Always Some except for std lib functions
memory: Option<EnvironmentRef>,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
meta: Vec<Metadata>, meta: Vec<Metadata>,
}, },
@ -112,6 +108,31 @@ pub enum KclValue {
}, },
} }
#[derive(Debug, Clone, PartialEq, Default)]
pub enum FunctionSource {
#[default]
None,
Std {
func: crate::std::StdFn,
props: StdFnProps,
},
User {
ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
memory: EnvironmentRef,
},
}
impl JsonSchema for FunctionSource {
fn schema_name() -> String {
"FunctionSource".to_owned()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
// TODO: Actually generate a reasonable schema.
gen.subschema_for::<()>()
}
}
impl From<SketchSet> for KclValue { impl From<SketchSet> for KclValue {
fn from(sg: SketchSet) -> Self { fn from(sg: SketchSet) -> Self {
match sg { match sg {
@ -230,12 +251,16 @@ impl KclValue {
} }
pub(crate) fn function_def_source_range(&self) -> Option<SourceRange> { pub(crate) fn function_def_source_range(&self) -> Option<SourceRange> {
let KclValue::Function { expression, .. } = self else { let KclValue::Function {
value: FunctionSource::User { ast, .. },
..
} = self
else {
return None; return None;
}; };
// TODO: It would be nice if we could extract the source range starting // TODO: It would be nice if we could extract the source range starting
// at the fn, but that's the variable declaration. // at the fn, but that's the variable declaration.
Some(expression.as_source_range()) Some(ast.as_source_range())
} }
pub(crate) fn get_solid_set(&self) -> Result<SolidSet> { pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
@ -322,7 +347,7 @@ impl KclValue {
pub(crate) fn map_env_ref(&self, env_map: &HashMap<EnvironmentRef, EnvironmentRef>) -> Self { pub(crate) fn map_env_ref(&self, env_map: &HashMap<EnvironmentRef, EnvironmentRef>) -> Self {
let mut result = self.clone(); let mut result = self.clone();
if let KclValue::Function { if let KclValue::Function {
memory: Some(ref mut memory), value: FunctionSource::User { ref mut memory, .. },
.. ..
} = result } = result
{ {
@ -486,21 +511,11 @@ impl KclValue {
} }
/// If this value is of type function, return it. /// If this value is of type function, return it.
pub fn get_function(&self) -> Option<FnAsArg<'_>> { pub fn get_function(&self) -> Option<&FunctionSource> {
let KclValue::Function { match self {
func, KclValue::Function { value, .. } => Some(value),
expression, _ => None,
memory, }
meta: _,
} = &self
else {
return None;
};
Some(FnAsArg {
func: func.as_ref(),
expr: expression.to_owned(),
memory: *memory,
})
} }
/// Get a tag identifier from a memory item. /// Get a tag identifier from a memory item.
@ -556,38 +571,39 @@ impl KclValue {
ctx: ExecutorContext, ctx: ExecutorContext,
source_range: SourceRange, source_range: SourceRange,
) -> Result<Option<KclValue>, KclError> { ) -> Result<Option<KclValue>, KclError> {
let KclValue::Function { match self {
func, KclValue::Function {
expression, value: FunctionSource::Std { func, props },
memory: closure_memory, ..
.. } => {
} = &self if props.deprecated {
else { exec_state.warn(CompilationError::err(
return Err(KclError::Semantic(KclErrorDetails { source_range,
message: "not an in-memory function".to_string(), format!(
source_ranges: vec![], "`{}` is deprecated, see the docs for a recommended replacement",
})); props.name
}; ),
if let Some(func) = func { ));
exec_state.mut_memory().push_new_env_for_rust_call(); }
let args = crate::std::Args::new( exec_state.mut_memory().push_new_env_for_rust_call();
args, let args = crate::std::Args::new(
source_range, args,
ctx.clone(), source_range,
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic), ctx.clone(),
); exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
let result = func(exec_state, args).await.map(Some); );
exec_state.mut_memory().pop_env(); let result = func(exec_state, args).await.map(Some);
result exec_state.mut_memory().pop_env();
} else { result
crate::execution::exec_ast::call_user_defined_function( }
args, KclValue::Function {
closure_memory.unwrap(), value: FunctionSource::User { ast, memory },
expression.as_ref(), ..
exec_state, } => crate::execution::exec_ast::call_user_defined_function(args, *memory, ast, exec_state, &ctx).await,
&ctx, _ => Err(KclError::Semantic(KclErrorDetails {
) message: "cannot call this because it isn't a function".to_string(),
.await source_ranges: vec![source_range],
})),
} }
} }
@ -600,29 +616,33 @@ impl KclValue {
ctx: ExecutorContext, ctx: ExecutorContext,
callsite: SourceRange, callsite: SourceRange,
) -> Result<Option<KclValue>, KclError> { ) -> Result<Option<KclValue>, KclError> {
let KclValue::Function { match self {
func, KclValue::Function {
expression, value: FunctionSource::Std { func: _, props },
memory: closure_memory, ..
meta: _, } => {
} = &self if props.deprecated {
else { exec_state.warn(CompilationError::err(
return Err(KclError::Semantic(KclErrorDetails { callsite,
format!(
"`{}` is deprecated, see the docs for a recommended replacement",
props.name
),
));
}
todo!("Implement KCL stdlib fns with keyword args");
}
KclValue::Function {
value: FunctionSource::User { ast, memory },
..
} => {
crate::execution::exec_ast::call_user_defined_function_kw(args.kw_args, *memory, ast, exec_state, &ctx)
.await
}
_ => Err(KclError::Semantic(KclErrorDetails {
message: "cannot call this because it isn't a function".to_string(), message: "cannot call this because it isn't a function".to_string(),
source_ranges: vec![callsite], source_ranges: vec![callsite],
})); })),
};
if let Some(_func) = func {
todo!("Implement calling KCL stdlib fns that are aliased. Part of https://github.com/KittyCAD/modeling-app/issues/4600");
} else {
crate::execution::exec_ast::call_user_defined_function_kw(
args.kw_args,
closure_memory.unwrap(),
expression.as_ref(),
exec_state,
&ctx,
)
.await
} }
} }
} }

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