Compare commits

...

121 Commits

Author SHA1 Message Date
73fb4e567d Use dispatch
Fixes #839
2023-10-12 00:49:02 -05:00
616b168002 Add another unit test 2023-10-11 23:02:53 -05:00
0fe2fa14e0 Fix another regression 2023-10-11 23:02:52 -05:00
7956b66302 Allow whitespace within open parentheses 2023-10-11 23:02:52 -05:00
48fa65e2e6 Allow whitespace before commas in comma-separated lists 2023-10-11 23:02:52 -05:00
f8d0cb0b92 Empty functions are no longer valid 2023-10-11 23:02:52 -05:00
b7e5a27aae Allow arithmetic on LHS of a pipe expression 2023-10-11 23:02:51 -05:00
643497f6d0 Correct inaccurate test, add CommentStyle to tests 2023-10-11 23:02:51 -05:00
e2ad24360a More little bugfixes
NonCodeMeta can have many nodes in the 'start' not just 0 or 1

fix Double newline after block comments
2023-10-11 23:02:51 -05:00
c3a61f368e Adjust test expectations because they're expecting inaccuracies which have been fixed 2023-10-11 23:02:51 -05:00
0c9250d2c5 Fix custom deserializer for NonCodeMeta 2023-10-11 23:02:50 -05:00
3666f42e41 Fix yarn tests 2023-10-11 23:02:50 -05:00
cd01d1c755 Accept fewer kinds of value on RHS of a |> operator
This yields SIGNIFICANT speedup
2023-10-11 23:02:50 -05:00
cf177c10a9 Separate benches for parsing and lexing 2023-10-11 23:02:49 -05:00
09615164eb New parser built with Winnow
Fixes #716
2023-10-11 23:02:49 -05:00
5b90686e5e fix for culling (#835)
* start of fix for culling;

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

* update lock

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

* 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-10-11 18:30:04 -07:00
298269d117 Bump tokio from 1.32.0 to 1.33.0 in /src-tauri (#822)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.32.0...tokio-1.33.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 17:47:35 -07:00
b379f6518f Add Language Data's Comment Tokens to Support Toggle Commenting Shortcuts in the Editor (#809)
Add Language Data's Comment Tokens to Support Toggle Comment Shortcuts

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-11 16:59:54 -07:00
6b22c8789d Bump tokio from 1.32.0 to 1.33.0 in /src/wasm-lib (#820)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.32.0...tokio-1.33.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 16:57:33 -07:00
cb4683e70b Bump proc-macro2 from 1.0.67 to 1.0.69 in /src/wasm-lib (#810)
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.67 to 1.0.69.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.67...1.0.69)

---
updated-dependencies:
- dependency-name: proc-macro2
  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>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-11 16:57:18 -07:00
0a020d9959 Bump tauri-plugin-fs-extra from 9f27e6e to 9b20f28 in /src-tauri (#828)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9f27e6e` to `9b20f28`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9f27e6e441...9b20f28d74)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 16:57:08 -07:00
7aae3dccdc Use route loader data to instantiate the kcl code (#832) 2023-10-11 16:29:22 -04:00
818bf96d0b update rendered images (#831)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-11 11:49:55 -07:00
03bc2eaf22 Docs: std.md now displays correct number of members of an array (#825)
* First draft up

* removed print statements

* running cargo fmt

---------

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-11 11:12:21 -07:00
8ad1476c13 wait to execute code until planes are ready (#830)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-11 13:40:54 -04:00
6c15a743a2 remove useAppMode hook file (#829) 2023-10-11 04:12:29 +00:00
d0930477ad xstate migration (#713)
* Add basic Popover functionality

* Fix up light mode of basic bar

* Add support for 2D and 3D mode styling

* Turn toolbar buttons back on

* Remove ActionButton until after tool logic refactor

* Add transitions

* Add initial modeling machine
This is not a full description of how the modelingMachine should work,
but begins to replicate all of the features of our useStore in XState
instead of zustand.

* Add fillet tool flow

* Refactor: break out engine manager setup into hook
Preparing for making a wrapper component around the App
that will manage the engine manager at the same level as
the modelingMachine.

* Create modeling provider, move engine management to it

* Refactor: move other engine-related useEffect into hook

* Add TS schema, selection actions to modelingMachine

* Add barebones modeling machine to app
Only implementing adding to code-based selections in the text editor so far

* Update moved useEffect hook after merge

* give myself reminder TODO

* fix engineCommandManager waitForReady Promise

* enable devtools

* make utility class for handling default planes

* progresson startNewSketch and EditSketch

* add provider to tests

* too large of a commit
put all of the lang state into another singleton, but did lots of work on xstate too

* fix edit sketch ast issue

* re-execute on sketch exit

* prettierignore xstate typegen file

* add move tool button back in

* handle mouse commands with xState states

* fix move

* remove old imports

* big useStore delete

* fix some destructuring bugs

* start of constraint actions

* add horizontal/vertical distance constraints

* fix more destructuring errors

* fix

* add angle constaints

* add align vertically/horizontally constraints

* add length and equal length constraints

* rename modal states to be more cmd bar friendly

* add doesPipeHave query

* add another query

* add extrude states

* state machine clean up

* xstate layout tweak

* make xstate types happy

* Revamp cursor logic and place curors after ast mod

* Xstate merge (#796)

* turning back on all planes (#720)

* updates

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

tests

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

fix more tests

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

fixes

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

fix stdlib

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

fix tests

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

fixes

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

updates

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

updates

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

compile

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

update sample code

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

re-enable the planes

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

updates

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

fix all tests

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

updates

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

boilerplate

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

Cut release v0.9.2 (#714)

rust make default planes

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

updates

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

use the planes from engine

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

fixups

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

updates

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

fixes

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

updates

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>

negative args

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

diable camera

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

hide planes

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

updates

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

updates

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

updatress

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

fmt

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

Update src/hooks/useAppMode.ts

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

Update src/hooks/useAppMode.ts

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

cleanups

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

Bump kittycad from 0.2.26 to 0.2.27 in /src-tauri (#726)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

---
updated-dependencies:
- dependency-name: kittycad
  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>

Bump tauri-plugin-fs-extra from `b04bde3` to `6c7a4c0` in /src-tauri (#725)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `b04bde3` to `6c7a4c0`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](b04bde3461...6c7a4c0984)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump toml from 0.8.0 to 0.8.1 in /src-tauri (#724)

Bumps [toml](https://github.com/toml-rs/toml) from 0.8.0 to 0.8.1.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.8.1)

---
updated-dependencies:
- dependency-name: toml
  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>

Bump openapitor from `61a1605` to `d3e98c4` in /src/wasm-lib (#723)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `61a1605` to `d3e98c4`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](61a16059b3...d3e98c4ec0)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump kittycad from 0.2.26 to 0.2.27 in /src/wasm-lib (#722)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

---
updated-dependencies:
- dependency-name: kittycad
  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>

Bump thiserror from 1.0.48 to 1.0.49 in /src/wasm-lib (#721)

Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.48...1.0.49)

---
updated-dependencies:
- dependency-name: thiserror
  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>

Bump expectorate from 1.0.7 to 1.1.0 in /src/wasm-lib (#712)

Bumps [expectorate](https://github.com/oxidecomputer/expectorate) from 1.0.7 to 1.1.0.
- [Release notes](https://github.com/oxidecomputer/expectorate/releases)
- [Commits](https://github.com/oxidecomputer/expectorate/compare/v1.0.7...v1.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump clap from 4.4.4 to 4.4.5 in /src/wasm-lib (#711)

Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.4...v4.4.5)

---
updated-dependencies:
- dependency-name: clap
  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>

refactor cleanup

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

updates

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

updates

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

type improvements

* use new sketchmode no camera

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

* js working better

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

* start of negative planes

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

* tests and neg

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

* updates

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

* images

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

* norma;s

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

* better initial load of planes

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

* ts

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

* updates

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

* fixes

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

* updates

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

* fixes

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

* fix tsc

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

* fix edit sketch

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

* add regression test for 2d solid issue

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

* updates

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

* show planes

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

* fix clippy

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

* fix tests

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

* canecel in progress

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

* fix ci as well

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>

* stopping point

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

* updates

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

* fixes

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

* refactor

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

* updates

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

* it works

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

* updates

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

* Hide planes (#797)

* hide planes in one go

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

* update hide;

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>

* make tsc happy

* Make "Replay Onboarding" button available on home settings page (#804)

* Fix unrelated bug, settings button in the home sidebar
doesn't go to the home settings after my previous fixes to routes

* Turn on "Replay Onboarding" button in home settings

* Use ONBOARDING_PROJECT_NAME in both places

* Fix formatting

* Cut release v0.10.0 (#803)

Co-authored-by: Frank Noirot <frank@kittycad.io>

* Bump kittycad from 0.2.28 to 0.2.31 in /src-tauri (#798)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.28 to 0.2.31.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.28...v0.2.31)

---
updated-dependencies:
- dependency-name: kittycad
  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>

* Bump openapitor from `fa0345c` to `c122a9b` in /src/wasm-lib (#800)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `fa0345c` to `c122a9b`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](fa0345c514...c122a9b1d6)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump syn from 2.0.37 to 2.0.38 in /src/wasm-lib (#801)

Bumps [syn](https://github.com/dtolnay/syn) from 2.0.37 to 2.0.38.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.37...2.0.38)

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

* Bump winnow from 0.5.15 to 0.5.16 in /src/wasm-lib (#799)

Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.15 to 0.5.16.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.15...v0.5.16)

---
updated-dependencies:
- dependency-name: winnow
  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>

* Bump tauri-plugin-fs-extra from `fa32d1a` to `9f27e6e` in /src-tauri (#802)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `fa32d1a` to `9f27e6e`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](fa32d1afa9...9f27e6e441)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* better plane selection

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

* use the sketch plane id

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

* add todo w bug

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

* Improve Prop Typings for Modals. Remove instances of `any`. (#792)

* Update typings for modals. Remove instances of `any`

* Fix generic type for creating modals

* cleanup other stuffs

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

* make plane id available when selecting default plane

* few clean up things

* change enter sketch action order to make sure plane id is available to 'enter edit mode'

* Revert "Improve Prop Typings for Modals. Remove instances of `any`. (… (#813)

Revert "Improve Prop Typings for Modals. Remove instances of `any`. (#792)"

This reverts commit 629f326f4c.

* ffmpeg instructions (#814)

* fix some tsc stuff

* small tweak

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jason Rametta <rametta@outlook.com>

* clean up

* fix test and tsc

* remove one more thing from useStore

* tweak state digrame layout

* fmt

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Frank Johnson <frankjohnson1993@gmail.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jason Rametta <rametta@outlook.com>
2023-10-11 13:36:54 +11:00
e5e30d231b ffmpeg instructions (#814) 2023-10-10 06:54:50 +11:00
9822576077 Revert "Improve Prop Typings for Modals. Remove instances of any. (… (#813)
Revert "Improve Prop Typings for Modals. Remove instances of `any`. (#792)"

This reverts commit 629f326f4c.
2023-10-10 06:43:25 +11:00
629f326f4c Improve Prop Typings for Modals. Remove instances of any. (#792)
* Update typings for modals. Remove instances of `any`

* Fix generic type for creating modals
2023-10-06 16:34:21 -04:00
89b880d9ae Bump tauri-plugin-fs-extra from fa32d1a to 9f27e6e in /src-tauri (#802)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `fa32d1a` to `9f27e6e`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](fa32d1afa9...9f27e6e441)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-06 11:48:54 -07:00
f6de0de1bf Bump winnow from 0.5.15 to 0.5.16 in /src/wasm-lib (#799)
Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.15 to 0.5.16.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.15...v0.5.16)

---
updated-dependencies:
- dependency-name: winnow
  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-10-06 11:48:42 -07:00
65ebb86b67 Bump syn from 2.0.37 to 2.0.38 in /src/wasm-lib (#801)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.37 to 2.0.38.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.37...2.0.38)

---
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-10-06 11:48:27 -07:00
cce8274902 Bump openapitor from fa0345c to c122a9b in /src/wasm-lib (#800)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `fa0345c` to `c122a9b`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](fa0345c514...c122a9b1d6)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-06 11:48:15 -07:00
c515bef8e4 Bump kittycad from 0.2.28 to 0.2.31 in /src-tauri (#798)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.28 to 0.2.31.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.28...v0.2.31)

---
updated-dependencies:
- dependency-name: kittycad
  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-10-06 11:47:58 -07:00
b17e61d963 Cut release v0.10.0 (#803)
Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-10-06 11:09:54 -04:00
d31d07d9c8 Make "Replay Onboarding" button available on home settings page (#804)
* Fix unrelated bug, settings button in the home sidebar
doesn't go to the home settings after my previous fixes to routes

* Turn on "Replay Onboarding" button in home settings

* Use ONBOARDING_PROJECT_NAME in both places

* Fix formatting
2023-10-06 10:00:35 -04:00
7aa2d63c21 Hide planes (#797)
* hide planes in one go

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

* update hide;

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-10-05 19:54:31 -07:00
e1081b0ee6 turning back on all planes (#720)
* updates

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

tests

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

fix more tests

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

fixes

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

fix stdlib

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

fix tests

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

fixes

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

updates

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

updates

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

compile

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

update sample code

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

re-enable the planes

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

updates

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

fix all tests

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

updates

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

boilerplate

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

Cut release v0.9.2 (#714)

rust make default planes

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

updates

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

use the planes from engine

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

fixups

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

updates

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

fixes

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

updates

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>

negative args

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

diable camera

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

hide planes

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

updates

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

updates

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

updatress

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

fmt

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

Update src/hooks/useAppMode.ts

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

Update src/hooks/useAppMode.ts

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

cleanups

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

Bump kittycad from 0.2.26 to 0.2.27 in /src-tauri (#726)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

---
updated-dependencies:
- dependency-name: kittycad
  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>

Bump tauri-plugin-fs-extra from `b04bde3` to `6c7a4c0` in /src-tauri (#725)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `b04bde3` to `6c7a4c0`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](b04bde3461...6c7a4c0984)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump toml from 0.8.0 to 0.8.1 in /src-tauri (#724)

Bumps [toml](https://github.com/toml-rs/toml) from 0.8.0 to 0.8.1.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.8.1)

---
updated-dependencies:
- dependency-name: toml
  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>

Bump openapitor from `61a1605` to `d3e98c4` in /src/wasm-lib (#723)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `61a1605` to `d3e98c4`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](61a16059b3...d3e98c4ec0)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump kittycad from 0.2.26 to 0.2.27 in /src/wasm-lib (#722)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

---
updated-dependencies:
- dependency-name: kittycad
  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>

Bump thiserror from 1.0.48 to 1.0.49 in /src/wasm-lib (#721)

Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.48...1.0.49)

---
updated-dependencies:
- dependency-name: thiserror
  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>

Bump expectorate from 1.0.7 to 1.1.0 in /src/wasm-lib (#712)

Bumps [expectorate](https://github.com/oxidecomputer/expectorate) from 1.0.7 to 1.1.0.
- [Release notes](https://github.com/oxidecomputer/expectorate/releases)
- [Commits](https://github.com/oxidecomputer/expectorate/compare/v1.0.7...v1.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump clap from 4.4.4 to 4.4.5 in /src/wasm-lib (#711)

Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.4...v4.4.5)

---
updated-dependencies:
- dependency-name: clap
  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>

refactor cleanup

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

updates

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

updates

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

type improvements

* use new sketchmode no camera

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

* js working better

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

* start of negative planes

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

* tests and neg

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

* updates

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

* images

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

* norma;s

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

* better initial load of planes

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

* ts

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

* updates

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

* fixes

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

* updates

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

* fixes

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

* fix tsc

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

* fix edit sketch

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

* add regression test for 2d solid issue

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

* updates

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

* show planes

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

* fix clippy

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

* fix tests

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

* canecel in progress

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

* fix ci as well

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-10-05 14:27:48 -07:00
59223279b7 Type Error: Type error engineConnection.ts (#786)
* TypeError Fix

* removed not needed console log

* took into account adamchalmers feedback and fixed cl type error

* pretty up

* Irev-dev feedback fixes issue
2023-10-05 16:18:50 +11:00
8a4e717565 Use absolute URLs to settings to avoid relative URL edge cases (#781)
* Create useAbsoluteFilePath hook

* Fix "report bug" link on Error page

* Replace relative URL to settings with absolute URL

* Replace other absolute file URLs to use common hook

* Use named const for default browser file name

* Fix UI tests that now rely on useRouteLoaderData()

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

---------

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-10-04 18:00:55 -04:00
80b542ca18 Allow Vertical Scroll Wheel to affect Horizontal Scroll for Toolbar (#780) 2023-10-04 09:35:50 -07:00
e4bfc863ea Bump re-resizable from 6.9.9 to 6.9.11 (#777)
Bumps [re-resizable](https://github.com/bokuweb/react-resizable-box) from 6.9.9 to 6.9.11.
- [Release notes](https://github.com/bokuweb/react-resizable-box/releases)
- [Changelog](https://github.com/bokuweb/re-resizable/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bokuweb/react-resizable-box/commits)

---
updated-dependencies:
- dependency-name: re-resizable
  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-10-04 10:08:10 -05:00
77ef255de4 Bump tauri from 1.5.0 to 1.5.1 in /src-tauri (#761)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.5...tauri-v1.5.1)

---
updated-dependencies:
- dependency-name: tauri
  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-10-04 09:21:58 -05:00
64c3841079 Bump tauri-plugin-fs-extra from 9b96996 to fa32d1a in /src-tauri (#776)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9b96996` to `fa32d1a`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9b96996b5a...fa32d1afa9)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 23:23:43 -05:00
c7bb6bc845 Bump reqwest from 0.11.21 to 0.11.22 in /src/wasm-lib (#775)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.21 to 0.11.22.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.21...v0.11.22)

---
updated-dependencies:
- dependency-name: reqwest
  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-10-03 23:23:18 -05:00
1af8a8c64f Cut release v0.9.5 (#772) 2023-10-03 17:35:16 -07:00
eb4776826b disable pip for firefox (#770)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:11:44 -07:00
f3dd0469d5 re-execute when we remount the app (#769)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:07:09 -07:00
deea74754d save actual file contents (#768)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:06:45 -07:00
3fd798c704 Bump @adobe/css-tools from 4.2.0 to 4.3.1 (#353)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.2.0 to 4.3.1.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2023-10-03 14:12:58 -05:00
cc9eaf2991 Bump get-func-name from 2.0.0 to 2.0.2 (#730)
Bumps [get-func-name](https://github.com/chaijs/get-func-name) from 2.0.0 to 2.0.2.
- [Release notes](https://github.com/chaijs/get-func-name/releases)
- [Commits](https://github.com/chaijs/get-func-name/commits/v2.0.2)

---
updated-dependencies:
- dependency-name: get-func-name
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 14:12:46 -05:00
6f24031220 add mit (#765)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 11:59:57 -07:00
672bcd297f Bump toml from 0.8.1 to 0.8.2 in /src-tauri (#760)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.1 to 0.8.2.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.1...toml-v0.8.2)

---
updated-dependencies:
- dependency-name: toml
  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-10-03 07:20:21 -07:00
3bc182fe16 Bump reqwest from 0.11.20 to 0.11.21 in /src/wasm-lib (#762)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.20 to 0.11.21.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.20...v0.11.21)

---
updated-dependencies:
- dependency-name: reqwest
  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-10-03 07:20:08 -07:00
589cd39eec Cut release v0.9.4 (#759)
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-02 20:09:53 -04:00
63feebef5c add report a bug button (#758)
* add report a bug button

* format
2023-10-02 16:33:33 -07:00
65037abd9a new sample code w tangental arc (#756)
* new sample code w tangental arc

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

* line 6

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

* make sure sample code works in test

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-02 16:14:09 -07:00
97bc339a62 Bump tauri-plugin-fs-extra from 9af4c37 to 9b96996 in /src-tauri (#751)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9af4c37` to `9b96996`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9af4c3727c...9b96996b5a)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 12:27:24 -07:00
4e9a6375a5 Bump tauri-build from 1.4.1 to 1.5.0 in /src-tauri (#752)
Bumps [tauri-build](https://github.com/tauri-apps/tauri) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-build-v1.4.1...tauri-build-v1.5)

---
updated-dependencies:
- dependency-name: tauri-build
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 12:27:16 -07:00
3d19dfb800 Cut release v0.9.3 (#753) 2023-10-02 14:14:00 -04:00
d2a7b84292 Request a feature should open externally (#737)
Fixes #659
2023-10-02 13:26:23 -04:00
9e02bab155 Explicitly close the Engine Connection when ICE fails (#732)
This will avoid letting the stack think its ready until it times out,
and will hopefully help with faster retry logic in the future

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>
2023-10-02 09:50:49 -04:00
7352de5a70 Bump vitest from 0.34.1 to 0.34.6 (#747)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 0.34.1 to 0.34.6.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v0.34.6/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  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-30 12:24:39 +10:00
9797d0cb81 Fix pipe expression start (#746) 2023-09-29 22:19:20 -04:00
83907fa9db adds tangentalArc and tangentalArcTo to the stdlib (#748)
* start of tangental arc

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

* updates

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

* update docs

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

* fixes

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-29 14:41:14 -07:00
a367be4e2b add gizmo (#738)
* add gizmo

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

* updates

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

* fixups

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

* fix force ast execution

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-29 12:41:58 -07:00
056fa00adc Bump tauri from 1.4.1 to 1.5.0 in /src-tauri (#742)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.4.1...tauri-v1.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 12:03:35 -07:00
4759fb2e6f Bump postcss from 8.4.27 to 8.4.31 (#743)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.27 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.27...8.4.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 11:12:08 -07:00
45f497d9cd Bump tauri-build from 1.4.0 to 1.4.1 in /src-tauri (#741)
Bumps [tauri-build](https://github.com/tauri-apps/tauri) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-build-v1.4...tauri-build-v1.4.1)

---
updated-dependencies:
- dependency-name: tauri-build
  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-29 11:11:57 -07:00
dc61bdebdf Bump tauri-plugin-fs-extra from 3d27909 to 9af4c37 in /src-tauri (#740)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `3d27909` to `9af4c37`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](3d279094d4...9af4c3727c)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 11:11:42 -07:00
61943055e5 refactor a bit before more rust port (#739)
* partially there

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

* cleanup

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

* fixes

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

* fixups

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-29 11:11:01 -07:00
416fe0f644 Bump clap from 4.4.5 to 4.4.6 in /src/wasm-lib (#744)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.5 to 4.4.6.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.5...v4.4.6)

---
updated-dependencies:
- dependency-name: clap
  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-29 12:41:22 -04:00
708465d818 Bump kittycad from 0.2.27 to 0.2.28 in /src-tauri (#734)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.27 to 0.2.28.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.27...v0.2.28)

---
updated-dependencies:
- dependency-name: kittycad
  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-28 18:09:56 -07:00
e706fb02d6 Bump openapitor from d3e98c4 to fa0345c in /src/wasm-lib (#733)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `d3e98c4` to `fa0345c`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](d3e98c4ec0...fa0345c514)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-28 18:09:46 -07:00
1bf7daa474 Bump tauri-plugin-fs-extra from 6c7a4c0 to 3d27909 in /src-tauri (#735)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `6c7a4c0` to `3d27909`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](6c7a4c0984...3d279094d4)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-28 18:09:31 -07:00
ffc47f8f40 actually use the units the user sets (#727)
* actually use the units the user sets

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

* update kittycad lib

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

* next

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

* fix tests

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

* updates

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

* fix bug

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

* fix types

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-27 15:42:16 -07:00
768aaa84f6 Bump clap from 4.4.4 to 4.4.5 in /src/wasm-lib (#711)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.4...v4.4.5)

---
updated-dependencies:
- dependency-name: clap
  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-27 09:10:07 -07:00
f3a700eec8 Bump expectorate from 1.0.7 to 1.1.0 in /src/wasm-lib (#712)
Bumps [expectorate](https://github.com/oxidecomputer/expectorate) from 1.0.7 to 1.1.0.
- [Release notes](https://github.com/oxidecomputer/expectorate/releases)
- [Commits](https://github.com/oxidecomputer/expectorate/compare/v1.0.7...v1.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:39:16 -07:00
c853637a9a Bump thiserror from 1.0.48 to 1.0.49 in /src/wasm-lib (#721)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.48...1.0.49)

---
updated-dependencies:
- dependency-name: thiserror
  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-27 08:39:03 -07:00
9af30d9ef6 Bump kittycad from 0.2.26 to 0.2.27 in /src/wasm-lib (#722)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

---
updated-dependencies:
- dependency-name: kittycad
  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-27 08:38:49 -07:00
6164714a6b Bump openapitor from 61a1605 to d3e98c4 in /src/wasm-lib (#723)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `61a1605` to `d3e98c4`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](61a16059b3...d3e98c4ec0)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:38:37 -07:00
64ceb98eba Bump toml from 0.8.0 to 0.8.1 in /src-tauri (#724)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.0 to 0.8.1.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.8.1)

---
updated-dependencies:
- dependency-name: toml
  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-27 08:38:25 -07:00
2cbf260900 Bump tauri-plugin-fs-extra from b04bde3 to 6c7a4c0 in /src-tauri (#725)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `b04bde3` to `6c7a4c0`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](b04bde3461...6c7a4c0984)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 08:38:14 -07:00
cfaaedf602 Bump kittycad from 0.2.26 to 0.2.27 in /src-tauri (#726)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

---
updated-dependencies:
- dependency-name: kittycad
  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-27 08:37:54 -07:00
12b3717eb5 Cut release v0.9.2 (#714) 2023-09-26 20:39:05 -04:00
0bc685b0c4 Bump tungstenite from 0.20.0 to 0.20.1 in /src/wasm-lib/kcl/fuzz (#709)
Bumps [tungstenite](https://github.com/snapview/tungstenite-rs) from 0.20.0 to 0.20.1.
- [Changelog](https://github.com/snapview/tungstenite-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/snapview/tungstenite-rs/compare/v0.20.0...v0.20.1)

---
updated-dependencies:
- dependency-name: tungstenite
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 20:49:16 -07:00
9ee032771a unused dep (#710) 2023-09-26 03:22:05 +00:00
c307ddd1b1 resize (#706)
* start of resize

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

* refactor

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

* check if 0

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

* updates

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

* updates

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

* will work w new lib

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

* new types

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

* handle resize effect

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2023-09-25 19:49:53 -07:00
a30818ff2b fixes negative args in function (#707)
* updates

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

* fixes

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

* updates

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>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-25 15:25:58 -07:00
53e763d938 fix close arc (#704)
* fix close arc

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

* much bigger radius

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-25 12:14:41 -07:00
8f74cd1d0c Bump tauri-plugin-fs-extra from 0190f68 to b04bde3 in /src-tauri (#702)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `0190f68` to `b04bde3`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](0190f68f1d...b04bde3461)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 09:33:53 -07:00
c271942897 remove errors (#703) 2023-09-25 07:28:03 +00:00
a03d09b41d Restructure tokenizer module (#700)
* Remove duplicated tests

These tests already were copied to tokeniser2.rs, so removing them doesn't affect code coverage.

* Move tokeniser to its own module

Now there's a module for tokens, and the tokenizer/lexer implementation is private within the token module.
2023-09-24 20:01:17 -05:00
2971b7752b Bump rust websocket libraries (#701)
Changelog: https://github.com/snapview/tokio-tungstenite/blob/master/CHANGELOG.md#0201
2023-09-24 21:34:31 +00:00
70e99eb00b Refactor is_code_token into a method (#699)
* Refactor is_code_token into a method

* Fix typos, use Parser as it was imported
2023-09-24 21:11:36 +00:00
5c66af59d2 New tokenizer based on winnow (#697)
* New tokenizer, using Winnow instead of regexes.

Between 1.3x and 4.4x speedup on lexer benchmarks :)

* Use dispatch instead of alt

Most of the time, if you know the first character of a token, you can narrow down its potential possible token types, instead of just trying each token type until one succeeds.

This further speeds up the lexer. Compared to main, this branch is now between 3x and 12x faster than main.
2023-09-22 21:57:39 -05:00
6dda6daeef Use separate benchmarks for lexing and parsing (#698) 2023-09-23 02:01:18 +00:00
b5387f1220 Cut release v0.9.1 (#693)
* bump to ️v0.9.1️

* update bump instructions

* readme update

* read me again

* change pr convention
2023-09-22 10:38:17 +10:00
fd5921b366 Convert the lexer to be iterative not recursive (#691)
This is often more memory-efficient (does not create a bunch of stack
frames)
2023-09-21 19:19:08 -05:00
716ad938fc stop gap for large files making editor slow (#690)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-21 16:13:22 -07:00
40136eb392 Bump kittycad from 0.2.25 to 0.2.26 in /src-tauri (#680)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.25 to 0.2.26.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.25...v0.2.26)

---
updated-dependencies:
- dependency-name: kittycad
  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-21 14:05:55 -07:00
8d2b89fcd1 Bump openapitor from 0d121f6 to 61a1605 in /src/wasm-lib (#679)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `0d121f6` to `61a1605`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](0d121f6881...61a16059b3)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:22:04 -05:00
ad9fba3390 Bump tauri-plugin-fs-extra from 76832e6 to 0190f68 in /src-tauri (#681)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `76832e6` to `0190f68`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](76832e60bf...0190f68f1d)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:21:08 -05:00
911c43af50 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src-tauri (#687)
Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

---
updated-dependencies:
- dependency-name: phonenumber
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:37 -05:00
ab4e04f6c2 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src/wasm-lib (#685)
Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

---
updated-dependencies:
- dependency-name: phonenumber
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:23 -05:00
94aef05f74 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src/wasm-lib/kcl/fuzz (#686)
Bump phonenumber in /src/wasm-lib/kcl/fuzz

Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

---
updated-dependencies:
- dependency-name: phonenumber
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:10 -05:00
d820cf2446 Tokenizer is accidentally quadratic (#689)
* Add comments and rename a function

* Typo: paran -> paren

* Use bytes, not string, for the tokenizer

* Fix typo
2023-09-21 14:18:42 -05:00
0c724c4971 Start to restructure the Engine's connection to the backend (#674)
* Start to restructure the Engine's connectio to the backend

1) Add in a tearDown stub for when the Engine is torn down. This is now
   distinct from a 'close', which will not stop connect from trying
   again. Running tearDown will mark the connection to not be retried
   and close active connections.

2) Move the retry logic out of connect and into the constructor. It will
   attempt to reconnect at the same rate as we had previously.

3) The timeout will now only close the connection, not restart it.

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>

* Don't continue on dead conn & setTimeout on init only

* Clean up extra setTimeout

* Keep track of connection timeouts and clear on close

* Fix tsc by defining Timeout

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>

* appease the format gods

---------

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>
Co-authored-by: Adam Sunderland <adam@kittycad.io>
2023-09-21 12:07:47 -04:00
b54ac4a694 improve getNodePathFromSourceRange and therefore the ast explorer aswell (#683)
improve getNodePathFromSourceRange and therefore the ast explorer as well
2023-09-21 05:40:41 +00:00
27227092b1 app stuck on blur when engine errors (#682)
* temp fix for when engine returns error

* don't add extrued to show function
2023-09-21 04:32:47 +00:00
04e1b92a5b Add a benchmark for parsing pipes-on-pipes (#678) 2023-09-21 03:13:07 +00:00
0553cd4621 tests for big files (#675)
* shit;

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

* updates

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>

* u[dates;

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

* updates

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

* still ignore the big one

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

* Add big kitt SVG to benchmarks

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Adam Chalmers <adam.s.chalmers@gmail.com>
2023-09-20 19:35:37 -07:00
61a0c88af4 Add IDE dirs to .gitignore (#676) 2023-09-21 02:03:09 +00:00
d5b0544437 Bump tauri-plugin-fs-extra from 5b814f5 to 76832e6 in /src-tauri (#657)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `5b814f5` to `76832e6`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](5b814f56e6...76832e60bf)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-20 18:47:38 -07:00
6cc8af5c23 make stdlib functions async (#672)
* wip

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

updates

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

updates

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

fixes

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

closer

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>

* closer

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

* closer

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

* compiles

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

* connection

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

* fixes

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

* fix wasm

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

* timeout

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

* remove the drop

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

* drop handle

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

* updates

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

* fixes

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

* fix

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-20 18:27:08 -07:00
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
163 changed files with 16083 additions and 5425 deletions

View File

@ -15,6 +15,9 @@ on:
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo build
jobs:
cargobuild:

View File

@ -15,6 +15,9 @@ on:
- '**/rust-toolchain.toml'
- '**.rs'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo clippy
jobs:
cargoclippy:
@ -54,4 +57,4 @@ jobs:
- name: Run clippy
run: |
cd "${{ matrix.dir }}"
cargo clippy --all --tests -- -D warnings
cargo clippy --all --tests --benches -- -D warnings

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

@ -0,0 +1,40 @@
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
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
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

@ -18,6 +18,9 @@ on:
permissions:
packages: read
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo fmt
jobs:
cargofmt:

View File

@ -17,6 +17,9 @@ on:
- .github/workflows/cargo-test.yml
workflow_dispatch:
permissions: read-all
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo test
jobs:
cargotest:
@ -58,4 +61,5 @@ jobs:
cargo nextest run --workspace --no-fail-fast -P ci
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000

View File

@ -8,6 +8,9 @@ on:
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
check-format:
runs-on: 'ubuntu-20.04'

5
.gitignore vendored
View File

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

View File

@ -7,3 +7,6 @@ coverage
target
src/wasm-lib/pkg
src/wasm-lib/kcl/bindings
# XState generated files
src/machines/modelingMachine.typegen.ts

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2023 The KittyCAD Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -88,6 +88,13 @@ yarn test
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
For running the rust (not tauri rust though) only, you can
```bash
cd src/wasm-lib
cargo test
```
but you will need to have install ffmpeg prior to.
## Tauri
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
@ -123,13 +130,24 @@ Before you submit a contribution PR to this repo, please ensure that:
## Release a new version
1. Bump the versions in the .json files by creating a `Bump to v{x}.{y}.{z}` PR, committing the changes from
1. Bump the versions in the .json files by creating a `Cut release v{x}.{y}.{z}` PR, committing the changes from
```bash
VERSION=x.y.z yarn run bump-jsons
```
The PR may serve as a place to discuss the human-readable changelog and extra QA.
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following
```typescript
console.log(
'- ' +
Array.from(
document.querySelectorAll('[data-hovercard-type="pull_request"]')
).map((a) => `[${a.innerText}](${a.href})`).join(`
- `)
)
```
grab the md list and delete any that are older than the last bump
2. Merge the PR

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.8.2",
"version": "0.10.0",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.9.0",
@ -10,7 +10,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.13",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.37",
"@kittycad/lib": "^0.0.43",
"@lezer/javascript": "^1.4.7",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
@ -25,13 +25,15 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@uiw/react-codemirror": "^4.21.13",
"@xstate/inspect": "^0.8.0",
"@xstate/react": "^3.2.2",
"crypto-js": "^4.1.1",
"debounce-promise": "^3.1.2",
"formik": "^2.4.3",
"fuse.js": "^6.6.2",
"http-server": "^14.1.1",
"json-rpc-2.0": "^1.6.0",
"re-resizable": "^6.9.9",
"re-resizable": "^6.9.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
@ -47,7 +49,7 @@
"ts-node": "^10.9.1",
"typescript": "^4.4.2",
"uuid": "^9.0.0",
"vitest": "^0.34.1",
"vitest": "^0.34.6",
"vscode-jsonrpc": "^8.1.0",
"vscode-languageserver-protocol": "^3.17.3",
"wasm-pack": "^0.12.1",
@ -65,12 +67,13 @@
"pretest": "yarn remove-importmeta",
"test": "vitest --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",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src",
"fmt-check": "prettier --check ./src",
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --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": "(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\"",
@ -101,7 +104,7 @@
"@babel/preset-env": "^7.22.9",
"@tauri-apps/cli": "^1.3.1",
"@types/crypto-js": "^4.1.1",
"@types/debounce": "^1.2.1",
"@types/debounce-promise": "^3.1.6",
"@types/isomorphic-fetch": "^0.0.36",
"@types/react-modal": "^3.16.0",
"@types/uuid": "^9.0.1",
@ -115,7 +118,7 @@
"eslint-plugin-css-modules": "^2.11.0",
"happy-dom": "^10.8.0",
"husky": "^8.0.3",
"postcss": "^8.4.19",
"postcss": "^8.4.31",
"prettier": "^2.8.0",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.2.4",

198
src-tauri/Cargo.lock generated
View File

@ -84,7 +84,7 @@ dependencies = [
"tauri-build",
"tauri-plugin-fs-extra",
"tokio",
"toml 0.8.0",
"toml 0.8.2",
]
[[package]]
@ -155,6 +155,20 @@ version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[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]]
name = "bincode"
version = "1.3.3"
@ -1295,7 +1309,21 @@ checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
dependencies = [
"log",
"mac",
"markup5ever",
"markup5ever 0.10.1",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "html5ever"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
dependencies = [
"log",
"mac",
"markup5ever 0.11.0",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -1630,13 +1658,14 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.25"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7"
checksum = "539b323537b877fc8dd130362b8f1af9af8051c19208bb8bfd816ab7c330f2bb"
dependencies = [
"anyhow",
"async-trait",
"base64 0.21.2",
"bigdecimal",
"bytes",
"chrono",
"data-encoding",
@ -1671,7 +1700,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
dependencies = [
"cssparser",
"html5ever",
"html5ever 0.25.2",
"matches",
"selectors",
]
[[package]]
name = "kuchikiki"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8"
dependencies = [
"cssparser",
"html5ever 0.26.0",
"indexmap 1.9.3",
"matches",
"selectors",
]
@ -1688,6 +1730,12 @@ version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "libm"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
[[package]]
name = "line-wrap"
version = "0.1.1"
@ -1727,9 +1775,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.18"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
dependencies = [
"serde",
]
@ -1781,7 +1829,21 @@ checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
dependencies = [
"log",
"phf 0.8.0",
"phf_codegen",
"phf_codegen 0.8.0",
"string_cache",
"string_cache_codegen",
"tendril",
]
[[package]]
name = "markup5ever"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
dependencies = [
"log",
"phf 0.10.1",
"phf_codegen 0.10.0",
"string_cache",
"string_cache_codegen",
"tendril",
@ -1959,6 +2021,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-bigint"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@ -1982,9 +2055,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
]
@ -2342,6 +2415,16 @@ dependencies = [
"phf_shared 0.8.0",
]
[[package]]
name = "phf_codegen"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
@ -2410,9 +2493,9 @@ dependencies = [
[[package]]
name = "phonenumber"
version = "0.3.2+8.13.9"
version = "0.3.3+8.13.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34749f64ea9d76f10cdc8a859588b57775f59177c7dd91f744d620bd62982d6f"
checksum = "635f3e6288e4f01c049d89332a031bd74f25d64b6fb94703ca966e819488cd06"
dependencies = [
"bincode",
"either",
@ -2425,6 +2508,7 @@ dependencies = [
"regex-cache",
"serde",
"serde_derive",
"strum",
"thiserror",
]
@ -3021,10 +3105,11 @@ dependencies = [
[[package]]
name = "schemars"
version = "0.8.13"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
dependencies = [
"bigdecimal",
"bytes",
"chrono",
"dyn-clone",
@ -3037,9 +3122,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "0.8.13"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
dependencies = [
"proc-macro2",
"quote",
@ -3105,7 +3190,7 @@ dependencies = [
"log",
"matches",
"phf 0.8.0",
"phf_codegen",
"phf_codegen 0.8.0",
"precomputed-hash",
"servo_arc",
"smallvec",
@ -3458,6 +3543,28 @@ dependencies = [
"syn 2.0.33",
]
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.109",
]
[[package]]
name = "syn"
version = "1.0.109"
@ -3605,9 +3712,9 @@ dependencies = [
[[package]]
name = "tauri"
version = "1.4.1"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e"
checksum = "0238c5063bf9613054149a1b6bce4935922e532b7d8211f36989a490a79806be"
dependencies = [
"anyhow",
"base64 0.21.2",
@ -3661,12 +3768,13 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d2edd6a259b5591c8efdeb9d5702cb53515b82a6affebd55c7fd6d3a27b7d1b"
checksum = "defbfc551bd38ab997e5f8e458f87396d2559d05ce32095076ad6c30f7fc5f9c"
dependencies = [
"anyhow",
"cargo_toml",
"dirs-next",
"heck 0.4.1",
"json-patch",
"semver",
@ -3674,13 +3782,14 @@ dependencies = [
"serde_json",
"tauri-utils",
"tauri-winres",
"walkdir",
]
[[package]]
name = "tauri-codegen"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54ad2d49fdeab4a08717f5b49a163bdc72efc3b1950b6758245fcde79b645e1a"
checksum = "7b3475e55acec0b4a50fb96435f19631fb58cbcd31923e1a213de5c382536bbb"
dependencies = [
"base64 0.21.2",
"brotli",
@ -3704,9 +3813,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eb12a2454e747896929338d93b0642144bb51e0dddbb36e579035731f0d76b7"
checksum = "613740228de92d9196b795ac455091d3a5fbdac2654abb8bb07d010b62ab43af"
dependencies = [
"heck 0.4.1",
"proc-macro2",
@ -3719,7 +3828,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs-extra"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#5b814f56e6368fdec46c4ddb04a07e0923ff995a"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#9b20f28d747f6ec3ba5a80bfcd5edc1d573b4c90"
dependencies = [
"log",
"serde",
@ -3730,9 +3839,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "0.14.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "108683199cb18f96d2d4134187bb789964143c845d2d154848dda209191fd769"
checksum = "07f8e9e53e00e9f41212c115749e87d5cd2a9eebccafca77a19722eeecd56d43"
dependencies = [
"gtk",
"http",
@ -3751,9 +3860,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "0.14.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7aa256a1407a3a091b5d843eccc1a5042289baf0a43d1179d9f0fcfea37c1b"
checksum = "8141d72b6b65f2008911e9ef5b98a68d1e3413b7a1464e8f85eb3673bb19a895"
dependencies = [
"cocoa",
"gtk",
@ -3771,19 +3880,20 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84"
checksum = "34d55e185904a84a419308d523c2c6891d5e2dbcee740c4997eb42e75a7b0f46"
dependencies = [
"brotli",
"ctor",
"dunce",
"glob",
"heck 0.4.1",
"html5ever",
"html5ever 0.26.0",
"infer",
"json-patch",
"kuchiki",
"kuchikiki",
"log",
"memchr",
"phf 0.10.1",
"proc-macro2",
@ -3897,9 +4007,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.32.0"
version = "1.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
dependencies = [
"backtrace",
"bytes",
@ -3968,14 +4078,14 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e"
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.20.0",
"toml_edit 0.20.2",
]
[[package]]
@ -4002,9 +4112,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.20.0"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [
"indexmap 2.0.0",
"serde",
@ -4799,9 +4909,9 @@ dependencies = [
[[package]]
name = "wry"
version = "0.24.3"
version = "0.24.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33748f35413c8a98d45f7a08832d848c0c5915501803d1faade5a4ebcd258cea"
checksum = "88ef04bdad49eba2e01f06e53688c8413bd6a87b0bc14b72284465cf96e3578e"
dependencies = [
"base64 0.13.1",
"block",
@ -4813,7 +4923,7 @@ dependencies = [
"gio",
"glib",
"gtk",
"html5ever",
"html5ever 0.25.2",
"http",
"kuchiki",
"libc",

View File

@ -12,18 +12,18 @@ rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.4.0", features = [] }
tauri-build = { version = "1.5.0", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.25"
kittycad = "0.2.31"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "1.4.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
tauri = { version = "1.5.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" }
tokio = { version = "1.32.0", features = ["time"] }
toml = "0.8.0"
tokio = { version = "1.33.0", features = ["time"] }
toml = "0.8.2"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "kittycad-modeling",
"version": "0.8.2"
"version": "0.10.0"
},
"tauri": {
"allowlist": {

View File

@ -1,9 +1,16 @@
import { render, screen } from '@testing-library/react'
import { App } from './App'
import { describe, test, vi } from 'vitest'
import { BrowserRouter } from 'react-router-dom'
import {
Route,
RouterProvider,
createMemoryRouter,
createRoutesFromElements,
} from 'react-router-dom'
import { GlobalStateProvider } from './components/GlobalStateProvider'
import CommandBarProvider from 'components/CommandBar'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { BROWSER_FILE_NAME } from 'Router'
let listener: ((rect: any) => void) | undefined = undefined
;(global as any).ResizeObserver = class ResizeObserver {
@ -24,7 +31,7 @@ describe('App tests', () => {
>
return {
...actual,
useParams: () => ({ id: 'new' }),
useParams: () => ({ id: BROWSER_FILE_NAME }),
useLoaderData: () => ({ code: null }),
}
})
@ -41,12 +48,26 @@ describe('App tests', () => {
})
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return (
<BrowserRouter>
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
// We have to use a memory router in the testing environment,
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
const router = createMemoryRouter(
createRoutesFromElements(
<Route
path="/file/:id"
element={
<CommandBarProvider>
<GlobalStateProvider>
<ModelingMachineProvider>{children}</ModelingMachineProvider>
</GlobalStateProvider>
</CommandBarProvider>
}
/>
),
{
initialEntries: ['/file/new'],
initialIndex: 0,
}
)
return <RouterProvider router={router} />
}

View File

@ -1,4 +1,4 @@
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
import { useEffect, useCallback, MouseEventHandler } from 'react'
import { DebugPanel } from './components/DebugPanel'
import { v4 as uuidv4 } from 'uuid'
import { PaneType, useStore } from './useStore'
@ -29,46 +29,33 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
import { CodeMenu } from 'components/CodeMenu'
import { TextEditor } from 'components/TextEditor'
import { Themes, getSystemTheme } from 'lib/theme'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
import { engineCommandManager } from './lang/std/engineConnection'
import { kclManager } from 'lang/KclSinglton'
import { useModelingContext } from 'hooks/useModelingContext'
export function App() {
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
const streamRef = useRef<HTMLDivElement>(null)
useHotKeyListener()
const {
setCode,
engineCommandManager,
buttonDownInStream,
openPanes,
setOpenPanes,
didDragInStream,
streamDimensions,
guiMode,
setGuiMode,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
setCode: s.setCode,
engineCommandManager: s.engineCommandManager,
buttonDownInStream: s.buttonDownInStream,
openPanes: s.openPanes,
setOpenPanes: s.setOpenPanes,
didDragInStream: s.didDragInStream,
streamDimensions: s.streamDimensions,
executeAst: s.executeAst,
}))
const {
auth: {
context: { token },
},
settings: {
context: { showDebugPanel, onboardingStatus, cameraControls, theme },
},
} = useGlobalStateContext()
const { settings } = useGlobalStateContext()
const { showDebugPanel, onboardingStatus, cameraControls, theme } =
settings?.context || {}
const { state, send } = useModelingContext()
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
@ -85,50 +72,7 @@ export function App() {
useHotkeys('shift + l', () => togglePane('logs'))
useHotkeys('shift + e', () => togglePane('kclErrors'))
useHotkeys('shift + d', () => togglePane('debug'))
useHotkeys('esc', () => {
if (guiMode.mode === 'sketch') {
if (guiMode.sketchMode === 'selectFace') return
if (guiMode.sketchMode === 'sketchEdit') {
// TODO: share this with Toolbar's "Exit sketch" button
// exiting sketch should be done consistently across all exits
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
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 {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'select',
},
})
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: guiMode.rotation,
position: guiMode.position,
pathToNode: guiMode.pathToNode,
pathId: guiMode.pathId,
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
})
}
} else {
setGuiMode({ mode: 'default' })
}
})
useHotkeys('esc', () => send('Cancel'))
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
(p) => p === onboardingStatus
@ -142,21 +86,20 @@ export function App() {
// on mount, and overwrite any locally-stored code
useEffect(() => {
if (isTauri() && loadedCode !== null) {
setCode(loadedCode)
kclManager.setCode(loadedCode)
}
return () => {
// Clear code on unmount if in desktop app
if (isTauri()) {
setCode('')
kclManager.setCode('')
}
}
}, [loadedCode, setCode])
}, [loadedCode])
useSetupEngineManager(streamRef, token)
useEngineConnectionSubscriptions()
const debounceSocketSend = throttle<EngineCommand>((message) => {
engineCommandManager?.sendSceneCommand(message)
engineCommandManager.sendSceneCommand(message)
}, 16)
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
e.nativeEvent.preventDefault()
@ -170,10 +113,7 @@ export function App() {
const newCmdId = uuidv4()
if (buttonDownInStream === undefined) {
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any)
) {
if (state.matches('Sketch.Line Tool')) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
@ -193,7 +133,7 @@ export function App() {
})
}
} else {
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
if (state.matches('Sketch.Move Tool')) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
@ -216,7 +156,6 @@ export function App() {
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
interaction = 'zoom'
} else {
console.log('none')
return
}
@ -234,9 +173,8 @@ export function App() {
return (
<div
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none"
className="relative h-full flex flex-col"
onMouseMove={handleMouseMove}
ref={streamRef}
>
<AppHeader
className={

View File

@ -3,10 +3,8 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
// Wrapper around protected routes, used in src/Router.tsx
export const Auth = ({ children }: React.PropsWithChildren) => {
const {
auth: { state },
} = useGlobalStateContext()
const isLoggingIn = state.matches('checkIfLoggedIn')
const { auth } = useGlobalStateContext()
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
return isLoggingIn ? (
<Loading>Loading KittyCAD Modeling App...</Loading>

View File

@ -40,6 +40,8 @@ import { ContextFrom } from 'xstate'
import CommandBarProvider from 'components/CommandBar'
import { TEST, VITE_KC_SENTRY_DSN } from './env'
import * as Sentry from '@sentry/react'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { KclContextProvider } from 'lang/KclSinglton'
if (VITE_KC_SENTRY_DSN && !TEST) {
Sentry.init({
@ -94,6 +96,8 @@ export const paths = {
) as typeof onboardingPaths,
}
export const BROWSER_FILE_NAME = 'new'
export type IndexLoaderData = {
code: string | null
project?: ProjectWithEntryPointMetadata
@ -129,7 +133,9 @@ const router = createBrowserRouter(
{
path: paths.INDEX,
loader: () =>
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
isTauri()
? redirect(paths.HOME)
: redirect(paths.FILE + '/' + BROWSER_FILE_NAME),
errorElement: <ErrorPage />,
},
{
@ -137,7 +143,11 @@ const router = createBrowserRouter(
element: (
<Auth>
<Outlet />
<App />
<KclContextProvider>
<ModelingMachineProvider>
<App />
</ModelingMachineProvider>
</KclContextProvider>
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth>
),
@ -167,7 +177,7 @@ const router = createBrowserRouter(
)
}
if (params.id && params.id !== 'new') {
if (params.id && params.id !== BROWSER_FILE_NAME) {
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
const entrypoint_metadata = await metadata(
@ -212,7 +222,7 @@ const router = createBrowserRouter(
),
loader: async () => {
if (!isTauri()) {
return redirect(paths.FILE + '/new')
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
}
const fetchedStorage = localStorage?.getItem(SETTINGS_PERSIST_KEY)
const persistedSettings = JSON.parse(fetchedStorage || '{}') as Partial<

View File

@ -1,23 +1,26 @@
import { useStore, toolTips, ToolTip } from './useStore'
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert'
import { RemoveConstrainingValues } from './components/Toolbar/RemoveConstrainingValues'
import { EqualLength } from './components/Toolbar/EqualLength'
import { EqualAngle } from './components/Toolbar/EqualAngle'
import { Intersect } from './components/Toolbar/Intersect'
import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance'
import { SetAngleLength } from './components/Toolbar/setAngleLength'
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
import { Fragment, useEffect } from 'react'
// import { HorzVert } from './components/Toolbar/HorzVert'
// import { RemoveConstrainingValues } from './components/Toolbar/RemoveConstrainingValues'
// import { EqualLength } from './components/Toolbar/EqualLength'
// import { EqualAngle } from './components/Toolbar/EqualAngle'
// import { Intersect } from './components/Toolbar/Intersect'
// import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance'
// import { SetAngleLength } from './components/Toolbar/setAngleLength'
// import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
// import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
import { Fragment, WheelEvent, useRef, useMemo } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
import { Popover, Transition } from '@headlessui/react'
import styles from './Toolbar.module.css'
import { v4 as uuidv4 } from 'uuid'
import { useAppMode } from 'hooks/useAppMode'
import { isCursorInSketchCommandRange } from 'lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { engineCommandManager } from './lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager } from 'lang/KclSinglton'
export const sketchButtonClassnames = {
background:
@ -43,222 +46,140 @@ const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
}
export const Toolbar = () => {
const {
setGuiMode,
guiMode,
selectionRanges,
ast,
updateAst,
programMemory,
engineCommandManager,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
ast: s.ast,
updateAst: s.updateAst,
programMemory: s.programMemory,
engineCommandManager: s.engineCommandManager,
executeAst: s.executeAst,
}))
useAppMode()
const { state, send, context } = useModelingContext()
const toolbarButtonsRef = useRef<HTMLSpanElement>(null)
const pathId = useMemo(
() =>
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
context.selectionRanges
),
[engineCommandManager.artifactMap, context.selectionRanges]
)
useEffect(() => {
console.log('guiMode', guiMode)
}, [guiMode])
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
const span = toolbarButtonsRef.current
if (!span) {
return
}
span.scrollLeft = span.scrollLeft += ev.deltaY
}
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
return (
<span className={styles.toolbarButtons + ' ' + className}>
{guiMode.mode === 'default' && (
<span
ref={toolbarButtonsRef}
onWheel={handleToolbarButtonsWheelEvent}
className={styles.toolbarButtons + ' ' + className}
>
{state.nextEvents.includes('Enter sketch') && (
<button
onClick={() => {
setGuiMode({
mode: 'sketch',
sketchMode: 'selectFace',
})
}}
onClick={() => send({ type: 'Enter sketch' })}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Start Sketch
</button>
)}
{guiMode.mode === 'canEditExtrude' && (
{state.nextEvents.includes('Enter sketch') && pathId && (
<button
onClick={() => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst } = sketchOnExtrudedFace(
ast,
pathToNode,
programMemory
)
updateAst(modifiedAst, true)
}}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Sketch on Face
</button>
)}
{guiMode.mode === 'canEditSketch' && (
<button
onClick={() => {
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
setGuiMode({
mode: 'sketch',
sketchMode: 'enterSketchEdit',
pathToNode: pathToNode,
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathId: guiMode.pathId,
})
}}
onClick={() => send({ type: 'Enter sketch' })}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Edit Sketch
</button>
)}
{guiMode.mode === 'canEditSketch' && (
<>
<button
onClick={() => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast,
pathToNode
)
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}}
className="group"
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
</button>
<button
onClick={() => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast,
pathToNode,
false
)
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}}
className="group"
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude as new
</button>
</>
)}
{guiMode.mode === 'sketch' && (
<button
onClick={() => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' })
executeAst()
}}
className="group"
>
<ActionIcon
icon="exit"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Exit sketch
{state.nextEvents.includes('Cancel') && !state.matches('idle') && (
<button onClick={() => send({ type: 'Cancel' })} className="group">
<ActionIcon icon="exit" className="!p-0.5" size="md" />
Exit Sketch
</button>
)}
{toolTips
.filter(
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
(sketchFnName) => ['sketch_line', 'move'].includes(sketchFnName)
)
.map((sketchFnName) => {
if (
guiMode.mode !== 'sketch' ||
!('isTooltip' in guiMode || guiMode.sketchMode === 'sketchEdit')
{state.matches('Sketch') && !state.matches('idle') && (
<button
onClick={() =>
state.matches('Sketch.Line Tool')
? send('CancelSketch')
: send('Equip tool')
}
className={
'group ' +
(state.matches('Sketch.Line Tool')
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
}
>
<ActionIcon icon="line" className="!p-0.5" size="md" />
Line
</button>
)}
{state.matches('Sketch') && (
<button
onClick={() =>
state.matches('Sketch.Move Tool')
? send('CancelSketch')
: send('Equip move tool')
}
className={
'group ' +
(state.matches('Sketch.Move Tool')
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
}
>
<ActionIcon icon="move" className="!p-0.5" size="md" />
Move
</button>
)}
{state.matches('Sketch.SketchIdle') &&
state.nextEvents
.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
)
return null
return (
.map((eventName) => (
<button
key={sketchFnName}
onClick={() => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool:
guiMode.sketchMode === sketchFnName
? 'select'
: (sketchFnName as any),
},
})
setGuiMode({
...guiMode,
...(guiMode.sketchMode === sketchFnName
? {
sketchMode: 'sketchEdit',
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
}
: {
sketchMode: sketchFnName,
waitingFirstClick: true,
isTooltip: true,
pathId: guiMode.pathId,
}),
})
}}
className={
'group ' +
(guiMode.sketchMode === sketchFnName
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
key={eventName}
onClick={() => send(eventName)}
className="group"
disabled={
!state.nextEvents
.filter((event) => state.can(event as any))
.includes(eventName)
}
title={eventName}
>
<ActionIcon
icon={sketchFnName.includes('line') ? 'line' : 'move'}
className="!p-0.5"
icon={'line'} // TODO
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{sketchFnLabels[sketchFnName]}
{eventName
.replace('Make segment ', '')
.replace('Constrain ', '')}
</button>
)
})}
<HorzVert horOrVert="horizontal" />
))}
{state.matches('idle') && (
<button
onClick={() => send('extrude intent')}
disabled={!state.can('extrude intent')}
className="group"
title={
state.can('extrude intent')
? 'extrude'
: 'sketches need to be closed, or not already extruded'
}
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
</button>
)}
{/* <HorzVert horOrVert="horizontal" />
<HorzVert horOrVert="vertical" />
<EqualLength />
<EqualAngle />
@ -274,16 +195,20 @@ export const Toolbar = () => {
<SetAngleLength angleOrLength="setLength" />
<Intersect />
<RemoveConstrainingValues />
<SetAngleBetween />
<SetAngleBetween /> */}
</span>
)
}
return (
<Popover className={styles.toolbarWrapper + ' ' + guiMode.mode}>
<Popover
className={
styles.toolbarWrapper + state.matches('Sketch') ? ' sketch' : ''
}
>
<div className={styles.toolbar}>
<span className={styles.toolbarCap + ' ' + styles.label}>
{guiMode.mode === 'sketch' ? '2D' : '3D'}
{state.matches('Sketch') ? '2D' : '3D'}
</span>
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
<ToolbarButtons />
@ -319,7 +244,7 @@ export const Toolbar = () => {
<p
className={`${styles.toolbarCap} ${styles.label} !self-center rounded-r-full w-fit`}
>
You're in {guiMode.mode === 'sketch' ? '2D' : '3D'}
You're in {state.matches('Sketch') ? '2D' : '3D'}
</p>
<Popover.Button className="p-2 flex items-center justify-center rounded-sm bg-chalkboard-20 text-chalkboard-110 dark:bg-chalkboard-70 dark:text-chalkboard-20 border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-60">
<FontAwesomeIcon icon={faX} className="w-4 h-4" />

View File

@ -23,10 +23,7 @@ type ActionButtonAsLink = BaseActionButtonProps &
}
type ActionButtonAsExternal = BaseActionButtonProps &
Omit<
React.AnchorHTMLAttributes<HTMLAnchorElement>,
keyof BaseActionButtonProps
> & {
Omit<LinkProps, keyof BaseActionButtonProps> & {
Element: 'externalLink'
}
@ -69,12 +66,17 @@ export const ActionButton = (props: ActionButtonProps) => {
)
}
case 'externalLink': {
const { Element, icon, children, className, ...rest } = props
const { Element, to, icon, children, className, ...rest } = props
return (
<a className={classNames} {...rest}>
<Link
to={to || paths.INDEX}
className={classNames}
{...rest}
target="_blank"
>
{icon && <ActionIcon {...icon} />}
{children}
</a>
</Link>
)
}
default: {

View File

@ -20,11 +20,8 @@ export const AppHeader = ({
className = '',
enableMenu = false,
}: AppHeaderProps) => {
const {
auth: {
context: { user },
},
} = useGlobalStateContext()
const { auth } = useGlobalStateContext()
const user = auth?.context?.user
return (
<header

View File

@ -1,18 +1,18 @@
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager } from 'lang/KclSinglton'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { useEffect, useRef, useState } from 'react'
import { useStore } from 'useStore'
export function AstExplorer() {
const { ast, setHighlightRange, selectionRanges } = useStore((s) => ({
ast: s.ast,
setHighlightRange: s.setHighlightRange,
selectionRanges: s.selectionRanges,
}))
const setHighlightRange = useStore((s) => s.setHighlightRange)
const { context } = useModelingContext()
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections?.[0]?.range
// TODO maybe need to have callback to make sure it stays in sync
kclManager.ast,
context.selectionRanges.codeBasedSelections?.[0]?.range
)
const node = getNodeFromPath(ast, pathToNode).node
const node = getNodeFromPath(kclManager.ast, pathToNode).node
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
return (
@ -46,7 +46,11 @@ export function AstExplorer() {
}}
>
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
<DisplayObj obj={ast} filterKeys={filterKeys} node={node} />
<DisplayObj
obj={kclManager.ast}
filterKeys={filterKeys}
node={node}
/>
</pre>
</div>
</div>
@ -84,10 +88,8 @@ function DisplayObj({
filterKeys: string[]
node: any
}) {
const { setHighlightRange, setCursor2 } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
setCursor2: s.setCursor2,
}))
const setHighlightRange = useStore((s) => s.setHighlightRange)
const { send } = useModelingContext()
const ref = useRef<HTMLPreElement>(null)
const [hasCursor, setHasCursor] = useState(false)
const [isCollapsed, setIsCollapsed] = useState(false)
@ -118,7 +120,16 @@ function DisplayObj({
setHighlightRange([obj?.start || 0, obj.end])
}}
onClick={(e) => {
setCursor2({ type: 'default', range: [obj?.start || 0, obj.end || 0] })
send({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: {
type: 'default',
range: [obj?.start || 0, obj.end || 0],
},
},
})
e.stopPropagation()
}}
>

View File

@ -1,7 +1,5 @@
import { useEffect, useState, useRef } from 'react'
import { parser_wasm } from '../lang/abstractSyntaxTree'
import { BinaryPart, Value } from '../lang/abstractSyntaxTreeTypes'
import { executor } from '../lang/executor'
import { parse, BinaryPart, Value, executor } from '../lang/wasm'
import {
createIdentifier,
createLiteral,
@ -9,7 +7,9 @@ import {
findUniqueName,
} from '../lang/modifyAst'
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
import { useStore } from '../useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSinglton'
import { useModelingContext } from 'hooks/useModelingContext'
export const AvailableVars = ({
onVarClick,
@ -92,14 +92,9 @@ export function useCalc({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { ast, programMemory, selectionRange, engineCommandManager } = useStore(
(s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
engineCommandManager: s.engineCommandManager,
})
)
const { programMemory } = useKclContext()
const { context } = useModelingContext()
const selectionRange = context.selectionRanges.codeBasedSelections[0].range
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
@ -119,9 +114,7 @@ export function useCalc({
inputRef.current &&
inputRef.current.setSelectionRange(0, String(value).length)
}, 100)
if (ast) {
setNewVariableName(findUniqueName(ast, valueName))
}
setNewVariableName(findUniqueName(kclManager.ast, valueName))
}, [])
useEffect(() => {
@ -134,21 +127,30 @@ export function useCalc({
}, [newVariableName])
useEffect(() => {
if (!ast || !programMemory || !selectionRange) return
const varInfo = findAllPreviousVariables(ast, programMemory, selectionRange)
if (!programMemory || !selectionRange) return
const varInfo = findAllPreviousVariables(
kclManager.ast,
programMemory,
selectionRange
)
setAvailableVarInfo(varInfo)
}, [ast, programMemory, selectionRange])
}, [kclManager.ast, programMemory, selectionRange])
useEffect(() => {
if (!engineCommandManager) return
try {
const code = `const __result__ = ${value}\nshow(__result__)`
const ast = parser_wasm(code)
const ast = parse(code)
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})
executor(ast, _programMem, engineCommandManager).then((programMemory) => {
executor(
ast,
_programMem,
engineCommandManager,
kclManager.defaultPlanes
).then((programMemory) => {
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&

View File

@ -5,16 +5,13 @@ import {
faEllipsis,
} from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from './ActionIcon'
import { useStore } from 'useStore'
import styles from './CodeMenu.module.css'
import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { editorShortcutMeta } from './TextEditor'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { kclManager } from 'lang/KclSinglton'
export const CodeMenu = ({ children }: PropsWithChildren) => {
const { formatCode } = useStore((s) => ({
formatCode: s.formatCode,
}))
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
useConvertToVariable()
@ -41,7 +38,10 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
</Menu.Button>
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
<Menu.Item>
<button onClick={() => formatCode()} className={styles.button}>
<button
onClick={() => kclManager.format()}
className={styles.button}
>
<span>Format code</span>
<small>{editorShortcutMeta.formatCode.display}</small>
</button>

View File

@ -1,5 +1,4 @@
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore'
import { v4 as uuidv4 } from 'uuid'
import { EngineCommand } from '../lang/std/engineConnection'
import { useState } from 'react'
@ -7,6 +6,7 @@ import { ActionButton } from '../components/ActionButton'
import { faCheck } from '@fortawesome/free-solid-svg-icons'
import { isReducedMotion } from 'lang/util'
import { AstExplorer } from './AstExplorer'
import { engineCommandManager } from '../lang/std/engineConnection'
type SketchModeCmd = Extract<
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
@ -14,9 +14,6 @@ type SketchModeCmd = Extract<
>
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
const { engineCommandManager } = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
}))
const [sketchModeCmd, setSketchModeCmd] = useState<SketchModeCmd>({
type: 'default_camera_enable_sketch_mode',
origin: { x: 0, y: 0, z: 0 },
@ -70,19 +67,18 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
className="w-16"
type="checkbox"
checked={sketchModeCmd.ortho}
onChange={(a) => {
console.log(a, (a as any).checked)
onChange={(a) =>
setSketchModeCmd({
...sketchModeCmd,
ortho: a.target.checked,
})
}}
}
/>
</div>
<ActionButton
Element="button"
onClick={() => {
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: sketchModeCmd,
cmd_id: uuidv4(),

View File

@ -47,11 +47,9 @@ export const ErrorPage = () => {
Clear storage
</ActionButton>
<ActionButton
Element="link"
Element="externalLink"
icon={{ icon: faBug }}
target="_blank"
rel="noopener noreferrer"
to="https://discord.com/channels/915388055236509727/1138967922614743060"
to="https://github.com/KittyCAD/modeling-app/issues/new"
>
Report Bug
</ActionButton>

View File

@ -1,13 +1,17 @@
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from './ActionButton'
import Modal from 'react-modal'
import React from 'react'
import { useFormik } from 'formik'
import { Models } from '@kittycad/lib'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
type OutputFormat = Models['OutputFormat_type']
type OutputTypeKey = OutputFormat['type']
type ExtractStorageTypes<T> = T extends { storage: infer U } ? U : never
type StorageUnion = ExtractStorageTypes<OutputFormat>
interface ExportButtonProps extends React.PropsWithChildren {
className?: {
@ -18,14 +22,19 @@ interface ExportButtonProps extends React.PropsWithChildren {
}
export const ExportButton = ({ children, className }: ExportButtonProps) => {
const { engineCommandManager } = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
}))
const [modalIsOpen, setIsOpen] = React.useState(false)
const {
settings: {
state: {
context: { baseUnit },
},
},
} = useGlobalStateContext()
const defaultType = 'gltf'
const [type, setType] = React.useState(defaultType)
const [type, setType] = React.useState<OutputTypeKey>(defaultType)
const defaultStorage = 'embedded'
const [storage, setStorage] = React.useState<StorageUnion>(defaultStorage)
function openModal() {
setIsOpen(true)
@ -38,7 +47,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
// Default to gltf and embedded.
const initialValues: OutputFormat = {
type: defaultType,
storage: 'embedded',
storage: defaultStorage,
presentation: 'pretty',
}
const formik = useFormik({
@ -66,7 +75,18 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
},
}
}
engineCommandManager?.sendSceneCommand({
if (values.type === 'obj' || values.type === 'stl') {
values.units = baseUnit
}
if (
values.type === 'ply' ||
values.type === 'stl' ||
values.type === 'gltf'
) {
// Set the storage type.
values.storage = storage
}
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'export',
@ -75,6 +95,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
// in the scene to export. In that case, you'd pass the IDs thru here.
entity_ids: [],
format: values,
source_unit: baseUnit,
},
cmd_id: uuidv4(),
})
@ -109,7 +130,17 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
id="type"
name="type"
onChange={(e) => {
setType(e.target.value)
setType(e.target.value as OutputTypeKey)
if (e.target.value === 'gltf') {
// Set default to embedded.
setStorage('embedded')
} else if (e.target.value === 'ply') {
// Set default to ascii.
setStorage('ascii')
} else if (e.target.value === 'stl') {
// Set default to ascii.
setStorage('ascii')
}
formik.handleChange(e)
}}
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
@ -127,10 +158,10 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
<select
id="storage"
name="storage"
onChange={formik.handleChange}
value={
'storage' in formik.values ? formik.values.storage : ''
}
onChange={(e) => {
setStorage(e.target.value as StorageUnion)
formik.handleChange(e)
}}
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
>
{type === 'gltf' && (

View File

@ -1,8 +1,8 @@
import ReactJson from 'react-json-view'
import { useEffect } from 'react'
import { useStore } from '../useStore'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { Themes } from '../lib/theme'
import { useKclContext } from 'lang/KclSinglton'
const ReactJsonTypeHack = ReactJson as any
@ -11,9 +11,7 @@ interface LogPanelProps extends CollapsiblePanelProps {
}
export const Logs = ({ theme = Themes.Light, ...props }: LogPanelProps) => {
const { logs } = useStore(({ logs }) => ({
logs,
}))
const { logs } = useKclContext()
useEffect(() => {
const element = document.querySelector('.console-tile')
if (element) {
@ -47,21 +45,19 @@ export const KCLErrors = ({
theme = Themes.Light,
...props
}: LogPanelProps) => {
const { kclErrors } = useStore(({ kclErrors }) => ({
kclErrors,
}))
const { errors } = useKclContext()
useEffect(() => {
const element = document.querySelector('.console-tile')
if (element) {
element.scrollTop = element.scrollHeight - element.clientHeight
}
}, [kclErrors])
}, [errors])
return (
<CollapsiblePanel {...props}>
<div className="h-full relative">
<div className="absolute inset-0 flex flex-col">
<ReactJsonTypeHack
src={kclErrors}
src={errors}
collapsed={1}
collapseStringsAfterLength={60}
enableClipboard={false}

View File

@ -1,7 +1,6 @@
import { processMemory } from './MemoryPanel'
import { parser_wasm } from '../lang/abstractSyntaxTree'
import { enginelessExecutor } from '../lib/testHelpers'
import { initPromise } from '../lang/rust'
import { initPromise, parse } from '../lang/wasm'
beforeAll(() => initPromise)
@ -15,18 +14,20 @@ describe('processMemory', () => {
}
const otherVar = myFn(5)
const theExtrude = startSketchAt([0, 0])
const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.4, myVar], %)
|> lineTo([-0.76, otherVar], %)
|> extrude(4, %)
const theSketch = startSketchAt([0, 0])
const theSketch = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-3.35, 0.17], %)
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)
// |> rx(90, %)
show(theExtrude, theSketch)`
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast, {
root: {},
return: null,

View File

@ -1,9 +1,9 @@
import ReactJson from 'react-json-view'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore'
import { useMemo } from 'react'
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
import { Themes } from '../lib/theme'
import { useKclContext } from 'lang/KclSinglton'
interface MemoryPanelProps extends CollapsiblePanelProps {
theme?: Exclude<Themes, Themes.System>
@ -13,9 +13,7 @@ export const MemoryPanel = ({
theme = Themes.Light,
...props
}: MemoryPanelProps) => {
const { programMemory } = useStore((s) => ({
programMemory: s.programMemory,
}))
const { programMemory } = useKclContext()
const ProcessedMemory = useMemo(
() => processMemory(programMemory),
[programMemory]
@ -50,7 +48,7 @@ export const MemoryPanel = ({
export const processMemory = (programMemory: ProgramMemory) => {
const processedMemory: any = {}
Object.keys(programMemory.root).forEach((key) => {
Object.keys(programMemory?.root || {}).forEach((key) => {
const val = programMemory.root[key]
if (typeof val.value !== 'function') {
if (val.type === 'SketchGroup') {

View File

@ -0,0 +1,355 @@
import { useMachine } from '@xstate/react'
import React, { createContext, useEffect, useRef } from 'react'
import {
AnyStateMachine,
ContextFrom,
InterpreterFrom,
Prop,
StateFrom,
assign,
} from 'xstate'
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { isCursorInSketchCommandRange } from 'lang/util'
import { engineCommandManager } from 'lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { addStartSketch } from 'lang/modifyAst'
import { roundOff } from 'lib/utils'
import { recast, parse, Program, VariableDeclarator } from 'lang/wasm'
import { getNodeFromPath } from 'lang/queryAst'
import {
addCloseToPipe,
addNewSketchLn,
compareVec2Epsilon,
} from 'lang/std/sketch'
import { kclManager } from 'lang/KclSinglton'
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
import { applyConstraintAngleBetween } from './Toolbar/SetAngleBetween'
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
import { toast } from 'react-hot-toast'
import { pathMapToSelections } from 'lang/util'
import {
dispatchCodeMirrorCursor,
setCodeMirrorCursor,
useStore,
} from 'useStore'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
context: ContextFrom<T>
send: Prop<InterpreterFrom<T>, 'send'>
}
export const ModelingMachineContext = createContext(
{} as MachineContext<typeof modelingMachine>
)
export const ModelingMachineProvider = ({
children,
}: {
children: React.ReactNode
}) => {
const { auth } = useGlobalStateContext()
const token = auth?.context?.token
const streamRef = useRef<HTMLDivElement>(null)
useSetupEngineManager(streamRef, token)
const { isShiftDown, editorView } = useStore((s) => ({
isShiftDown: s.isShiftDown,
editorView: s.editorView,
}))
// const { commands } = useCommandsContext()
// Settings machine setup
// const retrievedSettings = useRef(
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
// )
// What should we persist from modeling state? Nothing?
// const persistedSettings = Object.assign(
// settingsMachine.initialState.context,
// JSON.parse(retrievedSettings.current) as Partial<
// (typeof settingsMachine)['context']
// >
// )
const [modelingState, modelingSend] = useMachine(modelingMachine, {
// context: persistedSettings,
actions: {
'Modify AST': () => {},
'Update code selection cursors': () => {},
'show default planes': () => {
kclManager.showPlanes()
},
'create path': async () => {
const sketchUuid = uuidv4()
const proms = [
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: sketchUuid,
cmd: {
type: 'start_path',
},
}),
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: sketchUuid,
},
}),
]
await Promise.all(proms)
},
'AST start new sketch': assign((_, { data: { coords, axis } }) => {
// Something really weird must have happened for this to happen.
if (!axis) {
console.error('axis is undefined for starting a new sketch')
return {}
}
const _addStartSketch = addStartSketch(
kclManager.ast,
axis,
[roundOff(coords[0].x), roundOff(coords[0].y)],
[
roundOff(coords[1].x - coords[0].x),
roundOff(coords[1].y - coords[0].y),
]
)
const _modifiedAst = _addStartSketch.modifiedAst
const _pathToNode = _addStartSketch.pathToNode
const newCode = recast(_modifiedAst)
const astWithUpdatedSource = parse(newCode)
kclManager.executeAstMock(astWithUpdatedSource, true)
return {
sketchPathToNode: _pathToNode,
}
}),
'AST add line segment': ({ sketchPathToNode }, { data: { coords } }) => {
if (!sketchPathToNode) return
const lastCoord = coords[coords.length - 1]
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
sketchPathToNode,
'VariableDeclarator'
)
const variableName = varDec.id.name
const sketchGroup = kclManager.programMemory.root[variableName]
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
const initialCoords = sketchGroup.value[0].from
const isClose = compareVec2Epsilon(initialCoords, [
lastCoord.x,
lastCoord.y,
])
let _modifiedAst: Program
if (!isClose) {
_modifiedAst = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [lastCoord.x, lastCoord.y],
fnName: 'line',
pathToNode: sketchPathToNode,
}).modifiedAst
kclManager.executeAstMock(_modifiedAst, true)
// kclManager.updateAst(_modifiedAst, false)
} else {
_modifiedAst = addCloseToPipe({
node: kclManager.ast,
programMemory: kclManager.programMemory,
pathToNode: sketchPathToNode,
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
kclManager.executeAstMock(_modifiedAst, true)
// updateAst(_modifiedAst, true)
}
},
'sketch exit execute': () => {
kclManager.executeAst()
},
'set tool': () => {}, // TODO
'toast extrude failed': () => {
toast.error(
'Extrude failed, sketches need to be closed, or not already extruded'
)
},
'Set selection': assign(({ selectionRanges }, event) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
return { selectionRanges: setSelections.selection }
else if (setSelections.selectionType === 'otherSelection')
return {
selectionRanges: {
...selectionRanges,
otherSelections: [setSelections.selection],
},
}
else if (!editorView) return {}
else if (setSelections.selectionType === 'singleCodeCursor') {
// This DOES NOT set the `selectionRanges` in xstate context
// instead it updates/dispatches to the editor, which in turn updates the xstate context
// I've found this the best way to deal with the editor without causing an infinite loop
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
// because we want to respect the user manually placing the cursor too.
const selectionRangeTypeMap = setCodeMirrorCursor({
codeSelection: setSelections.selection,
currestSelections: selectionRanges,
editorView,
isShiftDown,
})
return {
selectionRangeTypeMap,
}
}
// This DOES NOT set the `selectionRanges` in xstate context
// same as comment above
const selectionRangeTypeMap = dispatchCodeMirrorCursor({
selections: setSelections.selection,
editorView,
})
return {
selectionRangeTypeMap,
}
}),
},
guards: {
'Selection contains axis': () => true,
'Selection contains edge': () => true,
'Selection contains face': () => true,
'Selection contains line': () => true,
'Selection contains point': () => true,
'Selection is not empty': () => true,
'Selection is one face': ({ selectionRanges }) => {
return !!isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
},
},
services: {
'Get horizontal info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
constraint: 'setHorzDistance',
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get vertical info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
constraint: 'setVertDistance',
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get angle info': async ({ selectionRanges }): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintAngleBetween({
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get length info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } = await applyConstraintAngleLength(
{ selectionRanges }
)
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
},
devTools: true,
})
useEffect(() => {
engineCommandManager.onPlaneSelected((plane_id: string) => {
if (modelingState.nextEvents.includes('Select default plane')) {
modelingSend({
type: 'Select default plane',
data: { planeId: plane_id },
})
}
})
}, [kclManager.defaultPlanes, modelingSend, modelingState.nextEvents])
// useStateMachineCommands({
// state: settingsState,
// send: settingsSend,
// commands,
// owner: 'settings',
// commandBarMeta: settingsCommandBarMeta,
// })
return (
<ModelingMachineContext.Provider
value={{
state: modelingState,
context: modelingState.context,
send: modelingSend,
}}
>
{/* TODO #818: maybe pass reff down to children/app.ts or render app.tsx directly?
since realistically it won't ever have generic children that isn't app.tsx */}
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
{children}
</div>
</ModelingMachineContext.Provider>
)
}
export default ModelingMachineProvider

View File

@ -2,6 +2,8 @@ import { fireEvent, render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { ProjectWithEntryPointMetadata } from '../Router'
import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar'
const now = new Date()
const projectWellFormed = {
@ -38,7 +40,11 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders the project name', () => {
render(
<BrowserRouter>
<ProjectSidebarMenu project={projectWellFormed} />
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu project={projectWellFormed} />
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)
@ -55,7 +61,11 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders app name if given no project', () => {
render(
<BrowserRouter>
<ProjectSidebarMenu />
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu />
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)
@ -69,7 +79,14 @@ describe('ProjectSidebarMenu tests', () => {
test('Renders as a link if set to do so', () => {
render(
<BrowserRouter>
<ProjectSidebarMenu project={projectWellFormed} renderAsLink={true} />
<CommandBarProvider>
<GlobalStateProvider>
<ProjectSidebarMenu
project={projectWellFormed}
renderAsLink={true}
/>
</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)

View File

@ -1,6 +1,6 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'
import { Value } from '../lang/abstractSyntaxTreeTypes'
import { Value } from '../lang/wasm'
import {
AvailableVars,
addToInputHelper,

View File

@ -1,6 +1,6 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'
import { Value } from '../lang/abstractSyntaxTreeTypes'
import { Value } from '../lang/wasm'
import {
AvailableVars,
addToInputHelper,

View File

@ -7,24 +7,17 @@ import {
} from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { getNormalisedCoordinates, roundOff } from '../lib/utils'
import { getNormalisedCoordinates } from '../lib/utils'
import Loading from './Loading'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
import { Models } from '@kittycad/lib'
import { addStartSketch } from 'lang/modifyAst'
import {
addCloseToPipe,
addNewSketchLn,
compareVec2Epsilon,
} from 'lang/std/sketch'
import { getNodeFromPath } from 'lang/queryAst'
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'
import { Program, VariableDeclarator, modifyAstForSketch } from 'lang/wasm'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager, useKclContext } from 'lang/KclSinglton'
export const Stream = ({ className = '' }) => {
const [isLoading, setIsLoading] = useState(true)
@ -32,37 +25,21 @@ export const Stream = ({ className = '' }) => {
const videoRef = useRef<HTMLVideoElement>(null)
const {
mediaStream,
engineCommandManager,
setButtonDownInStream,
didDragInStream,
setDidDragInStream,
streamDimensions,
isExecuting,
guiMode,
ast,
updateAst,
setGuiMode,
programMemory,
} = useStore((s) => ({
mediaStream: s.mediaStream,
engineCommandManager: s.engineCommandManager,
setButtonDownInStream: s.setButtonDownInStream,
fileId: s.fileId,
didDragInStream: s.didDragInStream,
setDidDragInStream: s.setDidDragInStream,
streamDimensions: s.streamDimensions,
isExecuting: s.isExecuting,
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
setGuiMode: s.setGuiMode,
programMemory: s.programMemory,
}))
const {
settings: {
context: { cameraControls },
},
} = useGlobalStateContext()
const { settings } = useGlobalStateContext()
const cameraControls = settings?.context?.cameraControls
const { send, state, context } = useModelingContext()
const { isExecuting } = useKclContext()
useEffect(() => {
if (
@ -73,7 +50,7 @@ export const Stream = ({ className = '' }) => {
if (!videoRef.current) return
if (!mediaStream) return
videoRef.current.srcObject = mediaStream
}, [mediaStream, engineCommandManager])
}, [mediaStream])
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
if (!videoRef.current) return
@ -106,8 +83,8 @@ export const Stream = ({ className = '' }) => {
interaction = 'zoom'
}
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
engineCommandManager?.sendSceneCommand({
if (state.matches('Sketch.Move Tool')) {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'handle_mouse_drag_start',
@ -115,13 +92,8 @@ export const Stream = ({ className = '' }) => {
},
cmd_id: newId,
})
} else if (
!(
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any)
)
) {
engineCommandManager?.sendSceneCommand({
} else if (!state.matches('Sketch.Line Tool')) {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_start',
@ -139,7 +111,7 @@ export const Stream = ({ className = '' }) => {
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
@ -176,215 +148,141 @@ export const Stream = ({ className = '' }) => {
cmd_id: newCmdId,
}
if (!didDragInStream) {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'select_with_point',
selection_type: 'add',
selected_at_window: { x, y },
},
cmd_id: uuidv4(),
})
}
if (!didDragInStream && guiMode.mode === 'default') {
if (!didDragInStream && state.matches('Sketch no face')) {
command.cmd = {
type: 'select_with_point',
selection_type: 'add',
selected_at_window: { x, y },
}
} else if (
(!didDragInStream &&
guiMode.mode === 'sketch' &&
['move', 'select'].includes(guiMode.sketchMode)) ||
(guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any))
) {
engineCommandManager.sendSceneCommand(command)
} else if (!didDragInStream && state.matches('Sketch.Line Tool')) {
command.cmd = {
type: 'mouse_click',
window: { x, y },
}
engineCommandManager.sendSceneCommand(command).then(async (resp) => {
const entities_modified = resp?.data?.data?.entities_modified
if (!entities_modified) return
if (state.matches('Sketch.Line Tool.No Points')) {
send('Add point')
} else if (state.matches('Sketch.Line Tool.Point Added')) {
const curve = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
// We need the normal for the plane we are on.
const plane = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'get_sketch_mode_plane',
},
})
const z_axis = plane.data.data.z_axis
// Get the current axis.
let currentAxis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null =
null
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
if (z_axis.z === -1) {
currentAxis = '-xy'
} else {
currentAxis = 'xy'
}
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
if (z_axis.x === -1) {
currentAxis = '-yz'
} else {
currentAxis = 'yz'
}
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
if (z_axis.y === -1) {
currentAxis = '-xz'
} else {
currentAxis = 'xz'
}
}
send({ type: 'Add point', data: { coords, axis: currentAxis } })
} else if (state.matches('Sketch.Line Tool.Segment Added')) {
const curve = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
send({ type: 'Add point', data: { coords, axis: null } })
}
})
} else if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('move' as any)
!didDragInStream &&
(state.matches('Sketch.SketchIdle') ||
state.matches('idle') ||
state.matches('awaiting selection'))
) {
command.cmd = {
type: 'select_with_point',
selected_at_window: { x, y },
selection_type: 'add',
}
engineCommandManager.sendSceneCommand(command)
} else if (!didDragInStream && state.matches('Sketch.Move Tool')) {
command.cmd = {
type: 'select_with_point',
selected_at_window: { x, y },
selection_type: 'add',
}
engineCommandManager.sendSceneCommand(command)
} else if (didDragInStream && state.matches('Sketch.Move Tool')) {
command.cmd = {
type: 'handle_mouse_drag_end',
window: { x, y },
}
}
engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
if (!(guiMode.mode === 'sketch')) return
if (guiMode.sketchMode === 'selectFace') return
// Check if the sketch group already exists.
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
guiMode.pathToNode,
'VariableDeclarator'
).node
const variableName = varDec?.id?.name
const sketchGroup = programMemory.root[variableName]
const isEditingExistingSketch =
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 (
resp?.data?.data?.entities_modified?.length &&
guiMode.waitingFirstClick &&
!isEditingExistingSketch
) {
const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: resp?.data?.data?.entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
const _addStartSketch = addStartSketch(
ast,
[roundOff(coords[0].x), roundOff(coords[0].y)],
[
roundOff(coords[1].x - coords[0].x),
roundOff(coords[1].y - coords[0].y),
]
)
const _modifiedAst = _addStartSketch.modifiedAst
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({
...guiMode,
pathToNode: _pathToNode,
waitingFirstClick: false,
})
updateAst(_modifiedAst, false)
} else if (
resp?.data?.data?.entities_modified?.length &&
(!guiMode.waitingFirstClick || isEditingExistingSketch)
) {
const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: resp?.data?.data?.entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
ast,
guiMode.pathToNode,
engineCommandManager.sendSceneCommand(command).then(async () => {
if (!context.sketchPathToNode) return
const varDec = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
context.sketchPathToNode,
'VariableDeclarator'
)
const variableName = varDec.id.name
const sketchGroup = programMemory.root[variableName]
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
const initialCoords = sketchGroup.value[0].from
).node
const variableName = varDec?.id?.name
const isClose = compareVec2Epsilon(initialCoords, [
coords[1].x,
coords[1].y,
])
let _modifiedAst: Program
if (!isClose) {
_modifiedAst = addNewSketchLn({
node: ast,
programMemory,
to: [coords[1].x, coords[1].y],
fnName: 'line',
pathToNode: guiMode.pathToNode,
}).modifiedAst
updateAst(_modifiedAst, false)
} else {
_modifiedAst = addCloseToPipe({
node: ast,
programMemory,
pathToNode: guiMode.pathToNode,
})
setGuiMode({
mode: 'default',
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
updateAst(_modifiedAst, true)
// Get the current plane string for plane we are on.
let currentPlaneString = ''
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
currentPlaneString = 'XY'
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
currentPlaneString = 'YZ'
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
currentPlaneString = 'XZ'
}
}
})
// Do not supporting editing/moving lines on a non-default plane.
// Eventually we can support this but for now we will just throw an
// error.
if (currentPlaneString === '') return
const updatedAst: Program = await modifyAstForSketch(
engineCommandManager,
kclManager.ast,
variableName,
currentPlaneString,
context.sketchEnginePathId
)
kclManager.executeAstMock(updatedAst, true)
})
}
setDidDragInStream(false)
setClickCoords(undefined)
}
@ -415,7 +313,8 @@ export const Stream = ({ className = '' }) => {
onWheel={handleScroll}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
className={`w-full h-full ${isExecuting && 'blur-md'}`}
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
disablePictureInPicture
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
/>
{isLoading && (

View File

@ -11,7 +11,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { Themes } from 'lib/theme'
import { useMemo } from 'react'
import { useMemo, useState } from 'react'
import { linter, lintGutter } from '@codemirror/lint'
import { Selections, useStore } from 'useStore'
import { LanguageServerClient } from 'editor/lsp'
@ -29,7 +29,10 @@ import {
import { isOverlap, roundOff } from 'lib/utils'
import { kclErrToDiagnostic } from 'lang/errors'
import { CSSRuleObject } from 'tailwindcss/types/config'
import { useModelingContext } from 'hooks/useModelingContext'
import interact from '@replit/codemirror-interact'
import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSinglton'
export const editorShortcutMeta = {
formatCode: {
@ -48,37 +51,22 @@ export const TextEditor = ({
theme: Themes.Light | Themes.Dark
}) => {
const pathParams = useParams()
const {
code,
deferredSetCode,
editorView,
engineCommandManager,
formatCode,
isLSPServerReady,
selectionRanges,
selectionRangeTypeMap,
setEditorView,
setIsLSPServerReady,
setSelectionRanges,
} = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
editorView: s.editorView,
engineCommandManager: s.engineCommandManager,
formatCode: s.formatCode,
isLSPServerReady: s.isLSPServerReady,
selectionRanges: s.selectionRanges,
selectionRangeTypeMap: s.selectionRangeTypeMap,
setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady,
setSelectionRanges: s.setSelectionRanges,
}))
const { editorView, isLSPServerReady, setEditorView, setIsLSPServerReady } =
useStore((s) => ({
editorView: s.editorView,
isLSPServerReady: s.isLSPServerReady,
setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady,
}))
const { code, errors } = useKclContext()
const {
settings: {
context: { textWrapping },
},
} = useGlobalStateContext()
context: { selectionRanges, selectionRangeTypeMap },
send,
} = useModelingContext()
const { settings: { context: { textWrapping } = {} } = {} } =
useGlobalStateContext()
const { setCommandBarOpen } = useCommandsContext()
const { enable: convertEnabled, handleClick: convertCallback } =
useConvertToVariable()
@ -123,12 +111,12 @@ export const TextEditor = ({
}, [lspClient, isLSPServerReady])
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
deferredSetCode(value)
const onChange = (newCode: string, viewUpdate: ViewUpdate) => {
kclManager.setCodeAndExecute(newCode)
if (isTauri() && pathParams.id) {
// Save the file to disk
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
writeTextFile(pathParams.id + '/' + PROJECT_ENTRYPOINT, value).catch(
writeTextFile(pathParams.id + '/' + PROJECT_ENTRYPOINT, newCode).catch(
(err) => {
// TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err)
@ -173,7 +161,7 @@ export const TextEditor = ({
const idBasedSelections = codeBasedSelections
.map(({ type, range }) => {
const hasOverlap = Object.entries(
engineCommandManager?.sourceRangeMap || {}
engineCommandManager.sourceRangeMap || {}
).filter(([_, sourceRange]) => {
return isOverlap(sourceRange, range)
})
@ -186,15 +174,22 @@ export const TextEditor = ({
})
.filter(Boolean) as any
engineCommandManager?.cusorsSelected({
engineCommandManager.cusorsSelected({
otherSelections: [],
idBasedSelections,
})
setSelectionRanges({
otherSelections: [],
codeBasedSelections,
})
selectionRanges &&
send({
type: 'Set selection',
data: {
selectionType: 'mirrorCodeMirrorSelections',
selection: {
...selectionRanges,
codeBasedSelections,
},
},
})
}
const editorExtensions = useMemo(() => {
@ -211,7 +206,7 @@ export const TextEditor = ({
{
key: editorShortcutMeta.formatCode.codeMirror,
run: () => {
formatCode()
kclManager.format()
return true
},
},
@ -235,7 +230,7 @@ export const TextEditor = ({
extensions.push(
lintGutter(),
linter((_view) => {
return kclErrToDiagnostic(useStore.getState().kclErrors)
return kclErrToDiagnostic(errors)
}),
interact({
rules: [

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore'
import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTreeTypes'
import { Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
@ -11,34 +11,30 @@ import {
transformSecondarySketchLinesTagFirst,
getTransformInfos,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
/*
export const EqualAngle = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const [enableEqual, setEnableEqual] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
@ -46,7 +42,7 @@ export const EqualAngle = () => {
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
@ -59,7 +55,7 @@ export const EqualAngle = () => {
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
kclManager.ast,
'equalAngle'
)
setTransformInfos(theTransforms)
@ -76,15 +72,15 @@ export const EqualAngle = () => {
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
if (!transformInfos) return
const { modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
})
updateAst(modifiedAst, true, {
kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
@ -103,3 +99,4 @@ export const EqualAngle = () => {
</button>
)
}
*/

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore'
import { Value, VariableDeclarator } from '../../lang/abstractSyntaxTreeTypes'
import { Selections, toolTips, useStore } from '../../useStore'
import { Program, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
@ -10,81 +10,41 @@ import {
TransformInfo,
transformSecondarySketchLinesTagFirst,
getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
/*
export const EqualLength = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const [enableEqual, setEnableEqual] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const { enabled, transforms } = setEqualLengthInfo({ selectionRanges })
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'equalLength'
)
setTransformInfos(theTransforms)
const _enableEqual =
!!secondaryVarDecs.length &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnableEqual(_enableEqual)
setTransformInfos(transforms)
setEnableEqual(enabled)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={() => {
if (!(transformInfos && ast)) return
if (!transformInfos) return
const { modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
})
updateAst(modifiedAst, true, {
kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
@ -103,3 +63,72 @@ export const EqualLength = () => {
</button>
)
}
*/
export function setEqualLengthInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const transforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
'equalLength'
)
const enabled =
!!secondaryVarDecs.length &&
isAllTooltips &&
isOthersLinkedToPrimary &&
transforms.every(Boolean)
return { enabled, transforms }
}
export function applyConstraintEqualLength({
selectionRanges,
}: {
selectionRanges: Selections
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const { enabled, transforms } = setEqualLengthInfo({ selectionRanges })
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
})
return { modifiedAst, pathToNodeMap }
// kclManager.updateAst(modifiedAst, true, {
// // callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -1,69 +1,53 @@
import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/abstractSyntaxTreeTypes'
import { Program, ProgramMemory, Value } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import {
PathToNodeMap,
TransformInfo,
getTransformInfos,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
import { Selections } from 'useStore'
/*
export const HorzVert = ({
horOrVert,
}: {
horOrVert: 'vertical' | 'horizontal'
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const [enableHorz, setEnableHorz] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(selectionRanges, ast, horOrVert)
setTransformInfos(theTransforms)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableHorz(_enableHorz)
const { enabled, transforms } = horzVertInfo(selectionRanges, horOrVert)
setTransformInfos(transforms)
setEnableHorz(enabled)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={() => {
if (!transformInfos || !ast) return
if (!transformInfos) return
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
ast,
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
updateAst(modifiedAst, true, {
kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
@ -82,3 +66,51 @@ export const HorzVert = ({
</button>
)
}
*/
export function horzVertInfo(
selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal'
) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
selectionRanges,
kclManager.ast,
horOrVert
)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
return { enabled: _enableHorz, transforms: theTransforms }
}
export function applyConstraintHorzVert(
selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal',
ast: Program,
programMemory: ProgramMemory
): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const transformInfos = horzVertInfo(selectionRanges, horOrVert).transforms
return transformAstSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
})
// kclManager.updateAst(modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -1,11 +1,7 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import {
BinaryPart,
Value,
VariableDeclarator,
} from '../../lang/abstractSyntaxTreeTypes'
import { BinaryPart, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
@ -20,26 +16,22 @@ import {
import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(GetInfoModal as any)
/*
export const Intersect = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
const [forecdSelectionRanges, setForcedSelectionRanges] =
useState<typeof selectionRanges>()
useEffect(() => {
if (!ast) return
if (selectionRanges.codeBasedSelections.length < 2) {
setEnable(false)
setForcedSelectionRanges({ ...selectionRanges })
@ -49,8 +41,8 @@ export const Intersect = () => {
const previousSegment =
selectionRanges.codeBasedSelections.length > 1 &&
isLinesParallelAndConstrained(
ast,
programMemory,
kclManager.ast,
kclManager.programMemory,
selectionRanges.codeBasedSelections[0],
selectionRanges.codeBasedSelections[1]
)
@ -74,15 +66,15 @@ export const Intersect = () => {
setForcedSelectionRanges(_forcedSelectionRanges)
const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
@ -90,7 +82,7 @@ export const Intersect = () => {
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
@ -107,7 +99,7 @@ export const Intersect = () => {
codeBasedSelections:
_forcedSelectionRanges.codeBasedSelections.slice(1),
},
ast,
kclManager.ast,
'intersect'
)
setTransformInfos(theTransforms)
@ -125,13 +117,13 @@ export const Intersect = () => {
return (
<button
onClick={async () => {
if (!(transformInfos && ast && forecdSelectionRanges)) return
if (!(transformInfos && forecdSelectionRanges)) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)),
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: forecdSelectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
})
const {
segName,
@ -154,7 +146,7 @@ export const Intersect = () => {
initialVariableName: 'offset',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -166,10 +158,10 @@ export const Intersect = () => {
)
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
ast: kclManager.ast,
selectionRanges: forecdSelectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
@ -182,7 +174,7 @@ export const Intersect = () => {
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
@ -194,3 +186,4 @@ export const Intersect = () => {
</button>
)
}
*/

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/abstractSyntaxTreeTypes'
import { Value } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
@ -10,27 +10,23 @@ import {
getRemoveConstraintsTransforms,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
/*
export const RemoveConstrainingValues = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const [enableHorz, setEnableHorz] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
@ -41,7 +37,7 @@ export const RemoveConstrainingValues = () => {
try {
const theTransforms = getRemoveConstraintsTransforms(
selectionRanges,
ast,
kclManager.ast,
'removeConstrainingValues'
)
setTransformInfos(theTransforms)
@ -57,15 +53,15 @@ export const RemoveConstrainingValues = () => {
return (
<button
onClick={() => {
if (!transformInfos || !ast) return
if (!transformInfos) return
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
ast,
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
updateAst(modifiedAst, true, {
kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
@ -76,3 +72,4 @@ export const RemoveConstrainingValues = () => {
</button>
)
}
*/

View File

@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/abstractSyntaxTreeTypes'
import { Value } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
@ -18,7 +18,7 @@ import {
createVariableDeclaration,
} from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(SetAngleLengthModal as any)
@ -31,16 +31,13 @@ const buttonLabels: Record<ButtonType, string> = {
snapToXAxis: 'Snap To X Axis',
}
/*
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const disType: ConstraintType =
buttonType === 'xAbs' || buttonType === 'yAbs'
? buttonType
@ -50,13 +47,13 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const [enableAngLen, setEnableAngLen] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression')
.node
)
const isAllTooltips = nodes.every(
(node) =>
@ -64,7 +61,11 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(selectionRanges, ast, disType)
const theTransforms = getTransformInfos(
selectionRanges,
kclManager.ast,
disType
)
setTransformInfos(theTransforms)
const enableY =
@ -90,12 +91,12 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
if (!transformInfos) return
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
try {
@ -112,10 +113,10 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
@ -129,11 +130,11 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} catch (e) {
console.log('e', e)
console.log('error', e)
}
}}
disabled={!enableAngLen}
@ -143,3 +144,4 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
</button>
)
}
*/

View File

@ -1,11 +1,7 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import {
BinaryPart,
Value,
VariableDeclarator,
} from '../../lang/abstractSyntaxTreeTypes'
import { Selections, toolTips, useStore } from '../../useStore'
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
@ -15,82 +11,41 @@ import {
TransformInfo,
transformSecondarySketchLinesTagFirst,
getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(GetInfoModal as any)
/*
export const SetAngleBetween = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'setAngleBetween'
)
setTransformInfos(theTransforms)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnable(_enableEqual)
const { enabled, transforms } = angleBetweenInfo({ selectionRanges })
setTransformInfos(transforms)
setEnable(enabled)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
if (!transformInfos) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)),
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
})
const {
segName,
@ -113,7 +68,7 @@ export const SetAngleBetween = () => {
initialVariableName: 'angle',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -125,10 +80,10 @@ export const SetAngleBetween = () => {
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
@ -141,7 +96,7 @@ export const SetAngleBetween = () => {
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
@ -153,3 +108,135 @@ export const SetAngleBetween = () => {
</button>
)
}
*/
export function angleBetweenInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
'setAngleBetween'
)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
return { enabled: _enableEqual, transforms: theTransforms }
}
export async function applyConstraintAngleBetween({
selectionRanges,
}: // constraint,
{
selectionRanges: Selections
// constraint: 'setHorzDistance' | 'setVertDistance'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const transformInfos = angleBetweenInfo({ selectionRanges }).transforms
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
}: {
segName: string
value: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
} = await getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: 'angle',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
return {
modifiedAst,
pathToNodeMap,
}
// kclManager.updateAst(modifiedAst, true, {
// TODO handle cursor
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}
const finalValue = removeDoubleNegatives(
valueNode as BinaryPart,
sign,
variableName
)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap: _pathToNodeMap,
}
// kclManager.updateAst(_modifiedAst, true, {
// TODO handle cursor
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -1,11 +1,7 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import {
BinaryPart,
Value,
VariableDeclarator,
} from '../../lang/abstractSyntaxTreeTypes'
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
@ -16,13 +12,13 @@ import {
transformSecondarySketchLinesTagFirst,
getTransformInfos,
ConstraintType,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
import { Selections } from 'useStore'
const getModalInfo = create(GetInfoModal as any)
@ -39,20 +35,17 @@ const buttonLabels: Record<ButtonType, string> = {
alignEndsVertically: 'Align Ends Vertically',
}
/*
export const SetHorzVertDistance = ({
buttonType,
}: {
buttonType: ButtonType
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const constraint: ConstraintType =
buttonType === 'setHorzDistance' || buttonType === 'setVertDistance'
? buttonType
@ -62,51 +55,12 @@ export const SetHorzVertDistance = ({
const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
[
...toolTips,
'startSketchAt', // TODO probably a better place for this to live
].includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
constraint
)
setTransformInfos(theTransforms)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnable(_enableEqual)
const { transforms, enabled } = horzVertDistanceInfo({
selectionRanges,
constraint,
})
setTransformInfos(transforms)
setEnable(enabled)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
@ -117,13 +71,13 @@ export const SetHorzVertDistance = ({
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
if (!transformInfos) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)),
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
})
const {
segName,
@ -148,7 +102,7 @@ export const SetHorzVertDistance = ({
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any))
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -158,10 +112,10 @@ export const SetHorzVertDistance = ({
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast,
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
@ -174,7 +128,7 @@ export const SetHorzVertDistance = ({
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
@ -186,3 +140,175 @@ export const SetHorzVertDistance = ({
</button>
)
}
*/
export function horzVertDistanceInfo({
selectionRanges,
constraint,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
[
...toolTips,
'startSketchAt', // TODO probably a better place for this to live
].includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
constraint
)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
return { enabled: _enableEqual, transforms: theTransforms }
}
export async function applyConstraintHorzVertDistance({
selectionRanges,
constraint,
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
isAlign = false,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
isAlign?: boolean
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const transformInfos = horzVertDistanceInfo({
selectionRanges,
constraint,
}).transforms
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
}: {
segName: string
value: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
} = await (!isAlign &&
getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any))
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
return {
modifiedAst,
pathToNodeMap,
}
// TODO handle cursor stuff
// kclManager.updateAst(modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
} else {
let finalValue = isAlign
? createLiteral(0)
: removeDoubleNegatives(valueNode as BinaryPart, sign, variableName)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
// TODO handle cursor stuff
// kclManager.updateAst(_modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}
}
export function applyConstraintHorzVertAlign({
selectionRanges,
constraint,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const transformInfos = horzVertDistanceInfo({
selectionRanges,
constraint,
}).transforms
let finalValue = createLiteral(0)
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceValueUsedInTransform: finalValue,
})
return {
modifiedAst: modifiedAst,
pathToNodeMap,
}
// TODO handle cursor stuff
// kclManager.updateAst(_modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -1,12 +1,13 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/abstractSyntaxTreeTypes'
import { Selections, toolTips, useStore } from '../../useStore'
import { Program, Value } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import {
PathToNodeMap,
TransformInfo,
getTransformInfos,
transformAstSketchLines,
@ -19,7 +20,7 @@ import {
} from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { normaliseAngle } from '../../lib/utils'
import { updateCursors } from '../../lang/util'
import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(SetAngleLengthModal as any)
@ -30,54 +31,39 @@ const buttonLabels: Record<ButtonType, string> = {
setLength: 'Set Length',
}
/*
export const SetAngleLength = ({
angleOrLength,
}: {
angleOrLength: ButtonType
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor,
}))
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
guiMode: s.guiMode,
selectionRanges: s.selectionRanges,
setCursor: s.setCursor,
}))
const [enableAngLen, setEnableAngLen] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const { enabled, transforms } = setAngleLengthInfo({
selectionRanges,
angleOrLength,
})
const theTransforms = getTransformInfos(selectionRanges, ast, angleOrLength)
setTransformInfos(theTransforms)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableAngLen(_enableHorz)
setTransformInfos(transforms)
setEnableAngLen(enabled)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
return (
<button
onClick={async () => {
if (!(transformInfos && ast)) return
if (!transformInfos) return
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
try {
@ -126,10 +112,10 @@ export const SetAngleLength = ({
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)),
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
@ -143,11 +129,11 @@ export const SetAngleLength = ({
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} catch (e) {
console.log('e', e)
console.log('erorr', e)
}
}}
disabled={!enableAngLen}
@ -157,3 +143,119 @@ export const SetAngleLength = ({
</button>
)
}
*/
export function setAngleLengthInfo({
selectionRanges,
angleOrLength = 'setLength',
}: {
selectionRanges: Selections
angleOrLength?: 'setLength' | 'setAngle'
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const transforms = getTransformInfos(
selectionRanges,
kclManager.ast,
angleOrLength
)
const enabled = isAllTooltips && transforms.every(Boolean)
return { enabled, transforms }
}
export async function applyConstraintAngleLength({
selectionRanges,
angleOrLength = 'setLength',
}: {
selectionRanges: Selections
angleOrLength?: 'setLength' | 'setAngle'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const { transforms } = setAngleLengthInfo({ selectionRanges, angleOrLength })
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
try {
const isReferencingYAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'y-axis'
const isReferencingYAxisAngle =
isReferencingYAxis && angleOrLength === 'setAngle'
const isReferencingXAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'x-axis'
const isReferencingXAxisAngle =
isReferencingXAxis && angleOrLength === 'setAngle'
let forceVal = valueUsedInTransform || 0
let calcIdentifier = createIdentifier('_0')
if (isReferencingYAxisAngle) {
calcIdentifier = createIdentifier(forceVal < 0 ? '_270' : '_90')
forceVal = normaliseAngle(forceVal + (forceVal < 0 ? 90 : -90))
} else if (isReferencingXAxisAngle) {
calcIdentifier = createIdentifier(Math.abs(forceVal) > 90 ? '_180' : '_0')
forceVal =
Math.abs(forceVal) > 90 ? normaliseAngle(forceVal - 180) : forceVal
}
const { valueNode, variableName, newVariableInsertIndex, sign } =
await getModalInfo({
value: forceVal,
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
shouldCreateVariable: true,
} as any)
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
if (
isReferencingYAxisAngle ||
(isReferencingXAxisAngle && calcIdentifier.name !== '_0')
) {
finalValue = createBinaryExpressionWithUnary([calcIdentifier, finalValue])
}
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
// kclManager.updateAst(_modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
} catch (e) {
console.log('erorr', e)
throw e
}
}

View File

@ -1,6 +1,11 @@
import { fireEvent, render, screen } from '@testing-library/react'
import UserSidebarMenu from './UserSidebarMenu'
import { BrowserRouter } from 'react-router-dom'
import {
Route,
RouterProvider,
createMemoryRouter,
createRoutesFromElements,
} from 'react-router-dom'
import { Models } from '@kittycad/lib'
import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar'
@ -93,11 +98,24 @@ describe('UserSidebarMenu tests', () => {
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return (
<BrowserRouter>
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
// We have to use a memory router in the testing environment,
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
const router = createMemoryRouter(
createRoutesFromElements(
<Route
path="/file/:id"
element={
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
}
/>
),
{
initialEntries: ['/file/new'],
initialIndex: 0,
}
)
return <RouterProvider router={router} />
}

View File

@ -1,24 +1,28 @@
import { Popover, Transition } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
import {
faBars,
faBug,
faGear,
faSignOutAlt,
} from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useState } from 'react'
import { paths } from '../Router'
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
import { Models } from '@kittycad/lib'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
type User = Models['User_type']
const UserSidebarMenu = ({ user }: { user?: User }) => {
const location = useLocation()
const filePath = useAbsoluteFilePath()
const displayedName = getDisplayName(user)
const [imageLoadFailed, setImageLoadFailed] = useState(false)
const navigate = useNavigate()
const {
auth: { send },
} = useGlobalStateContext()
const send = useGlobalStateContext()?.auth?.send
// Fallback logic for displaying user's "name":
// 1. user.name
@ -127,23 +131,30 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
// since /settings is a nested route the sidebar doesn't close
// automatically when navigating to it
close()
navigate(
(location.pathname.endsWith('/')
? location.pathname.slice(0, -1)
: location.pathname) + paths.SETTINGS
)
const targetPath = location.pathname.includes(paths.FILE)
? filePath + paths.SETTINGS
: paths.HOME + paths.SETTINGS
navigate(targetPath)
}}
>
Settings
</ActionButton>
<ActionButton
Element="link"
Element="externalLink"
to="https://github.com/KittyCAD/modeling-app/discussions"
icon={{ icon: faGithub }}
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
>
Request a feature
</ActionButton>
<ActionButton
Element="externalLink"
to="https://github.com/KittyCAD/modeling-app/issues/new"
icon={{ icon: faBug }}
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
>
Report a bug
</ActionButton>
<ActionButton
Element="button"
onClick={() => send('Log out')}

View File

@ -109,7 +109,6 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
}
}
messageString += message
// console.log(messageString)
return
})

View File

@ -9,6 +9,7 @@ import { LanguageServerClient } from '.'
import { kclPlugin } from './plugin'
import type * as LSP from 'vscode-languageserver-protocol'
import { parser as jsParser } from '@lezer/javascript'
import { EditorState } from '@uiw/react-codemirror'
const data = defineLanguageFacet({})
@ -22,7 +23,25 @@ export default function kclLanguage(options: LanguageOptions): LanguageSupport {
// For now let's use the javascript parser.
// It works really well and has good syntax highlighting.
// We can use our lsp for the rest.
const lang = new Language(data, jsParser, [], 'kcl')
const lang = new Language(
data,
jsParser,
[
EditorState.languageData.of(() => [
{
// https://codemirror.net/docs/ref/#commands.CommentTokens
commentTokens: {
line: '//',
block: {
open: '/*',
close: '*/',
},
},
},
]),
],
'kcl'
)
// Create our supporting extension.
const kclLsp = kclPlugin({

View File

@ -13,6 +13,7 @@ import {
CompletionItemKind,
CompletionTriggerKind,
} from 'vscode-languageserver-protocol'
import debounce from 'debounce-promise'
import type {
Completion,
@ -53,14 +54,11 @@ export class LanguageServerPlugin implements PluginValue {
private languageId: string
private documentVersion: number
private changesTimeout: number
constructor(private view: EditorView, private allowHTMLContent: boolean) {
this.client = this.view.state.facet(client)
this.documentUri = this.view.state.facet(documentUri)
this.languageId = this.view.state.facet(languageId)
this.documentVersion = 0
this.changesTimeout = 0
this.client.attachPlugin(this)
@ -71,12 +69,10 @@ export class LanguageServerPlugin implements PluginValue {
update({ docChanged }: ViewUpdate) {
if (!docChanged) return
if (this.changesTimeout) clearTimeout(this.changesTimeout)
this.changesTimeout = window.setTimeout(() => {
this.sendChange({
documentText: this.view.state.doc.toString(),
})
}, changesDelay)
this.sendChange({
documentText: this.view.state.doc.toString(),
})
}
destroy() {
@ -99,14 +95,32 @@ export class LanguageServerPlugin implements PluginValue {
async sendChange({ documentText }: { documentText: string }) {
if (!this.client.ready) return
if (documentText.length > 5000) {
// Clear out the text it thinks we have, large documents will throw a stack error.
// This is obviously not a good fix but it works for now til we figure
// out the stack limits in wasm and also rewrite the parser.
// Since this is only for hover and completions it will be fine,
// completions will still work for stdlib but hover will not.
// That seems like a fine trade-off for a working editor for the time
// being.
documentText = ''
}
try {
await this.client.textDocumentDidChange({
textDocument: {
uri: this.documentUri,
version: this.documentVersion++,
debounce(
() => {
return this.client.textDocumentDidChange({
textDocument: {
uri: this.documentUri,
version: this.documentVersion++,
},
contentChanges: [{ text: documentText }],
})
},
contentChanges: [{ text: documentText }],
})
changesDelay,
{ leading: true }
)
} catch (e) {
console.error(e)
}

View File

@ -1,8 +1,5 @@
// all web app environment variables are defined here, jest doesn't like import.meta.env so centralising them here
// allows us to mock them in one place, see src/setupTests.ts, it pulls the variable names and valuse from .env.development
// note the exported variable name must match the env var name for the jest mocks to work
// i.e. const VITE_MY_VAR = import.meta.env.VITE_MY_VAR
// Maybe this file should be generated in a GHA from .env.development?
// env vars were centralised so they could be mocked in jest
// but isn't needed anymore with vite, so is now just a convention
export const VITE_KC_API_WS_MODELING_URL = import.meta.env
.VITE_KC_API_WS_MODELING_URL
@ -12,3 +9,4 @@ export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
.VITE_KC_CONNECTION_TIMEOUT_MS
export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN
export const TEST = import.meta.env.TEST
export const DEV = import.meta.env.DEV

View File

@ -0,0 +1,12 @@
import { BROWSER_FILE_NAME, IndexLoaderData, paths } from 'Router'
import { useRouteLoaderData } from 'react-router-dom'
export function useAbsoluteFilePath() {
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
return (
paths.FILE +
'/' +
encodeURIComponent(routeData?.project?.path || BROWSER_FILE_NAME)
)
}

View File

@ -1,297 +0,0 @@
// needed somewhere to dump this logic,
// Once we have xState this should be removed
import { useStore, Selections } from 'useStore'
import { useEffect, useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
import { Models } from '@kittycad/lib/dist/types/src'
import { isReducedMotion } from 'lang/util'
import { isOverlap } from 'lib/utils'
interface DefaultPlanes {
xy: string
// TODO re-enable
// yz: string
// xz: string
}
export function useAppMode() {
const {
guiMode,
setGuiMode,
selectionRanges,
engineCommandManager,
selectionRangeTypeMap,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
engineCommandManager: s.engineCommandManager,
selectionRangeTypeMap: s.selectionRangeTypeMap,
}))
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
useEffect(() => {
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'selectFace' &&
engineCommandManager
) {
const createAndShowPlanes = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
} else {
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) {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
}
if (guiMode.mode === 'default') {
const pathId =
engineCommandManager &&
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
if (pathId) {
setGuiMode({
mode: 'canEditSketch',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
pathId,
})
}
} else if (guiMode.mode === 'canEditSketch') {
if (
!engineCommandManager ||
!isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
) {
setGuiMode({
mode: 'default',
})
}
}
}, [
guiMode,
guiMode.mode,
engineCommandManager,
selectionRanges,
selectionRangeTypeMap,
])
useEffect(() => {
const unSub = engineCommandManager?.subscribeTo({
event: 'select_with_point',
callback: async ({ data }) => {
if (!data.entity_id) return
if (!defaultPlanes) return
if (!Object.values(defaultPlanes || {}).includes(data.entity_id)) {
// user clicked something else in the scene
return
}
const sketchModeResponse = await engineCommandManager?.sendSceneCommand(
{
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: data.entity_id,
ortho: true,
animated: !isReducedMotion(),
},
}
)
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
const sketchUuid = uuidv4()
const proms: any[] = []
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: sketchUuid,
cmd: {
type: 'start_path',
},
})
)
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: sketchUuid,
},
})
)
const res = await Promise.all(proms)
console.log('res', res)
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
pathId: sketchUuid,
})
console.log('sketchModeResponse', sketchModeResponse)
},
})
return unSub
}, [engineCommandManager, defaultPlanes])
}
async function createPlane(
engineCommandManager: EngineCommandManager,
{
x_axis,
y_axis,
color,
}: {
x_axis: Models['Point3d_type']
y_axis: Models['Point3d_type']
color: Models['Color_type']
}
) {
const planeId = uuidv4()
await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'make_plane',
size: 60,
origin: { x: 0, y: 0, z: 0 },
x_axis,
y_axis,
clobber: false,
},
cmd_id: planeId,
})
await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'plane_set_color',
plane_id: planeId,
color,
},
cmd_id: uuidv4(),
})
return planeId
}
function setDefaultPlanesHidden(
engineCommandManager: EngineCommandManager | undefined,
defaultPlanes: DefaultPlanes,
hidden: boolean
) {
Object.values(defaultPlanes).forEach((planeId) => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: hidden,
},
})
})
}
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(
artifactMap: ArtifactMap,
selectionRanges: Selections
): string | false {
const overlapingEntries = Object.entries(artifactMap || {}).filter(
([id, artifact]) =>
selectionRanges.codeBasedSelections.some(
(selection) =>
Array.isArray(selection?.range) &&
Array.isArray(artifact?.range) &&
isOverlap(selection.range, artifact.range) &&
(artifact.commandType === 'start_path' ||
artifact.commandType === 'extend_path' ||
artifact.commandType === 'close_path')
)
)
return overlapingEntries.length && overlapingEntries[0][1].parentId
? overlapingEntries[0][1].parentId
: overlapingEntries.find(
([, artifact]) => artifact.commandType === 'start_path'
)?.[0] || false
}

View File

@ -1,18 +1,14 @@
import { useEffect } from 'react'
import { useStore } from 'useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from './useModelingContext'
export function useEngineConnectionSubscriptions() {
const {
engineCommandManager,
setCursor2,
setHighlightRange,
highlightRange,
} = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
setCursor2: s.setCursor2,
const { setHighlightRange, highlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
const { send } = useModelingContext()
useEffect(() => {
if (!engineCommandManager) return
@ -35,16 +31,25 @@ export function useEngineConnectionSubscriptions() {
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) {
setCursor2()
send({
type: 'Set selection',
data: { selectionType: 'singleCodeCursor' },
})
return
}
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' })
send({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: { range: sourceRange, type: 'default' },
},
})
},
})
return () => {
unSubHover()
unSubClick()
}
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
}, [engineCommandManager, setHighlightRange, highlightRange])
}

View File

@ -0,0 +1,6 @@
import { ModelingMachineContext } from 'components/ModelingMachineProvider'
import { useContext } from 'react'
export const useModelingContext = () => {
return useContext(ModelingMachineContext)
}

View File

@ -1,53 +1,94 @@
import { useLayoutEffect } from 'react'
import { _executor } from '../lang/executor'
import { useLayoutEffect, useEffect, useRef } from 'react'
import { _executor, parse } from '../lang/wasm'
import { useStore } from '../useStore'
import { EngineCommandManager } from '../lang/std/engineConnection'
import { engineCommandManager } from '../lang/std/engineConnection'
import { deferExecution } from 'lib/utils'
import { kclManager } from 'lang/KclSinglton'
export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>,
token?: string
) {
const {
setEngineCommandManager,
setMediaStream,
setIsStreamReady,
setStreamDimensions,
executeCode,
streamDimensions,
} = useStore((s) => ({
setEngineCommandManager: s.setEngineCommandManager,
setMediaStream: s.setMediaStream,
setIsStreamReady: s.setIsStreamReady,
setStreamDimensions: s.setStreamDimensions,
executeCode: s.executeCode,
streamDimensions: s.streamDimensions,
}))
const streamWidth = streamRef?.current?.offsetWidth
const streamHeight = streamRef?.current?.offsetHeight
const hasSetNonZeroDimensions = useRef<boolean>(false)
useEffect(() => {
kclManager.executeCode()
}, [])
useLayoutEffect(() => {
// Load the engine command manager once with the initial width and height,
// then we do not want to reload it.
const { width: quadWidth, height: quadHeight } = getDimensions(
streamWidth,
streamHeight
)
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
engineCommandManager.start({
setMediaStream,
setIsStreamReady,
width: quadWidth,
height: quadHeight,
executeCode: (code?: string) => {
const _ast = parse(code || kclManager.code)
return kclManager.executeAst(_ast, true)
},
token,
})
setStreamDimensions({
streamWidth: quadWidth,
streamHeight: quadHeight,
})
hasSetNonZeroDimensions.current = true
}
}, [streamRef?.current?.offsetWidth, streamRef?.current?.offsetHeight])
useEffect(() => {
const handleResize = deferExecution(() => {
const { width, height } = getDimensions(
streamRef?.current?.offsetWidth,
streamRef?.current?.offsetHeight
)
if (
streamDimensions.streamWidth !== width ||
streamDimensions.streamHeight !== height
) {
engineCommandManager.handleResize({
streamWidth: width,
streamHeight: height,
})
setStreamDimensions({
streamWidth: width,
streamHeight: height,
})
}
}, 500)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
}
function getDimensions(streamWidth?: number, streamHeight?: number) {
const width = streamWidth ? streamWidth : 0
const quadWidth = Math.round(width / 4) * 4
const height = streamHeight ? streamHeight : 0
const quadHeight = Math.round(height / 4) * 4
useLayoutEffect(() => {
setStreamDimensions({
streamWidth: quadWidth,
streamHeight: quadHeight,
})
if (!width || !height) return
const eng = new EngineCommandManager({
setMediaStream,
setIsStreamReady,
width: quadWidth,
height: quadHeight,
token,
})
setEngineCommandManager(eng)
eng.waitForReady.then(() => {
executeCode()
})
return () => {
eng?.tearDown()
}
}, [quadWidth, quadHeight])
return { width: quadWidth, height: quadHeight }
}

View File

@ -1,54 +1,45 @@
import { SetVarNameModal } from 'components/SetVarNameModal'
import { kclManager } from 'lang/KclSinglton'
import { moveValueIntoNewVariable } from 'lang/modifyAst'
import { isNodeSafeToReplace } from 'lang/queryAst'
import { useEffect, useState } from 'react'
import { create } from 'react-modal-promise'
import { useStore } from 'useStore'
import { useModelingContext } from './useModelingContext'
const getModalInfo = create(SetVarNameModal as any)
export function useConvertToVariable() {
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
(s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
})
)
const { context } = useModelingContext()
const [enable, setEnabled] = useState(false)
useEffect(() => {
if (!ast) return
const { isSafe, value } = isNodeSafeToReplace(
ast,
selectionRanges.codeBasedSelections?.[0]?.range || []
kclManager.ast,
context.selectionRanges.codeBasedSelections?.[0]?.range || []
)
const canReplace = isSafe && value.type !== 'Identifier'
const isOnlyOneSelection = selectionRanges.codeBasedSelections.length === 1
const isOnlyOneSelection =
context.selectionRanges.codeBasedSelections.length === 1
const _enableHorz = canReplace && isOnlyOneSelection
setEnabled(_enableHorz)
}, [guiMode, selectionRanges])
}, [context.selectionRanges])
const handleClick = async () => {
if (!ast) return
try {
const { variableName } = await getModalInfo({
valueName: 'var',
} as any)
const { modifiedAst: _modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
selectionRanges.codeBasedSelections[0].range,
kclManager.ast,
kclManager.programMemory,
context.selectionRanges.codeBasedSelections[0].range,
variableName
)
updateAst(_modifiedAst, true)
kclManager.updateAst(_modifiedAst, true)
} catch (e) {
console.log('e', e)
console.log('error', e)
}
}

View File

@ -4,6 +4,13 @@ import reportWebVitals from './reportWebVitals'
import { Toaster } from 'react-hot-toast'
import { Router } from './Router'
import { HotkeysProvider } from 'react-hotkeys-hook'
import { inspect } from '@xstate/inspect'
import { DEV } from 'env'
if (DEV)
inspect({
iframe: false,
})
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

353
src/lang/KclSinglton.tsx Normal file
View File

@ -0,0 +1,353 @@
import { Selections, executeAst, executeCode } from 'useStore'
import { KCLError } from './errors'
import {
EngineCommandManager,
engineCommandManager,
} from './std/engineConnection'
import { deferExecution } from 'lib/utils'
import {
initPromise,
parse,
PathToNode,
Program,
ProgramMemory,
recast,
} from 'lang/wasm'
import { bracket } from 'lib/exampleKcl'
import { createContext, useContext, useEffect, useState } from 'react'
import { getNodeFromPath } from './queryAst'
import { IndexLoaderData } from 'Router'
import { useLoaderData } from 'react-router-dom'
const PERSIST_CODE_TOKEN = 'persistCode'
class KclManager {
private _code = bracket
private _ast: Program = {
body: [],
start: 0,
end: 0,
nonCodeMeta: {
nonCodeNodes: {},
start: [],
},
}
private _programMemory: ProgramMemory = {
root: {},
return: null,
}
private _logs: string[] = []
private _kclErrors: KCLError[] = []
private _isExecuting = false
engineCommandManager: EngineCommandManager
private _defferer = deferExecution((code: string) => {
const ast = parse(code)
this.executeAst(ast)
}, 600)
private _isExecutingCallback: (a: boolean) => void = () => {}
private _codeCallBack: (arg: string) => void = () => {}
private _astCallBack: (arg: Program) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
get ast() {
return this._ast
}
set ast(ast) {
this._ast = ast
this._astCallBack(ast)
}
get code() {
return this._code
}
set code(code) {
this._code = code
this._codeCallBack(code)
}
get programMemory() {
return this._programMemory
}
set programMemory(programMemory) {
this._programMemory = programMemory
this._programMemoryCallBack(programMemory)
}
get defaultPlanes() {
return this?.engineCommandManager?.defaultPlanes
}
get logs() {
return this._logs
}
set logs(logs) {
this._logs = logs
this._logsCallBack(logs)
}
get kclErrors() {
return this._kclErrors
}
set kclErrors(kclErrors) {
this._kclErrors = kclErrors
this._kclErrorsCallBack(kclErrors)
}
get isExecuting() {
return this._isExecuting
}
set isExecuting(isExecuting) {
this._isExecuting = isExecuting
this._isExecutingCallback(isExecuting)
}
constructor(engineCommandManager: EngineCommandManager) {
this.engineCommandManager = engineCommandManager
const storedCode = localStorage.getItem(PERSIST_CODE_TOKEN)
// TODO #819 remove zustand persistance logic in a few months
// short term migration, shouldn't make a difference for tauri app users
// anyway since that's filesystem based.
const zustandStore = JSON.parse(localStorage.getItem('store') || '{}')
if (storedCode === null && zustandStore?.state?.code) {
this.code = zustandStore.state.code
localStorage.setItem(PERSIST_CODE_TOKEN, this._code)
zustandStore.state.code = ''
localStorage.setItem('store', JSON.stringify(zustandStore))
} else if (storedCode === null) {
this.code = bracket
} else {
this.code = storedCode
}
}
registerCallBacks({
setCode,
setProgramMemory,
setAst,
setLogs,
setKclErrors,
setIsExecuting,
}: {
setCode: (arg: string) => void
setProgramMemory: (arg: ProgramMemory) => void
setAst: (arg: Program) => void
setLogs: (arg: string[]) => void
setKclErrors: (arg: KCLError[]) => void
setIsExecuting: (arg: boolean) => void
}) {
this._codeCallBack = setCode
this._programMemoryCallBack = setProgramMemory
this._astCallBack = setAst
this._logsCallBack = setLogs
this._kclErrorsCallBack = setKclErrors
this._isExecutingCallback = setIsExecuting
}
async executeAst(ast: Program = this._ast, updateCode = false) {
this.isExecuting = true
await initPromise
const { logs, errors, programMemory } = await executeAst({
ast,
engineCommandManager: this.engineCommandManager,
defaultPlanes: this.defaultPlanes,
})
this.isExecuting = false
this._logs = logs
this._kclErrors = errors
this._programMemory = programMemory
this._ast = { ...ast }
if (updateCode) {
this._code = recast(ast)
this._codeCallBack(this._code)
}
}
async executeAstMock(ast: Program = this._ast, updateCode = false) {
await initPromise
const newCode = recast(ast)
const newAst = parse(newCode)
await this?.engineCommandManager?.waitForReady
if (updateCode) {
this.setCode(recast(ast))
}
this._ast = { ...newAst }
const { logs, errors, programMemory } = await executeAst({
ast: newAst,
engineCommandManager: this.engineCommandManager,
defaultPlanes: this.defaultPlanes,
useFakeExecutor: true,
})
this._logs = logs
this._kclErrors = errors
this._programMemory = programMemory
}
async executeCode(code?: string) {
await initPromise
await this?.engineCommandManager?.waitForReady
if (!this?.engineCommandManager?.planesInitialized()) return
const result = await executeCode({
engineCommandManager,
code: code || this._code,
lastAst: this._ast,
defaultPlanes: this.defaultPlanes,
force: false,
})
if (!result.isChange) return
const { logs, errors, programMemory, ast } = result
this.logs = logs
this.kclErrors = errors
this.programMemory = programMemory
this.ast = ast
if (code) this.code = code
}
setCode(code: string) {
this._code = code
this._codeCallBack(code)
localStorage.setItem(PERSIST_CODE_TOKEN, code)
}
setCodeAndExecute(code: string) {
this.setCode(code)
if (code.trim()) {
this._defferer(code)
return
}
this._ast = {
body: [],
start: 0,
end: 0,
nonCodeMeta: {
nonCodeNodes: {},
start: [],
},
}
this._programMemory = {
root: {},
return: null,
}
this.engineCommandManager.endSession()
}
format() {
this.code = recast(parse(kclManager.code))
}
// There's overlapping resposibility between updateAst and executeAst.
// updateAst was added as it was used a lot before xState migration so makes the port easier.
// but should probably have think about which of the function to keep
async updateAst(
ast: Program,
execute: boolean,
optionalParams?: {
focusPath?: PathToNode
callBack?: (ast: Program) => void
}
): Promise<Selections | null> {
const newCode = recast(ast)
const astWithUpdatedSource = parse(newCode)
optionalParams?.callBack?.(astWithUpdatedSource)
let returnVal: Selections | null = null
this.code = newCode
if (optionalParams?.focusPath) {
const { node } = getNodeFromPath<any>(
astWithUpdatedSource,
optionalParams?.focusPath
)
const { start, end } = node
if (!start || !end) return null
returnVal = {
codeBasedSelections: [
{
type: 'default',
range: [start, end],
},
],
otherSelections: [],
}
}
if (execute) {
// Call execute on the set ast.
await this.executeAst(astWithUpdatedSource)
} else {
// When we don't re-execute, we still want to update the program
// memory with the new ast. So we will hit the mock executor
// instead.
await this.executeAstMock(astWithUpdatedSource)
}
return returnVal
}
getPlaneId(axis: 'xy' | 'xz' | 'yz'): string {
return this.defaultPlanes[axis]
}
showPlanes() {
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, false)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, false)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, false)
}
hidePlanes() {
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, true)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
}
export const kclManager = new KclManager(engineCommandManager)
const KclContext = createContext({
code: kclManager.code,
programMemory: kclManager.programMemory,
ast: kclManager.ast,
isExecuting: kclManager.isExecuting,
errors: kclManager.kclErrors,
logs: kclManager.logs,
})
export function useKclContext() {
return useContext(KclContext)
}
export function KclContextProvider({
children,
}: {
children: React.ReactNode
}) {
// If we try to use this component anywhere but under the paths.FILE route it will fail
// Because useLoaderData assumes we are on within it's context.
const { code: loadedCode } = useLoaderData() as IndexLoaderData
const [code, setCode] = useState(loadedCode || kclManager.code)
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [ast, setAst] = useState(kclManager.ast)
const [isExecuting, setIsExecuting] = useState(false)
const [errors, setErrors] = useState<KCLError[]>([])
const [logs, setLogs] = useState<string[]>([])
useEffect(() => {
kclManager.registerCallBacks({
setCode,
setProgramMemory,
setAst,
setLogs,
setKclErrors: setErrors,
setIsExecuting,
})
}, [])
return (
<KclContext.Provider
value={{
code,
programMemory,
ast,
isExecuting,
errors,
logs,
}}
>
{children}
</KclContext.Provider>
)
}

View File

@ -1,12 +1,11 @@
import { parser_wasm } from './abstractSyntaxTree'
import { KCLError } from './errors'
import { initPromise } from './rust'
import { initPromise, parse } from './wasm'
beforeAll(() => initPromise)
describe('testing AST', () => {
test('5 + 6', () => {
const result = parser_wasm('5 +6')
const result = parse('5 +6')
delete (result as any).nonCodeMeta
expect(result.body).toEqual([
{
@ -37,7 +36,7 @@ describe('testing AST', () => {
])
})
test('const myVar = 5', () => {
const { body } = parser_wasm('const myVar = 5')
const { body } = parse('const myVar = 5')
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -71,7 +70,7 @@ describe('testing AST', () => {
const code = `const myVar = 5
const newVar = myVar + 1
`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -142,44 +141,8 @@ const newVar = myVar + 1
})
describe('testing function declaration', () => {
test('fn funcN = () => {}', () => {
const { body } = parser_wasm('fn funcN = () => {}')
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
expect(body).toEqual([
{
type: 'VariableDeclaration',
start: 0,
end: 19,
kind: 'fn',
declarations: [
{
type: 'VariableDeclarator',
start: 3,
end: 19,
id: {
type: 'Identifier',
start: 3,
end: 8,
name: 'funcN',
},
init: {
type: 'FunctionExpression',
start: 11,
end: 19,
params: [],
body: {
start: 17,
end: 19,
body: [],
},
},
},
],
},
])
})
test('fn funcN = (a, b) => {return a + b}', () => {
const { body } = parser_wasm(
const { body } = parse(
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
)
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
@ -256,7 +219,7 @@ describe('testing function declaration', () => {
test('call expression assignment', () => {
const code = `fn funcN = (a, b) => { return a + b }
const myVar = funcN(1, 2)`
const { body } = parser_wasm(code)
const { body } = parse(code)
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
expect(body).toEqual([
{
@ -388,7 +351,7 @@ describe('testing pipe operator special', () => {
|> lineTo([1, 1], %)
|> rx(45, %)
`
const { body } = parser_wasm(code)
const { body } = parse(code)
delete (body[0] as any).declarations[0].init.nonCodeMeta
expect(body).toEqual([
{
@ -404,7 +367,7 @@ describe('testing pipe operator special', () => {
id: { type: 'Identifier', start: 6, end: 14, name: 'mySketch' },
init: {
type: 'PipeExpression',
start: 15,
start: 17,
end: 145,
body: [
{
@ -624,7 +587,7 @@ describe('testing pipe operator special', () => {
})
test('pipe operator with binary expression', () => {
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
const { body } = parser_wasm(code)
const { body } = parse(code)
delete (body as any)[0].declarations[0].init.nonCodeMeta
expect(body).toEqual([
{
@ -645,7 +608,7 @@ describe('testing pipe operator special', () => {
},
init: {
type: 'PipeExpression',
start: 12,
start: 14,
end: 36,
body: [
{
@ -706,7 +669,7 @@ describe('testing pipe operator special', () => {
})
test('array expression', () => {
let code = `const yo = [1, '2', three, 4 + 5]`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -781,7 +744,7 @@ describe('testing pipe operator special', () => {
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -925,7 +888,7 @@ describe('testing pipe operator special', () => {
const code = `const yo = {key: {
key2: 'value'
}}`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -993,7 +956,7 @@ describe('testing pipe operator special', () => {
})
test('object expression with array ast', () => {
const code = `const yo = {key: [1, '2']}`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1057,7 +1020,7 @@ describe('testing pipe operator special', () => {
})
test('object memberExpression simple', () => {
const code = `const prop = yo.one.two`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1112,7 +1075,7 @@ describe('testing pipe operator special', () => {
})
test('object memberExpression with square braces', () => {
const code = `const prop = yo.one["two"]`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1168,7 +1131,7 @@ describe('testing pipe operator special', () => {
})
test('object memberExpression with two square braces literal and identifier', () => {
const code = `const prop = yo["one"][two]`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1227,7 +1190,7 @@ describe('testing pipe operator special', () => {
describe('nests binary expressions correctly', () => {
it('works with the simple case', () => {
const code = `const yo = 1 + 2`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1271,7 +1234,7 @@ describe('nests binary expressions correctly', () => {
it('should nest according to precedence with multiply first', () => {
// should be binExp { binExp { lit-1 * lit-2 } + lit}
const code = `const yo = 1 * 2 + 3`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1328,7 +1291,7 @@ describe('nests binary expressions correctly', () => {
it('should nest according to precedence with sum first', () => {
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
const code = `const yo = 1 + 2 * 3`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1384,7 +1347,7 @@ describe('nests binary expressions correctly', () => {
})
it('should nest properly with two opperators of equal precedence', () => {
const code = `const yo = 1 + 2 - 3`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
@ -1421,7 +1384,7 @@ describe('nests binary expressions correctly', () => {
})
it('should nest properly with two opperators of equal (but higher) precedence', () => {
const code = `const yo = 1 * 2 / 3`
const { body } = parser_wasm(code)
const { body } = parse(code)
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
@ -1458,7 +1421,7 @@ describe('nests binary expressions correctly', () => {
})
it('should nest properly with longer example', () => {
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
const { body } = parser_wasm(code)
const { body } = parse(code)
const init = (body[0] as any).declarations[0].init
expect(init).toEqual({
type: 'BinaryExpression',
@ -1514,24 +1477,23 @@ const key = 'c'`
const nonCodeMetaInstance = {
type: 'NonCodeNode',
start: code.indexOf('\n// this is a comment'),
end: code.indexOf('const key'),
end: code.indexOf('const key') - 1,
value: {
type: 'blockComment',
style: 'line',
value: 'this is a comment',
},
}
const { nonCodeMeta } = parser_wasm(code)
expect(nonCodeMeta.nonCodeNodes[0]).toEqual(nonCodeMetaInstance)
const { nonCodeMeta } = parse(code)
expect(nonCodeMeta.nonCodeNodes[0][0]).toEqual(nonCodeMetaInstance)
// 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 { nonCodeMeta: nonCodeMeta2 } = parser_wasm(
codeWithExtraStartWhitespace
)
expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
const { nonCodeMeta: nonCodeMeta2 } = parse(codeWithExtraStartWhitespace)
expect(nonCodeMeta2.nonCodeNodes[0][0].value).toStrictEqual(
nonCodeMetaInstance.value
)
expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
expect(nonCodeMeta2.nonCodeNodes[0][0].start).not.toBe(
nonCodeMetaInstance.start
)
})
@ -1545,16 +1507,17 @@ const key = 'c'`
|> close(%)
`
const { body } = parser_wasm(code)
const { body } = parse(code)
const indexOfSecondLineToExpression = 2
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
.nonCodeNodes
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
expect(sketchNonCodeMeta[indexOfSecondLineToExpression][0]).toEqual({
type: 'NonCodeNode',
start: 106,
end: 166,
end: 163,
value: {
type: 'blockComment',
type: 'inlineComment',
style: 'block',
value: 'this is\n a comment\n spanning a few lines',
},
})
@ -1569,16 +1532,17 @@ const key = 'c'`
' |> rx(90, %)',
].join('\n')
const { body } = parser_wasm(code)
const { body } = parse(code)
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
.nonCodeNodes
expect(sketchNonCodeMeta[3]).toEqual({
.nonCodeNodes[3][0]
expect(sketchNonCodeMeta).toEqual({
type: 'NonCodeNode',
start: 125,
end: 141,
end: 138,
value: {
type: 'blockComment',
value: 'a comment',
style: 'line',
},
})
})
@ -1587,7 +1551,7 @@ const key = 'c'`
describe('test UnaryExpression', () => {
it('should parse a unary expression in simple var dec situation', () => {
const code = `const myVar = -min(4, 100)`
const { body } = parser_wasm(code)
const { body } = parse(code)
const myVarInit = (body?.[0] as any).declarations[0]?.init
expect(myVarInit).toEqual({
type: 'UnaryExpression',
@ -1613,7 +1577,7 @@ describe('test UnaryExpression', () => {
describe('testing nested call expressions', () => {
it('callExp in a binExp in a callExp', () => {
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
const { body } = parser_wasm(code)
const { body } = parse(code)
const myVarInit = (body?.[0] as any).declarations[0]?.init
expect(myVarInit).toEqual({
type: 'CallExpression',
@ -1651,7 +1615,7 @@ describe('testing nested call expressions', () => {
describe('should recognise callExpresions in binaryExpressions', () => {
const code = "xLineTo(segEndX('seg02', %) + 1, %)"
it('should recognise the callExp', () => {
const { body } = parser_wasm(code)
const { body } = parse(code)
const callExpArgs = (body?.[0] as any).expression?.arguments
expect(callExpArgs).toEqual([
{
@ -1690,18 +1654,13 @@ describe('parsing errors', () => {
let _theError
try {
const result = expect(parser_wasm(code))
console.log('result', result)
const result = expect(parse(code))
} catch (e) {
_theError = e
}
const theError = _theError as any
expect(theError).toEqual(
new KCLError(
'unexpected',
'Unexpected token Token { token_type: Brace, start: 29, end: 30, value: "}" }',
[[29, 30]]
)
new KCLError('syntax', 'Unexpected token', [[27, 28]])
)
})
})

View File

@ -1,48 +0,0 @@
import { Program } from './abstractSyntaxTreeTypes'
import { parse_js } from '../wasm-lib/pkg/wasm_lib'
import { initPromise } from './rust'
import { Token } from './tokeniser'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
ranges.map(([start, end]) => [start, end])
export const parser_wasm = (code: string): Program => {
try {
const program: Program = parse_js(code)
return program
} 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
}
}
export async function asyncParser(code: string): Promise<Program> {
await initPromise
try {
const program: Program = parse_js(code)
return program
} 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
}
}
export function rangeOfToken(token: Token | undefined): [number, number][] {
return token === undefined ? [] : [[token.start, token.end]]
}

View File

@ -1,37 +0,0 @@
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Value } from '../wasm-lib/kcl/bindings/Value'
export type { ObjectExpression } from '../wasm-lib/kcl/bindings/ObjectExpression'
export type { MemberExpression } from '../wasm-lib/kcl/bindings/MemberExpression'
export type { PipeExpression } from '../wasm-lib/kcl/bindings/PipeExpression'
export type { VariableDeclaration } from '../wasm-lib/kcl/bindings/VariableDeclaration'
export type { PipeSubstitution } from '../wasm-lib/kcl/bindings/PipeSubstitution'
export type { Identifier } from '../wasm-lib/kcl/bindings/Identifier'
export type { UnaryExpression } from '../wasm-lib/kcl/bindings/UnaryExpression'
export type { BinaryExpression } from '../wasm-lib/kcl/bindings/BinaryExpression'
export type { ReturnStatement } from '../wasm-lib/kcl/bindings/ReturnStatement'
export type { ExpressionStatement } from '../wasm-lib/kcl/bindings/ExpressionStatement'
export type { CallExpression } from '../wasm-lib/kcl/bindings/CallExpression'
export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator'
export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
export type SyntaxType =
| 'Program'
| 'ExpressionStatement'
| 'BinaryExpression'
| 'CallExpression'
| 'Identifier'
| 'ReturnStatement'
| 'VariableDeclaration'
| 'VariableDeclarator'
| 'MemberExpression'
| 'ArrayExpression'
| 'ObjectExpression'
| 'ObjectProperty'
| 'FunctionExpression'
| 'PipeExpression'
| 'PipeSubstitution'
| 'Literal'
| 'NonCodeNode'
| 'UnaryExpression'

View File

@ -1,5 +1,4 @@
import { parser_wasm } from './abstractSyntaxTree'
import { initPromise } from './rust'
import { parse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(() => initPromise)
@ -8,12 +7,13 @@ describe('testing artifacts', () => {
// Enable rotations #152
test('sketch artifacts', async () => {
const code = `
const mySketch001 = startSketchAt([0, 0])
const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
show(mySketch001)`
const programMemory = await enginelessExecutor(parser_wasm(code))
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const shown = programMemory?.return?.map(
// @ts-ignore
@ -28,7 +28,7 @@ show(mySketch001)`
name: '',
__geoMeta: {
id: expect.any(String),
sourceRange: [21, 42],
sourceRange: [46, 71],
},
},
value: [
@ -38,7 +38,7 @@ show(mySketch001)`
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [48, 73],
sourceRange: [77, 102],
id: expect.any(String),
},
},
@ -48,7 +48,7 @@ show(mySketch001)`
from: [-1.59, -1.54],
name: '',
__geoMeta: {
sourceRange: [79, 103],
sourceRange: [108, 132],
id: expect.any(String),
},
},
@ -56,20 +56,22 @@ show(mySketch001)`
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
id: expect.any(String),
__meta: [{ sourceRange: [21, 42] }],
planeId: expect.any(String),
__meta: [{ sourceRange: [46, 71] }],
},
])
})
test('extrude artifacts', async () => {
// Enable rotations #152
const code = `
const mySketch001 = startSketchAt([0, 0])
const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
|> extrude(2, %)
show(mySketch001)`
const programMemory = await enginelessExecutor(parser_wasm(code))
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const shown = programMemory?.return?.map(
// @ts-ignore
@ -83,7 +85,7 @@ show(mySketch001)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [21, 42] }],
__meta: [{ sourceRange: [46, 71] }],
},
])
})
@ -91,7 +93,8 @@ show(mySketch001)`
// Enable rotations #152
// TODO #153 in order for getExtrudeWallTransform to work we need to query the engine for the location of a face.
const code = `
const sk1 = startSketchAt([0, 0])
const sk1 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.5, 0], %)
|> lineTo({ to: [0, 10], tag: "p" }, %)
|> lineTo([2.5, 0], %)
@ -100,7 +103,8 @@ const sk1 = startSketchAt([0, 0])
// |> ry(5, %)
const theExtrude = extrude(2, sk1)
// const theTransf = getExtrudeWallTransform('p', theExtrude)
const sk2 = startSketchAt([0, 0])
const sk2 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.5, 0], %)
|> lineTo({ to: [0, 3], tag: "p" }, %)
|> lineTo([2.5, 0], %)
@ -109,7 +113,7 @@ const sk2 = startSketchAt([0, 0])
show(theExtrude, sk2)`
const programMemory = await enginelessExecutor(parser_wasm(code))
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const geos = programMemory?.return?.map(
// @ts-ignore
@ -123,7 +127,7 @@ show(theExtrude, sk2)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [13, 34] }],
__meta: [{ sourceRange: [38, 63] }],
},
{
type: 'ExtrudeGroup',
@ -132,7 +136,7 @@ show(theExtrude, sk2)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [302, 323] }],
__meta: [{ sourceRange: [356, 381] }],
},
])
})

View File

@ -72,7 +72,7 @@ export class KCLUndefinedValueError extends KCLError {
* Currently the diagnostics are all errors, but in the future they could include lints.
* */
export function kclErrToDiagnostic(errors: KCLError[]): Diagnostic[] {
return errors.flatMap((err) => {
return errors?.flatMap((err) => {
return err.sourceRanges.map(([from, to]) => {
return { from, to, message: err.msg, severity: 'error' }
})

View File

@ -1,10 +1,7 @@
import fs from 'node:fs'
import { parser_wasm } from './abstractSyntaxTree'
import { ProgramMemory, SketchGroup } from './executor'
import { initPromise } from './rust'
import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { vi } from 'vitest'
import { KCLError } from './errors'
beforeAll(() => initPromise)
@ -44,7 +41,8 @@ const newVar = myVar + 1`
expect(root.magicNum.value).toBe(69)
})
it('sketch declaration', async () => {
let code = `const mySketch = startSketchAt([0,0])
let code = `const mySketch = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> lineTo({to: [0,2], tag: "myPath"}, %)
|> lineTo([2,3], %)
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
@ -60,7 +58,7 @@ show(mySketch)
to: [0, 2],
from: [0, 0],
__geoMeta: {
sourceRange: [43, 80],
sourceRange: [72, 109],
id: expect.any(String),
},
name: 'myPath',
@ -71,7 +69,7 @@ show(mySketch)
from: [0, 2],
name: '',
__geoMeta: {
sourceRange: [86, 102],
sourceRange: [115, 131],
id: expect.any(String),
},
},
@ -80,7 +78,7 @@ show(mySketch)
to: [5, -1],
from: [2, 3],
__geoMeta: {
sourceRange: [108, 151],
sourceRange: [137, 180],
id: expect.any(String),
},
name: 'rightPath',
@ -90,8 +88,8 @@ show(mySketch)
expect(_return).toEqual([
{
type: 'Identifier',
start: 174,
end: 182,
start: 203,
end: 211,
name: 'mySketch',
},
])
@ -135,7 +133,8 @@ show(mySketch)
it('execute pipe sketch into call expression', async () => {
// Enable rotations #152
const code = [
'const mySk1 = startSketchAt([0,0])',
"const mySk1 = startSketchOn('XY')",
' |> startProfileAt([0,0], %)',
' |> lineTo([1,1], %)',
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
' |> lineTo([1,1], %)',
@ -150,7 +149,7 @@ show(mySketch)
name: '',
__geoMeta: {
id: expect.any(String),
sourceRange: [14, 34],
sourceRange: [39, 63],
},
},
value: [
@ -160,7 +159,7 @@ show(mySketch)
from: [0, 0],
name: '',
__geoMeta: {
sourceRange: [40, 56],
sourceRange: [69, 85],
id: expect.any(String),
},
},
@ -169,7 +168,7 @@ show(mySketch)
to: [0, 1],
from: [1, 1],
__geoMeta: {
sourceRange: [62, 100],
sourceRange: [91, 129],
id: expect.any(String),
},
name: 'myPath',
@ -180,7 +179,7 @@ show(mySketch)
from: [0, 1],
name: '',
__geoMeta: {
sourceRange: [106, 122],
sourceRange: [135, 151],
id: expect.any(String),
},
},
@ -188,7 +187,8 @@ show(mySketch)
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
id: expect.any(String),
__meta: [{ sourceRange: [14, 34] }],
planeId: expect.any(String),
__meta: [{ sourceRange: [39, 63] }],
})
})
it('execute array expression', async () => {
@ -332,7 +332,8 @@ describe('testing math operators', () => {
})
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
const code = [
'const part001 = startSketchAt([0, 0])',
"const part001 = startSketchOn('XY')",
' |> startProfileAt([0, 0], %)',
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
].join('\n')
const { root } = await exe(code)
@ -344,7 +345,8 @@ describe('testing math operators', () => {
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
const code = [
`const myVar = 3`,
`const part001 = startSketchAt([0, 0])`,
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
` |> line([`,
` min(segLen('seg01', %), myVar),`,
@ -380,7 +382,8 @@ describe('testing math operators', () => {
describe('Testing Errors', () => {
it('should throw an error when a variable is not defined', async () => {
const code = `const myVar = 5
const theExtrude = startSketchAt([0, 0])
const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([-2.4, 5], %)
|> line([-0.76], myVarZ, %)
|> line([5,5], %)
@ -391,7 +394,7 @@ show(theExtrude)`
new KCLError(
'undefined_value',
'memory item key `myVarZ` is not defined',
[[100, 106]]
[[129, 135]]
)
)
})
@ -403,7 +406,7 @@ async function exe(
code: string,
programMemory: ProgramMemory = { root: {}, return: null }
) {
const ast = parser_wasm(code)
const ast = parse(code)
const result = await enginelessExecutor(ast, programMemory)
return result

View File

@ -1,81 +0,0 @@
import { Program } from './abstractSyntaxTreeTypes'
import {
EngineCommandManager,
ArtifactMap,
SourceRangeMap,
} from './std/engineConnection'
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from './abstractSyntaxTree'
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
export type { Position } from '../wasm-lib/kcl/bindings/Position'
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
export type { Path } from '../wasm-lib/kcl/bindings/Path'
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
export type PathToNode = [string | number, string][]
interface Memory {
[key: string]: MemoryItem
}
export interface ProgramMemory {
root: Memory
return: ProgramReturn | null
}
export const executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager,
// work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added.
tempMapCallback: (a: {
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}) => void = () => {}
): Promise<ProgramMemory> => {
engineCommandManager.startNewSession()
const _programMemory = await _executor(
node,
programMemory,
engineCommandManager
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands(node, _programMemory)
tempMapCallback({ artifactMap, sourceRangeMap })
engineCommandManager.endSession()
return _programMemory
}
export const _executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager
): Promise<ProgramMemory> => {
try {
const memory: ProgramMemory = await execute_wasm(
JSON.stringify(node),
JSON.stringify(programMemory),
engineCommandManager
)
return memory
} 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
}
}

View File

@ -1,6 +1,5 @@
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
import { parser_wasm } from './abstractSyntaxTree'
import { initPromise } from './rust'
import { Identifier, parse, initPromise } from './wasm'
beforeAll(() => initPromise)
@ -20,11 +19,88 @@ const sk3 = startSketchAt([0, 0])
lineToSubstringIndex + subStr.length,
]
const ast = parser_wasm(code)
const ast = parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const { node } = getNodeFromPath<any>(ast, nodePath)
expect([node.start, node.end]).toEqual(sourceRange)
expect(node.type).toBe('CallExpression')
})
it('gets path right for function definition params', () => {
const code = `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 subStr = 'pos, scale'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
subStrIndex,
subStrIndex + 'pos'.length,
]
const ast = parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Identifier>(ast, nodePath).node
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['params', 'FunctionExpression'],
[0, 'index'],
])
expect(node.type).toBe('Identifier')
expect(node.name).toBe('pos')
})
it('gets path right for deep within function definition body', () => {
const code = `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 subStr = 'scale, 0'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
subStrIndex,
subStrIndex + 'scale'.length,
]
const ast = parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Identifier>(ast, nodePath).node
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'FunctionExpression'],
['body', 'FunctionExpression'],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
['elements', 'ArrayExpression'],
[0, 'index'],
])
expect(node.type).toBe('Identifier')
expect(node.name).toBe('scale')
})
})

View File

@ -1,4 +1,4 @@
import { parser_wasm } from './abstractSyntaxTree'
import { parse, recast, initPromise } from './wasm'
import {
createLiteral,
createIdentifier,
@ -13,8 +13,6 @@ import {
giveSketchFnCallTag,
moveValueIntoNewVariable,
} from './modifyAst'
import { recast } from './recast'
import { initPromise } from './rust'
import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(() => initPromise)
@ -106,13 +104,13 @@ describe('Testing addSketchTo', () => {
body: [],
start: 0,
end: 0,
nonCodeMeta: { nonCodeNodes: {}, start: null },
nonCodeMeta: { nonCodeNodes: {}, start: [] },
},
'yz'
)
const str = recast(result.modifiedAst)
expect(str).toBe(`const part001 = startSketchAt('default')
|> ry(90, %)
expect(str).toBe(`const part001 = startSketchOn('YZ')
|> startProfileAt('default', %)
|> line('default', %)
show(part001)
`)
@ -126,7 +124,7 @@ function giveSketchFnCallTagTestHelper(
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
// this wrapper changes the input and output to code
// making it more of an integration test, but easier to read the test intention is the goal
const ast = parser_wasm(code)
const ast = parse(code)
const start = code.indexOf(searchStr)
const range: [number, number] = [start, start + searchStr.length]
const { modifiedAst, tag, isTagExisting } = giveSketchFnCallTag(ast, range)
@ -135,7 +133,8 @@ function giveSketchFnCallTagTestHelper(
}
describe('Testing giveSketchFnCallTag', () => {
const code = `const part001 = startSketchAt([0, 0])
const code = `const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([-2.57, -0.13], %)
|> line([0, 0.83], %)
|> line([0.82, 0.34], %)
@ -187,7 +186,8 @@ fn ghi = (x) => {
const abc = 3
const identifierGuy = 5
const yo = 5 + 6
const part001 = startSketchAt([-1.2, 4.83])
const part001 = startSketchOn('XY')
|> startProfileAt([-1.2, 4.83], %)
|> line([2.8, 0], %)
|> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %)
@ -197,7 +197,7 @@ const part001 = startSketchAt([-1.2, 4.83])
const yo2 = hmm([identifierGuy + 5])
show(part001)`
it('should move a binary expression into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable(
@ -211,7 +211,7 @@ show(part001)`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable(
@ -225,7 +225,7 @@ show(part001)`
expect(newCode).toContain(`line([newVar, 0], %)`)
})
it('should move a callExpression into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable(
@ -239,7 +239,7 @@ show(part001)`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a binary expression with call expression into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable(
@ -253,7 +253,7 @@ show(part001)`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a identifier into a new variable', async () => {
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable(

View File

@ -14,14 +14,15 @@ import {
ObjectExpression,
UnaryExpression,
BinaryExpression,
} from './abstractSyntaxTreeTypes'
PathToNode,
ProgramMemory,
} from './wasm'
import {
findAllPreviousVariables,
getNodeFromPath,
getNodePathFromSourceRange,
isNodeSafeToReplace,
} from './queryAst'
import { PathToNode, ProgramMemory } from './executor'
import {
addTagForSketchOnFace,
getFirstArg,
@ -31,21 +32,26 @@ import { isLiteralArrayOrStatic } from './std/sketchcombos'
export function addStartSketch(
node: Program,
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz',
start: [number, number],
end: [number, number]
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
const _node = { ...node }
const _name = findUniqueName(node, 'part')
const startSketchAt = createCallExpression('startSketchAt', [
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
createLiteral(axis.toUpperCase()),
])
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
createPipeSubstitution(),
])
const initialLineTo = createCallExpression('line', [
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
createPipeSubstitution(),
])
const pipeBody = [startSketchAt, initialLineTo]
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
const variableDeclaration = createVariableDeclaration(
_name,
@ -78,11 +84,11 @@ export function addSketchTo(
const _node = { ...node }
const _name = name || findUniqueName(node, 'part')
const startSketchAt = createCallExpressionStdLib('startSketchAt', [
createLiteral('default'),
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
createLiteral(axis.toUpperCase()),
])
const rotate = createCallExpression(axis === 'xz' ? 'rx' : 'ry', [
createLiteral(90),
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createLiteral('default'),
createPipeSubstitution(),
])
const initialLineTo = createCallExpressionStdLib('line', [
@ -90,10 +96,7 @@ export function addSketchTo(
createPipeSubstitution(),
])
const pipeBody =
axis !== 'xy'
? [startSketchAt, rotate, initialLineTo]
: [startSketchAt, initialLineTo]
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
const variableDeclaration = createVariableDeclaration(
_name,
@ -321,7 +324,7 @@ export function extrudeSketch(
[0, 'index'],
]
return {
modifiedAst: addToShow(_node, name),
modifiedAst: node,
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
pathToExtrudeArg,
}
@ -537,7 +540,7 @@ export function createPipeExpression(
start: 0,
end: 0,
body,
nonCodeMeta: { nonCodeNodes: {}, start: null },
nonCodeMeta: { nonCodeNodes: {}, start: [] },
}
}

View File

@ -1,11 +1,12 @@
import { parser_wasm } from './abstractSyntaxTree'
import { parse, recast, initPromise } from './wasm'
import {
findAllPreviousVariables,
isNodeSafeToReplace,
isTypeInValue,
getNodePathFromSourceRange,
doesPipeHaveCallExp,
hasExtrudeSketchGroup,
} from './queryAst'
import { initPromise } from './rust'
import { enginelessExecutor } from '../lib/testHelpers'
import {
createArrayExpression,
@ -13,7 +14,6 @@ import {
createLiteral,
createPipeSubstitution,
} from './modifyAst'
import { recast } from './recast'
beforeAll(() => initPromise)
@ -28,7 +28,8 @@ const halfArmAngle = armAngle / 2
const arrExpShouldNotBeIncluded = [1, 2, 3]
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> yLineTo(1, %)
|> xLine(3.84, %) // selection-range-7ish-before-this
@ -36,7 +37,7 @@ const variableBelowShouldNotBeIncluded = 3
show(part001)`
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
@ -59,7 +60,8 @@ show(part001)`
})
describe('testing argIsNotIdentifier', () => {
const code = `const part001 = startSketchAt([-1.2, 4.83])
const code = `const part001 = startSketchOn('XY')
|> startProfileAt([-1.2, 4.83], %)
|> line([2.8, 0], %)
|> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %)
@ -70,7 +72,7 @@ const yo = 5 + 6
const yo2 = hmm([identifierGuy + 5])
show(part001)`
it('find a safe binaryExpression', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('100 + 100') + 2
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -84,7 +86,7 @@ show(part001)`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe Identifier', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('abc')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -92,7 +94,7 @@ show(part001)`
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
})
it('find a safe CallExpression', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('def')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -106,7 +108,7 @@ show(part001)`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('ghi')
const range: [number, number] = [rangeStart, rangeStart]
const result = isNodeSafeToReplace(ast, range)
@ -115,7 +117,7 @@ show(part001)`
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
})
it('find an UNsafe Identifier, as it is a callee', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('ine([2.8,')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(false)
@ -125,7 +127,7 @@ show(part001)`
)
})
it("find a safe BinaryExpression that's assigned to a variable", () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('5 + 6') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -139,7 +141,7 @@ show(part001)`
expect(outCode).toContain(`const yo = replaceName`)
})
it('find a safe BinaryExpression that has a CallExpression within', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('jkl') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -155,7 +157,7 @@ show(part001)`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe BinaryExpression within a CallExpression', () => {
const ast = parser_wasm(code)
const ast = parse(code)
const rangeStart = code.indexOf('identifierGuy') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
@ -196,14 +198,15 @@ show(part001)`
})
describe('testing getNodePathFromSourceRange', () => {
const code = `const part001 = startSketchAt([0.39, -0.05])
const code = `const part001 = startSketchOn('XY')
|> startProfileAt([0.39, -0.05], %)
|> line([0.94, 2.61], %)
|> line([-0.21, -1.4], %)
show(part001)`
it('finds the second line when cursor is put at the end', () => {
const searchLn = `line([0.94, 2.61], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = parser_wasm(code)
const ast = parse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
@ -212,13 +215,13 @@ show(part001)`
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[1, 'index'],
[2, 'index'],
])
})
it('finds the last line when cursor is put at the end', () => {
const searchLn = `line([-0.21, -1.4], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = parser_wasm(code)
const ast = parse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const expected = [
['body', ''],
@ -227,7 +230,7 @@ show(part001)`
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
[3, 'index'],
]
expect(result).toEqual(expected)
// expect similar result for start of line
@ -245,3 +248,114 @@ show(part001)`
expect(selectWholeThing).toEqual(expected)
})
})
describe('testing doesPipeHave', () => {
it('finds close', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> close(%)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(true)
})
it('finds extrude', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> close(%)
|> extrude(1, %)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'extrude',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(true)
})
it('does NOT find close', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(false)
})
it('returns false if not a pipe', () => {
const exampleCode = `const length001 = 2`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [9, 10] },
})
expect(result).toEqual(false)
})
})
describe('testing hasExtrudeSketchGroup', () => {
it('find sketch group', async () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [100, 101] },
programMemory,
})
expect(result).toEqual(true)
})
it('find extrude group', async () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> extrude(1, %)`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [100, 101] },
programMemory,
})
expect(result).toEqual(true)
})
it('finds nothing', async () => {
const exampleCode = `const length001 = 2`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [10, 11] },
programMemory,
})
expect(result).toEqual(false)
})
})

View File

@ -1,4 +1,3 @@
import { PathToNode, ProgramMemory, SketchGroup, SourceRange } from './executor'
import { Selection, ToolTip } from '../useStore'
import {
BinaryExpression,
@ -10,7 +9,12 @@ import {
VariableDeclaration,
ReturnStatement,
ArrayExpression,
} from './abstractSyntaxTreeTypes'
PathToNode,
ProgramMemory,
SketchGroup,
SourceRange,
PipeExpression,
} from './wasm'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
import { getAngle } from '../lib/utils'
@ -239,7 +243,29 @@ function moreNodePathFromSourceRange(
}
return path
}
console.error('not implemented')
if (_node.type === 'FunctionExpression' && isInRange) {
for (let i = 0; i < _node.params.length; i++) {
const param = _node.params[i]
if (param.start <= start && param.end >= end) {
path.push(['params', 'FunctionExpression'])
path.push([i, 'index'])
return moreNodePathFromSourceRange(param, sourceRange, path)
}
}
if (_node.body.start <= start && _node.body.end >= end) {
path.push(['body', 'FunctionExpression'])
const fnBody = _node.body.body
for (let i = 0; i < fnBody.length; i++) {
const statement = fnBody[i]
if (statement.start <= start && statement.end >= end) {
path.push(['body', 'FunctionExpression'])
path.push([i, 'index'])
return moreNodePathFromSourceRange(statement, sourceRange, path)
}
}
}
}
console.error('not implemented: ' + node.type)
return path
}
@ -486,3 +512,47 @@ export function isLinesParallelAndConstrained(
}
}
}
export function doesPipeHaveCallExp({
ast,
selection,
calleeName,
}: {
calleeName: string
ast: Program
selection: Selection
}): boolean {
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const pipeExpression = getNodeFromPath<PipeExpression>(
ast,
pathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression') return false
return pipeExpression.body.some(
(expression) =>
expression.type === 'CallExpression' &&
expression.callee.name === calleeName
)
}
export function hasExtrudeSketchGroup({
ast,
selection,
programMemory,
}: {
ast: Program
selection: Selection
programMemory: ProgramMemory
}): boolean {
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const varDec = getNodeFromPath<VariableDeclaration>(
ast,
pathToNode,
'VariableDeclaration'
).node
if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declarations[0].id.name
const varValue = programMemory?.root[varName]
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
}

View File

@ -1,8 +1,5 @@
import { recast } from './recast'
import { parser_wasm } from './abstractSyntaxTree'
import { Program } from './abstractSyntaxTreeTypes'
import { parse, Program, recast, initPromise } from './wasm'
import fs from 'node:fs'
import { initPromise } from './rust'
beforeAll(() => initPromise)
@ -275,21 +272,20 @@ const mySk1 = startSketchAt([0, 0])
`
const { ast } = code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(`// comment at start
expect(recasted).toBe(`/* comment at start */
const mySk1 = startSketchAt([0, 0])
|> lineTo([1, 1], %)
// comment here
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|> lineTo([1, 1], %)
/* and
here
a comment between pipe expression statements */
|> lineTo([1, 1], %) /* and
here */
// a comment between pipe expression statements
|> rx(90, %)
// and another with just white space between others below
|> ry(45, %)
|> rx(45, %)
// one more for good measure
/* one more for good measure */
`)
})
})
@ -366,6 +362,6 @@ describe('it recasts binary expression using brackets where needed', () => {
// helpers
function code2ast(code: string): { ast: Program } {
const ast = parser_wasm(code)
const ast = parse(code)
return { ast }
}

View File

@ -1,13 +0,0 @@
import { Program } from './abstractSyntaxTreeTypes'
import { recast_wasm } from '../wasm-lib/pkg/wasm_lib'
export const recast = (ast: Program): string => {
try {
const s: string = recast_wasm(JSON.stringify(ast))
return s
} catch (e) {
// TODO: do something real with the error.
console.log('recast', e)
throw e
}
}

View File

@ -1,20 +0,0 @@
import init from '../wasm-lib/pkg/wasm_lib'
const initialise = async () => {
const baseUrl =
typeof window === 'undefined'
? 'http://127.0.0.1:3000'
: window.location.origin.includes('tauri://localhost')
? 'tauri://localhost'
: window.location.origin.includes('localhost')
? 'http://localhost:3000'
: window.location.origin && window.location.origin !== 'null'
? window.location.origin
: 'http://localhost:3000'
const fullUrl = baseUrl + '/wasm_lib_bg.wasm'
const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer()
return init(buffer)
}
export const initPromise = initialise()

View File

@ -1,4 +1,9 @@
import { ProgramMemory, SourceRange } from 'lang/executor'
import {
ProgramMemory,
SourceRange,
Program,
VariableDeclarator,
} from 'lang/wasm'
import { Selections } from 'useStore'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
import { Models } from '@kittycad/lib'
@ -6,7 +11,7 @@ import { exportSave } from 'lib/exportSave'
import { v4 as uuidv4 } from 'uuid'
import * as Sentry from '@sentry/react'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
let lastMessage = ''
@ -23,6 +28,10 @@ interface ResultCommand extends CommandInfo {
data: any
raw: WebSocketResponse
}
interface FailedCommand extends CommandInfo {
type: 'failed'
errors: Models['FailureWebSocketResponse_type']['errors']
}
interface PendingCommand extends CommandInfo {
type: 'pending'
promise: Promise<any>
@ -30,7 +39,7 @@ interface PendingCommand extends CommandInfo {
}
export interface ArtifactMap {
[key: string]: ResultCommand | PendingCommand
[key: string]: ResultCommand | PendingCommand | FailedCommand
}
export interface SourceRangeMap {
[key: string]: SourceRange
@ -41,6 +50,11 @@ interface NewTrackArgs {
mediaStream: MediaStream
}
// This looks funny, I know. This is needed because node and the browser
// disagree as to the type. In a browser it's a number, but in node it's a
// "Timeout".
type Timeout = ReturnType<typeof setTimeout>
type ClientMetrics = Models['ClientMetrics_type']
// EngineConnection encapsulates the connection(s) to the Engine
@ -52,6 +66,9 @@ export class EngineConnection {
unreliableDataChannel?: RTCDataChannel
private ready: boolean
private connecting: boolean
private dead: boolean
private failedConnTimeout: Timeout | null
readonly url: string
private readonly token?: string
@ -87,6 +104,9 @@ export class EngineConnection {
this.url = url
this.token = token
this.ready = false
this.connecting = false
this.dead = false
this.failedConnTimeout = null
this.onWebsocketOpen = onWebsocketOpen
this.onDataChannelOpen = onDataChannelOpen
this.onEngineConnectionOpen = onEngineConnectionOpen
@ -97,7 +117,10 @@ export class EngineConnection {
// TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 10000
setInterval(() => {
let pingInterval = setInterval(() => {
if (this.dead) {
clearInterval(pingInterval)
}
if (this.isReady()) {
// When we're online, every 10 seconds, we'll attempt to put a 'ping'
// command through the WebSocket connection. This will help both ends
@ -106,6 +129,24 @@ export class EngineConnection {
this.send({ type: 'ping' })
}
}, pingIntervalMs)
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
let connectInterval = setInterval(() => {
if (this.dead) {
clearInterval(connectInterval)
return
}
if (this.isReady()) {
return
}
console.log('connecting via retry')
this.connect()
}, connectionTimeoutMs)
}
// isConnecting will return true when connect has been called, but the full
// WebRTC is not online.
isConnecting() {
return this.connecting
}
// isReady will return true only when the WebRTC *and* WebSocket connection
// are connected. During setup, the WebSocket connection comes online first,
@ -114,6 +155,10 @@ export class EngineConnection {
isReady() {
return this.ready
}
tearDown() {
this.dead = true
this.close()
}
// shouldTrace will return true when Sentry should be used to instrument
// the Engine.
shouldTrace() {
@ -125,8 +170,10 @@ export class EngineConnection {
// This will attempt the full handshake, and retry if the connection
// did not establish.
connect() {
// TODO(paultag): make this safe to call multiple times, and figure out
// when a connection is in progress (state: connecting or something).
console.log('connect was called')
if (this.isConnecting() || this.isReady()) {
return
}
// Information on the connect transaction
@ -240,14 +287,18 @@ export class EngineConnection {
const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
if (!message.success) {
const errorsString = message?.errors
?.map((error) => {
return ` - ${error.error_code}: ${error.message}`
})
.join('\n')
if (message.request_id) {
console.error(`Error in response to request ${message.request_id}:`)
console.error(
`Error in response to request ${message.request_id}:\n${errorsString}`
)
} else {
console.error(`Error from server:`)
console.error(`Error from server:\n${errorsString}`)
}
message?.errors?.forEach((error) => {
console.error(` - ${error.error_code}: ${error.message}`)
})
return
}
@ -314,6 +365,11 @@ export class EngineConnection {
if (this.shouldTrace()) {
iceSpan.resolve?.()
}
} else if (this.pc?.iceConnectionState === 'failed') {
// failed is a terminal state; let's explicitly kill the
// connection to the server at this point.
console.log('failed to negotiate ice connection; restarting')
this.close()
}
})
@ -358,20 +414,6 @@ export class EngineConnection {
})
})
}
// TODO(paultag): This ought to be both controllable, as well as something
// like exponential backoff to have some grace on the backend, as well as
// fix responsiveness for clients that had a weird network hiccup.
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
setTimeout(() => {
if (this.isReady()) {
return
}
console.log('engine connection timeout on connection, retrying')
this.close()
this.connect()
}, connectionTimeoutMs)
})
this.pc.addEventListener('track', (event) => {
@ -459,8 +501,11 @@ export class EngineConnection {
this.onDataChannelOpen(this)
this.onEngineConnectionOpen(this)
this.ready = true
this.connecting = false
// Do this after we set the connection is ready to avoid errors when
// we try to send messages before the connection is ready.
this.onEngineConnectionOpen(this)
})
this.unreliableDataChannel.addEventListener('close', (event) => {
@ -474,6 +519,22 @@ export class EngineConnection {
})
})
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
if (this.failedConnTimeout) {
console.log('clearing timeout before set')
clearTimeout(this.failedConnTimeout)
this.failedConnTimeout = null
}
console.log('timeout set')
this.failedConnTimeout = setTimeout(() => {
if (this.isReady()) {
return
}
console.log('engine connection timeout on connection, closing')
this.close()
}, connectionTimeoutMs)
this.onConnectionStarted(this)
}
unreliableSend(message: object | string) {
@ -498,9 +559,15 @@ export class EngineConnection {
this.pc = undefined
this.unreliableDataChannel = undefined
this.webrtcStatsCollector = undefined
if (this.failedConnTimeout) {
console.log('closed timeout in close')
clearTimeout(this.failedConnTimeout)
this.failedConnTimeout = null
}
this.onClose(this)
this.ready = false
this.connecting = false
}
}
@ -531,8 +598,14 @@ export class EngineCommandManager {
outSequence = 1
inSequence = 1
engineConnection?: EngineConnection
waitForReady: Promise<void> = new Promise(() => {})
defaultPlanes: DefaultPlanes = { xy: '', yz: '', xz: '' }
// Folks should realize that wait for ready does not get called _everytime_
// the connection resets and restarts, it only gets called the first time.
// Be careful what you put here.
private resolveReady = () => {}
waitForReady: Promise<void> = new Promise((resolve) => {
this.resolveReady = resolve
})
subscriptions: {
[event: string]: {
@ -544,22 +617,36 @@ export class EngineCommandManager {
[localUnsubscribeId: string]: (a: any) => void
}
} = {} as any
constructor({
constructor() {
this.engineConnection = undefined
}
start({
setMediaStream,
setIsStreamReady,
width,
height,
executeCode,
token,
}: {
setMediaStream: (stream: MediaStream) => void
setIsStreamReady: (isStreamReady: boolean) => void
width: number
height: number
executeCode: (code?: string, force?: boolean) => void
token?: string
}) {
this.waitForReady = new Promise((resolve) => {
this.resolveReady = resolve
})
if (width === 0 || height === 0) {
return
}
// If we already have an engine connection, just need to resize the stream.
if (this.engineConnection) {
this.handleResize({ streamWidth: width, streamHeight: height })
return
}
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}`
this.engineConnection = new EngineConnection({
url,
@ -567,6 +654,35 @@ export class EngineCommandManager {
onEngineConnectionOpen: () => {
this.resolveReady()
setIsStreamReady(true)
// Make the axis gizmo.
// We do this after the connection opened to avoid a race condition.
// Connected opened is the last thing that happens when the stream
// is ready.
// We also do this here because we want to ensure we create the gizmo
// and execute the code everytime the stream is restarted.
const gizmoId = uuidv4()
this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: gizmoId,
cmd: {
type: 'make_axes_gizmo',
clobber: false,
// If true, axes gizmo will be placed in the corner of the screen.
// If false, it will be placed at the origin of the scene.
gizmo_mode: true,
},
})
// Inisialize the planes.
this.initPlanes().then(() => {
// We execute the code here to make sure if the stream was to
// restart in a session, we want to make sure to execute the code.
// We force it to re-execute the code because we want to make sure
// the code is executed everytime the stream is restarted.
// We pass undefined for the code so it reads from the current state.
executeCode(undefined, true)
})
},
onClose: () => {
setIsStreamReady(false)
@ -617,6 +733,12 @@ export class EngineCommandManager {
message.request_id
) {
this.handleModelingCommand(message.resp, message.request_id)
} else if (
!message.success &&
message.request_id &&
this.artifactMap[message.request_id]
) {
this.handleFailedModelingCommand(message)
}
}
})
@ -636,6 +758,30 @@ export class EngineCommandManager {
this.engineConnection?.connect()
}
handleResize({
streamWidth,
streamHeight,
}: {
streamWidth: number
streamHeight: number
}) {
console.log('handleResize', streamWidth, streamHeight)
if (!this.engineConnection?.isReady()) {
return
}
const resizeCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'reconfigure_stream',
width: streamWidth,
height: streamHeight,
fps: 60,
},
}
this.engineConnection?.send(resizeCmd)
}
handleModelingCommand(message: WebSocketResponse, id: string) {
if (message.type !== 'modeling') {
return
@ -673,8 +819,40 @@ export class EngineCommandManager {
}
}
}
handleFailedModelingCommand({
request_id,
errors,
}: Models['FailureWebSocketResponse_type']) {
const id = request_id
if (!id) return
const command = this.artifactMap[id]
if (command && command.type === 'pending') {
const resolve = command.resolve
this.artifactMap[id] = {
type: 'failed',
range: command.range,
commandType: command.commandType,
parentId: command.parentId ? command.parentId : undefined,
errors,
}
resolve({
id,
commandType: command.commandType,
range: command.range,
errors,
})
} else {
this.artifactMap[id] = {
type: 'failed',
range: command.range,
commandType: command.commandType,
parentId: command.parentId ? command.parentId : undefined,
errors,
}
}
}
tearDown() {
this.engineConnection?.close()
this.engineConnection?.tearDown()
}
startNewSession() {
this.artifactMap = {}
@ -769,6 +947,14 @@ export class EngineCommandManager {
})
}
sendSceneCommand(command: EngineCommand): Promise<any> {
if (this.engineConnection === undefined) {
return Promise.resolve()
}
if (!this.engineConnection?.isReady()) {
return Promise.resolve()
}
if (
command.type === 'modeling_cmd_req' &&
command.cmd.type !== lastMessage
@ -776,10 +962,6 @@ export class EngineCommandManager {
console.log('sending command', command.cmd.type)
lastMessage = command.cmd.type
}
if (!this.engineConnection?.isReady()) {
console.log('socket not ready')
return Promise.resolve()
}
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
const cmd = command.cmd
if (
@ -821,10 +1003,12 @@ export class EngineCommandManager {
range: SourceRange
command: EngineCommand | string
}): Promise<any> {
if (this.engineConnection === undefined) {
return Promise.resolve()
}
this.sourceRangeMap[id] = range
if (!this.engineConnection?.isReady()) {
console.log('socket not ready')
return Promise.resolve()
}
this.engineConnection?.send(command)
@ -867,6 +1051,9 @@ export class EngineCommandManager {
rangeStr: string,
commandStr: string
): Promise<any> {
if (this.engineConnection === undefined) {
return Promise.resolve()
}
if (id === undefined) {
throw new Error('id is undefined')
}
@ -890,6 +1077,8 @@ export class EngineCommandManager {
}
if (command.type === 'result') {
return command.data
} else if (command.type === 'failed') {
return Promise.resolve(command.errors)
}
return command.promise
}
@ -915,6 +1104,9 @@ export class EngineCommandManager {
}
}
private async fixIdMappings(ast: Program, programMemory: ProgramMemory) {
if (this.engineConnection === undefined) {
return
}
/* This is a temporary solution since the cmd_ids that are sent through when
sending 'extend_path' ids are not used as the segment ids.
@ -993,4 +1185,102 @@ export class EngineCommandManager {
}
})
}
private async initPlanes() {
const [xy, yz, xz] = [
await this.createPlane({
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 },
}),
await this.createPlane({
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 },
}),
await this.createPlane({
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 },
}),
]
this.defaultPlanes = { xy, yz, xz }
this.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) return
if (
![
this.defaultPlanes.xy,
this.defaultPlanes.yz,
this.defaultPlanes.xz,
].includes(data.entity_id)
)
return
this.onPlaneSelectCallback(data.entity_id)
},
})
}
planesInitialized(): boolean {
return (
this.defaultPlanes.xy !== '' &&
this.defaultPlanes.yz !== '' &&
this.defaultPlanes.xz !== ''
)
}
onPlaneSelectCallback = (id: string) => {}
onPlaneSelected(callback: (id: string) => void) {
this.onPlaneSelectCallback = callback
}
async setPlaneHidden(id: string, hidden: boolean): Promise<string> {
return await this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: id,
hidden: hidden,
},
})
}
private async createPlane({
x_axis,
y_axis,
color,
}: {
x_axis: Models['Point3d_type']
y_axis: Models['Point3d_type']
color: Models['Color_type']
}): Promise<string> {
const planeId = uuidv4()
await this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'make_plane',
size: 60,
origin: { x: 0, y: 0, z: 0 },
x_axis,
y_axis,
clobber: false,
hide: true,
},
cmd_id: planeId,
})
await this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'plane_set_color',
plane_id: planeId,
color,
},
cmd_id: uuidv4(),
})
await this.setPlaneHidden(planeId, true)
return planeId
}
}
export const engineCommandManager = new EngineCommandManager()

View File

@ -6,13 +6,9 @@ import {
getXComponent,
addCloseToPipe,
} from './sketch'
import { parser_wasm } from '../abstractSyntaxTree'
import { parse, recast, initPromise } from '../wasm'
import { getNodePathFromSourceRange } from '../queryAst'
import { recast } from '../recast'
import { enginelessExecutor } from '../../lib/testHelpers'
import { initPromise } from '../rust'
beforeAll(() => initPromise)
const eachQuad: [number, [number, number]][] = [
[-315, [1, 1]],
@ -29,6 +25,8 @@ const eachQuad: [number, [number, number]][] = [
[675, [1, -1]],
]
beforeAll(() => initPromise)
describe('testing getYComponent', () => {
it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
const expected: [number, number][] = []
@ -98,15 +96,16 @@ describe('testing changeSketchArguments', () => {
const lineAfterChange = 'lineTo([2, 3], %)'
test('changeSketchArguments', async () => {
// Enable rotations #152
const genCode = (line: string) => `const mySketch001 = startSketchAt([0, 0])
const genCode = (line: string) => `const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> ${line}
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
// |> rx(45, %)
show(mySketch001)
`
const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange)
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
const { modifiedAst } = changeSketchArguments(
@ -114,20 +113,6 @@ show(mySketch001)
programMemory,
[sourceStart, sourceStart + lineToChange.length],
[2, 3],
{
mode: 'sketch',
sketchMode: 'sketchEdit',
pathId: '',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
},
[0, 0]
)
expect(recast(modifiedAst)).toBe(expectedCode)
@ -139,15 +124,16 @@ describe('testing addNewSketchLn', () => {
test('addNewSketchLn', async () => {
// Enable rotations #152
const code = `
const mySketch001 = startSketchAt([0, 0])
const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
show(mySketch001)`
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
expect(sourceStart).toBe(66)
expect(sourceStart).toBe(95)
let { modifiedAst } = addNewSketchLn({
node: ast,
programMemory,
@ -162,7 +148,8 @@ show(mySketch001)`
],
})
// Enable rotations #152
let expectedCode = `const mySketch001 = startSketchAt([0, 0])
let expectedCode = `const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
@ -183,7 +170,8 @@ show(mySketch001)
],
})
expectedCode = `const mySketch001 = startSketchAt([0, 0])
expectedCode = `const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
@ -198,14 +186,15 @@ describe('testing addTagForSketchOnFace', () => {
it('needs to be in it', async () => {
const originalLine = 'lineTo([-1.59, -1.54], %)'
// Enable rotations #152
const genCode = (line: string) => `const mySketch001 = startSketchAt([0, 0])
const genCode = (line: string) => `const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
// |> rx(45, %)
|> ${line}
|> lineTo([0.46, -5.82], %)
show(mySketch001)
`
const code = genCode(originalLine)
const ast = parser_wasm(code)
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalLine)
const sourceRange: [number, number] = [

View File

@ -4,9 +4,6 @@ import {
SketchGroup,
SourceRange,
PathToNode,
MemoryItem,
} from '../executor'
import {
Program,
PipeExpression,
CallExpression,
@ -14,14 +11,14 @@ import {
Value,
Literal,
VariableDeclaration,
} from '../abstractSyntaxTreeTypes'
} from '../wasm'
import {
getNodeFromPath,
getNodeFromPathCurry,
getNodePathFromSourceRange,
} from '../queryAst'
import { isLiteralArrayOrStatic } from './sketchcombos'
import { GuiModes, toolTips, ToolTip } from '../../useStore'
import { toolTips, ToolTip } from '../../useStore'
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
import { generateUuidFromHashSeed } from '../../lib/uuid'
@ -38,7 +35,6 @@ import {
findUniqueName,
} from '../modifyAst'
import { roundOff, getLength, getAngle } from '../../lib/utils'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { perpendicularDistance } from 'sketch-helpers'
export type Coords2d = [number, number]
@ -912,7 +908,6 @@ export function changeSketchArguments(
programMemory: ProgramMemory,
sourceRange: SourceRange,
args: [number, number],
guiMode: GuiModes,
from: [number, number]
): { modifiedAst: Program } {
const _node = { ...node }
@ -921,7 +916,6 @@ export function changeSketchArguments(
_node,
thePath
)
if (guiMode.mode !== 'sketch') throw new Error('not in sketch mode')
if (callExpression?.callee?.name in sketchLineHelperMap) {
const { updateArgs } = sketchLineHelperMap[callExpression.callee.name]

View File

@ -1,12 +1,9 @@
import { parser_wasm } from '../abstractSyntaxTree'
import { SketchGroup } from '../executor'
import { parse, SketchGroup, recast, initPromise } from '../wasm'
import {
ConstraintType,
getTransformInfos,
transformAstSketchLines,
} from './sketchcombos'
import { recast } from '../recast'
import { initPromise } from '../rust'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { Selection } from '../../useStore'
import { enginelessExecutor } from '../../lib/testHelpers'
@ -31,7 +28,7 @@ async function testingSwapSketchFnCall({
type: 'default',
range: [startIndex, startIndex + callToSwap.length],
}
const ast = parser_wasm(inputCode)
const ast = parse(inputCode)
const programMemory = await enginelessExecutor(ast)
const selections = {
codeBasedSelections: [range],
@ -55,7 +52,8 @@ async function testingSwapSketchFnCall({
describe('testing swaping out sketch calls with xLine/xLineTo', () => {
const bigExampleArr = [
`const part001 = startSketchAt([0, 0])`,
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
` |> angledLine({`,
@ -280,7 +278,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
`const angledLineOfYLengthY = 0.89`,
`const angledLineToXx = -1.86`,
`const angledLineToYy = -0.76`,
`const part001 = startSketchAt([0, 0])`,
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
// ` |> rx(90, %)`,
` |> lineTo([1, 1], %)`,
` |> line([lineX, 2.13], %)`,
@ -374,14 +373,15 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
describe('testing getSketchSegmentIndexFromSourceRange', () => {
const code = `
const part001 = startSketchAt([0, 0.04]) // segment-in-start
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0.04], %) // segment-in-start
|> line([0, 0.4], %)
|> xLine(3.48, %)
|> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)
show(part001)`
it('normal case works', async () => {
const programMemory = await enginelessExecutor(parser_wasm(code))
const programMemory = await enginelessExecutor(parse(code))
const index = code.indexOf('// normal-segment') - 7
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
programMemory.root['part001'] as SketchGroup,
@ -395,7 +395,7 @@ show(part001)`
})
})
it('verify it works when the segment is in the `start` property', async () => {
const programMemory = await enginelessExecutor(parser_wasm(code))
const programMemory = await enginelessExecutor(parse(code))
const index = code.indexOf('// segment-in-start') - 7
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
programMemory.root['part001'] as SketchGroup,

View File

@ -3,8 +3,10 @@ import {
Program,
VariableDeclarator,
CallExpression,
} from '../abstractSyntaxTreeTypes'
import { SketchGroup, SourceRange, Path } from '../executor'
SketchGroup,
SourceRange,
Path,
} from '../wasm'
export function getSketchSegmentFromSourceRange(
sketchGroup: SketchGroup,

View File

@ -1,5 +1,4 @@
import { parser_wasm } from '../abstractSyntaxTree'
import { Value } from '../abstractSyntaxTreeTypes'
import { parse, Value, recast, initPromise } from '../wasm'
import {
getConstraintType,
getTransformInfos,
@ -8,10 +7,8 @@ import {
ConstraintType,
getConstraintLevelFromSourceRange,
} from './sketchcombos'
import { initPromise } from '../rust'
import { Selections, ToolTip } from '../../useStore'
import { enginelessExecutor } from '../../lib/testHelpers'
import { recast } from '../../lang/recast'
beforeAll(() => initPromise)
@ -63,7 +60,7 @@ describe('testing getConstraintType', () => {
function getConstraintTypeFromSourceHelper(
code: string
): ReturnType<typeof getConstraintType> {
const ast = parser_wasm(code)
const ast = parse(code)
const args = (ast.body[0] as any).expression.arguments[0].elements as [
Value,
Value
@ -74,7 +71,7 @@ function getConstraintTypeFromSourceHelper(
function getConstraintTypeFromSourceHelper2(
code: string
): ReturnType<typeof getConstraintType> {
const ast = parser_wasm(code)
const ast = parse(code)
const arg = (ast.body[0] as any).expression.arguments[0] as Value
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
return getConstraintType(arg, fnName)
@ -95,7 +92,8 @@ const myVar2 = 5
const myVar3 = 6
const myAng = 40
const myAng2 = 134
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([1, 3.82], %) // ln-should-get-tag
|> lineTo([myVar, 1], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|> lineTo([1, myVar], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
@ -131,7 +129,8 @@ const myVar2 = 5
const myVar3 = 6
const myAng = 40
const myAng2 = 134
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
@ -199,7 +198,7 @@ const part001 = startSketchAt([0, 0])
show(part001)
`
it('should transform the ast', async () => {
const ast = parser_wasm(inputScript)
const ast = parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('//'))
@ -234,7 +233,8 @@ describe('testing transformAstForSketchLines for vertical and horizontal constra
const inputScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|> line([-1.07, myVar], %) // select for vertical constraint 1
@ -262,7 +262,8 @@ show(part001)
const expectModifiedScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
|> xLine(-6.28, %) // select for horizontal constraint 1
|> line([-1.07, myVar], %) // select for vertical constraint 1
@ -286,7 +287,7 @@ const part001 = startSketchAt([0, 0])
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
show(part001)
`
const ast = parser_wasm(inputScript)
const ast = parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for horizontal constraint'))
@ -320,7 +321,8 @@ show(part001)
const expectModifiedScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|> yLine(myVar, %) // select for vertical constraint 1
@ -344,7 +346,7 @@ const part001 = startSketchAt([0, 0])
|> yLineTo(myVar, %) // select for vertical constraint 10
show(part001)
`
const ast = parser_wasm(inputScript)
const ast = parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for vertical constraint'))
@ -379,7 +381,8 @@ show(part001)
describe('testing transformAstForSketchLines for vertical and horizontal distance constraints', () => {
describe('testing setHorzDistance for line', () => {
const inputScript = `const myVar = 1
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0.31, 1.67], %) // base selection
|> line([0.45, 1.46], %)
|> line([0.45, 1.46], %) // free
@ -435,7 +438,7 @@ async function helperThing(
linesOfInterest: string[],
constraint: ConstraintType
): Promise<string> {
const ast = parser_wasm(inputScript)
const ast = parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) =>
@ -480,7 +483,8 @@ const baseThickHalf = baseThick / 2
const halfHeight = totalHeight / 2
const halfArmAngle = armAngle / 2
const part001 = startSketchAt([-0.01, -0.05])
const part001 = startSketchOn('XY')
|> startProfileAt([-0.01, -0.05], %)
|> line([0.01, 0.94 + 0], %) // partial
|> xLine(3.03, %) // partial
|> angledLine({
@ -498,7 +502,7 @@ const part001 = startSketchAt([-0.01, -0.05])
|> xLine(-3.43 + 0, %) // full
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full
show(part001)`
const ast = parser_wasm(code)
const ast = parse(code)
const constraintLevels: ReturnType<
typeof getConstraintLevelFromSourceRange
>[] = ['full', 'partial', 'free']

View File

@ -6,7 +6,9 @@ import {
Value,
BinaryPart,
VariableDeclarator,
} from '../abstractSyntaxTreeTypes'
PathToNode,
ProgramMemory,
} from '../wasm'
import {
getNodeFromPath,
getNodeFromPathCurry,
@ -25,10 +27,8 @@ import {
giveSketchFnCallTag,
} from '../modifyAst'
import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
import { PathToNode, ProgramMemory } from '../executor'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
import { MemoryItem } from 'wasm-lib/kcl/bindings/MemoryItem'
type LineInputsType =
| 'xAbsolute'
@ -1279,7 +1279,7 @@ export function getTransformInfos(
}) as TransformInfo[]
return theTransforms
} catch (error) {
console.log(error)
console.log('error', error)
return []
}
}
@ -1306,7 +1306,7 @@ export function getRemoveConstraintsTransforms(
return theTransforms
}
type PathToNodeMap = { [key: number]: PathToNode }
export type PathToNodeMap = { [key: number]: PathToNode }
export function transformSecondarySketchLinesTagFirst({
ast,

View File

@ -1,12 +1,12 @@
import { parser_wasm } from '../abstractSyntaxTree'
import { parse, initPromise } from '../wasm'
import { enginelessExecutor } from '../../lib/testHelpers'
import { initPromise } from '../rust'
beforeAll(() => initPromise)
describe('testing angledLineThatIntersects', () => {
it('angledLineThatIntersects should intersect with another line', async () => {
const code = (offset: string) => `const part001 = startSketchAt([0, 0])
const code = (offset: string) => `const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo({to:[2, 2], tag: "yo"}, %)
|> lineTo([3, 1], %)
|> angledLineThatIntersects({
@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
}, %)
const intersect = segEndX('yo2', part001)
show(part001)`
const { root } = await enginelessExecutor(parser_wasm(code('-1')))
const { root } = await enginelessExecutor(parse(code('-1')))
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
const { root: noOffset } = await enginelessExecutor(parser_wasm(code('0')))
const { root: noOffset } = await enginelessExecutor(parse(code('0')))
expect(noOffset.intersect.value).toBeCloseTo(1)
})
})

View File

@ -1,7 +1,12 @@
import { ProgramMemory, Path, SourceRange } from '../executor'
import { Program, Value } from '../abstractSyntaxTreeTypes'
import {
ProgramMemory,
Path,
SourceRange,
Program,
Value,
PathToNode,
} from '../wasm'
import { ToolTip } from '../../useStore'
import { PathToNode } from '../executor'
import { EngineCommandManager } from './engineConnection'
export interface InternalFirstArg {

View File

@ -1,5 +1,4 @@
import { lexer, asyncLexer } from './tokeniser'
import { initPromise } from './rust'
import { lexer, initPromise } from './wasm'
beforeAll(() => initPromise)
@ -10,9 +9,9 @@ describe('testing lexer', () => {
const code3 = `const yo = 45 /* this is a comment
const ya = 6 */
const yi=45`
expect(await asyncLexer(code)).toEqual(lexer(code))
expect(await asyncLexer(code2)).toEqual(lexer(code2))
expect(await asyncLexer(code3)).toEqual(lexer(code3))
expect(lexer(code)).toEqual(lexer(code))
expect(lexer(code2)).toEqual(lexer(code2))
expect(lexer(code3)).toEqual(lexer(code3))
})
it('test lexer', () => {
expect(stringSummaryLexer('1 + 2')).toEqual([

View File

@ -1,28 +0,0 @@
import { lexer_js } from '../wasm-lib/pkg/wasm_lib'
import { initPromise } from './rust'
import { Token } from '../wasm-lib/kcl/bindings/Token'
export type { Token } from '../wasm-lib/kcl/bindings/Token'
export async function asyncLexer(str: string): Promise<Token[]> {
await initPromise
try {
const tokens: Token[] = lexer_js(str)
return tokens
} catch (e) {
// TODO: do something real with the error.
console.log('lexer', e)
throw e
}
}
export function lexer(str: string): Token[] {
try {
const tokens: Token[] = lexer_js(str)
return tokens
} catch (e) {
// TODO: do something real with the error.
console.log('lexer', e)
throw e
}
}

View File

@ -1,30 +1,29 @@
import { Selections, StoreState } from '../useStore'
import { Program } from './abstractSyntaxTreeTypes'
import { PathToNode } from './executor'
import { Program, PathToNode } from './wasm'
import { getNodeFromPath } from './queryAst'
import { ArtifactMap } from './std/engineConnection'
import { isOverlap } from 'lib/utils'
export function updateCursors(
setCursor: StoreState['setCursor'],
selectionRanges: Selections,
export function pathMapToSelections(
ast: Program,
prevSelections: Selections,
pathToNodeMap: { [key: number]: PathToNode }
): (newAst: Program) => void {
return (newAst: Program) => {
const newSelections: Selections = {
...selectionRanges,
codeBasedSelections: [],
}
Object.entries(pathToNodeMap).forEach(([index, path]) => {
const node = getNodeFromPath(newAst, path).node as any
const type = selectionRanges.codeBasedSelections[Number(index)].type
if (node) {
newSelections.codeBasedSelections.push({
range: [node.start, node.end],
type: type || 'default',
})
}
})
setCursor(newSelections)
): Selections {
const newSelections: Selections = {
...prevSelections,
codeBasedSelections: [],
}
Object.entries(pathToNodeMap).forEach(([index, path]) => {
const node = getNodeFromPath(ast, path).node as any
const type = prevSelections.codeBasedSelections[Number(index)].type
if (node) {
newSelections.codeBasedSelections.push({
range: [node.start, node.end],
type: type || 'default',
})
}
})
return newSelections
}
export function isReducedMotion(): boolean {
@ -35,3 +34,27 @@ export function isReducedMotion(): boolean {
window.matchMedia('(prefers-reduced-motion)').matches
)
}
export function isCursorInSketchCommandRange(
artifactMap: ArtifactMap,
selectionRanges: Selections
): string | false {
const overlapingEntries: [string, ArtifactMap[string]][] = Object.entries(
artifactMap
).filter(([id, artifact]: [string, ArtifactMap[string]]) =>
selectionRanges.codeBasedSelections.some(
(selection) =>
Array.isArray(selection?.range) &&
Array.isArray(artifact?.range) &&
isOverlap(selection.range, artifact.range) &&
(artifact.commandType === 'start_path' ||
artifact.commandType === 'extend_path' ||
artifact.commandType === 'close_path')
)
)
return overlapingEntries.length && overlapingEntries[0][1].parentId
? overlapingEntries[0][1].parentId
: overlapingEntries.find(
([, artifact]) => artifact.commandType === 'start_path'
)?.[0] || false
}

222
src/lang/wasm.ts Normal file
View File

@ -0,0 +1,222 @@
import init, {
parse_wasm,
recast_wasm,
execute_wasm,
lexer_wasm,
modify_ast_for_sketch_wasm,
} from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import {
EngineCommandManager,
ArtifactMap,
SourceRangeMap,
} from './std/engineConnection'
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
import type { Program } from '../wasm-lib/kcl/bindings/Program'
import type { Token } from '../wasm-lib/kcl/bindings/Token'
import { DefaultPlanes } from '../wasm-lib/kcl/bindings/DefaultPlanes'
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Value } from '../wasm-lib/kcl/bindings/Value'
export type { ObjectExpression } from '../wasm-lib/kcl/bindings/ObjectExpression'
export type { MemberExpression } from '../wasm-lib/kcl/bindings/MemberExpression'
export type { PipeExpression } from '../wasm-lib/kcl/bindings/PipeExpression'
export type { VariableDeclaration } from '../wasm-lib/kcl/bindings/VariableDeclaration'
export type { PipeSubstitution } from '../wasm-lib/kcl/bindings/PipeSubstitution'
export type { Identifier } from '../wasm-lib/kcl/bindings/Identifier'
export type { UnaryExpression } from '../wasm-lib/kcl/bindings/UnaryExpression'
export type { BinaryExpression } from '../wasm-lib/kcl/bindings/BinaryExpression'
export type { ReturnStatement } from '../wasm-lib/kcl/bindings/ReturnStatement'
export type { ExpressionStatement } from '../wasm-lib/kcl/bindings/ExpressionStatement'
export type { CallExpression } from '../wasm-lib/kcl/bindings/CallExpression'
export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator'
export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
export type SyntaxType =
| 'Program'
| 'ExpressionStatement'
| 'BinaryExpression'
| 'CallExpression'
| 'Identifier'
| 'ReturnStatement'
| 'VariableDeclaration'
| 'VariableDeclarator'
| 'MemberExpression'
| 'ArrayExpression'
| 'ObjectExpression'
| 'ObjectProperty'
| 'FunctionExpression'
| 'PipeExpression'
| 'PipeSubstitution'
| 'Literal'
| 'NonCodeNode'
| 'UnaryExpression'
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
export type { Position } from '../wasm-lib/kcl/bindings/Position'
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
export type { Path } from '../wasm-lib/kcl/bindings/Path'
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
// Initialise the wasm module.
const initialise = async () => {
const baseUrl =
typeof window === 'undefined'
? 'http://127.0.0.1:3000'
: window.location.origin.includes('tauri://localhost')
? 'tauri://localhost'
: window.location.origin.includes('localhost')
? 'http://localhost:3000'
: window.location.origin && window.location.origin !== 'null'
? window.location.origin
: 'http://localhost:3000'
const fullUrl = baseUrl + '/wasm_lib_bg.wasm'
const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer()
return init(buffer)
}
export const initPromise = initialise()
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
ranges.map(([start, end]) => [start, end])
export const parse = (code: string): Program => {
try {
const program: Program = parse_wasm(code)
return program
} 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
}
}
export type PathToNode = [string | number, string][]
interface Memory {
[key: string]: MemoryItem
}
export interface ProgramMemory {
root: Memory
return: ProgramReturn | null
}
export const executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager,
planes: DefaultPlanes,
// work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added.
tempMapCallback: (a: {
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}) => void = () => {}
): Promise<ProgramMemory> => {
engineCommandManager.startNewSession()
const _programMemory = await _executor(
node,
programMemory,
engineCommandManager,
planes
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands(node, _programMemory)
tempMapCallback({ artifactMap, sourceRangeMap })
engineCommandManager.endSession()
return _programMemory
}
export const _executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager,
planes: DefaultPlanes
): Promise<ProgramMemory> => {
try {
const memory: ProgramMemory = await execute_wasm(
JSON.stringify(node),
JSON.stringify(programMemory),
engineCommandManager,
JSON.stringify(planes)
)
return memory
} 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
}
}
export const recast = (ast: Program): string => {
try {
const s: string = recast_wasm(JSON.stringify(ast))
return s
} catch (e) {
// TODO: do something real with the error.
console.log('recast error', e)
throw e
}
}
export function lexer(str: string): Token[] {
try {
const tokens: Token[] = lexer_wasm(str)
return tokens
} catch (e) {
// TODO: do something real with the error.
console.log('lexer error', e)
throw e
}
}
export const modifyAstForSketch = async (
engineCommandManager: EngineCommandManager,
ast: Program,
variableName: string,
currentPlane: string,
engineId: string
): Promise<Program> => {
try {
const updatedAst: Program = await modify_ast_for_sketch_wasm(
engineCommandManager,
JSON.stringify(ast),
variableName,
JSON.stringify(currentPlane),
engineId
)
return updatedAst
} 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
}
}

View File

@ -1,19 +1,28 @@
export const bracket = `// Material: 6061-T6 Aluminum
const sigmaAllow = 35000 // psi
const width = 9 // inch
export const bracket = `const sigmaAllow = 15000 // psi
const width = 11 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
const distance = 12 // inches
const FOS = 2
const thickness = sqrt(distance * p * FOS * 6 / ( sigmaAllow * width ))
const filletR = thickness * 2
const shelfMountL = 9
const wallMountL = 8
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], %)
const bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, wallMountL], %)
|> tangentalArc({
radius: filletR,
offset: 90
}, %)
|> line([-shelfMountL, 0], %)
|> line([0, -thickness], %)
|> line([-leg2 + thickness, 0], %)
|> line([0, -leg1 + thickness], %)
|> line([shelfMountL, 0], %)
|> tangentalArc({
radius: filletR - thickness,
offset: -90
}, %)
|> line([0, -wallMountL], %)
|> close(%)
|> extrude(width, %)

View File

@ -25,7 +25,7 @@ export async function exportSave(data: ArrayBuffer) {
}
// Write the file.
await writeBinaryFile(filePath, uintArray)
await writeBinaryFile(filePath, file.contents)
} else {
// Download the file to the user's computer.
// Now we need to download the files to the user's downloads folder.
@ -39,6 +39,6 @@ export async function exportSave(data: ArrayBuffer) {
}
} catch (e) {
// TODO: do something real with the error.
console.log('export', e)
console.log('export error', e)
}
}

View File

@ -36,7 +36,7 @@ export async function initializeProjectDirectory(directory: string) {
try {
docDirectory = await documentDir()
} catch (e) {
console.log(e)
console.log('error', e)
docDirectory = await homeDir() // seems to work better on Linux
}

View File

@ -1,10 +1,12 @@
import { Program } from '../lang/abstractSyntaxTreeTypes'
import { ProgramMemory, _executor } from '../lang/executor'
import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm'
import {
EngineCommandManager,
EngineCommand,
} from '../lang/std/engineConnection'
import { SourceRange } from 'lang/executor'
import { Models } from '@kittycad/lib'
import { v4 as uuidv4 } from 'uuid'
type WebSocketResponse = Models['OkWebSocketResponseData_type']
class MockEngineCommandManager {
constructor(mockParams: {
@ -23,7 +25,13 @@ class MockEngineCommandManager {
range: SourceRange
command: EngineCommand
}): Promise<any> {
return Promise.resolve()
const response: WebSocketResponse = {
type: 'modeling',
data: {
modeling_response: { type: 'empty' },
},
}
return Promise.resolve(JSON.stringify(response))
}
sendModelingCommandFromWasm(
id: string,
@ -57,7 +65,11 @@ export async function enginelessExecutor(
}) as any as EngineCommandManager
await mockEngineCommandManager.waitForReady
mockEngineCommandManager.startNewSession()
const programMemory = await _executor(ast, pm, mockEngineCommandManager)
const programMemory = await _executor(ast, pm, mockEngineCommandManager, {
xy: uuidv4(),
yz: uuidv4(),
xz: uuidv4(),
})
await mockEngineCommandManager.waitForAllCommands()
return programMemory
}
@ -66,15 +78,21 @@ export async function executor(
ast: Program,
pm: ProgramMemory = { root: {}, return: null }
): Promise<ProgramMemory> {
const engineCommandManager = new EngineCommandManager({
const engineCommandManager = new EngineCommandManager()
engineCommandManager.start({
setIsStreamReady: () => {},
setMediaStream: () => {},
width: 100,
height: 100,
width: 0,
height: 0,
executeCode: () => {},
})
await engineCommandManager.waitForReady
engineCommandManager.startNewSession()
const programMemory = await _executor(ast, pm, engineCommandManager)
const programMemory = await _executor(ast, pm, engineCommandManager, {
xy: uuidv4(),
yz: uuidv4(),
xz: uuidv4(),
})
await engineCommandManager.waitForAllCommands(ast, programMemory)
return programMemory
}

View File

@ -1,5 +1,5 @@
import { isOverlap, roundOff } from './utils'
import { SourceRange } from '../lang/executor'
import { SourceRange } from '../lang/wasm'
describe('testing isOverlapping', () => {
testBothOrders([0, 3], [3, 10])

View File

@ -1,4 +1,4 @@
import { SourceRange } from '../lang/executor'
import { SourceRange } from '../lang/wasm'
export function isOverlap(a: SourceRange, b: SourceRange) {
const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]

View File

@ -0,0 +1,853 @@
import { PathToNode } from 'lang/wasm'
import { engineCommandManager } from 'lang/std/engineConnection'
import { isReducedMotion } from 'lang/util'
import { Axis, Selection, SelectionRangeTypeMap, Selections } from 'useStore'
import { assign, createMachine } from 'xstate'
import { v4 as uuidv4 } from 'uuid'
import { isCursorInSketchCommandRange } from 'lang/util'
import {
doesPipeHaveCallExp,
getNodePathFromSourceRange,
hasExtrudeSketchGroup,
} from 'lang/queryAst'
import { kclManager } from 'lang/KclSinglton'
import {
horzVertInfo,
applyConstraintHorzVert,
} from 'components/Toolbar/HorzVert'
import {
applyConstraintHorzVertAlign,
horzVertDistanceInfo,
} from 'components/Toolbar/SetHorzVertDistance'
import { angleBetweenInfo } from 'components/Toolbar/SetAngleBetween'
import { setAngleLengthInfo } from 'components/Toolbar/setAngleLength'
import {
applyConstraintEqualLength,
setEqualLengthInfo,
} from 'components/Toolbar/EqualLength'
import { extrudeSketch } from 'lang/modifyAst'
import { getNodeFromPath } from '../lang/queryAst'
import { CallExpression, PipeExpression } from '../lang/wasm'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
export type SetSelections =
| {
selectionType: 'singleCodeCursor'
selection?: Selection
}
| {
selectionType: 'otherSelection'
selection: Axis
}
| {
selectionType: 'completeSelection'
selection: Selections
}
| {
selectionType: 'mirrorCodeMirrorSelections'
selection: Selections
}
export const modelingMachine = createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogDMAVgDsAOmEiAjLMGCAHIIAsAJlHKANCACeiaQDZBEsctGrVATmmiaNQQF8H2tBhz5x2CJjAEAymDsAASwWGDknNy0DEggLGyRPLECCMrCCuL20qrCRpYagvnaegjZBtLiBqLploIGCtZVyk4u6Fh4UJ7evgAicGERQSx47NG88RxcSaApdcKSNKIKi8ppwsrLwsX60oriqgaWBgbKImpWqi0gru0eXj4EfaE+g5AwY7ETibyzx5lLu1sygM61ERV0+ho0mU4gURlOwihlis2SuN3cnXuvX6L2CJD42FgH2YrEm3B+QnM4mqdhoyPUILqBm2CAaFTS5houQUCMczmubQxXQeAVxQ1QI2JcVJ32SiGUXPEhjBtWklgaokMzIhqVUNHEG0WgjVqkEUN2aMFHWFvlF4WCbzAUq+UwpqRoGQ2pwacPW4JKJxhgYU0kWu3Wymklrc1qx-gGeIJRPo4xlrrlqUEqkqdMKOSRNEOLPWGTVhkswlU0O5fNaMbu3XjYoAZiRKM60+SMwqMgY7Go6mbalCWaIjLCjtJ0n36tUFNHbpjGwBRXDsMAAJxCAGtAuQABYdhLpmY7SPiSzAyOXwGFUQs01BtXVEEV9VSZr89Gxldrzc7vdD2kGISWPLtTwQepBGpEN8lDGhVBDLYdTUfVZzvYFQ3MS4vytBsHieBMglbdsU0+Ttpn4Sk0KzBR1EKdING1EozQqYxajyNQ6MOBchTjO1BhITBMCPMlKJSSNoIsEEllQrlhGQko9RhZEDFUL1vWEUMcLrRcbUeHF7SCISRLI0CxLdRR2ROJQwQQ2RmMQENJD1OxjR5aE+1rAV6yXB4wD4dgNwAVwwIIRjANdRNlCDlGRJU6kfapK0vdYWSnBQJEsfJMtODYrM-XS+MbAKgtCsBwr-KLgNTMDxPlawJ0DKtXNkLl0o2NjNULPMPQ2HSfL0vxd3YA9iDIShMGGwDopPKjSg0fUaDURFFoVbJ7x1S9oOSmQcl2CtRF461ptG-dxFOg8AElGwE4JhiiszpTqt1pB9C8NA8plynVFlyn1EMzVfdTrAU46PEu87IZukUiNCKAAFtItGJ6XXA+a3vVD6vV2Y41IQlkzAMakLCnD0K0jK9wc6SGLpG67G0IsUHpRkDnosjM3oUi91SsRYjmUywWRUDJRFEY0PXzdQrGpunALls6YexZ4jPhpHHrZtH6tKHkJHkLJ1AUGpsofQxxBEWpxayCXFFl2noZXABHYLsCYIJ2FQVBTM1ijLK5Njp20wtPMEUc+yVRC4vgpZlqjXDfIVg9E-3JWCGXZ3XaCBHUAANwqj2vdm9HZg9NjpPUZbIykFkKYNTD1nsGycjt+modb1OAmCFWIimIvtbVMwczF4wtXqRyEDHfVsiNwtahyTU46Kk7W+T1PkBIXcQjARHkaCPON04cghL716kKVdYjErEWFEy9LoXZKsQT7I3jWEFv5Ydh5183tXd-3VANzYAAF7cHYMfVGvtOZggyOkewqkjBqQrOlOBlQqjyCrEoacR145DRXp-XwRBuCwCCiQPAQR-6AJAWuISQQICEjARQJ0ECXpQOqJULm+QVArFvjqcsMIDg2UwRsdIhVBpCntu3RshDcDEI3KQ3Ae9NyHxoXQ4hE0mE+xYRBGwlh9QpQOp5awdR0piAqOLLS5g0HmlEd+CGeDJEPGkbI+Rxl8A+BPpzOEKk+wOT6nYHk6VNgGkOLkUJOQ1jvzOqvKRRCSFkJ8Pgdgh5mEc20UbDIN4GjHAKopfQjQLzLAVCIrkdE344PEfYwCqcnFxIURQ4BoCTI6GMjgKAuAPHaKWMTJQCFzBZIOEbdK6kYSRg0EsRCdhLyWEiUnfBxBYlyLIfvZRwlmlCWwG0jpGNljQQVMCKohZzBiHSiIHasg1DIhEFpOEMy25VJiTI2pQQwDOxoQkqASStkpDejYA0voqg5HKFOLQvC0gwK0sOPs1g3K3PEAAGTwBVAAKp7TAacM5u2znnd2qKvlCEUDA8ZCk1TpE8qOJQSouQ9SnMIfIGhYUItwMi1F4gAAKEo1xBAAIIQAwBAAgPKIDiklCkmK80zTAnNg0dIUg1JqFWMWKESoThQkyn6I2UgGWIqCCir2F1t7q2CIKyAAreXCo1rVVJ4rEIVEOJlLyZpCz1EJqaA0Bx1JPx+dJLVTKdUsoCDvTlxr+WEIRkwHw64gjuA0ZasVsxgQSBnsPMFyJcmpBvkqXKozlpWDij65leqA2Gu5byk16cXZuyZQAdxxYXUVc1ZgV32G9MeCEjhHGFm9JU2Uq5yGlTYvCNMV6MoLZgcQV1cAcAIHihA6k1T-CuZqXIVc-r2EyPUMQmokJ2AMPmv1eqJ1TqoDVciWj5qmmypkS5cClAghBSUSsxNCglJSosRYu7ynL3liO-dY6AByqAgjspGLAU1QqWYzoZJYC8G0wSvuRMWdSSoim6N0a-TKsKuVVtIcEepVCwGYFofQ9RlVmyoAIBAbgYBPC4BzqgXc4gYDsAALR4caZgZjeAyOQaWPMRCyJNTGmBccdKS7YRmmMG5fs0zP12PllhnD5CAENOoYR1RDDKCkfI5uDcADxDhpIOwMjG4EaMcCKx5T+GhKcdwNx+txdECG3mIdaWjQjaXnSlUDIqroTmHDOsTD2GOCKIPtgI+aniOMK0xRqjNG6MMaY8x5ZYXrNcdQDx3RkhzDytonCYwJjGqIUyvIWwiJnyBcU8l8LRG1FRbSwQHTemDNGYAaZxLVXUu2fS-Z7W6hMtiHUupXLRhNolGBTtG+4tjBwjpLUCrwWyBQB8NFyjTK4v0eo4lxbPgbN2c0ValIfXSynGylBKcVZAmVgvB6ZayxR4enm3iNxFV6uNY3PpzAhnjNtfM9tsAu3uv7bjY5lUkhgTqnbR6qcgTdhXtkDYeHxojiPajZFD5+4VuxbwPFzb5n3lJIBxl4m6hKwKQ0Ju1Q6V1QVGWh6E4Fh8wbBR-jjHr2Ny6fe8177ZmWMs8Jz1t0fXibbSrPkBUsc01qj2Cqxu8gn5KFuUEXAgGSK2iIhgVswVMD3U+0ymd2Q4qSArG9a8PJgShxQlOGDUI6hG2nuYbytjOgkCC5wfAW9cRTHjF3BMvcBec2DtSXRY4eTHEjND3hy7qR9g2Oqe1l55yyc6AecI24Oge-tF7-Xoz9jLo5AcXpJi1J-NWOaOosdZYp-IGn933dEjTpPeZYHpRKaVGNBXDQ6pwcdSkOwlQtLr3LBwvyZXGB4CxCd7GhtQg6SSBkHIRQKh1D3sQMx5zyxzRSCkLog4i8xE-h8FPhzqRi-b8OYcTU8ILdjcHBHXRdI3ohlOI7wdycj-90ONmHNq09lQnUCyZydzRQTqLSY4KcWFfBd-V6S5fYJkG3eQQoKoGuPUXPKQBAkMB3AdBOWmH9XVTAKAjMaSEwS5WlaoLMCwFfWdM0SoHtK+RCDBGWJPZOeFbVPA8QHoKjAgiCYwag3IcWG1VVBkRDaCZ9f6WlKwBXJgnA1gllYDINUtCALg89NDc2ThXKBAk4UbRASsIMFDG+EEdYCwPdNgotXeYNJQxtcOODCmfZIcRDXQrkOPAw84YwllQ9dgCwxzV+I3U0UgqQBnSg65fYC9FQFQU4OkD9JeOTKJXAllADIDDldgcfJvafWdE4YmU4H6U0aeOEewyoFDOkLIRiWFNAbFPAzwhAErLKQFMwBNOoaQGuagy8coVaeeKoGTKIodeTV3JTShdjGrDTF7LrCoi9JaGwARDBAfTzfrXIIrUJKEEEFHDrCLWrTTNLEYhUHaQcKEHNbIZaExDYWA0AjiCwI4RPTo5ghTBbZ7LTDY35PUIwIcZaEQJQQJRYTIEWBEewOoCsZnNHJJW409A7RzcPD6d8dBCFXIKnEQTINtZKaoPUAaJ3ZOJXFXNsMACos46kEQVVawCsQsSnFCd4woHkCItqKsWWF3HDdPOvOaLWN0XKC8A5dzG1PgkxTKHmJYAofKJ-SvfcVPGk33dGekzmBnSQE4anQ2OKQodk7MWQCxREFVOiJwJwIAA */
id: 'Modeling',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
predictableActionArguments: true,
preserveActionOrder: true,
context: {
guiMode: 'default',
selection: [] as string[],
selectionRanges: {
otherSelections: [],
codeBasedSelections: [],
} as Selections,
selectionRangeTypeMap: {} as SelectionRangeTypeMap,
sketchPathToNode: null as PathToNode | null, // maybe too specific, and we should have a generic pathToNode, but being specific seems less risky when I'm not sure
sketchEnginePathId: '' as string,
sketchPlaneId: '' as string,
},
schema: {
events: {} as
| { type: 'Deselect all' }
| { type: 'Deselect edge'; data: Selection & { type: 'edge' } }
| { type: 'Deselect axis'; data: Axis }
| {
type: 'Deselect segment'
data: Selection & { type: 'line' | 'arc' }
}
| { type: 'Deselect face'; data: Selection & { type: 'face' } }
| {
type: 'Deselect point'
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
}
| { type: 'Enter sketch' }
| { type: 'Select all'; data: Selection & { type: 'all ' } }
| { type: 'Select edge'; data: Selection & { type: 'edge' } }
| { type: 'Select axis'; data: Axis }
| { type: 'Select segment'; data: Selection & { type: 'line' | 'arc' } }
| { type: 'Select face'; data: Selection & { type: 'face' } }
| { type: 'Select default plane'; data: { planeId: string } }
| { type: 'Set selection'; data: SetSelections }
| {
type: 'Select point'
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
}
| { type: 'Sketch no face' }
| { type: 'Toggle gui mode' }
| { type: 'Cancel' }
| { type: 'CancelSketch' }
| {
type: 'Add point'
data: {
coords: { x: number; y: number }[]
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null
}
}
| { type: 'Equip tool' }
| { type: 'Equip move tool' }
| { type: 'Set radius' }
| { type: 'Complete line' }
| { type: 'Set distance' }
| { type: 'Equip new tool' }
| { type: 'update_code'; data: string }
| { type: 'Make segment horizontal' }
| { type: 'Make segment vertical' }
| { type: 'Constrain horizontal distance' }
| { type: 'Constrain vertical distance' }
| { type: 'Constrain angle' }
| { type: 'Constrain horizontally align' }
| { type: 'Constrain vertically align' }
| { type: 'Constrain length' }
| { type: 'Constrain equal length' }
| { type: 'extrude intent' },
// ,
},
states: {
idle: {
on: {
'Set selection': {
target: 'idle',
internal: true,
actions: 'Set selection',
},
'Deselect point': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains point',
},
'Deselect edge': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains edge',
},
'Deselect axis': {
target: 'idle',
internal: true,
actions: [
'Remove from other selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains axis',
},
'Select point': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Select edge': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Select axis': {
target: 'idle',
internal: true,
actions: [
'Add to other selection',
// 'Engine: add highlight',
],
},
'Select face': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Enter sketch': [
{
target: 'Sketch',
cond: 'Selection is one face',
actions: [
'set sketch metadata',
'sketch mode enabled',
'edit mode enter',
],
},
'Sketch no face',
],
'Deselect face': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains face',
},
'Select all': {
target: 'idle',
internal: true,
actions: 'Add to code-based selection',
},
'Deselect all': {
target: 'idle',
internal: true,
actions: [
'Clear selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection is not empty',
},
'extrude intent': [
{
target: 'awaiting selection',
cond: 'has no selection',
},
{
target: 'idle',
cond: 'has valid extrude selection',
internal: true,
actions: 'AST extrude',
},
],
},
},
Sketch: {
states: {
SketchIdle: {
on: {
'Select point': {
target: 'SketchIdle',
internal: true,
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Select segment': {
target: 'SketchIdle',
internal: true,
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Deselect point': {
target: 'SketchIdle',
internal: true,
cond: 'Selection contains point',
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Deselect segment': {
target: 'SketchIdle',
internal: true,
cond: 'Selection contains line',
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Equip tool': {
target: 'Line Tool',
actions: 'set tool line',
},
'Equip move tool': 'Move Tool',
'Set selection': {
target: 'SketchIdle',
internal: true,
actions: 'Set selection',
},
'Make segment vertical': {
cond: 'Can make selection vertical',
target: 'SketchIdle',
internal: true,
actions: ['Make selection vertical'],
},
'Make segment horizontal': {
target: 'SketchIdle',
internal: true,
cond: 'Can make selection horizontal',
actions: ['Make selection horizontal'],
},
'Constrain horizontal distance': {
target: 'Await horizontal distance info',
cond: 'Can constrain horizontal distance',
},
'Constrain vertical distance': {
target: 'Await vertical distance info',
cond: 'Can constrain vertical distance',
},
'Constrain angle': {
target: 'Await angle info',
cond: 'Can constrain angle',
},
'Constrain length': {
target: 'Await length info',
cond: 'Can constrain length',
},
'Constrain horizontally align': {
cond: 'Can constrain horizontally align',
target: 'SketchIdle',
internal: true,
actions: ['Constrain horizontally align'],
},
'Constrain vertically align': {
cond: 'Can constrain vertically align',
target: 'SketchIdle',
internal: true,
actions: ['Constrain vertically align'],
},
'Constrain equal length': {
cond: 'Can constrain equal length',
target: 'SketchIdle',
internal: true,
actions: ['Constrain equal length'],
},
},
entry: 'equip select',
},
'Line Tool': {
states: {
Done: {
type: 'final',
},
'Point Added': {
on: {
'Add point': {
target: 'Segment Added',
actions: ['AST start new sketch'],
},
},
},
'Segment Added': {
on: {
'Add point': {
target: 'Segment Added',
internal: true,
actions: ['AST add line segment'],
},
'Complete line': {
target: 'Done',
actions: ['Modify AST', 'Update code selection cursors'],
},
'Equip new tool': {
target: 'Segment Added',
internal: true,
actions: 'set tool',
},
},
},
Init: {
always: [
{
target: 'Segment Added',
cond: 'is editing existing sketch',
},
'No Points',
],
},
'No Points': {
on: {
'Add point': 'Point Added',
},
},
},
// invoke: [
// {
// src: 'createLine',
// id: 'Create line',
// onDone: 'SketchIdle',
// },
// ],
initial: 'Init',
on: {
'Equip move tool': 'Move Tool',
},
},
'Move Tool': {
entry: 'set tool move',
},
'Await horizontal distance info': {
invoke: {
src: 'Get horizontal info',
id: 'get-horizontal-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await vertical distance info': {
invoke: {
src: 'Get vertical info',
id: 'get-vertical-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await angle info': {
invoke: {
src: 'Get angle info',
id: 'get-angle-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await length info': {
invoke: {
src: 'Get length info',
id: 'get-length-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
},
initial: 'SketchIdle',
on: {
CancelSketch: '.SketchIdle',
},
exit: 'sketch exit execute',
},
'Sketch no face': {
entry: 'show default planes',
exit: 'hide default planes',
on: {
'Select default plane': {
target: 'Sketch',
actions: [
'reset sketch metadata',
'set default plane id',
'sketch mode enabled',
'create path',
],
},
},
},
'awaiting selection': {
on: {
'Set selection': {
target: 'checking selection',
actions: 'Set selection',
},
},
},
'checking selection': {
always: [
{
target: 'idle',
cond: 'has valid extrude selection',
actions: 'AST extrude',
},
{
target: 'idle',
actions: 'toast extrude failed',
},
],
},
},
initial: 'idle',
on: {
Cancel: {
target: 'idle',
// TODO what if we're existing extrude equiped, should these actions still be fired?
// mabye cancel needs to have a guard for if else logic?
actions: [
'edit_mode_exit',
'default_camera_disable_sketch_mode',
'reset sketch metadata',
],
},
},
},
{
guards: {
'is editing existing sketch': ({ sketchPathToNode }) =>
!!sketchPathToNode,
'Can make selection horizontal': ({ selectionRanges }) =>
horzVertInfo(selectionRanges, 'horizontal').enabled,
'Can make selection vertical': ({ selectionRanges }) =>
horzVertInfo(selectionRanges, 'vertical').enabled,
'Can constrain horizontal distance': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain vertical distance': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setVertDistance' })
.enabled,
'Can constrain angle': ({ selectionRanges }) =>
angleBetweenInfo({ selectionRanges }).enabled,
'Can constrain length': ({ selectionRanges }) =>
setAngleLengthInfo({ selectionRanges }).enabled,
'Can constrain horizontally align': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain vertically align': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain equal length': ({ selectionRanges }) =>
setEqualLengthInfo({ selectionRanges }).enabled,
'has no selection': ({ selectionRanges }) => {
if (selectionRanges?.codeBasedSelections?.length < 1) return true
const selection = selectionRanges?.codeBasedSelections?.[0] || {}
return (
selectionRanges.codeBasedSelections.length === 1 &&
!hasExtrudeSketchGroup({
ast: kclManager.ast,
programMemory: kclManager.programMemory,
selection,
})
)
},
'has valid extrude selection': ({ selectionRanges }) => {
if (selectionRanges.codeBasedSelections.length !== 1) return false
const isSketchPipe = isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
const common = {
selection: selectionRanges.codeBasedSelections[0],
ast: kclManager.ast,
}
const hasClose = doesPipeHaveCallExp({ calleeName: 'close', ...common })
const hasExtrude = doesPipeHaveCallExp({
calleeName: 'extrude',
...common,
})
return !!isSketchPipe && hasClose && !hasExtrude
},
},
actions: {
'Add to code-based selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
event.data,
],
}),
}),
'Add to other selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
otherSelections: [...selectionRanges.otherSelections, event.data],
}),
}),
'Remove from code-based selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
event.data,
],
}),
}),
'Remove from other selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
otherSelections: [...selectionRanges.otherSelections, event.data],
}),
}),
'Clear selection': assign({
selectionRanges: () => ({
otherSelections: [],
codeBasedSelections: [],
}),
}),
'sketch mode enabled': ({ sketchPlaneId }) => {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: sketchPlaneId,
ortho: true,
animated: !isReducedMotion(),
},
})
},
'edit mode enter': ({ selectionRanges }) => {
const pathId = isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
pathId &&
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: pathId,
},
})
},
'hide default planes': ({}) => {
kclManager.hidePlanes()
},
edit_mode_exit: () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
}),
default_camera_disable_sketch_mode: () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
}),
'reset sketch metadata': assign({
sketchPathToNode: null,
sketchEnginePathId: '',
sketchPlaneId: '',
}),
'set sketch metadata': assign(({ selectionRanges }) => {
const sourceRange = selectionRanges.codeBasedSelections[0].range
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
sourceRange
)
const pipeExpression = getNodeFromPath<PipeExpression>(
kclManager.ast,
sketchPathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression') return {}
const sketchCallExpression = pipeExpression.body.find(
(e) =>
e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
) as CallExpression
if (!sketchCallExpression) return {}
const firstArg = sketchCallExpression.arguments[0]
let planeId = ''
if (firstArg.type === 'Literal' && firstArg.value) {
const planeStrCleaned = firstArg.value
.toString()
.toLowerCase()
.replace('-', '')
if (
planeStrCleaned === 'xy' ||
planeStrCleaned === 'xz' ||
planeStrCleaned === 'yz'
) {
planeId = kclManager.getPlaneId(planeStrCleaned)
}
}
console.log('planeId', planeId)
const sketchEnginePathId =
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
) || ''
return {
sketchPathToNode,
sketchEnginePathId,
sketchPlaneId: planeId,
}
}),
'set tool line': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'sketch_line',
},
}),
'equip select': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'select',
},
}),
'set tool move': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'move',
},
}),
'Make selection horizontal': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVert(
selectionRanges,
'horizontal',
kclManager.ast,
kclManager.programMemory
)
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'Make selection vertical': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVert(
selectionRanges,
'vertical',
kclManager.ast,
kclManager.programMemory
)
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'Constrain horizontally align': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVertAlign({
selectionRanges,
constraint: 'setVertDistance',
})
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'Constrain vertically align': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVertAlign({
selectionRanges,
constraint: 'setHorzDistance',
})
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'Constrain equal length': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintEqualLength({
selectionRanges,
})
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'AST extrude': ({ selectionRanges }) => {
const pathToNode = getNodePathFromSourceRange(
kclManager.ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
kclManager.ast,
pathToNode
)
// TODO not handling focusPath correctly I think
kclManager.updateAst(modifiedAst, true, {
focusPath: pathToExtrudeArg,
})
},
'set default plane id': assign({
sketchPlaneId: (_, { data }) => data.planeId,
}),
},
}
)

View File

@ -0,0 +1,98 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"": { type: "" };
"done.invoke.get-angle-info": { type: "done.invoke.get-angle-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.get-horizontal-info": { type: "done.invoke.get-horizontal-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.get-length-info": { type: "done.invoke.get-length-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.get-vertical-info": { type: "done.invoke.get-vertical-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"error.platform.get-angle-info": { type: "error.platform.get-angle-info"; data: unknown };
"error.platform.get-horizontal-info": { type: "error.platform.get-horizontal-info"; data: unknown };
"error.platform.get-length-info": { type: "error.platform.get-length-info"; data: unknown };
"error.platform.get-vertical-info": { type: "error.platform.get-vertical-info"; data: unknown };
"xstate.init": { type: "xstate.init" };
"xstate.stop": { type: "xstate.stop" };
};
invokeSrcNameMap: {
"Get angle info": "done.invoke.get-angle-info";
"Get horizontal info": "done.invoke.get-horizontal-info";
"Get length info": "done.invoke.get-length-info";
"Get vertical info": "done.invoke.get-vertical-info";
};
missingImplementations: {
actions: "AST add line segment" | "AST start new sketch" | "Modify AST" | "Set selection" | "Update code selection cursors" | "create path" | "set tool" | "show default planes" | "sketch exit execute" | "toast extrude failed";
delays: never;
guards: "Selection contains axis" | "Selection contains edge" | "Selection contains face" | "Selection contains line" | "Selection contains point" | "Selection is not empty" | "Selection is one face";
services: "Get angle info" | "Get horizontal info" | "Get length info" | "Get vertical info";
};
eventsCausingActions: {
"AST add line segment": "Add point";
"AST extrude": "" | "extrude intent";
"AST start new sketch": "Add point";
"Add to code-based selection": "Deselect point" | "Deselect segment" | "Select all" | "Select edge" | "Select face" | "Select point" | "Select segment";
"Add to other selection": "Select axis";
"Clear selection": "Deselect all";
"Constrain equal length": "Constrain equal length";
"Constrain horizontally align": "Constrain horizontally align";
"Constrain vertically align": "Constrain vertically align";
"Make selection horizontal": "Make segment horizontal";
"Make selection vertical": "Make segment vertical";
"Modify AST": "Complete line";
"Remove from code-based selection": "Deselect edge" | "Deselect face" | "Deselect point";
"Remove from other selection": "Deselect axis";
"Set selection": "Set selection" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-vertical-info";
"Update code selection cursors": "Complete line" | "Deselect all" | "Deselect axis" | "Deselect edge" | "Deselect face" | "Deselect point" | "Deselect segment" | "Select edge" | "Select face" | "Select point" | "Select segment";
"create path": "Select default plane";
"default_camera_disable_sketch_mode": "Cancel";
"edit mode enter": "Enter sketch";
"edit_mode_exit": "Cancel";
"equip select": "CancelSketch" | "Constrain equal length" | "Constrain horizontally align" | "Constrain vertically align" | "Deselect point" | "Deselect segment" | "Enter sketch" | "Make segment horizontal" | "Make segment vertical" | "Select default plane" | "Select point" | "Select segment" | "Set selection" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-vertical-info" | "error.platform.get-angle-info" | "error.platform.get-horizontal-info" | "error.platform.get-length-info" | "error.platform.get-vertical-info";
"hide default planes": "Cancel" | "Select default plane" | "xstate.stop";
"reset sketch metadata": "Cancel" | "Select default plane";
"set default plane id": "Select default plane";
"set sketch metadata": "Enter sketch";
"set tool": "Equip new tool";
"set tool line": "Equip tool";
"set tool move": "Equip move tool";
"show default planes": "Enter sketch";
"sketch exit execute": "Cancel" | "Complete line" | "xstate.stop";
"sketch mode enabled": "Enter sketch" | "Select default plane";
"toast extrude failed": "";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
"Can constrain angle": "Constrain angle";
"Can constrain equal length": "Constrain equal length";
"Can constrain horizontal distance": "Constrain horizontal distance";
"Can constrain horizontally align": "Constrain horizontally align";
"Can constrain length": "Constrain length";
"Can constrain vertical distance": "Constrain vertical distance";
"Can constrain vertically align": "Constrain vertically align";
"Can make selection horizontal": "Make segment horizontal";
"Can make selection vertical": "Make segment vertical";
"Selection contains axis": "Deselect axis";
"Selection contains edge": "Deselect edge";
"Selection contains face": "Deselect face";
"Selection contains line": "Deselect segment";
"Selection contains point": "Deselect point";
"Selection is not empty": "Deselect all";
"Selection is one face": "Enter sketch";
"has no selection": "extrude intent";
"has valid extrude selection": "" | "extrude intent";
"is editing existing sketch": "";
};
eventsCausingServices: {
"Get angle info": "Constrain angle";
"Get horizontal info": "Constrain horizontal distance";
"Get length info": "Constrain length";
"Get vertical info": "Constrain vertical distance";
};
matchesStates: "Sketch" | "Sketch no face" | "Sketch.Await angle info" | "Sketch.Await horizontal distance info" | "Sketch.Await length info" | "Sketch.Await vertical distance info" | "Sketch.Line Tool" | "Sketch.Line Tool.Done" | "Sketch.Line Tool.Init" | "Sketch.Line Tool.No Points" | "Sketch.Line Tool.Point Added" | "Sketch.Line Tool.Segment Added" | "Sketch.Move Tool" | "Sketch.SketchIdle" | "awaiting selection" | "checking selection" | "idle" | { "Sketch"?: "Await angle info" | "Await horizontal distance info" | "Await length info" | "Await vertical distance info" | "Line Tool" | "Move Tool" | "SketchIdle" | { "Line Tool"?: "Done" | "Init" | "No Points" | "Point Added" | "Segment Added"; }; };
tags: never;
}

View File

@ -2,6 +2,7 @@ import { assign, createMachine } from 'xstate'
import { CommandBarMeta } from '../lib/commands'
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
import { CameraSystem, cameraSystems } from 'lib/cameraControls'
import { Models } from '@kittycad/lib'
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
@ -11,11 +12,11 @@ export enum UnitSystem {
}
export const baseUnits = {
imperial: ['in', 'ft'],
imperial: ['in', 'ft', 'yd'],
metric: ['mm', 'cm', 'm'],
} as const
export type BaseUnit = 'in' | 'ft' | 'mm' | 'cm' | 'm'
export type BaseUnit = Models['UnitLength_type']
export const baseUnitsUnion = Object.values(baseUnits).flatMap((v) => v)

View File

@ -2,18 +2,15 @@ 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'
import { kclManager } from 'lang/KclSinglton'
export default function FutureWork() {
const dismiss = useDismiss()
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
useEffect(() => {
deferredSetCode(bracket)
}, [deferredSetCode])
kclManager.setCode(bracket)
}, [kclManager.setCode])
return (
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">

View File

@ -1,10 +1,14 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import {
ONBOARDING_PROJECT_NAME,
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,
@ -15,24 +19,28 @@ import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom'
import { paths } from 'Router'
import { useEffect } from 'react'
import { kclManager } from 'lang/KclSinglton'
function OnboardingWithNewFile() {
const navigate = useNavigate()
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.INDEX)
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
context: { defaultDirectory, defaultProjectName },
context: { defaultDirectory },
},
} = useGlobalStateContext()
async function createAndOpenNewProject() {
const projects = await getProjectsInDir(defaultDirectory)
const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
const name = interpolateProjectNameWithIndex(defaultProjectName, nextIndex)
const nextIndex = await getNextProjectIndex(
ONBOARDING_PROJECT_NAME,
projects
)
const name = interpolateProjectNameWithIndex(
ONBOARDING_PROJECT_NAME,
nextIndex
)
const newFile = await createNewProject(defaultDirectory + '/' + name)
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
}
@ -65,7 +73,7 @@ function OnboardingWithNewFile() {
<ActionButton
Element="button"
onClick={() => {
deferredSetCode(bracket)
kclManager.setCode(bracket)
next()
}}
icon={{ icon: faArrowRight }}
@ -116,10 +124,6 @@ function OnboardingWithNewFile() {
}
export default function Introduction() {
const { deferredSetCode, code } = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
state: {
@ -136,10 +140,10 @@ export default function Introduction() {
const next = useNextClick(onboardingPaths.CAMERA)
useEffect(() => {
if (code === '') deferredSetCode(bracket)
}, [code, deferredSetCode])
if (kclManager.code === '') kclManager.setCode(bracket)
}, [kclManager.code, kclManager.setCode])
return !(code !== '' && code !== bracket) ? (
return !(kclManager.code !== '' && kclManager.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">

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