Compare commits

...

47 Commits

Author SHA1 Message Date
888104080e bump v0.9.0 (#673) 2023-09-21 10:38:40 +10:00
b6769889e3 Handle relative paths at kcl level (#506)
* handle relative paths at kcl level

* fmt

* update kittycad

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

* updates

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

* bump

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

* fix tests

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-09-21 10:36:26 +10:00
a32258dac4 Engine manager can be cloned (#671) 2023-09-20 16:22:47 -07:00
18dbbad244 Use an actor to manage the Tokio engine connection (#669)
* Use an actor to manage the Tokio engine connection

This means EngineManager trait's methods take &self not &mut self, and the tokio implementation can be cloned.

* Clean up code
2023-09-20 16:59:03 -05:00
b67c16cc9d Benchmark for KCL parser (#664)
* KCL benchmarks

* CI for benchmarks

* More specific name for benchmark

* Benchmark the right directory

* Format
2023-09-20 13:15:28 -05:00
ad482641ef Unit test for zero-param programs (#663) 2023-09-20 10:51:49 -05:00
9ee24845a1 Bump to v0.8.2 (#656)
Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-09-20 14:41:23 +00:00
e69d263252 Revert swapping setCode for deferredSetCode in App (#662)
This fix was implemented in https://github.com/KittyCAD/modeling-app/pull/649
to try and address https://github.com/KittyCAD/modeling-app/issues/545.
However, we need to run `setCode` to execute immediately here or else
files will not render in the 3D view when opened, as reported by @pierremtb.

Reverting for now to allow for a timely release https://github.com/KittyCAD/modeling-app/pull/656

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-09-20 10:32:36 -04:00
111738f38e Fix the debug panel overflow (#653) 2023-09-20 05:36:29 -04:00
e34501cc5a Ast fixes (#650)
* allow using member expressions for memory items

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

* fixes pi in binary expressions

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

* add fix

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-19 16:05:53 -07:00
c767c1c3a6 Replace setCode with deferredSetCode in App (#649)
* Replace `setCode` with `deferredSetCode` in App

* Remove unused OpenFileButton component
2023-09-19 22:07:54 +00:00
e399a8f938 Franknoirot/ux papercuts 4 (#640)
* Add Cmd + / to support windows, update walkthrough

* Fix #628 dark mode <select> bg color

* Fix #621 by narrowing margins and moving to left

---------

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-09-19 18:06:13 -04:00
59d5f2524a fix function inside show not executing (#641)
* start of heap changes

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

* fix show bug

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

* cleanup

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

* new images

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-19 14:20:14 -07:00
b47ebd14d2 Bump to v0.8.1 (#637)
Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-09-19 15:55:55 -04:00
e74bcd0695 make it so the lsp server doesnt vom on restart (#636)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-19 15:17:34 -04:00
22161ec386 Variables needs to scroll (#624)
* Variables needs to scroll
Fixes #609

* Run yarn fmt

---------

Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-09-19 18:29:22 +00:00
ada46c4317 Fix Tauri auth in development (#635)
* Fix Tauri auth in development

* Fix Rust formatting
2023-09-19 14:08:26 -04:00
6675fa8d1e UX Papercuts 3: use absolute paths, add error page with buttons to help refresh, etc (#615)
* Fix #593: don't prevent default on link click

* Use absolute/explicit path for settings
Trying to test fix for #594

* Broken: replace almost all relative URLs with absolute

* Clean up to use clean useDismiss with absolute path

* Merge branch 'main' into franknoirot/ux-papercuts-3a

* Add buttons to home, reload, clear, and bug report on error screen
2023-09-19 14:06:56 -04:00
075d2debce Bump to v0.8.0 (#561)
Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-09-19 00:22:18 -04:00
488e41ac0e Fix docs link, test fixing relative URLs in Windows (#606)
* Fix #593: don't prevent default on link click

* Use absolute/explicit path for settings
Trying to test fix for #594

* Broken: replace almost all relative URLs with absolute

* add relative jump backs util

* dot dot slash everywhere

* use usLocation not window.location

* the one that got away

* fmt 🤦‍♂️

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2023-09-19 13:55:14 +10:00
8147f5f1eb CLI-only code behind a feature flag (#614) 2023-09-18 21:38:40 -05:00
bc7e9d9789 non wasm engine errors (#612)
updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-18 17:31:11 -07:00
8d493d6517 Cargo update (#611) 2023-09-18 17:22:25 -07:00
9fa98d6f3f Don't wipe cache for yarn build:wasm (#610)
You can still run a clean build (no cache) with yarn build:wasm-clean
2023-09-18 17:10:16 -07:00
24a31c94e7 Typo: noneCode => nonCode (#607) 2023-09-18 18:14:12 -05:00
76e3207251 recast fix (#608)
fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-18 15:56:11 -07:00
e2237fa9f6 use deferredSetCode so the 3D view resets (#604) 2023-09-18 17:44:25 -04:00
ae4aa82129 windows fix (#598)
Break out windows sed in CI action

Add run-script-os, break apart find-replace for each

Fix importmeta command typos

Fix windows commands in packag.json and ci.yml
I actually ran these on my windows machine

Update package.json

call remove-import-meta from ci



fucking sed



updates



only remove import meta pre tests



updates



fix order

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-18 13:59:40 -07:00
14b287a746 Franknoirot/ux papercuts 1 (#596)
* Properly show dark logo in System-light theme

* Fix linting errors for fill-rule and clip-rule

* Support system-light theme on Parametric Modeling step as well

Signed-off-by: Frank Noirot <frank@kittycad.io>

* Fix line number ref in Parametric Modeling copy

Signed-off-by: Frank Noirot <frank@kittycad.io>

* Copyediting tweaks

* Fix part name disappearing even when there is space

---------

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-09-18 13:13:04 -07:00
dd1b7631fa round two decimal places on move (#591)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-18 16:42:53 +10:00
f98f782b40 Bump syn from 2.0.33 to 2.0.37 in /src/wasm-lib (#586)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.33 to 2.0.37.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.33...2.0.37)

---
updated-dependencies:
- dependency-name: syn
  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>
2023-09-17 23:40:33 -07:00
01f5ecdc36 Bump schemars from 0.8.13 to 0.8.15 in /src/wasm-lib (#587)
Bumps [schemars](https://github.com/GREsau/schemars) from 0.8.13 to 0.8.15.
- [Release notes](https://github.com/GREsau/schemars/releases)
- [Changelog](https://github.com/GREsau/schemars/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GREsau/schemars/compare/v0.8.13...v0.8.15)

---
updated-dependencies:
- dependency-name: schemars
  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>
2023-09-17 23:38:05 -07:00
5297d3e142 both edit and move in one PR (#566)
* get the data for where lines are

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

* make pretty

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

* updates

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

* fmt

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

* new shit

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

* beginning of stufff

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

* cleanup

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

* updates

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

* add new fns

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

* basic function

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

* fix ups to keep order

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

* further

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

* failing test

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

* do it in rust

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

* trait

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

* start of ui integration

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

* updates

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

* weird shit

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

* generate close on close

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

* start of constraint functions

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

* helper functions

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

* make work

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

* updates

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

* constraints w ranges

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

* updates

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

* fmt

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

* skip

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

* fixes

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

* fixes

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

* comment

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

* fixes

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

* throw

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

* make close a bit less sensitive in move scenario

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

* cleanup shit we didnt end up using

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

* make it less hard to close

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

* Fix edit after sketch

* Move to plane for sketch

* Fix pathToNode for ast mods

* Fix exit sketch mode with escape

* Fix fmt since my editor did it wrong

* updates

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

* fix link

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Adam Sunderland <adam@kittycad.io>
2023-09-17 21:57:43 -07:00
f71f44968b remove unneeded import (#565) 2023-09-17 09:19:53 +00:00
7b79998c40 put .env.dev back (#564) 2023-09-17 09:17:14 +00:00
4632d407c1 Sign macOS builds (#539)
* Sign macOS builds
Fixes #295

* Try without creds, only cert

* Add more creds
2023-09-16 13:41:07 -04:00
58d7e59ca4 Let tauri-action perform Windows signing (#535)
* Check if tauri-action could sign Windows builds automatically
Fixes #533

* Cleanup

* Change to sha256

* Clean up
2023-09-16 04:48:32 -04:00
f592d8db84 Franknoirot/pretty buttons (#550) 2023-09-16 01:23:11 -04:00
31eca3728e add sample script as integration test (#559)
* updates

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-15 20:45:28 -07:00
c5d8779af4 Update onboarding to be more complete (#551)
* Update Introduction

* Update Camera step

* Change link to expectations

Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com>

* Set outline for onboarding

* Add Streaming step

* Remove Units step

* Add default kcl script

* Add Code Editor step

* Add Parametric Modeling step

* Add Interactive Numbers step

* Update bracket to use sqrt

* Add Command K step

* Assuage @jessfraz's code itchies

* Add User Menu step

* Add Project Menu step

* Add Export step

* Improve error page to actually show error

* Update the sketch step

* Add Future Work section

* Bring back the bracket code on the final step

* Set up the code to the bracket when starting onboarding

* Fix missing import

* Don't throw away users code if not empty

* Prompt the user if they have content in their file

---------

Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com>
2023-09-16 02:37:40 +00:00
cf686bdeb0 adds more math functions and fixes parens (#558)
* nested parens fix

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

* e, tau

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

* docs

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

* remove test w log since that is a stdlib fn now

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-15 17:40:57 -07:00
ae7143a94f add a bunch of math stdlib fns (#555)
add a bunch of math stuff

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-15 15:54:49 -07:00
f2b24849b3 Move calls to the unreliable channel into a helper method (#543)
This'll let us handle JSON encoding in one place, as well as adding any
logging or anything. Not a huge deal, but it's a minor cleanup I made
while looking into an unrelated bug.

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>
2023-09-15 17:14:58 -04:00
35d6530406 Bump actions/checkout from 3 to 4 (#379)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 13:20:59 -07:00
01208221c7 Bump serde_json from 1.0.106 to 1.0.107 in /src/wasm-lib (#518)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.106 to 1.0.107.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.106...v1.0.107)

---
updated-dependencies:
- dependency-name: serde_json
  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>
2023-09-15 13:20:43 -07:00
fbbed3fbfb Bump serde_json from 1.0.106 to 1.0.107 in /src-tauri (#519)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.106 to 1.0.107.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.106...v1.0.107)

---
updated-dependencies:
- dependency-name: serde_json
  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>
2023-09-15 13:20:22 -07:00
ce51f26701 fix negative word binary expression (#549)
* fix negative word binary expression

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-15 13:19:53 -07:00
113 changed files with 5958 additions and 1055 deletions

View File

@ -1,6 +1,6 @@
VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.kittycad.io VITE_KC_API_BASE_URL=https://api.dev.kittycad.io
VITE_KC_SITE_BASE_URL=https://kittycad.io VITE_KC_SITE_BASE_URL=https://dev.kittycad.io
VITE_KC_SKIP_AUTH=false VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000 VITE_KC_CONNECTION_TIMEOUT_MS=5000
VITE_KC_SENTRY_DSN= VITE_KC_SENTRY_DSN=

View File

@ -24,7 +24,7 @@ jobs:
matrix: matrix:
dir: ['src/wasm-lib'] dir: ['src/wasm-lib']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install latest rust - name: Install latest rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1

View File

@ -24,7 +24,7 @@ jobs:
matrix: matrix:
dir: ['src/wasm-lib'] dir: ['src/wasm-lib']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install latest rust - name: Install latest rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -54,4 +54,4 @@ jobs:
- name: Run clippy - name: Run clippy
run: | run: |
cd "${{ matrix.dir }}" cd "${{ matrix.dir }}"
cargo clippy --all --tests -- -D warnings cargo clippy --all --tests --benches -- -D warnings

37
.github/workflows/cargo-criterion.yml vendored Normal file
View File

@ -0,0 +1,37 @@
on:
push:
branches:
- main
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-criterion.yml
pull_request:
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-criterion.yml
workflow_dispatch:
permissions: read-all
name: cargo criterion
jobs:
cargocriterion:
name: cargo criterion
runs-on: ubuntu-latest-8-cores
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
run: |
cargo install cargo-criterion
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1
- name: Benchmark kcl library
shell: bash
run: |-
cd src/wasm-lib/kcl; cargo criterion

View File

@ -27,7 +27,7 @@ jobs:
matrix: matrix:
dir: ['src/wasm-lib', 'src-tauri'] dir: ['src/wasm-lib', 'src-tauri']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install latest rust - name: Install latest rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:

View File

@ -26,7 +26,7 @@ jobs:
matrix: matrix:
dir: ['src/wasm-lib'] dir: ['src/wasm-lib']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install latest rust - name: Install latest rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -55,7 +55,7 @@ jobs:
shell: bash shell: bash
run: |- run: |-
cd "${{ matrix.dir }}" cd "${{ matrix.dir }}"
cargo test --all cargo nextest run --workspace --no-fail-fast -P ci
env: env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}

View File

@ -9,11 +9,10 @@ on:
types: [published] types: [published]
jobs: jobs:
check-format: check-format:
runs-on: 'ubuntu-20.04' runs-on: 'ubuntu-20.04'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
@ -25,7 +24,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
@ -33,19 +32,17 @@ jobs:
- run: yarn install - run: yarn install
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
workspaces: "./src/wasm-lib" workspaces: './src/wasm-lib'
- run: yarn build:wasm - run: yarn build:wasm
- run: yarn tsc - run: yarn tsc
build-test-web: build-test-web:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
outputs: outputs:
version: ${{ steps.export_version.outputs.version }} version: ${{ steps.export_version.outputs.version }}
steps: steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
@ -56,7 +53,7 @@ jobs:
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
workspaces: "./src/wasm-lib" workspaces: './src/wasm-lib'
- run: yarn build:wasm - run: yarn build:wasm
@ -69,7 +66,6 @@ jobs:
- id: export_version - id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT" run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
build-apps: build-apps:
needs: [check-format, build-test-web, check-types] needs: [check-format, build-test-web, check-types]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -77,8 +73,7 @@ jobs:
matrix: matrix:
os: [macos-latest, ubuntu-20.04, windows-latest] os: [macos-latest, ubuntu-20.04, windows-latest]
steps: steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: install ubuntu system dependencies - name: install ubuntu system dependencies
if: matrix.os == 'ubuntu-20.04' if: matrix.os == 'ubuntu-20.04'
@ -104,7 +99,7 @@ jobs:
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
workspaces: "./src/wasm-lib" workspaces: './src/wasm-lib'
- name: wasm prep - name: wasm prep
shell: bash shell: bash
@ -114,18 +109,6 @@ jobs:
cd ../../ cd ../../
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
- name: macos sed
if: matrix.os == 'macos-latest'
shell: bash
run: |
sed -i '' 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
- name: ubuntu and windows sed
if: matrix.os != 'macos-latest'
shell: bash
run: |
sed -i 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
- name: Fix format - name: Fix format
run: yarn fmt run: yarn fmt
@ -134,38 +117,11 @@ jobs:
run: | run: |
rustup target add aarch64-apple-darwin rustup target add aarch64-apple-darwin
- name: Build the app for the current platform (no upload) - name: Prepare Windows certificate and variables
uses: tauri-apps/tauri-action@v0 if: matrix.os == 'windows-latest'
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
- uses: actions/upload-artifact@v3
with:
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
sign-windows-msi:
runs-on: windows-latest
if: github.event_name == 'release'
needs: [build-test-web, build-apps]
env:
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
steps:
- uses: actions/download-artifact@v3
- name: Setup Certificate
run: | run: |
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
cat /d/Certificate_pkcs12.p12 cat /d/Certificate_pkcs12.p12
shell: bash
- name: Set variables
id: variables
run: |
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}" echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV" echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV" echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
@ -176,7 +132,8 @@ jobs:
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
shell: bash shell: bash
- name: Setup SSM KSP on windows latest - name: Setup Windows certicate with SSM KSP
if: matrix.os == 'windows-latest'
run: | run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
msiexec /i smtools-windows-x64.msi /quiet /qn msiexec /i smtools-windows-x64.msi /quiet /qn
@ -186,28 +143,32 @@ jobs:
smksp_cert_sync.exe smksp_cert_sync.exe
shell: cmd shell: cmd
- name: Signing using Signtool - name: Build and sign the app for the current platform
run: | uses: tauri-apps/tauri-action@v0
signtool.exe sign /sha1 ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "artifact\msi\*.msi" env:
signtool.exe verify /v /pa "artifact\msi\*.msi" TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
# TODO: for the updater, investigate if we need to also replace what's in the .zip, and what to do about the .sig file APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
with:
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
path: artifact/* path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
publish-apps-release: publish-apps-release:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
if: github.event_name == 'release' if: github.event_name == 'release'
needs: [build-test-web, build-apps, sign-windows-msi] needs: [build-test-web, build-apps]
env: env:
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }} VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
PUB_DATE: ${{ github.event.release.created_at }} PUB_DATE: ${{ github.event.release.created_at }}
NOTES: ${{ github.event.release.body }} NOTES: ${{ github.event.release.body }}
steps: steps:
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
- name: Generate the update static endpoint - name: Generate the update static endpoint

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3.5.0 - uses: actions/checkout@v4
- shell: bash - shell: bash
run: | run: |
# checkout our branch # checkout our branch

2
.gitignore vendored
View File

@ -22,6 +22,8 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
src/wasm-lib/.idea
# rust # rust
src/wasm-lib/target src/wasm-lib/target
src/wasm-lib/bindings src/wasm-lib/bindings

View File

@ -1,4 +1,60 @@
[ [
{
"name": "abs",
"summary": "Computes the absolute value of a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "acos",
"summary": "Computes the arccosine of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "angleToMatchLengthX", "name": "angleToMatchLengthX",
"summary": "Returns the angle to match the given length for x.", "summary": "Returns the angle to match the given length for x.",
@ -7473,6 +7529,62 @@
"unpublished": false, "unpublished": false,
"deprecated": false "deprecated": false
}, },
{
"name": "asin",
"summary": "Computes the arcsine of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "atan",
"summary": "Computes the arctangent of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "bezierCurve", "name": "bezierCurve",
"summary": "Draw a bezier curve.", "summary": "Draw a bezier curve.",
@ -8446,6 +8558,34 @@
"unpublished": false, "unpublished": false,
"deprecated": false "deprecated": false
}, },
{
"name": "ceil",
"summary": "Computes the smallest integer greater than or equal to a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "close", "name": "close",
"summary": "Close the current sketch.", "summary": "Close the current sketch.",
@ -9350,6 +9490,24 @@
"unpublished": false, "unpublished": false,
"deprecated": false "deprecated": false
}, },
{
"name": "e",
"summary": "Return the value of Eulers number `e`.",
"description": "",
"tags": [],
"args": [],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "extrude", "name": "extrude",
"summary": "Extrudes by a given amount.", "summary": "Extrudes by a given amount.",
@ -9944,6 +10102,34 @@
"unpublished": false, "unpublished": false,
"deprecated": false "deprecated": false
}, },
{
"name": "floor",
"summary": "Computes the largest integer less than or equal to a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "getExtrudeWallTransform", "name": "getExtrudeWallTransform",
"summary": "Returns the extrude wall transform.", "summary": "Returns the extrude wall transform.",
@ -13015,9 +13201,161 @@
"unpublished": false, "unpublished": false,
"deprecated": false "deprecated": false
}, },
{
"name": "ln",
"summary": "Computes the natural logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log",
"summary": "Computes the logarithm of the number with respect to an arbitrary base.",
"description": "The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "base",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log10",
"summary": "Computes the base 10 logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log2",
"summary": "Computes the base 2 logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "max",
"summary": "Computes the maximum of the given arguments.",
"description": "",
"tags": [],
"args": [
{
"name": "args",
"type": "[number]",
"schema": {
"type": "array",
"items": {
"type": "number",
"format": "double"
}
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "min", "name": "min",
"summary": "Returns the minimum of the given arguments.", "summary": "Computes the minimum of the given arguments.",
"description": "", "description": "",
"tags": [], "tags": [],
"args": [ "args": [
@ -13048,7 +13386,7 @@
}, },
{ {
"name": "pi", "name": "pi",
"summary": "Return the value of `pi`.", "summary": "Return the value of `pi`. Archimedes constant (π).",
"description": "", "description": "",
"tags": [], "tags": [],
"args": [], "args": [],
@ -13064,6 +13402,43 @@
"unpublished": false, "unpublished": false,
"deprecated": false "deprecated": false
}, },
{
"name": "pow",
"summary": "Computes the number to a power.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "pow",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "segAng", "name": "segAng",
"summary": "Returns the angle of the segment.", "summary": "Returns the angle of the segment.",
@ -15376,6 +15751,34 @@
"unpublished": false, "unpublished": false,
"deprecated": false "deprecated": false
}, },
{
"name": "sqrt",
"summary": "Computes the square root of a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "startSketchAt", "name": "startSketchAt",
"summary": "Start a sketch at a given point.", "summary": "Start a sketch at a given point.",
@ -15891,6 +16294,24 @@
"unpublished": false, "unpublished": false,
"deprecated": false "deprecated": false
}, },
{
"name": "tau",
"summary": "Return the value of `tau`. The full circle constant (τ). Equal to 2π.",
"description": "",
"tags": [],
"args": [],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{ {
"name": "xLine", "name": "xLine",
"summary": "Draw a line on the x-axis.", "summary": "Draw a line on the x-axis.",

View File

@ -5,6 +5,8 @@
## Table of Contents ## Table of Contents
* [Functions](#functions) * [Functions](#functions)
* [`abs`](#abs)
* [`acos`](#acos)
* [`angleToMatchLengthX`](#angleToMatchLengthX) * [`angleToMatchLengthX`](#angleToMatchLengthX)
* [`angleToMatchLengthY`](#angleToMatchLengthY) * [`angleToMatchLengthY`](#angleToMatchLengthY)
* [`angledLine`](#angledLine) * [`angledLine`](#angledLine)
@ -14,10 +16,15 @@
* [`angledLineToX`](#angledLineToX) * [`angledLineToX`](#angledLineToX)
* [`angledLineToY`](#angledLineToY) * [`angledLineToY`](#angledLineToY)
* [`arc`](#arc) * [`arc`](#arc)
* [`asin`](#asin)
* [`atan`](#atan)
* [`bezierCurve`](#bezierCurve) * [`bezierCurve`](#bezierCurve)
* [`ceil`](#ceil)
* [`close`](#close) * [`close`](#close)
* [`cos`](#cos) * [`cos`](#cos)
* [`e`](#e)
* [`extrude`](#extrude) * [`extrude`](#extrude)
* [`floor`](#floor)
* [`getExtrudeWallTransform`](#getExtrudeWallTransform) * [`getExtrudeWallTransform`](#getExtrudeWallTransform)
* [`lastSegX`](#lastSegX) * [`lastSegX`](#lastSegX)
* [`lastSegY`](#lastSegY) * [`lastSegY`](#lastSegY)
@ -26,16 +33,24 @@
* [`legLen`](#legLen) * [`legLen`](#legLen)
* [`line`](#line) * [`line`](#line)
* [`lineTo`](#lineTo) * [`lineTo`](#lineTo)
* [`ln`](#ln)
* [`log`](#log)
* [`log10`](#log10)
* [`log2`](#log2)
* [`max`](#max)
* [`min`](#min) * [`min`](#min)
* [`pi`](#pi) * [`pi`](#pi)
* [`pow`](#pow)
* [`segAng`](#segAng) * [`segAng`](#segAng)
* [`segEndX`](#segEndX) * [`segEndX`](#segEndX)
* [`segEndY`](#segEndY) * [`segEndY`](#segEndY)
* [`segLen`](#segLen) * [`segLen`](#segLen)
* [`show`](#show) * [`show`](#show)
* [`sin`](#sin) * [`sin`](#sin)
* [`sqrt`](#sqrt)
* [`startSketchAt`](#startSketchAt) * [`startSketchAt`](#startSketchAt)
* [`tan`](#tan) * [`tan`](#tan)
* [`tau`](#tau)
* [`xLine`](#xLine) * [`xLine`](#xLine)
* [`xLineTo`](#xLineTo) * [`xLineTo`](#xLineTo)
* [`yLine`](#yLine) * [`yLine`](#yLine)
@ -44,6 +59,46 @@
## Functions ## Functions
### abs
Computes the absolute value of a number.
```
abs(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### acos
Computes the arccosine of a number (in radians).
```
acos(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### angleToMatchLengthX ### angleToMatchLengthX
Returns the angle to match the given length for x. Returns the angle to match the given length for x.
@ -1332,6 +1387,46 @@ arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
### asin
Computes the arcsine of a number (in radians).
```
asin(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### atan
Computes the arctangent of a number (in radians).
```
atan(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### bezierCurve ### bezierCurve
Draw a bezier curve. Draw a bezier curve.
@ -1497,6 +1592,26 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
### ceil
Computes the smallest integer greater than or equal to a number.
```
ceil(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### close ### close
Close the current sketch. Close the current sketch.
@ -1661,6 +1776,25 @@ cos(num: number) -> number
### e
Return the value of Eulers number `e`.
```
e() -> number
```
#### Arguments
#### Returns
* `number`
### extrude ### extrude
Extrudes by a given amount. Extrudes by a given amount.
@ -1770,6 +1904,26 @@ extrude(length: number, sketch_group: SketchGroup) -> ExtrudeGroup
### floor
Computes the largest integer less than or equal to a number.
```
floor(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### getExtrudeWallTransform ### getExtrudeWallTransform
Returns the extrude wall transform. Returns the extrude wall transform.
@ -2358,9 +2512,110 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
### ln
Computes the natural logarithm of the number.
```
ln(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### log
Computes the logarithm of the number with respect to an arbitrary base.
The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.
```
log(num: number, base: number) -> number
```
#### Arguments
* `num`: `number`
* `base`: `number`
#### Returns
* `number`
### log10
Computes the base 10 logarithm of the number.
```
log10(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### log2
Computes the base 2 logarithm of the number.
```
log2(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### max
Computes the maximum of the given arguments.
```
max(args: [number]) -> number
```
#### Arguments
* `args`: `[number]`
#### Returns
* `number`
### min ### min
Returns the minimum of the given arguments. Computes the minimum of the given arguments.
@ -2380,7 +2635,7 @@ min(args: [number]) -> number
### pi ### pi
Return the value of `pi`. Return the value of `pi`. Archimedes constant (π).
@ -2397,6 +2652,27 @@ pi() -> number
### pow
Computes the number to a power.
```
pow(num: number, pow: number) -> number
```
#### Arguments
* `num`: `number`
* `pow`: `number`
#### Returns
* `number`
### segAng ### segAng
Returns the angle of the segment. Returns the angle of the segment.
@ -2827,6 +3103,26 @@ sin(num: number) -> number
### sqrt
Computes the square root of a number.
```
sqrt(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### startSketchAt ### startSketchAt
Start a sketch at a given point. Start a sketch at a given point.
@ -2938,6 +3234,25 @@ tan(num: number) -> number
### tau
Return the value of `tau`. The full circle constant (τ). Equal to 2π.
```
tau() -> number
```
#### Arguments
#### Returns
* `number`
### xLine ### xLine
Draw a line on the x-axis. Draw a line on the x-axis.

View File

@ -1,6 +1,6 @@
{ {
"name": "untitled-app", "name": "untitled-app",
"version": "0.7.1", "version": "0.9.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.9.0", "@codemirror/autocomplete": "^6.9.0",
@ -10,7 +10,7 @@
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.13", "@headlessui/react": "^1.7.13",
"@headlessui/tailwindcss": "^0.2.0", "@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.37", "@kittycad/lib": "^0.0.38",
"@lezer/javascript": "^1.4.7", "@lezer/javascript": "^1.4.7",
"@open-rpc/client-js": "^1.8.1", "@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6", "@react-hook/resize-observer": "^1.2.6",
@ -62,15 +62,17 @@
"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",
"pretest": "yarn remove-importmeta",
"test": "vitest --mode development", "test": "vitest --mode development",
"test:nowatch": "vitest run --mode development", "test:nowatch": "vitest run --mode development",
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests)", "test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
"test:cov": "vitest run --coverage --mode development", "test:cov": "vitest run --coverage --mode development",
"simpleserver:ci": "http-server ./public --cors -p 3000 &", "simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver": "http-server ./public --cors -p 3000", "simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src", "fmt": "prettier --write ./src",
"fmt-check": "prettier --check ./src", "fmt-check": "prettier --check ./src",
"build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta", "build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
"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": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings", "wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src", "lint": "eslint --fix src",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

46
public/kcma-logomark.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

30
src-tauri/Cargo.lock generated
View File

@ -2182,6 +2182,17 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "os_info"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
dependencies = [
"log",
"serde",
"winapi",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -3152,9 +3163,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.106" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [ dependencies = [
"itoa 1.0.6", "itoa 1.0.6",
"ryu", "ryu",
@ -3469,6 +3480,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sys-locale"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee"
dependencies = [
"js-sys",
"libc",
"wasm-bindgen",
"web-sys",
"windows-sys 0.45.0",
]
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "5.0.0" version = "5.0.0"
@ -3604,6 +3628,7 @@ dependencies = [
"objc", "objc",
"once_cell", "once_cell",
"open", "open",
"os_info",
"percent-encoding", "percent-encoding",
"rand 0.8.5", "rand 0.8.5",
"raw-window-handle", "raw-window-handle",
@ -3616,6 +3641,7 @@ dependencies = [
"serde_repr", "serde_repr",
"serialize-to-javascript", "serialize-to-javascript",
"state", "state",
"sys-locale",
"tar", "tar",
"tauri-macros", "tauri-macros",
"tauri-runtime", "tauri-runtime",

View File

@ -20,7 +20,7 @@ kittycad = "0.2.25"
oauth2 = "4.4.2" oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tauri = { version = "1.4.1", features = ["dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] } tauri = { version = "1.4.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tokio = { version = "1.32.0", features = ["time"] } tokio = { version = "1.32.0", features = ["time"] }
toml = "0.8.0" toml = "0.8.0"

View File

@ -6,6 +6,7 @@ use std::io::Read;
use anyhow::Result; use anyhow::Result;
use oauth2::TokenResponse; use oauth2::TokenResponse;
use tauri::{InvokeError, Manager}; use tauri::{InvokeError, Manager};
const DEFAULT_HOST: &str = "https://api.kittycad.io";
/// This command returns the a json string parse from a toml file at the path. /// This command returns the a json string parse from a toml file at the path.
#[tauri::command] #[tauri::command]
@ -88,11 +89,34 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
///This command returns the KittyCAD user info given a token. ///This command returns the KittyCAD user info given a token.
/// The string returned from this method is the user info as a json string. /// The string returned from this method is the user info as a json string.
#[tauri::command] #[tauri::command]
async fn get_user(token: Option<String>) -> Result<kittycad::types::User, InvokeError> { async fn get_user(
token: Option<String>,
hostname: &str,
) -> Result<kittycad::types::User, InvokeError> {
// Use the host passed in if it's set.
// Otherwise, use the default host.
let host = if hostname.is_empty() {
DEFAULT_HOST.to_string()
} else {
hostname.to_string()
};
// Change the baseURL to the one we want.
let mut baseurl = host.to_string();
if !host.starts_with("http://") && !host.starts_with("https://") {
baseurl = format!("https://{host}");
if host.starts_with("localhost") {
baseurl = format!("http://{host}")
}
}
println!("Getting user info..."); println!("Getting user info...");
// use kittycad library to fetch the user info from /user/me // use kittycad library to fetch the user info from /user/me
let client = kittycad::Client::new(token.unwrap()); let mut client = kittycad::Client::new(token.unwrap());
if baseurl != DEFAULT_HOST {
client.set_base_url(&baseurl);
}
let user_info: kittycad::types::User = client let user_info: kittycad::types::User = client
.users() .users()

View File

@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "kittycad-modeling", "productName": "kittycad-modeling",
"version": "0.7.1" "version": "0.9.0"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {
@ -36,6 +36,9 @@
"https://api.dev.kittycad.io/*" "https://api.dev.kittycad.io/*"
] ]
}, },
"os": {
"all": true
},
"shell": { "shell": {
"open": true "open": true
}, },
@ -71,9 +74,9 @@
"shortDescription": "", "shortDescription": "",
"targets": "all", "targets": "all",
"windows": { "windows": {
"certificateThumbprint": null, "certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
"digestAlgorithm": "sha256", "digestAlgorithm": "sha256",
"timestampUrl": "" "timestampUrl": "http://timestamp.digicert.com"
} }
}, },
"security": { "security": {

View File

@ -1,7 +1,6 @@
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react' import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
import { DebugPanel } from './components/DebugPanel' import { DebugPanel } from './components/DebugPanel'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { _executor } from './lang/executor'
import { PaneType, useStore } from './useStore' import { PaneType, useStore } from './useStore'
import { Logs, KCLErrors } from './components/Logs' import { Logs, KCLErrors } from './components/Logs'
import { CollapsiblePanel } from './components/CollapsiblePanel' import { CollapsiblePanel } from './components/CollapsiblePanel'
@ -48,6 +47,7 @@ export function App() {
streamDimensions, streamDimensions,
guiMode, guiMode,
setGuiMode, setGuiMode,
executeAst,
} = useStore((s) => ({ } = useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
setGuiMode: s.setGuiMode, setGuiMode: s.setGuiMode,
@ -58,6 +58,7 @@ export function App() {
setOpenPanes: s.setOpenPanes, setOpenPanes: s.setOpenPanes,
didDragInStream: s.didDragInStream, didDragInStream: s.didDragInStream,
streamDimensions: s.streamDimensions, streamDimensions: s.streamDimensions,
executeAst: s.executeAst,
})) }))
const { const {
@ -88,12 +89,23 @@ export function App() {
if (guiMode.mode === 'sketch') { if (guiMode.mode === 'sketch') {
if (guiMode.sketchMode === 'selectFace') return if (guiMode.sketchMode === 'selectFace') return
if (guiMode.sketchMode === 'sketchEdit') { if (guiMode.sketchMode === 'sketchEdit') {
// TODO: share this with Toolbar's "Exit sketch" button
// exiting sketch should be done consistently across all exits
engineCommandManager?.sendSceneCommand({ engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' }, cmd: { type: 'edit_mode_exit' },
}) })
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' }) setGuiMode({ mode: 'default' })
// this is necessary to get the UI back into a consistent
// state right now, hopefully won't need to rerender
// when exiting sketch mode in the future
executeAst()
} else { } else {
engineCommandManager?.sendSceneCommand({ engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
@ -109,6 +121,7 @@ export function App() {
rotation: guiMode.rotation, rotation: guiMode.rotation,
position: guiMode.position, position: guiMode.position,
pathToNode: guiMode.pathToNode, pathToNode: guiMode.pathToNode,
pathId: guiMode.pathId,
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion // todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
}) })
} }
@ -117,8 +130,9 @@ export function App() {
} }
}) })
const paneOpacity = const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
onboardingStatus === onboardingPaths.CAMERA (p) => p === onboardingStatus
)
? 'opacity-20' ? 'opacity-20'
: didDragInStream : didDragInStream
? 'opacity-40' ? 'opacity-40'
@ -255,7 +269,7 @@ export function App() {
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100', 'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
}} }}
> >
<div className="h-full flex flex-col justify-between"> <div id="code-pane" className="h-full flex flex-col justify-between">
<CollapsiblePanel <CollapsiblePanel
title="Code" title="Code"
icon={faCode} icon={faCode}

View File

@ -6,9 +6,9 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
const { const {
auth: { state }, auth: { state },
} = useGlobalStateContext() } = useGlobalStateContext()
const isLoggedIn = state.matches('checkIfLoggedIn') const isLoggingIn = state.matches('checkIfLoggedIn')
return isLoggedIn ? ( return isLoggingIn ? (
<Loading>Loading KittyCAD Modeling App...</Loading> <Loading>Loading KittyCAD Modeling App...</Loading>
) : ( ) : (
<>{children}</> <>{children}</>

View File

@ -130,6 +130,7 @@ const router = createBrowserRouter(
path: paths.INDEX, path: paths.INDEX,
loader: () => loader: () =>
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'), isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
errorElement: <ErrorPage />,
}, },
{ {
path: paths.FILE + '/:id', path: paths.FILE + '/:id',
@ -140,7 +141,6 @@ const router = createBrowserRouter(
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />} {!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth> </Auth>
), ),
errorElement: <ErrorPage />,
id: paths.FILE, id: paths.FILE,
loader: async ({ loader: async ({
request, request,

View File

@ -47,15 +47,52 @@
@apply hover:bg-cool-20; @apply hover:bg-cool-20;
} }
.smallScrollbar::-webkit-scrollbar { .toolbarButtons::-webkit-scrollbar {
@apply h-0.5; @apply h-0.5;
} }
.smallScrollbar { .toolbarButtons {
@apply overflow-x-auto; @apply flex items-center overflow-x-auto;
scrollbar-width: thin; scrollbar-width: thin;
} }
.toolbarButtons button {
@apply text-chalkboard-90 bg-chalkboard-10/50 border-chalkboard-50 whitespace-nowrap;
display: inline-flex;
align-items: center;
justify-content: center;
@apply gap-1.5 p-0.5 pr-1;
@apply rounded-sm;
}
:global(.dark) .toolbarButtons button {
@apply text-chalkboard-30 bg-chalkboard-90/50 border-chalkboard-50;
}
.toolbarButtons button:hover {
@apply text-cool-90 bg-cool-10;
}
:global(.sketch) .toolbarButtons button:hover {
@apply text-fern-90 bg-fern-10;
}
.toolbarButtons button:disabled {
@apply text-chalkboard-70 bg-chalkboard-30;
}
.toolbarButtons button:disabled:hover {
@apply !bg-inherit !text-inherit cursor-not-allowed;
}
:global(.dark) .toolbarButtons button {
@apply text-chalkboard-20 border-chalkboard-50;
}
:global(.dark) .toolbarButtons button:hover {
@apply text-cool-10 border-chalkboard-50 bg-cool-90;
}
:global(.dark .sketch) .toolbarButtons button:hover {
@apply text-fern-10 border-chalkboard-50 bg-fern-90;
}
:global(.dark) .toolbarButtons button:disabled {
@apply text-chalkboard-40 bg-chalkboard-80;
}
:global(.dark) .popoverToggle { :global(.dark) .popoverToggle {
@apply hover:bg-cool-90; @apply hover:bg-cool-90;
} }

View File

@ -1,4 +1,4 @@
import { useStore, toolTips, Selections } from './useStore' import { useStore, toolTips, ToolTip } from './useStore'
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst' import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst' import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert' import { HorzVert } from './components/Toolbar/HorzVert'
@ -17,6 +17,30 @@ import { Popover, Transition } from '@headlessui/react'
import styles from './Toolbar.module.css' import styles from './Toolbar.module.css'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { useAppMode } from 'hooks/useAppMode' import { useAppMode } from 'hooks/useAppMode'
import { ActionIcon } from 'components/ActionIcon'
export const sketchButtonClassnames = {
background:
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-fern-20 dark:group-hover:bg-fern-10 dark:hover:bg-fern-10 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50',
icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
}
const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
sketch_line: 'Line',
line: 'Line',
move: 'Move',
angledLine: 'Angled Line',
angledLineThatIntersects: 'Angled Line That Intersects',
angledLineOfXLength: 'Angled Line Of X Length',
angledLineOfYLength: 'Angled Line Of Y Length',
angledLineToX: 'Angled Line To X',
angledLineToY: 'Angled Line To Y',
lineTo: 'Line to Point',
xLine: 'Horizontal Line',
yLine: 'Vertical Line',
xLineTo: 'Horizontal Line to Point',
yLineTo: 'Vertical Line to Point',
}
export const Toolbar = () => { export const Toolbar = () => {
const { const {
@ -44,9 +68,9 @@ export const Toolbar = () => {
console.log('guiMode', guiMode) console.log('guiMode', guiMode)
}, [guiMode]) }, [guiMode])
function ToolbarButtons() { function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
return ( return (
<span className={styles.smallScrollbar}> <span className={styles.toolbarButtons + ' ' + className}>
{guiMode.mode === 'default' && ( {guiMode.mode === 'default' && (
<button <button
onClick={() => { onClick={() => {
@ -55,7 +79,9 @@ export const Toolbar = () => {
sketchMode: 'selectFace', sketchMode: 'selectFace',
}) })
}} }}
className="group"
> >
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Start Sketch Start Sketch
</button> </button>
)} )}
@ -74,31 +100,31 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst, true) updateAst(modifiedAst, true)
}} }}
className="group"
> >
SketchOnFace <ActionIcon icon="sketch" className="!p-0.5" size="md" />
Sketch on Face
</button> </button>
)} )}
{guiMode.mode === 'canEditSketch' && ( {guiMode.mode === 'canEditSketch' && (
<button <button
onClick={() => { onClick={() => {
console.log('guiMode.pathId', guiMode.pathId) const pathToNode = getNodePathFromSourceRange(
engineCommandManager?.sendSceneCommand({ ast,
type: 'modeling_cmd_req', selectionRanges.codeBasedSelections[0].range
cmd_id: uuidv4(), )
cmd: {
type: 'edit_mode_enter',
target: guiMode.pathId,
},
})
setGuiMode({ setGuiMode({
mode: 'sketch', mode: 'sketch',
sketchMode: 'sketchEdit', sketchMode: 'enterSketchEdit',
pathToNode: guiMode.pathToNode, pathToNode: pathToNode,
rotation: guiMode.rotation, rotation: [0, 0, 0, 1],
position: guiMode.position, position: [0, 0, 0],
pathId: guiMode.pathId,
}) })
}} }}
className="group"
> >
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Edit Sketch Edit Sketch
</button> </button>
)} )}
@ -117,8 +143,10 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg }) updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}} }}
className="group"
> >
ExtrudeSketch <ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
</button> </button>
<button <button
onClick={() => { onClick={() => {
@ -134,8 +162,10 @@ export const Toolbar = () => {
) )
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg }) updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}} }}
className="group"
> >
ExtrudeSketch (w/o pipe) <ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude as new
</button> </button>
</> </>
)} )}
@ -157,7 +187,15 @@ export const Toolbar = () => {
setGuiMode({ mode: 'default' }) setGuiMode({ mode: 'default' })
executeAst() executeAst()
}} }}
className="group"
> >
<ActionIcon
icon="exit"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Exit sketch Exit sketch
</button> </button>
)} )}
@ -198,12 +236,25 @@ export const Toolbar = () => {
sketchMode: sketchFnName, sketchMode: sketchFnName,
waitingFirstClick: true, waitingFirstClick: true,
isTooltip: true, isTooltip: true,
pathId: guiMode.pathId,
}), }),
}) })
}} }}
className={
'group ' +
(guiMode.sketchMode === sketchFnName
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
}
> >
{sketchFnName} <ActionIcon
{guiMode.sketchMode === sketchFnName && ''} icon={sketchFnName.includes('line') ? 'line' : 'move'}
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{sketchFnLabels[sketchFnName]}
</button> </button>
) )
})} })}
@ -234,7 +285,7 @@ export const Toolbar = () => {
<span className={styles.toolbarCap + ' ' + styles.label}> <span className={styles.toolbarCap + ' ' + styles.label}>
{guiMode.mode === 'sketch' ? '2D' : '3D'} {guiMode.mode === 'sketch' ? '2D' : '3D'}
</span> </span>
<menu className="flex flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap"> <menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
<ToolbarButtons /> <ToolbarButtons />
</menu> </menu>
<Popover.Button <Popover.Button
@ -275,7 +326,7 @@ export const Toolbar = () => {
</Popover.Button> </Popover.Button>
</section> </section>
<section> <section>
<ToolbarButtons /> <ToolbarButtons className="flex-wrap" />
</section> </section>
</Popover.Panel> </Popover.Panel>
</Transition> </Transition>

View File

@ -4,6 +4,7 @@ import {
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { IconDefinition as BrandIconDefinition } from '@fortawesome/free-brands-svg-icons' import { IconDefinition as BrandIconDefinition } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { CustomIcon, CustomIconName } from './CustomIcon'
const iconSizes = { const iconSizes = {
sm: 12, sm: 12,
@ -13,7 +14,7 @@ const iconSizes = {
} }
export interface ActionIconProps extends React.PropsWithChildren { export interface ActionIconProps extends React.PropsWithChildren {
icon?: SolidIconDefinition | BrandIconDefinition icon?: SolidIconDefinition | BrandIconDefinition | CustomIconName
className?: string className?: string
bgClassName?: string bgClassName?: string
iconClassName?: string iconClassName?: string
@ -28,25 +29,39 @@ export const ActionIcon = ({
size = 'md', size = 'md',
children, children,
}: ActionIconProps) => { }: ActionIconProps) => {
// By default, we reverse the icon color and background color in dark mode
const computedIconClassName =
iconClassName ||
`text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50`
const computedBgClassName =
bgClassName ||
`bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10 group-disabled:bg-chalkboard-80 dark:group-disabled:bg-chalkboard-80`
return ( return (
<div <div
className={ className={
`p-${ `p-${
size === 'xl' ? '2' : '1' size === 'xl' ? '2' : '1'
} w-fit inline-grid place-content-center ${className} ` + } w-fit inline-grid place-content-center ${className} ` +
(bgClassName || computedBgClassName
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10')
} }
> >
{children || ( {children ? (
children
) : typeof icon === 'string' ? (
<CustomIcon
name={icon}
width={iconSizes[size]}
height={iconSizes[size]}
className={computedIconClassName}
/>
) : (
<FontAwesomeIcon <FontAwesomeIcon
icon={icon} icon={icon}
width={iconSizes[size]} width={iconSizes[size]}
height={iconSizes[size]} height={iconSizes[size]}
className={ className={computedIconClassName}
iconClassName ||
'text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-liquid-100 dark:group-hover:text-liquid-100 dark:hover:text-liquid-100'
}
/> />
)} )}
</div> </div>

View File

@ -29,7 +29,7 @@ export const AppHeader = ({
return ( return (
<header <header
className={ className={
(showToolbar ? 'grid ' : 'flex justify-between ') + (showToolbar ? 'w-full grid ' : 'flex justify-between ') +
styles.header + styles.header +
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' + ' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
className className
@ -38,7 +38,7 @@ export const AppHeader = ({
<ProjectSidebarMenu renderAsLink={!enableMenu} project={project} /> <ProjectSidebarMenu renderAsLink={!enableMenu} project={project} />
{/* Toolbar if the context deems it */} {/* Toolbar if the context deems it */}
{showToolbar && ( {showToolbar && (
<div className="max-w-4xl"> <div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
<Toolbar /> <Toolbar />
</div> </div>
)} )}

View File

@ -23,7 +23,8 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
<div <div
className="relative" className="relative"
onClick={(e) => { onClick={(e) => {
if (e.eventPhase === 3) { const target = e.target as HTMLElement
if (e.eventPhase === 3 && target.closest('a') === null) {
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
} }

View File

@ -62,7 +62,7 @@ export const CommandBarProvider = ({
const CommandBar = () => { const CommandBar = () => {
const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext() const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext()
useHotkeys('meta+k', () => { useHotkeys(['meta+k', 'meta+/'], () => {
if (commands.length === 0) return if (commands.length === 0) return
setCommandBarOpen(!commandBarOpen) setCommandBarOpen(!commandBarOpen)
}) })
@ -221,10 +221,10 @@ const CommandBar = () => {
<Combobox <Combobox
value={selectedCommand} value={selectedCommand}
onChange={handleCommandSelection} onChange={handleCommandSelection}
className="rounded relative mx-auto p-2 bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-xl w-full shadow-lg" className="relative w-full max-w-xl p-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
as="div" as="div"
> >
<div className="flex gap-2 items-center"> <div className="flex items-center gap-2">
<ActionIcon icon={faSearch} size="xl" className="rounded-sm" /> <ActionIcon icon={faSearch} size="xl" className="rounded-sm" />
<div> <div>
{inSubCommand && ( {inSubCommand && (
@ -235,7 +235,7 @@ const CommandBar = () => {
)} )}
<Combobox.Input <Combobox.Input
onChange={(event) => setQuery(event.target.value)} onChange={(event) => setQuery(event.target.value)}
className="bg-transparent focus:outline-none w-full" className="w-full bg-transparent focus:outline-none"
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.metaKey && event.key === 'k') if (event.metaKey && event.key === 'k')
setCommandBarOpen(false) setCommandBarOpen(false)
@ -264,12 +264,12 @@ const CommandBar = () => {
/> />
</div> </div>
</div> </div>
<Combobox.Options static className="max-h-96 overflow-y-auto"> <Combobox.Options static className="overflow-y-auto max-h-96">
{filteredCommands?.map((commandResult) => ( {filteredCommands?.map((commandResult) => (
<Combobox.Option <Combobox.Option
key={commandResult.item.name} key={commandResult.item.name}
value={commandResult} value={commandResult}
className="my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90 py-1 px-2" className="px-2 py-1 my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90"
> >
<p>{commandResult.item.name}</p> <p>{commandResult.item.name}</p>
{(commandResult.item as SubCommand).description && ( {(commandResult.item as SubCommand).description && (

View File

@ -0,0 +1,161 @@
export type CustomIconName =
| 'equal'
| 'exit'
| 'extrude'
| 'horizontal'
| 'line'
| 'move'
| 'parallel'
| 'sketch'
| 'vertical'
export const CustomIcon = ({
name,
...props
}: {
name: CustomIconName
} & React.SVGProps<SVGSVGElement>) => {
switch (name) {
case 'equal':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 8.78V7H14.52V8.78H5ZM5 13.02V11.24H14.52V13.02H5Z"
fill="currentColor"
/>
</svg>
)
case 'exit':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 10L3 10M3 10L6.5 6.5M3 10L6.5 13.5"
stroke="currentColor"
/>
</svg>
)
case 'extrude':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 3L10.3536 3.35355L12.3536 5.35355L11.6465 6.06066L10.5 4.91421V11.5854C11.0826 11.7913 11.5 12.3469 11.5 13C11.5 13.8284 10.8284 14.5 10 14.5C9.17157 14.5 8.5 13.8284 8.5 13C8.5 12.3469 8.91741 11.7913 9.5 11.5854V4.91421L8.35356 6.06066L7.64645 5.35355L9.64645 3.35355L10 3ZM1.95887 12.3282L8 8.63644V9.80838L2.91773 12.9142L10 17.2423L17.0823 12.9142L12 9.80838V8.63644L18.0411 12.3282L19 12.9142L19 14.9683H18V13.5253L10.5 18.1087V19.9683H9.5V18.1087L2 13.5253V14.9683H1L1 12.9142L1.95887 12.3282Z"
fill="currentColor"
/>
</svg>
)
case 'horizontal':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4 9.5H16V11.5H4V9.5Z"
fill="currentColor"
/>
</svg>
)
case 'line':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.5 6C16.3284 6 17 5.32843 17 4.5C17 3.67157 16.3284 3 15.5 3C14.6716 3 14 3.67157 14 4.5C14 4.73107 14.0522 4.94993 14.1456 5.14543L5.14543 14.1456C4.94993 14.0522 4.73107 14 4.5 14C3.67157 14 3 14.6716 3 15.5C3 16.3284 3.67157 17 4.5 17C5.32843 17 6 16.3284 6 15.5C6 15.2679 5.94729 15.0482 5.8532 14.852L14.852 5.8532C15.0482 5.94729 15.2679 6 15.5 6Z"
fill="currentColor"
/>
</svg>
)
case 'move':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 2.29289L10.3536 2.64645L12.3536 4.64645L11.6465 5.35355L10.5 4.20711V8V9.50001H12L15.7929 9.50001L14.6465 8.35356L15.3536 7.64645L17.3536 9.64645L17.7071 10L17.3536 10.3536L15.3536 12.3536L14.6465 11.6465L15.7929 10.5H12H10.5V12V15.7929L11.6465 14.6464L12.3536 15.3536L10.3536 17.3536L10 17.7071L9.64645 17.3536L7.64645 15.3536L8.35356 14.6464L9.50001 15.7929V12V10.5H8.00001H4.20712L5.35357 11.6465L4.64646 12.3536L2.64646 10.3536L2.29291 10L2.64646 9.64645L4.64646 7.64645L5.35357 8.35356L4.20712 9.50001H8.00001H9.50001V8V4.20711L8.35356 5.35355L7.64645 4.64645L9.64645 2.64645L10 2.29289Z"
fill="currentColor"
/>
</svg>
)
case 'parallel':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 16V4H6V16H8ZM14 16V4H12V16H14Z"
fill="currentColor"
/>
</svg>
)
case 'sketch':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.8037 13.4035L15.5509 14.1635L16.3682 16.8386L13.5521 16.1346L12.8186 15.3885L14.8037 13.4035ZM14.1025 12.6903L12.1175 14.6754L3.48609 5.89624C2.94588 5.34678 2.94963 4.46456 3.49448 3.91971C4.04591 3.36828 4.94112 3.37208 5.48786 3.92817L14.1025 12.6903ZM6.20094 3.22709L16.4357 13.6371L17.5003 17.1216L17.8412 18.2376L16.7091 17.9546L13.0364 17.0364L2.77301 6.59732C1.84793 5.6564 1.85434 4.14564 2.78737 3.2126C3.73167 2.2683 5.26468 2.27481 6.20094 3.22709Z"
fill="currentColor"
/>
</svg>
)
case 'vertical':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11 4V16H9V4H11Z"
fill="currentColor"
/>
</svg>
)
}
}

View File

@ -30,7 +30,11 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
return ( return (
<CollapsiblePanel <CollapsiblePanel
{...props} {...props}
className={'!absolute !h-auto bottom-5 right-5 ' + className} className={
'!absolute overflow-hidden !h-auto bottom-5 right-5 ' + className
}
// header height, top-5, and bottom-5
style={{ maxHeight: 'calc(100% - 3rem - 1.25rem - 1.25rem)' }}
> >
<section className="p-4 flex flex-col gap-4"> <section className="p-4 flex flex-col gap-4">
<Xyz <Xyz

View File

@ -40,12 +40,12 @@ const DownloadAppBanner = () => {
</code> </code>
, and isn't backed up anywhere! Visit{' '} , and isn't backed up anywhere! Visit{' '}
<a <a
href="https://github.com/KittyCAD/modeling-app/releases" href="https://kittycad.io/modeling-app/download"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline" className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
> >
our GitHub repository our website
</a>{' '} </a>{' '}
to download the app for the best experience. to download the app for the best experience.
</p> </p>

View File

@ -1,8 +1,62 @@
import { isTauri } from 'lib/isTauri'
import { useRouteError, isRouteErrorResponse } from 'react-router-dom'
import { ActionButton } from './ActionButton'
import {
faBug,
faHome,
faRefresh,
faTrash,
} from '@fortawesome/free-solid-svg-icons'
export const ErrorPage = () => { export const ErrorPage = () => {
let error = useRouteError()
console.error('error', error)
return ( return (
<div className="flex flex-col items-center justify-center h-screen"> <div className="flex flex-col items-center justify-center h-screen">
<h1 className="text-4xl font-bold">404</h1> <section className="max-w-full xl:max-w-4xl mx-auto">
<p className="text-2xl font-bold">Page not found</p> <h1 className="text-4xl mb-8 font-bold">
An unexpected error occurred
</h1>
{isRouteErrorResponse(error) && (
<p className="mb-8">
{error.status}: {error.data}
</p>
)}
<div className="flex justify-between gap-2 mt-6">
{isTauri() && (
<ActionButton Element="link" to={'/'} icon={{ icon: faHome }}>
Go Home
</ActionButton>
)}
<ActionButton
Element="button"
icon={{ icon: faRefresh }}
onClick={() => window.location.reload()}
>
Reload
</ActionButton>
<ActionButton
Element="button"
icon={{ icon: faTrash }}
onClick={() => {
window.localStorage.clear()
}}
>
Clear storage
</ActionButton>
<ActionButton
Element="link"
icon={{ icon: faBug }}
target="_blank"
rel="noopener noreferrer"
to="https://discord.com/channels/915388055236509727/1138967922614743060"
>
Report Bug
</ActionButton>
</div>
</section>
</div> </div>
) )
} }

View File

@ -24,7 +24,11 @@ export const MemoryPanel = ({
<CollapsiblePanel {...props}> <CollapsiblePanel {...props}>
<div className="h-full relative"> <div className="h-full relative">
<div className="absolute inset-0 flex flex-col items-start"> <div className="absolute inset-0 flex flex-col items-start">
<div className=" h-full console-tile w-full"> <div
className="overflow-y-auto h-full console-tile w-full"
style={{ marginBottom: 36 }}
>
{/* 36px is the height of PanelHeader */}
<ReactJson <ReactJson
src={ProcessedMemory} src={ProcessedMemory}
collapsed={1} collapsed={1}

View File

@ -1,42 +0,0 @@
import { invoke } from '@tauri-apps/api/tauri'
import { open } from '@tauri-apps/api/dialog'
import { useStore } from '../useStore'
export const OpenFileButton = () => {
const { setCode } = useStore((s) => ({
setCode: s.setCode,
}))
const handleClick = async () => {
const selected = await open({
multiple: false,
directory: false,
filters: [
{
name: 'CAD',
extensions: ['toml'],
},
],
})
if (Array.isArray(selected)) {
// User selected multiple files
// We should not get here, since multiple is false.
} else if (selected === null) {
// User cancelled the selection
// Do nothing.
} else {
// User selected a single file
// We want to invoke our command to read the file.
const json: string = await invoke('read_toml', { path: selected })
const packageDetails = JSON.parse(json).package
if (packageDetails.main) {
const absPath = [
...selected.split('/').slice(0, -1),
packageDetails.main,
].join('/')
const file: string = await invoke('read_txt_file', { path: absPath })
setCode(file)
}
}
}
return <button onClick={() => handleClick()}>Open File</button>
}

View File

@ -16,8 +16,8 @@ const ProjectSidebarMenu = ({
}) => { }) => {
return renderAsLink ? ( return renderAsLink ? (
<Link <Link
to={'../'} to={paths.HOME}
className="flex items-center gap-4 my-2" className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
data-testid="project-sidebar-link" data-testid="project-sidebar-link"
> >
<img <img
@ -26,7 +26,7 @@ const ProjectSidebarMenu = ({
className="h-9 w-auto" className="h-9 w-auto"
/> />
<span <span
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max" className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block"
data-testid="project-sidebar-link-name" data-testid="project-sidebar-link-name"
> >
{project?.name ? project.name : 'KittyCAD Modeling App'} {project?.name ? project.name : 'KittyCAD Modeling App'}
@ -35,15 +35,15 @@ const ProjectSidebarMenu = ({
) : ( ) : (
<Popover className="relative"> <Popover className="relative">
<Popover.Button <Popover.Button
className="border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50" className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
data-testid="project-sidebar-toggle" data-testid="project-sidebar-toggle"
> >
<img <img
src="/kitt-8bit-winking.svg" src="/kitt-8bit-winking.svg"
alt="KittyCAD App" alt="KittyCAD App"
className="h-9 w-auto" className="h-full w-auto"
/> />
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max"> <span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block">
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'} {isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
</span> </span>
</Popover.Button> </Popover.Button>

View File

@ -21,6 +21,10 @@ import {
} from 'lang/std/sketch' } from 'lang/std/sketch'
import { getNodeFromPath } from 'lang/queryAst' import { getNodeFromPath } from 'lang/queryAst'
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes' import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
import { modify_ast_for_sketch } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from 'lang/errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from 'lang/abstractSyntaxTree'
export const Stream = ({ className = '' }) => { export const Stream = ({ className = '' }) => {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
@ -211,14 +215,9 @@ export const Stream = ({ className = '' }) => {
} }
} }
engineCommandManager?.sendSceneCommand(command).then(async (resp) => { engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
if (command?.cmd?.type !== 'mouse_click' || !ast) return if (!(guiMode.mode === 'sketch')) return
if (
!( if (guiMode.sketchMode === 'selectFace') return
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any as 'line')
)
)
return
// Check if the sketch group already exists. // Check if the sketch group already exists.
const varDec = getNodeFromPath<VariableDeclarator>( const varDec = getNodeFromPath<VariableDeclarator>(
@ -230,6 +229,56 @@ export const Stream = ({ className = '' }) => {
const sketchGroup = programMemory.root[variableName] const sketchGroup = programMemory.root[variableName]
const isEditingExistingSketch = const isEditingExistingSketch =
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
let sketchGroupId = ''
if (sketchGroup && sketchGroup.type === 'SketchGroup') {
sketchGroupId = sketchGroup.id
}
if (
guiMode.sketchMode === ('move' as any as 'line') &&
command.cmd.type === 'handle_mouse_drag_end'
) {
// Let's get the updated ast.
if (sketchGroupId === '') return
console.log('guiMode.pathId', guiMode.pathId)
// We have a problem if we do not have an id for the sketch group.
if (
guiMode.pathId === undefined ||
guiMode.pathId === null ||
guiMode.pathId === ''
)
return
let engineId = guiMode.pathId
try {
const updatedAst: Program = await modify_ast_for_sketch(
engineCommandManager,
JSON.stringify(ast),
variableName,
engineId
)
updateAst(updatedAst, false)
} catch (e: any) {
const parsed: RustKclError = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
)
console.log(kclError)
throw kclError
}
return
}
if (command?.cmd?.type !== 'mouse_click' || !ast) return
if (!(guiMode.sketchMode === ('sketch_line' as any as 'line'))) return
if ( if (
resp?.data?.data?.entities_modified?.length && resp?.data?.data?.entities_modified?.length &&
@ -257,6 +306,16 @@ export const Stream = ({ className = '' }) => {
const _modifiedAst = _addStartSketch.modifiedAst const _modifiedAst = _addStartSketch.modifiedAst
const _pathToNode = _addStartSketch.pathToNode const _pathToNode = _addStartSketch.pathToNode
// We need to update the guiMode with the right pathId so that we can
// move lines later and send the right sketch id to the engine.
for (const [id, artifact] of Object.entries(
engineCommandManager.artifactMap
)) {
if (artifact.commandType === 'start_path') {
guiMode.pathId = id
}
}
setGuiMode({ setGuiMode({
...guiMode, ...guiMode,
pathToNode: _pathToNode, pathToNode: _pathToNode,
@ -315,9 +374,12 @@ export const Stream = ({ className = '' }) => {
engineCommandManager?.sendSceneCommand({ engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { cmd: { type: 'edit_mode_exit' },
type: 'sketch_mode_disable', })
}, engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
}) })
updateAst(_modifiedAst, true) updateAst(_modifiedAst, true)
} }

View File

@ -12,6 +12,8 @@ import {
getTransformInfos, getTransformInfos,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util' import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const EqualAngle = () => { export const EqualAngle = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
@ -87,9 +89,17 @@ export const EqualAngle = () => {
}) })
}} }}
disabled={!enableEqual} disabled={!enableEqual}
title="yo dawg" title="Parallel (or equal angle)"
className="group"
> >
parallel <ActionIcon
icon="parallel"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Parallel
</button> </button>
) )
} }

View File

@ -12,6 +12,8 @@ import {
getTransformInfos, getTransformInfos,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util' import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const EqualLength = () => { export const EqualLength = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
@ -87,8 +89,16 @@ export const EqualLength = () => {
}) })
}} }}
disabled={!enableEqual} disabled={!enableEqual}
title="yo dawg" className="group"
title="Equal Length"
> >
<ActionIcon
icon="equal"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Equal Length Equal Length
</button> </button>
) )

View File

@ -11,6 +11,8 @@ import {
transformAstSketchLines, transformAstSketchLines,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util' import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const HorzVert = ({ export const HorzVert = ({
horOrVert, horOrVert,
@ -66,9 +68,17 @@ export const HorzVert = ({
}) })
}} }}
disabled={!enableHorz} disabled={!enableHorz}
title="yo dawg" className="group"
title={horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
> >
{horOrVert === 'horizontal' ? 'Horz' : 'Vert'} <ActionIcon
icon={horOrVert === 'horizontal' ? 'horizontal' : 'vertical'}
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
</button> </button>
) )
} }

View File

@ -188,8 +188,9 @@ export const Intersect = () => {
} }
}} }}
disabled={!enable} disabled={!enable}
title="Set Perpendicular Distance"
> >
perpendicularDistance Set Perpendicular Distance
</button> </button>
) )
} }

View File

@ -70,7 +70,7 @@ export const RemoveConstrainingValues = () => {
}) })
}} }}
disabled={!enableHorz} disabled={!enableHorz}
title="yo dawg" title="Remove Constraining Values"
> >
Remove Constraining Values Remove Constraining Values
</button> </button>

View File

@ -22,11 +22,16 @@ import { updateCursors } from '../../lang/util'
const getModalInfo = create(SetAngleLengthModal as any) const getModalInfo = create(SetAngleLengthModal as any)
export const SetAbsDistance = ({ type ButtonType = 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
buttonType,
}: { const buttonLabels: Record<ButtonType, string> = {
buttonType: 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis' xAbs: 'Set distance from X Axis',
}) => { yAbs: 'Set distance from Y Axis',
snapToYAxis: 'Snap To Y Axis',
snapToXAxis: 'Snap To X Axis',
}
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({ useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
@ -132,8 +137,9 @@ export const SetAbsDistance = ({
} }
}} }}
disabled={!enableAngLen} disabled={!enableAngLen}
title={buttonLabels[buttonType]}
> >
{buttonType} {buttonLabels[buttonType]}
</button> </button>
) )
} }

View File

@ -147,8 +147,9 @@ export const SetAngleBetween = () => {
} }
}} }}
disabled={!enable} disabled={!enable}
title="Set Angle Between"
> >
angleBetween Set Angle Between
</button> </button>
) )
} }

View File

@ -21,17 +21,28 @@ import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst' import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util' import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
const getModalInfo = create(GetInfoModal as any) const getModalInfo = create(GetInfoModal as any)
export const SetHorzVertDistance = ({ type ButtonType =
buttonType,
}: {
buttonType:
| 'setHorzDistance' | 'setHorzDistance'
| 'setVertDistance' | 'setVertDistance'
| 'alignEndsHorizontally' | 'alignEndsHorizontally'
| 'alignEndsVertically' | 'alignEndsVertically'
const buttonLabels: Record<ButtonType, string> = {
setHorzDistance: 'Set Horizontal Distance',
setVertDistance: 'Set Vertical Distance',
alignEndsHorizontally: 'Align Ends Horizontally',
alignEndsVertically: 'Align Ends Vertically',
}
export const SetHorzVertDistance = ({
buttonType,
}: {
buttonType: ButtonType
}) => { }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({ useStore((s) => ({
@ -169,8 +180,9 @@ export const SetHorzVertDistance = ({
} }
}} }}
disabled={!enable} disabled={!enable}
title={buttonLabels[buttonType]}
> >
{buttonType} {buttonLabels[buttonType]}
</button> </button>
) )
} }

View File

@ -23,10 +23,17 @@ import { updateCursors } from '../../lang/util'
const getModalInfo = create(SetAngleLengthModal as any) const getModalInfo = create(SetAngleLengthModal as any)
type ButtonType = 'setAngle' | 'setLength'
const buttonLabels: Record<ButtonType, string> = {
setAngle: 'Set Angle',
setLength: 'Set Length',
}
export const SetAngleLength = ({ export const SetAngleLength = ({
angleOrLength, angleOrLength,
}: { }: {
angleOrLength: 'setAngle' | 'setLength' angleOrLength: ButtonType
}) => { }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({ useStore((s) => ({
@ -144,8 +151,9 @@ export const SetAngleLength = ({
} }
}} }}
disabled={!enableAngLen} disabled={!enableAngLen}
title={buttonLabels[angleOrLength]}
> >
{angleOrLength} {buttonLabels[angleOrLength]}
</button> </button>
) )
} }

View File

@ -2,7 +2,7 @@ import { Popover, Transition } from '@headlessui/react'
import { ActionButton } from './ActionButton' import { ActionButton } from './ActionButton'
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons' import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons' import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useState } from 'react' import { Fragment, useState } from 'react'
import { paths } from '../Router' import { paths } from '../Router'
import makeUrlPathRelative from '../lib/makeUrlPathRelative' import makeUrlPathRelative from '../lib/makeUrlPathRelative'
@ -12,6 +12,7 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
type User = Models['User_type'] type User = Models['User_type']
const UserSidebarMenu = ({ user }: { user?: User }) => { const UserSidebarMenu = ({ user }: { user?: User }) => {
const location = useLocation()
const displayedName = getDisplayName(user) const displayedName = getDisplayName(user)
const [imageLoadFailed, setImageLoadFailed] = useState(false) const [imageLoadFailed, setImageLoadFailed] = useState(false)
const navigate = useNavigate() const navigate = useNavigate()
@ -38,7 +39,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
<Popover className="relative"> <Popover className="relative">
{user?.image && !imageLoadFailed ? ( {user?.image && !imageLoadFailed ? (
<Popover.Button <Popover.Button
className="border-0 rounded-full w-fit p-0 focus:outline-none group" className="border-0 rounded-full w-fit min-w-max p-0 focus:outline-none group"
data-testid="user-sidebar-toggle" data-testid="user-sidebar-toggle"
> >
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 group-focus:border-liquid-50 overflow-hidden"> <div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 group-focus:border-liquid-50 overflow-hidden">
@ -126,7 +127,11 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
// since /settings is a nested route the sidebar doesn't close // since /settings is a nested route the sidebar doesn't close
// automatically when navigating to it // automatically when navigating to it
close() close()
navigate(makeUrlPathRelative(paths.SETTINGS)) navigate(
(location.pathname.endsWith('/')
? location.pathname.slice(0, -1)
: location.pathname) + paths.SETTINGS
)
}} }}
> >
Settings Settings

View File

@ -5,8 +5,6 @@ import init, {
} from '../../wasm-lib/pkg/wasm_lib' } from '../../wasm-lib/pkg/wasm_lib'
import { FromServer, IntoServer } from './codec' import { FromServer, IntoServer } from './codec'
let server: null | Server
export default class Server { export default class Server {
readonly initOutput: InitOutput readonly initOutput: InitOutput
readonly #intoServer: IntoServer readonly #intoServer: IntoServer
@ -26,12 +24,8 @@ export default class Server {
intoServer: IntoServer, intoServer: IntoServer,
fromServer: FromServer fromServer: FromServer
): Promise<Server> { ): Promise<Server> {
if (null == server) {
const initOutput = await init() const initOutput = await init()
server = new Server(initOutput, intoServer, fromServer) const server = new Server(initOutput, intoServer, fromServer)
} else {
console.warn('Server already initialized; ignoring')
}
return server return server
} }

View File

@ -37,27 +37,62 @@ export function useAppMode() {
guiMode.sketchMode === 'selectFace' && guiMode.sketchMode === 'selectFace' &&
engineCommandManager engineCommandManager
) { ) {
const createAndShowPlanes = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) { if (!defaultPlanes) {
const xy = createPlane(engineCommandManager, { localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
x_axis: { x: 1, y: 0, z: 0 }, setDefaultPlanes(localDefaultPlanes)
y_axis: { x: 0, y: 1, z: 0 },
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
})
// TODO re-enable
// const yz = createPlane(engineCommandManager, {
// x_axis: { x: 0, y: 1, z: 0 },
// y_axis: { x: 0, y: 0, z: 1 },
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
// })
// const xz = createPlane(engineCommandManager, {
// x_axis: { x: 1, y: 0, z: 0 },
// y_axis: { x: 0, y: 0, z: 1 },
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
// })
setDefaultPlanes({ xy })
} else { } else {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, false) localDefaultPlanes = defaultPlanes
} }
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
}
createAndShowPlanes()
}
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'enterSketchEdit' &&
engineCommandManager
) {
const enableSketchMode = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
// TODO figure out the plane to use based on the sketch
// maybe it's easier to make a new plane than rely on the defaults
await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: localDefaultPlanes.xy,
ortho: true,
animated: !isReducedMotion(),
},
})
const proms: any[] = []
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: guiMode.pathId,
},
})
)
await Promise.all(proms)
}
enableSketchMode()
setGuiMode({
...guiMode,
sketchMode: 'sketchEdit',
})
} }
if (guiMode.mode !== 'sketch' && defaultPlanes) { if (guiMode.mode !== 'sketch' && defaultPlanes) {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true) setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
@ -151,6 +186,7 @@ export function useAppMode() {
rotation: [0, 0, 0, 1], rotation: [0, 0, 0, 1],
position: [0, 0, 0], position: [0, 0, 0],
pathToNode: [], pathToNode: [],
pathId: sketchUuid,
}) })
console.log('sketchModeResponse', sketchModeResponse) console.log('sketchModeResponse', sketchModeResponse)
@ -160,7 +196,7 @@ export function useAppMode() {
}, [engineCommandManager, defaultPlanes]) }, [engineCommandManager, defaultPlanes])
} }
function createPlane( async function createPlane(
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
{ {
x_axis, x_axis,
@ -173,7 +209,7 @@ function createPlane(
} }
) { ) {
const planeId = uuidv4() const planeId = uuidv4()
engineCommandManager?.sendSceneCommand({ await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd: { cmd: {
type: 'make_plane', type: 'make_plane',
@ -185,7 +221,7 @@ function createPlane(
}, },
cmd_id: planeId, cmd_id: planeId,
}) })
engineCommandManager?.sendSceneCommand({ await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd: { cmd: {
type: 'plane_set_color', type: 'plane_set_color',
@ -215,6 +251,28 @@ function setDefaultPlanesHidden(
}) })
} }
async function initDefaultPlanes(
engineCommandManager: EngineCommandManager
): Promise<DefaultPlanes> {
const xy = await createPlane(engineCommandManager, {
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
})
// TODO re-enable
// const yz = createPlane(engineCommandManager, {
// x_axis: { x: 0, y: 1, z: 0 },
// y_axis: { x: 0, y: 0, z: 1 },
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
// })
// const xz = createPlane(engineCommandManager, {
// x_axis: { x: 1, y: 0, z: 0 },
// y_axis: { x: 0, y: 0, z: 1 },
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
// })
return { xy }
}
function isCursorInSketchCommandRange( function isCursorInSketchCommandRange(
artifactMap: ArtifactMap, artifactMap: ArtifactMap,
selectionRanges: Selections selectionRanges: Selections

View File

@ -0,0 +1,13 @@
import { useLocation } from 'react-router-dom'
export function useDotDotSlash(): (count?: number) => string {
const location = useLocation()
const dotDotSlash = (count = 1): string => {
// since we can't use relative paths (../) for windows
if (location.pathname === '/') return ''
const path = location.pathname.slice(0, location.pathname.lastIndexOf('/'))
if (count <= 1) return path
return dotDotSlash(count - 1)
}
return dotDotSlash
}

View File

@ -31,6 +31,14 @@ body.dark {
@apply text-chalkboard-10; @apply text-chalkboard-10;
} }
select {
@apply bg-chalkboard-20;
}
.dark select {
@apply bg-chalkboard-90;
}
::-webkit-scrollbar { ::-webkit-scrollbar {
@apply w-2 h-2 rounded-sm; @apply w-2 h-2 rounded-sm;
@apply bg-chalkboard-20; @apply bg-chalkboard-20;

View File

@ -139,54 +139,6 @@ const newVar = myVar + 1
}, },
]) ])
}) })
test('using std function "log"', () => {
const code = `log(5, "hello", aIdentifier)`
const { body } = parser_wasm(code)
expect(body).toEqual([
{
type: 'ExpressionStatement',
start: 0,
end: 28,
expression: {
type: 'CallExpression',
start: 0,
end: 28,
callee: {
type: 'Identifier',
start: 0,
end: 3,
name: 'log',
},
arguments: [
{
type: 'Literal',
start: 4,
end: 5,
value: 5,
raw: '5',
},
{
type: 'Literal',
start: 7,
end: 14,
value: 'hello',
raw: '"hello"',
},
{
type: 'Identifier',
start: 16,
end: 27,
name: 'aIdentifier',
},
],
function: {
type: 'InMemory',
},
optional: false,
},
},
])
})
}) })
describe('testing function declaration', () => { describe('testing function declaration', () => {
@ -1560,7 +1512,7 @@ const yo = { a: { b: { c: '123' } } }
// this is a comment // this is a comment
const key = 'c'` const key = 'c'`
const nonCodeMetaInstance = { const nonCodeMetaInstance = {
type: 'NoneCodeNode', type: 'NonCodeNode',
start: code.indexOf('\n// this is a comment'), start: code.indexOf('\n// this is a comment'),
end: code.indexOf('const key'), end: code.indexOf('const key'),
value: { value: {
@ -1569,17 +1521,17 @@ const key = 'c'`
}, },
} }
const { nonCodeMeta } = parser_wasm(code) const { nonCodeMeta } = parser_wasm(code)
expect(nonCodeMeta.noneCodeNodes[0]).toEqual(nonCodeMetaInstance) expect(nonCodeMeta.nonCodeNodes[0]).toEqual(nonCodeMetaInstance)
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though) // extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
const codeWithExtraStartWhitespace = '\n\n\n' + code const codeWithExtraStartWhitespace = '\n\n\n' + code
const { nonCodeMeta: nonCodeMeta2 } = parser_wasm( const { nonCodeMeta: nonCodeMeta2 } = parser_wasm(
codeWithExtraStartWhitespace codeWithExtraStartWhitespace
) )
expect(nonCodeMeta2.noneCodeNodes[0].value).toStrictEqual( expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
nonCodeMetaInstance.value nonCodeMetaInstance.value
) )
expect(nonCodeMeta2.noneCodeNodes[0].start).not.toBe( expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
nonCodeMetaInstance.start nonCodeMetaInstance.start
) )
}) })
@ -1596,9 +1548,9 @@ const key = 'c'`
const { body } = parser_wasm(code) const { body } = parser_wasm(code)
const indexOfSecondLineToExpression = 2 const indexOfSecondLineToExpression = 2
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
.noneCodeNodes .nonCodeNodes
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({ expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
type: 'NoneCodeNode', type: 'NonCodeNode',
start: 106, start: 106,
end: 166, end: 166,
value: { value: {
@ -1619,9 +1571,9 @@ const key = 'c'`
const { body } = parser_wasm(code) const { body } = parser_wasm(code)
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
.noneCodeNodes .nonCodeNodes
expect(sketchNonCodeMeta[3]).toEqual({ expect(sketchNonCodeMeta[3]).toEqual({
type: 'NoneCodeNode', type: 'NonCodeNode',
start: 125, start: 125,
end: 141, end: 141,
value: { value: {

View File

@ -33,5 +33,5 @@ export type SyntaxType =
| 'PipeExpression' | 'PipeExpression'
| 'PipeSubstitution' | 'PipeSubstitution'
| 'Literal' | 'Literal'
| 'NoneCodeNode' | 'NonCodeNode'
| 'UnaryExpression' | 'UnaryExpression'

View File

@ -106,7 +106,7 @@ describe('Testing addSketchTo', () => {
body: [], body: [],
start: 0, start: 0,
end: 0, end: 0,
nonCodeMeta: { noneCodeNodes: {}, start: null }, nonCodeMeta: { nonCodeNodes: {}, start: null },
}, },
'yz' 'yz'
) )

View File

@ -537,7 +537,7 @@ export function createPipeExpression(
start: 0, start: 0,
end: 0, end: 0,
body, body,
nonCodeMeta: { noneCodeNodes: {}, start: null }, nonCodeMeta: { nonCodeNodes: {}, start: null },
} }
} }

View File

@ -15,9 +15,13 @@ interface CommandInfo {
range: SourceRange range: SourceRange
parentId?: string parentId?: string
} }
type WebSocketResponse = Models['OkWebSocketResponseData_type']
interface ResultCommand extends CommandInfo { interface ResultCommand extends CommandInfo {
type: 'result' type: 'result'
data: any data: any
raw: WebSocketResponse
} }
interface PendingCommand extends CommandInfo { interface PendingCommand extends CommandInfo {
type: 'pending' type: 'pending'
@ -37,8 +41,6 @@ interface NewTrackArgs {
mediaStream: MediaStream mediaStream: MediaStream
} }
type WebSocketResponse = Models['OkWebSocketResponseData_type']
type ClientMetrics = Models['ClientMetrics_type'] type ClientMetrics = Models['ClientMetrics_type']
// EngineConnection encapsulates the connection(s) to the Engine // EngineConnection encapsulates the connection(s) to the Engine
@ -474,6 +476,13 @@ export class EngineConnection {
this.onConnectionStarted(this) this.onConnectionStarted(this)
} }
unreliableSend(message: object | string) {
// TODO(paultag): Add in logic to determine the connection state and
// take actions if needed?
this.unreliableDataChannel?.send(
typeof message === 'string' ? message : JSON.stringify(message)
)
}
send(message: object | string) { send(message: object | string) {
// TODO(paultag): Add in logic to determine the connection state and // TODO(paultag): Add in logic to determine the connection state and
// take actions if needed? // take actions if needed?
@ -645,12 +654,14 @@ export class EngineCommandManager {
commandType: command.commandType, commandType: command.commandType,
parentId: command.parentId ? command.parentId : undefined, parentId: command.parentId ? command.parentId : undefined,
data: modelingResponse, data: modelingResponse,
raw: message,
} }
resolve({ resolve({
id, id,
commandType: command.commandType, commandType: command.commandType,
range: command.range, range: command.range,
data: modelingResponse, data: modelingResponse,
raw: message,
}) })
} else { } else {
this.artifactMap[id] = { this.artifactMap[id] = {
@ -658,6 +669,7 @@ export class EngineCommandManager {
commandType: command?.commandType, commandType: command?.commandType,
range: command?.range, range: command?.range,
data: modelingResponse, data: modelingResponse,
raw: message,
} }
} }
} }
@ -777,9 +789,7 @@ export class EngineCommandManager {
) { ) {
cmd.sequence = this.outSequence cmd.sequence = this.outSequence
this.outSequence++ this.outSequence++
this.engineConnection?.unreliableDataChannel?.send( this.engineConnection?.unreliableSend(command)
JSON.stringify(command)
)
return Promise.resolve() return Promise.resolve()
} else if ( } else if (
cmd.type === 'highlight_set_entity' && cmd.type === 'highlight_set_entity' &&
@ -787,9 +797,7 @@ export class EngineCommandManager {
) { ) {
cmd.sequence = this.outSequence cmd.sequence = this.outSequence
this.outSequence++ this.outSequence++
this.engineConnection?.unreliableDataChannel?.send( this.engineConnection?.unreliableSend(command)
JSON.stringify(command)
)
return Promise.resolve() return Promise.resolve()
} else if ( } else if (
cmd.type === 'mouse_move' && cmd.type === 'mouse_move' &&
@ -797,9 +805,7 @@ export class EngineCommandManager {
) { ) {
cmd.sequence = this.outSequence cmd.sequence = this.outSequence
this.outSequence++ this.outSequence++
this.engineConnection?.unreliableDataChannel?.send( this.engineConnection?.unreliableSend(command)
JSON.stringify(command)
)
return Promise.resolve() return Promise.resolve()
} }
// since it's not mouse drag or highlighting send over TCP and keep track of the command // since it's not mouse drag or highlighting send over TCP and keep track of the command
@ -872,7 +878,10 @@ export class EngineCommandManager {
} }
const range: SourceRange = JSON.parse(rangeStr) const range: SourceRange = JSON.parse(rangeStr)
return this.sendModelingCommand({ id, range, command: commandStr }) // We only care about the modeling command response.
return this.sendModelingCommand({ id, range, command: commandStr }).then(
({ raw }) => JSON.stringify(raw)
)
} }
commandResult(id: string): Promise<any> { commandResult(id: string): Promise<any> {
const command = this.artifactMap[id] const command = this.artifactMap[id]
@ -942,7 +951,6 @@ export class EngineCommandManager {
pathInfos.forEach(({ originalId, segments }) => { pathInfos.forEach(({ originalId, segments }) => {
const originalArtifact = this.artifactMap[originalId] const originalArtifact = this.artifactMap[originalId]
if (!originalArtifact || originalArtifact.type === 'pending') { if (!originalArtifact || originalArtifact.type === 'pending') {
console.log('problem')
return return
} }
const pipeExpPath = getNodePathFromSourceRange( const pipeExpPath = getNodePathFromSourceRange(
@ -955,23 +963,20 @@ export class EngineCommandManager {
'VariableDeclarator' 'VariableDeclarator'
).node ).node
if (pipeExp.type !== 'VariableDeclarator') { if (pipeExp.type !== 'VariableDeclarator') {
console.log('problem', pipeExp, pipeExpPath, ast)
return return
} }
const variableName = pipeExp.id.name const variableName = pipeExp.id.name
const memoryItem = programMemory.root[variableName] const memoryItem = programMemory.root[variableName]
if (!memoryItem) { if (!memoryItem) {
console.log('problem', variableName, programMemory)
return return
} else if (memoryItem.type !== 'SketchGroup') { } else if (memoryItem.type !== 'SketchGroup') {
console.log('problem', memoryItem, programMemory)
return return
} }
const relevantSegments = segments.filter( const relevantSegments = segments.filter(
({ command_id }: { command_id: string | null }) => command_id ({ command_id }: { command_id: string | null }) => command_id
) )
if (memoryItem.value.length !== relevantSegments.length) { if (memoryItem.value.length !== relevantSegments.length) {
console.log('problem', memoryItem.value, relevantSegments)
return return
} }
for (let i = 0; i < relevantSegments.length; i++) { for (let i = 0; i < relevantSegments.length; i++) {
@ -981,9 +986,11 @@ export class EngineCommandManager {
const artifact = this.artifactMap[oldId] const artifact = this.artifactMap[oldId]
delete this.artifactMap[oldId] delete this.artifactMap[oldId]
delete this.sourceRangeMap[oldId] delete this.sourceRangeMap[oldId]
if (artifact) {
this.artifactMap[engineSegment.command_id] = artifact this.artifactMap[engineSegment.command_id] = artifact
this.sourceRangeMap[engineSegment.command_id] = artifact.range this.sourceRangeMap[engineSegment.command_id] = artifact.range
} }
}
}) })
} }
} }

View File

@ -117,6 +117,7 @@ show(mySketch001)
{ {
mode: 'sketch', mode: 'sketch',
sketchMode: 'sketchEdit', sketchMode: 'sketchEdit',
pathId: '',
rotation: [0, 0, 0, 1], rotation: [0, 0, 0, 1],
position: [0, 0, 0], position: [0, 0, 0],
pathToNode: [ pathToNode: [

21
src/lib/exampleKcl.ts Normal file
View File

@ -0,0 +1,21 @@
export const bracket = `// Material: 6061-T6 Aluminum
const sigmaAllow = 35000 // psi
const width = 9 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
const bracket = startSketchAt([0, 0])
|> line([0, leg1], %)
|> line([leg2, 0], %)
|> line([0, -thickness], %)
|> line([-leg2 + thickness, 0], %)
|> line([0, -leg1 + thickness], %)
|> close(%)
|> extrude(width, %)
show(bracket)
`

View File

@ -4,6 +4,7 @@ import withBaseURL from '../lib/withBaseURL'
import { CommandBarMeta } from '../lib/commands' import { CommandBarMeta } from '../lib/commands'
import { isTauri } from 'lib/isTauri' import { isTauri } from 'lib/isTauri'
import { invoke } from '@tauri-apps/api' import { invoke } from '@tauri-apps/api'
import { VITE_KC_API_BASE_URL } from 'env'
const SKIP_AUTH = const SKIP_AUTH =
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
@ -132,6 +133,7 @@ async function getUser(context: UserContext) {
.catch((err) => console.error('error from Browser getUser', err)) .catch((err) => console.error('error from Browser getUser', err))
: invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', { : invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', {
token: context.token, token: context.token,
hostname: VITE_KC_API_BASE_URL,
}).catch((err) => console.error('error from Tauri getUser', err)) }).catch((err) => console.error('error from Tauri getUser', err))
const user = await userPromise const user = await userPromise

View File

@ -2,48 +2,78 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton' import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.' import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore' import { useStore } from '../../useStore'
import { SettingsSection } from 'routes/Settings'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import {
CameraSystem,
cameraMouseDragGuards,
cameraSystems,
} from 'lib/cameraControls'
export default function Units() { export default function Units() {
const { buttonDownInStream } = useStore((s) => ({ const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream, buttonDownInStream: s.buttonDownInStream,
})) }))
const dismiss = useDismiss() const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.SKETCHING) const next = useNextClick(onboardingPaths.STREAMING)
const {
settings: {
send,
state: {
context: { cameraControls },
},
},
} = useGlobalStateContext()
return ( return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none"> <div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
<div <div
className={ className={
'max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >
<h1 className="text-2xl font-bold">Camera</h1> <SettingsSection
<p className="mt-6"> title="Camera Controls"
Moving the camera is easy! The controls are as you might expect: description="How you want to control the camera in the 3D view. Try them out above and choose the one that feels most comfortable to you."
</p> className="my-4 last-of-type:mb-12"
<ul className="list-disc list-outside ms-8 mb-4"> >
<li>Click and drag anywhere in the scene to rotate the camera</li> <select
id="camera-controls"
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
value={cameraControls}
onChange={(e) => {
send({
type: 'Set Camera Controls',
data: { cameraControls: e.target.value as CameraSystem },
})
}}
>
{cameraSystems.map((program) => (
<option key={program} value={program}>
{program}
</option>
))}
</select>
<ul className="mx-4 my-2 text-sm leading-relaxed">
<li> <li>
Hold down the <kbd>Shift</kbd> key while clicking and dragging to <strong>Pan:</strong>{' '}
pan the camera {cameraMouseDragGuards[cameraControls].pan.description}
</li> </li>
<li> <li>
Hold down the <kbd>Ctrl</kbd> key while dragging to zoom. You can <strong>Zoom:</strong>{' '}
also use the scroll wheel to zoom in and out. {cameraMouseDragGuards[cameraControls].zoom.description}
</li>
<li>
<strong>Rotate:</strong>{' '}
{cameraMouseDragGuards[cameraControls].rotate.description}
</li> </li>
</ul> </ul>
<p> </SettingsSection>
What you're seeing here is just a video, and your interactions are <div className="flex justify-between">
being sent to our Geometry Engine API, which sends back video frames
in real time. How cool is that? It means that you can use KittyCAD
Modeling App (or whatever you want to build) on any device, even a
cheap laptop with no graphics card!
</p>
<div className="flex justify-between mt-6">
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => dismiss('../../')} onClick={dismiss}
icon={{ icon={{
icon: faXmark, icon: faXmark,
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
@ -59,7 +89,7 @@ export default function Units() {
onClick={next} onClick={next}
icon={{ icon: faArrowRight }} icon={{ icon: faArrowRight }}
> >
Next: Sketching Next: Streaming
</ActionButton> </ActionButton>
</div> </div>
</div> </div>

View File

@ -0,0 +1,85 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { Platform, platform } from '@tauri-apps/api/os'
import { useEffect, useState } from 'react'
export default function CmdK() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.USER_MENU)
const [platformName, setPlatformName] = useState<Platform | ''>('')
useEffect(() => {
async function getPlatform() {
setPlatformName(await platform())
}
getPlatform()
}, [setPlatformName])
return (
<div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none">
<div
className={
'max-w-full xl:max-w-4xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<h2 className="text-2xl">Command Bar</h2>
<p className="my-4">
Press{' '}
{platformName === 'win32' ? (
<>
<kbd>Win</kbd> + <kbd>/</kbd>
</>
) : (
<>
<kbd>OS</kbd> + <kbd>K</kbd>
</>
)}{' '}
to open the command bar. Try changing your theme with it.
</p>
<p className="my-4">
We are working on a command bar that will allow you to quickly see and
search for any available commands. We are building KittyCAD Modeling
App's state management system on top of{' '}
<a
href="https://xstate.js.org/"
rel="noreferrer noopener"
target="_blank"
>
XState
</a>
. Currently you can only control settings, authentication, and file
management from the command bar, but we will be powering modeling
commands with it soon.
</p>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: User Menu
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,85 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
export default function CodeEditor() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
style={{ clipPath: useBackdropHighlight('code-pane') }}
></div>
<div
className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">
Editing code with <code>kcl</code>
</h2>
<p className="my-4">
The left pane is where you write your code. It's a code editor with
syntax highlighting and autocompletion. We've decided to take the
difficult route of writing our own languagecalled <code>kcl</code>
for describing geometry, because don't want to inherit all the
other functionality from existing languages. We have a lot of ideas
about how <code>kcl</code> will evolve, and we want to hear your
thoughts on it.
</p>
<p className="my-4">
We've built a language server for <code>kcl</code> that provides
documentation and autocompletion automatically generated from our
compiler code. You can try it out by hovering over some of the
function names in the pane now. If you like using VSCode, you can
try out our{' '}
<a
href="https://marketplace.visualstudio.com/items?itemName=KittyCAD.kcl-language-server"
rel="noreferrer noopener"
target="_blank"
>
VSCode extension
</a>
.
</p>
<p className="my-4">
You can resize the pane by dragging the handle on the right, and you
can collapse it by clicking the title bar or pressing{' '}
<kbd>Shift</kbd> + <kbd>C</kbd>.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Parametric Modeling
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,65 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
export default function Export() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.SKETCHING)
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div
className={
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">Export</h2>
<p className="my-4">
Try opening the project menu and clicking "Export Model".
</p>
<p className="my-4">
KittyCAD Modeling App uses our open-source extension proposal for
the GLTF file format.{' '}
<a
href="https://kittycad.io/docs/api/convert-cad-file"
rel="noopener noreferrer"
target="_blank"
>
Our conversion API
</a>{' '}
can convert to and from most common CAD file formats, allowing
export to almost any CAD software.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Sketching
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,59 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { useDismiss } from '.'
import { useEffect } from 'react'
import { useStore } from 'useStore'
import { bracket } from 'lib/exampleKcl'
export default function FutureWork() {
const dismiss = useDismiss()
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
useEffect(() => {
deferredSetCode(bracket)
}, [deferredSetCode])
return (
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
<div className="max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Future Work</h1>
<p className="my-4">
We have curves, cuts, and many more CAD features coming soon. We want
your feedback on this user interface, and we want to know what
features you want to see next. Please message us in the Discord server
and open issues on GitHub.
</p>
<p className="my-4">
If you make anything with the app we'd love to see it! Thank you for
taking time to try out KittyCAD Modeling App, and build the future of
hardware design with us 💚.
</p>
<p className="my-4">— The KittyCAD Team</p>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={dismiss}
icon={{ icon: faArrowRight }}
>
Finish
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,125 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
export default function InteractiveNumbers() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.COMMAND_K)
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
style={{ clipPath: useBackdropHighlight('code-pane') }}
></div>
<div
className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1 overflow-y-auto mb-6">
<h2 className="text-2xl">Interactive Numbers</h2>
<p className="my-4">
Let's do a little bit of hybrid editing to this part.
</p>
<p className="my-4">
Try changing the value of <code>width</code> on line 3 by holding
the <kbd>Alt</kbd> (or <kbd>Option</kbd>) key and dragging the
number left and right. You can hold down different modifier keys to
change the value by different increments:
</p>
<table className="border-collapse text-sm mx-auto my-4">
<tbody>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Shift + Cmd/Win</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
0.01
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Cmd/Win</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
0.1
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
1
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Shift</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
10
</td>
</tr>
</tbody>
</table>
<p className="my-4">
Our code editor is built with{' '}
<a
href="https://codemirror.net/"
target="_blank"
rel="noreferrer noopeneer"
>
CodeMirror
</a>
, a great open-source project with extensions that make it even more
dynamic and interactive, including{' '}
<a
href="https://github.com/replit/codemirror-interact/"
target="_blank"
rel="noreferrer noopeneer"
>
one by the Replit team
</a>{' '}
lets you interact with numbers in your code by dragging them around.
</p>
<p className="my-4">
Editing code should feel as interactive as point-and-click when you
want it to be, so that you can work in the way that feels most
natural to you. We're going to keep extending the text editor, and
we'd love to hear your ideas for how to make it better.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Command Bar
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,29 +1,183 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons' import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton' import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.' import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { Themes, getSystemTheme } from 'lib/theme'
import { bracket } from 'lib/exampleKcl'
import { useStore } from 'useStore'
import {
createNewProject,
getNextProjectIndex,
getProjectsInDir,
interpolateProjectNameWithIndex,
} from 'lib/tauriFS'
import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom'
import { paths } from 'Router'
import { useEffect } from 'react'
export default function Introduction() { function OnboardingWithNewFile() {
const navigate = useNavigate()
const dismiss = useDismiss() const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.UNITS) const next = useNextClick(onboardingPaths.INDEX)
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
context: { defaultDirectory, defaultProjectName },
},
} = useGlobalStateContext()
async function createAndOpenNewProject() {
const projects = await getProjectsInDir(defaultDirectory)
const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
const name = interpolateProjectNameWithIndex(defaultProjectName, nextIndex)
const newFile = await createNewProject(defaultDirectory + '/' + name)
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
}
return ( return (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50"> <div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded"> <div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold"> {!isTauri() ? (
Welcome to the KittyCAD Modeling App <>
<h1 className="text-2xl font-bold text-warn-80 dark:text-warn-10">
Replaying onboarding resets your code
</h1> </h1>
<p className="my-2"> <p className="my-4">
A browser-first, GPU-streaming hardware design tool that lets you edit We see you have some of your own code written in this project.
visually, with code, or both. Please save it somewhere else before continuing the onboarding.
</p>
<p className="my-2">
Powered by the first API created for anyone to build hardware design
tools.
</p> </p>
<div className="flex justify-between mt-6"> <div className="flex justify-between mt-6">
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => dismiss('../')} onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={() => {
deferredSetCode(bracket)
next()
}}
icon={{ icon: faArrowRight }}
>
Overwrite code and continue
</ActionButton>
</div>
</>
) : (
<>
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
Would you like to create a new project?
</h1>
<section className="my-12">
<p className="my-4">
You have some content in this project that we don't want to
overwrite. If you would like to create a new project, please
click the button below.
</p>
</section>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={createAndOpenNewProject}
icon={{ icon: faArrowRight }}
>
Make a new project
</ActionButton>
</div>
</>
)}
</div>
</div>
)
}
export default function Introduction() {
const { deferredSetCode, code } = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
state: {
context: { theme },
},
},
} = useGlobalStateContext()
const getLogoTheme = () =>
theme === Themes.Light ||
(theme === Themes.System && getSystemTheme() === Themes.Light)
? '-dark'
: ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.CAMERA)
useEffect(() => {
if (code === '') deferredSetCode(bracket)
}, [code, deferredSetCode])
return !(code !== '' && code !== bracket) ? (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
<img
src={`/kcma-logomark${getLogoTheme()}.svg`}
alt="KittyCAD Modeling App"
className="max-w-full h-20"
/>
<span className="bg-energy-10 text-energy-80 px-3 py-1 rounded-full text-base">
Alpha
</span>
</h1>
<section className="my-12">
<p className="my-4">
Welcome to KittyCAD Modeling App! This is a hardware design tool
that lets you edit visually, with code, or both. It's powered by the
first API created for anyone to build hardware design tools. The 3D
view is not running on your computer, but is instead being streamed
to you from a remote GPU as video.
</p>
<p className="my-4">
This is an alpha release, so you will encounter bugs and missing
features. You can read our{' '}
<a
href="https://gist.github.com/jgomez720/5cd53fb7e8e54079f6dc0d2625de5393"
target="_blank"
rel="noreferrer noopener"
>
expectations for alpha users here
</a>
. Please give us feedback on your experience! We are trying to
release as early as possible to get feedback from users like you.
</p>
</section>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{ icon={{
icon: faXmark, icon: faXmark,
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
@ -44,5 +198,7 @@ export default function Introduction() {
</div> </div>
</div> </div>
</div> </div>
) : (
<OnboardingWithNewFile />
) )
} }

View File

@ -0,0 +1,85 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
import { Themes, getSystemTheme } from 'lib/theme'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
export default function ParametricModeling() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const {
settings: {
context: { theme },
},
} = useGlobalStateContext()
const getImageTheme = () =>
theme === Themes.Light ||
(theme === Themes.System && getSystemTheme() === Themes.Light)
? '-dark'
: ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.INTERACTIVE_NUMBERS)
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
style={{ clipPath: useBackdropHighlight('code-pane') }}
></div>
<div
className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1 overflow-y-auto mb-6">
<h2 className="text-2xl">Towards true parametric modeling</h2>
<p className="my-4">
This example script shows how having access to the code
representation of a part can allow us to do things that are tedious
or impossible in traditional CAD software. Here we are building a
simplified shelf bracket out of aluminum:
</p>
<figure className="my-4 w-3/4 mx-auto">
<img
src={`/onboarding-bracket${getImageTheme()}.png`}
alt="Bracket"
/>
<figcaption className="text-small italic text-center">
A simplified shelf bracket
</figcaption>
</figure>
<p className="my-4">
We are able to easily calculate the thickness of the material based
on the width of the bracket to meet a set safety factor on line 6.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Interactive Numbers
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,56 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { isTauri } from 'lib/isTauri'
export default function ProjectMenu() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.EXPORT)
return (
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
<div
className={
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">Project Menu</h2>
<p className="my-4">
Click on Kitt in the upper left to open the project menu. You can
only {isTauri() && 'go home or '}export your modelwhich we'll talk
about next—for now. We'll add more options here soon, especially as
we add support for multi-file assemblies.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Export
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,21 +1,44 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons' import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton' import { ActionButton } from '../../components/ActionButton'
import { useDismiss } from '.' import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from 'useStore'
import { useEffect } from 'react'
export default function Sketching() { export default function Sketching() {
const { deferredSetCode, buttonDownInStream } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss() const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.FUTURE_WORK)
useEffect(() => {
deferredSetCode('')
}, [deferredSetCode])
return ( return (
<div className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 z-50"> <div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div className="max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded"> <div
className={
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<h1 className="text-2xl font-bold">Sketching</h1> <h1 className="text-2xl font-bold">Sketching</h1>
<p className="mt-6"> <p className="my-4">
We still have to implement this step, and the rest of the tutorial! Our 3D modeling tools are still very much a work in progress, but we
want to show you some early features. Try creating a sketch by
clicking Create Sketch in the top toolbar, then clicking the Line
tool, and clicking in the 3D view.
</p>
<p className="my-4">
Watch the code pane as you click. Point-and-click interactions are
always just modifying and generating code in KittyCAD Modeling App.
</p> </p>
<div className="flex justify-between mt-6"> <div className="flex justify-between mt-6">
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => dismiss('../../')} onClick={dismiss}
icon={{ icon={{
icon: faXmark, icon: faXmark,
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
@ -28,10 +51,10 @@ export default function Sketching() {
</ActionButton> </ActionButton>
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => dismiss('../../')} onClick={next}
icon={{ icon: faArrowRight }} icon={{ icon: faArrowRight }}
> >
Finish Next: Future Work
</ActionButton> </ActionButton>
</div> </div>
</div> </div>

View File

@ -0,0 +1,66 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
export default function Streaming() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.EDITOR)
return (
<div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none">
<div
className={
'max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">Streaming Video</h2>
<p className="my-4">
The 3D view is not running on your computer. Instead, our
infrastructure spins up the KittyCAD Geometry Engine on a remote
GPU, KittyCAD Modeling App sends it a series of commands via
Websockets and WebRTC, and the Geometry Engine sends back a video
stream of the 3D view.
</p>
<p className="my-4">
This means that you could run KittyCAD Modeling App on a Chromebook,
a tablet, or even a phone, as long as you have a good internet
connection.
</p>
<p className="my-4">
It also means that whatever tools you build on top of the KittyCAD
Geometry Engine will be able to run on any device with a browser,
and you won't have to worry about the performance of the device.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Code Editing
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -66,7 +66,7 @@ export default function Units() {
<div className="flex justify-between mt-6"> <div className="flex justify-between mt-6">
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => dismiss('../../')} onClick={dismiss}
icon={{ icon={{
icon: faXmark, icon: faXmark,
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',

View File

@ -0,0 +1,53 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
export default function UserMenu() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PROJECT_MENU)
return (
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
<div
className={
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">User Menu</h2>
<p className="my-4">
Click your avatar on the upper right to open the user menu. You can
change your settings, sign out, or request a feature.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Project Menu
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,18 +1,36 @@
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { Outlet, useNavigate } from 'react-router-dom' import { Outlet, useRouteLoaderData, useNavigate } from 'react-router-dom'
import Introduction from './Introduction' import Introduction from './Introduction'
import Units from './Units'
import Camera from './Camera' import Camera from './Camera'
import Sketching from './Sketching' import Sketching from './Sketching'
import { useCallback } from 'react' import { useCallback } from 'react'
import makeUrlPathRelative from '../../lib/makeUrlPathRelative' import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import Streaming from './Streaming'
import CodeEditor from './CodeEditor'
import ParametricModeling from './ParametricModeling'
import InteractiveNumbers from './InteractiveNumbers'
import CmdK from './CmdK'
import UserMenu from './UserMenu'
import ProjectMenu from './ProjectMenu'
import Export from './Export'
import FutureWork from './FutureWork'
import { IndexLoaderData, paths } from 'Router'
export const onboardingPaths = { export const onboardingPaths = {
INDEX: '/', INDEX: '/',
UNITS: '/units',
CAMERA: '/camera', CAMERA: '/camera',
STREAMING: '/streaming',
EDITOR: '/editor',
PARAMETRIC_MODELING: '/parametric-modeling',
INTERACTIVE_NUMBERS: '/interactive-numbers',
COMMAND_K: '/command-k',
USER_MENU: '/user-menu',
PROJECT_MENU: '/project-menu',
EXPORT: '/export',
MOVE: '/move',
SKETCHING: '/sketching', SKETCHING: '/sketching',
FUTURE_WORK: '/future-work',
} }
export const onboardingRoutes = [ export const onboardingRoutes = [
@ -20,18 +38,51 @@ export const onboardingRoutes = [
index: true, index: true,
element: <Introduction />, element: <Introduction />,
}, },
{
path: makeUrlPathRelative(onboardingPaths.UNITS),
element: <Units />,
},
{ {
path: makeUrlPathRelative(onboardingPaths.CAMERA), path: makeUrlPathRelative(onboardingPaths.CAMERA),
element: <Camera />, element: <Camera />,
}, },
{
path: makeUrlPathRelative(onboardingPaths.STREAMING),
element: <Streaming />,
},
{
path: makeUrlPathRelative(onboardingPaths.EDITOR),
element: <CodeEditor />,
},
{
path: makeUrlPathRelative(onboardingPaths.PARAMETRIC_MODELING),
element: <ParametricModeling />,
},
{
path: makeUrlPathRelative(onboardingPaths.INTERACTIVE_NUMBERS),
element: <InteractiveNumbers />,
},
{
path: makeUrlPathRelative(onboardingPaths.COMMAND_K),
element: <CmdK />,
},
{
path: makeUrlPathRelative(onboardingPaths.USER_MENU),
element: <UserMenu />,
},
{
path: makeUrlPathRelative(onboardingPaths.PROJECT_MENU),
element: <ProjectMenu />,
},
{
path: makeUrlPathRelative(onboardingPaths.EXPORT),
element: <Export />,
},
// Export / conversion API
{ {
path: makeUrlPathRelative(onboardingPaths.SKETCHING), path: makeUrlPathRelative(onboardingPaths.SKETCHING),
element: <Sketching />, element: <Sketching />,
}, },
{
path: makeUrlPathRelative(onboardingPaths.FUTURE_WORK),
element: <FutureWork />,
},
] ]
export function useNextClick(newStatus: string) { export function useNextClick(newStatus: string) {
@ -39,37 +90,44 @@ export function useNextClick(newStatus: string) {
settings: { send }, settings: { send },
} = useGlobalStateContext() } = useGlobalStateContext()
const navigate = useNavigate() const navigate = useNavigate()
const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData
return useCallback(() => { return useCallback(() => {
send({ send({
type: 'Set Onboarding Status', type: 'Set Onboarding Status',
data: { onboardingStatus: newStatus }, data: { onboardingStatus: newStatus },
}) })
navigate((newStatus !== onboardingPaths.UNITS ? '..' : '.') + newStatus) navigate(
}, [newStatus, send, navigate]) paths.FILE +
'/' +
encodeURIComponent(project?.path || 'new') +
paths.ONBOARDING.INDEX.slice(0, -1) +
newStatus
)
}, [project, newStatus, send, navigate])
} }
export function useDismiss() { export function useDismiss() {
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
const { const {
settings: { send }, settings: { send },
} = useGlobalStateContext() } = useGlobalStateContext()
const navigate = useNavigate() const navigate = useNavigate()
return useCallback( return useCallback(() => {
(path: string) => {
send({ send({
type: 'Set Onboarding Status', type: 'Set Onboarding Status',
data: { onboardingStatus: 'dismissed' }, data: { onboardingStatus: 'dismissed' },
}) })
navigate(path) navigate(
}, paths.FILE + '/' + encodeURIComponent(routeData?.project?.path || 'new')
[send, navigate]
) )
}, [send, navigate, routeData])
} }
const Onboarding = () => { const Onboarding = () => {
const dismiss = useDismiss() const dismiss = useDismiss()
useHotkeys('esc', () => dismiss('../')) useHotkeys('esc', dismiss)
return ( return (
<> <>

View File

@ -23,12 +23,14 @@ import {
cameraMouseDragGuards, cameraMouseDragGuards,
} from 'lib/cameraControls' } from 'lib/cameraControls'
import { UnitSystem } from 'machines/settingsMachine' import { UnitSystem } from 'machines/settingsMachine'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export const Settings = () => { export const Settings = () => {
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
useHotkeys('esc', () => navigate('../')) const dotDotSlash = useDotDotSlash()
useHotkeys('esc', () => navigate(dotDotSlash()))
const { const {
settings: { settings: {
send, send,
@ -62,11 +64,11 @@ export const Settings = () => {
} }
return ( return (
<div className="body-bg fixed inset-0 z-40 overflow-auto"> <div className="fixed inset-0 z-40 overflow-auto body-bg">
<AppHeader showToolbar={false} project={loaderData?.project}> <AppHeader showToolbar={false} project={loaderData?.project}>
<ActionButton <ActionButton
Element="link" Element="link"
to={'../'} to={location.pathname.replace(paths.SETTINGS, '')}
icon={{ icon={{
icon: faXmark, icon: faXmark,
bgClassName: 'bg-destroy-80', bgClassName: 'bg-destroy-80',
@ -78,9 +80,9 @@ export const Settings = () => {
Close Close
</ActionButton> </ActionButton>
</AppHeader> </AppHeader>
<div className="my-24 max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto my-24">
<h1 className="text-4xl font-bold">User Settings</h1> <h1 className="text-4xl font-bold">User Settings</h1>
<p className="mt-6 max-w-2xl"> <p className="max-w-2xl mt-6">
Don't see the feature you want? Check to see if it's on{' '} Don't see the feature you want? Check to see if it's on{' '}
<a <a
href="https://github.com/KittyCAD/modeling-app/discussions" href="https://github.com/KittyCAD/modeling-app/discussions"
@ -98,7 +100,7 @@ export const Settings = () => {
> >
<select <select
id="camera-controls" id="camera-controls"
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent" className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
value={cameraControls} value={cameraControls}
onChange={(e) => { onChange={(e) => {
send({ send({
@ -113,7 +115,7 @@ export const Settings = () => {
</option> </option>
))} ))}
</select> </select>
<ul className="text-sm my-2 mx-4 leading-relaxed"> <ul className="mx-4 my-2 text-sm leading-relaxed">
<li> <li>
<strong>Pan:</strong>{' '} <strong>Pan:</strong>{' '}
{cameraMouseDragGuards[cameraControls].pan.description} {cameraMouseDragGuards[cameraControls].pan.description}
@ -134,7 +136,7 @@ export const Settings = () => {
title="Default Directory" title="Default Directory"
description="Where newly-created projects are saved on your local computer" description="Where newly-created projects are saved on your local computer"
> >
<div className="w-full flex gap-4 p-1 rounded border border-chalkboard-30"> <div className="flex w-full gap-4 p-1 border rounded border-chalkboard-30">
<input <input
className="flex-1 px-2 bg-transparent" className="flex-1 px-2 bg-transparent"
value={defaultDirectory} value={defaultDirectory}
@ -161,7 +163,7 @@ export const Settings = () => {
description="Name template for new projects. Use $n to include an incrementing index" description="Name template for new projects. Use $n to include an incrementing index"
> >
<input <input
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent" className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
defaultValue={defaultProjectName} defaultValue={defaultProjectName}
onBlur={(e) => { onBlur={(e) => {
const newValue = e.target.value.trim() || DEFAULT_PROJECT_NAME const newValue = e.target.value.trim() || DEFAULT_PROJECT_NAME
@ -205,7 +207,7 @@ export const Settings = () => {
> >
<select <select
id="base-unit" id="base-unit"
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent" className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
value={baseUnit} value={baseUnit}
onChange={(e) => { onChange={(e) => {
send({ send({
@ -239,7 +241,7 @@ export const Settings = () => {
> >
<select <select
id="settings-theme" id="settings-theme"
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent" className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
value={theme} value={theme}
onChange={(e) => { onChange={(e) => {
send({ send({
@ -267,7 +269,7 @@ export const Settings = () => {
type: 'Set Onboarding Status', type: 'Set Onboarding Status',
data: { onboardingStatus: '' }, data: { onboardingStatus: '' },
}) })
navigate('..' + paths.ONBOARDING.INDEX) navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
}} }}
icon={{ icon: faArrowRotateBack }} icon={{ icon: faArrowRotateBack }}
> >
@ -283,15 +285,22 @@ export const Settings = () => {
interface SettingsSectionProps extends React.PropsWithChildren { interface SettingsSectionProps extends React.PropsWithChildren {
title: string title: string
description?: string description?: string
className?: string
} }
export function SettingsSection({ export function SettingsSection({
title, title,
description, description,
className,
children, children,
}: SettingsSectionProps) { }: SettingsSectionProps) {
return ( return (
<section className="my-16 last-of-type:mb-24 grid grid-cols-2 gap-12 items-start"> <section
className={
'my-16 last-of-type:mb-24 grid grid-cols-2 gap-12 items-start ' +
className
}
>
<div className="w-80"> <div className="w-80">
<h2 className="text-2xl">{title}</h2> <h2 className="text-2xl">{title}</h2>
<p className="mt-2 text-sm">{description}</p> <p className="mt-2 text-sm">{description}</p>

View File

@ -18,6 +18,7 @@ import { EngineCommandManager } from './lang/std/engineConnection'
import { KCLError } from './lang/errors' import { KCLError } from './lang/errors'
import { deferExecution } from 'lib/utils' import { deferExecution } from 'lib/utils'
import { _executor } from './lang/executor' import { _executor } from './lang/executor'
import { bracket } from 'lib/exampleKcl'
export type Selection = { export type Selection = {
type: 'default' | 'line-end' | 'line-mid' type: 'default' | 'line-end' | 'line-mid'
@ -70,7 +71,7 @@ export type GuiModes =
waitingFirstClick: boolean waitingFirstClick: boolean
rotation: Rotation rotation: Rotation
position: Position position: Position
id?: string pathId: string
pathToNode: PathToNode pathToNode: PathToNode
} }
| { | {
@ -79,6 +80,15 @@ export type GuiModes =
rotation: Rotation rotation: Rotation
position: Position position: Position
pathToNode: PathToNode pathToNode: PathToNode
pathId: string
}
| {
mode: 'sketch'
sketchMode: 'enterSketchEdit'
rotation: Rotation
position: Position
pathToNode: PathToNode
pathId: string
} }
| { | {
mode: 'sketch' mode: 'sketch'
@ -312,7 +322,7 @@ export const useStore = create<StoreState>()(
end: 0, end: 0,
body: [], body: [],
nonCodeMeta: { nonCodeMeta: {
noneCodeNodes: {}, nonCodeNodes: {},
start: null, start: null,
}, },
}, },
@ -409,7 +419,7 @@ export const useStore = create<StoreState>()(
}, 100) as unknown as number }, 100) as unknown as number
) )
}, },
code: '', code: bracket,
setCode: (code) => set({ code }), setCode: (code) => set({ code }),
deferredSetCode: (code) => { deferredSetCode: (code) => {
set({ code }) set({ code })
@ -544,7 +554,7 @@ async function executeCode({
end: 0, end: 0,
body: [], body: [],
nonCodeMeta: { nonCodeMeta: {
noneCodeNodes: {}, nonCodeNodes: {},
start: null, start: null,
}, },
}, },

View File

@ -0,0 +1,22 @@
# Each test can have at most 4 threads, but if its name contains "serial_test_", then it
# also requires 4 threads.
# This means such tests run one at a time, with 4 threads.
[test-groups]
serial-integration = { max-threads = 4 }
[profile.default]
slow-timeout = { period = "10s", terminate-after = 1 }
[profile.ci]
slow-timeout = { period = "60s", terminate-after = 10 }
[[profile.default.overrides]]
filter = "test(serial_test_)"
test-group = "serial-integration"
threads-required = 4
[[profile.ci.overrides]]
filter = "test(serial_test_)"
test-group = "serial-integration"
threads-required = 4

420
src/wasm-lib/Cargo.lock generated
View File

@ -41,9 +41,9 @@ dependencies = [
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.0.4" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" checksum = "0f2135563fb5c609d2b2b87c1e8ce7bc41b0b45430fa9661f457981503dd5bf0"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -63,6 +63,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.5.0" version = "0.5.0"
@ -79,9 +85,9 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
@ -150,7 +156,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -205,9 +211,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.2" version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]] [[package]]
name = "bigdecimal" name = "bigdecimal"
@ -220,6 +226,20 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "bigdecimal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "454bca3db10617b88b566f205ed190aedb0e0e6dd4cad61d3988a72e8c5594cb"
dependencies = [
"autocfg",
"libm",
"num-bigint",
"num-integer",
"num-traits",
"serde",
]
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "1.3.3" version = "1.3.3"
@ -312,9 +332,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.13.0" version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
@ -330,13 +350,19 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@ -363,9 +389,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.30" version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@ -374,6 +400,33 @@ dependencies = [
"windows-targets 0.48.5", "windows-targets 0.48.5",
] ]
[[package]]
name = "ciborium"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
[[package]]
name = "ciborium-ll"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
dependencies = [
"ciborium-io",
"half 1.8.2",
]
[[package]] [[package]]
name = "clang-sys" name = "clang-sys"
version = "1.6.1" version = "1.6.1"
@ -387,9 +440,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.4.3" version = "4.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -397,9 +450,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.4.2" version = "4.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -419,7 +472,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -507,6 +560,42 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools 0.10.5",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools 0.10.5",
]
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.8" version = "0.5.8"
@ -593,7 +682,7 @@ checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]] [[package]]
name = "derive-docs" name = "derive-docs"
version = "0.1.3" version = "0.1.4"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"expectorate", "expectorate",
@ -603,21 +692,7 @@ dependencies = [
"quote", "quote",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.33", "syn 2.0.37",
]
[[package]]
name = "derive-docs"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe5c5ea065cfabc5a7c5e8ed616e369fbf108c4be01e0e5609bc9846a732664"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"serde",
"serde_tokenstream",
"syn 2.0.33",
] ]
[[package]] [[package]]
@ -692,9 +767,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
dependencies = [ dependencies = [
"errno-dragonfly", "errno-dragonfly",
"libc", "libc",
@ -724,13 +799,13 @@ dependencies = [
[[package]] [[package]]
name = "exr" name = "exr"
version = "1.7.0" version = "1.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18" checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8"
dependencies = [ dependencies = [
"bit_field", "bit_field",
"flume", "flume",
"half", "half 2.2.1",
"lebe", "lebe",
"miniz_oxide", "miniz_oxide",
"rayon-core", "rayon-core",
@ -790,14 +865,10 @@ dependencies = [
[[package]] [[package]]
name = "flume" name = "flume"
version = "0.10.14" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
dependencies = [ dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"pin-project",
"spin 0.9.8", "spin 0.9.8",
] ]
@ -891,7 +962,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -1001,6 +1072,12 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]] [[package]]
name = "half" name = "half"
version = "2.2.1" version = "2.2.1"
@ -1227,17 +1304,6 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.2",
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.8.0" version = "2.8.0"
@ -1251,7 +1317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [ dependencies = [
"hermit-abi 0.3.2", "hermit-abi 0.3.2",
"rustix 0.38.9", "rustix",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -1310,13 +1376,15 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.1.26" version = "0.1.30"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"bson", "bson",
"clap", "clap",
"criterion",
"dashmap", "dashmap",
"derive-docs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "derive-docs",
"expectorate", "expectorate",
"futures", "futures",
"itertools 0.11.0", "itertools 0.11.0",
@ -1338,17 +1406,19 @@ dependencies = [
"uuid", "uuid",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys",
] ]
[[package]] [[package]]
name = "kittycad" name = "kittycad"
version = "0.2.25" version = "0.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7" checksum = "e2623ee601ce203476229df3f9d3a14664cb43e3f7455e9ac8ed91aacaa6163d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"base64 0.21.2", "base64 0.21.4",
"bigdecimal 0.4.1",
"bytes", "bytes",
"chrono", "chrono",
"data-encoding", "data-encoding",
@ -1396,9 +1466,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1410,6 +1480,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "libm"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.6" version = "0.5.6"
@ -1418,15 +1494,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.8" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
[[package]]
name = "linux-raw-sys"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -1477,9 +1547,9 @@ checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@ -1533,15 +1603,6 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "newline-converter" name = "newline-converter"
version = "0.3.0" version = "0.3.0"
@ -1629,9 +1690,9 @@ checksum = "049950a25a8f69e9673ed52fc58749548cee71194f6c3a8a04b80863637ce722"
[[package]] [[package]]
name = "object" name = "object"
version = "0.32.0" version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1648,10 +1709,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2" checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]] [[package]]
name = "openapitor" name = "openapitor"
version = "0.0.9" version = "0.0.9"
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#3d74c1dfb41146a268a644e9fde2c19a8cd66895" source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#0d121f6881da91b4a30bee18bbfe50e4a2096073"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -1795,9 +1862,9 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
"regex-syntax 0.7.4", "regex-syntax 0.7.5",
"structmeta", "structmeta",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -1814,10 +1881,11 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.7.2" version = "2.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33"
dependencies = [ dependencies = [
"memchr",
"thiserror", "thiserror",
"ucd-trie", "ucd-trie",
] ]
@ -1859,7 +1927,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -1880,6 +1948,34 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "plotters"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
[[package]]
name = "plotters-svg"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
dependencies = [
"plotters-backend",
]
[[package]] [[package]]
name = "png" name = "png"
version = "0.17.10" version = "0.17.10"
@ -2058,25 +2154,25 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.3" version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-automata", "regex-automata",
"regex-syntax 0.7.4", "regex-syntax 0.7.5",
] ]
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.6" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-syntax 0.7.4", "regex-syntax 0.7.5",
] ]
[[package]] [[package]]
@ -2099,9 +2195,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
@ -2109,7 +2205,7 @@ version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [ dependencies = [
"base64 0.21.2", "base64 0.21.4",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
@ -2264,36 +2360,22 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.23" version = "0.38.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys 0.48.0",
]
[[package]]
name = "rustix"
version = "0.38.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags 2.4.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.5", "linux-raw-sys",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.21.6" version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
dependencies = [ dependencies = [
"log", "log",
"ring", "ring",
@ -2319,14 +2401,14 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
dependencies = [ dependencies = [
"base64 0.21.2", "base64 0.21.4",
] ]
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.101.4" version = "0.101.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -2364,11 +2446,12 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.13" version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
dependencies = [ dependencies = [
"bigdecimal", "bigdecimal 0.3.1",
"bigdecimal 0.4.1",
"bytes", "bytes",
"chrono", "chrono",
"dyn-clone", "dyn-clone",
@ -2381,9 +2464,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars_derive" name = "schemars_derive"
version = "0.8.13" version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2474,7 +2557,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -2490,9 +2573,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.106" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [ dependencies = [
"indexmap 2.0.0", "indexmap 2.0.0",
"itoa", "itoa",
@ -2508,7 +2591,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -2520,7 +2603,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -2699,9 +2782,9 @@ dependencies = [
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.3" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.48.0", "windows-sys 0.48.0",
@ -2737,7 +2820,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive", "structmeta-derive",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -2748,7 +2831,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -2764,9 +2847,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.33" version = "2.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2803,7 +2886,7 @@ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"redox_syscall 0.3.5", "redox_syscall 0.3.5",
"rustix 0.38.9", "rustix",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -2829,11 +2912,11 @@ dependencies = [
[[package]] [[package]]
name = "terminal_size" name = "terminal_size"
version = "0.2.6" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [ dependencies = [
"rustix 0.37.23", "rustix",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -2854,7 +2937,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -2880,9 +2963,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.27" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07" checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
@ -2901,13 +2984,23 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.13" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9" checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
dependencies = [ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -2937,7 +3030,7 @@ dependencies = [
"parking_lot 0.12.1", "parking_lot 0.12.1",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2 0.5.3", "socket2 0.5.4",
"tokio-macros", "tokio-macros",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -2950,7 +3043,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -3066,7 +3159,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -3095,7 +3188,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
] ]
[[package]] [[package]]
@ -3181,7 +3274,7 @@ dependencies = [
"Inflector", "Inflector",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
"termcolor", "termcolor",
] ]
@ -3220,9 +3313,9 @@ dependencies = [
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.16.0" version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
@ -3247,9 +3340,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.11" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@ -3286,9 +3379,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.4.0" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
@ -3339,9 +3432,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.3" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [ dependencies = [
"same-file", "same-file",
"winapi-util", "winapi-util",
@ -3383,7 +3476,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3418,7 +3511,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.33", "syn 2.0.37",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3441,6 +3534,7 @@ dependencies = [
"js-sys", "js-sys",
"kcl-lib", "kcl-lib",
"kittycad", "kittycad",
"pretty_assertions",
"reqwest", "reqwest",
"serde_json", "serde_json",
"tokio", "tokio",

View File

@ -12,7 +12,8 @@ bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
gloo-utils = "0.2.0" gloo-utils = "0.2.0"
kcl-lib = { path = "kcl" } kcl-lib = { path = "kcl" }
kittycad = { version = "0.2.25", default-features = false, features = ["js"] } kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
serde_json = "1.0.106" serde_json = "1.0.107"
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
wasm-bindgen = "0.2.87" wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4.37" wasm-bindgen-futures = "0.4.37"
@ -20,6 +21,7 @@ wasm-bindgen-futures = "0.4.37"
anyhow = "1" anyhow = "1"
image = "0.24.7" image = "0.24.7"
kittycad = "0.2.25" kittycad = "0.2.25"
pretty_assertions = "1.4.0"
reqwest = { version = "0.11.20", default-features = false } reqwest = { version = "0.11.20", default-features = false }
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] } tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
twenty-twenty = "0.6.1" twenty-twenty = "0.6.1"
@ -50,3 +52,11 @@ members = [
"derive-docs", "derive-docs",
"kcl", "kcl",
] ]
[[test]]
name = "executor"
path = "tests/executor/main.rs"
[[test]]
name = "modify"
path = "tests/modify/main.rs"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "derive-docs" name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.3" version = "0.1.4"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -16,7 +16,7 @@ proc-macro2 = "1"
quote = "1" quote = "1"
serde = { version = "1.0.188", features = ["derive"] } serde = { version = "1.0.188", features = ["derive"] }
serde_tokenstream = "0.2" serde_tokenstream = "0.2"
syn = { version = "2.0.33", features = ["full"] } syn = { version = "2.0.37", features = ["full"] }
[dev-dependencies] [dev-dependencies]
expectorate = "1.0.7" expectorate = "1.0.7"

View File

@ -212,6 +212,12 @@ fn do_stdlib_inner(
quote! { quote! {
Vec<#ty_ident> Vec<#ty_ident>
} }
} else if ty_string.starts_with("Box<") {
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
let ty_ident = format_ident!("{}", ty_string);
quote! {
#ty_ident
}
} else { } else {
let ty_ident = format_ident!("{}", ty_string); let ty_ident = format_ident!("{}", ty_string);
quote! { quote! {
@ -250,7 +256,15 @@ fn do_stdlib_inner(
.replace("Result < ", "") .replace("Result < ", "")
.replace(", KclError >", ""); .replace(", KclError >", "");
let return_type = if !ret_ty_string.is_empty() { let return_type = if !ret_ty_string.is_empty() {
let ret_ty_string = ret_ty_string.trim().to_string(); let ret_ty_string = if ret_ty_string.starts_with("Box <") {
ret_ty_string
.trim_start_matches("Box <")
.trim_end_matches('>')
.trim()
.to_string()
} else {
ret_ty_string.trim().to_string()
};
let ret_ty_ident = format_ident!("{}", ret_ty_string); let ret_ty_ident = format_ident!("{}", ret_ty_string);
let ret_ty_string = clean_type(&ret_ty_string); let ret_ty_string = clean_type(&ret_ty_string);
quote! { quote! {
@ -475,6 +489,9 @@ fn clean_type(t: &str) -> String {
if t.starts_with("Vec<") { if t.starts_with("Vec<") {
t = t.replace("Vec<", "[").replace('>', "]"); t = t.replace("Vec<", "[").replace('>', "]");
} }
if t.starts_with("Box<") {
t = t.replace("Box<", "").replace('>', "");
}
if t == "f64" { if t == "f64" {
return "number".to_string(); return "number".to_string();
@ -564,4 +581,26 @@ mod tests {
assert!(errors.is_empty()); assert!(errors.is_empty());
expectorate::assert_contents("tests/show.gen", &openapitor::types::get_text_fmt(&item).unwrap()); expectorate::assert_contents("tests/show.gen", &openapitor::types::get_text_fmt(&item).unwrap());
} }
#[test]
fn test_stdlib_box() {
let (item, errors) = do_stdlib(
quote! {
name = "show",
},
quote! {
fn inner_show(
/// The args to do shit to.
args: Box<f64>
) -> Box<f64> {
args
}
},
)
.unwrap();
let _expected = quote! {};
assert!(errors.is_empty());
expectorate::assert_contents("tests/box.gen", &openapitor::types::get_text_fmt(&item).unwrap());
}
} }

View File

@ -0,0 +1,70 @@
#[allow(non_camel_case_types, missing_docs)]
#[doc = "Std lib function: show"]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
#[ts(export)]
pub(crate) struct Show {}
#[allow(non_upper_case_globals, missing_docs)]
#[doc = "Std lib function: show"]
pub(crate) const Show: Show = Show {};
impl crate::docs::StdLibFn for Show {
fn name(&self) -> String {
"show".to_string()
}
fn summary(&self) -> String {
"".to_string()
}
fn description(&self) -> String {
"".to_string()
}
fn tags(&self) -> Vec<String> {
vec![]
}
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "number".to_string(),
schema: f64::json_schema(&mut generator),
required: true,
}]
}
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
Some(crate::docs::StdLibFnArg {
name: "".to_string(),
type_: "number".to_string(),
schema: f64::json_schema(&mut generator),
required: true,
})
}
fn unpublished(&self) -> bool {
false
}
fn deprecated(&self) -> bool {
false
}
fn std_lib_fn(&self) -> crate::std::StdFn {
show
}
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
Box::new(self.clone())
}
}
fn inner_show(#[doc = r" The args to do shit to."] args: Box<f64>) -> Box<f64> {
args
}

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language" description = "KittyCAD Language"
version = "0.1.26" version = "0.1.30"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -9,17 +9,18 @@ license = "MIT"
[dependencies] [dependencies]
anyhow = { version = "1.0.75", features = ["backtrace"] } anyhow = { version = "1.0.75", features = ["backtrace"] }
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"] } async-trait = "0.1.73"
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"], optional = true }
dashmap = "5.5.3" dashmap = "5.5.3"
derive-docs = { version = "0.1.3" } #derive-docs = { version = "0.1.4" }
#derive-docs = { path = "../derive-docs" } derive-docs = { path = "../derive-docs" }
kittycad = { version = "0.2.25", default-features = false, features = ["js"] } kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
parse-display = "0.8.2" parse-display = "0.8.2"
regex = "1.7.1" regex = "1.7.1"
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] } schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
serde = { version = "1.0.188", features = ["derive"] } serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.106" serde_json = "1.0.107"
thiserror = "1.0.48" thiserror = "1.0.48"
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] } ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] }
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] } uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
@ -29,6 +30,7 @@ js-sys = { version = "0.3.64" }
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] } tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
wasm-bindgen = "0.2.87" wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4.37" wasm-bindgen-futures = "0.4.37"
web-sys = { version = "0.3.64", features = ["console"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] } bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
@ -40,6 +42,7 @@ tower-lsp = { version = "0.20.0", features = ["proposed"] }
[features] [features]
default = ["engine"] default = ["engine"]
cli = ["dep:clap"]
engine = [] engine = []
[profile.release] [profile.release]
@ -47,7 +50,12 @@ panic = "abort"
debug = true debug = true
[dev-dependencies] [dev-dependencies]
criterion = "0.5.1"
expectorate = "1.0.7" expectorate = "1.0.7"
itertools = "0.11.0" itertools = "0.11.0"
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] } tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
[[bench]]
name = "compiler_benchmark"
harness = false

View File

@ -0,0 +1,27 @@
use criterion::{criterion_group, criterion_main, Criterion};
pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("parse + lex cube program", |b| b.iter(lex_and_parse_cube));
}
fn lex_and_parse_cube() {
let program = r#"
fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const b1 = cube([0,0], 10)
const pt1 = b1[0]
show(b1)"#;
let tokens = kcl_lib::tokeniser::lexer(program);
let parser = kcl_lib::parser::Parser::new(tokens);
parser.ast().unwrap();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -0,0 +1,2 @@
pub mod modify;
pub mod types;

View File

@ -0,0 +1,294 @@
use kittycad::types::{ModelingCmd, Point3D};
use super::types::ConstraintLevel;
use crate::{
ast::types::{
ArrayExpression, CallExpression, FormatOptions, Literal, PipeExpression, PipeSubstitution, Program,
VariableDeclarator,
},
engine::{EngineConnection, EngineManager},
errors::{KclError, KclErrorDetails},
executor::{Point2d, SourceRange},
};
#[derive(Debug)]
/// The control point data for a curve or line.
pub struct ControlPointData {
/// The control points for the curve or line.
pub points: Vec<kittycad::types::Point3D>,
/// The command that created this curve or line.
pub command: kittycad::types::PathCommand,
/// The id of the curve or line.
pub id: uuid::Uuid,
}
const EPSILON: f64 = 0.015625; // or 2^-6
/// Update the AST to reflect the new state of the program after something like
/// a move or a new line.
pub async fn modify_ast_for_sketch(
engine: &mut EngineConnection,
program: &mut Program,
// The name of the sketch.
sketch_name: &str,
// The ID of the parent sketch.
sketch_id: uuid::Uuid,
) -> Result<String, KclError> {
// First we need to check if this sketch is constrained (even partially).
// If it is, we cannot modify it.
// Get the information about the sketch.
if let Some(ast_sketch) = program.get_variable(sketch_name) {
let constraint_level = ast_sketch.get_constraint_level();
match &constraint_level {
ConstraintLevel::None { source_ranges: _ } => {}
ConstraintLevel::Ignore { source_ranges: _ } => {}
ConstraintLevel::Partial {
source_ranges: _,
levels,
} => {
return Err(KclError::Engine(KclErrorDetails {
message: format!(
"Sketch {} is constrained `{}` and cannot be modified",
sketch_name, constraint_level
),
source_ranges: levels.get_all_partial_or_full_source_ranges(),
}));
}
ConstraintLevel::Full { source_ranges } => {
return Err(KclError::Engine(KclErrorDetails {
message: format!(
"Sketch {} is constrained `{}` and cannot be modified",
sketch_name, constraint_level
),
source_ranges: source_ranges.clone(),
}));
}
}
}
// Let's start by getting the path info.
// Let's get the path info.
let resp = engine
.send_modeling_cmd_get_response(
uuid::Uuid::new_v4(),
SourceRange::default(),
ModelingCmd::PathGetInfo { path_id: sketch_id },
)
.await?;
let kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::PathGetInfo { data: path_info },
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("Get path info response was not as expected: {:?}", resp),
source_ranges: vec![SourceRange::default()],
}));
};
/* // Let's try to get the children of the sketch.
let resp = engine
.send_modeling_cmd_get_response(
uuid::Uuid::new_v4(),
SourceRange::default(),
ModelingCmd::EntityGetAllChildUuids { entity_id: sketch_id },
)
.await?;
let kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::EntityGetAllChildUuids { data: children_info },
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("Get child info response was not as expected: {:?}", resp),
source_ranges: vec![SourceRange::default()],
}));
};
println!("children_info: {:#?}", children_info);
// Let's try to get the parent id.
let resp = engine
.send_modeling_cmd_get_response(
uuid::Uuid::new_v4(),
SourceRange::default(),
ModelingCmd::EntityGetParentId { entity_id: sketch_id },
)
.await?;
let kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::EntityGetParentId { data: parent_info },
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("Get parent id response was not as expected: {:?}", resp),
source_ranges: vec![SourceRange::default()],
}));
};
println!("parent_info: {:#?}", parent_info);*/
// Now let's get the control points for all the segments.
// TODO: We should probably await all these at once so we aren't going one by one.
// But I guess this is fine for now.
// We absolutely have to preserve the order of the control points.
let mut control_points = Vec::new();
for segment in &path_info.segments {
if let Some(command_id) = &segment.command_id {
let h = engine.send_modeling_cmd_get_response(
uuid::Uuid::new_v4(),
SourceRange::default(),
ModelingCmd::CurveGetControlPoints { curve_id: *command_id },
);
let kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::CurveGetControlPoints { data },
} = h.await?
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("Curve get control points response was not as expected: {:?}", resp),
source_ranges: vec![SourceRange::default()],
}));
};
control_points.push(ControlPointData {
points: data.control_points.clone(),
command: segment.command.clone(),
id: *command_id,
});
}
}
if control_points.is_empty() {
return Err(KclError::Engine(KclErrorDetails {
message: format!("No control points found for sketch {}", sketch_name),
source_ranges: vec![SourceRange::default()],
}));
}
let first_control_points = control_points.first().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!("No control points found for sketch {}", sketch_name),
source_ranges: vec![SourceRange::default()],
})
})?;
let mut additional_lines = Vec::new();
let mut last_point = first_control_points.points[1].clone();
for control_point in control_points[1..].iter() {
additional_lines.push([
(control_point.points[1].x - last_point.x),
(control_point.points[1].y - last_point.y),
]);
last_point = Point3D {
x: control_point.points[1].x,
y: control_point.points[1].y,
z: control_point.points[1].z,
};
}
// Okay now let's recalculate the sketch from the control points.
let start_sketch_at_end = Point3D {
x: (first_control_points.points[1].x - first_control_points.points[0].x),
y: (first_control_points.points[1].y - first_control_points.points[0].y),
z: (first_control_points.points[1].z - first_control_points.points[0].z),
};
let sketch = create_start_sketch_at(
sketch_name,
[first_control_points.points[0].x, first_control_points.points[0].y],
[start_sketch_at_end.x, start_sketch_at_end.y],
additional_lines,
)?;
// Add the sketch back to the program.
program.replace_variable(sketch_name, sketch);
let recasted = program.recast(&FormatOptions::default(), 0);
// Re-parse the ast so we get the correct source ranges.
let tokens = crate::tokeniser::lexer(&recasted);
let parser = crate::parser::Parser::new(tokens);
*program = parser.ast()?;
Ok(recasted)
}
/// Create a pipe expression that starts a sketch at the given point and draws a line to the given point.
fn create_start_sketch_at(
name: &str,
start: [f64; 2],
end: [f64; 2],
additional_lines: Vec<[f64; 2]>,
) -> Result<VariableDeclarator, KclError> {
let start_sketch_at = CallExpression::new(
"startSketchAt",
vec![ArrayExpression::new(vec![
Literal::new(round_before_recast(start[0]).into()).into(),
Literal::new(round_before_recast(start[1]).into()).into(),
])
.into()],
)?;
// Keep track of where we are so we can close the sketch if we need to.
let mut current_position = Point2d {
x: start[0],
y: start[1],
};
current_position.x += end[0];
current_position.y += end[1];
let initial_line = CallExpression::new(
"line",
vec![
ArrayExpression::new(vec![
Literal::new(round_before_recast(end[0]).into()).into(),
Literal::new(round_before_recast(end[1]).into()).into(),
])
.into(),
PipeSubstitution::new().into(),
],
)?;
let mut pipe_body = vec![start_sketch_at.into(), initial_line.into()];
for (index, line) in additional_lines.iter().enumerate() {
current_position.x += line[0];
current_position.y += line[1];
// If we are on the last line, check if we have to close the sketch.
if index == additional_lines.len() - 1 {
let diff_x = (current_position.x - start[0]).abs();
let diff_y = (current_position.y - start[1]).abs();
// Compare the end of the last line to the start of the first line.
// This is a bit more lenient if you look at the value of epsilon.
if diff_x <= EPSILON && diff_y <= EPSILON {
// We have to close the sketch.
let close = CallExpression::new("close", vec![PipeSubstitution::new().into()])?;
pipe_body.push(close.into());
break;
}
}
// TODO: we should check if we should close the sketch.
let line = CallExpression::new(
"line",
vec![
ArrayExpression::new(vec![
Literal::new(round_before_recast(line[0]).into()).into(),
Literal::new(round_before_recast(line[1]).into()).into(),
])
.into(),
PipeSubstitution::new().into(),
],
)?;
pipe_body.push(line.into());
}
Ok(VariableDeclarator::new(name, PipeExpression::new(pipe_body).into()))
}
fn round_before_recast(num: f64) -> f64 {
// We use 2 decimal places.
(num * 100.0).round() / 100.0
}

View File

@ -23,7 +23,7 @@ pub struct Program {
pub start: usize, pub start: usize,
pub end: usize, pub end: usize,
pub body: Vec<BodyItem>, pub body: Vec<BodyItem>,
pub non_code_meta: NoneCodeMeta, pub non_code_meta: NonCodeMeta,
} }
impl Program { impl Program {
@ -81,7 +81,7 @@ impl Program {
"\n".to_string() "\n".to_string()
}; };
let custom_white_space_or_comment = match self.non_code_meta.none_code_nodes.get(&index) { let custom_white_space_or_comment = match self.non_code_meta.non_code_nodes.get(&index) {
Some(custom_white_space_or_comment) => custom_white_space_or_comment.format(&indentation), Some(custom_white_space_or_comment) => custom_white_space_or_comment.format(&indentation),
None => String::new(), None => String::new(),
}; };
@ -237,6 +237,47 @@ impl Program {
} }
} }
} }
/// Replace a variable declaration with the given name with a new one.
pub fn replace_variable(&mut self, name: &str, declarator: VariableDeclarator) {
for item in &mut self.body {
match item {
BodyItem::ExpressionStatement(_expression_statement) => {
continue;
}
BodyItem::VariableDeclaration(ref mut variable_declaration) => {
for declaration in &mut variable_declaration.declarations {
if declaration.id.name == name {
*declaration = declarator;
return;
}
}
}
BodyItem::ReturnStatement(_return_statement) => continue,
}
}
}
/// Get the variable declaration with the given name.
pub fn get_variable(&self, name: &str) -> Option<&VariableDeclarator> {
for item in &self.body {
match item {
BodyItem::ExpressionStatement(_expression_statement) => {
continue;
}
BodyItem::VariableDeclaration(variable_declaration) => {
for declaration in &variable_declaration.declarations {
if declaration.id.name == name {
return Some(declaration);
}
}
}
BodyItem::ReturnStatement(_return_statement) => continue,
}
}
None
}
} }
pub trait ValueMeta { pub trait ValueMeta {
@ -247,7 +288,7 @@ pub trait ValueMeta {
macro_rules! impl_value_meta { macro_rules! impl_value_meta {
{$name:ident} => { {$name:ident} => {
impl crate::abstract_syntax_tree_types::ValueMeta for $name { impl crate::ast::types::ValueMeta for $name {
fn start(&self) -> usize { fn start(&self) -> usize {
self.start self.start
} }
@ -426,6 +467,26 @@ impl Value {
Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name), Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
} }
} }
/// Get the constraint level for a value type.
pub fn get_constraint_level(&self) -> ConstraintLevel {
match self {
Value::Literal(literal) => literal.get_constraint_level(),
Value::Identifier(identifier) => identifier.get_constraint_level(),
Value::BinaryExpression(binary_expression) => binary_expression.get_constraint_level(),
Value::FunctionExpression(function_identifier) => function_identifier.get_constraint_level(),
Value::CallExpression(call_expression) => call_expression.get_constraint_level(),
Value::PipeExpression(pipe_expression) => pipe_expression.get_constraint_level(),
Value::PipeSubstitution(pipe_substitution) => ConstraintLevel::Ignore {
source_ranges: vec![pipe_substitution.into()],
},
Value::ArrayExpression(array_expression) => array_expression.get_constraint_level(),
Value::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
Value::MemberExpression(member_expression) => member_expression.get_constraint_level(),
Value::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
}
}
} }
impl From<Value> for crate::executor::SourceRange { impl From<Value> for crate::executor::SourceRange {
@ -465,6 +526,18 @@ impl From<&BinaryPart> for crate::executor::SourceRange {
} }
impl BinaryPart { impl BinaryPart {
/// Get the constraint level.
pub fn get_constraint_level(&self) -> ConstraintLevel {
match self {
BinaryPart::Literal(literal) => literal.get_constraint_level(),
BinaryPart::Identifier(identifier) => identifier.get_constraint_level(),
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_constraint_level(),
BinaryPart::CallExpression(call_expression) => call_expression.get_constraint_level(),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
}
}
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
match &self { match &self {
BinaryPart::Literal(literal) => literal.recast(), BinaryPart::Literal(literal) => literal.recast(),
@ -521,11 +594,7 @@ impl BinaryPart {
} }
BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, &mut new_pipe_info, engine), BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, &mut new_pipe_info, engine),
BinaryPart::UnaryExpression(unary_expression) => { BinaryPart::UnaryExpression(unary_expression) => {
// Return an error this should not happen. unary_expression.get_result(memory, &mut new_pipe_info, engine)
Err(KclError::Semantic(KclErrorDetails {
message: format!("UnaryExpression should not be a BinaryPart: {:?}", unary_expression),
source_ranges: vec![unary_expression.into()],
}))
} }
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory), BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory),
} }
@ -571,26 +640,26 @@ impl BinaryPart {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub struct NoneCodeNode { pub struct NonCodeNode {
pub start: usize, pub start: usize,
pub end: usize, pub end: usize,
pub value: NoneCodeValue, pub value: NonCodeValue,
} }
impl NoneCodeNode { impl NonCodeNode {
pub fn value(&self) -> String { pub fn value(&self) -> String {
match &self.value { match &self.value {
NoneCodeValue::InlineComment { value } => value.clone(), NonCodeValue::InlineComment { value } => value.clone(),
NoneCodeValue::BlockComment { value } => value.clone(), NonCodeValue::BlockComment { value } => value.clone(),
NoneCodeValue::NewLineBlockComment { value } => value.clone(), NonCodeValue::NewLineBlockComment { value } => value.clone(),
NoneCodeValue::NewLine => "\n\n".to_string(), NonCodeValue::NewLine => "\n\n".to_string(),
} }
} }
pub fn format(&self, indentation: &str) -> String { pub fn format(&self, indentation: &str) -> String {
match &self.value { match &self.value {
NoneCodeValue::InlineComment { value } => format!(" // {}\n", value), NonCodeValue::InlineComment { value } => format!(" // {}\n", value),
NoneCodeValue::BlockComment { value } => { NonCodeValue::BlockComment { value } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n" }; let add_start_new_line = if self.start == 0 { "" } else { "\n" };
if value.contains('\n') { if value.contains('\n') {
format!("{}{}/* {} */\n", add_start_new_line, indentation, value) format!("{}{}/* {} */\n", add_start_new_line, indentation, value)
@ -598,7 +667,7 @@ impl NoneCodeNode {
format!("{}{}// {}\n", add_start_new_line, indentation, value) format!("{}{}// {}\n", add_start_new_line, indentation, value)
} }
} }
NoneCodeValue::NewLineBlockComment { value } => { NonCodeValue::NewLineBlockComment { value } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" }; let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
if value.contains('\n') { if value.contains('\n') {
format!("{}{}/* {} */\n", add_start_new_line, indentation, value) format!("{}{}/* {} */\n", add_start_new_line, indentation, value)
@ -606,7 +675,7 @@ impl NoneCodeNode {
format!("{}{}// {}\n", add_start_new_line, indentation, value) format!("{}{}// {}\n", add_start_new_line, indentation, value)
} }
} }
NoneCodeValue::NewLine => "\n\n".to_string(), NonCodeValue::NewLine => "\n\n".to_string(),
} }
} }
} }
@ -614,7 +683,7 @@ impl NoneCodeNode {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
pub enum NoneCodeValue { pub enum NonCodeValue {
/// An inline comment. /// An inline comment.
/// An example of this is the following: `1 + 1 // This is an inline comment`. /// An example of this is the following: `1 + 1 // This is an inline comment`.
InlineComment { InlineComment {
@ -643,35 +712,35 @@ pub enum NoneCodeValue {
NewLine, NewLine,
} }
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct NoneCodeMeta { pub struct NonCodeMeta {
pub none_code_nodes: HashMap<usize, NoneCodeNode>, pub non_code_nodes: HashMap<usize, NonCodeNode>,
pub start: Option<NoneCodeNode>, pub start: Option<NonCodeNode>,
} }
// implement Deserialize manually because we to force the keys of none_code_nodes to be usize // implement Deserialize manually because we to force the keys of non_code_nodes to be usize
// and by default the ts type { [statementIndex: number]: NoneCodeNode } serializes to a string i.e. "0", "1", etc. // and by default the ts type { [statementIndex: number]: NonCodeNode } serializes to a string i.e. "0", "1", etc.
impl<'de> Deserialize<'de> for NoneCodeMeta { impl<'de> Deserialize<'de> for NonCodeMeta {
fn deserialize<D>(deserializer: D) -> Result<NoneCodeMeta, D::Error> fn deserialize<D>(deserializer: D) -> Result<NonCodeMeta, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct NoneCodeMetaHelper { struct NonCodeMetaHelper {
none_code_nodes: HashMap<String, NoneCodeNode>, non_code_nodes: HashMap<String, NonCodeNode>,
start: Option<NoneCodeNode>, start: Option<NonCodeNode>,
} }
let helper = NoneCodeMetaHelper::deserialize(deserializer)?; let helper = NonCodeMetaHelper::deserialize(deserializer)?;
let mut none_code_nodes = HashMap::new(); let mut non_code_nodes = HashMap::new();
for (key, value) in helper.none_code_nodes { for (key, value) in helper.non_code_nodes {
none_code_nodes.insert(key.parse().map_err(serde::de::Error::custom)?, value); non_code_nodes.insert(key.parse().map_err(serde::de::Error::custom)?, value);
} }
Ok(NoneCodeMeta { Ok(NonCodeMeta {
none_code_nodes, non_code_nodes,
start: helper.start, start: helper.start,
}) })
} }
@ -702,7 +771,33 @@ pub struct CallExpression {
impl_value_meta!(CallExpression); impl_value_meta!(CallExpression);
impl From<CallExpression> for Value {
fn from(call_expression: CallExpression) -> Self {
Value::CallExpression(Box::new(call_expression))
}
}
impl CallExpression { impl CallExpression {
pub fn new(name: &str, arguments: Vec<Value>) -> Result<Self, KclError> {
// Create our stdlib.
let stdlib = crate::std::StdLib::new();
let func = stdlib.get(name).ok_or_else(|| {
KclError::UndefinedValue(KclErrorDetails {
message: format!("Function {} is not defined", name),
source_ranges: vec![],
})
})?;
Ok(Self {
start: 0,
end: 0,
callee: Identifier::new(name),
arguments,
optional: false,
function: Function::StdLib { func },
})
}
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
format!( format!(
"{}({})", "{}({})",
@ -776,6 +871,11 @@ impl CallExpression {
match &self.function { match &self.function {
Function::StdLib { func } => { Function::StdLib { func } => {
/* let source_range: SourceRange = self.into();
println!(
"Calling stdlib function: {}, source_range: {:?}, args: {:?}",
fn_name, source_range, fn_args
);*/
// Attempt to call the function. // Attempt to call the function.
let mut args = crate::std::Args::new(fn_args, self.into(), engine); let mut args = crate::std::Args::new(fn_args, self.into(), engine);
let result = func.std_lib_fn()(&mut args)?; let result = func.std_lib_fn()(&mut args)?;
@ -843,6 +943,23 @@ impl CallExpression {
arg.rename_identifiers(old_name, new_name); arg.rename_identifiers(old_name, new_name);
} }
} }
/// Return the constraint level for this call expression.
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.arguments.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
// Iterate over the arguments and get the constraint level for each one.
let mut constraint_levels = ConstraintLevels::new();
for arg in &self.arguments {
constraint_levels.push(arg.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
} }
/// A function declaration. /// A function declaration.
@ -883,6 +1000,15 @@ pub struct VariableDeclaration {
impl_value_meta!(VariableDeclaration); impl_value_meta!(VariableDeclaration);
impl VariableDeclaration { impl VariableDeclaration {
pub fn new(declarations: Vec<VariableDeclarator>, kind: VariableKind) -> Self {
Self {
start: 0,
end: 0,
declarations,
kind,
}
}
/// Returns a value that includes the given character position. /// Returns a value that includes the given character position.
pub fn get_value_for_position(&self, pos: usize) -> Option<&Value> { pub fn get_value_for_position(&self, pos: usize) -> Option<&Value> {
for declaration in &self.declarations { for declaration in &self.declarations {
@ -1063,6 +1189,21 @@ pub struct VariableDeclarator {
impl_value_meta!(VariableDeclarator); impl_value_meta!(VariableDeclarator);
impl VariableDeclarator {
pub fn new(name: &str, init: Value) -> Self {
Self {
start: 0,
end: 0,
id: Identifier::new(name),
init,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
self.init.get_constraint_level()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -1075,7 +1216,30 @@ pub struct Literal {
impl_value_meta!(Literal); impl_value_meta!(Literal);
impl From<Literal> for Value {
fn from(literal: Literal) -> Self {
Value::Literal(Box::new(literal))
}
}
impl Literal { impl Literal {
pub fn new(value: serde_json::Value) -> Self {
Self {
start: 0,
end: 0,
raw: value.to_string(),
value,
}
}
/// Get the constraint level for this literal.
/// Literals are always not constrained.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::None {
source_ranges: vec![self.into()],
}
}
fn recast(&self) -> String { fn recast(&self) -> String {
if let serde_json::Value::String(value) = &self.value { if let serde_json::Value::String(value) = &self.value {
let quote = if self.raw.trim().starts_with('"') { '"' } else { '\'' }; let quote = if self.raw.trim().starts_with('"') { '"' } else { '\'' };
@ -1120,6 +1284,22 @@ pub struct Identifier {
impl_value_meta!(Identifier); impl_value_meta!(Identifier);
impl Identifier { impl Identifier {
pub fn new(name: &str) -> Self {
Self {
start: 0,
end: 0,
name: name.to_string(),
}
}
/// Get the constraint level for this identifier.
/// Identifier are always fully constrained.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Full {
source_ranges: vec![self.into()],
}
}
/// Rename all identifiers that have the old name to the new given name. /// Rename all identifiers that have the old name to the new given name.
fn rename(&mut self, old_name: &str, new_name: &str) { fn rename(&mut self, old_name: &str, new_name: &str) {
if self.name == old_name { if self.name == old_name {
@ -1138,6 +1318,24 @@ pub struct PipeSubstitution {
impl_value_meta!(PipeSubstitution); impl_value_meta!(PipeSubstitution);
impl PipeSubstitution {
pub fn new() -> Self {
Self { start: 0, end: 0 }
}
}
impl Default for PipeSubstitution {
fn default() -> Self {
Self::new()
}
}
impl From<PipeSubstitution> for Value {
fn from(pipe_substitution: PipeSubstitution) -> Self {
Value::PipeSubstitution(Box::new(pipe_substitution))
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -1149,7 +1347,36 @@ pub struct ArrayExpression {
impl_value_meta!(ArrayExpression); impl_value_meta!(ArrayExpression);
impl From<ArrayExpression> for Value {
fn from(array_expression: ArrayExpression) -> Self {
Value::ArrayExpression(Box::new(array_expression))
}
}
impl ArrayExpression { impl ArrayExpression {
pub fn new(elements: Vec<Value>) -> Self {
Self {
start: 0,
end: 0,
elements,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.elements.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
let mut constraint_levels = ConstraintLevels::new();
for element in &self.elements {
constraint_levels.push(element.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
let flat_recast = format!( let flat_recast = format!(
"[{}]", "[{}]",
@ -1272,6 +1499,29 @@ pub struct ObjectExpression {
} }
impl ObjectExpression { impl ObjectExpression {
pub fn new(properties: Vec<ObjectProperty>) -> Self {
Self {
start: 0,
end: 0,
properties,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.properties.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
let mut constraint_levels = ConstraintLevels::new();
for property in &self.properties {
constraint_levels.push(property.value.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
let flat_recast = format!( let flat_recast = format!(
"{{ {} }}", "{{ {} }}",
@ -1527,6 +1777,14 @@ pub struct MemberExpression {
impl_value_meta!(MemberExpression); impl_value_meta!(MemberExpression);
impl MemberExpression { impl MemberExpression {
/// Get the constraint level for a member expression.
/// This is always fully constrained.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Full {
source_ranges: vec![self.into()],
}
}
fn recast(&self) -> String { fn recast(&self) -> String {
let key_str = match &self.property { let key_str = match &self.property {
LiteralIdentifier::Identifier(identifier) => { LiteralIdentifier::Identifier(identifier) => {
@ -1562,10 +1820,11 @@ impl MemberExpression {
let value = memory.get(&identifier.name, identifier.into())?; let value = memory.get(&identifier.name, identifier.into())?;
value.clone() value.clone()
} }
} };
.get_json_value()?;
if let serde_json::Value::Array(array) = array { let array_json = array.get_json_value()?;
if let serde_json::Value::Array(array) = array_json {
if let Some(value) = array.get(index) { if let Some(value) = array.get(index) {
Ok(MemoryItem::UserVal(UserVal { Ok(MemoryItem::UserVal(UserVal {
value: value.clone(), value: value.clone(),
@ -1613,10 +1872,11 @@ impl MemberExpression {
let value = memory.get(&identifier.name, identifier.into())?; let value = memory.get(&identifier.name, identifier.into())?;
value.clone() value.clone()
} }
} };
.get_json_value()?;
if let serde_json::Value::Object(map) = object { let object_json = object.get_json_value()?;
if let serde_json::Value::Object(map) = object_json {
if let Some(value) = map.get(&property_name) { if let Some(value) = map.get(&property_name) {
Ok(MemoryItem::UserVal(UserVal { Ok(MemoryItem::UserVal(UserVal {
value: value.clone(), value: value.clone(),
@ -1676,6 +1936,26 @@ pub struct BinaryExpression {
impl_value_meta!(BinaryExpression); impl_value_meta!(BinaryExpression);
impl BinaryExpression { impl BinaryExpression {
pub fn new(operator: BinaryOperator, left: BinaryPart, right: BinaryPart) -> Self {
Self {
start: left.start(),
end: right.end(),
operator,
left,
right,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
let left_constraint_level = self.left.get_constraint_level();
let right_constraint_level = self.right.get_constraint_level();
let mut constraint_levels = ConstraintLevels::new();
constraint_levels.push(left_constraint_level);
constraint_levels.push(right_constraint_level);
constraint_levels.get_constraint_level(self.into())
}
pub fn precedence(&self) -> u8 { pub fn precedence(&self) -> u8 {
self.operator.precedence() self.operator.precedence()
} }
@ -1691,7 +1971,9 @@ impl BinaryExpression {
let should_wrap_right = match &self.right { let should_wrap_right = match &self.right {
BinaryPart::BinaryExpression(bin_exp) => { BinaryPart::BinaryExpression(bin_exp) => {
self.precedence() > bin_exp.precedence() || self.operator == BinaryOperator::Sub self.precedence() > bin_exp.precedence()
|| self.operator == BinaryOperator::Sub
|| self.operator == BinaryOperator::Div
} }
_ => false, _ => false,
}; };
@ -1874,6 +2156,19 @@ pub struct UnaryExpression {
impl_value_meta!(UnaryExpression); impl_value_meta!(UnaryExpression);
impl UnaryExpression { impl UnaryExpression {
pub fn new(operator: UnaryOperator, argument: BinaryPart) -> Self {
Self {
start: 0,
end: argument.end(),
operator,
argument,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
self.argument.get_constraint_level()
}
fn recast(&self, options: &FormatOptions) -> String { fn recast(&self, options: &FormatOptions) -> String {
format!("{}{}", &self.operator, self.argument.recast(options, 0)) format!("{}{}", &self.operator, self.argument.recast(options, 0))
} }
@ -1943,12 +2238,43 @@ pub struct PipeExpression {
pub start: usize, pub start: usize,
pub end: usize, pub end: usize,
pub body: Vec<Value>, pub body: Vec<Value>,
pub non_code_meta: NoneCodeMeta, pub non_code_meta: NonCodeMeta,
} }
impl_value_meta!(PipeExpression); impl_value_meta!(PipeExpression);
impl From<PipeExpression> for Value {
fn from(pipe_expression: PipeExpression) -> Self {
Value::PipeExpression(Box::new(pipe_expression))
}
}
impl PipeExpression { impl PipeExpression {
pub fn new(body: Vec<Value>) -> Self {
Self {
start: 0,
end: 0,
body,
non_code_meta: Default::default(),
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.body.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
// Iterate over all body expressions.
let mut constraint_levels = ConstraintLevels::new();
for expression in &self.body {
constraint_levels.push(expression.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
self.body self.body
.iter() .iter()
@ -1957,7 +2283,7 @@ impl PipeExpression {
let indentation = options.get_indentation(indentation_level + 1); let indentation = options.get_indentation(indentation_level + 1);
let mut s = statement.recast(options, indentation_level + 1, true); let mut s = statement.recast(options, indentation_level + 1, true);
let non_code_meta = self.non_code_meta.clone(); let non_code_meta = self.non_code_meta.clone();
if let Some(non_code_meta_value) = non_code_meta.none_code_nodes.get(&index) { if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
s += non_code_meta_value.format(&indentation).trim_end_matches('\n') s += non_code_meta_value.format(&indentation).trim_end_matches('\n')
} }
@ -2067,6 +2393,13 @@ pub struct FunctionExpression {
impl_value_meta!(FunctionExpression); impl_value_meta!(FunctionExpression);
impl FunctionExpression { impl FunctionExpression {
/// Function expressions don't really apply.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
}
}
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
// We don't want to end with a new line inside nested functions. // We don't want to end with a new line inside nested functions.
let mut new_options = options.clone(); let mut new_options = options.clone();
@ -2171,11 +2504,150 @@ impl FormatOptions {
} }
} }
/// The constraint level.
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS, JsonSchema, Display)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[display(style = "snake_case")]
pub enum ConstraintLevel {
/// Ignore constraints.
/// This is useful for stuff like pipe substitutions where we don't want it to
/// factor into the overall constraint level.
/// Like empty arrays or objects, etc.
#[display("ignore")]
Ignore { source_ranges: Vec<SourceRange> },
/// No constraints.
#[display("none")]
None { source_ranges: Vec<SourceRange> },
/// Partially constrained.
#[display("partial")]
Partial {
source_ranges: Vec<SourceRange>,
levels: ConstraintLevels,
},
/// Fully constrained.
#[display("full")]
Full { source_ranges: Vec<SourceRange> },
}
impl From<ConstraintLevel> for Vec<SourceRange> {
fn from(constraint_level: ConstraintLevel) -> Self {
match constraint_level {
ConstraintLevel::Ignore { source_ranges } => source_ranges,
ConstraintLevel::None { source_ranges } => source_ranges,
ConstraintLevel::Partial {
source_ranges,
levels: _,
} => source_ranges,
ConstraintLevel::Full { source_ranges } => source_ranges,
}
}
}
impl PartialEq for ConstraintLevel {
fn eq(&self, other: &Self) -> bool {
// Just check the variant.
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}
impl ConstraintLevel {
pub fn update_source_ranges(&self, source_range: SourceRange) -> Self {
match self {
ConstraintLevel::Ignore { source_ranges: _ } => ConstraintLevel::Ignore {
source_ranges: vec![source_range],
},
ConstraintLevel::None { source_ranges: _ } => ConstraintLevel::None {
source_ranges: vec![source_range],
},
ConstraintLevel::Partial {
source_ranges: _,
levels,
} => ConstraintLevel::Partial {
source_ranges: vec![source_range],
levels: levels.clone(),
},
ConstraintLevel::Full { source_ranges: _ } => ConstraintLevel::Full {
source_ranges: vec![source_range],
},
}
}
}
/// A vector of constraint levels.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct ConstraintLevels(pub Vec<ConstraintLevel>);
impl Default for ConstraintLevels {
fn default() -> Self {
Self::new()
}
}
impl ConstraintLevels {
pub fn new() -> Self {
Self(vec![])
}
pub fn push(&mut self, constraint_level: ConstraintLevel) {
self.0.push(constraint_level);
}
/// Get the overall constraint level.
pub fn get_constraint_level(&self, source_range: SourceRange) -> ConstraintLevel {
if self.0.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![source_range],
};
}
// Check if all the constraint levels are the same.
if self
.0
.iter()
.all(|level| *level == self.0[0] || matches!(level, ConstraintLevel::Ignore { .. }))
{
self.0[0].clone()
} else {
ConstraintLevel::Partial {
source_ranges: vec![source_range],
levels: self.clone(),
}
}
}
pub fn get_all_partial_or_full_source_ranges(&self) -> Vec<SourceRange> {
let mut source_ranges = Vec::new();
// Add to our source ranges anything that is not none or ignore.
for level in &self.0 {
match level {
ConstraintLevel::None { source_ranges: _ } => {}
ConstraintLevel::Ignore { source_ranges: _ } => {}
ConstraintLevel::Partial {
source_ranges: _,
levels,
} => {
source_ranges.extend(levels.get_all_partial_or_full_source_ranges());
}
ConstraintLevel::Full {
source_ranges: full_source_ranges,
} => {
source_ranges.extend(full_source_ranges);
}
}
}
source_ranges
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::*;
// We have this as a test so we can ensure it never panics with an unwrap in the server. // We have this as a test so we can ensure it never panics with an unwrap in the server.
#[test] #[test]
fn test_variable_kind_to_completion() { fn test_variable_kind_to_completion() {
@ -2208,9 +2680,8 @@ show(part001)"#;
#[test] #[test]
fn test_recast_with_std_and_non_stdlib() { fn test_recast_with_std_and_non_stdlib() {
let some_program_string = r#"{"body":[{"type":"VariableDeclaration","start":0,"end":0,"declarations":[{"type":"VariableDeclarator","start":0,"end":0,"id":{"type":"Identifier","start":0,"end":0,"name":"part001"},"init":{"type":"PipeExpression","start":0,"end":0,"body":[{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"startSketchAt"},"function":{"type":"StdLib","func":{"name":"startSketchAt","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"ry"},"function":{"type":"InMemory"},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":90,"raw":"90"},{"type":"PipeSubstitution","start":0,"end":0}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"line"},"function":{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"},{"type":"PipeSubstitution","start":0,"end":0}]}],"nonCodeMeta":{"noneCodeNodes":{},"start":null}}}],"kind":"const"},{"type":"ExpressionStatement","start":0,"end":0,"expression":{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"show"},"function":{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Identifier","start":0,"end":0,"name":"part001"}]}}],"start":0,"end":0,"nonCodeMeta":{"noneCodeNodes":{},"start":null}}"#; let some_program_string = r#"{"body":[{"type":"VariableDeclaration","start":0,"end":0,"declarations":[{"type":"VariableDeclarator","start":0,"end":0,"id":{"type":"Identifier","start":0,"end":0,"name":"part001"},"init":{"type":"PipeExpression","start":0,"end":0,"body":[{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"startSketchAt"},"function":{"type":"StdLib","func":{"name":"startSketchAt","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"ry"},"function":{"type":"InMemory"},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":90,"raw":"90"},{"type":"PipeSubstitution","start":0,"end":0}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"line"},"function":{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"},{"type":"PipeSubstitution","start":0,"end":0}]}],"nonCodeMeta":{"nonCodeNodes":{},"start":null}}}],"kind":"const"},{"type":"ExpressionStatement","start":0,"end":0,"expression":{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"show"},"function":{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Identifier","start":0,"end":0,"name":"part001"}]}}],"start":0,"end":0,"nonCodeMeta":{"nonCodeNodes":{},"start":null}}"#;
let some_program: crate::abstract_syntax_tree_types::Program = let some_program: crate::ast::types::Program = serde_json::from_str(some_program_string).unwrap();
serde_json::from_str(some_program_string).unwrap();
let recasted = some_program.recast(&Default::default(), 0); let recasted = some_program.recast(&Default::default(), 0);
assert_eq!( assert_eq!(
@ -2425,7 +2896,7 @@ const things = "things"
let some_program_string = r#"let b = { let some_program_string = r#"let b = {
"end": 141, "end": 141,
"start": 125, "start": 125,
"type": "NoneCodeNode", "type": "NonCodeNode",
"value": " "value": "
// a comment // a comment
" "
@ -2605,4 +3076,20 @@ show(firstExtrude)
let recasted = program.recast(&Default::default(), 0); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted.trim(), some_program_string); assert_eq!(recasted.trim(), some_program_string);
} }
#[tokio::test(flavor = "multi_thread")]
async fn test_recast_math_nested_parens() {
let some_program_string = r#"const distance = 5
const p = 3
const FOS = 2
const sigmaAllow = 8
const width = 20
const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
let tokens = crate::tokeniser::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted.trim(), some_program_string);
}
} }

View File

@ -486,7 +486,7 @@ mod tests {
#[test] #[test]
fn test_serialize_function() { fn test_serialize_function() {
let some_function = crate::abstract_syntax_tree_types::Function::StdLib { let some_function = crate::ast::types::Function::StdLib {
func: Box::new(crate::std::sketch::Line), func: Box::new(crate::std::sketch::Line),
}; };
let serialized = serde_json::to_string(&some_function).unwrap(); let serialized = serde_json::to_string(&some_function).unwrap();
@ -496,12 +496,11 @@ mod tests {
#[test] #[test]
fn test_deserialize_function() { fn test_deserialize_function() {
let some_function_string = r#"{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#; let some_function_string = r#"{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
let some_function: crate::abstract_syntax_tree_types::Function = let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
serde_json::from_str(some_function_string).unwrap();
assert_eq!( assert_eq!(
some_function, some_function,
crate::abstract_syntax_tree_types::Function::StdLib { crate::ast::types::Function::StdLib {
func: Box::new(crate::std::sketch::Line), func: Box::new(crate::std::sketch::Line),
} }
); );
@ -510,12 +509,11 @@ mod tests {
#[test] #[test]
fn test_deserialize_function_show() { fn test_deserialize_function_show() {
let some_function_string = r#"{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#; let some_function_string = r#"{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
let some_function: crate::abstract_syntax_tree_types::Function = let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
serde_json::from_str(some_function_string).unwrap();
assert_eq!( assert_eq!(
some_function, some_function,
crate::abstract_syntax_tree_types::Function::StdLib { crate::ast::types::Function::StdLib {
func: Box::new(crate::std::Show), func: Box::new(crate::std::Show),
} }
); );

View File

@ -3,19 +3,24 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::{anyhow, Result};
use dashmap::DashMap;
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse}; use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse};
use tokio::sync::{mpsc, oneshot};
use tokio_tungstenite::tungstenite::Message as WsMsg; use tokio_tungstenite::tungstenite::Message as WsMsg;
use crate::errors::{KclError, KclErrorDetails}; use crate::{
engine::EngineManager,
errors::{KclError, KclErrorDetails},
};
#[derive(Debug)] type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>;
#[derive(Debug, Clone)]
pub struct EngineConnection { pub struct EngineConnection {
tcp_write: futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>, engine_req_tx: mpsc::Sender<ToEngineReq>,
tcp_read_handle: tokio::task::JoinHandle<Result<()>>, tcp_read_handle: Arc<tokio::task::JoinHandle<Result<()>>>,
export_notifier: Arc<tokio::sync::Notify>, responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
snapshot_notifier: Arc<tokio::sync::Notify>,
} }
impl Drop for EngineConnection { impl Drop for EngineConnection {
@ -31,8 +36,10 @@ pub struct TcpRead {
impl TcpRead { impl TcpRead {
pub async fn read(&mut self) -> Result<WebSocketResponse> { pub async fn read(&mut self) -> Result<WebSocketResponse> {
let msg = self.stream.next().await.unwrap()?; let Some(msg) = self.stream.next().await else {
let msg: WebSocketResponse = match msg { anyhow::bail!("Failed to read from websocket");
};
let msg: WebSocketResponse = match msg? {
WsMsg::Text(text) => serde_json::from_str(&text)?, WsMsg::Text(text) => serde_json::from_str(&text)?,
WsMsg::Binary(bin) => bson::from_slice(&bin)?, WsMsg::Binary(bin) => bson::from_slice(&bin)?,
other => anyhow::bail!("Unexpected websocket message from server: {}", other), other => anyhow::bail!("Unexpected websocket message from server: {}", other),
@ -41,18 +48,37 @@ impl TcpRead {
} }
} }
impl EngineConnection { /// Requests to send to the engine, and a way to await a response.
pub async fn new(ws: reqwest::Upgraded, export_dir: &str, snapshot_file: &str) -> Result<EngineConnection> { struct ToEngineReq {
// Make sure the export directory exists and that it is a directory. /// The request to send
let export_dir = std::path::Path::new(export_dir).to_owned(); req: WebSocketRequest,
if !export_dir.exists() { /// If this resolves to Ok, the request was sent.
anyhow::bail!("Export directory does not exist: {}", export_dir.display()); /// If this resolves to Err, the request could not be sent.
} /// If this has not yet resolved, the request has not been sent yet.
// Make sure it is a directory. request_sent: oneshot::Sender<Result<()>>,
if !export_dir.is_dir() {
anyhow::bail!("Export directory is not a directory: {}", export_dir.display());
} }
impl EngineConnection {
/// Start waiting for incoming engine requests, and send each one over the WebSocket to the engine.
async fn start_write_actor(mut tcp_write: WebSocketTcpWrite, mut engine_req_rx: mpsc::Receiver<ToEngineReq>) {
while let Some(req) = engine_req_rx.recv().await {
let ToEngineReq { req, request_sent } = req;
let res = Self::inner_send_to_engine(req, &mut tcp_write).await;
let _ = request_sent.send(res);
}
}
/// Send the given `request` to the engine via the WebSocket connection `tcp_write`.
async fn inner_send_to_engine(request: WebSocketRequest, tcp_write: &mut WebSocketTcpWrite) -> Result<()> {
let msg = serde_json::to_string(&request).map_err(|e| anyhow!("could not serialize json: {e}"))?;
tcp_write
.send(WsMsg::Text(msg))
.await
.map_err(|e| anyhow!("could not send json over websocket: {e}"))?;
Ok(())
}
pub async fn new(ws: reqwest::Upgraded) -> Result<EngineConnection> {
let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket( let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket(
ws, ws,
tokio_tungstenite::tungstenite::protocol::Role::Client, tokio_tungstenite::tungstenite::protocol::Role::Client,
@ -61,118 +87,103 @@ impl EngineConnection {
.await; .await;
let (tcp_write, tcp_read) = ws_stream.split(); let (tcp_write, tcp_read) = ws_stream.split();
let (engine_req_tx, engine_req_rx) = mpsc::channel(10);
tokio::task::spawn(Self::start_write_actor(tcp_write, engine_req_rx));
let mut tcp_read = TcpRead { stream: tcp_read }; let mut tcp_read = TcpRead { stream: tcp_read };
let export_notifier = Arc::new(tokio::sync::Notify::new()); let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
let export_notifier_clone = export_notifier.clone(); let responses_clone = responses.clone();
let snapshot_notifier = Arc::new(tokio::sync::Notify::new());
let snapshot_notifier_clone = snapshot_notifier.clone();
let snapshot_file = snapshot_file.to_owned();
let tcp_read_handle = tokio::spawn(async move { let tcp_read_handle = tokio::spawn(async move {
// Get Websocket messages from API server // Get Websocket messages from API server
loop { loop {
match tcp_read.read().await { match tcp_read.read().await {
Ok(ws_resp) => { Ok(ws_resp) => {
if let Some(success) = ws_resp.success { if let Some(id) = ws_resp.request_id {
if !success { responses_clone.insert(id, ws_resp.clone());
println!("got ws errors: {:?}", ws_resp.errors);
export_notifier.notify_one();
continue;
}
}
if let Some(msg) = ws_resp.resp {
match msg {
OkWebSocketResponseData::MetricsRequest {} => {
// @paultag todo
}
OkWebSocketResponseData::IceServerInfo { ice_servers } => {
println!("got ice server info: {:?}", ice_servers);
}
OkWebSocketResponseData::SdpAnswer { answer } => {
println!("got sdp answer: {:?}", answer);
}
OkWebSocketResponseData::TrickleIce { candidate } => {
println!("got trickle ice: {:?}", candidate);
}
OkWebSocketResponseData::Modeling { modeling_response } => {
if let kittycad::types::OkModelingCmdResponse::TakeSnapshot { data } =
modeling_response
{
if snapshot_file.is_empty() {
println!("Got snapshot, but no snapshot file specified.");
continue;
}
// Save the snapshot locally.
std::fs::write(&snapshot_file, data.contents)?;
snapshot_notifier.notify_one();
}
}
OkWebSocketResponseData::Export { files } => {
// Save the files to our export directory.
for file in files {
let path = export_dir.join(file.name);
std::fs::write(&path, file.contents)?;
println!("Wrote file: {}", path.display());
}
// Tell the export notifier that we have new files.
export_notifier.notify_one();
}
}
} }
} }
Err(e) => { Err(e) => {
println!("got ws error: {:?}", e); println!("got ws error: {:?}", e);
export_notifier.notify_one(); return Err(e);
continue;
} }
} }
} }
}); });
Ok(EngineConnection { Ok(EngineConnection {
tcp_write, engine_req_tx,
tcp_read_handle, tcp_read_handle: Arc::new(tcp_read_handle),
export_notifier: export_notifier_clone, responses,
snapshot_notifier: snapshot_notifier_clone,
}) })
} }
pub async fn wait_for_export(&self) {
self.export_notifier.notified().await;
} }
pub async fn wait_for_snapshot(&self) { #[async_trait::async_trait(?Send)]
self.snapshot_notifier.notified().await; impl EngineManager for EngineConnection {
} /// Send a modeling command.
/// Do not wait for the response message.
pub async fn tcp_send(&mut self, msg: WebSocketRequest) -> Result<()> { fn send_modeling_cmd(
let msg = serde_json::to_string(&msg)?; &self,
self.tcp_write.send(WsMsg::Text(msg)).await?;
Ok(())
}
pub fn send_modeling_cmd(
&mut self,
id: uuid::Uuid, id: uuid::Uuid,
source_range: crate::executor::SourceRange, source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd, cmd: kittycad::types::ModelingCmd,
) -> Result<(), KclError> { ) -> Result<(), KclError> {
futures::executor::block_on(self.tcp_send(WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id })).map_err( futures::executor::block_on(self.send_modeling_cmd_get_response(id, source_range, cmd))?;
|e| { Ok(())
}
/// Send a modeling command and wait for the response message.
async fn send_modeling_cmd_get_response(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
) -> Result<OkWebSocketResponseData, KclError> {
let (tx, rx) = oneshot::channel();
// Send the request to the engine, via the actor.
self.engine_req_tx
.send(ToEngineReq {
req: WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id },
request_sent: tx,
})
.await
.map_err(|e| {
KclError::Engine(KclErrorDetails { KclError::Engine(KclErrorDetails {
message: format!("Failed to send modeling command: {}", e), message: format!("Failed to send modeling command: {}", e),
source_ranges: vec![source_range], source_ranges: vec![source_range],
}) })
}, })?;
)?;
Ok(()) // Wait for the request to be sent.
rx.await
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("could not send request to the engine actor: {e}"),
source_ranges: vec![source_range],
})
})?
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("could not send request to the engine: {e}"),
source_ranges: vec![source_range],
})
})?;
// Wait for the response.
loop {
if let Some(resp) = self.responses.get(&id) {
return if let Some(data) = &resp.resp {
Ok(data.clone())
} else {
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", resp.errors),
source_ranges: vec![source_range],
}))
};
}
}
} }
} }

View File

@ -2,23 +2,36 @@
//! engine. //! engine.
use anyhow::Result; use anyhow::Result;
use kittycad::types::OkWebSocketResponseData;
use crate::errors::KclError; use crate::errors::KclError;
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct EngineConnection {} pub struct EngineConnection {}
impl EngineConnection { impl EngineConnection {
pub async fn new() -> Result<EngineConnection> { pub async fn new() -> Result<EngineConnection> {
Ok(EngineConnection {}) Ok(EngineConnection {})
} }
}
pub fn send_modeling_cmd( #[async_trait::async_trait(?Send)]
&mut self, impl crate::engine::EngineManager for EngineConnection {
fn send_modeling_cmd(
&self,
_id: uuid::Uuid, _id: uuid::Uuid,
_source_range: crate::executor::SourceRange, _source_range: crate::executor::SourceRange,
_cmd: kittycad::types::ModelingCmd, _cmd: kittycad::types::ModelingCmd,
) -> Result<(), KclError> { ) -> Result<(), KclError> {
Ok(()) Ok(())
} }
async fn send_modeling_cmd_get_response(
&self,
_id: uuid::Uuid,
_source_range: crate::executor::SourceRange,
_cmd: kittycad::types::ModelingCmd,
) -> Result<OkWebSocketResponseData, KclError> {
todo!()
}
} }

View File

@ -30,9 +30,12 @@ impl EngineConnection {
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> { pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
Ok(EngineConnection { manager }) Ok(EngineConnection { manager })
} }
}
pub fn send_modeling_cmd( #[async_trait::async_trait(?Send)]
&mut self, impl crate::engine::EngineManager for EngineConnection {
fn send_modeling_cmd(
&self,
id: uuid::Uuid, id: uuid::Uuid,
source_range: crate::executor::SourceRange, source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd, cmd: kittycad::types::ModelingCmd,
@ -55,4 +58,53 @@ impl EngineConnection {
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str); .sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
Ok(()) Ok(())
} }
async fn send_modeling_cmd_get_response(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
) -> Result<kittycad::types::OkWebSocketResponseData, KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize source range: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let ws_msg = WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id };
let cmd_str = serde_json::to_string(&ws_msg).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize modeling command: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let promise = self
.manager
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from engine: {:?}", e),
source_ranges: vec![source_range],
})
})?;
// Parse the value as a string.
let s = value.as_string().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to get string from response from engine: `{:?}`", value),
source_ranges: vec![source_range],
})
})?;
let modeling_result: kittycad::types::OkWebSocketResponseData = serde_json::from_str(&s).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to deserialize response from engine: {:?}", e),
source_ranges: vec![source_range],
})
})?;
Ok(modeling_result)
}
} }

View File

@ -1,10 +1,5 @@
//! Functions for managing engine communications. //! Functions for managing engine communications.
#[cfg(target_arch = "wasm32")]
#[cfg(not(test))]
#[cfg(feature = "engine")]
use wasm_bindgen::prelude::*;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[cfg(not(test))] #[cfg(not(test))]
#[cfg(feature = "engine")] #[cfg(feature = "engine")]
@ -31,37 +26,27 @@ pub use conn_mock::EngineConnection;
#[cfg(not(feature = "engine"))] #[cfg(not(feature = "engine"))]
#[cfg(not(test))] #[cfg(not(test))]
pub mod conn_mock; pub mod conn_mock;
use anyhow::Result;
#[cfg(not(feature = "engine"))] #[cfg(not(feature = "engine"))]
#[cfg(not(test))] #[cfg(not(test))]
pub use conn_mock::EngineConnection; pub use conn_mock::EngineConnection;
#[cfg(target_arch = "wasm32")] #[async_trait::async_trait(?Send)]
#[cfg(not(test))] pub trait EngineManager: Clone {
#[derive(Debug)] /// Send a modeling command.
#[wasm_bindgen] /// Do not wait for the response message.
pub struct EngineManager { fn send_modeling_cmd(
connection: EngineConnection, &self,
} id: uuid::Uuid,
#[cfg(target_arch = "wasm32")] source_range: crate::executor::SourceRange,
#[cfg(not(test))] cmd: kittycad::types::ModelingCmd,
#[cfg(feature = "engine")] ) -> Result<(), crate::errors::KclError>;
#[wasm_bindgen]
impl EngineManager {
#[wasm_bindgen(constructor)]
pub async fn new(manager: conn_wasm::EngineCommandManager) -> EngineManager {
EngineManager {
// This unwrap is safe because the connection is always created.
connection: EngineConnection::new(manager).await.unwrap(),
}
}
pub fn send_modeling_cmd(&mut self, id_str: &str, cmd_str: &str) -> Result<(), String> { /// Send a modeling command and wait for the response message.
let id = uuid::Uuid::parse_str(id_str).map_err(|e| e.to_string())?; async fn send_modeling_cmd_get_response(
let cmd = serde_json::from_str(cmd_str).map_err(|e| e.to_string())?; &self,
self.connection id: uuid::Uuid,
.send_modeling_cmd(id, crate::executor::SourceRange::default(), cmd) source_range: crate::executor::SourceRange,
.map_err(String::from)?; cmd: kittycad::types::ModelingCmd,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError>;
Ok(())
}
} }

View File

@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange}; use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
use crate::{ use crate::{
abstract_syntax_tree_types::{BodyItem, Function, FunctionExpression, Value}, ast::types::{BodyItem, Function, FunctionExpression, Value},
engine::EngineConnection, engine::EngineConnection,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
}; };
@ -101,8 +101,8 @@ impl ProgramReturn {
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum MemoryItem { pub enum MemoryItem {
UserVal(UserVal), UserVal(UserVal),
SketchGroup(SketchGroup), SketchGroup(Box<SketchGroup>),
ExtrudeGroup(ExtrudeGroup), ExtrudeGroup(Box<ExtrudeGroup>),
#[ts(skip)] #[ts(skip)]
ExtrudeTransform(ExtrudeTransform), ExtrudeTransform(ExtrudeTransform),
#[ts(skip)] #[ts(skip)]
@ -159,10 +159,12 @@ impl MemoryItem {
if let MemoryItem::UserVal(user_val) = self { if let MemoryItem::UserVal(user_val) = self {
Ok(user_val.value.clone()) Ok(user_val.value.clone())
} else { } else {
Err(KclError::Semantic(KclErrorDetails { serde_json::to_value(self).map_err(|err| {
message: format!("Not a user value: {:?}", self), KclError::Semantic(KclErrorDetails {
message: format!("Cannot convert memory item to json value: {:?}", err),
source_ranges: self.clone().into(), source_ranges: self.clone().into(),
})) })
})
} }
} }
@ -578,7 +580,7 @@ impl Default for PipeInfo {
/// Execute a AST's program. /// Execute a AST's program.
pub fn execute( pub fn execute(
program: crate::abstract_syntax_tree_types::Program, program: crate::ast::types::Program,
memory: &mut ProgramMemory, memory: &mut ProgramMemory,
options: BodyType, options: BodyType,
engine: &mut EngineConnection, engine: &mut EngineConnection,
@ -599,6 +601,10 @@ pub fn execute(
let memory_item = memory.get(&identifier.name, identifier.into())?; let memory_item = memory.get(&identifier.name, identifier.into())?;
args.push(memory_item.clone()); args.push(memory_item.clone());
} }
Value::CallExpression(call_expr) => {
let result = call_expr.execute(memory, &mut pipe_info, engine)?;
args.push(result);
}
// We do nothing for the rest. // We do nothing for the rest.
_ => (), _ => (),
} }
@ -1173,6 +1179,16 @@ show(thisBox)
); );
} }
#[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_with_pi() {
let ast = r#"const myVar = pi() * 2"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(std::f64::consts::TAU),
memory.root.get("myVar").unwrap().get_json_value().unwrap()
);
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_define_decimal_without_leading_zero() { async fn test_math_define_decimal_without_leading_zero() {
let ast = r#"let thing = .4 + 7"#; let ast = r#"let thing = .4 + 7"#;
@ -1182,4 +1198,92 @@ show(thisBox)
memory.root.get("thing").unwrap().get_json_value().unwrap() memory.root.get("thing").unwrap().get_json_value().unwrap()
); );
} }
#[tokio::test(flavor = "multi_thread")]
async fn test_zero_param_fn() {
let ast = r#"const sigmaAllow = 35000 // psi
const leg1 = 5 // inches
const leg2 = 8 // inches
fn thickness = () => { return 0.56 }
const bracket = startSketchAt([0,0])
|> line([0, leg1], %)
|> line([leg2, 0], %)
|> line([0, -thickness()], %)
|> line([-leg2 + thickness(), 0], %)
"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_math_negative_variable_in_binary_expression() {
let ast = r#"const sigmaAllow = 35000 // psi
const width = 1 // inch
const p = 150 // lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
const thickness = 0.56 // inches. App does not support square root function yet
const bracket = startSketchAt([0,0])
|> line([0, leg1], %)
|> line([leg2, 0], %)
|> line([0, -thickness], %)
|> line([-leg2 + thickness, 0], %)
"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_math_doubly_nested_parens() {
let ast = r#"const sigmaAllow = 35000 // psi
const width = 4 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
const thickness = 0.32 // inches. App does not support square root function yet
const bracket = startSketchAt([0,0])
|> line([0, leg1], %)
|> line([leg2, 0], %)
|> line([0, -thickness], %)
|> line([-1 * leg2 + thickness, 0], %)
|> line([0, -1 * leg1 + thickness], %)
|> close(%)
|> extrude(width, %)
show(bracket)
"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_math_nested_parens_one_less() {
let ast = r#"const sigmaAllow = 35000 // psi
const width = 4 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
const thickness = 0.32 // inches. App does not support square root function yet
const bracket = startSketchAt([0,0])
|> line([0, leg1], %)
|> line([leg2, 0], %)
|> line([0, -thickness], %)
|> line([-1 * leg2 + thickness, 0], %)
|> line([0, -1 * leg1 + thickness], %)
|> close(%)
|> extrude(width, %)
show(bracket)
"#;
parse_execute(ast).await.unwrap();
}
} }

View File

@ -1,4 +1,6 @@
pub mod abstract_syntax_tree_types; #![recursion_limit = "1024"]
pub mod ast;
pub mod docs; pub mod docs;
pub mod engine; pub mod engine;
pub mod errors; pub mod errors;

View File

@ -4,8 +4,9 @@ use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
abstract_syntax_tree_types::{ ast::types::{
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression, ValueMeta, BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression,
UnaryExpression, ValueMeta,
}, },
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::SourceRange, executor::SourceRange,
@ -40,7 +41,7 @@ pub struct ParenthesisToken {
pub end: usize, pub end: usize,
} }
crate::abstract_syntax_tree_types::impl_value_meta!(ParenthesisToken); crate::ast::types::impl_value_meta!(ParenthesisToken);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
@ -55,7 +56,7 @@ pub struct ExtendedBinaryExpression {
pub end_extended: Option<usize>, pub end_extended: Option<usize>,
} }
crate::abstract_syntax_tree_types::impl_value_meta!(ExtendedBinaryExpression); crate::ast::types::impl_value_meta!(ExtendedBinaryExpression);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
@ -69,7 +70,7 @@ pub struct ExtendedLiteral {
pub end_extended: Option<usize>, pub end_extended: Option<usize>,
} }
crate::abstract_syntax_tree_types::impl_value_meta!(ExtendedLiteral); crate::ast::types::impl_value_meta!(ExtendedLiteral);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export)] #[ts(export)]
@ -82,6 +83,7 @@ pub enum MathExpression {
ExtendedBinaryExpression(Box<ExtendedBinaryExpression>), ExtendedBinaryExpression(Box<ExtendedBinaryExpression>),
ParenthesisToken(Box<ParenthesisToken>), ParenthesisToken(Box<ParenthesisToken>),
MemberExpression(Box<MemberExpression>), MemberExpression(Box<MemberExpression>),
UnaryExpression(Box<UnaryExpression>),
} }
impl MathExpression { impl MathExpression {
@ -94,6 +96,7 @@ impl MathExpression {
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(), MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(),
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.start(), MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.start(),
MathExpression::MemberExpression(member_expression) => member_expression.start(), MathExpression::MemberExpression(member_expression) => member_expression.start(),
MathExpression::UnaryExpression(unary_expression) => unary_expression.start(),
} }
} }
@ -106,6 +109,7 @@ impl MathExpression {
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(), MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(),
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.end(), MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.end(),
MathExpression::MemberExpression(member_expression) => member_expression.end(), MathExpression::MemberExpression(member_expression) => member_expression.end(),
MathExpression::UnaryExpression(unary_expression) => unary_expression.end(),
} }
} }
} }
@ -208,7 +212,8 @@ impl ReversePolishNotation {
if prevtoken.token_type == TokenType::Operator { if prevtoken.token_type == TokenType::Operator {
// Get the next token and see if it is a number. // Get the next token and see if it is a number.
if let Ok(nexttoken) = self.parser.get_token(1) { if let Ok(nexttoken) = self.parser.get_token(1) {
if nexttoken.token_type == TokenType::Number { if nexttoken.token_type == TokenType::Number || nexttoken.token_type == TokenType::Word
{
// We have a negative number/ word or string. // We have a negative number/ word or string.
// Change the value of the token to be the negative number/ word or string. // Change the value of the token to be the negative number/ word or string.
let mut new_token = nexttoken.clone(); let mut new_token = nexttoken.clone();
@ -250,7 +255,7 @@ impl ReversePolishNotation {
&& current_token.value == "-" && current_token.value == "-"
{ {
if let Ok(nexttoken) = self.parser.get_token(1) { if let Ok(nexttoken) = self.parser.get_token(1) {
if nexttoken.token_type == TokenType::Number { if nexttoken.token_type == TokenType::Number || nexttoken.token_type == TokenType::Word {
// We have a negative number/ word or string. // We have a negative number/ word or string.
// Change the value of the token to be the negative number/ word or string. // Change the value of the token to be the negative number/ word or string.
let mut new_token = nexttoken.clone(); let mut new_token = nexttoken.clone();
@ -417,9 +422,8 @@ impl ReversePolishNotation {
{ {
let closing_brace = self.parser.find_closing_brace(1, 0, "")?; let closing_brace = self.parser.find_closing_brace(1, 0, "")?;
let mut new_stack = stack; let mut new_stack = stack;
new_stack.push(MathExpression::CallExpression(Box::new( let call_expression = self.parser.make_call_expression(0)?;
self.parser.make_call_expression(0)?.expression, new_stack.push(MathExpression::CallExpression(Box::new(call_expression.expression)));
)));
return self.build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack); return self.build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack);
} }
if reverse_polish_notation_tokens[1].token_type == TokenType::Period if reverse_polish_notation_tokens[1].token_type == TokenType::Period
@ -435,11 +439,25 @@ impl ReversePolishNotation {
); );
} }
let mut new_stack = stack; let mut new_stack = stack;
if current_token.value.starts_with('-') {
let expression = UnaryExpression {
start: current_token.start,
end: current_token.end,
operator: crate::ast::types::UnaryOperator::Neg,
argument: BinaryPart::Identifier(Box::new(Identifier {
name: current_token.value.trim_start_matches('-').to_string(),
start: current_token.start + 1,
end: current_token.end,
})),
};
new_stack.push(MathExpression::UnaryExpression(Box::new(expression)));
} else {
new_stack.push(MathExpression::Identifier(Box::new(Identifier { new_stack.push(MathExpression::Identifier(Box::new(Identifier {
name: current_token.value.clone(), name: current_token.value.clone(),
start: current_token.start, start: current_token.start,
end: current_token.end, end: current_token.end,
}))); })));
}
return self.build_tree(&reverse_polish_notation_tokens[1..], new_stack); return self.build_tree(&reverse_polish_notation_tokens[1..], new_stack);
} }
} else if current_token.token_type == TokenType::Brace && current_token.value == "(" { } else if current_token.token_type == TokenType::Brace && current_token.value == "(" {
@ -571,6 +589,10 @@ impl ReversePolishNotation {
BinaryPart::MemberExpression(member_expression.clone()), BinaryPart::MemberExpression(member_expression.clone()),
member_expression.start, member_expression.start,
), ),
MathExpression::UnaryExpression(unary_expression) => (
BinaryPart::UnaryExpression(unary_expression.clone()),
unary_expression.start,
),
a => { a => {
return Err(KclError::InvalidExpression(KclErrorDetails { return Err(KclError::InvalidExpression(KclErrorDetails {
source_ranges: vec![current_token.into()], source_ranges: vec![current_token.into()],
@ -605,6 +627,10 @@ impl ReversePolishNotation {
BinaryPart::MemberExpression(member_expression.clone()), BinaryPart::MemberExpression(member_expression.clone()),
member_expression.end, member_expression.end,
), ),
MathExpression::UnaryExpression(unary_expression) => (
BinaryPart::UnaryExpression(unary_expression.clone()),
unary_expression.end,
),
a => { a => {
return Err(KclError::InvalidExpression(KclErrorDetails { return Err(KclError::InvalidExpression(KclErrorDetails {
source_ranges: vec![current_token.into()], source_ranges: vec![current_token.into()],
@ -1244,4 +1270,22 @@ mod test {
let output = parser.build_tree(&input_tokens, vec![]).unwrap(); let output = parser.build_tree(&input_tokens, vec![]).unwrap();
assert_eq!(output, expected_output); assert_eq!(output, expected_output);
} }
#[test]
fn test_parse_expression_braces_around_lots_of_math() {
let code = "(distance * p * FOS * 6 / (sigmaAllow * width))";
let tokens = crate::tokeniser::lexer(code);
let mut parser = MathParser::new(&tokens);
let result = parser.parse();
assert!(result.is_ok());
}
#[test]
fn test_parse_expression_braces_around_internals_lots_of_math() {
let code = "distance * p * FOS * 6 / (sigmaAllow * width)";
let tokens = crate::tokeniser::lexer(code);
let mut parser = MathParser::new(&tokens);
let result = parser.parse();
assert!(result.is_ok());
}
} }

View File

@ -1,10 +1,10 @@
use std::{collections::HashMap, str::FromStr}; use std::{collections::HashMap, str::FromStr};
use crate::{ use crate::{
abstract_syntax_tree_types::{ ast::types::{
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement, ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NoneCodeMeta, FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta,
NoneCodeNode, NoneCodeValue, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution, NonCodeNode, NonCodeValue, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution,
Program, ReturnStatement, UnaryExpression, UnaryOperator, Value, VariableDeclaration, VariableDeclarator, Program, ReturnStatement, UnaryExpression, UnaryOperator, Value, VariableDeclaration, VariableDeclarator,
VariableKind, VariableKind,
}, },
@ -26,7 +26,7 @@ struct TokenReturn {
struct TokenReturnWithNonCode { struct TokenReturnWithNonCode {
token: Option<Token>, token: Option<Token>,
index: usize, index: usize,
non_code_node: Option<NoneCodeNode>, non_code_node: Option<NonCodeNode>,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -57,7 +57,7 @@ struct ArrayReturn {
struct PipeBodyReturn { struct PipeBodyReturn {
body: Vec<Value>, body: Vec<Value>,
last_index: usize, last_index: usize,
non_code_meta: NoneCodeMeta, non_code_meta: NonCodeMeta,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -103,9 +103,9 @@ pub struct ParamsResult {
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
struct UnaryExpressionResult { pub struct UnaryExpressionResult {
expression: UnaryExpression, pub expression: UnaryExpression,
last_index: usize, pub last_index: usize,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -136,7 +136,7 @@ struct ReturnStatementResult {
struct BodyResult { struct BodyResult {
body: Vec<BodyItem>, body: Vec<BodyItem>,
last_index: usize, last_index: usize,
non_code_meta: NoneCodeMeta, non_code_meta: NonCodeMeta,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -183,8 +183,8 @@ impl Parser {
let body = self.make_body( let body = self.make_body(
0, 0,
vec![], vec![],
NoneCodeMeta { NonCodeMeta {
none_code_nodes: HashMap::new(), non_code_nodes: HashMap::new(),
start: None, start: None,
}, },
)?; )?;
@ -269,7 +269,7 @@ impl Parser {
Ok(index + 1) Ok(index + 1)
} }
fn make_none_code_node(&self, index: usize) -> Result<(Option<NoneCodeNode>, usize), KclError> { fn make_non_code_node(&self, index: usize) -> Result<(Option<NonCodeNode>, usize), KclError> {
let end_index = self.find_end_of_non_code_node(index)?; let end_index = self.find_end_of_non_code_node(index)?;
let start_index = self.find_start_of_non_code_node(index)?; let start_index = self.find_start_of_non_code_node(index)?;
let non_code_tokens = self.tokens[index..end_index].to_vec(); let non_code_tokens = self.tokens[index..end_index].to_vec();
@ -286,10 +286,10 @@ impl Parser {
.contains("\n\n") .contains("\n\n")
{ {
return Ok(( return Ok((
Some(NoneCodeNode { Some(NonCodeNode {
start: self.tokens[start_index].start, start: self.tokens[start_index].start,
end: self.tokens[end_index - 1].end, end: self.tokens[end_index - 1].end,
value: NoneCodeValue::NewLine, value: NonCodeValue::NewLine,
}), }),
end_index - 1, end_index - 1,
)); ));
@ -330,17 +330,17 @@ impl Parser {
let is_new_line_comment = let is_new_line_comment =
start_end_string.starts_with('\n') || start_end_string.contains('\n') || start_index == 0 || index == 0; start_end_string.starts_with('\n') || start_end_string.contains('\n') || start_index == 0 || index == 0;
let node = NoneCodeNode { let node = NonCodeNode {
start: self.tokens[start_index].start, start: self.tokens[start_index].start,
end: self.tokens[end_index - 1].end, end: self.tokens[end_index - 1].end,
value: if start_end_string.starts_with("\n\n") && is_new_line_comment { value: if start_end_string.starts_with("\n\n") && is_new_line_comment {
// Preserve if they want a whitespace line before the comment. // Preserve if they want a whitespace line before the comment.
// But let's just allow one. // But let's just allow one.
NoneCodeValue::NewLineBlockComment { value: full_string } NonCodeValue::NewLineBlockComment { value: full_string }
} else if is_new_line_comment { } else if is_new_line_comment {
NoneCodeValue::BlockComment { value: full_string } NonCodeValue::BlockComment { value: full_string }
} else { } else {
NoneCodeValue::InlineComment { value: full_string } NonCodeValue::InlineComment { value: full_string }
}, },
}; };
Ok((Some(node), end_index - 1)) Ok((Some(node), end_index - 1))
@ -366,7 +366,7 @@ impl Parser {
}; };
if is_not_code_token(token) { if is_not_code_token(token) {
let non_code_node = self.make_none_code_node(new_index)?; let non_code_node = self.make_non_code_node(new_index)?;
let new_new_index = non_code_node.1 + 1; let new_new_index = non_code_node.1 + 1;
let bonus_non_code_node = non_code_node.0; let bonus_non_code_node = non_code_node.0;
@ -731,7 +731,9 @@ impl Parser {
let maybe_operator = self.next_meaningful_token(index, None)?; let maybe_operator = self.next_meaningful_token(index, None)?;
if let Some(maybe_operator_token) = maybe_operator.token { if let Some(maybe_operator_token) = maybe_operator.token {
if maybe_operator_token.token_type == TokenType::Number { if maybe_operator_token.token_type == TokenType::Number
|| maybe_operator_token.token_type == TokenType::Word
{
return self.find_end_of_binary_expression(maybe_operator.index); return self.find_end_of_binary_expression(maybe_operator.index);
} else if maybe_operator_token.token_type != TokenType::Operator } else if maybe_operator_token.token_type != TokenType::Operator
|| maybe_operator_token.value == PIPE_OPERATOR || maybe_operator_token.value == PIPE_OPERATOR
@ -775,6 +777,7 @@ impl Parser {
}); });
} }
} }
if current_token.token_type == TokenType::Word if current_token.token_type == TokenType::Word
|| current_token.token_type == TokenType::Number || current_token.token_type == TokenType::Number
|| current_token.token_type == TokenType::String || current_token.token_type == TokenType::String
@ -878,10 +881,12 @@ impl Parser {
last_index: function_expression.last_index, last_index: function_expression.last_index,
}) })
} else { } else {
return Err(KclError::Unimplemented(KclErrorDetails { // This is likely a binary expression that starts with a parenthesis.
source_ranges: vec![current_token.into()], let binary_expression = self.make_binary_expression(index)?;
message: "expression with braces".to_string(), Ok(ValueReturn {
})); value: Value::BinaryExpression(Box::new(binary_expression.expression)),
last_index: binary_expression.last_index,
})
} }
} else { } else {
return Err(KclError::Unimplemented(KclErrorDetails { return Err(KclError::Unimplemented(KclErrorDetails {
@ -1023,13 +1028,13 @@ impl Parser {
&self, &self,
index: usize, index: usize,
previous_values: Vec<Value>, previous_values: Vec<Value>,
previous_non_code_meta: Option<NoneCodeMeta>, previous_non_code_meta: Option<NonCodeMeta>,
) -> Result<PipeBodyReturn, KclError> { ) -> Result<PipeBodyReturn, KclError> {
let non_code_meta = match previous_non_code_meta { let non_code_meta = match previous_non_code_meta {
Some(meta) => meta, Some(meta) => meta,
None => NoneCodeMeta { None => NonCodeMeta {
start: None, start: None,
none_code_nodes: HashMap::new(), non_code_nodes: HashMap::new(),
}, },
}; };
let current_token = self.get_token(index)?; let current_token = self.get_token(index)?;
@ -1056,10 +1061,10 @@ impl Parser {
non_code_meta, non_code_meta,
}); });
} }
let mut _non_code_meta: NoneCodeMeta; let mut _non_code_meta: NonCodeMeta;
if let Some(node) = next_pipe.non_code_node { if let Some(node) = next_pipe.non_code_node {
_non_code_meta = non_code_meta; _non_code_meta = non_code_meta;
_non_code_meta.none_code_nodes.insert(previous_values.len(), node); _non_code_meta.non_code_nodes.insert(previous_values.len(), node);
} else { } else {
_non_code_meta = non_code_meta; _non_code_meta = non_code_meta;
} }
@ -1245,11 +1250,19 @@ impl Parser {
}) })
})?; })?;
let closing_brace_token = self.get_token(closing_brace_index)?; let closing_brace_token = self.get_token(closing_brace_index)?;
let args = self.make_arguments(brace_token.index, vec![])?; // Account for if we have no args.
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) { let args = if brace_token.index + 1 == closing_brace_index {
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn } ArgumentsReturn {
arguments: vec![],
last_index: closing_brace_index,
}
} else { } else {
crate::abstract_syntax_tree_types::Function::InMemory self.make_arguments(brace_token.index, vec![])?
};
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
crate::ast::types::Function::StdLib { func: stdlib_fn }
} else {
crate::ast::types::Function::InMemory
}; };
Ok(CallExpressionResult { Ok(CallExpressionResult {
expression: CallExpression { expression: CallExpression {
@ -1418,7 +1431,7 @@ impl Parser {
self.make_params(next_brace_or_comma_token.index, _previous_params) self.make_params(next_brace_or_comma_token.index, _previous_params)
} }
fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> { pub fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
let current_token = self.get_token(index)?; let current_token = self.get_token(index)?;
let next_token = self.next_meaningful_token(index, None)?; let next_token = self.next_meaningful_token(index, None)?;
if next_token.token.is_none() { if next_token.token.is_none() {
@ -1583,7 +1596,7 @@ impl Parser {
&self, &self,
token_index: usize, token_index: usize,
previous_body: Vec<BodyItem>, previous_body: Vec<BodyItem>,
previous_non_code_meta: NoneCodeMeta, previous_non_code_meta: NonCodeMeta,
) -> Result<BodyResult, KclError> { ) -> Result<BodyResult, KclError> {
let mut non_code_meta = previous_non_code_meta; let mut non_code_meta = previous_non_code_meta;
if self.tokens.is_empty() { if self.tokens.is_empty() {
@ -1616,7 +1629,7 @@ impl Parser {
if previous_body.is_empty() { if previous_body.is_empty() {
non_code_meta.start = next_token.non_code_node; non_code_meta.start = next_token.non_code_node;
} else { } else {
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone()); non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
} }
} }
return self.make_body(next_token.index, previous_body, non_code_meta); return self.make_body(next_token.index, previous_body, non_code_meta);
@ -1624,14 +1637,14 @@ impl Parser {
let next = self.next_meaningful_token(token_index, None)?; let next = self.next_meaningful_token(token_index, None)?;
if let Some(node) = &next.non_code_node { if let Some(node) = &next.non_code_node {
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone()); non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
} }
if token.token_type == TokenType::Keyword && VariableKind::from_str(&token.value).is_ok() { if token.token_type == TokenType::Keyword && VariableKind::from_str(&token.value).is_ok() {
let declaration = self.make_variable_declaration(token_index)?; let declaration = self.make_variable_declaration(token_index)?;
let next_thing = self.next_meaningful_token(declaration.last_index, None)?; let next_thing = self.next_meaningful_token(declaration.last_index, None)?;
if let Some(node) = &next_thing.non_code_node { if let Some(node) = &next_thing.non_code_node {
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone()); non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
} }
let mut _previous_body = previous_body; let mut _previous_body = previous_body;
_previous_body.push(BodyItem::VariableDeclaration(VariableDeclaration { _previous_body.push(BodyItem::VariableDeclaration(VariableDeclaration {
@ -1652,7 +1665,7 @@ impl Parser {
let statement = self.make_return_statement(token_index)?; let statement = self.make_return_statement(token_index)?;
let next_thing = self.next_meaningful_token(statement.last_index, None)?; let next_thing = self.next_meaningful_token(statement.last_index, None)?;
if let Some(node) = &next_thing.non_code_node { if let Some(node) = &next_thing.non_code_node {
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone()); non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
} }
let mut _previous_body = previous_body; let mut _previous_body = previous_body;
_previous_body.push(BodyItem::ReturnStatement(ReturnStatement { _previous_body.push(BodyItem::ReturnStatement(ReturnStatement {
@ -1676,7 +1689,7 @@ impl Parser {
let expression = self.make_expression_statement(token_index)?; let expression = self.make_expression_statement(token_index)?;
let next_thing = self.next_meaningful_token(expression.last_index, None)?; let next_thing = self.next_meaningful_token(expression.last_index, None)?;
if let Some(node) = &next_thing.non_code_node { if let Some(node) = &next_thing.non_code_node {
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone()); non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
} }
let mut _previous_body = previous_body; let mut _previous_body = previous_body;
_previous_body.push(BodyItem::ExpressionStatement(ExpressionStatement { _previous_body.push(BodyItem::ExpressionStatement(ExpressionStatement {
@ -1699,7 +1712,7 @@ impl Parser {
&& next_thing_token.token_type == TokenType::Operator && next_thing_token.token_type == TokenType::Operator
{ {
if let Some(node) = &next_thing.non_code_node { if let Some(node) = &next_thing.non_code_node {
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone()); non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
} }
let expression = self.make_expression_statement(token_index)?; let expression = self.make_expression_statement(token_index)?;
let mut _previous_body = previous_body; let mut _previous_body = previous_body;
@ -1730,8 +1743,8 @@ impl Parser {
BodyResult { BodyResult {
body: vec![], body: vec![],
last_index: next_token_index, last_index: next_token_index,
non_code_meta: NoneCodeMeta { non_code_meta: NonCodeMeta {
none_code_nodes: HashMap::new(), non_code_nodes: HashMap::new(),
start: None, start: None,
}, },
} }
@ -1739,8 +1752,8 @@ impl Parser {
self.make_body( self.make_body(
next_token_index, next_token_index,
vec![], vec![],
NoneCodeMeta { NonCodeMeta {
none_code_nodes: HashMap::new(), non_code_nodes: HashMap::new(),
start: None, start: None,
}, },
)? )?
@ -1786,7 +1799,7 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::*; use super::*;
use crate::abstract_syntax_tree_types::BinaryOperator; use crate::ast::types::BinaryOperator;
#[test] #[test]
fn test_make_identifier() { fn test_make_identifier() {
@ -1866,16 +1879,16 @@ mod tests {
); );
} }
#[test] #[test]
fn test_make_none_code_node() { fn test_make_non_code_node() {
let tokens = crate::tokeniser::lexer("log(5, \"hello\", aIdentifier)"); let tokens = crate::tokeniser::lexer("log(5, \"hello\", aIdentifier)");
let parser = Parser::new(tokens); let parser = Parser::new(tokens);
let index = 4; let index = 4;
let expected_output = (None, 4); let expected_output = (None, 4);
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output); assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
let index = 7; let index = 7;
let expected_output = (None, 7); let expected_output = (None, 7);
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output); assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
let tokens = crate::tokeniser::lexer( let tokens = crate::tokeniser::lexer(
r#" r#"
const yo = { a: { b: { c: '123' } } } const yo = { a: { b: { c: '123' } } }
@ -1885,28 +1898,28 @@ const key = 'c'"#,
let parser = Parser::new(tokens); let parser = Parser::new(tokens);
let index = 0; let index = 0;
let expected_output = (None, 0); let expected_output = (None, 0);
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output); assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
let index = 2; let index = 2;
let expected_output = (None, 2); let expected_output = (None, 2);
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output); assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
let index = 2; let index = 2;
let expected_output = (None, 2); let expected_output = (None, 2);
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output); assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
let index = 29; let index = 29;
let expected_output = ( let expected_output = (
Some(NoneCodeNode { Some(NonCodeNode {
start: 38, start: 38,
end: 60, end: 60,
value: NoneCodeValue::BlockComment { value: NonCodeValue::BlockComment {
value: "this is a comment".to_string(), value: "this is a comment".to_string(),
}, },
}), }),
31, 31,
); );
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output); assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
let tokens = crate::tokeniser::lexer( let tokens = crate::tokeniser::lexer(
r#"const mySketch = startSketchAt([0,0]) r#"const mySketch = startSketchAt([0,0])
|> lineTo({ to: [0, 1], tag: 'myPath' }, %) |> lineTo({ to: [0, 1], tag: 'myPath' }, %)
@ -1919,16 +1932,16 @@ const key = 'c'"#,
let parser = Parser::new(tokens); let parser = Parser::new(tokens);
let index = 57; let index = 57;
let expected_output = ( let expected_output = (
Some(NoneCodeNode { Some(NonCodeNode {
start: 106, start: 106,
end: 166, end: 166,
value: NoneCodeValue::BlockComment { value: NonCodeValue::BlockComment {
value: "this is\n a comment\n spanning a few lines".to_string(), value: "this is\n a comment\n spanning a few lines".to_string(),
}, },
}), }),
59, 59,
); );
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output); assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
} }
#[test] #[test]
@ -2741,6 +2754,12 @@ show(mySk1)"#;
let tokens = crate::tokeniser::lexer(code); let tokens = crate::tokeniser::lexer(code);
let parser = Parser::new(tokens); let parser = Parser::new(tokens);
let _end = parser.find_end_of_binary_expression(0).unwrap(); let _end = parser.find_end_of_binary_expression(0).unwrap();
// with call expression at the end of binary expression
let code = "-legX + 2, ";
let tokens = crate::tokeniser::lexer(code);
let parser = Parser::new(tokens.clone());
let end = parser.find_end_of_binary_expression(0).unwrap();
assert_eq!(tokens[end].value, "2");
} }
#[test] #[test]
@ -2895,8 +2914,8 @@ show(mySk1)"#;
.make_body( .make_body(
0, 0,
vec![], vec![],
NoneCodeMeta { NonCodeMeta {
none_code_nodes: HashMap::new(), non_code_nodes: HashMap::new(),
start: None, start: None,
}, },
) )
@ -2933,8 +2952,8 @@ show(mySk1)"#;
})), })),
})), })),
})], })],
non_code_meta: NoneCodeMeta { non_code_meta: NonCodeMeta {
none_code_nodes: Default::default(), non_code_nodes: Default::default(),
start: None, start: None,
}, },
}; };
@ -3079,6 +3098,20 @@ const secondExtrude = startSketchAt([0,0])
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test]
fn test_parse_negative_in_array_binary_expression() {
let tokens = crate::tokeniser::lexer(
r#"const leg1 = 5
const thickness = 0.56
const bracket = [-leg2 + thickness, 0]
"#,
);
let parser = Parser::new(tokens);
let result = parser.ast();
assert!(result.is_ok());
}
#[test] #[test]
fn test_parse_nested_open_brackets() { fn test_parse_nested_open_brackets() {
let tokens = crate::tokeniser::lexer( let tokens = crate::tokeniser::lexer(
@ -3254,8 +3287,8 @@ e
}], }],
kind: VariableKind::Const, kind: VariableKind::Const,
})], })],
non_code_meta: NoneCodeMeta { non_code_meta: NonCodeMeta {
none_code_nodes: Default::default(), non_code_nodes: Default::default(),
start: None, start: None,
}, },
}; };
@ -3332,6 +3365,19 @@ e
); );
} }
#[test]
fn zero_param_function() {
let program = r#"
fn firstPrimeNumber = () => {
return 2
}
firstPrimeNumber()
"#;
let tokens = crate::tokeniser::lexer(program);
let parser = crate::parser::Parser::new(tokens);
let _ast = parser.ast().unwrap();
}
#[test] #[test]
fn test_keyword_ok_in_fn_args_return() { fn test_keyword_ok_in_fn_args_return() {
let some_program_string = r#"fn thing = (param) => { let some_program_string = r#"fn thing = (param) => {
@ -3384,4 +3430,47 @@ thing(false)
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 2])], message: "Expected a `let` variable kind, found: `fn`" }"# r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 2])], message: "Expected a `let` variable kind, found: `fn`" }"#
); );
} }
#[test]
fn test_member_expression_sketch_group() {
let some_program_string = r#"fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const b1 = cube([0,0], 10)
const b2 = cube([3,3], 4)
const pt1 = b1[0]
const pt2 = b2[0]
show(b1)
show(b2)"#;
let tokens = crate::tokeniser::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
parser.ast().unwrap();
}
#[test]
fn test_math_with_stdlib() {
let some_program_string = r#"const d2r = pi() / 2
let other_thing = 2 * cos(3)"#;
let tokens = crate::tokeniser::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
parser.ast().unwrap();
}
#[test]
#[ignore] // ignore until more stack fixes
fn test_parse_pipes_on_pipes() {
let code = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
let tokens = crate::tokeniser::lexer(code);
let parser = crate::parser::Parser::new(tokens);
parser.ast().unwrap();
}
} }

View File

@ -3,21 +3,23 @@
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::Result; use anyhow::Result;
#[cfg(feature = "cli")]
use clap::Parser; use clap::Parser;
use dashmap::DashMap; use dashmap::DashMap;
use tower_lsp::{jsonrpc::Result as RpcResult, lsp_types::*, Client, LanguageServer}; use tower_lsp::{jsonrpc::Result as RpcResult, lsp_types::*, Client, LanguageServer};
use crate::{abstract_syntax_tree_types::VariableKind, executor::SourceRange, parser::PIPE_OPERATOR}; use crate::{ast::types::VariableKind, executor::SourceRange, parser::PIPE_OPERATOR};
/// A subcommand for running the server. /// A subcommand for running the server.
#[derive(Parser, Clone, Debug)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "cli", derive(Parser))]
pub struct Server { pub struct Server {
/// Port that the server should listen /// Port that the server should listen
#[clap(long, default_value = "8080")] #[cfg_attr(feature = "cli", clap(long, default_value = "8080"))]
pub socket: i32, pub socket: i32,
/// Listen over stdin and stdout instead of a tcp socket. /// Listen over stdin and stdout instead of a tcp socket.
#[clap(short, long, default_value = "false")] #[cfg_attr(feature = "cli", clap(short, long, default_value = "false"))]
pub stdio: bool, pub stdio: bool,
} }
@ -34,7 +36,7 @@ pub struct Backend {
/// Token maps. /// Token maps.
pub token_map: DashMap<String, Vec<crate::tokeniser::Token>>, pub token_map: DashMap<String, Vec<crate::tokeniser::Token>>,
/// AST maps. /// AST maps.
pub ast_map: DashMap<String, crate::abstract_syntax_tree_types::Program>, pub ast_map: DashMap<String, crate::ast::types::Program>,
/// Current code. /// Current code.
pub current_code_map: DashMap<String, String>, pub current_code_map: DashMap<String, String>,
/// Diagnostics. /// Diagnostics.
@ -171,19 +173,19 @@ impl Backend {
for item in &ast.body { for item in &ast.body {
match item { match item {
crate::abstract_syntax_tree_types::BodyItem::ExpressionStatement(_) => continue, crate::ast::types::BodyItem::ExpressionStatement(_) => continue,
crate::abstract_syntax_tree_types::BodyItem::ReturnStatement(_) => continue, crate::ast::types::BodyItem::ReturnStatement(_) => continue,
crate::abstract_syntax_tree_types::BodyItem::VariableDeclaration(variable) => { crate::ast::types::BodyItem::VariableDeclaration(variable) => {
// We only want to complete variables. // We only want to complete variables.
for declaration in &variable.declarations { for declaration in &variable.declarations {
completions.push(CompletionItem { completions.push(CompletionItem {
label: declaration.id.name.to_string(), label: declaration.id.name.to_string(),
label_details: None, label_details: None,
kind: Some(match variable.kind { kind: Some(match variable.kind {
crate::abstract_syntax_tree_types::VariableKind::Let => CompletionItemKind::VARIABLE, crate::ast::types::VariableKind::Let => CompletionItemKind::VARIABLE,
crate::abstract_syntax_tree_types::VariableKind::Const => CompletionItemKind::CONSTANT, crate::ast::types::VariableKind::Const => CompletionItemKind::CONSTANT,
crate::abstract_syntax_tree_types::VariableKind::Var => CompletionItemKind::VARIABLE, crate::ast::types::VariableKind::Var => CompletionItemKind::VARIABLE,
crate::abstract_syntax_tree_types::VariableKind::Fn => CompletionItemKind::FUNCTION, crate::ast::types::VariableKind::Fn => CompletionItemKind::FUNCTION,
}), }),
detail: Some(variable.kind.to_string()), detail: Some(variable.kind.to_string()),
documentation: None, documentation: None,
@ -368,7 +370,7 @@ impl LanguageServer for Backend {
}; };
match hover { match hover {
crate::abstract_syntax_tree_types::Hover::Function { name, range } => { crate::ast::types::Hover::Function { name, range } => {
// Get the docs for this function. // Get the docs for this function.
let Some(completion) = self.stdlib_completions.get(&name) else { let Some(completion) = self.stdlib_completions.get(&name) else {
return Ok(None); return Ok(None);
@ -399,7 +401,7 @@ impl LanguageServer for Backend {
range: Some(range), range: Some(range),
})) }))
} }
crate::abstract_syntax_tree_types::Hover::Signature { .. } => Ok(None), crate::ast::types::Hover::Signature { .. } => Ok(None),
} }
} }
@ -482,7 +484,7 @@ impl LanguageServer for Backend {
}; };
match hover { match hover {
crate::abstract_syntax_tree_types::Hover::Function { name, range: _ } => { crate::ast::types::Hover::Function { name, range: _ } => {
// Get the docs for this function. // Get the docs for this function.
let Some(signature) = self.stdlib_signatures.get(&name) else { let Some(signature) = self.stdlib_signatures.get(&name) else {
return Ok(None); return Ok(None);
@ -490,7 +492,7 @@ impl LanguageServer for Backend {
Ok(Some(signature.clone())) Ok(Some(signature.clone()))
} }
crate::abstract_syntax_tree_types::Hover::Signature { crate::ast::types::Hover::Signature {
name, name,
parameter_index, parameter_index,
range: _, range: _,
@ -554,7 +556,7 @@ impl LanguageServer for Backend {
}; };
// Now recast it. // Now recast it.
let recast = ast.recast( let recast = ast.recast(
&crate::abstract_syntax_tree_types::FormatOptions { &crate::ast::types::FormatOptions {
tab_size: params.options.tab_size as usize, tab_size: params.options.tab_size as usize,
insert_final_newline: params.options.insert_final_newline.unwrap_or(false), insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
use_tabs: !params.options.insert_spaces, use_tabs: !params.options.insert_spaces,

View File

@ -23,7 +23,7 @@ pub fn extrude(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "extrude" name = "extrude"
}] }]
fn inner_extrude(length: f64, sketch_group: SketchGroup, args: &mut Args) -> Result<ExtrudeGroup, KclError> { fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: &mut Args) -> Result<Box<ExtrudeGroup>, KclError> {
let id = uuid::Uuid::new_v4(); let id = uuid::Uuid::new_v4();
let cmd = kittycad::types::ModelingCmd::Extrude { let cmd = kittycad::types::ModelingCmd::Extrude {
@ -33,7 +33,7 @@ fn inner_extrude(length: f64, sketch_group: SketchGroup, args: &mut Args) -> Res
}; };
args.send_modeling_cmd(id, cmd)?; args.send_modeling_cmd(id, cmd)?;
Ok(ExtrudeGroup { Ok(Box::new(ExtrudeGroup {
id, id,
// TODO, this is just an empty array now, should be deleted. This // TODO, this is just an empty array now, should be deleted. This
// comment was originally in the JS code. // comment was originally in the JS code.
@ -42,13 +42,13 @@ fn inner_extrude(length: f64, sketch_group: SketchGroup, args: &mut Args) -> Res
position: sketch_group.position, position: sketch_group.position,
rotation: sketch_group.rotation, rotation: sketch_group.rotation,
meta: sketch_group.meta, meta: sketch_group.meta,
}) }))
} }
/// Returns the extrude wall transform. /// Returns the extrude wall transform.
pub fn get_extrude_wall_transform(args: &mut Args) -> Result<MemoryItem, KclError> { pub fn get_extrude_wall_transform(args: &mut Args) -> Result<MemoryItem, KclError> {
let (surface_name, extrude_group) = args.get_path_name_extrude_group()?; let (surface_name, extrude_group) = args.get_path_name_extrude_group()?;
let result = inner_get_extrude_wall_transform(&surface_name, extrude_group, args)?; let result = inner_get_extrude_wall_transform(&surface_name, *extrude_group, args)?;
Ok(MemoryItem::ExtrudeTransform(result)) Ok(MemoryItem::ExtrudeTransform(result))
} }

View File

@ -4,7 +4,11 @@ use anyhow::Result;
use derive_docs::stdlib; use derive_docs::stdlib;
use schemars::JsonSchema; use schemars::JsonSchema;
use crate::{errors::KclError, executor::MemoryItem, std::Args}; use crate::{
errors::{KclError, KclErrorDetails},
executor::MemoryItem,
std::Args,
};
/// Computes the cosine of a number (in radians). /// Computes the cosine of a number (in radians).
pub fn cos(args: &mut Args) -> Result<MemoryItem, KclError> { pub fn cos(args: &mut Args) -> Result<MemoryItem, KclError> {
@ -54,17 +58,320 @@ fn inner_tan(num: f64) -> Result<f64, KclError> {
Ok(num.tan()) Ok(num.tan())
} }
/// Return the value of `pi`. /// Return the value of `pi`. Archimedes constant (π).
pub fn pi(args: &mut Args) -> Result<MemoryItem, KclError> { pub fn pi(args: &mut Args) -> Result<MemoryItem, KclError> {
let result = inner_pi()?; let result = inner_pi()?;
args.make_user_val_from_f64(result) args.make_user_val_from_f64(result)
} }
/// Return the value of `pi`. /// Return the value of `pi`. Archimedes constant (π).
#[stdlib { #[stdlib {
name = "pi", name = "pi",
}] }]
fn inner_pi() -> Result<f64, KclError> { fn inner_pi() -> Result<f64, KclError> {
Ok(std::f64::consts::PI) Ok(std::f64::consts::PI)
} }
/// Computes the square root of a number.
pub fn sqrt(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_sqrt(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the square root of a number.
#[stdlib {
name = "sqrt",
}]
fn inner_sqrt(num: f64) -> Result<f64, KclError> {
Ok(num.sqrt())
}
/// Computes the absolute value of a number.
pub fn abs(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_abs(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the absolute value of a number.
#[stdlib {
name = "abs",
}]
fn inner_abs(num: f64) -> Result<f64, KclError> {
Ok(num.abs())
}
/// Computes the largest integer less than or equal to a number.
pub fn floor(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_floor(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the largest integer less than or equal to a number.
#[stdlib {
name = "floor",
}]
fn inner_floor(num: f64) -> Result<f64, KclError> {
Ok(num.floor())
}
/// Computes the smallest integer greater than or equal to a number.
pub fn ceil(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_ceil(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the smallest integer greater than or equal to a number.
#[stdlib {
name = "ceil",
}]
fn inner_ceil(num: f64) -> Result<f64, KclError> {
Ok(num.ceil())
}
/// Computes the minimum of the given arguments.
pub fn min(args: &mut Args) -> Result<MemoryItem, KclError> {
let nums = args.get_number_array()?;
let result = inner_min(nums);
args.make_user_val_from_f64(result)
}
/// Computes the minimum of the given arguments.
#[stdlib {
name = "min",
}]
fn inner_min(args: Vec<f64>) -> f64 {
let mut min = std::f64::MAX;
for arg in args.iter() {
if *arg < min {
min = *arg;
}
}
min
}
/// Computes the maximum of the given arguments.
pub fn max(args: &mut Args) -> Result<MemoryItem, KclError> {
let nums = args.get_number_array()?;
let result = inner_max(nums);
args.make_user_val_from_f64(result)
}
/// Computes the maximum of the given arguments.
#[stdlib {
name = "max",
}]
fn inner_max(args: Vec<f64>) -> f64 {
let mut max = std::f64::MAX;
for arg in args.iter() {
if *arg > max {
max = *arg;
}
}
max
}
/// Computes the number to a power.
pub fn pow(args: &mut Args) -> Result<MemoryItem, KclError> {
let nums = args.get_number_array()?;
if nums.len() > 2 {
return Err(KclError::Type(KclErrorDetails {
message: format!("expected 2 arguments, got {}", nums.len()),
source_ranges: vec![args.source_range],
}));
}
if nums.len() <= 1 {
return Err(KclError::Type(KclErrorDetails {
message: format!("expected 2 arguments, got {}", nums.len()),
source_ranges: vec![args.source_range],
}));
}
let result = inner_pow(nums[0], nums[1])?;
args.make_user_val_from_f64(result)
}
/// Computes the number to a power.
#[stdlib {
name = "pow",
}]
fn inner_pow(num: f64, pow: f64) -> Result<f64, KclError> {
Ok(num.powf(pow))
}
/// Computes the arccosine of a number (in radians).
pub fn acos(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_acos(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the arccosine of a number (in radians).
#[stdlib {
name = "acos",
}]
fn inner_acos(num: f64) -> Result<f64, KclError> {
Ok(num.acos())
}
/// Computes the arcsine of a number (in radians).
pub fn asin(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_asin(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the arcsine of a number (in radians).
#[stdlib {
name = "asin",
}]
fn inner_asin(num: f64) -> Result<f64, KclError> {
Ok(num.asin())
}
/// Computes the arctangent of a number (in radians).
pub fn atan(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_atan(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the arctangent of a number (in radians).
#[stdlib {
name = "atan",
}]
fn inner_atan(num: f64) -> Result<f64, KclError> {
Ok(num.atan())
}
/// Computes the logarithm of the number with respect to an arbitrary base.
///
/// The result might not be correctly rounded owing to implementation
/// details; `log2()` can produce more accurate results for base 2,
/// and `log10()` can produce more accurate results for base 10.
pub fn log(args: &mut Args) -> Result<MemoryItem, KclError> {
let nums = args.get_number_array()?;
if nums.len() > 2 {
return Err(KclError::Type(KclErrorDetails {
message: format!("expected 2 arguments, got {}", nums.len()),
source_ranges: vec![args.source_range],
}));
}
if nums.len() <= 1 {
return Err(KclError::Type(KclErrorDetails {
message: format!("expected 2 arguments, got {}", nums.len()),
source_ranges: vec![args.source_range],
}));
}
let result = inner_log(nums[0], nums[1])?;
args.make_user_val_from_f64(result)
}
/// Computes the logarithm of the number with respect to an arbitrary base.
///
/// The result might not be correctly rounded owing to implementation
/// details; `log2()` can produce more accurate results for base 2,
/// and `log10()` can produce more accurate results for base 10.
#[stdlib {
name = "log",
}]
fn inner_log(num: f64, base: f64) -> Result<f64, KclError> {
Ok(num.log(base))
}
/// Computes the base 2 logarithm of the number.
pub fn log2(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_log2(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the base 2 logarithm of the number.
#[stdlib {
name = "log2",
}]
fn inner_log2(num: f64) -> Result<f64, KclError> {
Ok(num.log2())
}
/// Computes the base 10 logarithm of the number.
pub fn log10(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_log10(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the base 10 logarithm of the number.
#[stdlib {
name = "log10",
}]
fn inner_log10(num: f64) -> Result<f64, KclError> {
Ok(num.log10())
}
/// Computes the natural logarithm of the number.
pub fn ln(args: &mut Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let result = inner_ln(num)?;
args.make_user_val_from_f64(result)
}
/// Computes the natural logarithm of the number.
#[stdlib {
name = "ln",
}]
fn inner_ln(num: f64) -> Result<f64, KclError> {
Ok(num.ln())
}
/// Return the value of Eulers number `e`.
pub fn e(args: &mut Args) -> Result<MemoryItem, KclError> {
let result = inner_e()?;
args.make_user_val_from_f64(result)
}
/// Return the value of Eulers number `e`.
#[stdlib {
name = "e",
}]
fn inner_e() -> Result<f64, KclError> {
Ok(std::f64::consts::E)
}
/// Return the value of `tau`. The full circle constant (τ). Equal to 2π.
pub fn tau(args: &mut Args) -> Result<MemoryItem, KclError> {
let result = inner_tau()?;
args.make_user_val_from_f64(result)
}
/// Return the value of `tau`. The full circle constant (τ). Equal to 2π.
#[stdlib {
name = "tau",
}]
fn inner_tau() -> Result<f64, KclError> {
Ok(std::f64::consts::TAU)
}

View File

@ -15,8 +15,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
abstract_syntax_tree_types::parse_json_number_as_f64, ast::types::parse_json_number_as_f64,
engine::EngineConnection, engine::{EngineConnection, EngineManager},
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ExtrudeGroup, MemoryItem, Metadata, SketchGroup, SourceRange}, executor::{ExtrudeGroup, MemoryItem, Metadata, SketchGroup, SourceRange},
}; };
@ -32,7 +32,6 @@ impl StdLib {
pub fn new() -> Self { pub fn new() -> Self {
let internal_fns: Vec<Box<(dyn crate::docs::StdLibFn)>> = vec![ let internal_fns: Vec<Box<(dyn crate::docs::StdLibFn)>> = vec![
Box::new(Show), Box::new(Show),
Box::new(Min),
Box::new(LegLen), Box::new(LegLen),
Box::new(LegAngX), Box::new(LegAngX),
Box::new(LegAngY), Box::new(LegAngY),
@ -65,7 +64,23 @@ impl StdLib {
Box::new(crate::std::math::Cos), Box::new(crate::std::math::Cos),
Box::new(crate::std::math::Sin), Box::new(crate::std::math::Sin),
Box::new(crate::std::math::Tan), Box::new(crate::std::math::Tan),
Box::new(crate::std::math::Acos),
Box::new(crate::std::math::Asin),
Box::new(crate::std::math::Atan),
Box::new(crate::std::math::Pi), Box::new(crate::std::math::Pi),
Box::new(crate::std::math::E),
Box::new(crate::std::math::Tau),
Box::new(crate::std::math::Sqrt),
Box::new(crate::std::math::Abs),
Box::new(crate::std::math::Floor),
Box::new(crate::std::math::Ceil),
Box::new(crate::std::math::Min),
Box::new(crate::std::math::Max),
Box::new(crate::std::math::Pow),
Box::new(crate::std::math::Log),
Box::new(crate::std::math::Log2),
Box::new(crate::std::math::Log10),
Box::new(crate::std::math::Ln),
]; ];
let mut fns = HashMap::new(); let mut fns = HashMap::new();
@ -164,7 +179,7 @@ impl<'a> Args<'a> {
Ok((numbers[0], numbers[1])) Ok((numbers[0], numbers[1]))
} }
fn get_segment_name_sketch_group(&self) -> Result<(String, SketchGroup), KclError> { fn get_segment_name_sketch_group(&self) -> Result<(String, Box<SketchGroup>), KclError> {
// Iterate over our args, the first argument should be a UserVal with a string value. // Iterate over our args, the first argument should be a UserVal with a string value.
// The second argument should be a SketchGroup. // The second argument should be a SketchGroup.
let first_value = self let first_value = self
@ -206,7 +221,7 @@ impl<'a> Args<'a> {
Ok((segment_name, sketch_group)) Ok((segment_name, sketch_group))
} }
fn get_sketch_group(&self) -> Result<SketchGroup, KclError> { fn get_sketch_group(&self) -> Result<Box<SketchGroup>, KclError> {
let first_value = self.args.first().ok_or_else(|| { let first_value = self.args.first().ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!("Expected a SketchGroup as the first argument, found `{:?}`", self.args), message: format!("Expected a SketchGroup as the first argument, found `{:?}`", self.args),
@ -248,7 +263,7 @@ impl<'a> Args<'a> {
Ok(data) Ok(data)
} }
fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, SketchGroup), KclError> { fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<SketchGroup>), KclError> {
let first_value = self let first_value = self
.args .args
.first() .first()
@ -286,7 +301,7 @@ impl<'a> Args<'a> {
Ok((data, sketch_group)) Ok((data, sketch_group))
} }
fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, SketchGroup), KclError> { fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, Box<SketchGroup>), KclError> {
// Iterate over our args, the first argument should be a UserVal with a string value. // Iterate over our args, the first argument should be a UserVal with a string value.
// The second argument should be a number. // The second argument should be a number.
// The third argument should be a SketchGroup. // The third argument should be a SketchGroup.
@ -342,7 +357,7 @@ impl<'a> Args<'a> {
Ok((segment_name, to_number, sketch_group)) Ok((segment_name, to_number, sketch_group))
} }
fn get_number_sketch_group(&self) -> Result<(f64, SketchGroup), KclError> { fn get_number_sketch_group(&self) -> Result<(f64, Box<SketchGroup>), KclError> {
// Iterate over our args, the first argument should be a number. // Iterate over our args, the first argument should be a number.
// The second argument should be a SketchGroup. // The second argument should be a SketchGroup.
let first_value = self let first_value = self
@ -377,7 +392,7 @@ impl<'a> Args<'a> {
Ok((number, sketch_group)) Ok((number, sketch_group))
} }
fn get_path_name_extrude_group(&self) -> Result<(String, ExtrudeGroup), KclError> { fn get_path_name_extrude_group(&self) -> Result<(String, Box<ExtrudeGroup>), KclError> {
// Iterate over our args, the first argument should be a UserVal with a string value. // Iterate over our args, the first argument should be a UserVal with a string value.
// The second argument should be a ExtrudeGroup. // The second argument should be a ExtrudeGroup.
let first_value = self let first_value = self
@ -426,29 +441,6 @@ impl<'a> Args<'a> {
} }
} }
/// Returns the minimum of the given arguments.
pub fn min(args: &mut Args) -> Result<MemoryItem, KclError> {
let nums = args.get_number_array()?;
let result = inner_min(nums);
args.make_user_val_from_f64(result)
}
/// Returns the minimum of the given arguments.
#[stdlib {
name = "min",
}]
fn inner_min(args: Vec<f64>) -> f64 {
let mut min = std::f64::MAX;
for arg in args.iter() {
if *arg < min {
min = *arg;
}
}
min
}
/// Render a model. /// Render a model.
// This never actually gets called so this is fine. // This never actually gets called so this is fine.
pub fn show(args: &mut Args) -> Result<MemoryItem, KclError> { pub fn show(args: &mut Args) -> Result<MemoryItem, KclError> {
@ -462,7 +454,7 @@ pub fn show(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "show", name = "show",
}] }]
fn inner_show(_sketch: SketchGroup) {} fn inner_show(_sketch: Box<SketchGroup>) {}
/// Returns the length of the given leg. /// Returns the length of the given leg.
pub fn leg_length(args: &mut Args) -> Result<MemoryItem, KclError> { pub fn leg_length(args: &mut Args) -> Result<MemoryItem, KclError> {
@ -526,9 +518,10 @@ pub enum Primitive {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::std::StdLib;
use itertools::Itertools; use itertools::Itertools;
use crate::std::StdLib;
#[test] #[test]
fn test_generate_stdlib_markdown_docs() { fn test_generate_stdlib_markdown_docs() {
let stdlib = StdLib::new(); let stdlib = StdLib::new();

View File

@ -22,7 +22,7 @@ pub fn segment_end_x(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "segEndX", name = "segEndX",
}] }]
fn inner_segment_end_x(segment_name: &str, sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> { fn inner_segment_end_x(segment_name: &str, sketch_group: Box<SketchGroup>, args: &mut Args) -> Result<f64, KclError> {
let line = sketch_group.get_base_by_name_or_start(segment_name).ok_or_else(|| { let line = sketch_group.get_base_by_name_or_start(segment_name).ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!( message: format!(
@ -48,7 +48,7 @@ pub fn segment_end_y(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "segEndY", name = "segEndY",
}] }]
fn inner_segment_end_y(segment_name: &str, sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> { fn inner_segment_end_y(segment_name: &str, sketch_group: Box<SketchGroup>, args: &mut Args) -> Result<f64, KclError> {
let line = sketch_group.get_base_by_name_or_start(segment_name).ok_or_else(|| { let line = sketch_group.get_base_by_name_or_start(segment_name).ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!( message: format!(
@ -74,7 +74,7 @@ pub fn last_segment_x(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "lastSegX", name = "lastSegX",
}] }]
fn inner_last_segment_x(sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> { fn inner_last_segment_x(sketch_group: Box<SketchGroup>, args: &mut Args) -> Result<f64, KclError> {
let last_line = sketch_group let last_line = sketch_group
.value .value
.last() .last()
@ -104,7 +104,7 @@ pub fn last_segment_y(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "lastSegY", name = "lastSegY",
}] }]
fn inner_last_segment_y(sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> { fn inner_last_segment_y(sketch_group: Box<SketchGroup>, args: &mut Args) -> Result<f64, KclError> {
let last_line = sketch_group let last_line = sketch_group
.value .value
.last() .last()
@ -133,7 +133,7 @@ pub fn segment_length(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "segLen", name = "segLen",
}] }]
fn inner_segment_length(segment_name: &str, sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> { fn inner_segment_length(segment_name: &str, sketch_group: Box<SketchGroup>, args: &mut Args) -> Result<f64, KclError> {
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| { let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!( message: format!(
@ -162,7 +162,7 @@ pub fn segment_angle(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "segAng", name = "segAng",
}] }]
fn inner_segment_angle(segment_name: &str, sketch_group: SketchGroup, args: &mut Args) -> Result<f64, KclError> { fn inner_segment_angle(segment_name: &str, sketch_group: Box<SketchGroup>, args: &mut Args) -> Result<f64, KclError> {
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| { let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!( message: format!(
@ -193,7 +193,7 @@ pub fn angle_to_match_length_x(args: &mut Args) -> Result<MemoryItem, KclError>
fn inner_angle_to_match_length_x( fn inner_angle_to_match_length_x(
segment_name: &str, segment_name: &str,
to: f64, to: f64,
sketch_group: SketchGroup, sketch_group: Box<SketchGroup>,
args: &mut Args, args: &mut Args,
) -> Result<f64, KclError> { ) -> Result<f64, KclError> {
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| { let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {
@ -248,7 +248,7 @@ pub fn angle_to_match_length_y(args: &mut Args) -> Result<MemoryItem, KclError>
fn inner_angle_to_match_length_y( fn inner_angle_to_match_length_y(
segment_name: &str, segment_name: &str,
to: f64, to: f64,
sketch_group: SketchGroup, sketch_group: Box<SketchGroup>,
args: &mut Args, args: &mut Args,
) -> Result<f64, KclError> { ) -> Result<f64, KclError> {
let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| { let path = sketch_group.get_path_by_name(segment_name).ok_or_else(|| {

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