Compare commits

...

69 Commits

Author SHA1 Message Date
d8cc57b843 Cut release v0.17.0 (#1866) 2024-03-25 09:51:17 -04:00
e76db4a621 remove function that's only used once (#1889) 2024-03-25 04:48:51 +00:00
027e947bd5 make setupSketch more composable instead of a monster function (#1879)
* make setupSketch more composable instead of monster function

* clean up
2024-03-25 15:20:43 +11:00
0983dcca22 Add batch support to current KCL implementation (#1871)
* wip

* wip

* updates

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

* more batch shit

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

* fixes

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

* push up mods

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

* go back to batch id

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

* add unlocks back

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

* remove logging

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

* port to wasm

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

* use a trait

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

* updates

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

* snapshojts

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

* artifact map fix

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

* cleanup

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

* remove the blur

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

* hacks on hacks

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

* artifact map clean up

* tweak

* fix so extrudes work

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

* start of id to source range infra

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

* basic map to ids and source ranges

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

* make typescript happy

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

* flush at end of exxecute

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

* small thing for flush

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

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

* trigger ci

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: 49lf <ircsurfer33@gmail.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-23 22:45:55 +00:00
846fc99bbc change KCL Samples link (#1869) 2024-03-22 13:25:59 -07:00
c258ede25c Bump @testing-library/user-event from 14.5.1 to 14.5.2 (#1859)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 14.5.1 to 14.5.2.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.5.1...v14.5.2)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  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-03-22 20:22:26 +00:00
4cc1b3d5ed Bump happy-dom from 14.2.0 to 14.3.1 (#1861)
Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 14.2.0 to 14.3.1.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v14.2.0...v14.3.1)

---
updated-dependencies:
- dependency-name: happy-dom
  dependency-type: direct:development
  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-03-22 13:00:04 -07:00
be0dd1512d Bump @codemirror/autocomplete from 6.10.2 to 6.15.0 (#1862)
Bumps [@codemirror/autocomplete](https://github.com/codemirror/autocomplete) from 6.10.2 to 6.15.0.
- [Changelog](https://github.com/codemirror/autocomplete/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/autocomplete/compare/6.10.2...6.15.0)

---
updated-dependencies:
- dependency-name: "@codemirror/autocomplete"
  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-03-22 12:59:55 -07:00
a5156c3f5d Add *.zoo.dev origins to Tauri HTTP scopes (#1868) 2024-03-22 19:26:52 +00:00
8038b5d7a3 Make engine background color driven by theme setting (#1842)
* Bump ts lib

* Make engine background color driven by theme setting

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

* Force snapshots to dark mode, fix theme init

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

* Don't assume we have engineCommandManager in CameraControls,
because for some reason it's being loaded before engineConnection in test environments?

* Merge branch 'main' into change-bg-color

* Replace optional chaining with this.engineCommandManager

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

* Okay now all snapshot tests are actually in dark mode

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

* trigger ci

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-03-22 09:35:07 -04:00
54b234360e robustening for autocomplete test (#1864) 2024-03-22 07:10:49 +00:00
465d933d53 circular dependencies refactor (#1863)
* circular dependencies refactor

* clean up
2024-03-22 05:55:30 +00:00
ccd0c619a6 Grackle: integrate source ranges (#1852)
Insert KCL source ranges into the KCVM instructions.
2024-03-22 05:16:27 +00:00
7b570bf525 add parsing of types to functions (#1844)
* add parsing of types to functions

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

* add array

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

* fix clippy

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

* add objects

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

* updates

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

* fixes

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

* updates

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

* updates

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

* updates

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

* add return types

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

* fix

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-22 00:14:30 +00:00
44d1c29801 set token when fetching user (#1851)
* set token when fetching user

* clean up
2024-03-21 16:32:41 -07:00
0e916cfd5b SketchOnFace UI (#1664)
* always enter edit mode

* initial blocking of extra code-mirror updates

* dry out code

* rejig selections

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

* clean up

* stream clean up

* update export

* sketch mode can be entered and exited for extrude faces

But has bugs

* startSketchOn working in some cases, editsketch animation working but not orientation of instersection plane etc

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

This reverts commit 406fca4c55.

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

* remove comment

* add sketch on face e2e test

* tweenCamToNegYAxis should respect reduced motion

* initial sketch on face working with test

* remove temporary toolbar button and xState flow

* un-used vars

* snapshot test tweak

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

* type tidy up

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

This reverts commit c39b8ebf95.

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

This reverts commit fecf6f490a.

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

* rename

* sketch on sketch on sketch

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

* typo

* startSketchOn Endcaps

end works, start is weird still

* clear selections for entity_ids that are not recognised

* fix sketch on end cap of second order extrustion

* tiny clean up

* fix sketch on close segment/face

* clean up 'lastCodeMirrorSelectionUpdatedFromScene'

* add code mode test for sketchOnExtrudedFace

* make end cap selection more robust

* update js artifacts for extrudes

* update kcl docs

* clean up

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-22 10:23:04 +11:00
e773e932b0 better error lsp server (#1850)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-21 22:14:55 +00:00
2d39fd32ce Revert "Bump @lezer/javascript from 1.4.9 to 1.4.13 (#1812)" (#1849)
This reverts commit 3e4ce44dc9.
2024-03-21 22:07:27 +00:00
5a585a6c2d Grackle: Fix a clippy lint (#1848) 2024-03-21 16:49:10 -05:00
c09d6ee6bd Bump KCVM, integrating InstructionKind (#1847) 2024-03-21 21:17:48 +00:00
09b55259ab Up cam throttle (#1843)
up cam throttle
2024-03-21 20:53:58 +00:00
68b61c9832 Fix new lints from rust 1.77 (#1845) 2024-03-21 15:41:26 -05:00
469ca94437 Bump react-router-dom from 6.14.2 to 6.22.3 (#1837)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.14.2 to 6.22.3.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.3/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  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-03-21 12:10:00 -07:00
1d3850b46a Bump tailwindcss from 3.3.6 to 3.4.1 (#1835) 2024-03-21 18:46:47 +00:00
0358343285 Bump vite-tsconfig-paths from 4.3.1 to 4.3.2 (#1838) 2024-03-21 07:06:44 +00:00
38b0603fa2 Bump @types/node from 16.18.39 to 18.19.26 (#1834) 2024-03-20 23:45:27 -07:00
e48a8b6c5d Bump @wdio/local-runner from 8.24.3 to 8.35.1 (#1833) 2024-03-20 23:45:17 -07:00
73e573b251 Bump @tauri-apps/api from 1.5.1 to 1.5.3 (#1830) 2024-03-20 23:45:08 -07:00
793409d53d Bump @types/uuid from 9.0.4 to 9.0.8 (#1832) 2024-03-20 23:44:57 -07:00
3e9ab16c4b Bump ts-node from 10.9.1 to 10.9.2 (#1831) 2024-03-20 23:44:48 -07:00
ab226bc86f Bump eslint from 8.53.0 to 8.57.0 (#1826) 2024-03-20 22:55:58 -07:00
97677e4474 Bump typescript from 5.3.2 to 5.4.3 (#1829) 2024-03-20 22:55:42 -07:00
37fbc8c9ab Bump zustand from 4.4.5 to 4.5.2 (#1827) 2024-03-20 22:55:32 -07:00
29d61da552 Bump @types/debounce-promise from 3.1.8 to 3.1.9 (#1823) 2024-03-21 05:34:27 +00:00
ad2f669ec6 Bump autoprefixer from 10.4.14 to 10.4.19 (#1821) 2024-03-21 05:29:39 +00:00
d66aad8b5d Bump @headlessui/react from 1.7.17 to 1.7.18 (#1822) 2024-03-21 05:27:10 +00:00
d8b8710a0d Bump react-hotkeys-hook from 4.4.1 to 4.5.0 (#1824) 2024-03-20 22:24:35 -07:00
8f8ba2dca5 Bump happy-dom from 10.8.0 to 14.2.0 (#1820) 2024-03-20 22:23:32 -07:00
970b0abb54 Bump @tauri-apps/cli from 1.5.6 to 1.5.11 (#1817) 2024-03-21 05:00:39 +00:00
06b464816f Bump husky from 8.0.3 to 9.0.11 (#1819) 2024-03-21 04:40:51 +00:00
08f7bb2811 Bump vite from 5.1.3 to 5.2.2 (#1816) 2024-03-21 04:40:08 +00:00
197df9f25d Bump @uiw/react-codemirror from 4.21.20 to 4.21.24 (#1818) 2024-03-21 04:39:55 +00:00
4d387dfaf7 Bump @types/crypto-js from 4.1.1 to 4.2.2 (#1813) 2024-03-20 21:21:11 -07:00
912b97bea5 Bump @types/react-dom from 18.2.7 to 18.2.22 (#1814) 2024-03-21 04:17:33 +00:00
3e4ce44dc9 Bump @lezer/javascript from 1.4.9 to 1.4.13 (#1812) 2024-03-21 04:14:51 +00:00
c2058a05fa Bump vitest from 1.3.1 to 1.4.0 (#1811) 2024-03-21 04:13:57 +00:00
7a57965690 Bump clap from 4.5.2 to 4.5.3 in /src/wasm-lib (#1805)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.2 to 4.5.3.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.2...v4.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 03:57:58 +00:00
c5b115ba97 Bump tauri-plugin-fs-extra from 160498d to 8cd4a39 in /src-tauri (#1809) 2024-03-21 03:53:13 +00:00
90057c2dda add regression test for angletomatch (#1806) 2024-03-21 03:44:52 +00:00
f3e59690d6 Bump async-trait from 0.1.77 to 0.1.78 in /src/wasm-lib (#1807)
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.77 to 0.1.78.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.77...0.1.78)

---
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-03-21 03:39:46 +00:00
9642a44a02 Bump actions/checkout from 3 to 4 (#1803)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 03:33:24 +00:00
252c7651ac Bump actions/github-script from 6 to 7 (#1804)
Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/github-script
  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-03-21 03:33:09 +00:00
79edcf3826 Bump @wdio/mocha-framework from 8.24.3 to 8.35.0 (#1798)
Bumps [@wdio/mocha-framework](https://github.com/webdriverio/webdriverio/tree/HEAD/packages/wdio-mocha-framework) from 8.24.3 to 8.35.0.
- [Release notes](https://github.com/webdriverio/webdriverio/releases)
- [Changelog](https://github.com/webdriverio/webdriverio/blob/v8.35.0/CHANGELOG.md)
- [Commits](https://github.com/webdriverio/webdriverio/commits/v8.35.0/packages/wdio-mocha-framework)

---
updated-dependencies:
- dependency-name: "@wdio/mocha-framework"
  dependency-type: direct:development
  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-03-21 03:19:52 +00:00
b05ac3a05f fix angleToMatchLengthXY (#1765)
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-03-21 03:14:32 +00:00
1d01ba454b Bump openapitor from 72dcf45 to 8512cea in /src/wasm-lib (#1796)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `72dcf45` to `8512cea`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](72dcf4573b...8512ceae8e)

---
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-03-21 03:05:03 +00:00
bf1d6963fe Bump @types/wicg-file-system-access from 2020.9.6 to 2023.10.5 (#1795)
Bumps [@types/wicg-file-system-access](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/wicg-file-system-access) from 2020.9.6 to 2023.10.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/wicg-file-system-access)

---
updated-dependencies:
- dependency-name: "@types/wicg-file-system-access"
  dependency-type: direct:development
  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-03-20 19:59:48 -07:00
176ee63cb9 Bump web-vitals from 3.5.0 to 3.5.2 (#1797)
Bumps [web-vitals](https://github.com/GoogleChrome/web-vitals) from 3.5.0 to 3.5.2.
- [Changelog](https://github.com/GoogleChrome/web-vitals/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GoogleChrome/web-vitals/compare/v3.5.0...v3.5.2)

---
updated-dependencies:
- dependency-name: web-vitals
  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-03-20 19:57:00 -07:00
1ae8059c2b Bump @wdio/spec-reporter from 8.24.2 to 8.32.4 (#1799)
Bumps [@wdio/spec-reporter](https://github.com/webdriverio/webdriverio/tree/HEAD/packages/wdio-spec-reporter) from 8.24.2 to 8.32.4.
- [Release notes](https://github.com/webdriverio/webdriverio/releases)
- [Changelog](https://github.com/webdriverio/webdriverio/blob/v8.32.4/CHANGELOG.md)
- [Commits](https://github.com/webdriverio/webdriverio/commits/v8.32.4/packages/wdio-spec-reporter)

---
updated-dependencies:
- dependency-name: "@wdio/spec-reporter"
  dependency-type: direct:development
  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-03-20 19:52:47 -07:00
93f406d005 Bump @types/react from 18.2.41 to 18.2.67 (#1800)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.41 to 18.2.67.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  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-03-20 19:44:47 -07:00
e97833f0ed Bump kittycad-execution-plan from 494225a to 80998de in /src/wasm-lib (#1801)
Bump kittycad-execution-plan in /src/wasm-lib

Bumps [kittycad-execution-plan](https://github.com/KittyCAD/modeling-api) from `494225a` to `80998de`.
- [Commits](494225aaac...80998dea69)

---
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-03-20 19:44:25 -07:00
35417dd8a6 Bump syn from 2.0.52 to 2.0.53 in /src/wasm-lib (#1793)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.52 to 2.0.53.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.52...2.0.53)

---
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-03-20 19:35:28 -07:00
cf0560dcfb fix dependabot perms (#1792)
add dependabot to playwright

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-21 00:48:08 +00:00
3659946653 ignore test_copilot_lsp_completions til new model deployed (#1791)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-20 17:40:46 -07:00
156c51484a Bump async-recursion from 1.0.5 to 1.1.0 in /src/wasm-lib (#1775)
Bumps [async-recursion](https://github.com/dcchut/async-recursion) from 1.0.5 to 1.1.0.
- [Release notes](https://github.com/dcchut/async-recursion/releases)
- [Commits](https://github.com/dcchut/async-recursion/compare/v1.0.5...v1.1.0)

---
updated-dependencies:
- dependency-name: async-recursion
  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-03-20 17:07:49 -07:00
dc8dd4bc72 Bump uuid from 1.7.0 to 1.8.0 in /src/wasm-lib (#1776)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.7.0...1.8.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-03-20 17:07:15 -07:00
335add67bd Bump thiserror from 1.0.57 to 1.0.58 in /src/wasm-lib (#1778)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.57 to 1.0.58.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.57...1.0.58)

---
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-03-20 17:06:42 -07:00
231794a69d Bump tauri-plugin-fs-extra from 6db4320 to 160498d in /src-tauri (#1779)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `6db4320` to `160498d`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](6db4320e98...160498dae5)

---
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-03-20 17:05:43 -07:00
8e5a6bc6fc bump kittycad.rs (#1784)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-20 15:39:33 -07:00
4f82121105 Add tangentialArcTo to grackle stdlib (#1731)
* Add tangentialArcTo to grackle stdlib

* Clean up test
2024-03-20 14:54:28 -04:00
191 changed files with 5803 additions and 3324 deletions

View File

@ -16,10 +16,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Comment on PR
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
const message = '`src/lib/exampleKcl.ts` has been updated in this PR, please review and update the `src/routes/onboarding`, if needed.';

View File

@ -9,6 +9,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
jobs:
playwright-ubuntu:
timeout-minutes: 60

View File

@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -76,6 +76,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -244,6 +246,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -76,6 +76,8 @@ startSketchOn('XZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -244,6 +246,8 @@ startSketchOn('XZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -77,6 +77,8 @@ startSketchOn('YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -245,6 +247,8 @@ startSketchOn('YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -82,6 +82,8 @@ const part001 = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -250,6 +252,8 @@ const part001 = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -76,6 +76,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -244,6 +246,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -75,6 +75,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -243,6 +245,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -86,6 +86,8 @@ startSketchOn('-YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -254,6 +256,8 @@ startSketchOn('-YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -79,6 +79,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -247,6 +249,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -71,6 +71,8 @@ const rectangle = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -135,6 +137,8 @@ const rectangle = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -302,6 +306,8 @@ const rectangle = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -72,6 +72,8 @@ startSketchOn('YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -240,6 +242,8 @@ startSketchOn('YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -67,6 +67,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -68,6 +68,8 @@ const square = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -236,6 +238,8 @@ const square = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -403,6 +407,8 @@ const square = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -66,6 +66,8 @@ startSketchOn("YZ")
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -66,6 +66,8 @@ startSketchOn("YZ")
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -66,6 +66,8 @@ startSketchOn('-XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -234,6 +236,8 @@ startSketchOn('-XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -72,6 +72,8 @@ const part = rectShape([0, 0], 20, 20)
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -240,6 +242,8 @@ const part = rectShape([0, 0], 20, 20)
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -80,6 +80,8 @@ const part = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -77,6 +77,8 @@ const part = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -67,6 +67,8 @@ startSketchOn("YZ")
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -67,6 +67,8 @@ startSketchOn("YZ")
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -67,6 +67,8 @@ startSketchOn("YZ")
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -57,6 +57,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -128,6 +130,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -65,6 +65,8 @@ startSketchAt([0, 0])
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -254,6 +254,8 @@ string
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

File diff suppressed because it is too large Load Diff

View File

@ -75,6 +75,8 @@ startSketchOn('-YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -243,6 +245,8 @@ startSketchOn('-YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -65,6 +65,8 @@ startSketchOn('-YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -233,6 +235,8 @@ startSketchOn('-YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -66,6 +66,8 @@ startSketchOn('YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -234,6 +236,8 @@ startSketchOn('YZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -66,6 +66,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -234,6 +236,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -66,6 +66,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -234,6 +236,8 @@ startSketchOn('XY')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

View File

@ -67,6 +67,8 @@ startSketchOn('XZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
@ -235,6 +237,8 @@ startSketchOn('XZ')
},
} |
{
// the face id the sketch is on
faceId: uuid,
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 163 KiB

View File

@ -20,6 +20,8 @@ const commonPoints = {
startAt: '[9.06, -12.22]',
num1: 9.14,
num2: 18.2,
// num1: 9.64,
// num2: 19.19,
}
test.beforeEach(async ({ context, page }) => {
@ -76,6 +78,7 @@ test('Basic sketch', async ({ page }) => {
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await u.closeDebugPanel()
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
@ -86,7 +89,6 @@ test('Basic sketch', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
@ -472,8 +474,18 @@ test('Auto complete works', async ({ page }) => {
const u = getUtils(page)
// const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.setViewportSize({ width: 1200, height: 500 })
const lspStartPromise = page.waitForEvent('console', async (message) => {
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
// but that doesn't seem to make it to the console for macos/safari :(
if (message.text().includes('start kcl lsp')) {
await new Promise((resolve) => setTimeout(resolve, 200))
return true
}
return false
})
await page.goto('/')
await u.waitForAuthSkipAppStart()
await lspStartPromise
// this test might be brittle as we add and remove functions
// but should also be easy to update.
@ -625,7 +637,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
const emptySpaceClick = () =>
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
const topHorzSegmentClick = () =>
page.mouse.click(709, 289).then(() => page.waitForTimeout(100))
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
const bottomHorzSegmentClick = () =>
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
@ -640,13 +652,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.waitForTimeout(700) // wait for animation
const startXPx = 600
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
@ -727,13 +738,18 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await emptySpaceClick()
// select segment in editor than another segment in scene and check there are two cursors
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.waitForTimeout(300)
await page.keyboard.down('Shift')
await expect(page.locator('.cm-cursor')).toHaveCount(1)
// TODO change this back to shift click in the scene, not cmd click in the editor
await bottomHorzSegmentClick()
await page.keyboard.up('Shift')
await expect(page.locator('.cm-cursor')).toHaveCount(1)
await page.keyboard.down(process.platform === 'linux' ? 'Control' : 'Meta')
await page.waitForTimeout(100)
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(500)
await page.keyboard.up(process.platform === 'linux' ? 'Control' : 'Meta')
// clear selection by clicking on nothing
await emptySpaceClick()
@ -918,13 +934,13 @@ test('Can add multiple sketches', async ({ page }) => {
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
const startXPx = 600
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
@ -1372,10 +1388,129 @@ test('Snap to close works (at any scale)', async ({ page }) => {
) => `const part001 = startSketchOn('XZ')
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|> line([${roundOff(scale * 175.36)}, 0], %)
|> line([0, -${roundOff(scale * 175.37) + fudge}], %)
|> line([0, -${roundOff(scale * 175.36) + fudge}], %)
|> close(%)`
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
})
test('Sketch on face', async ({ page, context }) => {
const u = getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
|> startProfileAt([3.29, 7.86], %)
|> line([2.48, 2.44], %)
|> line([2.66, 1.17], %)
|> line([3.75, 0.46], %)
|> line([4.99, -0.46], %)
|> line([3.3, -2.12], %)
|> line([2.16, -3.33], %)
|> line([0.85, -3.08], %)
|> line([-0.18, -3.36], %)
|> line([-3.86, -2.73], %)
|> line([-17.67, 0.85], %)
|> close(%)
|> extrude(5 + 7, %)`
)
})
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
let previousCodeContent = await page.locator('.cm-content').innerText()
await page.mouse.click(793, 133)
const firstClickPosition = [612, 238]
const secondClickPosition = [661, 242]
const thirdClickPosition = [609, 267]
await page.waitForTimeout(300)
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %)
|> line([4.18, -0.35], %)
|> line([-4.44, -2.13], %)
|> close(%)`)
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.updateCamPosition([1049, 239, 686])
await u.closeDebugPanel()
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(200)
const pointToDragFirst = [691, 237]
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
await page.mouse.down()
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
steps: 5,
})
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %)
|> line([2.81, -0.33], %)
|> line([-4.44, -2.13], %)
|> close(%)`)
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
await page.getByRole('button', { name: 'Extrude' }).click()
await expect(page.getByTestId('command-bar')).toBeVisible()
await page.keyboard.press('Enter')
await expect(page.getByText('Confirm Extrude')).toBeVisible()
await page.keyboard.press('Enter')
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %)
|> line([2.81, -0.33], %)
|> line([-4.44, -2.13], %)
|> close(%)
|> extrude(5 + 7, %)`)
})

View File

@ -22,7 +22,7 @@ test.beforeEach(async ({ context, page }) => {
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
theme: 'dark',
unitSystem: 'imperial',
})
)
@ -397,7 +397,7 @@ test('Draft segments should look right', async ({ page, context }) => {
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
theme: 'dark',
unitSystem: 'imperial',
})
)
@ -475,7 +475,7 @@ test('Client side scene scale should match engine scale inch', async ({
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
theme: 'dark',
unitSystem: 'imperial',
})
)
@ -575,7 +575,7 @@ test('Client side scene scale should match engine scale mm', async ({
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
theme: 'dark',
unitSystem: 'metric',
})
)
@ -612,7 +612,7 @@ test('Client side scene scale should match engine scale mm', async ({
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)`)
|> startProfileAt([230.03, -310.32], %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
@ -622,7 +622,7 @@ test('Client side scene scale should match engine scale mm', async ({
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)
|> startProfileAt([230.03, -310.32], %)
|> line([232.2, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
@ -632,7 +632,7 @@ test('Client side scene scale should match engine scale mm', async ({
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)
|> startProfileAt([230.03, -310.32], %)
|> line([232.2, 0], %)
|> tangentialArcTo([694.43, -78.12], %)`)
@ -658,3 +658,48 @@ test('Client side scene scale should match engine scale mm', async ({
maxDiffPixels: 100,
})
})
test('Sketch on face with none z-up', async ({ page, context }) => {
const u = getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
|> startProfileAt([1.4, 2.47], %)
|> line({ to: [9.31, 10.55], tag: 'seg01' }, %)
|> line([11.91, -10.42], %)
|> close(%)
|> extrude(5 + 7, %)
const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-2.89, 1.82], %)
|> line([4.68, 3.05], %)
|> line({ to: [0, -7.79], tag: 'seg02' }, %)
|> close(%)
|> extrude(5 + 7, %)
`
)
})
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
let previousCodeContent = await page.locator('.cm-content').innerText()
// click at 641, 135
await page.mouse.click(641, 135)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(300)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
await page.waitForTimeout(200)
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -1,30 +1,30 @@
{
"name": "untitled-app",
"version": "0.16.0",
"version": "0.17.0",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.10.2",
"@codemirror/autocomplete": "^6.15.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.17",
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.55",
"@kittycad/lib": "^0.1.0",
"@lezer/javascript": "^1.4.9",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.0",
"@tauri-apps/api": "^1.5.1",
"@tauri-apps/api": "^1.5.3",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@testing-library/user-event": "^14.5.2",
"@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",
"@uiw/react-codemirror": "^4.21.20",
"@types/node": "^18.19.26",
"@types/react": "^18.2.67",
"@types/react-dom": "^18.2.22",
"@uiw/react-codemirror": "^4.21.24",
"@xstate/inspect": "^0.8.0",
"@xstate/react": "^3.2.2",
"crypto-js": "^4.2.0",
@ -39,27 +39,27 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-hotkeys-hook": "^4.4.1",
"react-hotkeys-hook": "^4.5.0",
"react-json-view": "^1.21.3",
"react-modal": "^3.16.1",
"react-modal-promise": "^1.0.2",
"react-router-dom": "^6.14.2",
"react-router-dom": "^6.22.3",
"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",
"ts-node": "^10.9.2",
"typescript": "^5.4.3",
"uuid": "^9.0.1",
"vitest": "^1.3.1",
"vitest": "^1.4.0",
"vscode-jsonrpc": "^8.1.0",
"vscode-languageserver-protocol": "^3.17.5",
"wasm-pack": "^0.12.1",
"web-vitals": "^3.5.0",
"web-vitals": "^3.5.2",
"ws": "^8.13.0",
"xstate": "^4.38.2",
"zustand": "^4.4.5"
"zustand": "^4.5.2"
},
"scripts": {
"start": "vite",
@ -110,41 +110,41 @@
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.23.3",
"@playwright/test": "^1.39.0",
"@tauri-apps/cli": "^1.5.6",
"@types/crypto-js": "^4.1.1",
"@types/debounce-promise": "^3.1.8",
"@tauri-apps/cli": "^1.5.11",
"@types/crypto-js": "^4.2.2",
"@types/debounce-promise": "^3.1.9",
"@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/uuid": "^9.0.8",
"@types/wait-on": "^5.3.4",
"@types/wicg-file-system-access": "^2020.9.6",
"@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.5",
"@vitejs/plugin-react": "^4.2.1",
"@wdio/cli": "^8.24.3",
"@wdio/globals": "^8.24.3",
"@wdio/local-runner": "^8.24.3",
"@wdio/mocha-framework": "^8.24.3",
"@wdio/spec-reporter": "^8.24.2",
"@wdio/local-runner": "^8.35.1",
"@wdio/mocha-framework": "^8.35.0",
"@wdio/spec-reporter": "^8.32.4",
"@xstate/cli": "^0.5.17",
"autoprefixer": "^10.4.13",
"eslint": "^8.53.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.12.0",
"happy-dom": "^10.8.0",
"husky": "^8.0.3",
"happy-dom": "^14.3.1",
"husky": "^9.0.11",
"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": "^5.1.3",
"tailwindcss": "^3.4.1",
"vite": "^5.2.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.1",
"vite-tsconfig-paths": "^4.3.2",
"vitest-webgl-canvas-mock": "^1.1.0",
"wait-on": "^7.2.0",
"yarn": "^1.22.19"

6
src-tauri/Cargo.lock generated
View File

@ -1664,9 +1664,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.60"
version = "0.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aa5906d0730bd90f6b3331fe57c04951d00743169a29ee96408767b4060605"
checksum = "949555aa013e1cefa68f59671cfe96602a459c903376c53a410aebf3831d291f"
dependencies = [
"anyhow",
"async-trait",
@ -3876,7 +3876,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs-extra"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#6db4320e98a4278d605dd05d4d87e1433939a7cd"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#8cd4a39864986ae3600be9f23d089afd3988c43a"
dependencies = [
"log",
"serde",

View File

@ -16,7 +16,7 @@ tauri-build = { version = "1.5.1", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.60"
kittycad = "0.2.61"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -7,7 +7,7 @@
},
"package": {
"productName": "zoo-modeling-app",
"version": "0.16.0"
"version": "0.17.0"
},
"tauri": {
"allowlist": {
@ -34,8 +34,11 @@
"request": true,
"scope": [
"https://dev.kittycad.io/*",
"https://dev.zoo.dev/*",
"https://kittycad.io/*",
"https://api.dev.kittycad.io/*"
"https://zoo.dev/*",
"https://api.dev.kittycad.io/*",
"https://api.dev.zoo.dev/*"
]
},
"os": {

View File

@ -28,7 +28,7 @@ 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 { engineCommandManager } from 'lib/singletons'
import { useModelingContext } from 'hooks/useModelingContext'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { isTauri } from 'lib/isTauri'

View File

@ -28,7 +28,7 @@ import {
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
import SettingsAuthProvider from 'components/SettingsAuthProvider'
import LspProvider from 'components/LspProvider'
import { KclContextProvider } from 'lang/KclSingleton'
import { KclContextProvider } from 'lang/KclProvider'
export const BROWSER_FILE_NAME = 'new'

View File

@ -1,12 +1,12 @@
import { WheelEvent, useRef, useMemo } from 'react'
import { isCursorInSketchCommandRange } from 'lang/util'
import { engineCommandManager } from './lang/std/engineConnection'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { useModelingContext } from 'hooks/useModelingContext'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { ActionButton } from 'components/ActionButton'
import usePlatform from 'hooks/usePlatform'
import { isSingleCursorInPipe } from 'lang/queryAst'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import { useKclContext } from 'lang/KclProvider'
import {
NetworkHealthState,
useNetworkStatus,

View File

@ -19,7 +19,7 @@ import {
import {
EngineCommand,
Subscription,
engineCommandManager,
EngineCommandManager,
} from 'lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { deg2Rad } from 'lib/utils2d'
@ -34,10 +34,6 @@ const tempQuaternion = new Quaternion() // just used for maths
type interactionType = 'pan' | 'rotate' | 'zoom'
const throttledEngCmd = throttle((cmd: EngineCommand) => {
engineCommandManager.sendSceneCommand(cmd)
}, 1000 / 15)
interface ThreeCamValues {
position: Vector3
quaternion: Quaternion
@ -62,68 +58,8 @@ export type ReactCameraProperties =
const lastCmdDelay = 50
const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
const cmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
...convertThreeCamValuesToEngineCam(threeValues),
},
}
engineCommandManager.sendSceneCommand(cmd)
}, 1000 / 15)
let lastPerspectiveCmd: EngineCommand | null = null
let lastPerspectiveCmdTime: number = Date.now()
let lastPerspectiveCmdTimeoutId: number | null = null
const sendLastPerspectiveReliableChannel = () => {
if (
lastPerspectiveCmd &&
Date.now() - lastPerspectiveCmdTime >= lastCmdDelay
) {
engineCommandManager.sendSceneCommand(lastPerspectiveCmd, true)
lastPerspectiveCmdTime = Date.now()
}
}
const throttledUpdateEngineFov = throttle(
(vals: {
position: Vector3
quaternion: Quaternion
zoom: number
fov: number
target: Vector3
}) => {
const cmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_perspective_settings',
...convertThreeCamValuesToEngineCam({
...vals,
isPerspective: true,
}),
fov_y: vals.fov,
...calculateNearFarFromFOV(vals.fov),
},
}
engineCommandManager.sendSceneCommand(cmd)
lastPerspectiveCmd = cmd
lastPerspectiveCmdTime = Date.now()
if (lastPerspectiveCmdTimeoutId !== null) {
clearTimeout(lastPerspectiveCmdTimeoutId)
}
lastPerspectiveCmdTimeoutId = setTimeout(
sendLastPerspectiveReliableChannel,
lastCmdDelay
) as any as number
},
1000 / 30
)
export class CameraControls {
engineCommandManager: EngineCommandManager
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
camera: PerspectiveCamera | OrthographicCamera
target: Vector3
@ -213,7 +149,77 @@ export class CameraControls {
this.update(true)
}
constructor(isOrtho = false, domElement: HTMLCanvasElement) {
throttledEngCmd = throttle((cmd: EngineCommand) => {
this.engineCommandManager.sendSceneCommand(cmd)
}, 1000 / 30)
throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
const cmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
...convertThreeCamValuesToEngineCam(threeValues),
},
}
this.engineCommandManager.sendSceneCommand(cmd)
}, 1000 / 15)
lastPerspectiveCmd: EngineCommand | null = null
lastPerspectiveCmdTime: number = Date.now()
lastPerspectiveCmdTimeoutId: number | null = null
sendLastPerspectiveReliableChannel = () => {
if (
this.lastPerspectiveCmd &&
Date.now() - this.lastPerspectiveCmdTime >= lastCmdDelay
) {
this.engineCommandManager.sendSceneCommand(this.lastPerspectiveCmd, true)
this.lastPerspectiveCmdTime = Date.now()
}
}
throttledUpdateEngineFov = throttle(
(vals: {
position: Vector3
quaternion: Quaternion
zoom: number
fov: number
target: Vector3
}) => {
const cmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_perspective_settings',
...convertThreeCamValuesToEngineCam({
...vals,
isPerspective: true,
}),
fov_y: vals.fov,
...calculateNearFarFromFOV(vals.fov),
},
}
this.engineCommandManager.sendSceneCommand(cmd)
this.lastPerspectiveCmd = cmd
this.lastPerspectiveCmdTime = Date.now()
if (this.lastPerspectiveCmdTimeoutId !== null) {
clearTimeout(this.lastPerspectiveCmdTimeoutId)
}
this.lastPerspectiveCmdTimeoutId = setTimeout(
this.sendLastPerspectiveReliableChannel,
lastCmdDelay
) as any as number
},
1000 / 30
)
constructor(
isOrtho = false,
domElement: HTMLCanvasElement,
engineCommandManager: EngineCommandManager
) {
this.engineCommandManager = engineCommandManager
this.camera = isOrtho ? new OrthographicCamera() : new PerspectiveCamera()
this.camera.up.set(0, 0, 1)
this.camera.far = 20000
@ -259,15 +265,15 @@ export class CameraControls {
this.onCameraChange()
}
setTimeout(() => {
engineCommandManager.subscribeTo({
this.engineCommandManager.subscribeTo({
event: 'camera_drag_end',
callback: cb,
})
engineCommandManager.subscribeTo({
this.engineCommandManager.subscribeTo({
event: 'default_camera_zoom',
callback: cb,
})
engineCommandManager.subscribeTo({
this.engineCommandManager.subscribeTo({
event: 'default_camera_get_settings',
callback: cb,
})
@ -310,7 +316,7 @@ export class CameraControls {
this.handleStart()
if (this.syncDirection === 'engineToClient') {
void engineCommandManager.sendSceneCommand({
void this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_start',
@ -334,7 +340,7 @@ export class CameraControls {
if (interaction === 'none') return
if (this.syncDirection === 'engineToClient') {
throttledEngCmd({
this.throttledEngCmd({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_move',
@ -377,7 +383,7 @@ export class CameraControls {
if (this.syncDirection === 'engineToClient') {
const interaction = this.getInteractionType(event)
if (interaction === 'none') return
void engineCommandManager.sendSceneCommand({
void this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_end',
@ -401,7 +407,7 @@ export class CameraControls {
this.handleEnd()
return
}
throttledEngCmd({
this.throttledEngCmd({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
@ -425,6 +431,7 @@ export class CameraControls {
if (this.camera instanceof OrthographicCamera) return
const { x: px, y: py, z: pz } = this.camera.position
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
const oldCamUp = this.camera.up.clone()
const aspect = window.innerWidth / window.innerHeight
this.lastPerspectiveFov = this.camera.fov
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
@ -436,7 +443,8 @@ export class CameraControls {
z_near,
z_far
)
this.camera.up.set(0, 0, 1)
this.camera.up.copy(oldCamUp)
this.camera.layers.enable(SKETCH_LAYER)
if (DEBUG_SHOW_INTERSECTION_PLANE)
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
@ -447,7 +455,7 @@ export class CameraControls {
this.camera.quaternion.set(qx, qy, qz, qw)
this.camera.updateProjectionMatrix()
engineCommandManager.sendSceneCommand({
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
@ -458,13 +466,14 @@ export class CameraControls {
}
private createPerspectiveCamera = () => {
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
const previousCamUp = this.camera.up.clone()
this.camera = new PerspectiveCamera(
this.lastPerspectiveFov,
window.innerWidth / window.innerHeight,
z_near,
z_far
)
this.camera.up.set(0, 0, 1)
this.camera.up.copy(previousCamUp)
this.camera.layers.enable(SKETCH_LAYER)
if (DEBUG_SHOW_INTERSECTION_PLANE)
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
@ -490,7 +499,7 @@ export class CameraControls {
}
usePerspectiveCamera = () => {
this._usePerspectiveCamera()
engineCommandManager.sendSceneCommand({
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
@ -558,7 +567,7 @@ export class CameraControls {
this.camera.near = z_near
this.camera.far = z_far
throttledUpdateEngineFov({
this.throttledUpdateEngineFov({
fov: newFov,
position: newPosition,
quaternion: this.camera.quaternion,
@ -618,7 +627,7 @@ export class CameraControls {
didChange = true
}
this.safeLookAtTarget()
this.safeLookAtTarget(this.camera.up)
// Update the camera's matrices
this.camera.updateMatrixWorld()
@ -683,48 +692,48 @@ export class CameraControls {
targetAngle = -Math.PI / 2,
duration = 500
): Promise<void> {
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
// zPosition should stay the same
const xyRadius = Math.sqrt(
(this.target.x - this.camera.position.x) ** 2 +
(this.target.y - this.camera.position.y) ** 2
)
const xyAngle = Math.atan2(
this.camera.position.y - this.target.y,
this.camera.position.x - this.target.x
)
this._isCamMovingCallback(true, true)
return new Promise((resolve) => {
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
// zPosition should stay the same
const xyRadius = Math.sqrt(
(this.target.x - this.camera.position.x) ** 2 +
(this.target.y - this.camera.position.y) ** 2
)
const xyAngle = Math.atan2(
this.camera.position.y - this.target.y,
this.camera.position.x - this.target.x
)
const camAtTime = (obj: { angle: number }) => {
const x = xyRadius * Math.cos(obj.angle)
const y = xyRadius * Math.sin(obj.angle)
this.camera.position.set(
this.target.x + x,
this.target.y + y,
this.camera.position.z
)
this.update()
this.onCameraChange()
}
const onComplete = (obj: { angle: number }) => {
camAtTime(obj)
this._isCamMovingCallback(false, true)
// resolve after a couple of frames
requestAnimationFrame(() => {
requestAnimationFrame(() => resolve())
})
}
this._isCamMovingCallback(true, true)
if (isReducedMotion()) {
onComplete({ angle: targetAngle })
return
}
new TWEEN.Tween({ angle: xyAngle })
.to({ angle: targetAngle }, duration)
.onUpdate((obj) => {
const x = xyRadius * Math.cos(obj.angle)
const y = xyRadius * Math.sin(obj.angle)
this.camera.position.set(
this.target.x + x,
this.target.y + y,
this.camera.position.z
)
this.update()
this.onCameraChange()
})
.onComplete((obj) => {
const x = xyRadius * Math.cos(obj.angle)
const y = xyRadius * Math.sin(obj.angle)
this.camera.position.set(
this.target.x + x,
this.target.y + y,
this.camera.position.z
)
this.update()
this.onCameraChange()
this._isCamMovingCallback(false, true)
// resolve after a couple of frames
requestAnimationFrame(() => {
requestAnimationFrame(() => resolve())
})
})
.onUpdate(camAtTime)
.onComplete(onComplete)
.start()
})
}
@ -778,6 +787,8 @@ export class CameraControls {
targetQuaternion,
animationProgress
)
const up = new Vector3(0, 0, 1).applyQuaternion(currentQ)
this.camera.up.copy(up)
const currentTarget = tempVec.lerpVectors(
initialTarget,
targetPosition,
@ -802,7 +813,7 @@ export class CameraControls {
const onComplete = async () => {
if (isReducedMotion() && toOrthographic) {
cameraAtTime(0.99)
cameraAtTime(0.9999)
this.useOrthographicCamera()
} else if (toOrthographic) {
await this.animateToOrthographic()
@ -863,37 +874,40 @@ export class CameraControls {
animateFovChange() // Start the animation
})
animateToPerspective = () =>
animateToPerspective = (targetCamUp = new Vector3(0, 0, 1)) =>
new Promise((resolve) => {
if (this.syncDirection === 'engineToClient')
if (this.syncDirection === 'engineToClient') {
console.warn(
'animate To Perspective not design to work with engineToClient syncDirection.'
)
}
this.isFovAnimationInProgress = true
// Immediately set the camera to perspective with a very low FOV
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
this.lastPerspectiveFov = 4
let currentFov = 4
this.camera.updateProjectionMatrix()
const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN
const initialCameraUp = this.camera.up.clone()
this.usePerspectiveCamera()
const tempVec = new Vector3()
const animateFovChange = () => {
if (this.camera instanceof OrthographicCamera) return
if (this.camera.fov < targetFov) {
// Increase the FOV
currentFov = Math.min(currentFov + fovAnimationStep, targetFov)
// this.camera.fov = currentFov
this.camera.updateProjectionMatrix()
this.dollyZoom(currentFov)
requestAnimationFrame(animateFovChange) // Continue the animation
} else {
// Set the flag to false as the FOV animation is complete
this.isFovAnimationInProgress = false
resolve(true)
}
const cameraAtTime = (t: number) => {
currentFov =
this.lastPerspectiveFov + (targetFov - this.lastPerspectiveFov) * t
const currentUp = tempVec.lerpVectors(initialCameraUp, targetCamUp, t)
this.camera.up.copy(currentUp)
this.dollyZoom(currentFov)
}
animateFovChange() // Start the animation
const onComplete = () => {
this.isFovAnimationInProgress = false
resolve(true)
}
new TWEEN.Tween({ t: 0 })
.to({ t: 1 }, isReducedMotion() ? 50 : FRAMES_TO_ANIMATE_IN * 16) // Assuming 60fps, hence 16ms per frame
.easing(TWEEN.Easing.Quadratic.InOut)
.onUpdate(({ t }) => cameraAtTime(t))
.onComplete(onComplete)
.start()
})
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
@ -916,7 +930,7 @@ export class CameraControls {
}
if (this.syncDirection === 'clientToEngine' || forceUpdate)
throttledUpdateEngineCamera({
this.throttledUpdateEngineCamera({
quaternion: this.camera.quaternion,
position: this.camera.position,
zoom: this.camera.zoom,

View File

@ -4,9 +4,10 @@ import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useStore } from 'useStore'
import { DEBUG_SHOW_BOTH_SCENES, sceneInfra } from './sceneInfra'
import { DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
import { ReactCameraProperties } from './CameraControls'
import { throttle } from 'lib/utils'
import { sceneInfra } from 'lib/singletons'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false)

View File

@ -40,3 +40,12 @@ export function isQuaternionVertical(q: Quaternion) {
// no x or y components means it's vertical
return compareVec2Epsilon2([v.x, v.y], [0, 0])
}
export function quaternionFromUpNForward(up: Vector3, forward: Vector3) {
const dummyCam = new PerspectiveCamera()
dummyCam.up.copy(up)
dummyCam.position.copy(forward)
dummyCam.lookAt(0, 0, 0)
dummyCam.updateMatrix()
return dummyCam.quaternion.clone()
}

View File

@ -5,7 +5,6 @@ import {
Group,
Intersection,
LineCurve3,
Matrix4,
Mesh,
MeshBasicMaterial,
Object3D,
@ -29,7 +28,6 @@ import {
INTERSECTION_PLANE_LAYER,
OnMouseEnterLeaveArgs,
RAYCASTABLE_PLANE,
sceneInfra,
SKETCH_GROUP_SEGMENTS,
SKETCH_LAYER,
X_AXIS,
@ -37,7 +35,7 @@ import {
Y_AXIS,
YZ_PLANE,
} from './sceneInfra'
import { isQuaternionVertical } from './helpers'
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
import {
CallExpression,
getTangentialArcToInfo,
@ -53,10 +51,9 @@ import {
VariableDeclaration,
VariableDeclarator,
} from 'lang/wasm'
import { kclManager } from 'lang/KclSingleton'
import { engineCommandManager, kclManager, sceneInfra } from 'lib/singletons'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst } from 'useStore'
import { engineCommandManager } from 'lang/std/engineConnection'
import { executeAst, useStore } from 'useStore'
import {
createArcGeometry,
dashedStraight,
@ -70,16 +67,23 @@ import {
changeSketchArguments,
updateStartProfileAtArgs,
} from 'lang/std/sketch'
import { isReducedMotion, throttle } from 'lib/utils'
import { throttle } from 'lib/utils'
import {
createArrayExpression,
createCallExpressionStdLib,
createLiteral,
createPipeSubstitution,
} from 'lang/modifyAst'
import { getEventForSegmentSelection } from 'lib/selections'
import {
getEventForSegmentSelection,
sendSelectEventToEngine,
} from 'lib/selections'
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { createGridHelper, orthoScale, perspScale } from './helpers'
import { Models } from '@kittycad/lib'
import { v4 as uuidv4 } from 'uuid'
import { SketchDetails } from 'machines/modelingMachine'
import { EngineCommandManager } from 'lang/std/engineConnection'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -95,14 +99,16 @@ export const PROFILE_START = 'profile-start'
// This singleton Class is responsible for all of the things the user sees and interacts with.
// That mostly mean sketch elements.
// Cameras, controls, raycasters, etc are handled by sceneInfra
class SceneEntities {
export class SceneEntities {
engineCommandManager: EngineCommandManager
scene: Scene
sceneProgramMemory: ProgramMemory = { root: {}, return: null }
activeSegments: { [key: string]: Group } = {}
intersectionPlane: Mesh | null = null
axisGroup: Group | null = null
currentSketchQuaternion: Quaternion | null = null
constructor() {
constructor(engineCommandManager: EngineCommandManager) {
this.engineCommandManager = engineCommandManager
this.scene = sceneInfra?.scene
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
}
@ -164,7 +170,7 @@ class SceneEntities {
console.warn('createIntersectionPlane called when it already exists')
return
}
const hundredM = 1000000
const hundredM = 100_0000
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
const planeMaterial = new MeshBasicMaterial({
color: 0xff0000,
@ -178,7 +184,12 @@ class SceneEntities {
this.intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER)
this.scene.add(this.intersectionPlane)
}
createSketchAxis(sketchPathToNode: PathToNode) {
createSketchAxis(
sketchPathToNode: PathToNode,
forward: [number, number, number],
up: [number, number, number],
sketchPosition?: [number, number, number]
) {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const baseXColor = 0x000055
const baseYColor = 0x550000
@ -238,14 +249,12 @@ class SceneEntities {
child.layers.set(SKETCH_LAYER)
})
const quat = quaternionFromSketchGroup(
sketchGroupFromPathToNode({
pathToNode: sketchPathToNode,
ast: kclManager.ast,
programMemory: kclManager.programMemory,
})
const quat = quaternionFromUpNForward(
new Vector3(...up),
new Vector3(...forward)
)
this.axisGroup.setRotationFromQuaternion(quat)
sketchPosition && this.axisGroup.position.set(...sketchPosition)
this.scene.add(this.axisGroup)
}
removeIntersectionPlane() {
@ -255,27 +264,33 @@ class SceneEntities {
async setupSketch({
sketchPathToNode,
ast,
// is draft line assumes the last segment is a draft line, and mods it as the user moves the mouse
draftSegment,
forward,
up,
position,
maybeModdedAst,
draftExpressionsIndices,
}: {
sketchPathToNode: PathToNode
ast?: Program
draftSegment?: DraftSegment
}) {
maybeModdedAst: Program
draftExpressionsIndices?: { start: number; end: number }
forward: [number, number, number]
up: [number, number, number]
position?: [number, number, number]
}): Promise<{
truncatedAst: Program
programMemoryOverride: ProgramMemory
sketchGroup: SketchGroup
variableDeclarationName: string
}> {
sceneInfra.resetMouseListeners()
this.createIntersectionPlane()
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
this.prepareTruncatedMemoryAndAst(
sketchPathToNode || [],
kclManager.ast,
draftSegment
)
this.prepareTruncatedMemoryAndAst(sketchPathToNode || [], maybeModdedAst)
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
const sketchGroup = sketchGroupFromPathToNode({
@ -283,9 +298,16 @@ class SceneEntities {
ast: kclManager.ast,
programMemory,
})
if (!Array.isArray(sketchGroup?.value)) return
if (!Array.isArray(sketchGroup?.value))
return {
truncatedAst,
programMemoryOverride,
sketchGroup,
variableDeclarationName,
}
this.sceneProgramMemory = programMemory
const group = new Group()
position && group.position.set(...position)
group.userData = {
type: SKETCH_GROUP_SEGMENTS,
pathToNode: sketchPathToNode,
@ -325,7 +347,10 @@ class SceneEntities {
kclManager.ast,
segment.__geoMeta.sourceRange
)
if (draftSegment && (sketchGroup.value[index - 1] || sketchGroup.start)) {
if (
draftExpressionsIndices &&
(sketchGroup.value[index - 1] || sketchGroup.start)
) {
const previousSegment =
sketchGroup.value[index - 1] || sketchGroup.start
const previousSegmentPathToNode = getNodePathFromSourceRange(
@ -340,7 +365,9 @@ class SceneEntities {
segPathToNode[1][0] = bodyIndex
}
const isDraftSegment =
draftSegment && index === sketchGroup.value.length - 1
draftExpressionsIndices &&
index <= draftExpressionsIndices.end &&
index >= draftExpressionsIndices.start
let seg
const callExpName = getNodeFromPath<CallExpression>(
kclManager.ast,
@ -377,122 +404,182 @@ class SceneEntities {
this.activeSegments[JSON.stringify(segPathToNode)] = seg
})
this.currentSketchQuaternion = quaternionFromSketchGroup(sketchGroup)
this.currentSketchQuaternion = quaternionFromUpNForward(
new Vector3(...up),
new Vector3(...forward)
)
group.setRotationFromQuaternion(this.currentSketchQuaternion)
this.intersectionPlane &&
this.intersectionPlane.setRotationFromQuaternion(
this.currentSketchQuaternion
)
this.intersectionPlane &&
position &&
this.intersectionPlane.position.set(...position)
this.scene.add(group)
if (!draftSegment) {
sceneInfra.setCallbacks({
onDrag: ({ selected, intersectionPoint, mouseEvent, intersects }) => {
if (mouseEvent.which !== 1) return
this.onDragSegment({
object: selected,
intersection2d: intersectionPoint.twoD,
intersects,
sketchPathToNode,
})
},
onMove: () => {},
onClick: (args) => {
if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) {
sceneInfra.modelingSend({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
},
})
return
}
const { selected } = args
const event = getEventForSegmentSelection(selected)
if (!event) return
sceneInfra.modelingSend(event)
},
...mouseEnterLeaveCallbacks(),
})
} else {
sceneInfra.setCallbacks({
onClick: async (args) => {
if (!args) return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
let intersection2d = intersectionPoint?.twoD
const profileStart = args.intersects
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
.find((a) => a?.name === PROFILE_START)
let modifiedAst
if (profileStart) {
modifiedAst = addCloseToPipe({
node: kclManager.ast,
programMemory: kclManager.programMemory,
pathToNode: sketchPathToNode,
})
} else if (intersection2d) {
const lastSegment = sketchGroup.value.slice(-1)[0]
modifiedAst = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [intersection2d.x, intersection2d.y],
from: [lastSegment.to[0], lastSegment.to[1]],
fnName:
lastSegment.type === 'TangentialArcTo'
? 'tangentialArcTo'
: 'line',
pathToNode: sketchPathToNode,
}).modifiedAst
} else {
// return early as we didn't modify the ast
return
}
kclManager.executeAstMock(modifiedAst, { updates: 'code' })
await this.tearDownSketch({ removeAxis: false })
this.setupSketch({ sketchPathToNode, draftSegment })
},
onMove: (args) => {
this.onDragSegment({
intersection2d: args.intersectionPoint.twoD,
object: Object.values(this.activeSegments).slice(-1)[0],
intersects: args.intersects,
sketchPathToNode,
draftInfo: {
draftSegment,
truncatedAst,
programMemoryOverride,
variableDeclarationName,
},
})
},
...mouseEnterLeaveCallbacks(),
})
}
sceneInfra.camControls.enableRotate = false
return {
truncatedAst,
programMemoryOverride,
sketchGroup,
variableDeclarationName,
}
}
updateAstAndRejigSketch = async (
sketchPathToNode: PathToNode,
modifiedAst: Program
modifiedAst: Program,
forward: [number, number, number],
up: [number, number, number],
origin: [number, number, number]
) => {
await kclManager.updateAst(modifiedAst, false)
await this.tearDownSketch({ removeAxis: false })
this.setupSketch({ sketchPathToNode })
await this.setupSketch({
sketchPathToNode,
forward,
up,
position: origin,
maybeModdedAst: kclManager.ast,
})
this.setupSketchIdleCallbacks(sketchPathToNode)
}
setUpDraftArc = async (sketchPathToNode: PathToNode) => {
await this.tearDownSketch({ removeAxis: false })
await new Promise((resolve) => setTimeout(resolve, 100))
this.setupSketch({ sketchPathToNode, draftSegment: 'tangentialArcTo' })
setUpDraftSegment = async (
sketchPathToNode: PathToNode,
forward: [number, number, number],
up: [number, number, number],
origin: [number, number, number],
segmentName: 'line' | 'tangentialArcTo' = 'line',
shouldTearDown = true
) => {
const _ast = JSON.parse(JSON.stringify(kclManager.ast))
const variableDeclarationName =
getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)?.node?.declarations?.[0]?.id?.name || ''
const sg = kclManager.programMemory.root[
variableDeclarationName
] as SketchGroup
const lastSeg = sg.value.slice(-1)[0] || sg.start
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
let modifiedAst = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [lastSeg.to[0], lastSeg.to[1]],
from: [lastSeg.to[0], lastSeg.to[1]],
fnName: segmentName,
pathToNode: sketchPathToNode,
}).modifiedAst
modifiedAst = parse(recast(modifiedAst))
const draftExpressionsIndices = { start: index, end: index }
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
const { truncatedAst, programMemoryOverride, sketchGroup } =
await this.setupSketch({
sketchPathToNode,
forward,
up,
position: origin,
maybeModdedAst: modifiedAst,
draftExpressionsIndices,
})
sceneInfra.setCallbacks({
onClick: async (args) => {
if (!args) return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
let intersection2d = intersectionPoint?.twoD
const profileStart = args.intersects
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
.find((a) => a?.name === PROFILE_START)
let modifiedAst
if (profileStart) {
modifiedAst = addCloseToPipe({
node: kclManager.ast,
programMemory: kclManager.programMemory,
pathToNode: sketchPathToNode,
})
} else if (intersection2d) {
const lastSegment = sketchGroup.value.slice(-1)[0]
modifiedAst = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [intersection2d.x, intersection2d.y],
from: [lastSegment.to[0], lastSegment.to[1]],
fnName:
lastSegment.type === 'TangentialArcTo'
? 'tangentialArcTo'
: 'line',
pathToNode: sketchPathToNode,
}).modifiedAst
} else {
// return early as we didn't modify the ast
return
}
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
this.setUpDraftSegment(
sketchPathToNode,
forward,
up,
origin,
segmentName
)
},
onMove: (args) => {
this.onDragSegment({
intersection2d: args.intersectionPoint.twoD,
object: Object.values(this.activeSegments).slice(-1)[0],
intersects: args.intersects,
sketchPathToNode,
draftInfo: {
truncatedAst,
programMemoryOverride,
variableDeclarationName,
},
})
},
...mouseEnterLeaveCallbacks(),
})
}
setUpDraftLine = async (sketchPathToNode: PathToNode) => {
await this.tearDownSketch({ removeAxis: false })
await new Promise((resolve) => setTimeout(resolve, 100))
this.setupSketch({ sketchPathToNode, draftSegment: 'line' })
setupSketchIdleCallbacks = (pathToNode: PathToNode) => {
sceneInfra.setCallbacks({
onDrag: ({ selected, intersectionPoint, mouseEvent, intersects }) => {
if (mouseEvent.which !== 1) return
this.onDragSegment({
object: selected,
intersection2d: intersectionPoint.twoD,
intersects,
sketchPathToNode: pathToNode,
})
},
onMove: () => {},
onClick: (args) => {
if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) {
sceneInfra.modelingSend({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
},
})
return
}
const { selected } = args
const event = getEventForSegmentSelection(selected)
if (!event) return
sceneInfra.modelingSend(event)
},
...mouseEnterLeaveCallbacks(),
})
}
onDraftLineMouseMove = () => {}
prepareTruncatedMemoryAndAst = (
sketchPathToNode: PathToNode,
ast?: Program,
@ -516,7 +603,6 @@ class SceneEntities {
sketchPathToNode: PathToNode
intersects: Intersection<Object3D<Object3DEventMap>>[]
draftInfo?: {
draftSegment: DraftSegment
truncatedAst: Program
programMemoryOverride: ProgramMemory
variableDeclarationName: string
@ -595,7 +681,7 @@ class SceneEntities {
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: engineCommandManager,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
@ -785,10 +871,10 @@ class SceneEntities {
}
}
async animateAfterSketch() {
if (isReducedMotion()) {
sceneInfra.camControls.usePerspectiveCamera()
return
}
// if (isReducedMotion()) {
// sceneInfra.camControls.usePerspectiveCamera()
// return
// }
await sceneInfra.camControls.animateToPerspective()
}
removeSketchGrid() {
@ -853,26 +939,81 @@ class SceneEntities {
const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type)
},
onClick: (args) => {
onClick: async (args) => {
const checkExtrudeFaceClick = async (): Promise<boolean> => {
const { streamDimensions } = useStore.getState()
const { entity_id } = await sendSelectEventToEngine(
args?.mouseEvent,
document.getElementById('video-stream') as HTMLVideoElement,
streamDimensions
)
if (!entity_id) return false
const artifact = this.engineCommandManager.artifactMap[entity_id]
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
return false
const faceInfo: Models['FaceIsPlanar_type'] = (
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'face_is_planar',
object_id: entity_id,
},
})
)?.data?.data
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
return false
const { z_axis, origin, y_axis } = faceInfo
const pathToNode = getNodePathFromSourceRange(
kclManager.ast,
artifact.range
)
sceneInfra.modelingSend({
type: 'Select default plane',
data: {
type: 'extrudeFace',
zAxis: [z_axis.x, z_axis.y, z_axis.z],
yAxis: [y_axis.x, y_axis.y, y_axis.z],
position: [origin.x, origin.y, origin.z].map(
(num) => num / sceneInfra._baseUnitMultiplier
) as [number, number, number],
extrudeSegmentPathToNode: pathToNode,
cap:
artifact?.additionalData?.type === 'cap'
? artifact.additionalData.info
: 'none',
},
})
return true
}
if (await checkExtrudeFaceClick()) return
if (!args || !args.intersects?.[0]) return
if (args.mouseEvent.which !== 1) return
const { intersects } = args
const type = intersects?.[0].object.name || ''
const posNorm = Number(intersects?.[0]?.normal?.z) > 0
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
let zAxis: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
let yAxis: [number, number, number] = [0, 1, 0]
if (type === YZ_PLANE) {
planeString = posNorm ? 'YZ' : '-YZ'
normal = posNorm ? [1, 0, 0] : [-1, 0, 0]
zAxis = posNorm ? [1, 0, 0] : [-1, 0, 0]
yAxis = [0, 0, 1]
} else if (type === XZ_PLANE) {
planeString = posNorm ? 'XZ' : '-XZ'
normal = posNorm ? [0, 1, 0] : [0, -1, 0]
zAxis = posNorm ? [0, 1, 0] : [0, -1, 0]
yAxis = [0, 0, 1]
}
sceneInfra.modelingSend({
type: 'Select default plane',
data: {
type: 'defaultPlane',
plane: planeString,
normal,
zAxis,
yAxis,
},
})
},
@ -882,8 +1023,6 @@ class SceneEntities {
export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ'
export const sceneEntitiesManager = new SceneEntities()
// calculations/pure-functions/easy to test so no excuse not to
function prepareTruncatedMemoryAndAst(
@ -1005,30 +1144,6 @@ export function sketchGroupFromPathToNode({
return programMemory.root[varDec?.id?.name || ''] as SketchGroup
}
export function quaternionFromSketchGroup(
sketchGroup: SketchGroup
): Quaternion {
// TODO figure what is happening in the executor that it's some times returning
// [x,y,z] and sometimes {x,y,z}
if (!sketchGroup?.zAxis) {
// sometimes sketchGroup is undefined,
// I don't quiet understand the circumstances yet
// and it's very intermittent so leaving this here for now
console.log('no zAxis', sketchGroup)
console.trace('no zAxis')
}
const zAxisVec = massageFormats(sketchGroup?.zAxis)
const yAxisVec = massageFormats(sketchGroup?.yAxis)
const xAxisVec = new Vector3().crossVectors(yAxisVec, zAxisVec).normalize()
let yAxisVecNormalized = yAxisVec.clone().normalize()
let zAxisVecNormalized = zAxisVec.clone().normalize()
let rotationMatrix = new Matrix4()
rotationMatrix.makeBasis(xAxisVec, yAxisVecNormalized, zAxisVecNormalized)
return new Quaternion().setFromRotationMatrix(rotationMatrix)
}
function colorSegment(object: any, color: number) {
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
if (segmentHead) {
@ -1063,10 +1178,68 @@ export function getSketchQuaternion(
programMemory: kclManager.programMemory,
})
const zAxis = sketchGroup?.zAxis || sketchNormalBackUp
return getQuaternionFromZAxis(massageFormats(zAxis))
}
export async function getSketchOrientationDetails(
sketchPathToNode: PathToNode
): Promise<{
quat: Quaternion
sketchDetails: SketchDetails
}> {
const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode,
ast: kclManager.ast,
programMemory: kclManager.programMemory,
})
if (sketchGroup.on.type === 'plane') {
const zAxis = sketchGroup?.zAxis
return {
quat: getQuaternionFromZAxis(massageFormats(zAxis)),
sketchDetails: {
sketchPathToNode,
zAxis: [zAxis.x, zAxis.y, zAxis.z],
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
origin: [0, 0, 0],
},
}
}
if (sketchGroup.on.type === 'face') {
const faceInfo: Models['FaceIsPlanar_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'face_is_planar',
object_id: sketchGroup.on.faceId,
},
})
)?.data?.data
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
throw new Error('faceInfo')
const { z_axis, y_axis, origin } = faceInfo
const quaternion = quaternionFromUpNForward(
new Vector3(y_axis.x, y_axis.y, y_axis.z),
new Vector3(z_axis.x, z_axis.y, z_axis.z)
)
return {
quat: quaternion,
sketchDetails: {
sketchPathToNode,
zAxis: [z_axis.x, z_axis.y, z_axis.z],
yAxis: [y_axis.x, y_axis.y, y_axis.z],
origin: [origin.x, origin.y, origin.z],
},
}
}
throw new Error(
'sketchGroup.on.type not recognized, has a new type been added?'
)
}
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
const dummyCam = new PerspectiveCamera()
dummyCam.up.set(0, 0, 1)
const _zAxis = massageFormats(zAxis)
dummyCam.position.copy(_zAxis)
dummyCam.position.copy(zAxis)
dummyCam.lookAt(0, 0, 0)
dummyCam.updateMatrix()
const quaternion = dummyCam.quaternion.clone()
@ -1075,7 +1248,7 @@ export function getSketchQuaternion(
// because vertical quaternions are a gimbal lock, for the orbit controls
// it's best to set them explicitly to the vertical position with a known good camera up
if (isVert && _zAxis.z < 0) {
if (isVert && zAxis.z < 0) {
quaternion.set(0, 1, 0, 0)
} else if (isVert) {
quaternion.set(0, 0, 0, 1)

View File

@ -27,6 +27,7 @@ import { Axis } from 'lib/selections'
import { type BaseUnit } from 'lib/settings/settingsTypes'
import { SETTINGS_PERSIST_KEY } from 'lib/constants'
import { CameraControls } from './CameraControls'
import { EngineCommandManager } from 'lang/std/engineConnection'
type SendType = ReturnType<typeof useModelingContext>['send']
@ -37,8 +38,10 @@ export const ZOOM_MAGIC_NUMBER = 63.5
export const INTERSECTION_PLANE_LAYER = 1
export const SKETCH_LAYER = 2
export const DEBUG_SHOW_INTERSECTION_PLANE = false
export const DEBUG_SHOW_BOTH_SCENES = false
// redundant types so that it can be changed temporarily but CI will catch the wrong type
export const DEBUG_SHOW_INTERSECTION_PLANE: false = false
export const DEBUG_SHOW_BOTH_SCENES: false = false
export const RAYCASTABLE_PLANE = 'raycastable-plane'
export const DEFAULT_PLANES = 'default-planes'
@ -84,7 +87,7 @@ interface OnMoveCallbackArgs {
// This singleton class is responsible for all of the under the hood setup for the client side scene.
// That is the cameras and switching between them, raycasters for click mouse events and their abstractions (onClick etc), setting up controls.
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
class SceneInfra {
export class SceneInfra {
static instance: SceneInfra
scene: Scene
renderer: WebGLRenderer
@ -97,13 +100,13 @@ class SceneInfra {
_baseUnitMultiplier = 1
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
onClickCallback: (arg: OnClickCallbackArgs) => void = () => {}
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
setCallbacks = (callbacks: {
onDrag?: (arg: OnDragCallbackArgs) => void
onMove?: (arg: OnMoveCallbackArgs) => void
onClick?: (arg?: OnClickCallbackArgs) => void
onClick?: (arg: OnClickCallbackArgs) => void
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
}) => {
@ -124,7 +127,7 @@ class SceneInfra {
)
}
resetMouseListeners = () => {
sceneInfra.setCallbacks({
this.setCallbacks({
onDrag: () => {},
onMove: () => {},
onClick: () => {},
@ -153,7 +156,7 @@ class SceneInfra {
} | null = null
mouseDownVector: null | Vector2 = null
constructor() {
constructor(engineCommandManager: EngineCommandManager) {
// SCENE
this.scene = new Scene()
this.scene.background = new Color(0x000000)
@ -176,7 +179,11 @@ class SceneInfra {
const x = Math.cos(ang) * length
const y = Math.sin(ang) * length
this.camControls = new CameraControls(false, this.renderer.domElement)
this.camControls = new CameraControls(
false,
this.renderer.domElement,
engineCommandManager
)
this.camControls.subscribeToCamChange(() => this.onCameraChange())
this.camControls.camera.layers.enable(SKETCH_LAYER)
this.camControls.camera.position.set(0, -x, y)
@ -219,9 +226,9 @@ class SceneInfra {
?.getObjectByName('gridHelper')
planesGroup &&
planesGroup.scale.set(
scale / sceneInfra._baseUnitMultiplier,
scale / sceneInfra._baseUnitMultiplier,
scale / sceneInfra._baseUnitMultiplier
scale / this._baseUnitMultiplier,
scale / this._baseUnitMultiplier,
scale / this._baseUnitMultiplier
)
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
}
@ -253,7 +260,7 @@ class SceneInfra {
} | null => {
this.planeRaycaster.setFromCamera(
this.currentMouseVector,
sceneInfra.camControls.camera
this.camControls.camera
)
const planeIntersects = this.planeRaycaster.intersectObjects(
this.scene.children,
@ -272,16 +279,19 @@ class SceneInfra {
let transformedPoint = intersectPoint.clone()
if (transformedPoint) {
transformedPoint.applyQuaternion(inversePlaneQuaternion)
transformedPoint?.sub(
new Vector3(...planePosition).applyQuaternion(inversePlaneQuaternion)
)
}
const twoD = new Vector2(
// I think the intersection plane doesn't get scale when nearly everything else does, maybe that should change
transformedPoint.x / this._baseUnitMultiplier,
transformedPoint.y / this._baseUnitMultiplier
) // z should be 0
const planePositionCorrected = new Vector3(
...planePosition
).applyQuaternion(inversePlaneQuaternion)
twoD.sub(new Vector2(...planePositionCorrected))
return {
twoD: new Vector2(
transformedPoint.x / this._baseUnitMultiplier,
transformedPoint.y / this._baseUnitMultiplier
), // z should be 0
twoD,
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
intersection: planeIntersects[0],
}
@ -464,7 +474,7 @@ class SceneInfra {
intersects,
})
} else {
this.onClickCallback()
this.onClickCallback({ mouseEvent, intersects })
}
// Clear the selected state whether it was dragged or not
this.selected = null
@ -478,7 +488,7 @@ class SceneInfra {
intersects,
})
} else {
this.onClickCallback()
this.onClickCallback({ mouseEvent, intersects })
}
}
showDefaultPlanes() {
@ -522,9 +532,9 @@ class SceneInfra {
this.camControls.target
)
planesGroup.scale.set(
sceneScale / sceneInfra._baseUnitMultiplier,
sceneScale / sceneInfra._baseUnitMultiplier,
sceneScale / sceneInfra._baseUnitMultiplier
sceneScale / this._baseUnitMultiplier,
sceneScale / this._baseUnitMultiplier,
sceneScale / this._baseUnitMultiplier
)
this.scene.add(planesGroup)
}
@ -535,7 +545,7 @@ class SceneInfra {
if (planesGroup) this.scene.remove(planesGroup)
}
updateOtherSelectionColors = (otherSelections: Axis[]) => {
const axisGroup = sceneInfra.scene.children.find(
const axisGroup = this.scene.children.find(
({ userData }) => userData?.type === AXIS_GROUP
)
const axisMap: { [key: string]: Axis } = {
@ -557,8 +567,6 @@ class SceneInfra {
}
}
export const sceneInfra = new SceneInfra()
export function getSceneScale(
camera: PerspectiveCamera | OrthographicCamera,
target: Vector3

View File

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

View File

@ -7,8 +7,8 @@ import {
findUniqueName,
} from '../lang/modifyAst'
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { useKclContext } from 'lang/KclProvider'
import { useModelingContext } from 'hooks/useModelingContext'
import { executeAst } from 'useStore'

View File

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react'
import { sceneInfra } from '../clientSideScene/sceneInfra'
import { engineCommandManager } from 'lang/std/engineConnection'
import { engineCommandManager, sceneInfra } from 'lib/singletons'
import { throttle, isReducedMotion } from 'lib/utils'
const updateDollyZoom = throttle(

View File

@ -6,7 +6,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/KclSingleton'
import { kclManager } from 'lib/singletons'
export const CodeMenu = ({ children }: PropsWithChildren) => {
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
@ -77,7 +77,7 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
<Menu.Item>
<a
className={styles.button}
href="https://github.com/KittyCAD/kcl-samples"
href="https://zoo.dev/docs/kcl-samples"
target="_blank"
rel="noopener noreferrer"
>

View File

@ -1,6 +1,6 @@
import { useSelector } from '@xstate/react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useKclContext } from 'lang/KclSingleton'
import { useKclContext } from 'lang/KclProvider'
import { CommandArgument } from 'lib/commandTypes'
import {
ResolvedSelectionType,

View File

@ -1,4 +1,5 @@
import { CommandLog, engineCommandManager } from 'lang/std/engineConnection'
import { CommandLog } from 'lang/std/engineConnection'
import { engineCommandManager } from 'lib/singletons'
import { useState, useEffect } from 'react'
function useEngineCommands(): [CommandLog[], () => void] {

View File

@ -13,7 +13,7 @@ import { useHotkeys } from 'react-hotkeys-hook'
import styles from './FileTree.module.css'
import { FILE_EXT, sortProject } from 'lib/tauriFS'
import { CustomIcon } from './CustomIcon'
import { kclManager } from 'lang/KclSingleton'
import { kclManager } from 'lib/singletons'
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
import { useLspContext } from './LspProvider'

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/KclSingleton'
import { useKclContext } from 'lang/KclProvider'
const ReactJsonTypeHack = ReactJson as any

View File

@ -38,7 +38,26 @@ describe('processMemory', () => {
myVar: 5,
myFn: undefined,
otherVar: 3,
theExtrude: [],
theExtrude: [
{
type: 'extrudePlane',
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
faceId: expect.any(String),
name: '',
id: expect.any(String),
sourceRange: [170, 194],
},
{
type: 'extrudePlane',
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
faceId: expect.any(String),
name: '',
id: expect.any(String),
sourceRange: [202, 230],
},
],
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: '' },

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/KclSingleton'
import { useKclContext } from 'lang/KclProvider'
interface MemoryPanelProps extends CollapsiblePanelProps {
theme?: Exclude<Themes, Themes.System>

View File

@ -12,8 +12,8 @@ import { SetSelections, modelingMachine } from 'machines/modelingMachine'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { isCursorInSketchCommandRange } from 'lang/util'
import { engineCommandManager } from 'lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import { kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
import { useKclContext } from 'lang/KclProvider'
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
import {
angleBetweenInfo,
@ -23,9 +23,9 @@ import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
import { pathMapToSelections } from 'lang/util'
import { useStore } from 'useStore'
import {
Selections,
canExtrudeSelection,
handleSelectionBatch,
handleSelectionWithShift,
isSelectionLastLine,
isSketchPipe,
} from 'lib/selections'
@ -33,15 +33,20 @@ 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'
import { isSingleCursorInPipe } from 'lang/queryAst'
import {
getSketchOrientationDetails,
getSketchQuaternion,
} from 'clientSideScene/sceneEntities'
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
import { Program, parse } from 'lang/wasm'
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
import { TEST } from 'env'
import { exportFromEngine } from 'lib/exportFromEngine'
import { Models } from '@kittycad/lib/dist/types/src'
import toast from 'react-hot-toast'
import { EditorSelection } from '@uiw/react-codemirror'
import { Vector3 } from 'three'
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -61,17 +66,23 @@ export const ModelingMachineProvider = ({
const {
auth,
settings: {
context: { baseUnit },
context: { baseUnit, theme },
},
} = useSettingsAuthContext()
const { code } = useKclContext()
const token = auth?.context?.token
const streamRef = useRef<HTMLDivElement>(null)
useSetupEngineManager(streamRef, token)
useSetupEngineManager(streamRef, token, theme)
const { isShiftDown, editorView } = useStore((s) => ({
const {
isShiftDown,
editorView,
setLastCodeMirrorSelectionUpdatedFromScene,
} = useStore((s) => ({
isShiftDown: s.isShiftDown,
editorView: s.editorView,
setLastCodeMirrorSelectionUpdatedFromScene:
s.setLastCodeMirrorSelectionUpdatedFromScene,
}))
// Settings machine setup
@ -92,92 +103,98 @@ export const ModelingMachineProvider = ({
{
actions: {
'sketch exit execute': () => {
kclManager.executeAst()
try {
kclManager.executeAst(parse(kclManager.code))
} catch (e) {
kclManager.executeAst()
}
},
'Set selection': assign(({ selectionRanges }, event) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data
if (!editorView) return {}
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
return { selectionRanges: setSelections.selection }
else if (setSelections.selectionType === 'otherSelection') {
const {
codeMirrorSelection,
selectionRangeTypeMap,
otherSelections,
} = handleSelectionWithShift({
otherSelection: setSelections.selection,
currentSelections: selectionRanges,
isShiftDown,
})
setTimeout(() => {
editorView.dispatch({
selection: codeMirrorSelection,
})
})
return {
selectionRangeTypeMap,
selectionRanges: {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections,
},
}
} else if (setSelections.selectionType === 'singleCodeCursor') {
// This DOES NOT set the `selectionRanges` in xstate context
// instead it updates/dispatches to the editor, which in turn updates the xstate context
// I've found this the best way to deal with the editor without causing an infinite loop
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
// because we want to respect the user manually placing the cursor too.
// for more details on how selections see `src/lib/selections.ts`.
const {
codeMirrorSelection,
selectionRangeTypeMap,
otherSelections,
} = handleSelectionWithShift({
codeSelection: setSelections.selection,
currentSelections: selectionRanges,
isShiftDown,
})
if (codeMirrorSelection) {
setTimeout(() => {
editorView.dispatch({
selection: codeMirrorSelection,
})
})
}
if (!setSelections.selection) {
return {
selectionRangeTypeMap,
selectionRanges: {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections,
},
const dispatchSelection = (selection?: EditorSelection) => {
if (!selection) return // TODO less of hack for the below please
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
setTimeout(() => editorView.dispatch({ selection }))
}
let selections: Selections = {
codeBasedSelections: [],
otherSelections: [],
}
if (setSelections.selectionType === 'singleCodeCursor') {
if (!setSelections.selection && isShiftDown) {
} else if (!setSelections.selection && !isShiftDown) {
selections = {
codeBasedSelections: [],
otherSelections: [],
}
} else if (setSelections.selection && !isShiftDown) {
selections = {
codeBasedSelections: [setSelections.selection],
otherSelections: [],
}
} else if (setSelections.selection && isShiftDown) {
selections = {
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
setSelections.selection,
],
otherSelections: selectionRanges.otherSelections,
}
}
const {
engineEvents,
codeMirrorSelection,
updateSceneObjectColors,
} = handleSelectionBatch({
selections,
})
codeMirrorSelection && dispatchSelection(codeMirrorSelection)
engineEvents &&
engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
updateSceneObjectColors()
return {
selectionRangeTypeMap,
selectionRanges: {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections,
},
selectionRanges: selections,
}
}
// This DOES NOT set the `selectionRanges` in xstate context
// same as comment above
const { codeMirrorSelection, selectionRangeTypeMap } =
handleSelectionBatch({
selections: setSelections.selection,
})
if (codeMirrorSelection) {
setTimeout(() => {
editorView.dispatch({
selection: codeMirrorSelection,
})
})
if (setSelections.selectionType === 'mirrorCodeMirrorSelections') {
return {
selectionRanges: setSelections.selection,
}
}
return { selectionRangeTypeMap }
if (setSelections.selectionType === 'otherSelection') {
if (isShiftDown) {
selections = {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections: [setSelections.selection],
}
} else {
selections = {
codeBasedSelections: [],
otherSelections: [setSelections.selection],
}
}
const { engineEvents, updateSceneObjectColors } =
handleSelectionBatch({
selections,
})
engineEvents &&
engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
updateSceneObjectColors()
return {
selectionRanges: selections,
}
}
return {}
}),
'Engine export': (_, event) => {
if (event.type !== 'Export' || TEST) return
@ -255,10 +272,10 @@ export const ModelingMachineProvider = ({
kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0,
},
services: {
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
if (!sketchPathToNode) return
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
if (!sketchDetails) return
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
const varDecIndex = sketchPathToNode[1][0]
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
// remove body item at varDecIndex
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
await kclManager.executeAstMock(newAst, { updates: 'code' })
@ -267,28 +284,69 @@ export const ModelingMachineProvider = ({
onDrag: () => {},
})
},
'animate-to-face': async (_, { data: { plane, normal } }) => {
'animate-to-face': async (_, { data }) => {
if (data.type === 'extrudeFace') {
const { modifiedAst, pathToNode: pathToNewSketchNode } =
sketchOnExtrudedFace(
kclManager.ast,
data.extrudeSegmentPathToNode,
kclManager.programMemory,
data.cap
)
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
const forward = new Vector3(...data.zAxis)
const up = new Vector3(...data.yAxis)
let target = new Vector3(...data.position).multiplyScalar(
sceneInfra._baseUnitMultiplier
)
const quaternion = quaternionFromUpNForward(up, forward)
await sceneInfra.camControls.tweenCameraToQuaternion(
quaternion,
target
)
return {
sketchPathToNode: pathToNewSketchNode,
zAxis: data.zAxis,
yAxis: data.yAxis,
origin: data.position,
}
}
const { modifiedAst, pathToNode } = startSketchOnDefault(
kclManager.ast,
plane
data.plane
)
await kclManager.updateAst(modifiedAst, false)
const quaternion = getSketchQuaternion(pathToNode, normal)
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
return {
sketchPathToNode: pathToNode,
sketchNormalBackUp: normal,
zAxis: data.zAxis,
yAxis: data.yAxis,
origin: [0, 0, 0],
}
},
'animate-to-sketch': async ({
sketchPathToNode,
sketchNormalBackUp,
}) => {
const quaternion = getSketchQuaternion(
sketchPathToNode || [],
sketchNormalBackUp
'animate-to-sketch': async ({ selectionRanges }) => {
const sourceRange = selectionRanges.codeBasedSelections[0].range
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
sourceRange
)
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
const info = await getSketchOrientationDetails(sketchPathToNode || [])
await sceneInfra.camControls.tweenCameraToQuaternion(
info.quat,
new Vector3(...info.sketchDetails.origin)
)
return {
sketchPathToNode: sketchPathToNode || [],
zAxis: info.sketchDetails.zAxis || null,
yAxis: info.sketchDetails.yAxis || null,
origin: info.sketchDetails.origin.map(
(a) => a / sceneInfra._baseUnitMultiplier
) as [number, number, number],
}
},
'Get horizontal info': async ({
selectionRanges,

View File

@ -5,12 +5,12 @@ import {
ConnectingType,
ConnectingTypeGroup,
DisconnectingType,
engineCommandManager,
EngineConnectionState,
EngineConnectionStateType,
ErrorType,
initialConnectingTypeGroupState,
} from '../lang/std/engineConnection'
import { engineCommandManager } from '../lib/singletons'
import Tooltip from './Tooltip'
export enum NetworkHealthState {

View File

@ -11,7 +11,7 @@ import {
validateSettings,
} from 'lib/settings/settingsUtils'
import { toast } from 'react-hot-toast'
import { setThemeClass, Themes } from 'lib/theme'
import { getThemeColorForEngine, setThemeClass, Themes } from 'lib/theme'
import {
AnyStateMachine,
ContextFrom,
@ -22,8 +22,8 @@ import {
import { isTauri } from 'lib/isTauri'
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
import { sceneInfra } from 'clientSideScene/sceneInfra'
import { kclManager } from 'lang/KclSingleton'
import { kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
import { v4 as uuidv4 } from 'uuid'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -96,6 +96,16 @@ export const SettingsAuthProviderBase = ({
: context.baseUnit
sceneInfra.baseUnit = newBaseUnit
},
setEngineTheme: (context) => {
engineCommandManager.sendSceneCommand({
cmd_id: uuidv4(),
type: 'modeling_cmd_req',
cmd: {
type: 'set_background_color',
color: getThemeColorForEngine(context.theme),
},
})
},
toastSuccess: (context, event) => {
const truncatedNewValue =
'data' in event && event.data instanceof Object
@ -147,7 +157,6 @@ export const SettingsAuthProviderBase = ({
actions: {
goToSignInPage: () => {
navigate(paths.SIGN_IN)
logout()
},
goToIndexPage: () => {

View File

@ -1,15 +1,13 @@
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { getNormalisedCoordinates } from '../lib/utils'
import Loading from './Loading'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Models } from '@kittycad/lib'
import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { useKclContext } from 'lang/KclSingleton'
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
import { butName } from 'lib/cameraControls'
import { sendSelectEventToEngine } from 'lib/selections'
export const Stream = ({ className = '' }: { className?: string }) => {
const [isLoading, setIsLoading] = useState(true)
@ -30,7 +28,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
}))
const { settings } = useSettingsAuthContext()
const { state } = useModelingContext()
const { isExecuting } = useKclContext()
const { overallState } = useNetworkStatus()
const isNetworkOkay = overallState === NetworkHealthState.Ok
@ -60,50 +57,14 @@ export const Stream = ({ className = '' }: { className?: string }) => {
setClickCoords({ x, y })
}
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
clientX,
clientY,
ctrlKey,
}) => {
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
if (!videoRef.current) return
setButtonDownInStream(undefined)
if (state.matches('Sketch')) return
if (state.matches('Sketch no face')) return
const { x, y } = getNormalisedCoordinates({
clientX,
clientY,
el: videoRef.current,
...streamDimensions,
})
const newCmdId = uuidv4()
const interaction = ctrlKey ? 'pan' : 'rotate'
const command: Models['WebSocketRequest_type'] = {
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_end',
interaction,
window: { x, y },
},
cmd_id: newCmdId,
}
if (!didDragInStream) {
command.cmd = {
type: 'select_with_point',
selected_at_window: { x, y },
selection_type: 'add',
}
engineCommandManager.sendSceneCommand(command)
} else if (didDragInStream) {
command.cmd = {
type: 'handle_mouse_drag_end',
window: { x, y },
}
void engineCommandManager.sendSceneCommand(command)
} else {
engineCommandManager.sendSceneCommand(command)
if (!didDragInStream && butName(e).left) {
sendSelectEventToEngine(e, videoRef.current, streamDimensions)
}
setDidDragInStream(false)
@ -140,9 +101,10 @@ export const Stream = ({ className = '' }: { className?: string }) => {
controls={false}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
className="w-full cursor-pointer h-full"
disablePictureInPicture
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
id="video-stream"
/>
<ClientSideScene cameraControls={settings.context?.cameraControls} />
{!isNetworkOkay && !isLoading && (

View File

@ -3,6 +3,7 @@ import ReactCodeMirror, {
Extension,
ViewUpdate,
keymap,
SelectionRange,
} from '@uiw/react-codemirror'
import { TEST } from 'env'
import { useCommandsContext } from 'hooks/useCommandsContext'
@ -19,10 +20,9 @@ import { kclErrToDiagnostic } from 'lang/errors'
import { CSSRuleObject } from 'tailwindcss/types/config'
import { useModelingContext } from 'hooks/useModelingContext'
import interact from '@replit/codemirror-interact'
import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSingleton'
import { engineCommandManager, sceneInfra, kclManager } from 'lib/singletons'
import { useKclContext } from 'lang/KclProvider'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { sceneInfra } from 'clientSideScene/sceneInfra'
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
import { useHotkeys } from 'react-hotkeys-hook'
import { useLspContext } from './LspProvider'
@ -75,7 +75,7 @@ export const TextEditor = ({
})
const {
context: { selectionRanges, selectionRangeTypeMap },
context: { selectionRanges },
send,
state,
} = useModelingContext()
@ -91,10 +91,27 @@ export const TextEditor = ({
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
else kclManager.setCode(newCode)
} //, []);
const lastSelection = useRef('')
const onUpdate = (viewUpdate: ViewUpdate) => {
if (!editorView) {
setEditorView(viewUpdate.view)
}
const selString = stringifyRanges(
viewUpdate?.state?.selection?.ranges || []
)
if (selString === lastSelection.current) {
// onUpdate is noisy and is fired a lot by extensions
// since we're only interested in selections changes we can ignore most of these.
return
}
lastSelection.current = selString
if (
// TODO find a less lazy way of getting the last
Date.now() - useStore.getState().lastCodeMirrorSelectionUpdatedFromScene <
150
)
return // update triggered by scene selection
if (sceneInfra.selected) return // mid drag
const ignoreEvents: ModelingMachineEvent['type'][] = [
'Equip Line tool',
@ -104,7 +121,6 @@ export const TextEditor = ({
const eventInfo = processCodeMirrorRanges({
codeMirrorRanges: viewUpdate.state.selection.ranges,
selectionRanges,
selectionRangeTypeMap,
isShiftDown,
})
if (!eventInfo) return
@ -226,3 +242,7 @@ export const TextEditor = ({
</div>
)
}
function stringifyRanges(ranges: readonly SelectionRange[]): string {
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
}

View File

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

View File

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

View File

@ -10,7 +10,7 @@ import {
getTransformInfos,
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { kclManager } from 'lang/KclSingleton'
import { kclManager } from 'lib/singletons'
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/KclSingleton'
import { kclManager } from 'lib/singletons'
const getModalInfo = createInfoModal(GetInfoModal)

View File

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

View File

@ -19,7 +19,7 @@ import {
createVariableDeclaration,
} from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lang/KclSingleton'
import { kclManager } from 'lib/singletons'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)

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/KclSingleton'
import { kclManager } from 'lib/singletons'
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/KclSingleton'
import { kclManager } from 'lib/singletons'
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/KclSingleton'
import { kclManager } from 'lib/singletons'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)

View File

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

View File

@ -33,10 +33,12 @@ export default class Server {
this.#fromServer,
fileSystemManager
)
if (!token || token === '') {
throw new Error(
type_ + ': auth token is required for lsp server to start'
)
}
if (type_ === 'copilot') {
if (!token || token === '') {
throw new Error('auth token is required for copilot')
}
await copilotLspRun(config, token)
} else if (type_ === 'kcl') {
await kclLspRun(config, token || '')

View File

@ -1,6 +1,6 @@
import { useEffect } from 'react'
import { useStore } from 'useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
import { engineCommandManager } from 'lib/singletons'
import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections'

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