Compare commits

...

95 Commits

Author SHA1 Message Date
70f3ded7e2 Cut release v0.15.0 (#1436)
Bump app version to v0.15.0

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-02-16 22:43:28 -05:00
095108252b snapshot extrude on each default plane (#1438)
* snapshot extrude on each default plane

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-17 12:19:46 +11:00
20b1c93f12 no camera sketch on face (#1412)
* no camera sketch on face

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>

* fix

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

* new screenshots

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

* fix

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

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

* updates

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>

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit e839d7101f.

* Revert "fixes"

This reverts commit 3df8b63e3a.

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-16 16:42:01 -08:00
3747a1b993 respect camera target (#1421)
* respect camera target

* make default planes scale

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-16 12:04:24 -08:00
198feb7d44 Bump syn from 2.0.48 to 2.0.49 in /src/wasm-lib (#1432)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.48 to 2.0.49.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.48...2.0.49)

---
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>
2024-02-16 09:55:39 -08:00
c7a8b8313e Bump tauri-plugin-fs-extra from 67405ae to 01211ff in /src-tauri (#1430)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `67405ae` to `01211ff`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](67405aed06...01211ff075)

---
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>
2024-02-16 09:55:21 -08:00
1576dc3256 Bump openapitor from 88b05a6 to 8db292e in /src/wasm-lib (#1433)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `88b05a6` to `8db292e`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](88b05a638f...8db292eaa7)

---
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>
2024-02-16 09:55:02 -08:00
341a3b7609 Bump kittycad from 0.2.50 to 0.2.53 in /src-tauri (#1431)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.50 to 0.2.53.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.50...v0.2.53)

---
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>
2024-02-16 09:54:48 -08:00
ecb42b89a6 check cam-far when adding sketch segments (#1434)
Co-authored-by: Frank Noirot <frank@kittycad.io>
2024-02-16 12:15:35 -05:00
f00ee3a44a Revert "File based settings (#1361)" (#1435)
This reverts commit 602e7afef6.
2024-02-16 09:09:58 -05:00
900e3b96ad CI for macOS on M1 runners (#1428)
* CI for macOS on M1 runners
Fixes #1427

* Install x86 target for Universal builds
2024-02-16 08:30:39 -05:00
15fae05659 Ghost text (#888)
* copilot

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

* updates

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

* refactor layout for copilot lsp

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

* start of server

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

* updates

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

* more clippy

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

* cleanup code

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

* fix

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

* compile wasm

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

* make work w wasm

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

* clippy

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

* cleanup

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

* cleanup

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

* cleanup unwraps

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>

* tests

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

* point to correct things

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>

* updates

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>

* cleanup

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

* updaes

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>

* shared backend features

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

* framework for workspace

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>

* updates

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

* cleanup lints

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

* fmt

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-02-15 13:56:31 -08:00
2730b6d152 Prevent default on backspace when not in a text field (#1367) 2024-02-15 19:25:26 +00:00
602e7afef6 File based settings (#1361)
* Rename GlobalStateContext to SettingsAuthContext

* Naive initial impl of settings persistence to file system

* Update app identifier in tauri config

* Add "show in folder" tauri command

* Load from and save to file system in Tauri app

* Add documents drive to tauri permission scope

* Add recursive prop to default dir selection dialog

* Add success toast to web restore defaults action

* Add a way to validate read-in settings

* Update imports to use separate settings lib file

* Validate localStorage-loaded settings, combine error message

* Add a e2e test for validation

* Clean up state state bugs

* Reverse validation looping so new users don't error

* update settingsMachine typegen to remove conflicts

* Fmt

* Fix TS errors
2024-02-15 14:14:14 -05:00
d9bcadb062 fix animation (#1426)
* fix animation to vertical quaternion

* test tweak
2024-02-15 20:32:59 +11:00
19f669b94c fix zoom cam change (#1420) 2024-02-15 09:13:37 +11:00
d9ef471385 Clean up new artifact types (#1419)
* clear up circular pattern and upgrade lib

* clean up imported object
2024-02-15 07:24:54 +11:00
39f8b306a2 Update KNOWN-ISSUES.md 2024-02-13 15:25:49 -08:00
19925d22c1 rename scene classes for clarity (#1409)
* rename for clarity

* typo

* make coverage happ+
somewhat pointless since we don't use coverage because its not complete with both vitest and playwright

* local storage issue

* fmt

* fix
2024-02-14 08:03:20 +11:00
e1af4b4219 add known issues file (#1408)
* updates

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

* fixes

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

* Update KNOWN-ISSUES.md

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-02-13 12:20:32 -08:00
c699611f5b Pull Circular patterns through to App (#1405)
* debugging steps

* add testing

* Update src/wasm-lib/tests/executor/main.rs

* generate docs and fmt

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: gserena <serena@zoo.dev>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2024-02-13 11:20:49 -08:00
00ede7ec1a clean up (#1407) 2024-02-14 06:19:52 +11:00
f30601bd2c cost part001 = startSketchOn(..) should be undone . . . (#1404)
* undo sketch if no lines have been created

* fix sketch axis bug

* fix wrong event origin bug

* race condition on animation ending

* remove logs

* codespell
2024-02-14 05:35:05 +11:00
cfbc77b62f Start end for sketch on face (#1406)
* updates

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

* add tests

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

* clippy

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-02-13 10:26:09 -08:00
808830d29e don't put scene commands in the artifact map (#1403) 2024-02-13 18:47:37 +11:00
e714103655 Bump winnow from 0.5.39 to 0.5.40 in /src/wasm-lib (#1402)
Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.39 to 0.5.40.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.39...v0.5.40)

---
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>
2024-02-13 16:33:19 +11:00
fbcb96add5 Sketch on face (#1371)
* add extra metadata to extrudeGroup

* add boilerplate

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

* updates

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

* cleanup and generate docs

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

* change plane id to entity id

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

* generate docs

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

* get face id from extrude using segment tag

* cleanup a bit

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

* cleanup a bit

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

* fix doc comment

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

* get rid of face_id in geo_meta

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

* updates

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

* sketch on face test

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

* updates

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

* cleanup edge_id

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

* updates

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

* fix value

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

* fix test

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>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-02-12 18:08:42 -08:00
7386ccf1bf Playwright README fix (#1345)
* Update playwright readme, and snaps
Will fix #1340

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-12 19:48:50 -05:00
6e73578933 fix bool (#1399)
fixes bool

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-02-12 15:33:54 -08:00
b88d5c8799 Add keyboard shortcut to settings (#1368) 2024-02-12 18:11:47 -05:00
5430c1fa66 Propagate errors UI (#1369)
* Pass engine connection state to NetworkHealthIndicator

* Create the basis for styling and further work

* Add icons

* Update styles on network health indicator

* Cleanup styles and unused state

* Rename State to NetworkHealthState

* Update tests

* fmt

---------

Co-authored-by: 49lf <ircsurfer33@gmail.com>
2024-02-12 16:00:31 -05:00
c0d4bb6c9f clean up (#1398) 2024-02-13 07:41:37 +11:00
25260a88c3 Import files (#1393)
* initial shit

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

* make file system work locally or with tauri

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

* fixxes

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

* updates

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

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

* fix docs

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

* tests

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

* updates;

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

* add tests

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

* updates

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

* add more tests

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

* better errrors

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

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

* better docs

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

* better docs

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

* cleanup

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

* fix

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

* better errors

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

* make no assign work

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>

* updates

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

* closer

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

* cleanup

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

* more additions to passing around fs

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

* make work

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

* updates

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

* updates

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>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-12 12:18:37 -08:00
b6d6f0f4c1 Bump actions/checkout from 2 to 4 (#1397)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-11 19:54:08 -08:00
b1276b2ed8 Bump actions/setup-python from 2 to 5 (#1396)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-11 19:54:01 -08:00
5f0f3f40d0 Fix default memory so its always initialized in one place -> rust (#1395)
* initial redo

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

* default memory

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

* rename values

* find tricky case

* fix test

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-02-11 18:26:09 -08:00
f1ea9b6ece small comment (#1394)
* small comment

* typo
2024-02-12 00:07:33 +00:00
b94c5be1af Linear patterns (#1362)
* add linear patterns

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

* updates

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

* fix clippy

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

* add failing test for serialisation issue

* cleanup tests

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

* cleanup memoryitem

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

* add test to serialize memory item from rust

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

* update test

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

* run cargo sort everywhere

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

* run fmt everywhere

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

* fix typo

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

* clean up linear paterns on re-execute

* selections fix for patterns

* fix clippy

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>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-02-11 15:08:54 -08:00
8378eb1e94 Bump image from 0.24.7 to 0.24.8 in /src/wasm-lib (#1392)
Bumps [image](https://github.com/image-rs/image) from 0.24.7 to 0.24.8.
- [Changelog](https://github.com/image-rs/image/blob/master/CHANGES.md)
- [Commits](https://github.com/image-rs/image/compare/v0.24.7...v0.24.8)

---
updated-dependencies:
- dependency-name: image
  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>
2024-02-11 12:30:24 -08:00
05f98a8c39 Bump tokio from 1.35.1 to 1.36.0 in /src/wasm-lib (#1391)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.35.1 to 1.36.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.35.1...tokio-1.36.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>
2024-02-11 12:10:17 -08:00
386571fa60 Bump uuid from 1.6.1 to 1.7.0 in /src/wasm-lib (#1390)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.6.1...1.7.0)

---
updated-dependencies:
- dependency-name: uuid
  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>
2024-02-11 12:10:08 -08:00
b0abdf4f70 Bump once_cell from 1.18.0 to 1.19.0 in /src/wasm-lib (#1389)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.18.0 to 1.19.0.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.18.0...v1.19.0)

---
updated-dependencies:
- dependency-name: once_cell
  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>
2024-02-11 12:09:59 -08:00
81e70e139f Bump parse-display from 0.8.2 to 0.9.0 in /src/wasm-lib (#1388)
Bumps [parse-display](https://github.com/frozenlib/parse-display) from 0.8.2 to 0.9.0.
- [Changelog](https://github.com/frozenlib/parse-display/blob/master/CHANGELOG.md)
- [Commits](https://github.com/frozenlib/parse-display/compare/v0.8.2...v0.9.0)

---
updated-dependencies:
- dependency-name: parse-display
  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>
2024-02-11 12:09:50 -08:00
d6bfc38d62 Bump wasm-bindgen-futures from 0.4.38 to 0.4.41 in /src/wasm-lib (#1387)
Bumps [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) from 0.4.38 to 0.4.41.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: wasm-bindgen-futures
  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>
2024-02-11 12:09:40 -08:00
ada66de92d Bump futures from 0.3.29 to 0.3.30 in /src/wasm-lib (#1382)
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.29 to 0.3.30.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.29...0.3.30)

---
updated-dependencies:
- dependency-name: futures
  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>
2024-02-11 19:54:02 +00:00
8f133f9662 Bump anyhow from 1.0.75 to 1.0.79 in /src/wasm-lib (#1386)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.75 to 1.0.79.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.75...1.0.79)

---
updated-dependencies:
- dependency-name: anyhow
  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>
2024-02-11 19:50:46 +00:00
b360dbb961 Bump async-trait from 0.1.74 to 0.1.77 in /src/wasm-lib (#1385)
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.74 to 0.1.77.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/commits)

---
updated-dependencies:
- dependency-name: async-trait
  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>
2024-02-11 19:50:42 +00:00
eca3dc2967 Bump proc-macro2 from 1.0.76 to 1.0.78 in /src/wasm-lib (#1384)
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.76 to 1.0.78.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.76...1.0.78)

---
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>
2024-02-11 19:50:34 +00:00
ae36ab6982 Bump regex from 1.10.2 to 1.10.3 in /src/wasm-lib (#1383)
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.2...1.10.3)

---
updated-dependencies:
- dependency-name: regex
  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>
2024-02-11 19:50:19 +00:00
8cb6cf1b8a Bump bson from 2.7.0 to 2.9.0 in /src/wasm-lib (#1381)
Bumps [bson](https://github.com/mongodb/bson-rust) from 2.7.0 to 2.9.0.
- [Release notes](https://github.com/mongodb/bson-rust/releases)
- [Commits](https://github.com/mongodb/bson-rust/compare/v2.7.0...v2.9.0)

---
updated-dependencies:
- dependency-name: bson
  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>
2024-02-11 19:40:26 +00:00
3c235c890a Bump winnow from 0.5.19 to 0.5.39 in /src/wasm-lib (#1380)
Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.19 to 0.5.39.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.19...v0.5.39)

---
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>
2024-02-11 19:40:19 +00:00
b6dfd30840 Bump reqwest from 0.11.22 to 0.11.24 in /src/wasm-lib (#1379)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.22 to 0.11.24.
- [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.22...v0.11.24)

---
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>
2024-02-11 19:40:06 +00:00
65d128eecd Bump web-sys from 0.3.65 to 0.3.68 in /src/wasm-lib (#1378)
Bumps [web-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.65 to 0.3.68.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: web-sys
  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>
2024-02-11 19:39:53 +00:00
77b7c602f2 Bump thiserror from 1.0.56 to 1.0.57 in /src/wasm-lib (#1377)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.56 to 1.0.57.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.56...1.0.57)

---
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>
2024-02-11 19:30:34 +00:00
fa0e61a2be Bump itertools from 0.12.0 to 0.12.1 in /src/wasm-lib (#1376)
Bumps [itertools](https://github.com/rust-itertools/itertools) from 0.12.0 to 0.12.1.
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.12.0...v0.12.1)

---
updated-dependencies:
- dependency-name: itertools
  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>
2024-02-11 11:22:48 -08:00
1cf35a611e Bump kittycad-execution-plan from 186ea7c to 632b75a in /src/wasm-lib (#1375)
Bump kittycad-execution-plan in /src/wasm-lib

Bumps [kittycad-execution-plan](https://github.com/KittyCAD/modeling-api) from `186ea7c` to `632b75a`.
- [Commits](186ea7c04d...632b75a024)

---
updated-dependencies:
- dependency-name: kittycad-execution-plan
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-11 11:22:23 -08:00
952d0e4c7c Bump js-sys from 0.3.65 to 0.3.68 in /src/wasm-lib (#1355)
Bumps [js-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.65 to 0.3.68.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: js-sys
  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>
2024-02-11 11:21:51 -08:00
0f85de9df8 Bump tokio-tungstenite from 0.20.1 to 0.21.0 in /src/wasm-lib (#1272)
Bumps [tokio-tungstenite](https://github.com/snapview/tokio-tungstenite) from 0.20.1 to 0.21.0.
- [Changelog](https://github.com/snapview/tokio-tungstenite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/snapview/tokio-tungstenite/compare/v0.20.1...v0.21.0)

---
updated-dependencies:
- dependency-name: tokio-tungstenite
  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>
2024-02-11 19:19:37 +00:00
0e8eed3f82 Bump wasm-bindgen from 0.2.88 to 0.2.91 in /src/wasm-lib (#1354)
Bumps [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) from 0.2.88 to 0.2.91.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.88...0.2.91)

---
updated-dependencies:
- dependency-name: wasm-bindgen
  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>
2024-02-11 11:10:52 -08:00
5b43a5075f Bump openapitor from 920ba7c to 88b05a6 in /src/wasm-lib (#1357)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `920ba7c` to `88b05a6`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](920ba7c69f...88b05a638f)

---
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>
2024-02-11 11:09:37 -08:00
f5ed4e37b2 Bump clap from 4.4.8 to 4.5.0 in /src/wasm-lib (#1363)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.8 to 4.5.0.
- [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/clap_complete-v4.4.8...clap_complete-v4.5.0)

---
updated-dependencies:
- dependency-name: clap
  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>
2024-02-11 11:09:03 -08:00
19c8da1a86 Add Discord release updates automation (#1346)
adding Discord release updates automation
2024-02-11 11:08:00 -08:00
a25f89aaba Bump tokio from 1.34.0 to 1.36.0 in /src-tauri (#1342)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.34.0 to 1.36.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.34.0...tokio-1.36.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>
2024-02-11 11:03:55 -08:00
aeebe5416f Bump kittycad from 0.2.42 to 0.2.50 in /src-tauri (#1358)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.42 to 0.2.50.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.42...v0.2.50)

---
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>
2024-02-11 10:55:04 -08:00
661788b8b0 Bump serde from 1.0.193 to 1.0.196 in /src-tauri (#1373)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.193 to 1.0.196.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.193...v1.0.196)

---
updated-dependencies:
- dependency-name: serde
  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>
2024-02-11 10:54:32 -08:00
ac24563159 Bump serde_json from 1.0.108 to 1.0.113 in /src-tauri (#1374)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.108 to 1.0.113.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.108...v1.0.113)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-11 10:54:23 -08:00
d17342dfb8 Bump anyhow from 1.0.75 to 1.0.79 in /src-tauri (#1372)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.75 to 1.0.79.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.75...1.0.79)

---
updated-dependencies:
- dependency-name: anyhow
  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>
2024-02-11 10:54:10 -08:00
2e93b58ae6 Bump tauri-plugin-fs-extra from 537053d to 67405ae in /src-tauri (#1370)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `537053d` to `67405ae`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](537053d317...67405aed06)

---
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>
2024-02-11 10:25:57 -08:00
6593656b08 Bump google-github-actions/setup-gcloud from 2.0.0 to 2.1.0 (#1311)
Bumps [google-github-actions/setup-gcloud](https://github.com/google-github-actions/setup-gcloud) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/google-github-actions/setup-gcloud/releases)
- [Changelog](https://github.com/google-github-actions/setup-gcloud/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/setup-gcloud/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: google-github-actions/setup-gcloud
  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>
2024-02-11 10:24:54 -08:00
47be749ec7 Bump tauri from 1.5.3 to 1.5.4 in /src-tauri (#1245)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.5.3 to 1.5.4.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.5.3...tauri-v1.5.4)

---
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>
2024-02-11 10:24:29 -08:00
a03e7f5c41 Bump tauri-build from 1.5.0 to 1.5.1 in /src-tauri (#1244)
Bumps [tauri-build](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-build-v1.5...tauri-build-v1.5.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>
2024-02-11 10:24:10 -08:00
b78e9fa131 Bump google-github-actions/auth from 2.0.0 to 2.1.1 (#1347)
Bumps [google-github-actions/auth](https://github.com/google-github-actions/auth) from 2.0.0 to 2.1.1.
- [Release notes](https://github.com/google-github-actions/auth/releases)
- [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/auth/compare/v2.0.0...v2.1.1)

---
updated-dependencies:
- dependency-name: google-github-actions/auth
  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>
2024-02-11 10:23:49 -08:00
c629233eaa Bump google-github-actions/upload-cloud-storage from 2.0.0 to 2.1.0 (#1313)
Bumps [google-github-actions/upload-cloud-storage](https://github.com/google-github-actions/upload-cloud-storage) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/google-github-actions/upload-cloud-storage/releases)
- [Changelog](https://github.com/google-github-actions/upload-cloud-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/upload-cloud-storage/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: google-github-actions/upload-cloud-storage
  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>
2024-02-11 10:23:32 -08:00
f640f7a5e0 Client sketch scene (#1271)
* updates

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>

* make tsc happ

* better error msg

* fix control point issue

* basic code gen working for tangentialArc

* partical fix for move with arcs

* tangential arc move

* fix

* make eslint rules less annoying

* inital refactor of some xstate stuff

* more old tangential arc clean up stuff

* more tweaks

* add testing

* tweak xstate inspect

* temp remove test

* update formating for less conflicts

* fix state machine layout after merge

* shrug, something weird with xstate typegen

* renaming some xstate events

* tweak numbers to make CI playwright happ

* CI hacks

* more CI hacks

* more CI hacks

* new hack strategy

* run tests agian

* make cmd bar less flaky

* ci hacks

* CI hacks

* CI hacks

* CI hacks

* clean up

* fix

* still have constraint stuff to deal with

* progress on move rules

* update source ranges after no execute code-mod

* typo

* mvp working

* hide show sketch overlay

* match scaling

* update arrow head style

* animate line tool

* bypass xstate for animations, much smoother

* add new segment working with refactor needed for setup paper sketch

* refactor setup paper sketch

* tangantialArcTo drag animations working

* tangential arc polish

* cargo fmt

* clippy

* more clippy

* mock canvas

* last of clippy?

* typo

* more clippy stuff

* move util function so they are shareable with typescript

* migrate a bunch to rust and only rust

* add arc center point for draft tangential ac

* clippy tweak

* delete uneeded test

* Rough start to scaling arrow heads.

The tangent arrow heads are basically nuked and replaced while the
straight line sections are just rotated and repositioned, this means they
miss out on updating scaling number after a screen size changes.
Needs fixing

* fix bug with tool tips

* fix draft line start position

Having drag the end of teh path before selecting a tooltip would result in the draft line starting where the path used to end, stale data

* some progress with pan maybe

* fmt

* inital camera sync working

For perspective camera at least

* change three.js to use z-up

* add grid

* orthographic camera working with polish items TODO

* fix zoom level when swapping camera

* fix up camera/orbit changing on cam change (pan wasn't being respected)

* tidy up

* use orbit target instead of assuming scene center

* dynamic fov working

* animate orthographic to perspective and reverse

* fix import

* temp fix for batch commands

* initial client side scene sketch working

* remove hover log

* FOV adjust fix

* fix comment

* tear down sketch and small tweaks

* some progress with camera tweening

* combine dollyZoom engine commands

see
https://github.com/KittyCAD/modeling-api/compare/kurt-perspective-settings?expand=1
and
https://github.com/KittyCAD/engine/compare/kurt-perspective-settings?expand=1

* make tests happy (mocks)

* fix tween to vertical/camera-up bug

* tween to each axis with hacky solutions in there

* fix startSketchOn planes

* tidy startSketchOn

* tweening okay for now I think

* get sketching on default planes working

* allow editing on all default planes

* clean up enter and exit sketch logic

* tidy

* tidy

* remove more default plane stuff

* start of draft line

* remove some annoying parts of the paper.js implementation

* fix drag than equip line bug

* comment

* don't animate on skech tear down since it's used for draft line

* remove more default plane shit

* style draft line

* refine dashed line

* draft line set up and tear down mostly happy

* add on click logic ready for draft lines

* sketch mode with drag and draft mode working solidly now, straight segments only

* default planes match colors, hover and select still TODO

* hover and click logic working for default planes

Now just need the code mode to fire to 'startSketchOn(...)'

* select default planes

* remove some logs

* fix update infinite loop

* start of orbitControls port to Franks control guards

* hiding scenes at different times

* scene hide on camera move should be respected by scroll zoom

* basic hover working

* Hook up user camera settings to ClientSideScene (#1334)

* Refactor to not import utilities from Router.tsx

* Stop tracking changes or formatting *.typegen.ts

* Hook up cameraControls to ClientSideScene

* Remove camera controls toggle from temp debug panel

---------

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

* add select segment moves cursor

* highlight segments yellow on hover

* cursor ranges effect 2d line colors

* fix constrainst i.e. make sure the sketch is rejiged

* selecting nothing should remove selections

* remove hardcoded strings

* update get_tangential_arc_to_info rust util

* initial drawing of tangential arcs in client scene

* fix tangentialArc arrow head direction

* correct userData types for tangential arcs

* get tangential arc updates working

Doesn't include draging the head of the tangential arc itself yet

* spot of clean up

* make selections work with tangential arcs

* get draft tangential segment animated

* fix initial click weirdness for adding new tangential line

* couple tweaks

* add grace pixels /threshold to raycast

* redo arc dashes so that they spawn from the ccenter of the arc

* fix multi drag bug

* fmt

* add temp solution for close

* add default axis hover colors, still needs select logic

* selection of axis works, just with out selection color

* get axis selection colors working

* fix outdate source ranges after drag problem

* update moreNodePathFromSourceRange

* fix ts-rs issue/workaround

* fix default plane weirdness

* fix tangential arc rounding issue

* review clean up part 1

* review clean up part 2

Big state-diagram cull

* clippy

* typo

* clippy

* fix xstate types with typegen

* fix types

* clippy

* catch error

* fix test import issue

Not sure exactly what was happening but guessing circular import that vite didn't like

* add axis/plane info to sketch group tests

* case changes because of rs-ts bug, can probably revert this later

* start of playwright test fixes

* reduce geo complexity for straight segments

* fix cam adjust tests

* Revert "Clean up vite build warnings (#1332)"

This reverts commit c1f661ab52.

* selection e2e test fixed<

* remove camToggle to allow playwright tests to pass

* remove drag test

too brittle and needs to be redone from the ground up anyway

* trigger CI

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

* fix last test

* clean up part 3

* clean up part 4

* clean up part 5

* clean up sketch enter exit logic

* fix engine side selections

* default plane should not be selected form 'onDragEnd'
i.e. rotating the camera should not mean the user acidently selects a plane

* clean up state diagram around animating to sketch mode

Embracing that the animation is async and puting the interdiate steps in the state diagram clean up some logic and solved some bugs at the same time

* add test for multiple sketches

* typo

* make highlight more robust

* type tweak

* scale segmenst with distance from camera so they have a consistent pixel size/ screenspace size

* Jess's advice

* tsc and fmt

* clean up part 6

remove integer from xstate names

* clean up part 7

* integrate sequency in to camera moves

* fix tests

* update snapshot e2e

* small snapshot change

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

* trigger ci

* Fix HomeLoaderData types

* update std stuff

* update kittycad rs client lib

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-11 12:59:00 +11:00
64398381a9 Make command bar arguments skippable, configure Extrude selection to be skippable (#1353)
* Make commands able to be configured as 'skippable'

* Make command machine able to skip arguments

* Add support for skippable selections, which are not known until within input component

* Update extrude command config to skippable

* Use defaultValue to seed initial arg values, not payload

* Remove unused `payload` command config prop

* Make skip and defaultValue types more exact

* Remove console logs

* fmt

* Revert type tightening, not worth the headache
2024-02-08 12:59:01 -05:00
0bc5534056 Fix build-test-apps (ubuntu-latest) (#1360)
* Revert "add kcl-samples menu item (#1352)"

This reverts commit a5879ceeda.

* Reapply "add kcl-samples menu item (#1352)"

This reverts commit 1c0ab6c8a2.

* Fix broken attempt at having tauri-driver part of Cargo.toml
2024-02-08 06:24:13 -05:00
9fc1df7c1d Add app version to UI in Settings (#1351)
* Make package version available in app code

* Show app version in settings page with link

* fmt

* Replace Vite define with Vite plugin

* Don't use import.meta.env in bare TS file
2024-02-07 11:36:19 -05:00
a5879ceeda add kcl-samples menu item (#1352) 2024-02-06 16:38:06 -07:00
379c30824e Grackle: handle complicated nested computed objects (#1348)
Previously, Grackle could compile a single computed property like `array[x]`. But it couldn't handle:

- Nested properties like `array[x][y]`
- Nested objects like `obj[x][y]`
- Arrays nested in objects, like `{x: [1,2,3]}`
- Objects in arrays like `[{a: 1}]`

It was quite difficult to find a way to handle this, which is why I started the EP Debugger project. The debugger helped me understand the VM much better and figure out a better way to handle these cases (the two new instructions AddrOfMember and CopyLen). Now Grackle can compile those cases!
2024-02-06 22:44:30 +11:00
a4d3263b88 Fix tauri e2e tests (tauri-driver = 0.1.3) (#1344)
* Fix tauri e2e tests (driver update)
Fixes #1343

* Clean up

* Clean up
2024-02-05 05:14:05 -05:00
c1f661ab52 Clean up vite build warnings (#1332)
* WIP Clean up vite build warnings
Fixes #1014

* Fix lint

* Fix React Hooks dependencies
Clean up, use void when await not straightforward

* Clean up

* Fix missing deconstruction
2024-01-31 04:17:24 -05:00
7d887a1497 Grackle: computed properties of objects (#1337) 2024-01-30 17:10:16 +11:00
4ca341e132 Grackle: Store KCL objects as KCEP objects (#1333)
* Grackle: Store KCL objects as KCEP objects

* Remove KCL SingleValue

* Fix a test, update map bindings

* Fix tests
2024-01-30 15:18:45 +11:00
c6249f36d2 Grackle: Runtime computed array indices (#1331) 2024-01-29 17:36:29 +11:00
dcbe5d7f75 Fix tauri tests in build-test-apps (#1328) 2024-01-27 02:59:43 -05:00
390cb2d51d Grackle: Write array length before array (#1326)
This gives the Execution Plan virtual machine the information it needs to look up indices of arrays at runtime.
2024-01-26 08:07:29 +00:00
98f7a564ea Use named fields for EpBinding::Sequence (#1325) 2024-01-26 18:38:54 +11:00
05f9e3c290 Grackle: update execution-plan repo (#1324) 2024-01-26 07:16:19 +00:00
09760fc2e9 Grackle: Allow objects to be params into arrays (#1322) 2024-01-25 00:05:41 +00:00
18ffc43e89 Grackle: Allow arrays to be args to functions (#1321)
Includes two refactors:

- Move array binding into its own method
- Use EvalPlan instead of an equivalent tuple (instructions, binding)
2024-01-24 23:38:18 +00:00
de63e4f19f Grackle: Refactor: Move error types into their own module (#1319)
Refactor: Move error types into their own submodule
2024-01-24 05:47:56 +00:00
b70b271e6b Grackle: compile KCL bools to EP bools (#1318) 2024-01-24 05:36:09 +00:00
08b7cdc5f6 Grackle: pipeline expressions (#1315)
Grackle can now compile |> pipelines. This means that these two programs compile to identical execution plans:

```kcl
fn double = (x) => { return x * 2 }
fn triple = (x) => { return x * 3 }
let x = 1 |> double(%) |> triple(%) // should be 6
```
```kcl
fn double = (x) => { return x * 2 }
fn triple = (x) => { return x * 3 }
let x = triple(double(1)) // should be 6
```

This required adding passing "what should % actually resolve to" through the program. This required modifying every call site of `plan_to_bind` and `plan_to_compute` to pass the data. To avoid doing this again, I wrapped that data into a struct called `Context` so that when we have more data like it, we can just add a new field and won't need to change every call site.
2024-01-24 10:05:40 +11:00
6efe6b54c0 Fix typo in onboarding (#1316)
fix typo
2024-01-23 17:46:34 -05:00
69f72d62e0 Rework initial engine connection logic (#1205) (#1221)
Rework EngineConnection class (#1205)

Co-authored-by: lf94 <inbox@leefallat.ca>
2024-01-23 13:13:43 -05:00
227 changed files with 48767 additions and 6171 deletions

View File

@ -1,3 +1,3 @@
[codespell]
ignore-words-list: crate,everytime
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo
skip: **/target,node_modules,build,**/Cargo.lock

View File

@ -1 +1,2 @@
src/wasm-lib/*
*.typegen.ts

View File

@ -17,12 +17,12 @@
"never"
],
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/no-floating-promises": "warn"
},
"overrides": [
{
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"rules": {
"@typescript-eslint/no-floating-promises": "warn",
"testing-library/prefer-screen-queries": "off"
}
}

29
.github/workflows/announce_release.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Announce Release
on:
release:
types: [published]
jobs:
announce_release:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Announce Release
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
RELEASE_VERSION: ${{ github.event.release.tag_name }}
RELEASE_BODY: ${{ github.event.release.body}}
run: python public/announce_release.py

View File

@ -46,6 +46,7 @@ jobs:
workspaces: './src/wasm-lib'
- run: yarn build:wasm
- run: yarn xstate:typegen
- run: yarn tsc
@ -125,7 +126,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
os: [macos-14, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
@ -188,10 +189,10 @@ jobs:
- name: Fix format
run: yarn fmt
- name: Install Universal target (MacOS only)
if: matrix.os == 'macos-latest'
- name: Install x86 target for Universal builds (MacOS only)
if: matrix.os == 'macos-14'
run: |
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
- name: Prepare certificate and variables (Windows only)
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
@ -225,7 +226,7 @@ jobs:
with:
includeRelease: false
includeDebug: true
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
args: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
- name: Build the app (release) and sign
uses: tauri-apps/tauri-action@v0
@ -241,12 +242,12 @@ jobs:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
TAURI_CONF_ARGS: "--config ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
with:
args: "${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}"
args: "${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}"
- uses: actions/upload-artifact@v3
if: matrix.os != 'ubuntu-latest'
env:
PREFIX: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }}
PREFIX: ${{ matrix.os == 'macos-14' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }}
MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}
with:
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
@ -254,7 +255,7 @@ jobs:
- name: Run e2e tests (linux only)
if: matrix.os == 'ubuntu-latest'
run: |
cargo install tauri-driver
cargo install tauri-driver@0.1.3
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
export VITE_KC_API_BASE_URL
xvfb-run yarn test:e2e:tauri
@ -337,17 +338,17 @@ jobs:
cat last_download.json
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.0.0'
uses: 'google-github-actions/auth@v2.1.1'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
- name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2.0.0
uses: google-github-actions/setup-gcloud@v2.1.0
with:
project_id: kittycadapi
- name: Upload release files to public bucket
uses: google-github-actions/upload-cloud-storage@v2.0.0
uses: google-github-actions/upload-cloud-storage@v2.1.0
with:
path: artifact
glob: '*/Zoo*'
@ -355,13 +356,13 @@ jobs:
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
- name: Upload update endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.0.0
uses: google-github-actions/upload-cloud-storage@v2.1.0
with:
path: last_update.json
destination: ${{ env.BUCKET_DIR }}
- name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.0.0
uses: google-github-actions/upload-cloud-storage@v2.1.0
with:
path: last_download.json
destination: ${{ env.BUCKET_DIR }}

4
.gitignore vendored
View File

@ -50,3 +50,7 @@ e2e/playwright/export-snapshots/*embedded.gltf
/playwright-report/
/blob-report/
/playwright/.cache/
## generated files
src/**/*.typegen.ts

View File

@ -10,4 +10,4 @@ src/wasm-lib/kcl/bindings
e2e/playwright/export-snapshots
# XState generated files
src/machines/modelingMachine.typegen.ts
src/machines/**.typegen.ts

View File

@ -182,7 +182,7 @@ For more information on fuzzing you can check out
First time running plawright locally, you'll need to add the secrets file
```bash
touch ./e2e/playwright/playwright-secrets.env
echo 'token="your-token"\nsnapshottoken="your-snapshot-token"' > ./e2e/playwright/playwright-secrets2.env
printf 'token="your-token"\nsnapshottoken="your-snapshot-token"' > ./e2e/playwright/playwright-secrets.env
```
then replace "your-token" with a dev token from dev.zoo.dev/account/api-tokens

16
docs/kcl/KNOWN-ISSUES.md Normal file
View File

@ -0,0 +1,16 @@
# Known Issues
The following are bugs that are not in modeling-app or kcl itself. These bugs
once fixed in engine will just start working here with no language changes.
- **Sketch on Face**: If your sketch is outside the edges of the face (on which you
are sketching) you will get multiple models returned instead of one single
model for that sketch and its underlying 3D object.
- **Patterns**: If you try and pass a pattern to `hole` currently only the first
item in the pattern is being subtracted. This is an engine bug that is being
worked on.
- **Import**: Right now you can import a file, even if that file has brep data
you cannot edit it. You also cannot move or transform the imported objects at
all. In the future, after v1, the engine will account for this.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -498,7 +498,7 @@
}
],
"asset": {
"generator": "kittycad.io",
"generator": "zoo.dev",
"version": "2.0"
},
"buffers": [
@ -882,7 +882,7 @@
"loops": [
[
14,
-1
1
]
]
},
@ -1953,12 +1953,17 @@
{
"type": "plane",
"plane": {
"normal": [
-1,
"xAxis": [
0,
1,
0
],
"point": [
"yAxis": [
0,
0,
-1
],
"origin": [
0,
-0.0127,
0.050800000000000005
@ -1968,12 +1973,17 @@
{
"type": "plane",
"plane": {
"normal": [
"xAxis": [
1,
0,
-1,
-0
0
],
"point": [
"yAxis": [
-0,
0,
1
],
"origin": [
0.039310000000000005,
-0.0254,
0.050800000000000005
@ -1983,12 +1993,17 @@
{
"type": "plane",
"plane": {
"normal": [
"xAxis": [
0.81915,
-0.5735800000000001,
-0.81915,
0
],
"point": [
"yAxis": [
0,
0,
1
],
"origin": [
0.11489,
-0.050800000000000005,
0.050800000000000005
@ -1998,12 +2013,17 @@
{
"type": "plane",
"plane": {
"normal": [
"xAxis": [
1,
0,
-1,
-0
0
],
"point": [
"yAxis": [
-0,
0,
1
],
"origin": [
0.19623,
-0.0762,
0.050800000000000005
@ -2013,12 +2033,17 @@
{
"type": "plane",
"plane": {
"normal": [
1,
"xAxis": [
0,
-0
1,
0
],
"point": [
"yAxis": [
-0,
0,
1
],
"origin": [
0.2413,
-0.06985000000000001,
0.050800000000000005
@ -2028,12 +2053,17 @@
{
"type": "plane",
"plane": {
"normal": [
0,
"xAxis": [
1,
-0
0,
0
],
"point": [
"yAxis": [
0,
-0,
-1
],
"origin": [
0.19823,
-0.0635,
0.050800000000000005
@ -2043,12 +2073,17 @@
{
"type": "plane",
"plane": {
"normal": [
0.5735800000000001,
"xAxis": [
0.81915,
-0.5735800000000001,
0
],
"point": [
"yAxis": [
0,
0,
-1
],
"origin": [
0.10982,
-0.03175,
0.050800000000000005
@ -2058,12 +2093,17 @@
{
"type": "plane",
"plane": {
"normal": [
"xAxis": [
0.90631,
0.4226200000000001,
-0.90631,
-0
0
],
"point": [
"yAxis": [
-0,
0,
1
],
"origin": [
0.10533,
0.01905,
0.050800000000000005
@ -2073,12 +2113,17 @@
{
"type": "plane",
"plane": {
"normal": [
"xAxis": [
1,
0,
-1,
-0
0
],
"point": [
"yAxis": [
-0,
0,
1
],
"origin": [
0.19374,
0.0381,
0.050800000000000005
@ -2088,12 +2133,17 @@
{
"type": "plane",
"plane": {
"normal": [
1,
"xAxis": [
0,
-0
1,
0
],
"point": [
"yAxis": [
-0,
0,
1
],
"origin": [
0.2413,
0.04445,
0.050800000000000005
@ -2103,12 +2153,17 @@
{
"type": "plane",
"plane": {
"normal": [
0,
"xAxis": [
1,
-0
0,
0
],
"point": [
"yAxis": [
0,
-0,
-1
],
"origin": [
0.19234,
0.050800000000000005,
0.050800000000000005
@ -2118,12 +2173,17 @@
{
"type": "plane",
"plane": {
"normal": [
-0.4226200000000001,
"xAxis": [
0.90631,
0.4226200000000001,
0
],
"point": [
"yAxis": [
0,
0,
-1
],
"origin": [
0.11614,
0.0381,
0.050800000000000005
@ -2133,12 +2193,17 @@
{
"type": "plane",
"plane": {
"normal": [
0,
"xAxis": [
1,
-0
0,
0
],
"point": [
"yAxis": [
0,
-0,
-1
],
"origin": [
0.04445,
0.0254,
0.050800000000000005
@ -2148,12 +2213,17 @@
{
"type": "plane",
"plane": {
"normal": [
-1,
"xAxis": [
0,
1,
0
],
"point": [
"yAxis": [
0,
0,
-1
],
"origin": [
0,
0.0127,
0.050800000000000005
@ -2163,12 +2233,17 @@
{
"type": "plane",
"plane": {
"normal": [
"xAxis": [
1,
0,
0,
1
0
],
"point": [
"yAxis": [
0,
1,
0
],
"origin": [
0,
0,
0
@ -2178,12 +2253,17 @@
{
"type": "plane",
"plane": {
"normal": [
"xAxis": [
1,
0,
0,
1
-0
],
"point": [
"yAxis": [
-0,
1,
0
],
"origin": [
0,
0,
0.1016
@ -2191,7 +2271,7 @@
}
}
],
"curves": [
"curves3D": [
{
"type": "line",
"line": {

View File

@ -1,7 +1,7 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((('kittycad.io export')), '2;1');
FILE_NAME('dump.step', '1970-01-01T00:00:00.0+00:00', ('Author unknown'), ('Organization unknown'), 'kittycad.io beta', 'kittycad.io', 'Authorization unknown');
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
FILE_NAME('dump.step', '1970-01-01T00:00:00.0+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
ENDSEC;
DATA;
@ -384,23 +384,23 @@ DATA;
#368 = ORIENTED_EDGE('NONE', *, *, #269, .T.);
#369 = EDGE_LOOP('NONE', (#355, #356, #357, #358, #359, #360, #361, #362, #363, #364, #365, #366, #367, #368));
#370 = CARTESIAN_POINT('NONE', (0, -0.0127, 0.0508));
#371 = DIRECTION('NONE', (-1, 0, -0));
#371 = DIRECTION('NONE', (-1, -0, 0));
#372 = AXIS2_PLACEMENT_3D('NONE', #370, #371, $);
#373 = PLANE('NONE', #372);
#374 = CARTESIAN_POINT('NONE', (0.039306734695977924, -0.025399999999999995, 0.0508));
#375 = DIRECTION('NONE', (0, -1, -0));
#375 = DIRECTION('NONE', (0, -1, 0));
#376 = AXIS2_PLACEMENT_3D('NONE', #374, #375, $);
#377 = PLANE('NONE', #376);
#378 = CARTESIAN_POINT('NONE', (0.11488842876320533, -0.05079999999999996, 0.05079999999999999));
#379 = DIRECTION('NONE', (-0.5735764363510459, -0.819152044288992, 0));
#379 = DIRECTION('NONE', (-0.5735764363510459, -0.8191520442889919, 0));
#380 = AXIS2_PLACEMENT_3D('NONE', #378, #379, $);
#381 = PLANE('NONE', #380);
#382 = CARTESIAN_POINT('NONE', (0.19623169406722757, -0.07619999999999999, 0.0508));
#383 = DIRECTION('NONE', (0, -1, -0));
#383 = DIRECTION('NONE', (0, -1, 0));
#384 = AXIS2_PLACEMENT_3D('NONE', #382, #383, $);
#385 = PLANE('NONE', #384);
#386 = CARTESIAN_POINT('NONE', (0.2413, -0.06985, 0.0508));
#387 = DIRECTION('NONE', (1, 0, -0));
#387 = DIRECTION('NONE', (1, -0, 0));
#388 = AXIS2_PLACEMENT_3D('NONE', #386, #387, $);
#389 = PLANE('NONE', #388);
#390 = CARTESIAN_POINT('NONE', (0.19823384137660915, -0.0635, 0.0508));
@ -408,19 +408,19 @@ DATA;
#392 = AXIS2_PLACEMENT_3D('NONE', #390, #391, $);
#393 = PLANE('NONE', #392);
#394 = CARTESIAN_POINT('NONE', (0.10982398353915601, -0.03174999999999997, 0.0508));
#395 = DIRECTION('NONE', (0.573576436351046, 0.8191520442889918, -0));
#395 = DIRECTION('NONE', (0.5735764363510459, 0.8191520442889917, -0));
#396 = AXIS2_PLACEMENT_3D('NONE', #394, #395, $);
#397 = PLANE('NONE', #396);
#398 = CARTESIAN_POINT('NONE', (0.105333141160801, 0.019049999999999987, 0.0508));
#399 = DIRECTION('NONE', (0.4226182617406993, -0.90630778703665, -0));
#399 = DIRECTION('NONE', (0.4226182617406993, -0.90630778703665, 0));
#400 = AXIS2_PLACEMENT_3D('NONE', #398, #399, $);
#401 = PLANE('NONE', #400);
#402 = CARTESIAN_POINT('NONE', (0.19374299899825406, 0.0381, 0.0508));
#403 = DIRECTION('NONE', (0, -1, -0));
#403 = DIRECTION('NONE', (0, -1, 0));
#404 = AXIS2_PLACEMENT_3D('NONE', #402, #403, $);
#405 = PLANE('NONE', #404);
#406 = CARTESIAN_POINT('NONE', (0.2413, 0.044449999999999996, 0.0508));
#407 = DIRECTION('NONE', (1, 0, -0));
#407 = DIRECTION('NONE', (1, -0, 0));
#408 = AXIS2_PLACEMENT_3D('NONE', #406, #407, $);
#409 = PLANE('NONE', #408);
#410 = CARTESIAN_POINT('NONE', (0.19233523789047138, 0.0508, 0.0508));
@ -436,7 +436,7 @@ DATA;
#420 = AXIS2_PLACEMENT_3D('NONE', #418, #419, $);
#421 = PLANE('NONE', #420);
#422 = CARTESIAN_POINT('NONE', (0, 0.0127, 0.0508));
#423 = DIRECTION('NONE', (-1, 0, -0));
#423 = DIRECTION('NONE', (-1, -0, 0));
#424 = AXIS2_PLACEMENT_3D('NONE', #422, #423, $);
#425 = PLANE('NONE', #424);
#426 = CARTESIAN_POINT('NONE', (0, 0, -0));
@ -475,7 +475,7 @@ DATA;
#459 = ADVANCED_FACE('NONE', (#458), #421, .T.);
#460 = FACE_OUTER_BOUND('NONE', #339, .T.);
#461 = ADVANCED_FACE('NONE', (#460), #425, .T.);
#462 = FACE_OUTER_BOUND('NONE', #354, .T.);
#462 = FACE_OUTER_BOUND('NONE', #354, .F.);
#463 = ADVANCED_FACE('NONE', (#462), #429, .F.);
#464 = FACE_OUTER_BOUND('NONE', #369, .T.);
#465 = ADVANCED_FACE('NONE', (#464), #433, .T.);

View File

@ -100,15 +100,15 @@ endfacet
facet normal 0.57357645 0 0.819152
outer loop
vertex 3.4311862 -0 -0.625
vertex 2.5385938 -0 0
vertex 2.5385938 -4 0
vertex 3.4311862 -4 -0.625
endloop
endfacet
facet normal 0.57357645 -0 0.819152
facet normal 0.5735763 0 0.8191522
outer loop
vertex 3.4311862 -4 -0.625
vertex 4.323779 -0 -1.25
vertex 3.4311862 -0 -0.625
vertex 2.5385938 -4 0
vertex 3.4311862 -4 -0.625
endloop
endfacet
facet normal 0.57357645 -0 0.819152
@ -118,11 +118,11 @@ facet normal 0.57357645 -0 0.819152
vertex 4.323779 -0 -1.25
endloop
endfacet
facet normal 0.5735763 0 0.8191522
facet normal 0.57357645 -0 0.819152
outer loop
vertex 2.5385938 -4 0
vertex 3.4311862 -0 -0.625
vertex 3.4311862 -4 -0.625
vertex 4.323779 -0 -1.25
vertex 2.5385938 -0 0
endloop
endfacet
facet normal 0.42261824 0 -0.9063078
@ -142,29 +142,29 @@ endfacet
facet normal 0.42261824 0 -0.9063078
outer loop
vertex 3.342784 -0 0.375
vertex 4.146974 -0 0.75
vertex 4.146974 -4 0.75
vertex 3.342784 -4 0.375
vertex 2.5385938 -0 0
endloop
endfacet
facet normal 0.42261833 0 -0.90630776
outer loop
vertex 4.146974 -0 0.75
vertex 5.755354 -0 1.5
vertex 5.755354 -4 1.5
vertex 4.146974 -4 0.75
endloop
endfacet
facet normal 0.42261824 0 -0.9063078
outer loop
vertex 3.342784 -4 0.375
vertex 2.5385938 -0 0
vertex 4.146974 -4 0.75
vertex 3.342784 -0 0.375
vertex 4.146974 -0 0.75
endloop
endfacet
facet normal 0.42261833 0 -0.90630776
outer loop
vertex 5.755354 -4 1.5
vertex 4.146974 -4 0.75
vertex 4.146974 -0 0.75
vertex 5.755354 -0 1.5
endloop
endfacet
facet normal 0 0 -1
@ -258,6 +258,13 @@ facet normal 0 1 -0
vertex 3.5 -0 1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 4.146974 -0 0.75
vertex 3.342784 -0 0.375
vertex 3.5 -0 1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 3.4311862 -0 -0.625
@ -265,11 +272,53 @@ facet normal 0 1 0
vertex 3.0950184 -0 -1
endloop
endfacet
facet normal 0 0.99999994 0
outer loop
vertex 4.146974 -0 0.75
vertex 5.644507 -0 2
vertex 5.755354 -0 1.5
endloop
endfacet
facet normal -0 1 0
outer loop
vertex 0 -0 1
vertex 3.5 -0 1
vertex 2.5385938 -0 0
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 3.342784 -0 0.375
vertex 3.5 -0 1
vertex 0 -0 1
vertex 2.5385938 -0 0
vertex 0 -0 0
endloop
endfacet
facet normal -0 1 0
outer loop
vertex 5.644507 -0 2
vertex 9.5 -0 2
vertex 5.755354 -0 1.5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 9.5 -0 2
vertex 9.5 -0 1.5
vertex 5.755354 -0 1.5
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 4.146974 -0 0.75
vertex 3.5 -0 1
vertex 5.644507 -0 2
endloop
endfacet
facet normal 0 0.99999994 0
outer loop
vertex 2.5385938 -0 0
vertex 3.4311862 -0 -0.625
vertex 3.0950184 -0 -1
endloop
endfacet
facet normal 0 0.99999994 0
@ -281,86 +330,37 @@ facet normal 0 0.99999994 0
endfacet
facet normal 0 1 0
outer loop
vertex 0 -0 -1
vertex 2.5385938 -0 0
vertex 3.0950184 -0 -1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 0 -0 -1
vertex 0 -0 0
vertex 2.5385938 -0 0
endloop
endfacet
facet normal 0 0.99999994 -0
outer loop
vertex 9.5 -0 -3
vertex 6.108964 -0 -2.5
vertex 9.5 -0 -2.5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 9.5 -0 -3
vertex 5.9513144 -0 -3
vertex 6.108964 -0 -2.5
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 5.9513144 -0 -3
vertex 4.323779 -0 -1.25
endloop
endfacet
facet normal 0 0.99999994 0
outer loop
vertex 9.5 -0 -2.5
vertex 9.5 -0 -3
vertex 6.108964 -0 -2.5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 5.644507 -0 2
vertex 5.755354 -0 1.5
vertex 4.146974 -0 0.75
vertex 6.108964 -0 -2.5
vertex 9.5 -0 -3
vertex 5.9513144 -0 -3
endloop
endfacet
facet normal 0 0.99999994 -0
facet normal 0 1 0
outer loop
vertex 2.5385938 -0 0
vertex 3.0950184 -0 -1
vertex 2.5385938 -0 0
vertex 3.4311862 -0 -0.625
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 4.146974 -0 0.75
vertex 3.5 -0 1
vertex 5.644507 -0 2
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 9.5 -0 1.5
vertex 5.755354 -0 1.5
vertex 9.5 -0 2
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 5.755354 -0 1.5
vertex 5.644507 -0 2
vertex 9.5 -0 2
vertex 0 -0 -1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 2.5385938 -0 0
vertex 0 -0 -1
vertex 0 -0 0
vertex 0 -0 1
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 3.5 -0 1
vertex 2.5385938 -0 0
vertex 0 -0 1
endloop
endfacet
facet normal -0 -1 0

View File

@ -1,7 +1,5 @@
import { test, expect } from '@playwright/test'
import { secrets } from './secrets'
import { EngineCommand } from '../../src/lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { getUtils } from './test-utils'
import waitOn from 'wait-on'
import { Themes } from '../../src/lib/theme'
@ -53,40 +51,38 @@ test('Basic sketch', async ({ page }) => {
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.waitForDefaultPlanesVisibilityChange()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await Promise.all([
u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
),
u.waitForDefaultPlanesVisibilityChange(),
])
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
// select a plane
await u.doAndWaitForCmd(() => page.mouse.click(700, 200), 'edit_mode_enter')
await u.waitForCmdReceive('set_tool')
await page.mouse.click(700, 200)
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Line' }).click(),
'set_tool'
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
const startXPx = 600
await u.doAndWaitForCmd(
() => page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10),
'mouse_click',
false
)
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt = '[23.89, -32.23]'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const startAt = '[18.26, -24.63]'
const num = '18.43'
const num = 24.11
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
@ -97,27 +93,22 @@ test('Basic sketch', async ({ page }) => {
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num}], %)`)
|> line([0, ${num + 0.01}], %)`)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num}], %)
|> line([-36.69, 0], %)`)
|> line([0, ${num + 0.01}], %)
|> line([-48, 0], %)`)
// deselect line tool
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Line' }).click(),
'set_tool'
)
await page.getByRole('button', { name: 'Line' }).click()
await page.waitForTimeout(100)
// click between first two clicks to get center of the line
await u.doAndWaitForCmd(
() => page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10),
'select_with_point'
)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10)
await page.waitForTimeout(100)
// hold down shift
await page.keyboard.down('Shift')
@ -133,7 +124,7 @@ test('Basic sketch', async ({ page }) => {
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
|> line({ to: [${num}, 0], tag: 'seg01' }, %)
|> line([0, ${num}], %)
|> line([0, ${num + 0.01}], %)
|> angledLine([180, segLen('seg01', %)], %)`)
})
@ -206,7 +197,7 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
test('executes on load', async ({ page, context }) => {
const u = getUtils(page)
await context.addInitScript(async (token) => {
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
@ -271,59 +262,41 @@ test('Can create sketches on all planes and their back sides', async ({
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.waitForDefaultPlanesVisibilityChange()
const camCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 15, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 },
vantage: { x: 30, y: 30, z: 30 },
},
}
const camPos: [number, number, number] = [100, 100, 100]
const TestSinglePlane = async ({
viewCmd,
expectedCode,
clickCoords,
}: {
viewCmd: EngineCommand
viewCmd: [number, number, number]
expectedCode: string
clickCoords: { x: number; y: number }
}) => {
await u.openDebugPanel()
await u.sendCustomCmd(viewCmd)
await u.updateCamPosition(viewCmd)
await u.clearCommandLogs()
// await page.waitForTimeout(200)
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.waitForDefaultPlanesVisibilityChange()
await u.closeDebugPanel()
await page.mouse.click(clickCoords.x, clickCoords.y)
await u.openDebugPanel()
await page.waitForTimeout(300) // wait for animation
await expect(page.getByRole('button', { name: 'Line' })).toBeVisible()
// draw a line
const startXPx = 600
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Line' }).click()
await u.waitForCmdReceive('set_tool')
await u.clearCommandLogs()
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await u.openDebugPanel()
await u.waitForCmdReceive('mouse_click')
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await u.openDebugPanel()
await expect(page.locator('.cm-content')).toHaveText(expectedCode)
await page.getByRole('button', { name: 'Line' }).click()
await u.clearCommandLogs()
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
@ -333,51 +306,41 @@ test('Can create sketches on all planes and their back sides', async ({
const codeTemplate = (
plane = 'XY',
sign = ''
rounded = false,
otherThing = '1'
) => `const part001 = startSketchOn('${plane}')
|> startProfileAt([${sign}6.88, -9.29], %)
|> line([${sign}6.95, 0], %)`
|> startProfileAt([28.9${otherThing}, -39${rounded ? '' : '.01'}], %)`
await TestSinglePlane({
viewCmd: camCmd,
viewCmd: camPos,
expectedCode: codeTemplate('XY'),
clickCoords: { x: 700, y: 350 }, // red plane
clickCoords: { x: 600, y: 388 }, // red plane
// clickCoords: { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
})
await TestSinglePlane({
viewCmd: camCmd,
expectedCode: codeTemplate('YZ'),
clickCoords: { x: 1000, y: 200 }, // green plane
viewCmd: camPos,
expectedCode: codeTemplate('YZ', true),
clickCoords: { x: 700, y: 300 }, // green plane
})
await TestSinglePlane({
viewCmd: camCmd,
expectedCode: codeTemplate('XZ', '-'),
clickCoords: { x: 630, y: 130 }, // blue plane
viewCmd: camPos,
expectedCode: codeTemplate('XZ'),
clickCoords: { x: 700, y: 80 }, // blue plane
})
// new camera angle to click the back side of all three planes
const camCmdBackSide: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: -15, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 },
vantage: { x: -30, y: -30, z: -30 },
},
}
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
await TestSinglePlane({
viewCmd: camCmdBackSide,
expectedCode: codeTemplate('-XY', '-'),
clickCoords: { x: 705, y: 136 }, // back of red plane
expectedCode: codeTemplate('-XY', false, '3'),
clickCoords: { x: 601, y: 118 }, // back of red plane
})
await TestSinglePlane({
viewCmd: camCmdBackSide,
expectedCode: codeTemplate('-YZ', '-'),
clickCoords: { x: 1000, y: 350 }, // back of green plane
expectedCode: codeTemplate('-YZ'),
clickCoords: { x: 730, y: 219 }, // back of green plane
})
await TestSinglePlane({
viewCmd: camCmdBackSide,
expectedCode: codeTemplate('-XZ'),
clickCoords: { x: 600, y: 400 }, // back of blue plane
expectedCode: codeTemplate('-XZ', true),
clickCoords: { x: 680, y: 427 }, // back of blue plane
})
})
@ -387,7 +350,6 @@ test('Auto complete works', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.waitForDefaultPlanesVisibilityChange()
// this test might be brittle as we add and remove functions
// but should also be easy to update.
@ -478,38 +440,36 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.waitForDefaultPlanesVisibilityChange()
const xAxisClick = () => page.mouse.click(700, 250)
const emptySpaceClick = () => page.mouse.click(700, 300)
const topHorzSegmentClick = () => page.mouse.click(700, 285)
const bottomHorzSegmentClick = () => page.mouse.click(750, 393)
const xAxisClick = () =>
page.mouse.click(700, 250).then(() => page.waitForTimeout(100))
const emptySpaceClick = () =>
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
const topHorzSegmentClick = () =>
page.mouse.click(709, 289).then(() => page.waitForTimeout(100))
const bottomHorzSegmentClick = () =>
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.waitForDefaultPlanesVisibilityChange()
// select a plane
await u.doAndWaitForCmd(() => page.mouse.click(700, 200), 'edit_mode_enter')
await u.waitForCmdReceive('set_tool')
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Line' }).click(),
'set_tool'
)
await page.mouse.click(700, 200)
await page.waitForTimeout(700) // wait for animation
const startXPx = 600
await u.doAndWaitForCmd(
() => page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10),
'mouse_click',
false
)
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt = '[23.89, -32.23]'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)`)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
const startAt = '[18.26, -24.63]'
const num = '18.43'
const num2 = '36.69'
const num = 24.11
const num2 = '48'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
@ -520,20 +480,17 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num}], %)`)
|> line([0, ${num + 0.01}], %)`)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num}], %)
|> line([0, ${num + 0.01}], %)
|> line([-${num2}, 0], %)`)
// deselect line tool
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Line' }).click(),
'set_tool'
)
await page.getByRole('button', { name: 'Line' }).click()
await u.closeDebugPanel()
const selectionSequence = async () => {
@ -555,79 +512,72 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// now check clicking works including axis
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
await u.doAndWaitForCmd(topHorzSegmentClick, 'select_with_point', false)
await topHorzSegmentClick()
await page.keyboard.down('Shift')
const absYButton = page.getByRole('button', { name: 'ABS Y' })
await expect(absYButton).toBeDisabled()
await u.doAndWaitForCmd(xAxisClick, 'select_with_point', false)
await xAxisClick()
await page.keyboard.up('Shift')
await absYButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
await u.doAndWaitForCmd(emptySpaceClick, 'select_clear', false)
await emptySpaceClick()
// same selection but click the axis first
await u.doAndWaitForCmd(xAxisClick, 'select_with_point', false)
await xAxisClick()
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await u.doAndWaitForCmd(topHorzSegmentClick, 'select_with_point', false)
await topHorzSegmentClick()
await page.keyboard.up('Shift')
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
await u.doAndWaitForCmd(emptySpaceClick, 'select_clear', false)
await emptySpaceClick()
// check the same selection again by putting cursor in code first then selecting axis
await u.doAndWaitForCmd(
() => page.getByText(` |> line([-${num2}, 0], %)`).click(),
'select_clear',
false
)
await page.getByText(` |> line([-${num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await expect(absYButton).toBeDisabled()
await u.doAndWaitForCmd(xAxisClick, 'select_with_point', false)
await xAxisClick()
await page.keyboard.up('Shift')
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
await u.doAndWaitForCmd(emptySpaceClick, 'select_clear', false)
await emptySpaceClick()
// select segment in editor than another segment in scene and check there are two cursors
await u.doAndWaitForCmd(
() => page.getByText(` |> line([-${num2}, 0], %)`).click(),
'select_clear',
false
)
await page.getByText(` |> line([-${num2}, 0], %)`).click()
await page.waitForTimeout(300)
await page.keyboard.down('Shift')
await expect(page.locator('.cm-cursor')).toHaveCount(1)
await u.doAndWaitForCmd(bottomHorzSegmentClick, 'select_with_point', false) // another segment, bottom one
await bottomHorzSegmentClick()
await page.keyboard.up('Shift')
await expect(page.locator('.cm-cursor')).toHaveCount(2)
// clear selection by clicking on nothing
await u.doAndWaitForCmd(emptySpaceClick, 'select_clear', false)
await emptySpaceClick()
}
await selectionSequence()
// hovering in fresh sketch worked, lets try exiting and re-entering
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
'edit_mode_exit'
)
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(200)
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// select a line
await u.doAndWaitForCmd(topHorzSegmentClick, 'select_clear', false)
await topHorzSegmentClick()
await page.waitForTimeout(200)
// enter sketch again
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
'edit_mode_enter',
false
)
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(700) // wait for animation
// hover again and check it works
await selectionSequence()
@ -697,6 +647,8 @@ test('Can extrude from the command bar', async ({ page, context }) => {
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
let cmdSearchBar = page.getByPlaceholder('Search commands')
await page.keyboard.press('Meta+K')
@ -710,10 +662,7 @@ test('Can extrude from the command bar', async ({ page, context }) => {
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
// Click to select face and set distance
await u.openAndClearDebugPanel()
await page.getByText('|> startProfileAt([-6.95, 4.98], %)').click()
await u.waitForCmdReceive('select_add')
await u.closeDebugPanel()
await page.getByRole('button', { name: 'Continue' }).click()
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
await page.keyboard.press('Enter')
@ -735,3 +684,166 @@ test('Can extrude from the command bar', async ({ page, context }) => {
|> extrude(5, %)`
)
})
test('Can add multiple sketches', async ({ page }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt = '[23.89, -32.23]'
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num = 24.11
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)`)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num + 0.01}], %)`)
await page.mouse.click(startXPx, 500 - PUR * 20)
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|> startProfileAt(${startAt}, %)
|> line([${num}, 0], %)
|> line([0, ${num + 0.01}], %)
|> line([-48, 0], %)`
await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch)
// exit the sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.updateCamPosition([0, 100, 100])
// start a new sketch
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
await page.mouse.click(673, 384)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await u.clearAndCloseDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt2 = '[23.61, -31.85]'
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num2 = 23.83
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)`.replace(/\s/g, '')
)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${num2}], %)`.replace(/\s/g, '')
)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${num2}], %)
|> line([-47.44, 0], %)`.replace(/\s/g, '')
)
})
test('ProgramMemory can be serialised', async ({ page, context }) => {
const u = getUtils(page)
await context.addInitScript(async (token) => {
localStorage.setItem(
'persistCode',
`const part = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, 1], %)
|> line([1, 0], %)
|> line([0, -1], %)
|> close(%)
|> extrude(1, %)
|> patternLinear({
axis: [1, 0, 1],
repetitions: 3,
distance: 6
}, %)`
)
})
await page.setViewportSize({ width: 1000, height: 500 })
await page.goto('/')
const messages: string[] = []
// Listen for all console events and push the message text to an array
page.on('console', (message) => messages.push(message.text()))
await u.waitForAuthSkipAppStart()
// wait for execution done
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
const forbiddenMessages = ['cannot serialize tagged newtype variant']
forbiddenMessages.forEach((forbiddenMessage) => {
messages.forEach((message) => {
expect(message).not.toContain(forbiddenMessage)
})
})
})

View File

@ -1,7 +1,5 @@
import { test, expect } from '@playwright/test'
import { secrets } from './secrets'
import { EngineCommand } from '../../src/lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { getUtils } from './test-utils'
import { Models } from '@kittycad/lib'
import fsp from 'fs/promises'
@ -40,19 +38,8 @@ test('change camera, show planes', async ({ page, context }) => {
await u.waitForAuthSkipAppStart()
await u.openAndClearDebugPanel()
const camCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 0, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 },
vantage: { x: 0, y: 85, z: 85 },
},
}
await u.sendCustomCmd(camCmd)
await u.waitForCmdReceive('default_camera_look_at')
const camPos: [number, number, number] = [0, 85, 85]
await u.updateCamPosition(camPos)
// rotate
await u.closeDebugPanel()
@ -62,13 +49,11 @@ test('change camera, show planes', async ({ page, context }) => {
await page.mouse.up({ button: 'right' })
await u.openDebugPanel()
await u.waitForCmdReceive('camera_drag_end')
await page.waitForTimeout(500)
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.waitForDefaultPlanesVisibilityChange()
await u.closeDebugPanel()
await expect(page).toHaveScreenshot({
@ -77,10 +62,8 @@ test('change camera, show planes', async ({ page, context }) => {
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.waitForDefaultPlanesVisibilityChange()
await u.sendCustomCmd(camCmd)
await u.waitForCmdReceive('default_camera_look_at')
await u.updateCamPosition(camPos)
await u.clearCommandLogs()
await u.closeDebugPanel()
@ -93,12 +76,10 @@ test('change camera, show planes', async ({ page, context }) => {
await page.keyboard.up('Shift')
await u.openDebugPanel()
await u.waitForCmdReceive('camera_drag_end')
await page.waitForTimeout(300)
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.waitForDefaultPlanesVisibilityChange()
await u.closeDebugPanel()
await expect(page).toHaveScreenshot({
@ -107,10 +88,8 @@ test('change camera, show planes', async ({ page, context }) => {
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.waitForDefaultPlanesVisibilityChange()
await u.sendCustomCmd(camCmd)
await u.waitForCmdReceive('default_camera_look_at')
await u.updateCamPosition(camPos)
await u.clearCommandLogs()
await u.closeDebugPanel()
@ -119,17 +98,15 @@ test('change camera, show planes', async ({ page, context }) => {
await page.keyboard.down('Control')
await page.mouse.move(700, 400)
await page.mouse.down({ button: 'right' })
await page.mouse.move(700, 350)
await page.mouse.move(700, 300)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Control')
await u.openDebugPanel()
await u.waitForCmdReceive('camera_drag_end')
await page.waitForTimeout(300)
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.waitForDefaultPlanesVisibilityChange()
await u.closeDebugPanel()
await expect(page).toHaveScreenshot({
@ -164,11 +141,11 @@ const part001 = startSketchOn('-XZ')
|> xLineTo({ to: totalLen, tag: 'seg03' }, %)
|> yLine({ length: -armThick, tag: 'seg01' }, %)
|> angledLineThatIntersects({
angle: _180,
angle: HALF_TURN,
offset: -armThick,
intersectTag: 'seg04'
}, %)
|> angledLineToY([segAng('seg04', %) + 180, _0], %)
|> angledLineToY([segAng('seg04', %) + 180, ZERO], %)
|> angledLineToY({
angle: -bottomAng,
to: -totalHeightHalf - armThick,
@ -177,12 +154,12 @@ const part001 = startSketchOn('-XZ')
|> xLineTo(segEndX('seg03', %) + 0, %)
|> yLine(-segLen('seg01', %), %)
|> angledLineThatIntersects({
angle: _180,
angle: HALF_TURN,
offset: -armThick,
intersectTag: 'seg02'
}, %)
|> angledLineToY([segAng('seg02', %) + 180, -baseHeight], %)
|> xLineTo(_0, %)
|> xLineTo(ZERO, %)
|> close(%)
|> extrude(4, %)`
)
@ -191,7 +168,6 @@ const part001 = startSketchOn('-XZ')
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.waitForDefaultPlanesVisibilityChange()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.waitForCmdReceive('extrude')
await page.waitForTimeout(1000)
@ -386,3 +362,60 @@ const part001 = startSketchOn('-XZ')
})
}
})
test('extrude on each default plane should be stable', async ({
page,
context,
}) => {
const u = getUtils(page)
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|> startProfileAt([14.06, 8.88], %)
|> line([12.98, -0.15], %)
|> line([5.56, 9.89], %)
|> line([-11.28, 8.96], %)
|> line([-10.81, -7.57], %)
|> close(%)
|> extrude(20, %)
`
await context.addInitScript(async (code) => {
localStorage.setItem('persistCode', code)
}, makeCode('XY'))
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// wait for execution done
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.getByText('Code').click()
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
await page.getByText('Code').click()
const runSnapshotsForOtherPlanes = async (plane = 'XY') => {
// clear code
await u.removeCurrentCode()
// add makeCode('XZ')
await page.locator('.cm-content').fill(makeCode(plane))
// wait for execution done
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.getByText('Code').click()
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
await page.getByText('Code').click()
}
await runSnapshotsForOtherPlanes('-XY')
await runSnapshotsForOtherPlanes('XZ')
await runSnapshotsForOtherPlanes('-XZ')
await runSnapshotsForOtherPlanes('YZ')
await runSnapshotsForOtherPlanes('-YZ')
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -80,10 +80,21 @@ export function getUtils(page: Page) {
waitForAuthSkipAppStart: () => waitForPageLoad(page),
removeCurrentCode: () => removeCurrentCode(page),
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
updateCamPosition: async (xyz: [number, number, number]) => {
const fillInput = async () => {
await page.fill('[data-testid="cam-x-position"]', String(xyz[0]))
await page.fill('[data-testid="cam-y-position"]', String(xyz[1]))
await page.fill('[data-testid="cam-z-position"]', String(xyz[2]))
}
await fillInput()
await page.waitForTimeout(100)
await fillInput()
await page.waitForTimeout(100)
await fillInput()
await page.waitForTimeout(100)
},
clearCommandLogs: () => clearCommandLogs(page),
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
waitForDefaultPlanesVisibilityChange: () =>
waitForDefaultPlanesToBeVisible(page),
openDebugPanel: () => openDebugPanel(page),
closeDebugPanel: () => closeDebugPanel(page),
openAndClearDebugPanel: async () => {

View File

@ -86,13 +86,11 @@ describe('ZMA (Tauri, Linux)', () => {
expect(await homeSection.getText()).toContain('project-000')
})
it('opens the new file and expects an error on Linux', async () => {
it('opens the new file and expects a loading stream', async () => {
const projectLink = await $('[data-testid="project-link"]')
await click(projectLink)
const error = await $('h3')
expect(await error.getText()).toContain(
"Can't find variable: RTCPeerConnection"
)
const loadingText = await $('[data-testid="loading-stream"]')
expect(await loadingText.getText()).toContain('Loading stream...')
await browser.execute('window.location.href = "tauri://localhost/home"')
})

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.14.0",
"version": "0.15.0",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.10.2",
@ -10,7 +10,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.17",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.46",
"@kittycad/lib": "^0.0.53",
"@lezer/javascript": "^1.4.9",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
@ -21,6 +21,7 @@
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@ts-stack/markdown": "^1.5.0",
"@tweenjs/tween.js": "^23.1.1",
"@types/node": "^16.7.13",
"@types/react": "^18.2.41",
"@types/react-dom": "^18.0.0",
@ -45,6 +46,7 @@
"sketch-helpers": "^0.0.4",
"swr": "^2.2.2",
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
"three": "^0.160.0",
"toml": "^3.0.0",
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
@ -82,7 +84,9 @@
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src",
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json"
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
"postinstall": "patch-package && yarn xstate:typegen",
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\""
},
"prettier": {
"trailingComma": "es5",
@ -113,6 +117,7 @@
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react-modal": "^3.16.3",
"@types/three": "^0.160.0",
"@types/uuid": "^9.0.4",
"@types/wait-on": "^5.3.4",
"@types/wicg-file-system-access": "^2020.9.6",
@ -124,21 +129,26 @@
"@wdio/local-runner": "^8.24.3",
"@wdio/mocha-framework": "^8.24.3",
"@wdio/spec-reporter": "^8.24.2",
"@xstate/cli": "^0.5.17",
"autoprefixer": "^10.4.13",
"eslint": "^8.53.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.12.0",
"happy-dom": "^10.8.0",
"husky": "^8.0.3",
"patch-package": "^8.0.0",
"pixelmatch": "^5.3.0",
"pngjs": "^7.0.0",
"postcss": "^8.4.31",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.0",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.3.6",
"vite": "^4.5.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.2.1",
"vitest-webgl-canvas-mock": "^1.1.0",
"wait-on": "^7.2.0",
"yarn": "^1.22.19"
}

138
patches/three+0.160.0.patch Normal file
View File

@ -0,0 +1,138 @@
diff --git a/node_modules/three/examples/jsm/controls/OrbitControls.js b/node_modules/three/examples/jsm/controls/OrbitControls.js
index f29e7fe..0ef636b 100644
--- a/node_modules/three/examples/jsm/controls/OrbitControls.js
+++ b/node_modules/three/examples/jsm/controls/OrbitControls.js
@@ -113,6 +113,25 @@ class OrbitControls extends EventDispatcher {
// public methods
//
+ this.interactionGuards = {
+ pan: {
+ description: 'Right click + Shift + drag or middle click + drag',
+ callback: (e) => e.button === 2 && !e.ctrlKey,
+ },
+ zoom: {
+ description: 'Scroll wheel or Right click + Ctrl + drag',
+ dragCallback: (e) => e.button === 2 && e.ctrlKey,
+ scrollCallback: () => true,
+ },
+ rotate: {
+ description: 'Right click + drag',
+ callback: (e) => e.button === 0,
+ },
+ }
+ this.setMouseGuards = (interactionGuards) => {
+ this.interactionGuards = interactionGuards
+ }
+
this.getPolarAngle = function () {
return spherical.phi;
@@ -1057,92 +1076,21 @@ class OrbitControls extends EventDispatcher {
function onMouseDown( event ) {
- let mouseAction;
-
- switch ( event.button ) {
-
- case 0:
-
- mouseAction = scope.mouseButtons.LEFT;
- break;
-
- case 1:
-
- mouseAction = scope.mouseButtons.MIDDLE;
- break;
-
- case 2:
-
- mouseAction = scope.mouseButtons.RIGHT;
- break;
-
- default:
-
- mouseAction = - 1;
-
- }
-
- switch ( mouseAction ) {
-
- case MOUSE.DOLLY:
-
- if ( scope.enableZoom === false ) return;
-
- handleMouseDownDolly( event );
-
- state = STATE.DOLLY;
-
- break;
-
- case MOUSE.ROTATE:
-
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
- if ( scope.enablePan === false ) return;
-
- handleMouseDownPan( event );
-
- state = STATE.PAN;
-
- } else {
-
- if ( scope.enableRotate === false ) return;
-
- handleMouseDownRotate( event );
-
- state = STATE.ROTATE;
-
- }
-
- break;
-
- case MOUSE.PAN:
-
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
- if ( scope.enableRotate === false ) return;
-
- handleMouseDownRotate( event );
-
- state = STATE.ROTATE;
-
- } else {
-
- if ( scope.enablePan === false ) return;
-
- handleMouseDownPan( event );
-
- state = STATE.PAN;
-
- }
-
- break;
-
- default:
-
- state = STATE.NONE;
-
- }
+ if (scope.interactionGuards.pan.callback(event)) {
+ if (scope.enablePan === false) return
+ handleMouseDownPan(event)
+ state = STATE.PAN
+ } else if (scope.interactionGuards.rotate.callback(event)) {
+ if (scope.enableRotate === false) return
+ handleMouseDownRotate(event)
+ state = STATE.ROTATE
+ } else if (scope.interactionGuards.zoom.dragCallback(event)) {
+ if (scope.enableZoom === false) return
+ handleMouseDownDolly(event)
+ state = STATE.DOLLY
+ } else {
+ return
+ }
if ( state !== STATE.NONE ) {

View File

@ -1,4 +1,4 @@
import { defineConfig, devices } from '@playwright/test';
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
@ -78,5 +78,4 @@ export default defineConfig({
// url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});
})

View File

@ -0,0 +1,26 @@
import requests
import os
webhook_url = os.getenv('DISCORD_WEBHOOK_URL')
release_version = os.getenv('RELEASE_VERSION')
release_body = os.getenv('RELEASE_BODY')
# message to send to Discord
data = {
"content":
f'''
**{release_version}** is now available! Check out the latest features and improvements here: https://zoo.dev/modeling-app/download
{release_body}
''',
"username": "Modeling App Release Updates",
"avatar_url": "https://raw.githubusercontent.com/KittyCAD/modeling-app/main/public/discord-avatar.png"
}
# POST request to the Discord webhook
response = requests.post(webhook_url, json=data)
# Check for success
if response.status_code == 204:
print("Successfully sent the message to Discord.")
else:
print("Failed to send the message to Discord.")

BIN
public/discord-avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

104
src-tauri/Cargo.lock generated
View File

@ -67,9 +67,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.75"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
[[package]]
name = "app"
@ -95,7 +95,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -542,7 +542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -552,7 +552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
dependencies = [
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -582,7 +582,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -593,7 +593,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
dependencies = [
"darling_core",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -899,7 +899,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -1664,9 +1664,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.42"
version = "0.2.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aa554d86b6dbbd976a659c912ae25ce817b4378eb12a5684907e263410f0a7b"
checksum = "a086e1a1bbddb3b38959c0f0ce6de6b3a3b7566e38e0b7d5fb101e91911beed4"
dependencies = [
"anyhow",
"async-trait",
@ -2215,7 +2215,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -2368,7 +2368,7 @@ dependencies = [
"regex",
"regex-syntax 0.7.5",
"structmeta",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -2487,7 +2487,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -2555,7 +2555,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -2657,9 +2657,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.67"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@ -2675,9 +2675,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -3235,9 +3235,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.193"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
@ -3253,13 +3253,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.193"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -3275,9 +3275,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.108"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa 1.0.6",
"ryu",
@ -3302,7 +3302,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -3352,7 +3352,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -3556,7 +3556,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -3567,7 +3567,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -3605,9 +3605,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.33"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@ -3760,9 +3760,9 @@ dependencies = [
[[package]]
name = "tauri"
version = "1.5.3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d563b672acde8d0cc4c1b1f5b855976923f67e8d6fe1eba51df0211e197be2"
checksum = "fd27c04b9543776a972c86ccf70660b517ecabbeced9fb58d8b961a13ad129af"
dependencies = [
"anyhow",
"bytes",
@ -3812,9 +3812,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defbfc551bd38ab997e5f8e458f87396d2559d05ce32095076ad6c30f7fc5f9c"
checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532"
dependencies = [
"anyhow",
"cargo_toml",
@ -3831,9 +3831,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "1.4.1"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b3475e55acec0b4a50fb96435f19631fb58cbcd31923e1a213de5c382536bbb"
checksum = "a1554c5857f65dbc377cefb6b97c8ac77b1cb2a90d30d3448114d5d6b48a77fc"
dependencies = [
"base64 0.21.2",
"brotli",
@ -3857,9 +3857,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "1.4.2"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acea6445eececebd72ed7720cfcca46eee3b5bad8eb408be8f7ef2e3f7411500"
checksum = "277abf361a3a6993ec16bcbb179de0d6518009b851090a01adfea12ac89fa875"
dependencies = [
"heck 0.4.1",
"proc-macro2",
@ -3872,7 +3872,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs-extra"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#537053d3171a7374a1a86fed422523e7b45a4fb8"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#01211ff0759d578e0e9ac8c98c31fdf09077eb34"
dependencies = [
"log",
"serde",
@ -3883,9 +3883,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "0.14.1"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07f8e9e53e00e9f41212c115749e87d5cd2a9eebccafca77a19722eeecd56d43"
checksum = "cf2d0652aa2891ff3e9caa2401405257ea29ab8372cce01f186a5825f1bd0e76"
dependencies = [
"gtk",
"http",
@ -3904,9 +3904,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "0.14.2"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "803a01101bc611ba03e13329951a1bde44287a54234189b9024b78619c1bc206"
checksum = "6cae61fbc731f690a4899681c9052dde6d05b159b44563ace8186fc1bfb7d158"
dependencies = [
"cocoa",
"gtk",
@ -3924,9 +3924,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "1.5.1"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a52165bb340e6f6a75f1f5eeeab1bb49f861c12abe3a176067d53642b5454986"
checksum = "ece74810b1d3d44f29f732a7ae09a63183d63949bbdd59c61f8ed2a1b70150db"
dependencies = [
"brotli",
"ctor",
@ -4009,7 +4009,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -4051,9 +4051,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.34.0"
version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
@ -4193,7 +4193,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
]
[[package]]
@ -4443,7 +4443,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
"wasm-bindgen-shared",
]
@ -4477,7 +4477,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@ -12,17 +12,17 @@ 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.5.0", features = [] }
tauri-build = { version = "1.5.1", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.42"
kittycad = "0.2.53"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "1.5.3", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
tauri = { version = "1.5.4", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tokio = { version = "1.34.0", features = ["time"] }
tokio = { version = "1.36.0", features = ["time"] }
toml = "0.8.2"
[features]

View File

@ -7,6 +7,7 @@ use std::io::Read;
use anyhow::Result;
use oauth2::TokenResponse;
use std::process::Command;
use tauri::{InvokeError, Manager};
const DEFAULT_HOST: &str = "https://api.kittycad.io";

View File

@ -7,7 +7,7 @@
},
"package": {
"productName": "zoo-modeling-app",
"version": "0.14.0"
"version": "0.15.0"
},
"tauri": {
"allowlist": {

View File

@ -1,73 +0,0 @@
import { render, screen } from '@testing-library/react'
import { App } from './App'
import { describe, test, vi } from 'vitest'
import {
Route,
RouterProvider,
createMemoryRouter,
createRoutesFromElements,
} from 'react-router-dom'
import { GlobalStateProvider } from './components/GlobalStateProvider'
import CommandBarProvider from 'components/CommandBar/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 {
constructor(ls: ((rect: any) => void) | undefined) {
listener = ls
}
observe() {}
unobserve() {}
disconnect() {}
}
describe('App tests', () => {
test('Renders the modeling app screen, including "Variables" pane.', () => {
vi.mock('react-router-dom', async () => {
const actual = (await vi.importActual('react-router-dom')) as Record<
string,
any
>
return {
...actual,
useParams: () => ({ id: BROWSER_FILE_NAME }),
useLoaderData: () => ({ code: null }),
}
})
render(
<TestWrap>
<App />
</TestWrap>
)
const linkElement = screen.getByText(/Variables/i)
expect(linkElement).toBeInTheDocument()
vi.restoreAllMocks()
})
})
function TestWrap({ children }: { children: React.ReactNode }) {
// 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

@ -19,21 +19,24 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import { useHotkeys } from 'react-hotkeys-hook'
import { getNormalisedCoordinates } from './lib/utils'
import { useLoaderData } from 'react-router-dom'
import { IndexLoaderData } from './Router'
import { useLoaderData, useNavigate } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { onboardingPaths } from 'routes/Onboarding'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { CodeMenu } from 'components/CodeMenu'
import { TextEditor } from 'components/TextEditor'
import { Themes, getSystemTheme } from 'lib/theme'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
import { engineCommandManager } from './lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { isTauri } from 'lib/isTauri'
export function App() {
const { project, file } = useLoaderData() as IndexLoaderData
const navigate = useNavigate()
const filePath = useAbsoluteFilePath()
useHotKeyListener()
const {
@ -51,8 +54,7 @@ export function App() {
}))
const { settings } = useGlobalStateContext()
const { showDebugPanel, onboardingStatus, cameraControls, theme } =
settings?.context || {}
const { showDebugPanel, onboardingStatus, theme } = settings?.context || {}
const { state, send } = useModelingContext()
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
@ -71,6 +73,16 @@ export function App() {
useHotkeys('shift + e', () => togglePane('kclErrors'))
useHotkeys('shift + d', () => togglePane('debug'))
useHotkeys('esc', () => send('Cancel'))
useHotkeys('backspace', (e) => {
e.preventDefault()
})
useHotkeys(
isTauri() ? 'mod + ,' : 'shift + mod + ,',
() => navigate(filePath + paths.SETTINGS),
{
splitKey: '|',
}
)
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
(p) => p === onboardingStatus
@ -84,9 +96,11 @@ export function App() {
const debounceSocketSend = throttle<EngineCommand>((message) => {
engineCommandManager.sendSceneCommand(message)
}, 16)
}, 1000 / 15)
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
e.nativeEvent.preventDefault()
if (state.matches('Sketch')) {
return
}
const { x, y } = getNormalisedCoordinates({
clientX: e.clientX,
@ -97,58 +111,11 @@ export function App() {
const newCmdId = uuidv4()
if (buttonDownInStream === undefined) {
if (state.matches('Sketch.Line Tool')) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
cmd: {
type: 'mouse_move',
window: { x, y },
},
})
} else {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd: {
type: 'highlight_set_entity',
selected_at_window: { x, y },
},
cmd_id: newCmdId,
})
}
} else {
if (state.matches('Sketch.Move Tool')) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
cmd: {
type: 'handle_mouse_drag_move',
window: { x, y },
},
})
return
}
const interactionGuards = cameraMouseDragGuards[cameraControls]
let interaction: CameraDragInteractionType_type
const eWithButton = { ...e, button: buttonDownInStream }
if (interactionGuards.pan.callback(eWithButton)) {
interaction = 'pan'
} else if (interactionGuards.rotate.callback(eWithButton)) {
interaction = 'rotate'
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
interaction = 'zoom'
} else {
return
}
debounceSocketSend({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_move',
interaction,
window: { x, y },
type: 'highlight_set_entity',
selected_at_window: { x, y },
},
cmd_id: newCmdId,
})
@ -238,6 +205,7 @@ export function App() {
open={openPanes.includes('debug')}
/>
)}
{/* <CamToggle /> */}
</div>
)
}

View File

@ -14,10 +14,7 @@ import {
import { useEffect } from 'react'
import { ErrorPage } from './components/ErrorPage'
import { Settings } from './routes/Settings'
import Onboarding, {
onboardingRoutes,
onboardingPaths,
} from './routes/Onboarding'
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
import SignIn from './routes/SignIn'
import { Auth } from './Auth'
import { isTauri } from './lib/isTauri'
@ -29,7 +26,7 @@ import {
isProjectDirectory,
PROJECT_ENTRYPOINT,
} from './lib/tauriFS'
import { metadata, type Metadata } from 'tauri-plugin-fs-extra-api'
import { metadata } from 'tauri-plugin-fs-extra-api'
import DownloadAppBanner from './components/DownloadAppBanner'
import { WasmErrBanner } from './components/WasmErrBanner'
import { GlobalStateProvider } from './components/GlobalStateProvider'
@ -42,9 +39,12 @@ import CommandBarProvider from 'components/CommandBar/CommandBar'
import { TEST, VITE_KC_SENTRY_DSN } from './env'
import * as Sentry from '@sentry/react'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { KclContextProvider, kclManager } from 'lang/KclSinglton'
import { KclContextProvider, kclManager } from 'lang/KclSingleton'
import FileMachineProvider from 'components/FileMachineProvider'
import { sep } from '@tauri-apps/api/path'
import { paths } from 'lib/paths'
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
import { fileSystemManager } from 'lang/std/fileSystemManager'
if (VITE_KC_SENTRY_DSN && !TEST) {
Sentry.init({
@ -78,43 +78,8 @@ if (VITE_KC_SENTRY_DSN && !TEST) {
})
}
const prependRoutes =
(routesObject: Record<string, string>) => (prepend: string) => {
return Object.fromEntries(
Object.entries(routesObject).map(([constName, path]) => [
constName,
prepend + path,
])
)
}
export const paths = {
INDEX: '/',
HOME: '/home',
FILE: '/file',
SETTINGS: '/settings',
SIGN_IN: '/signin',
ONBOARDING: prependRoutes(onboardingPaths)(
'/onboarding'
) as typeof onboardingPaths,
}
export const BROWSER_FILE_NAME = 'new'
export type IndexLoaderData = {
code: string | null
project?: ProjectWithEntryPointMetadata
file?: FileEntry
}
export type ProjectWithEntryPointMetadata = FileEntry & {
entrypointMetadata: Metadata
}
export type HomeLoaderData = {
projects: ProjectWithEntryPointMetadata[]
newDefaultDirectory?: string
}
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]
const addGlobalContextToElements = (
@ -146,18 +111,18 @@ const router = createBrowserRouter(
{
path: paths.FILE + '/:id',
element: (
<Auth>
<FileMachineProvider>
<KclContextProvider>
<KclContextProvider>
<Auth>
<FileMachineProvider>
<ModelingMachineProvider>
<Outlet />
<App />
</ModelingMachineProvider>
<WasmErrBanner />
</KclContextProvider>
</FileMachineProvider>
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth>
</FileMachineProvider>
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth>
</KclContextProvider>
),
id: paths.FILE,
loader: async ({
@ -210,6 +175,10 @@ const router = createBrowserRouter(
const children = await readDir(projectPath, { recursive: true })
kclManager.setCodeAndExecute(code, false)
// Set the file system manager to the project path
// So that WASM gets an updated path for operations
fileSystemManager.dir = projectPath
return {
code,
project: {
@ -249,7 +218,7 @@ const router = createBrowserRouter(
<Home />
</Auth>
),
loader: async () => {
loader: async (): Promise<HomeLoaderData | Response> => {
if (!isTauri()) {
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
}

View File

@ -89,44 +89,48 @@ export const Toolbar = () => {
</li>
)}
{state.matches('Sketch') && !state.matches('idle') && (
<li className="contents">
<ActionButton
Element="button"
onClick={() =>
state.matches('Sketch.Line Tool')
? send('CancelSketch')
: send('Equip tool')
}
aria-pressed={state.matches('Sketch.Line Tool')}
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
icon={{
icon: 'line',
bgClassName,
}}
>
Line
</ActionButton>
</li>
)}
{state.matches('Sketch') && (
<li className="contents">
<ActionButton
Element="button"
onClick={() =>
state.matches('Sketch.Move Tool')
? send('CancelSketch')
: send('Equip move tool')
}
aria-pressed={state.matches('Sketch.Move Tool')}
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
icon={{
icon: 'move',
bgClassName,
}}
>
Move
</ActionButton>
</li>
<>
<li className="contents" key="line-button">
<ActionButton
Element="button"
onClick={() =>
state?.matches('Sketch.Line tool')
? send('CancelSketch')
: send('Equip Line tool')
}
aria-pressed={state?.matches('Sketch.Line tool')}
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
icon={{
icon: 'line',
bgClassName,
}}
>
Line
</ActionButton>
</li>
<li className="contents" key="tangential-arc-button">
<ActionButton
Element="button"
onClick={() =>
state.matches('Sketch.Tangential arc to')
? send('CancelSketch')
: send('Equip tangential arc to')
}
aria-pressed={state.matches('Sketch.Tangential arc to')}
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
icon={{
icon: 'line',
bgClassName,
}}
disabled={
!state.can('Equip tangential arc to') &&
!state.matches('Sketch.Tangential arc to')
}
>
Tangential Arc
</ActionButton>
</li>
</>
)}
{state.matches('Sketch.SketchIdle') &&
state.nextEvents
@ -151,7 +155,7 @@ export const Toolbar = () => {
return 0
})
.map((eventName) => (
<li className="contents">
<li className="contents" key={eventName}>
<ActionButton
Element="button"
className="text-sm"

View File

@ -0,0 +1,252 @@
import { useRef, useEffect, useState } from 'react'
import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useStore } from 'useStore'
import {
DEBUG_SHOW_BOTH_SCENES,
ReactCameraProperties,
sceneInfra,
} from './sceneInfra'
import { throttle } from 'lib/utils'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false)
const [isTween, setIsTween] = useState(false)
const { state } = useModelingContext()
useEffect(() => {
sceneInfra.setIsCamMovingCallback((isMoving, isTween) => {
setIsCamMoving(isMoving)
setIsTween(isTween)
})
}, [])
if (DEBUG_SHOW_BOTH_SCENES || !isCamMoving)
return { hideClient: false, hideServer: false }
let hideServer = state.matches('Sketch')
if (isTween) {
hideServer = false
}
return { hideClient: !hideServer, hideServer }
}
export const ClientSideScene = ({
cameraControls,
}: {
cameraControls: ReturnType<
typeof useGlobalStateContext
>['settings']['context']['cameraControls']
}) => {
const canvasRef = useRef<HTMLDivElement>(null)
const { state, send } = useModelingContext()
const { hideClient, hideServer } = useShouldHideScene()
const { setHighlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
// Listen for changes to the camera controls setting
// and update the client-side scene's controls accordingly.
useEffect(() => {
sceneInfra.setInteractionGuards(cameraMouseDragGuards[cameraControls])
}, [cameraControls])
useEffect(() => {
sceneInfra.updateOtherSelectionColors(
state?.context?.selectionRanges?.otherSelections || []
)
}, [state?.context?.selectionRanges?.otherSelections])
useEffect(() => {
if (!canvasRef.current) return
const canvas = canvasRef.current
canvas.appendChild(sceneInfra.renderer.domElement)
sceneInfra.animate()
sceneInfra.setHighlightCallback(setHighlightRange)
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)
sceneInfra.setSend(send)
return () => {
canvas?.removeEventListener('mousemove', sceneInfra.onMouseMove)
canvas?.removeEventListener('mousedown', sceneInfra.onMouseDown)
canvas?.removeEventListener('mouseup', sceneInfra.onMouseUp)
}
}, [])
return (
<div
ref={canvasRef}
className={`absolute inset-0 h-full w-full transition-all duration-300 ${
hideClient ? 'opacity-0' : 'opacity-100'
} ${hideServer ? 'bg-black' : ''} ${
!hideClient && !hideServer && state.matches('Sketch')
? 'bg-black/80'
: ''
}`}
></div>
)
}
const throttled = throttle((a: ReactCameraProperties) => {
if (a.type === 'perspective' && a.fov) {
sceneInfra.dollyZoom(a.fov)
}
}, 1000 / 15)
export const CamDebugSettings = () => {
const [camSettings, setCamSettings] = useState<ReactCameraProperties>({
type: 'perspective',
fov: 12,
position: [0, 0, 0],
quaternion: [0, 0, 0, 1],
})
const [fov, setFov] = useState(12)
useEffect(() => {
sceneInfra.setReactCameraPropertiesCallback(setCamSettings)
}, [sceneInfra])
useEffect(() => {
if (camSettings.type === 'perspective' && camSettings.fov) {
setFov(camSettings.fov)
}
}, [(camSettings as any)?.fov])
return (
<div>
<h3>cam settings</h3>
perspective cam
<input
type="checkbox"
checked={camSettings.type === 'perspective'}
onChange={(e) => {
if (camSettings.type === 'perspective') {
sceneInfra.useOrthographicCamera()
} else {
sceneInfra.usePerspectiveCamera()
}
}}
/>
{camSettings.type === 'perspective' && (
<input
type="range"
min="4"
max="90"
step={0.5}
value={fov}
onChange={(e) => {
setFov(parseFloat(e.target.value))
throttled({
...camSettings,
fov: parseFloat(e.target.value),
})
}}
className="w-full cursor-pointer pointer-events-auto"
/>
)}
{camSettings.type === 'perspective' && (
<div>
<span>fov</span>
<input
type="number"
value={camSettings.fov}
className="text-black w-16"
onChange={(e) => {
sceneInfra.setCam({
...camSettings,
fov: parseFloat(e.target.value),
})
}}
/>
</div>
)}
{camSettings.type === 'orthographic' && (
<>
<div>
<span>fov</span>
<input
type="number"
value={camSettings.zoom}
className="text-black w-16"
onChange={(e) => {
sceneInfra.setCam({
...camSettings,
zoom: parseFloat(e.target.value),
})
}}
/>
</div>
</>
)}
<div>
Position
<ul className="flex">
<li>
<span className="pl-2 pr-1">x:</span>
<input
type="number"
step={5}
data-testid="cam-x-position"
value={camSettings.position[0]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.setCam({
...camSettings,
position: [
parseFloat(e.target.value),
camSettings.position[1],
camSettings.position[2],
],
})
}}
/>
</li>
<li>
<span className="pl-2 pr-1">y:</span>
<input
type="number"
step={5}
data-testid="cam-y-position"
value={camSettings.position[1]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.setCam({
...camSettings,
position: [
camSettings.position[0],
parseFloat(e.target.value),
camSettings.position[2],
],
})
}}
/>
</li>
<li>
<span className="pl-2 pr-1">z:</span>
<input
type="number"
step={5}
data-testid="cam-z-position"
value={camSettings.position[2]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.setCam({
...camSettings,
position: [
camSettings.position[0],
camSettings.position[1],
parseFloat(e.target.value),
],
})
}}
/>
</li>
</ul>
</div>
</div>
)
}

View File

@ -0,0 +1,33 @@
import {
GridHelper,
LineBasicMaterial,
OrthographicCamera,
PerspectiveCamera,
Group,
Mesh,
} from 'three'
export function createGridHelper({
size,
divisions,
}: {
size: number
divisions: number
}) {
const gridHelperMaterial = new LineBasicMaterial({
color: 0xaaaaaa,
transparent: true,
opacity: 0.5,
depthTest: false,
})
const gridHelper = new GridHelper(size, divisions, 0x0000ff, 0xffffff)
gridHelper.material = gridHelperMaterial
gridHelper.rotation.x = Math.PI / 2
return gridHelper
}
export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
0.55 / cam.zoom
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
(group.position.distanceTo(cam.position) * cam.fov) / 4000

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
import { Quaternion } from 'three'
import { isQuaternionVertical } from './sceneInfra'
describe('isQuaternionVertical', () => {
it('should identify vertical quaternions', () => {
const verticalQuaternions = [
new Quaternion(1, 0, 0, 0).normalize(), // bottom
new Quaternion(-0.7, 0.7, 0, 0).normalize(), // bottom 2
new Quaternion(0, 1, 0, 0).normalize(), // bottom 3
new Quaternion(0, 0, 0, 1).normalize(), // look from top
]
verticalQuaternions.forEach((quaternion) => {
expect(isQuaternionVertical(quaternion)).toBe(true)
})
})
it('should identify non-vertical quaternions', () => {
const nonVerticalQuaternions = [
new Quaternion(0.7, 0, 0, 0.7).normalize(), // front
new Quaternion(0, 0.7, 0.7, 0).normalize(), // back
new Quaternion(-0.5, 0.5, 0.5, -0.5).normalize(), // left side
new Quaternion(0.5, 0.5, 0.5, 0.5).normalize(), // right side
]
nonVerticalQuaternions.forEach((quaternion) => {
expect(isQuaternionVertical(quaternion)).toBe(false)
})
})
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,358 @@
import { Coords2d } from 'lang/std/sketch'
import {
BufferGeometry,
CatmullRomCurve3,
ConeGeometry,
CurvePath,
EllipseCurve,
ExtrudeGeometry,
Group,
LineCurve3,
Mesh,
MeshBasicMaterial,
NormalBufferAttributes,
Shape,
SphereGeometry,
Vector2,
Vector3,
} from 'three'
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import {
STRAIGHT_SEGMENT,
STRAIGHT_SEGMENT_BODY,
STRAIGHT_SEGMENT_DASH,
TANGENTIAL_ARC_TO_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT_BODY,
TANGENTIAL_ARC_TO__SEGMENT_DASH,
} from './sceneEntities'
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { ARROWHEAD } from './sceneInfra'
export function straightSegment({
from,
to,
id,
pathToNode,
isDraftSegment,
scale = 1,
}: {
from: Coords2d
to: Coords2d
id: string
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
}): Group {
const group = new Group()
const shape = new Shape()
shape.moveTo(0, -0.08 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line
let geometry
if (isDraftSegment) {
geometry = dashedStraight(from, to, shape, scale)
} else {
const line = new LineCurve3(
new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0)
)
geometry = new ExtrudeGeometry(shape, {
steps: 2,
bevelEnabled: false,
extrudePath: line,
})
}
const body = new MeshBasicMaterial({ color: 0xffffff })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
? STRAIGHT_SEGMENT_DASH
: STRAIGHT_SEGMENT_BODY
mesh.name = STRAIGHT_SEGMENT_BODY
group.userData = {
type: STRAIGHT_SEGMENT,
id,
from,
to,
pathToNode,
isSelected: false,
}
const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3()
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
group.add(mesh, arrowGroup)
return group
}
function createArrowhead(scale = 1): Group {
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
const arrowheadMesh = new Mesh(new ConeGeometry(0.31, 1.5, 12), arrowMaterial)
arrowheadMesh.position.set(0, -0.6, 0)
const sphereMesh = new Mesh(new SphereGeometry(0.27, 12, 12), arrowMaterial)
const arrowGroup = new Group()
arrowGroup.userData.type = ARROWHEAD
arrowGroup.name = ARROWHEAD
arrowGroup.add(arrowheadMesh, sphereMesh)
arrowGroup.lookAt(new Vector3(0, 1, 0))
arrowGroup.scale.set(scale, scale, scale)
return arrowGroup
}
export function tangentialArcToSegment({
prevSegment,
from,
to,
id,
pathToNode,
isDraftSegment,
scale = 1,
}: {
prevSegment: SketchGroup['value'][number]
from: Coords2d
to: Coords2d
id: string
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
}): Group {
const group = new Group()
const previousPoint =
prevSegment?.type === 'TangentialArcTo'
? getTangentPointFromPreviousArc(
prevSegment.center,
prevSegment.ccw,
prevSegment.to
)
: prevSegment.from
const { center, radius, startAngle, endAngle, ccw } = getTangentialArcToInfo({
arcStartPoint: from,
arcEndPoint: to,
tanPreviousPoint: previousPoint,
obtuse: true,
})
const geometry = createArcGeometry({
center,
radius,
startAngle,
endAngle,
ccw,
isDashed: isDraftSegment,
scale,
})
const body = new MeshBasicMaterial({ color: 0xffffff })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
? TANGENTIAL_ARC_TO__SEGMENT_DASH
: TANGENTIAL_ARC_TO_SEGMENT_BODY
group.userData = {
type: TANGENTIAL_ARC_TO_SEGMENT,
id,
from,
to,
prevSegment,
pathToNode,
isSelected: false,
}
const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0)
const arrowheadAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
group.add(mesh, arrowGroup)
return group
}
export function createArcGeometry({
center,
radius,
startAngle,
endAngle,
ccw,
isDashed = false,
scale = 1,
}: {
center: Coords2d
radius: number
startAngle: number
endAngle: number
ccw: boolean
isDashed?: boolean
scale?: number
}): BufferGeometry {
const dashSize = 1.2 * scale
const gapSize = 1.2 * scale
const arcStart = new EllipseCurve(
center[0],
center[1],
radius,
radius,
startAngle,
endAngle,
!ccw,
0
)
const arcEnd = new EllipseCurve(
center[0],
center[1],
radius,
radius,
endAngle,
startAngle,
ccw,
0
)
const shape = new Shape()
shape.moveTo(0, -0.08 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line
if (!isDashed) {
const points = arcStart.getPoints(50)
const path = new CurvePath<Vector3>()
path.add(new CatmullRomCurve3(points.map((p) => new Vector3(p.x, p.y, 0))))
return new ExtrudeGeometry(shape, {
steps: 100,
bevelEnabled: false,
extrudePath: path,
})
}
const length = arcStart.getLength()
const totalDashes = length / (dashSize + gapSize) // rounding makes the dashes jittery since the new dash is suddenly appears instead of growing into place
const dashesAtEachEnd = Math.min(100, totalDashes / 2) // Assuming we want 50 dashes total, 25 at each end
const dashGeometries = []
// Function to create a dash at a specific t value (0 to 1 along the curve)
const createDashAt = (t: number, curve: EllipseCurve) => {
const startVec = curve.getPoint(t)
const endVec = curve.getPoint(Math.min(0.5, t + dashSize / length))
const midVec = curve.getPoint(Math.min(0.5, t + dashSize / length / 2))
const dashCurve = new CurvePath<Vector3>()
dashCurve.add(
new CatmullRomCurve3([
new Vector3(startVec.x, startVec.y, 0),
new Vector3(midVec.x, midVec.y, 0),
new Vector3(endVec.x, endVec.y, 0),
])
)
return new ExtrudeGeometry(shape, {
steps: 3,
bevelEnabled: false,
extrudePath: dashCurve,
})
}
// Create dashes at the start of the arc
for (let i = 0; i < dashesAtEachEnd; i++) {
const t = i / totalDashes
dashGeometries.push(createDashAt(t, arcStart))
dashGeometries.push(createDashAt(t, arcEnd))
}
// fill in the remaining arc
const remainingArcLength = length - dashesAtEachEnd * 2 * (dashSize + gapSize)
if (remainingArcLength > 0) {
const remainingArcStartT = dashesAtEachEnd / totalDashes
const remainingArcEndT = 1 - remainingArcStartT
const centerVec = new Vector2(center[0], center[1])
const remainingArcStartVec = arcStart.getPoint(remainingArcStartT)
const remainingArcEndVec = arcStart.getPoint(remainingArcEndT)
const remainingArcCurve = new EllipseCurve(
arcStart.aX,
arcStart.aY,
arcStart.xRadius,
arcStart.yRadius,
new Vector2().subVectors(centerVec, remainingArcStartVec).angle() +
Math.PI,
new Vector2().subVectors(centerVec, remainingArcEndVec).angle() + Math.PI,
!ccw
)
const remainingArcPoints = remainingArcCurve.getPoints(50)
const remainingArcPath = new CurvePath<Vector3>()
remainingArcPath.add(
new CatmullRomCurve3(
remainingArcPoints.map((p) => new Vector3(p.x, p.y, 0))
)
)
const remainingArcGeometry = new ExtrudeGeometry(shape, {
steps: 50,
bevelEnabled: false,
extrudePath: remainingArcPath,
})
dashGeometries.push(remainingArcGeometry)
}
const geo = dashGeometries.length
? mergeGeometries(dashGeometries)
: new BufferGeometry()
geo.userData.type = 'dashed'
return geo
}
export function dashedStraight(
from: Coords2d,
to: Coords2d,
shape: Shape,
scale = 1
): BufferGeometry<NormalBufferAttributes> {
const dashSize = 1.2 * scale
const gapSize = 1.2 * scale // todo: gabSize is not respected
const dashLine = new LineCurve3(
new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0)
)
const length = dashLine.getLength()
const numberOfPoints = (length / (dashSize + gapSize)) * 2
const startOfLine = new Vector3(from[0], from[1], 0)
const endOfLine = new Vector3(to[0], to[1], 0)
const dashGeometries = []
const dashComponent = (xOrY: number, pointIndex: number) =>
((to[xOrY] - from[xOrY]) / numberOfPoints) * pointIndex + from[xOrY]
for (let i = 0; i < numberOfPoints; i += 2) {
const dashStart = new Vector3(dashComponent(0, i), dashComponent(1, i), 0)
let dashEnd = new Vector3(
dashComponent(0, i + 1),
dashComponent(1, i + 1),
0
)
if (startOfLine.distanceTo(dashEnd) > startOfLine.distanceTo(endOfLine))
dashEnd = endOfLine
if (dashEnd) {
const dashCurve = new LineCurve3(dashStart, dashEnd)
const dashGeometry = new ExtrudeGeometry(shape, {
steps: 1,
bevelEnabled: false,
extrudePath: dashCurve,
})
dashGeometries.push(dashGeometry)
}
}
const geo = dashGeometries.length
? mergeGeometries(dashGeometries)
: new BufferGeometry()
geo.userData.type = 'dashed'
return geo
}

View File

@ -1,6 +1,6 @@
import { ActionIcon, ActionIconProps } from './ActionIcon'
import React from 'react'
import { paths } from '../Router'
import { paths } from 'lib/paths'
import { Link } from 'react-router-dom'
import type { LinkProps } from 'react-router-dom'

View File

@ -1,6 +1,6 @@
import { Toolbar } from '../Toolbar'
import UserSidebarMenu from './UserSidebarMenu'
import { IndexLoaderData } from '../Router'
import { type IndexLoaderData } from 'lib/types'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import styles from './AppHeader.module.css'

View File

@ -1,5 +1,5 @@
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { useEffect, useRef, useState } from 'react'
import { useStore } from 'useStore'

View File

@ -8,7 +8,7 @@ import {
} from '../lang/modifyAst'
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSinglton'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import { useModelingContext } from 'hooks/useModelingContext'
import { executeAst } from 'useStore'
@ -148,7 +148,6 @@ export function useCalc({
executeAst({
ast,
engineCommandManager,
defaultPlanes: kclManager.defaultPlanes,
useFakeExecutor: true,
programMemoryOverride: JSON.parse(
JSON.stringify(kclManager.programMemory)

View File

@ -0,0 +1,75 @@
import { useState, useEffect } from 'react'
import { sceneInfra } from '../clientSideScene/sceneInfra'
import { engineCommandManager } from 'lang/std/engineConnection'
import { throttle, isReducedMotion } from 'lib/utils'
const updateDollyZoom = throttle(
(newFov: number) => sceneInfra.dollyZoom(newFov),
1000 / 15
)
export const CamToggle = () => {
const [isPerspective, setIsPerspective] = useState(true)
const [fov, setFov] = useState(40)
const [enableRotate, setEnableRotate] = useState(true)
useEffect(() => {
engineCommandManager.waitForReady.then(async () => {
sceneInfra.dollyZoom(fov)
})
}, [])
const toggleCamera = () => {
if (isPerspective) {
isReducedMotion()
? sceneInfra.useOrthographicCamera()
: sceneInfra.animateToOrthographic()
} else {
isReducedMotion()
? sceneInfra.usePerspectiveCamera()
: sceneInfra.animateToPerspective()
}
setIsPerspective(!isPerspective)
}
const handleFovChange = (newFov: number) => {
setFov(newFov)
updateDollyZoom(newFov)
}
return (
<div className="absolute right-14 bottom-3">
{isPerspective && (
<div className="">
<input
type="range"
min="4"
max="90"
step={0.5}
value={fov}
onChange={(e) => handleFovChange(Number(e.target.value))}
className="w-full cursor-pointer pointer-events-auto"
/>
</div>
)}
<button onClick={toggleCamera} className="">
{isPerspective
? 'Switch to Orthographic Camera'
: 'Switch to Perspective Camera'}
</button>
<button
onClick={() => {
if (enableRotate) {
sceneInfra.controls.enableRotate = false
} else {
sceneInfra.controls.enableRotate = true
}
setEnableRotate(!enableRotate)
}}
className=""
>
{enableRotate ? 'Disable Rotation' : 'Enable Rotation'}
</button>
</div>
)
}

View File

@ -9,7 +9,7 @@ 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'
import { kclManager } from 'lang/KclSingleton'
export const CodeMenu = ({ children }: PropsWithChildren) => {
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
@ -77,6 +77,24 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
</small>
</a>
</Menu.Item>
<Menu.Item>
<a
className={styles.button}
href="https://github.com/KittyCAD/kcl-samples"
target="_blank"
rel="noopener noreferrer"
>
<span>KCL samples</span>
<small>
On GitHub
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="ml-1 align-text-top"
width={12}
/>
</small>
</a>
</Menu.Item>
</Menu.Items>
</div>
</Menu>

View File

@ -1,6 +1,6 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from '../CustomIcon'
import React, { useState } from 'react'
import React, { ReactNode, useState } from 'react'
import { ActionButton } from '../ActionButton'
import { Selections, getSelectionTypeDisplayText } from 'lib/selections'
import { useHotkeys } from 'react-hotkeys-hook'
@ -99,15 +99,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
) : typeof argumentsToSubmit[argName] === 'object' ? (
JSON.stringify(argumentsToSubmit[argName])
) : (
argumentsToSubmit[argName]
)
) : arg.payload ? (
arg.inputType === 'selection' ? (
getSelectionTypeDisplayText(arg.payload as Selections)
) : typeof arg.payload === 'object' ? (
JSON.stringify(arg.payload)
) : (
arg.payload
<em>{argumentsToSubmit[argName] as ReactNode}</em>
)
) : (
<em>{argName}</em>

View File

@ -1,6 +1,6 @@
import { useSelector } from '@xstate/react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useKclContext } from 'lang/KclSinglton'
import { useKclContext } from 'lang/KclSingleton'
import { CommandArgument } from 'lib/commandTypes'
import {
ResolvedSelectionType,
@ -27,7 +27,7 @@ function CommandBarSelectionInput({
}) {
const { code } = useKclContext()
const inputRef = useRef<HTMLInputElement>(null)
const { commandBarSend } = useCommandsContext()
const { commandBarState, commandBarSend } = useCommandsContext()
const [hasSubmitted, setHasSubmitted] = useState(false)
const selection = useSelector(arg.actor, selectionSelector)
const [selectionsByType, setSelectionsByType] = useState<
@ -59,8 +59,16 @@ function CommandBarSelectionInput({
)
}, [selection])
// Fast-forward through this arg if it's marked as skippable
// and we have a valid selection already
useEffect(() => {
setCanSubmitSelection(canSubmitSelectionArg(selectionsByType, arg))
const argValue = commandBarState.context.argumentsToSubmit[arg.name]
if (canSubmitSelection && arg.skip && argValue === undefined) {
handleSubmit({
preventDefault: () => {},
} as React.FormEvent<HTMLFormElement>)
}
}, [selectionsByType, arg])
function handleChange() {

View File

@ -48,6 +48,7 @@ function CommandComboBox({
(event.metaKey && event.key === 'k') ||
(event.key === 'Backspace' && !event.currentTarget.value)
) {
event.preventDefault()
commandBarSend({ type: 'Close' })
}
}}

View File

@ -4,6 +4,8 @@ export type CustomIconName =
| 'arrowRight'
| 'arrowUp'
| 'checkmark'
| 'clipboardPlus'
| 'clipboardCheckmark'
| 'close'
| 'equal'
| 'extrude'
@ -13,8 +15,11 @@ export type CustomIconName =
| 'folderPlus'
| 'gear'
| 'horizontal'
| 'horizontalDash'
| 'line'
| 'move'
| 'network'
| 'networkCrossedOut'
| 'parallel'
| 'search'
| 'sketch'
@ -107,6 +112,38 @@ export const CustomIcon = ({
/>
</svg>
)
case 'clipboardCheckmark':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.5 3H7L13 3L13.5 3V3.5V4.00001L15.5 4.00002L16 4.00002V4.50002V10.0351C15.6905 9.85609 15.3548 9.71733 15 9.62602V5.00002L13.5 5.00001V6.50001V7.00001L13 7.00001L7 7.00001L6.5 7.00001V6.50001V5.00001L5 5.00001V16H10.8773C11.2024 16.4055 11.6047 16.7463 12.062 17H4.5H4V16.5V4.50001V4.00001L4.5 4.00001L6.5 4.00001V3.5V3ZM15.938 17C15.9588 16.9885 15.9794 16.9768 16 16.9649V17H15.938ZM7.5 4V4.50001V6.00001L12.5 6.00001V4.50001V4L7.5 4ZM13 9H7V8H13V9ZM15.6855 11.5L13.2101 14.8005L12.2071 13.7975L11.5 14.5046L12.9107 15.9153L13.6642 15.8617L16.4855 12.1L15.6855 11.5Z"
fill="currentColor"
/>
</svg>
)
case 'clipboardPlus':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.5 3H7L13 3L13.5 3V3.5V4.00001L15.5 4.00002L16 4.00002V4.50002V10.0351C15.6905 9.85609 15.3548 9.71733 15 9.62602V5.00002L13.5 5.00001V6.50001V7.00001L13 7.00001L7 7.00001L6.5 7.00001V6.50001V5.00001L5 5.00001V16H10.8773C11.2024 16.4055 11.6047 16.7463 12.062 17H4.5H4V16.5V4.50001V4.00001L4.5 4.00001L6.5 4.00001V3.5V3ZM15.938 17C15.9588 16.9885 15.9794 16.9768 16 16.9649V17H15.938ZM7.5 4V4.50001V6.00001L12.5 6.00001V4.50001V4L7.5 4ZM13 9H7V8H13V9ZM13.5 11V13H11.5V14H13.5V16H14.5V14H16.5V13H14.5V11H13.5Z"
fill="currentColor"
/>
</svg>
)
case 'close':
return (
<svg
@ -249,6 +286,22 @@ export const CustomIcon = ({
/>
</svg>
)
case 'horizontalDash':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 10.5H6V9.5H14V10.5Z"
fill="currentColor"
/>
</svg>
)
case 'line':
return (
<svg
@ -281,6 +334,38 @@ export const CustomIcon = ({
/>
</svg>
)
case 'network':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18 9.64741C17.1925 8.24871 16.0344 7.08457 14.6399 6.26971C13.2455 5.45486 11.6628 5.01742 10.0478 5.00051C8.4328 4.9836 6.84127 5.38779 5.43006 6.17326C4.01884 6.95873 2.83666 8.09837 2 9.47985L2.76881 9.94546C3.52456 8.69756 4.59243 7.66813 5.86718 6.95862C7.14193 6.2491 8.57955 5.88399 10.0384 5.89927C11.4972 5.91455 12.9269 6.30968 14.1865 7.04574C15.4461 7.7818 16.4922 8.83337 17.2216 10.0968L18 9.64741ZM15.2155 11.0953C14.6772 10.1628 13.9051 9.3867 12.9755 8.84347C12.0459 8.30023 10.9907 8.00861 9.91406 7.99733C8.8374 7.98606 7.77638 8.25552 6.83557 8.77917C5.89476 9.30281 5.10664 10.0626 4.54887 10.9836L5.34391 11.4651C5.81802 10.6822 6.48792 10.0364 7.28761 9.59132C8.0873 9.14622 8.98916 8.91718 9.90432 8.92676C10.8195 8.93635 11.7164 9.18423 12.5065 9.64598C13.2967 10.1077 13.953 10.7674 14.4106 11.56L15.2155 11.0953ZM10 14C10.8284 14 11.5 13.3284 11.5 12.5C11.5 11.6716 10.8284 11 10 11C9.17157 11 8.5 11.6716 8.5 12.5C8.5 13.3284 9.17157 14 10 14Z"
fill="currentColor"
/>
</svg>
)
case 'networkCrossedOut':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.35352 5.39647L14.253 15.296L14.9601 14.5889L5.06062 4.68936L4.35352 5.39647ZM12.5065 9.64599C11.9609 9.32713 11.3643 9.11025 10.746 9.00341L9.74058 7.99796C9.79835 7.99694 9.85618 7.99674 9.91406 7.99735C10.9907 8.00862 12.0459 8.30025 12.9755 8.84348C13.9051 9.38672 14.6772 10.1628 15.2155 11.0953L14.4106 11.56C13.953 10.7674 13.2967 10.1077 12.5065 9.64599ZM6.48788 8.98789L7.16295 9.66297C6.41824 10.1045 5.79317 10.7233 5.34391 11.4651L4.54887 10.9836C5.03646 10.1785 5.70009 9.49656 6.48788 8.98789ZM10.0384 5.89928C9.3134 5.89169 8.59366 5.97804 7.89655 6.15392L7.16867 5.42605C8.09637 5.13507 9.06776 4.99026 10.0478 5.00052C11.6628 5.01744 13.2455 5.45488 14.6399 6.26973C16.0344 7.08458 17.1925 8.24872 18 9.64742L17.2216 10.0968C16.4922 8.83338 15.4461 7.78181 14.1865 7.04575C12.9269 6.3097 11.4972 5.91456 10.0384 5.89928ZM5.00782 7.50783L4.36522 6.86524C3.42033 7.57557 2.61639 8.46208 2 9.47986L2.76881 9.94547C3.34775 8.98952 4.10986 8.16177 5.00782 7.50783ZM10 14C10.4142 14 10.7892 13.8321 11.0607 13.5607L8.93934 11.4394C8.66789 11.7108 8.5 12.0858 8.5 12.5C8.5 13.3284 9.17157 14 10 14Z"
fill="currentColor"
/>
</svg>
)
case 'parallel':
return (
<svg

View File

@ -1,6 +1,7 @@
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { AstExplorer } from './AstExplorer'
import { EngineCommands } from './EngineCommands'
import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
return (
@ -15,6 +16,7 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
>
<section className="p-4 flex flex-col gap-4">
<EngineCommands />
<CamDebugSettings />
<div style={{ height: '400px' }} className="overflow-y-auto">
<AstExplorer />
</div>

View File

@ -0,0 +1,43 @@
import { MoveDesc } from 'machines/modelingMachine'
export const DragWarningToast = (moveDescs: MoveDesc[]) => {
if (moveDescs.length === 1) {
return (
<div className="flex items-center">
<div>🔒</div>
<div className="dark:bg-slate-950/50 bg-slate-400/50 p-1 px-3 rounded-xl text-sm">
move disabled: line{' '}
<span className="dark:text-energy-20 text-lime-600">
{moveDescs[0].line}
</span>
:{' '}
<pre>
<code className="dark:text-energy-20 text-lime-600">
{moveDescs[0].snippet}
</code>
</pre>{' '}
is fully constrained
</div>
</div>
)
} else if (moveDescs.length > 1) {
return (
<div className="dark:bg-slate-950/50 bg-slate-400/50 p-1 px-3 rounded-xl text-sm">
<div>Move disabled as The following lines are constrained</div>
{moveDescs.map((desc, i) => {
return (
<div key={i}>
line {desc.line}:{' '}
<pre className="inline-block">
<code className="dark:text-energy-20 text-lime-600">
{moveDescs[0].snippet}
</code>
</pre>{' '}
</div>
)
})}
</div>
)
}
return null
}

View File

@ -1,6 +1,7 @@
import { useMachine } from '@xstate/react'
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { IndexLoaderData, paths } from '../Router'
import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths'
import React, { createContext } from 'react'
import { toast } from 'react-hot-toast'
import {

View File

@ -1,4 +1,5 @@
import { IndexLoaderData, paths } from 'Router'
import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths'
import { ActionButton } from './ActionButton'
import Tooltip from './Tooltip'
import { FileEntry } from '@tauri-apps/api/fs'

View File

@ -1,6 +1,6 @@
import { useMachine } from '@xstate/react'
import { useNavigate } from 'react-router-dom'
import { paths } from '../Router'
import { paths } from 'lib/paths'
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
import withBaseUrl from '../lib/withBaseURL'
import React, { createContext, useEffect, useRef } from 'react'

View File

@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
import { useEffect } from 'react'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { Themes } from '../lib/theme'
import { useKclContext } from 'lang/KclSinglton'
import { useKclContext } from 'lang/KclSingleton'
const ReactJsonTypeHack = ReactJson as any

View File

@ -41,9 +41,9 @@ describe('processMemory', () => {
otherVar: 3,
theExtrude: [],
theSketch: [
{ type: 'toPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
{ type: 'toPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
{ type: 'toPoint', to: [2.15, 4.32], from: [0.98, 5.16], name: '' },
{ type: 'ToPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
{ type: 'ToPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
{ type: 'ToPoint', to: [2.15, 4.32], from: [0.98, 5.16], name: '' },
],
})
})

View File

@ -3,7 +3,7 @@ import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useMemo } from 'react'
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
import { Themes } from '../lib/theme'
import { useKclContext } from 'lang/KclSinglton'
import { useKclContext } from 'lang/KclSingleton'
interface MemoryPanelProps extends CollapsiblePanelProps {
theme?: Exclude<Themes, Themes.System>

View File

@ -13,23 +13,7 @@ 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,
PipeExpression,
CallExpression,
} from 'lang/wasm'
import { getNodeFromPath } from 'lang/queryAst'
import {
addCloseToPipe,
addNewSketchLn,
compareVec2Epsilon,
} from 'lang/std/sketch'
import { kclManager, useKclContext } from 'lang/KclSinglton'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
import {
angleBetweenInfo,
@ -49,6 +33,10 @@ import { applyConstraintIntersect } from './Toolbar/Intersect'
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
import useStateMachineCommands from 'hooks/useStateMachineCommands'
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
import { sceneInfra } from 'clientSideScene/sceneInfra'
import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
import { startSketchOnDefault } from 'lang/modifyAst'
import { Program } from 'lang/wasm'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -92,181 +80,10 @@ export const ModelingMachineProvider = ({
const [modelingState, modelingSend, modelingActor] = useMachine(
modelingMachine,
{
// context: persistedSettings,
actions: {
'Modify AST': () => {},
'Update code selection cursors': () => {},
'show default planes': () => {
kclManager.showPlanes()
},
'create path': assign({
sketchEnginePathId: () => {
const sketchUuid = uuidv4()
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,
},
})
return sketchUuid
},
}),
'AST start new sketch': assign(
({ sketchEnginePathId }, { data: { coords, axis, segmentId } }) => {
if (!axis) {
// Something really weird must have happened for this to happen.
console.error('axis is undefined for starting a new sketch')
return {}
}
if (!segmentId) {
// Something really weird must have happened for this to happen.
console.error('segmentId 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)
const updatedPipeNode = getNodeFromPath<PipeExpression>(
astWithUpdatedSource,
_pathToNode
).node
const startProfileAtCallExp = updatedPipeNode.body.find(
(exp) =>
exp.type === 'CallExpression' &&
exp.callee.name === 'startProfileAt'
)
if (startProfileAtCallExp)
engineCommandManager.artifactMap[sketchEnginePathId] = {
type: 'result',
range: [startProfileAtCallExp.start, startProfileAtCallExp.end],
commandType: 'start_path',
data: null,
raw: {} as any,
}
const lineCallExp = updatedPipeNode.body.find(
(exp) =>
exp.type === 'CallExpression' && exp.callee.name === 'line'
)
if (lineCallExp)
engineCommandManager.artifactMap[segmentId] = {
type: 'result',
range: [lineCallExp.start, lineCallExp.end],
commandType: 'extend_path',
parentId: sketchEnginePathId,
data: null,
raw: {} as any,
}
kclManager.executeAstMock(astWithUpdatedSource, true)
return {
sketchPathToNode: _pathToNode,
}
}
),
'AST add line segment': async (
{ sketchPathToNode, sketchEnginePathId },
{ data: { coords, segmentId } }
) => {
if (!sketchPathToNode) return
const lastCoord = coords[coords.length - 1]
const pathInfo = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'path_get_info',
path_id: sketchEnginePathId,
},
})
const firstSegment = pathInfo?.data?.data?.segments.find(
(seg: any) => seg.command === 'line_to'
)
const firstSegCoords = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: firstSegment.command_id,
},
})
const startPathCoord = firstSegCoords?.data?.data?.control_points[0]
const isClose = compareVec2Epsilon(
[startPathCoord.x, startPathCoord.y],
[lastCoord.x, lastCoord.y]
)
let _modifiedAst: Program
if (!isClose) {
const newSketchLn = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [lastCoord.x, lastCoord.y],
from: [coords[0].x, coords[0].y],
fnName: 'line',
pathToNode: sketchPathToNode,
})
const _modifiedAst = newSketchLn.modifiedAst
kclManager.executeAstMock(_modifiedAst, true).then(() => {
const lineCallExp = getNodeFromPath<CallExpression>(
kclManager.ast,
newSketchLn.pathToNode
).node
if (segmentId)
engineCommandManager.artifactMap[segmentId] = {
type: 'result',
range: [lineCallExp.start, lineCallExp.end],
commandType: 'extend_path',
parentId: sketchEnginePathId,
data: null,
raw: {} as any,
}
})
} 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
'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
@ -274,36 +91,6 @@ export const ModelingMachineProvider = ({
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
return { selectionRanges: setSelections.selection }
else if (setSelections.selectionType === 'otherSelection') {
// TODO KittyCAD/engine/issues/1620: send axis highlight when it's working (if that's what we settle on)
// const axisAddCmd: EngineCommand = {
// type: 'modeling_cmd_req',
// cmd: {
// type: 'highlight_set_entities',
// entities: [
// setSelections.selection === 'x-axis'
// ? X_AXIS_UUID
// : Y_AXIS_UUID,
// ],
// },
// cmd_id: uuidv4(),
// }
// if (!isShiftDown) {
// engineCommandManager
// .sendSceneCommand({
// type: 'modeling_cmd_req',
// cmd: {
// type: 'select_clear',
// },
// cmd_id: uuidv4(),
// })
// .then(() => {
// engineCommandManager.sendSceneCommand(axisAddCmd)
// })
// } else {
// engineCommandManager.sendSceneCommand(axisAddCmd)
// }
const {
codeMirrorSelection,
selectionRangeTypeMap,
@ -384,12 +171,6 @@ export const ModelingMachineProvider = ({
}),
},
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,
'has valid extrude selection': ({ selectionRanges }) => {
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
@ -409,6 +190,40 @@ export const ModelingMachineProvider = ({
},
},
services: {
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
if (!sketchPathToNode) return
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
const varDecIndex = sketchPathToNode[1][0]
// remove body item at varDecIndex
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
await kclManager.executeAstMock(newAst, { updates: 'code' })
sceneInfra.setCallbacks({
onClick: () => {},
})
},
'animate-to-face': async (_, { data: { plane, normal } }) => {
const { modifiedAst, pathToNode } = startSketchOnDefault(
kclManager.ast,
plane
)
await kclManager.updateAst(modifiedAst, false)
const quaternion = getSketchQuaternion(pathToNode, normal)
await sceneInfra.tweenCameraToQuaternion(quaternion)
return {
sketchPathToNode: pathToNode,
sketchNormalBackUp: normal,
}
},
'animate-to-sketch': async ({
sketchPathToNode,
sketchNormalBackUp,
}) => {
const quaternion = getSketchQuaternion(
sketchPathToNode || [],
sketchNormalBackUp
)
await sceneInfra.tweenCameraToQuaternion(quaternion)
},
'Get horizontal info': async ({
selectionRanges,
}): Promise<SetSelections> => {
@ -542,17 +357,6 @@ export const ModelingMachineProvider = ({
}
)
useEffect(() => {
engineCommandManager.onPlaneSelected((plane_id: string) => {
if (modelingState.nextEvents.includes('Select default plane')) {
modelingSend({
type: 'Select default plane',
data: { planeId: plane_id },
})
}
})
}, [modelingSend, modelingState.nextEvents])
useEffect(() => {
kclManager.registerExecuteCallback(() => {
modelingSend({ type: 'Re-execute' })
@ -565,10 +369,7 @@ export const ModelingMachineProvider = ({
send: modelingSend,
actor: modelingActor,
commandBarConfig: modelingMachineConfig,
onCancel: () => {
console.log('firing onCancel!!')
modelingSend({ type: 'Cancel' })
},
onCancel: () => modelingSend({ type: 'Cancel' }),
})
return (

View File

@ -3,8 +3,9 @@ import { BrowserRouter } from 'react-router-dom'
import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar/CommandBar'
import {
NETWORK_CONTENT,
NETWORK_HEALTH_TEXT,
NetworkHealthIndicator,
NetworkHealthState,
} from './NetworkHealthIndicator'
function TestWrap({ children }: { children: React.ReactNode }) {
@ -28,8 +29,8 @@ describe('NetworkHealthIndicator tests', () => {
fireEvent.click(screen.getByTestId('network-toggle'))
expect(screen.getByTestId('network-good')).toHaveTextContent(
NETWORK_CONTENT.good
expect(screen.getByTestId('network')).toHaveTextContent(
NETWORK_HEALTH_TEXT[NetworkHealthState.Ok]
)
})
@ -43,8 +44,8 @@ describe('NetworkHealthIndicator tests', () => {
fireEvent.offline(window)
fireEvent.click(screen.getByTestId('network-toggle'))
expect(screen.getByTestId('network-bad')).toHaveTextContent(
NETWORK_CONTENT.bad
expect(screen.getByTestId('network')).toHaveTextContent(
NETWORK_HEALTH_TEXT[NetworkHealthState.Disconnected]
)
})
})

View File

@ -1,41 +1,186 @@
import { faExclamation, faWifi } from '@fortawesome/free-solid-svg-icons'
import { Popover } from '@headlessui/react'
import { useEffect, useState } from 'react'
import { ActionIcon } from './ActionIcon'
import { ActionIcon, ActionIconProps } from './ActionIcon'
import {
ConnectingType,
ConnectingTypeGroup,
DisconnectingType,
engineCommandManager,
EngineConnectionState,
EngineConnectionStateType,
ErrorType,
initialConnectingTypeGroupState,
} from '../lang/std/engineConnection'
import Tooltip from './Tooltip'
export const NETWORK_CONTENT = {
good: 'Network health is good',
bad: 'Network issue',
export enum NetworkHealthState {
Ok,
Issue,
Disconnected,
}
const NETWORK_MESSAGES = {
offline: 'You are offline',
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
[NetworkHealthState.Ok]: 'Connected',
[NetworkHealthState.Issue]: 'Problem',
[NetworkHealthState.Disconnected]: 'Offline',
}
type IconColorConfig = {
icon: string
bg: string
}
const hasIssueToIcon: Record<
string | number | symbol,
ActionIconProps['icon']
> = {
true: 'close',
undefined: 'horizontalDash',
false: 'checkmark',
}
const hasIssueToIconColors: Record<string | number | symbol, IconColorConfig> =
{
true: {
icon: 'text-destroy-80 dark:text-destroy-10',
bg: 'bg-destroy-10 dark:bg-destroy-80',
},
undefined: {
icon: 'text-chalkboard-70 dark:text-chalkboard-30',
bg: 'bg-chalkboard-30 dark:bg-chalkboard-70',
},
false: {
icon: 'text-chalkboard-110 dark:!text-chalkboard-10',
bg: 'bg-transparent dark:bg-transparent',
},
}
const overallConnectionStateColor: Record<NetworkHealthState, IconColorConfig> =
{
[NetworkHealthState.Ok]: {
icon: 'text-energy-80 dark:text-energy-10',
bg: 'bg-energy-10/30 dark:bg-energy-80/50',
},
[NetworkHealthState.Issue]: {
icon: 'text-destroy-80 dark:text-destroy-10',
bg: 'bg-destroy-10 dark:bg-destroy-80/80',
},
[NetworkHealthState.Disconnected]: {
icon: 'text-destroy-80 dark:text-destroy-10',
bg: 'bg-destroy-10 dark:bg-destroy-80',
},
}
const overallConnectionStateIcon: Record<
NetworkHealthState,
ActionIconProps['icon']
> = {
[NetworkHealthState.Ok]: 'network',
[NetworkHealthState.Issue]: 'networkCrossedOut',
[NetworkHealthState.Disconnected]: 'networkCrossedOut',
}
export const NetworkHealthIndicator = () => {
const [networkIssues, setNetworkIssues] = useState<string[]>([])
const hasIssues = [...networkIssues.values()].length > 0
const [steps, setSteps] = useState(initialConnectingTypeGroupState)
const [internetConnected, setInternetConnected] = useState<boolean>(true)
const [overallState, setOverallState] = useState<NetworkHealthState>(
NetworkHealthState.Ok
)
const [hasCopied, setHasCopied] = useState<boolean>(false)
const [error, setError] = useState<ErrorType | undefined>(undefined)
const issues: Record<ConnectingTypeGroup, boolean> = {
[ConnectingTypeGroup.WebSocket]: steps[ConnectingTypeGroup.WebSocket].some(
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
[ConnectingTypeGroup.ICE]: steps[ConnectingTypeGroup.ICE].some(
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
[ConnectingTypeGroup.WebRTC]: steps[ConnectingTypeGroup.WebRTC].some(
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
}
const hasIssues: boolean =
issues[ConnectingTypeGroup.WebSocket] ||
issues[ConnectingTypeGroup.ICE] ||
issues[ConnectingTypeGroup.WebRTC]
useEffect(() => {
const offlineListener = () =>
setNetworkIssues((issues) => {
return [
...issues.filter((issue) => issue !== NETWORK_MESSAGES.offline),
NETWORK_MESSAGES.offline,
]
})
window.addEventListener('offline', offlineListener)
setOverallState(
!internetConnected
? NetworkHealthState.Disconnected
: hasIssues
? NetworkHealthState.Issue
: NetworkHealthState.Ok
)
}, [hasIssues, internetConnected])
const onlineListener = () =>
setNetworkIssues((issues) => {
return [...issues.filter((issue) => issue !== NETWORK_MESSAGES.offline)]
})
window.addEventListener('online', onlineListener)
return () => {
window.removeEventListener('offline', offlineListener)
window.removeEventListener('online', onlineListener)
useEffect(() => {
const cb1 = () => {
setSteps(initialConnectingTypeGroupState)
setInternetConnected(true)
}
const cb2 = () => {
setInternetConnected(false)
}
window.addEventListener('online', cb1)
window.addEventListener('offline', cb2)
return () => {
window.removeEventListener('online', cb1)
window.removeEventListener('offline', cb2)
}
}, [])
useEffect(() => {
engineCommandManager.onConnectionStateChange(
(engineConnectionState: EngineConnectionState) => {
let hasSetAStep = false
if (
engineConnectionState.type === EngineConnectionStateType.Connecting
) {
const groups = Object.values(steps)
for (let group of groups) {
for (let step of group) {
if (step[0] !== engineConnectionState.value.type) continue
step[1] = true
hasSetAStep = true
}
}
}
if (
engineConnectionState.type === EngineConnectionStateType.Disconnecting
) {
const groups = Object.values(steps)
for (let group of groups) {
for (let step of group) {
if (
engineConnectionState.value.type === DisconnectingType.Error
) {
if (
engineConnectionState.value.value.lastConnectingValue
?.type === step[0]
) {
step[1] = false
hasSetAStep = true
}
}
}
if (engineConnectionState.value.type === DisconnectingType.Error) {
setError(engineConnectionState.value.value)
}
}
}
if (hasSetAStep) {
setSteps(steps)
}
}
)
}, [])
return (
@ -45,65 +190,94 @@ export const NetworkHealthIndicator = () => {
'p-0 border-none bg-transparent dark:bg-transparent relative ' +
(hasIssues
? 'focus-visible:outline-destroy-80'
: 'focus-visible:outline-succeed-80')
: 'focus-visible:outline-energy-80')
}
data-testid="network-toggle"
>
<span className="sr-only">Network Health</span>
<ActionIcon
icon={faWifi}
icon={overallConnectionStateIcon[overallState]}
className="p-1"
iconClassName={
hasIssues
? 'text-destroy-80 dark:text-destroy-30'
: 'text-succeed-80 dark:text-succeed-30'
}
iconClassName={overallConnectionStateColor[overallState].icon}
bgClassName={
'bg-transparent dark:bg-transparent ' +
(hasIssues
? 'hover:bg-destroy-10/50 hover:dark:bg-destroy-80/50 rounded'
: 'hover:bg-succeed-10/50 hover:dark:bg-succeed-80/50 rounded')
'rounded-sm ' + overallConnectionStateColor[overallState].bg
}
/>
<Tooltip position="blockEnd" delay={750} className="ui-open:hidden">
Network Health ({NETWORK_HEALTH_TEXT[overallState]})
</Tooltip>
</Popover.Button>
<Popover.Panel className="absolute right-0 left-auto top-full mt-1 w-56 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch py-2 bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm">
{!hasIssues ? (
<span
className="flex items-center justify-center gap-1 px-4"
data-testid="network-good"
<Popover.Panel className="absolute right-0 left-auto top-full mt-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm">
<div
className={`flex items-center justify-between p-2 rounded-t-sm ${overallConnectionStateColor[overallState].bg} ${overallConnectionStateColor[overallState].icon}`}
>
<h2 className="text-sm font-sans font-normal">Network health</h2>
<p
data-testid="network"
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
>
<ActionIcon
icon="checkmark"
bgClassName={'bg-succeed-10/50 dark:bg-succeed-80/50 rounded-sm'}
iconClassName={'text-succeed-80 dark:text-succeed-30'}
/>
{NETWORK_CONTENT.good}
</span>
) : (
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
<span
className="font-bold text-xs uppercase text-destroy-60 dark:text-destroy-50 px-4"
data-testid="network-bad"
{NETWORK_HEALTH_TEXT[overallState]}
</p>
</div>
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
{Object.keys(steps).map((name) => (
<li
key={name}
className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}
>
{NETWORK_CONTENT.bad}
{networkIssues.length > 1 ? 's' : ''}
</span>
{networkIssues.map((issue) => (
<li
key={issue}
className="flex items-center gap-1 py-2 my-2 last:mb-0"
>
<ActionIcon
icon={faExclamation}
bgClassName={'bg-destroy-10/50 dark:bg-destroy-80/50 rounded'}
iconClassName={'text-destroy-80 dark:text-destroy-30'}
className="ml-4"
/>
<p className="flex-1 mr-4">{issue}</p>
</li>
))}
</ul>
)}
<div className="flex items-center text-left gap-1">
<p className="flex-1">{name}</p>
{internetConnected ? (
<ActionIcon
size="lg"
icon={
hasIssueToIcon[
issues[name as ConnectingTypeGroup].toString()
]
}
iconClassName={
hasIssueToIconColors[
issues[name as ConnectingTypeGroup].toString()
].icon
}
bgClassName={
'rounded-sm ' +
hasIssueToIconColors[
issues[name as ConnectingTypeGroup].toString()
].bg
}
/>
) : (
<ActionIcon
icon={hasIssueToIcon.true}
bgClassName={hasIssueToIconColors.true.bg}
iconClassName={hasIssueToIconColors.true.icon}
/>
)}
</div>
{issues[name as ConnectingTypeGroup] && (
<button
onClick={async () => {
await navigator.clipboard.writeText(
JSON.stringify(error, null, 2) || ''
)
setHasCopied(true)
setTimeout(() => setHasCopied(false), 5000)
}}
className="flex w-fit gap-2 items-center bg-transparent text-sm p-1 py-0 my-0 -mx-1 text-destroy-80 dark:text-destroy-10 hover:bg-transparent border-transparent dark:border-transparent hover:border-destroy-80 dark:hover:border-destroy-80 dark:hover:bg-destroy-80"
>
{hasCopied ? 'Copied' : 'Copy Error'}
<ActionIcon
size="lg"
icon={hasCopied ? 'clipboardCheckmark' : 'clipboardPlus'}
iconClassName="text-inherit dark:text-inherit"
bgClassName="!bg-transparent"
/>
</button>
)}
</li>
))}
</ul>
</Popover.Panel>
</Popover>
)

View File

@ -1,5 +1,6 @@
import { FormEvent, useEffect, useRef, useState } from 'react'
import { type ProjectWithEntryPointMetadata, paths } from '../Router'
import { type ProjectWithEntryPointMetadata } from 'lib/types'
import { paths } from 'lib/paths'
import { Link } from 'react-router-dom'
import { ActionButton } from './ActionButton'
import {

View File

@ -1,7 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { ProjectWithEntryPointMetadata } from '../Router'
import { type ProjectWithEntryPointMetadata } from 'lib/types'
import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar/CommandBar'
import { APP_NAME } from 'lib/constants'

View File

@ -1,7 +1,8 @@
import { Popover, Transition } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { faHome } from '@fortawesome/free-solid-svg-icons'
import { IndexLoaderData, paths } from '../Router'
import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths'
import { isTauri } from '../lib/isTauri'
import { Link } from 'react-router-dom'
import { ExportButton } from './ExportButton'

View File

@ -11,16 +11,13 @@ import { getNormalisedCoordinates, throttle } 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 { getNodeFromPath } from 'lang/queryAst'
import { VariableDeclarator, recast, CallExpression } from 'lang/wasm'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager, useKclContext } from 'lang/KclSinglton'
import { changeSketchArguments } from 'lang/std/sketch'
import { useKclContext } from 'lang/KclSingleton'
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
export const Stream = ({ className = '' }) => {
export const Stream = ({ className = '' }: { className?: string }) => {
const [isLoading, setIsLoading] = useState(true)
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
const videoRef = useRef<HTMLVideoElement>(null)
@ -39,7 +36,7 @@ export const Stream = ({ className = '' }) => {
}))
const { settings } = useGlobalStateContext()
const cameraControls = settings?.context?.cameraControls
const { send, state, context } = useModelingContext()
const { state } = useModelingContext()
const { isExecuting } = useKclContext()
useEffect(() => {
@ -53,8 +50,10 @@ export const Stream = ({ className = '' }) => {
videoRef.current.srcObject = mediaStream
}, [mediaStream])
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
if (!videoRef.current) return
if (state.matches('Sketch')) return
if (state.matches('Sketch no face')) return
const { x, y } = getNormalisedCoordinates({
clientX: e.clientX,
clientY: e.clientY,
@ -62,55 +61,6 @@ export const Stream = ({ className = '' }) => {
...streamDimensions,
})
const newId = uuidv4()
const interactionGuards = cameraMouseDragGuards[cameraControls]
let interaction: CameraDragInteractionType_type = 'rotate'
if (
interactionGuards.pan.callback(e) ||
interactionGuards.pan.lenientDragStartButton === e.button
) {
interaction = 'pan'
} else if (
interactionGuards.rotate.callback(e) ||
interactionGuards.rotate.lenientDragStartButton === e.button
) {
interaction = 'rotate'
} else if (
interactionGuards.zoom.dragCallback(e) ||
interactionGuards.zoom.lenientDragStartButton === e.button
) {
interaction = 'zoom'
}
if (state.matches('Sketch.Move Tool')) {
if (
state.matches('Sketch.Move Tool.No move') ||
state.matches('Sketch.Move Tool.Move with execute')
) {
return
}
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'handle_mouse_drag_start',
window: { x, y },
},
cmd_id: newId,
})
} else if (!state.matches('Sketch.Line Tool')) {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_start',
interaction,
window: { x, y },
},
cmd_id: newId,
})
}
setButtonDownInStream(e.button)
setClickCoords({ x, y })
}
@ -128,13 +78,15 @@ export const Stream = ({ className = '' }) => {
})
}, Math.round(1000 / fps))
const handleMouseUp: MouseEventHandler<HTMLVideoElement> = ({
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
clientX,
clientY,
ctrlKey,
}) => {
if (!videoRef.current) return
setButtonDownInStream(undefined)
if (state.matches('Sketch')) return
if (state.matches('Sketch no face')) return
const { x, y } = getNormalisedCoordinates({
clientX,
clientY,
@ -155,206 +107,19 @@ export const Stream = ({ className = '' }) => {
cmd_id: newCmdId,
}
if (!didDragInStream && state.matches('Sketch no face')) {
command.cmd = {
type: 'select_with_point',
selection_type: 'add',
selected_at_window: { x, y },
}
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,
segmentId: entities_modified[0],
},
})
} 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, segmentId: entities_modified[0] },
})
}
})
} else if (
!didDragInStream &&
(state.matches('Sketch.SketchIdle') || state.matches('idle'))
) {
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')) {
if (!didDragInStream) {
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')) {
} else if (didDragInStream) {
command.cmd = {
type: 'handle_mouse_drag_end',
window: { x, y },
}
engineCommandManager.sendSceneCommand(command).then(async () => {
if (!context.sketchPathToNode) return
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
context.sketchPathToNode,
'VariableDeclarator'
)
// 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 pathInfo = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'path_get_info',
path_id: context.sketchEnginePathId,
},
})
const segmentsWithMappings = (
pathInfo?.data?.data?.segments as { command_id: string }[]
)
.filter(({ command_id }) => {
return command_id && engineCommandManager.artifactMap[command_id]
})
.map(({ command_id }) => command_id)
const segment2dInfo = await Promise.all(
segmentsWithMappings.map(async (segmentId) => {
const response = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: segmentId,
},
})
const controlPoints: [
{ x: number; y: number },
{ x: number; y: number }
] = response.data.data.control_points
return {
controlPoints,
segmentId,
}
})
)
let modifiedAst = { ...kclManager.ast }
let code = kclManager.code
for (const controlPoint of segment2dInfo) {
const range =
engineCommandManager.artifactMap[controlPoint.segmentId].range
if (!range) continue
const from = controlPoint.controlPoints[0]
const to = controlPoint.controlPoints[1]
const modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
range,
[to.x, to.y],
[from.x, from.y]
)
modifiedAst = modded.modifiedAst
// update artifact map ranges now that we have updated the ast.
code = recast(modded.modifiedAst)
const astWithCurrentRanges = kclManager.safeParse(code)
if (!astWithCurrentRanges) return
const updateNode = getNodeFromPath<CallExpression>(
astWithCurrentRanges,
modded.pathToNode
).node
engineCommandManager.artifactMap[controlPoint.segmentId].range = [
updateNode.start,
updateNode.end,
]
}
kclManager.executeAstMock(modifiedAst, true)
})
void engineCommandManager.sendSceneCommand(command)
} else {
engineCommandManager.sendSceneCommand(command)
}
@ -364,6 +129,8 @@ export const Stream = ({ className = '' }) => {
}
const handleMouseMove: MouseEventHandler<HTMLVideoElement> = (e) => {
if (state.matches('Sketch')) return
if (state.matches('Sketch no face')) return
if (!clickCoords) return
const delta =
@ -376,16 +143,19 @@ export const Stream = ({ className = '' }) => {
}
return (
<div id="stream" className={className}>
<div
id="stream"
className={className}
onMouseUp={handleMouseUp}
onMouseDown={handleMouseDown}
onContextMenu={(e) => e.preventDefault()}
onContextMenuCapture={(e) => e.preventDefault()}
>
<video
ref={videoRef}
muted
autoPlay
controls={false}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onContextMenu={(e) => e.preventDefault()}
onContextMenuCapture={(e) => e.preventDefault()}
onWheel={handleScroll}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
@ -393,6 +163,7 @@ export const Stream = ({ className = '' }) => {
disablePictureInPicture
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
/>
<ClientSideScene cameraControls={settings.context.cameraControls} />
{isLoading && (
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<Loading>

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, useRef } from 'react'
import { linter, lintGutter } from '@codemirror/lint'
import { useStore } from 'useStore'
import { processCodeMirrorRanges } from 'lib/selections'
@ -24,7 +24,10 @@ 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'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { sceneInfra } from 'clientSideScene/sceneInfra'
import { copilotBundle } from 'editor/copilot'
export const editorShortcutMeta = {
formatCode: {
@ -44,25 +47,31 @@ export const TextEditor = ({
}) => {
const {
editorView,
isLSPServerReady,
isKclLspServerReady,
isCopilotLspServerReady,
setEditorView,
setIsLSPServerReady,
setIsKclLspServerReady,
setIsCopilotLspServerReady,
isShiftDown,
} = useStore((s) => ({
editorView: s.editorView,
isLSPServerReady: s.isLSPServerReady,
isKclLspServerReady: s.isKclLspServerReady,
isCopilotLspServerReady: s.isCopilotLspServerReady,
setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady,
setIsKclLspServerReady: s.setIsKclLspServerReady,
setIsCopilotLspServerReady: s.setIsCopilotLspServerReady,
isShiftDown: s.isShiftDown,
}))
const { code, errors } = useKclContext()
const lastEvent = useRef({ event: '', time: Date.now() })
const {
context: { selectionRanges, selectionRangeTypeMap },
send,
state,
} = useModelingContext()
const { settings: { context: { textWrapping } = {} } = {} } =
const { settings: { context: { textWrapping } = {} } = {}, auth } =
useGlobalStateContext()
const { commandBarSend } = useCommandsContext()
const { enable: convertEnabled, handleClick: convertCallback } =
@ -71,20 +80,20 @@ export const TextEditor = ({
// So this is a bit weird, we need to initialize the lsp server and client.
// But the server happens async so we break this into two parts.
// Below is the client and server promise.
const { lspClient } = useMemo(() => {
const { lspClient: kclLspClient } = useMemo(() => {
const intoServer: IntoServer = new IntoServer()
const fromServer: FromServer = FromServer.create()
const client = new Client(fromServer, intoServer)
if (!TEST) {
Server.initialize(intoServer, fromServer).then((lspServer) => {
lspServer.start()
setIsLSPServerReady(true)
lspServer.start('kcl')
setIsKclLspServerReady(true)
})
}
const lspClient = new LanguageServerClient({ client })
return { lspClient }
}, [setIsLSPServerReady])
}, [setIsKclLspServerReady])
// Here we initialize the plugin which will start the client.
// When we have multi-file support the name of the file will be a dep of
@ -93,19 +102,57 @@ export const TextEditor = ({
// We do not want to restart the server, its just wasteful.
const kclLSP = useMemo(() => {
let plugin = null
if (isLSPServerReady && !TEST) {
if (isKclLspServerReady && !TEST) {
// Set up the lsp plugin.
const lsp = kclLanguage({
// When we have more than one file, we'll need to change this.
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
workspaceFolders: null,
client: lspClient,
client: kclLspClient,
})
plugin = lsp
}
return plugin
}, [lspClient, isLSPServerReady])
}, [kclLspClient, isKclLspServerReady])
const { lspClient: copilotLspClient } = useMemo(() => {
const intoServer: IntoServer = new IntoServer()
const fromServer: FromServer = FromServer.create()
const client = new Client(fromServer, intoServer)
if (!TEST) {
Server.initialize(intoServer, fromServer).then((lspServer) => {
const token = auth?.context?.token
lspServer.start('copilot', token)
setIsCopilotLspServerReady(true)
})
}
const lspClient = new LanguageServerClient({ client })
return { lspClient }
}, [setIsCopilotLspServerReady])
// Here we initialize the plugin which will start the client.
// When we have multi-file support the name of the file will be a dep of
// this use memo, as well as the directory structure, which I think is
// a good setup because it will restart the client but not the server :)
// We do not want to restart the server, its just wasteful.
const copilotLSP = useMemo(() => {
let plugin = null
if (isCopilotLspServerReady && !TEST) {
// Set up the lsp plugin.
const lsp = copilotBundle({
// When we have more than one file, we'll need to change this.
documentUri: `file:///we-just-have-one-file-for-now.kcl`,
workspaceFolders: null,
client: copilotLspClient,
allowHTMLContent: true,
})
plugin = lsp
}
return plugin
}, [copilotLspClient, isCopilotLspServerReady])
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (newCode: string) => {
@ -115,6 +162,12 @@ export const TextEditor = ({
if (!editorView) {
setEditorView(viewUpdate.view)
}
if (sceneInfra.selected) return // mid drag
const ignoreEvents: ModelingMachineEvent['type'][] = [
'Equip Line tool',
'Equip tangential arc to',
]
if (ignoreEvents.includes(state.event.type)) return
const eventInfo = processCodeMirrorRanges({
codeMirrorRanges: viewUpdate.state.selection.ranges,
selectionRanges,
@ -122,7 +175,20 @@ export const TextEditor = ({
isShiftDown,
})
if (!eventInfo) return
const deterministicEventInfo = {
...eventInfo,
engineEvents: eventInfo.engineEvents.map((e) => ({
...e,
cmd_id: 'static',
})),
}
const stringEvent = JSON.stringify(deterministicEventInfo)
if (
stringEvent === lastEvent.current.event &&
Date.now() - lastEvent.current.time < 500
)
return // don't repeat events
lastEvent.current = { event: stringEvent, time: Date.now() }
send(eventInfo.modelingEvent)
eventInfo.engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
@ -161,6 +227,7 @@ export const TextEditor = ({
] as Extension[]
if (kclLSP) extensions.push(kclLSP)
if (copilotLSP) extensions.push(copilotLSP)
// These extensions have proven to mess with vitest
if (!TEST) {

View File

@ -11,7 +11,7 @@ import {
getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
export function equalAngleInfo({
selectionRanges,

View File

@ -11,7 +11,7 @@ import {
getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
export function setEqualLengthInfo({
selectionRanges,

View File

@ -10,7 +10,7 @@ import {
getTransformInfos,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
export function horzVertInfo(
selectionRanges: Selections,

View File

@ -15,7 +15,7 @@ import {
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
const getModalInfo = createInfoModal(GetInfoModal)

View File

@ -10,7 +10,7 @@ import {
getRemoveConstraintsTransforms,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
export function removeConstrainingValuesInfo({
selectionRanges,

View File

@ -19,7 +19,7 @@ import {
createVariableDeclaration,
} from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
@ -139,7 +139,7 @@ export function applyConstraintAxisAlign({
constraint,
}).transforms
let finalValue = createIdentifier('_0')
let finalValue = createIdentifier('ZERO')
return transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),

View File

@ -14,7 +14,7 @@ import {
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
const getModalInfo = createInfoModal(GetInfoModal)

View File

@ -13,7 +13,7 @@ import {
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
import { Selections } from 'lib/selections'
const getModalInfo = createInfoModal(GetInfoModal)

View File

@ -21,7 +21,7 @@ import {
} from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { normaliseAngle } from '../../lib/utils'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
@ -89,12 +89,16 @@ export async function applyConstraintAngleLength({
isReferencingXAxis && angleOrLength === 'setAngle'
let forceVal = valueUsedInTransform || 0
let calcIdentifier = createIdentifier('_0')
let calcIdentifier = createIdentifier('ZERO')
if (isReferencingYAxisAngle) {
calcIdentifier = createIdentifier(forceVal < 0 ? '_270' : '_90')
calcIdentifier = createIdentifier(
forceVal < 0 ? 'THREE_QUARTER_TURN' : 'QUARTER_TURN'
)
forceVal = normaliseAngle(forceVal + (forceVal < 0 ? 90 : -90))
} else if (isReferencingXAxisAngle) {
calcIdentifier = createIdentifier(Math.abs(forceVal) > 90 ? '_180' : '_0')
calcIdentifier = createIdentifier(
Math.abs(forceVal) > 90 ? 'HALF_TURN' : 'ZERO'
)
forceVal =
Math.abs(forceVal) > 90 ? normaliseAngle(forceVal - 180) : forceVal
}
@ -112,7 +116,7 @@ export async function applyConstraintAngleLength({
)
if (
isReferencingYAxisAngle ||
(isReferencingXAxisAngle && calcIdentifier.name !== '_0')
(isReferencingXAxisAngle && calcIdentifier.name !== 'ZERO')
) {
finalValue = createBinaryExpressionWithUnary([calcIdentifier, finalValue])
}

View File

@ -4,7 +4,7 @@ import { faBars, faBug, 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 { paths } from 'lib/paths'
import { Models } from '@kittycad/lib'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'

View File

@ -1,7 +1,7 @@
import { Dialog } from '@headlessui/react'
import { useState } from 'react'
import { ActionButton } from './ActionButton'
import { useKclContext } from 'lang/KclSinglton'
import { useKclContext } from 'lang/KclSingleton'
export function WasmErrBanner() {
const [isBannerDismissed, setBannerDismissed] = useState(false)

503
src/editor/copilot/index.ts Normal file
View File

@ -0,0 +1,503 @@
/// Thanks to the Cursor folks for their heavy lifting here.
import { indentUnit } from '@codemirror/language'
import {
Decoration,
DecorationSet,
EditorView,
ViewPlugin,
ViewUpdate,
} from '@codemirror/view'
import {
Annotation,
EditorState,
Extension,
Facet,
Prec,
StateEffect,
StateField,
Transaction,
} from '@codemirror/state'
import { completionStatus } from '@codemirror/autocomplete'
import { docPathFacet, offsetToPos, posToOffset } from 'editor/lsp/util'
import { LanguageServerPlugin } from 'editor/lsp/plugin'
import { LanguageServerOptions } from 'editor/lsp/plugin'
import { LanguageServerClient } from 'editor/lsp'
// Create Facet for the current docPath
export const docPath = Facet.define<string, string>({
combine(value: readonly string[]) {
return value[value.length - 1]
},
})
export const relDocPath = Facet.define<string, string>({
combine(value: readonly string[]) {
return value[value.length - 1]
},
})
const ghostMark = Decoration.mark({ class: 'cm-ghostText' })
interface Suggestion {
text: string
displayText: string
cursorPos: number
startPos: number
endPos: number
endReplacement: number
uuid: string
}
// Effects to tell StateEffect what to do with GhostText
const addSuggestion = StateEffect.define<Suggestion>()
const acceptSuggestion = StateEffect.define<null>()
const clearSuggestion = StateEffect.define<null>()
const typeFirst = StateEffect.define<number>()
interface CompletionState {
ghostText: GhostText | null
}
interface GhostText {
text: string
displayText: string
displayPos: number
startPos: number
endGhostText: number
endReplacement: number
endPos: number
decorations: DecorationSet
weirdInsert: boolean
uuid: string
}
export const completionDecoration = StateField.define<CompletionState>({
create(_state: EditorState) {
return { ghostText: null }
},
update(state: CompletionState, transaction: Transaction) {
for (const effect of transaction.effects) {
if (effect.is(addSuggestion)) {
// When adding a suggestion, we set th ghostText
const {
text,
displayText,
endReplacement,
cursorPos,
startPos,
endPos,
uuid,
} = effect.value
const endGhostText = cursorPos + displayText.length
const decorations = Decoration.set([
ghostMark.range(cursorPos, endGhostText),
])
return {
ghostText: {
text,
displayText,
startPos,
endPos,
decorations,
displayPos: cursorPos,
endReplacement,
endGhostText,
weirdInsert: false,
uuid,
},
}
} else if (effect.is(acceptSuggestion)) {
if (state.ghostText) {
return { ghostText: null }
}
} else if (effect.is(typeFirst)) {
const numChars = effect.value
if (state.ghostText && !state.ghostText.weirdInsert) {
let {
text,
displayText,
displayPos,
startPos,
endPos,
endGhostText,
decorations,
endReplacement,
uuid,
} = state.ghostText
displayPos += numChars
displayText = displayText.slice(numChars)
if (startPos === endGhostText) {
return { ghostText: null }
} else {
decorations = Decoration.set([
ghostMark.range(displayPos, endGhostText),
])
return {
ghostText: {
text,
displayText,
startPos,
endPos,
decorations,
endGhostText,
endReplacement,
uuid,
displayPos,
weirdInsert: false,
},
}
}
}
} else if (effect.is(clearSuggestion)) {
return { ghostText: null }
}
}
// if (transaction.docChanged && state.ghostText) {
// if (transaction.
// onsole.log({changes: transaction.changes, transaction})
// const newGhostText = state.ghostText.decorations.map(transaction.changes)
// return {ghostText: {...state.ghostText, decorations: newGhostText}};
// }
return state
},
provide: (field) =>
EditorView.decorations.from(field, (value) =>
value.ghostText ? value.ghostText.decorations : Decoration.none
),
})
const copilotEvent = Annotation.define<null>()
/****************************************************************************
************************* COMMANDS ******************************************
*****************************************************************************/
const acceptSuggestionCommand = (
copilotClient: LanguageServerClient,
view: EditorView
) => {
// We delete the ghost text and insert the suggestion.
// We also set the cursor to the end of the suggestion.
const ghostText = view.state.field(completionDecoration)!.ghostText
if (!ghostText) {
return false
}
const ghostTextStart = ghostText.displayPos
const ghostTextEnd = ghostText.endGhostText
const actualTextStart = ghostText.startPos
const actualTextEnd = ghostText.endPos
const replacementEnd = ghostText.endReplacement
const suggestion = ghostText.text
view.dispatch({
changes: {
from: ghostTextStart,
to: ghostTextEnd,
insert: '',
},
// selection: {anchor: actualTextEnd},
effects: acceptSuggestion.of(null),
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)],
})
const tmpTextEnd = replacementEnd - (ghostTextEnd - ghostTextStart)
view.dispatch({
changes: {
from: actualTextStart,
to: tmpTextEnd,
insert: suggestion,
},
selection: { anchor: actualTextEnd },
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(true)],
})
copilotClient.accept(ghostText.uuid)
return true
}
export const rejectSuggestionCommand = (
copilotClient: LanguageServerClient,
view: EditorView
) => {
// We delete the suggestion, then carry through with the original keypress
const ghostText = view.state.field(completionDecoration)!.ghostText
if (!ghostText) {
return false
}
const ghostTextStart = ghostText.displayPos
const ghostTextEnd = ghostText.endGhostText
view.dispatch({
changes: {
from: ghostTextStart,
to: ghostTextEnd,
insert: '',
},
effects: clearSuggestion.of(null),
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)],
})
copilotClient.reject()
return false
}
const sameKeyCommand = (
copilotClient: LanguageServerClient,
view: EditorView,
key: string
) => {
// When we type a key that is the same as the first letter of the suggestion, we delete the first letter of the suggestion and carry through with the original keypress
const ghostText = view.state.field(completionDecoration)!.ghostText
if (!ghostText) {
return false
}
const ghostTextStart = ghostText.displayPos
const indent = view.state.facet(indentUnit)
if (key === 'Tab' && ghostText.displayText.startsWith(indent)) {
view.dispatch({
selection: { anchor: ghostTextStart + indent.length },
effects: typeFirst.of(indent.length),
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)],
})
return true
} else if (key === 'Tab') {
return acceptSuggestionCommand(copilotClient, view)
} else if (ghostText.weirdInsert || key !== ghostText.displayText[0]) {
return rejectSuggestionCommand(copilotClient, view)
} else if (ghostText.displayText.length === 1) {
return acceptSuggestionCommand(copilotClient, view)
} else {
// Use this to delete the first letter of the suggestion
view.dispatch({
selection: { anchor: ghostTextStart + 1 },
effects: typeFirst.of(1),
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)],
})
return true
}
}
const completionPlugin = (copilotClient: LanguageServerClient) =>
EditorView.domEventHandlers({
keydown(event, view) {
if (
event.key !== 'Shift' &&
event.key !== 'Control' &&
event.key !== 'Alt' &&
event.key !== 'Meta'
) {
return sameKeyCommand(copilotClient, view, event.key)
} else {
return false
}
},
mousedown(event, view) {
return rejectSuggestionCommand(copilotClient, view)
},
})
const viewCompletionPlugin = (copilotClient: LanguageServerClient) =>
EditorView.updateListener.of((update) => {
if (update.focusChanged) {
rejectSuggestionCommand(copilotClient, update.view)
}
})
// A view plugin that requests completions from the server after a delay
const completionRequester = (client: LanguageServerClient) => {
let timeout: any = null
let lastPos = 0
const badUpdate = (update: ViewUpdate) => {
for (const tr of update.transactions) {
if (tr.annotation(copilotEvent) !== undefined) {
return true
}
}
return false
}
const containsGhostText = (update: ViewUpdate) => {
return update.state.field(completionDecoration).ghostText != null
}
const autocompleting = (update: ViewUpdate) => {
return completionStatus(update.state) === 'active'
}
const notFocused = (update: ViewUpdate) => {
return !update.view.hasFocus
}
return EditorView.updateListener.of((update: ViewUpdate) => {
if (
update.docChanged &&
!update.transactions.some((tr) =>
tr.effects.some((e) => e.is(acceptSuggestion) || e.is(clearSuggestion))
)
) {
// Cancel the previous timeout
if (timeout) {
clearTimeout(timeout)
}
if (
badUpdate(update) ||
containsGhostText(update) ||
autocompleting(update) ||
notFocused(update)
) {
return
}
// Get the current position and source
const state = update.state
const pos = state.selection.main.head
const source = state.doc.toString()
const path = state.facet(docPath)
const relativePath = state.facet(relDocPath)
const languageId = 'kcl'
// Set a new timeout to request completion
timeout = setTimeout(async () => {
// Check if the position has changed
if (pos === lastPos) {
// Request completion from the server
try {
const completionResult = await client.getCompletion({
doc: {
source,
tabSize: state.facet(EditorState.tabSize),
indentSize: 1,
insertSpaces: true,
path,
uri: `file://${path}`,
relativePath,
languageId,
position: offsetToPos(state.doc, pos),
},
})
if (completionResult.completions.length === 0) {
return
}
let {
text,
displayText,
range: { start },
position,
uuid,
} = completionResult.completions[0]
const startPos = posToOffset(state.doc, {
line: start.line,
character: start.character,
})!
const endGhostPos =
posToOffset(state.doc, {
line: position.line,
character: position.character,
})! + displayText.length
// EndPos is the position that marks the complete end
// of what is to be replaced when we accept a completion
// result
const endPos = startPos + text.length
// Check if the position is still the same
if (
pos === lastPos &&
completionStatus(update.view.state) !== 'active' &&
update.view.hasFocus
) {
// Dispatch an effect to add the suggestion
// If the completion starts before the end of the line, check the end of the line with the end of the completion
const line = update.view.state.doc.lineAt(pos)
if (line.to !== pos) {
const ending = update.view.state.doc.sliceString(pos, line.to)
if (displayText.endsWith(ending)) {
displayText = displayText.slice(
0,
displayText.length - ending.length
)
} else if (displayText.includes(ending)) {
// Remove the ending
update.view.dispatch({
changes: {
from: pos,
to: line.to,
insert: '',
},
selection: { anchor: pos },
effects: typeFirst.of(ending.length),
annotations: [
copilotEvent.of(null),
Transaction.addToHistory.of(false),
],
})
}
}
update.view.dispatch({
changes: {
from: pos,
to: pos,
insert: displayText,
},
effects: [
addSuggestion.of({
displayText,
endReplacement: endGhostPos,
text,
cursorPos: pos,
startPos,
endPos,
uuid,
}),
],
annotations: [
copilotEvent.of(null),
Transaction.addToHistory.of(false),
],
})
}
} catch (error) {
console.warn('copilot completion failed', error)
// Javascript wait for 500ms for some reason is necessary here.
// TODO - FIGURE OUT WHY THIS RESOLVES THE BUG
await new Promise((resolve) => setTimeout(resolve, 300))
}
}
}, 150)
// Update the last position
lastPos = pos
}
})
}
export function copilotServer(options: LanguageServerOptions) {
let plugin: LanguageServerPlugin
return ViewPlugin.define(
(view) =>
(plugin = new LanguageServerPlugin(view, options.allowHTMLContent))
)
}
export const copilotBundle = (options: LanguageServerOptions): Extension => [
docPath.of(options.documentUri.split('/').pop()!),
docPathFacet.of(options.documentUri.split('/').pop()!),
relDocPath.of(options.documentUri.replace('file://', '')),
completionDecoration,
Prec.highest(completionPlugin(options.client)),
Prec.highest(viewCompletionPlugin(options.client)),
completionRequester(options.client),
copilotServer(options),
]

View File

@ -3,6 +3,67 @@ import Client from './client'
import { LanguageServerPlugin } from './plugin'
import { SemanticToken, deserializeTokens } from './semantic_tokens'
export interface CopilotGetCompletionsParams {
doc: {
source: string
tabSize: number
indentSize: number
insertSpaces: boolean
path: string
uri: string
relativePath: string
languageId: string
position: {
line: number
character: number
}
}
}
interface CopilotGetCompletionsResult {
completions: {
text: string
position: {
line: number
character: number
}
uuid: string
range: {
start: {
line: number
character: number
}
end: {
line: number
character: number
}
}
displayText: string
point: {
line: number
character: number
}
region: {
start: {
line: number
character: number
}
end: {
line: number
character: number
}
}
}[]
}
interface CopilotAcceptCompletionParams {
uuid: string
}
interface CopilotRejectCompletionParams {
uuids: string[]
}
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/
// Client to server then server to client
@ -17,6 +78,9 @@ interface LSPRequestMap {
LSP.SemanticTokensParams,
LSP.SemanticTokens
]
getCompletions: [CopilotGetCompletionsParams, CopilotGetCompletionsResult]
notifyAccepted: [CopilotAcceptCompletionParams, any]
notifyRejected: [CopilotRejectCompletionParams, any]
}
// Client to server
@ -55,6 +119,7 @@ export class LanguageServerClient {
private isUpdatingSemanticTokens: boolean = false
private semanticTokens: SemanticToken[] = []
private queuedUids: string[] = []
constructor(options: LanguageServerClientOptions) {
this.plugins = []
@ -62,6 +127,7 @@ export class LanguageServerClient {
this.ready = false
this.queuedUids = []
this.initializePromise = this.initialize()
}
@ -145,6 +211,33 @@ export class LanguageServerClient {
return this.client.notify(method, params)
}
async getCompletion(params: CopilotGetCompletionsParams) {
const response = await this.request('getCompletions', params)
//
this.queuedUids = [...response.completions.map((c) => c.uuid)]
return response
}
async accept(uuid: string) {
const badUids = this.queuedUids.filter((u) => u !== uuid)
this.queuedUids = []
await this.acceptCompletion({ uuid })
await this.rejectCompletions({ uuids: badUids })
}
async reject() {
const badUids = this.queuedUids
this.queuedUids = []
return await this.rejectCompletions({ uuids: badUids })
}
async acceptCompletion(params: CopilotAcceptCompletionParams) {
return await this.request('notifyAccepted', params)
}
async rejectCompletions(params: CopilotRejectCompletionParams) {
return await this.request('notifyRejected', params)
}
private processNotification(notification: Notification) {
for (const plugin of this.plugins) plugin.processNotification(notification)
}

View File

@ -10,7 +10,7 @@ import {
NodeSet,
} from '@lezer/common'
import { LanguageServerClient } from '.'
import { posToOffset } from './plugin'
import { posToOffset } from 'editor/lsp/util'
import { SemanticToken } from './semantic_tokens'
import { DocInput } from '@codemirror/language'
import { tags, styleTags } from '@lezer/highlight'

View File

@ -22,10 +22,10 @@ import type {
} from '@codemirror/autocomplete'
import type { PublishDiagnosticsParams } from 'vscode-languageserver-protocol'
import type { ViewUpdate, PluginValue } from '@codemirror/view'
import type { Text } from '@codemirror/state'
import type * as LSP from 'vscode-languageserver-protocol'
import { LanguageServerClient, Notification } from '.'
import { Marked } from '@ts-stack/markdown'
import { offsetToPos, posToOffset } from 'editor/lsp/util'
const changesDelay = 500
@ -343,24 +343,6 @@ export function kclPlugin(options: LanguageServerOptions) {
]
}
export function posToOffset(
doc: Text,
pos: { line: number; character: number }
): number | undefined {
if (pos.line >= doc.lines) return
const offset = doc.line(pos.line + 1).from + pos.character
if (offset > doc.length) return
return offset
}
function offsetToPos(doc: Text, offset: number) {
const line = doc.lineAt(offset)
return {
line: line.number - 1,
character: offset - line.from,
}
}
function formatContents(
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
): string {

View File

@ -1,6 +1,7 @@
import init, {
copilot_lsp_run,
InitOutput,
lsp_run,
kcl_lsp_run,
ServerConfig,
} from '../../wasm-lib/pkg/wasm_lib'
import { FromServer, IntoServer } from './codec'
@ -29,8 +30,15 @@ export default class Server {
return server
}
async start(): Promise<void> {
async start(type_: 'kcl' | 'copilot', token?: string): Promise<void> {
const config = new ServerConfig(this.#intoServer, this.#fromServer)
await lsp_run(config)
if (type_ === 'copilot') {
if (!token) {
throw new Error('auth token is required for copilot')
}
await copilot_lsp_run(config, token)
} else if (type_ === 'kcl') {
await kcl_lsp_run(config)
}
}
}

23
src/editor/lsp/util.ts Normal file
View File

@ -0,0 +1,23 @@
import { Facet, Text } from '@codemirror/state'
export function posToOffset(
doc: Text,
pos: { line: number; character: number }
): number | undefined {
if (pos.line >= doc.lines) return
const offset = doc.line(pos.line + 1).from + pos.character
if (offset > doc.length) return
return offset
}
export function offsetToPos(doc: Text, offset: number) {
const line = doc.lineAt(offset)
return {
line: line.number - 1,
character: offset - line.from,
}
}
export const docPathFacet = Facet.define<string, string>({
combine: (values) => values[values.length - 1],
})

View File

@ -1,4 +1,6 @@
import { BROWSER_FILE_NAME, IndexLoaderData, paths } from 'Router'
import { BROWSER_FILE_NAME } from 'Router'
import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths'
import { useRouteLoaderData } from 'react-router-dom'
export function useAbsoluteFilePath() {

View File

@ -15,6 +15,7 @@ export function useEngineConnectionSubscriptions() {
if (!engineCommandManager) return
const unSubHover = engineCommandManager.subscribeToUnreliable({
// Note this is our hover logic, "highlight_set_entity" is the event that is fired when we hover over an entity
event: 'highlight_set_entity',
callback: ({ data }) => {
if (data?.entity_id) {
@ -46,6 +47,6 @@ export function useEngineConnectionSubscriptions() {
engineCommandManager,
setHighlightRange,
highlightRange,
context.sketchEnginePathId,
context?.sketchEnginePathId,
])
}

View File

@ -3,7 +3,7 @@ import { parse } from '../lang/wasm'
import { useStore } from '../useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
import { deferExecution } from 'lib/utils'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>,

View File

@ -2,7 +2,7 @@ import {
SetVarNameModal,
createSetVarNameModal,
} from 'components/SetVarNameModal'
import { kclManager } from 'lang/KclSinglton'
import { kclManager } from 'lang/KclSingleton'
import { moveValueIntoNewVariable } from 'lang/modifyAst'
import { isNodeSafeToReplace } from 'lang/queryAst'
import { useEffect, useState } from 'react'

View File

@ -219,3 +219,8 @@ code {
word-break: normal;
word-wrap: break-word;
}
.cm-ghostText,
.cm-ghostText * {
color: rgb(120, 120, 120, 0.8) !important;
}

View File

@ -6,6 +6,8 @@ import { Router } from './Router'
import { HotkeysProvider } from 'react-hotkeys-hook'
// uncomment for xstate inspector
// import { DEV } from 'env'
// import { inspect } from '@xstate/inspect'
// if (DEV)
// inspect({
// iframe: false,

View File

@ -7,6 +7,7 @@ import {
} from './std/engineConnection'
import { deferExecution } from 'lib/utils'
import {
CallExpression,
initPromise,
parse,
PathToNode,
@ -17,7 +18,7 @@ import {
import { bracket } from 'lib/exampleKcl'
import { createContext, useContext, useEffect, useState } from 'react'
import { getNodeFromPath } from './queryAst'
import { IndexLoaderData } from 'Router'
import { type IndexLoaderData } from 'lib/types'
import { Params, useLoaderData } from 'react-router-dom'
import { isTauri } from 'lib/isTauri'
import { writeTextFile } from '@tauri-apps/api/fs'
@ -98,7 +99,7 @@ class KclManager {
})
})
} else {
localStorage.setItem(PERSIST_CODE_TOKEN, code)
safteLSSetItem(PERSIST_CODE_TOKEN, code)
}
}
@ -110,10 +111,6 @@ class KclManager {
this._programMemoryCallBack(programMemory)
}
get defaultPlanes() {
return this?.engineCommandManager?.defaultPlanes
}
get logs() {
return this._logs
}
@ -157,22 +154,24 @@ class KclManager {
this.code = ''
return
}
const storedCode = localStorage.getItem(PERSIST_CODE_TOKEN)
const storedCode = safeLSGetItem(PERSIST_CODE_TOKEN) || ''
// TODO #819 remove zustand persistence 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') || '{}')
const zustandStore = JSON.parse(safeLSGetItem('store') || '{}')
if (storedCode === null && zustandStore?.state?.code) {
this.code = zustandStore.state.code
localStorage.setItem(PERSIST_CODE_TOKEN, this._code)
safteLSSetItem(PERSIST_CODE_TOKEN, this._code)
zustandStore.state.code = ''
localStorage.setItem('store', JSON.stringify(zustandStore))
safteLSSetItem('store', JSON.stringify(zustandStore))
} else if (storedCode === null) {
console.log('stored brack thing')
this.code = bracket
} else {
this.code = storedCode
}
this.ensureWasmInit().then(() => {
this.ast = this.safeParse(this.code) || this.ast
})
}
registerCallBacks({
setCode,
@ -235,7 +234,6 @@ class KclManager {
const { logs, errors, programMemory } = await executeAst({
ast,
engineCommandManager: this.engineCommandManager,
defaultPlanes: this.defaultPlanes,
})
this.isExecuting = false
this.logs = logs
@ -251,13 +249,20 @@ class KclManager {
data: null,
})
}
async executeAstMock(ast: Program = this._ast, updateCode = false) {
async executeAstMock(
ast: Program = this._ast,
{
updates,
}: {
updates: 'none' | 'code' | 'codeAndArtifactRanges'
} = { updates: 'none' }
) {
await this.ensureWasmInit()
const newCode = recast(ast)
const newAst = this.safeParse(newCode)
if (!newAst) return
await this?.engineCommandManager?.waitForReady
if (updateCode) {
if (updates !== 'none') {
this.setCode(recast(ast))
}
this._ast = { ...newAst }
@ -265,22 +270,38 @@ class KclManager {
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
if (updates !== 'codeAndArtifactRanges') return
Object.entries(engineCommandManager.artifactMap).forEach(
([commandId, artifact]) => {
if (!artifact.pathToNode) return
const node = getNodeFromPath<CallExpression>(
kclManager.ast,
artifact.pathToNode,
'CallExpression'
).node
if (node.type !== 'CallExpression') return
const [oldStart, oldEnd] = artifact.range
if (oldStart === 0 && oldEnd === 0) return
if (oldStart === node.start && oldEnd === node.end) return
engineCommandManager.artifactMap[commandId].range = [
node.start,
node.end,
]
}
)
}
async executeCode(code?: string) {
await this.ensureWasmInit()
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
@ -366,25 +387,29 @@ class KclManager {
// 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, true)
await this.executeAstMock(astWithUpdatedSource, { updates: 'code' })
}
return returnVal
}
get defaultPlanes() {
return this?.engineCommandManager?.defaultPlanes
}
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)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, false)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, false)
void 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)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, true)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
}
@ -452,3 +477,13 @@ export function KclContextProvider({
</KclContext.Provider>
)
}
function safeLSGetItem(key: string) {
if (typeof window === 'undefined') return null
return localStorage?.getItem(key)
}
function safteLSSetItem(key: string, value: string) {
if (typeof window === 'undefined') return
localStorage?.setItem(key, value)
}

View File

@ -1653,7 +1653,7 @@ describe('parsing errors', () => {
let _theError
try {
const result = expect(parse(code))
let _ = expect(parse(code))
} catch (e) {
_theError = e
}

View File

@ -22,6 +22,7 @@ show(mySketch001)`
expect(shown).toEqual([
{
type: 'SketchGroup',
on: expect.any(Object),
start: {
to: [0, 0],
from: [0, 0],
@ -33,7 +34,7 @@ show(mySketch001)`
},
value: [
{
type: 'toPoint',
type: 'ToPoint',
name: '',
to: [-1.59, -1.54],
from: [0, 0],
@ -43,7 +44,7 @@ show(mySketch001)`
},
},
{
type: 'toPoint',
type: 'ToPoint',
to: [0.46, -5.82],
from: [-1.59, -1.54],
name: '',
@ -55,8 +56,11 @@ show(mySketch001)`
],
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
id: expect.any(String),
planeId: expect.any(String),
entityId: expect.any(String),
__meta: [{ sourceRange: [46, 71] }],
},
])
@ -85,6 +89,11 @@ show(mySketch001)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
endCapId: null,
startCapId: null,
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
__meta: [{ sourceRange: [46, 71] }],
},
])
@ -127,6 +136,11 @@ show(theExtrude, sk2)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
endCapId: null,
startCapId: null,
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
__meta: [{ sourceRange: [38, 63] }],
},
{
@ -136,6 +150,12 @@ show(theExtrude, sk2)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
endCapId: null,
startCapId: null,
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
__meta: [{ sourceRange: [356, 381] }],
},
])

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