Compare commits

...

87 Commits

Author SHA1 Message Date
2a4ff386e0 Merge branch 'main' into coredump-clientstate 2024-06-10 18:21:14 -07:00
081e34a600 Bump regex from 1.10.4 to 1.10.5 in /src/wasm-lib (#2637)
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.4 to 1.10.5.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.4...1.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 17:29:24 -07:00
541400f4be Cut release v0.22.1 (#2634) 2024-06-07 14:49:29 -04:00
39d249030d remove more page.clicks (#2630)
* remove more page.clicks

* fmt
2024-06-07 12:31:22 +10:00
f8a69fac73 Remove page.clicks from test utils (#2629)
* remove page.clicks from test utils

* timeout
2024-06-07 00:48:42 +00:00
24f4bf160f Add a right-click (or "context") menu to file tree and gizmo (#2628)
* Basic context menu components

* Working context menu!

* Show keyboard shortcuts in file tree context menu

* Add context menu to Gizmo

* Little polish on components

* Add a test for the gizmo, firm up other gizmo tests

* Updated Cargo lock

* Updated Cargo.lock
2024-06-06 19:56:46 -04:00
8011594e24 Fix most trackpad zoom jank (#2613)
* Remove zoom throttling

And use the mouse zoom for sketch mode

* test tweaks

* test tweak

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-06-07 09:48:54 +10:00
0e09affb8f Remove debug logging from Engine Connection (#2623)
* Remove debug logging from Engine Connection

Left console.log('connectionstatechange: ' + event.target?.connectionState) intentionally

* Bring that beat back

@lf94 request that we keep this one and also make sure it's in coredump.
2024-06-07 07:16:55 +10:00
197a47346a Refactor: Break functions into smaller functions (#2622)
* Factor ExecutionCtx into its own fn

* Add hyper for tests

* Further factor out functions
2024-06-07 07:01:41 +10:00
9d083710e0 Bump actions/cache from 3 to 4 (#2616)
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  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-06-06 13:05:20 -07:00
afa7c1dc4e Bump toml from 0.8.13 to 0.8.14 in /src/wasm-lib (#2615)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.13 to 0.8.14.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.13...toml-v0.8.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-06 13:05:01 -07:00
c74b695a71 Remove an orphaned grackle file (#2611) 2024-06-06 14:48:58 -05:00
d0c244e05e Do not aggressively disconnect when video stream goes down (#2621) 2024-06-06 11:40:39 -04:00
a315b77f02 More selection verification (#2619) 2024-06-06 11:55:22 +00:00
15c854ff18 verify sketches can be selected outside of sketches (#2618) 2024-06-06 08:07:42 +00:00
acd3a5717d improve selections and remove redundant edit_mode (#2617) 2024-06-06 16:03:10 +10:00
8a2555550f Adding a sample using a custom axis in revolve.rs (#2596)
* Adding a sample using a custom axis in revolve.rs

* Adding updated docs and snapshot of generated part

* Running fmt
2024-06-05 19:48:59 +00:00
62e75c852a Bump dawidd6/action-download-artifact from 4 to 5 (#2601)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 4 to 5.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  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-06-05 09:44:13 -07:00
max
dd3601ea7b Gizmo Normal Snapping (#2539)
* gizmo 2.0

nice and clickable

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

* initial mouse position fix

when the scene first loads, mouse position is 0,0, which renders the gizmo selected.

* animation loop / disposal optimization

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

* reset camera tweak

* add cam target to debug panel

* test stub

* reset camera position handle removed from gizmo

it is now a button in the debug panel

* gizmo refactoring

* small fix

* reset camera view

bug fix

* nicer updateCameraToAxis

now gizmo rotates around the target instead of world 0,0,0

* micro refactoring

* playwright update

* playwright remove timeout + fmt

* hide gizmo while loading stream

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

This reverts commit f0a506d6b9.

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

This reverts commit 2781261331.

* try make gizmo test more realiable

* tweak

* refactoring

* increase timeout time

* 1 sec wait after mouse click

* 3 sec timeout

* better clickPosition

* test with 10 sec timeout

* 0.5 sec timeout

* add passive update for gizmo to avoid some edge cases

* default_camera_get_settings after click

* try and remove timeouts

---------

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-06-05 12:43:12 +00:00
a5e7782d9a Re-enable rust cache for src-tauri on Windows (#2586) 2024-06-05 06:06:25 -04:00
79b0b70688 Bump ts-rs from badbac0 to be0349d in /src/wasm-lib (#2602) 2024-06-05 03:40:42 -05:00
1d134c1be0 Timeout ahead of flaky sign out (#2593) 2024-06-05 04:36:26 -04:00
1c58572234 cache playwright follow up (#2605)
cache plawwright follow up
2024-06-05 05:53:21 +00:00
ecee51e82b cache playwright browsers (#2604) 2024-06-05 05:10:49 +00:00
2be00f8f52 Merge branch 'main' into coredump-clientstate 2024-06-03 11:14:29 -07:00
5d66fc496f Merge branch 'main' into coredump-clientstate 2024-05-31 10:31:46 -07:00
764675d411 Merge branch 'main' into coredump-clientstate 2024-05-30 14:22:03 -07:00
4482b0fee4 Hoist upload_coredump to resolve scoping issues 2024-05-30 14:21:37 -07:00
a604660feb Revert to original state
I added a bunch of structs to be able to implement clientstate, but then was able to resolve these by upgrading ts-rs. The import statement had a lot of entries in the intermediate state. This reverts to the prior state in the codebase. No need for a reformatting change.
2024-05-30 14:11:52 -07:00
0465aa06d3 Merge branch 'main' into coredump-clientstate 2024-05-29 22:44:36 -07:00
62302ccfda WIP coredump json upload
I need @paultag or someone to help guide me through passing the coredump around properly.
2024-05-29 22:42:11 -07:00
313e586510 Remove window.Zoo for now
We're going to come back and do the xstate state capture after the update to xstate 5 which will provide a native api for both accessing xstate state and restoring state, which will be useful for debugging.
2024-05-29 22:40:28 -07:00
0b9f707063 WIP Upload json 2024-05-29 17:32:45 -07:00
fb7b0aadfb prettier fmt coredump.ts 2024-05-29 17:29:09 -07:00
cf54905539 prettier fmt 2024-05-29 17:28:30 -07:00
2fb318b154 Attach Zoo to window 2024-05-29 12:30:23 -07:00
9366f68a88 window.Zoo 2024-05-29 12:24:46 -07:00
6985678e25 Editor Manager 2024-05-29 12:24:34 -07:00
c56319fb74 Engine Command Manage console.log
Make coredump console log output more readable and traceable.
2024-05-29 09:33:39 -07:00
ec20e9752f Rust gimme JSON
This brings the code back to my original intent and strategy when I started building out this solution: gather all the JSON we may need under `client_state` and pass it back. 🎉
2024-05-29 09:17:04 -07:00
88b94c985f Finish KCL Manager coredump 2024-05-28 20:29:00 -07:00
d455189cb1 Simplify kcl_manager in Rust
MOAR JSON
2024-05-28 20:28:04 -07:00
27974dee04 Simplify XState representation in Rust 2024-05-28 19:54:37 -07:00
51acd5d6bf Simplify ModelingMachine expectations in Rust 2024-05-28 18:04:40 -07:00
48774ba8bf Capturing Scene Infra in CoreDump
️ Using JavaScript 
2024-05-28 13:31:43 -07:00
5a4834f37c More serde_json::Value less predefined struct 2024-05-28 12:16:17 -07:00
e30b634974 Add kclManager AST and kclErrors 2024-05-28 11:44:41 -07:00
6c9d0378f0 Update Cargo.lock 2024-05-28 09:24:54 -07:00
717838c702 Merge branch 'main' into coredump-clientstate 2024-05-28 09:24:46 -07:00
a92acc68a8 TODO? more singletons
Should we log anything from these?
2024-05-27 23:45:04 -07:00
0ca7095777 Gather KCL Manager info for CoreDump 2024-05-27 23:44:00 -07:00
8ce3b32f75 Update Cargo.lock 2024-05-27 23:42:01 -07:00
c0826690a1 Merge branch 'main' into coredump-clientstate 2024-05-27 15:47:25 -07:00
1fa03a8d58 Output clientState as JSON 2024-05-24 00:39:48 -07:00
9ea92d5a6d coredump engine command manager
Complete?!?
2024-05-24 00:22:13 -07:00
81bf96d52f Update Cargo.lock 2024-05-23 22:51:56 -07:00
3bbd63eb5d Merge branch 'main' into coredump-clientstate 2024-05-23 22:44:46 -07:00
2b0af61b09 deepClone types 2024-05-23 22:38:45 -07:00
a671fcb128 fmt 2024-05-23 19:07:21 -07:00
cbb5f67364 Remove token debug
Not sure why I committed this anyway
2024-05-23 19:07:21 -07:00
3a64f29ca6 Add sample artifact map 2024-05-23 19:07:21 -07:00
4cbf2d19b5 Not using HashMap...yet
serde_json::Value to the rescue. Thanks @paultag
2024-05-23 19:07:21 -07:00
64457cd2c2 serde_json::Value 💖 save us 2024-05-23 19:07:21 -07:00
4571ea4e08 WIP ArtifactMap dump 2024-05-23 19:07:21 -07:00
b586b38ad9 coredump typescript implementation
Connection state
2024-05-23 19:07:21 -07:00
8766861d07 Enable connection type dump 2024-05-23 19:07:21 -07:00
abe70f4406 Update coredump.fixture.json 2024-05-23 19:07:21 -07:00
a4452f775b Add serde_json with new support for ts-rs 2024-05-23 19:07:21 -07:00
b06158ba4e Patch ts-rs to 8.1.0 with paultag's fixes 2024-05-23 19:06:16 -07:00
7a42e9e868 Pull in more types 2024-05-23 19:04:33 -07:00
4e856abbcb Types for Engine Manager and Connection state 2024-05-23 19:04:33 -07:00
fef8139a3e Parse out XState Services 2024-05-23 19:01:48 -07:00
b578a88808 Need to upgrade ts-rs to 8.1 for serde json types 2024-05-23 19:01:48 -07:00
a33c15b667 Change _unused to meta 2024-05-23 19:01:48 -07:00
bdf1eef0d5 Properly import ClientState 2024-05-23 19:01:48 -07:00
41d900ad93 migrate over a few more app infos 2024-05-23 19:01:48 -07:00
5796ce02c3 Remove dummy response 2024-05-23 19:01:48 -07:00
229a89fb1d Convert AppInfo to CoreDumpInfo 2024-05-23 19:01:48 -07:00
6343545496 WIP Filling out state data 2024-05-23 19:01:48 -07:00
779b7038dd Include all the state objects 2024-05-23 19:01:48 -07:00
10cbfddc41 Add singleton base structure definitiions 2024-05-23 19:01:48 -07:00
958b68f06f Add XState base structure definitions 2024-05-23 19:01:48 -07:00
522dfdcbcc Removed unused reference 2024-05-23 19:01:48 -07:00
0ac1cbfcf9 Start to implement the ClienState struct
Signed-off-by: Paul R. Tagliamonte <paul@kittycad.io>
2024-05-23 19:01:01 -07:00
950f27ce2a Add client_state to coredump fixture 2024-05-23 19:01:01 -07:00
62e6559177 Create coredump.fixture.json
With data from #2316
2024-05-23 19:01:01 -07:00
8fbacd0363 WIP client state core dump plumbing 2024-05-23 19:01:01 -07:00
33 changed files with 2215 additions and 871 deletions

View File

@ -180,9 +180,7 @@ jobs:
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
# TODO: re-enable for Windows builds, see https://github.com/tauri-apps/tauri/issues/9045
- name: Setup Rust cache
if: matrix.os != 'windows-latest'
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'

View File

@ -46,12 +46,18 @@ jobs:
- uses: KittyCAD/action-install-cli@main
- name: Install dependencies
run: yarn
- name: Cache Playwright Browsers
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v4
uses: dawidd6/action-download-artifact@v5
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
@ -143,12 +149,20 @@ jobs:
cache: 'yarn'
- name: Install dependencies
run: yarn
- name: Cache Playwright Browsers
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-playwright-
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v4
uses: dawidd6/action-download-artifact@v5
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}

File diff suppressed because one or more lines are too long

View File

@ -56095,7 +56095,8 @@
"const part001 = startSketchOn('XY')\n |> startProfileAt([4, 12], %)\n |> line([2, 0], %)\n |> line([0, -6], %)\n |> line([4, -6], %)\n |> line([0, -6], %)\n |> line([-3.75, -4.5], %)\n |> line([0, -5.5], %)\n |> line([-2, 0], %)\n |> close(%)\n |> revolve({ axis: 'y', angle: 180 }, %)",
"const part001 = startSketchOn('XY')\n |> startProfileAt([4, 12], %)\n |> line([2, 0], %)\n |> line([0, -6], %)\n |> line([4, -6], %)\n |> line([0, -6], %)\n |> line([-3.75, -4.5], %)\n |> line([0, -5.5], %)\n |> line([-2, 0], %)\n |> close(%)\n |> revolve({ axis: 'y', angle: 180 }, %)\nconst part002 = startSketchOn(part001, 'end')\n |> startProfileAt([4.5, -5], %)\n |> line([0, 5], %)\n |> line([5, 0], %)\n |> line([0, -5], %)\n |> close(%)\n |> extrude(5, %)",
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 20], %)\n |> line([20, 0], %)\n |> line([0, -20], %)\n |> close(%)\n |> extrude(20, %)\n\nconst sketch001 = startSketchOn(box, \"END\")\n |> circle([10, 10], 4, %)\n |> revolve({ angle: -90, axis: 'y' }, %)",
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 20], %)\n |> line([20, 0], %)\n |> line([0, -20], %, 'revolveAxis')\n |> close(%)\n |> extrude(20, %)\n\nconst sketch001 = startSketchOn(box, \"END\")\n |> circle([10, 10], 4, %)\n |> revolve({\n angle: 90,\n axis: getOppositeEdge('revolveAxis', box)\n }, %)"
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 20], %)\n |> line([20, 0], %)\n |> line([0, -20], %, 'revolveAxis')\n |> close(%)\n |> extrude(20, %)\n\nconst sketch001 = startSketchOn(box, \"END\")\n |> circle([10, 10], 4, %)\n |> revolve({\n angle: 90,\n axis: getOppositeEdge('revolveAxis', box)\n }, %)",
"const sketch001 = startSketchOn('XY')\n |> startProfileAt([10, 0], %)\n |> line([5, -5], %)\n |> line([5, 5], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nconst part001 = revolve({\n axis: {\n custom: {\n axis: [0.0, 1.0, 0.0],\n origin: [0.0, 0.0, 0.0]\n }\n }\n}, sketch001)"
]
},
{

File diff suppressed because it is too large Load Diff

View File

@ -50,3 +50,25 @@ export const TEST_SETTINGS_CORRUPTED = {
textWrapping: true,
},
} satisfies Partial<SaveSettingsPayload>
export const TEST_CODE_GIZMO = `const part001 = startSketchOn('XZ')
|> startProfileAt([20, 0], %)
|> line([7.13, 4 + 0], %)
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
|> lineTo([20.14 + 0, -0.14 + 0], %)
|> xLineTo(29 + 0, %)
|> yLine(-3.14 + 0, %, 'a')
|> xLine(1.63, %)
|> angledLineOfXLength({ angle: 3 + 0, length: 3.14 }, %)
|> angledLineOfYLength({ angle: 30, length: 3 + 0 }, %)
|> angledLineToX({ angle: 22.14 + 0, to: 12 }, %)
|> angledLineToY({ angle: 30, to: 11.14 }, %)
|> angledLineThatIntersects({
angle: 3.14,
intersectTag: 'a',
offset: 0
}, %)
|> tangentialArcTo([13.14 + 0, 13.14], %)
|> close(%)
|> extrude(5 + 7, %)
`

View File

@ -12,14 +12,16 @@ async function waitForPageLoad(page: Page) {
// wait for 'Loading stream...' spinner
await page.getByTestId('loading-stream').waitFor()
// wait for all spinners to be gone
await page.getByTestId('loading').waitFor({ state: 'detached' })
await page
.getByTestId('loading')
.waitFor({ state: 'detached', timeout: 20_000 })
await page.getByTestId('start-sketch').waitFor()
}
async function removeCurrentCode(page: Page) {
const hotkey = process.platform === 'darwin' ? 'Meta' : 'Control'
await page.click('.cm-content')
await page.locator('.cm-content').click()
await page.keyboard.down(hotkey)
await page.keyboard.press('a')
await page.keyboard.up(hotkey)
@ -28,12 +30,12 @@ async function removeCurrentCode(page: Page) {
}
async function sendCustomCmd(page: Page, cmd: EngineCommand) {
await page.fill('[data-testid="custom-cmd-input"]', JSON.stringify(cmd))
await page.click('[data-testid="custom-cmd-send-button"]')
await page.getByTestId('custom-cmd-input').fill(JSON.stringify(cmd))
await page.getByTestId('custom-cmd-send-button').click()
}
async function clearCommandLogs(page: Page) {
await page.click('[data-testid="clear-commands"]')
await page.getByTestId('clear-commands').click()
}
async function expectCmdLog(page: Page, locatorStr: string) {
@ -162,12 +164,7 @@ export const getMovementUtils = (opts: any) => {
return ret.then(() => [last.x, last.y])
}
const expectCodeToBe = async (str: string) => {
await expect(opts.page.locator('.cm-content')).toHaveText(str)
await opts.page.waitForTimeout(100)
}
return { toSU, click00r, expectCodeToBe }
return { toSU, click00r }
}
export async function getUtils(page: Page) {
@ -228,6 +225,7 @@ export async function getUtils(page: Page) {
.locator(locator)
.boundingBox()
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
codeLocator: page.locator('.cm-content'),
doAndWaitForCmd: async (
fn: () => Promise<void>,
commandType: string,

View File

@ -152,6 +152,7 @@ describe('ZMA (Tauri)', () => {
})
it('signs out', async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
const menuButton = await $('[data-testid="user-sidebar-toggle"]')
await click(menuButton)
const signoutButton = await $('[data-testid="user-sidebar-sign-out"]')

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.22.0",
"version": "0.22.1",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.16.0",

112
src-tauri/Cargo.lock generated
View File

@ -200,7 +200,7 @@ dependencies = [
"tauri-plugin-shell",
"tauri-plugin-updater",
"tokio",
"toml 0.8.13",
"toml 0.8.14",
"url",
]
@ -766,7 +766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719"
dependencies = [
"serde",
"toml 0.8.13",
"toml 0.8.14",
]
[[package]]
@ -1373,7 +1373,7 @@ dependencies = [
"cc",
"memchr",
"rustc_version",
"toml 0.8.13",
"toml 0.8.14",
"vswhom",
"winreg 0.52.0",
]
@ -2578,8 +2578,6 @@ dependencies = [
"gltf-json",
"js-sys",
"kittycad",
"kittycad-execution-plan-macros",
"kittycad-execution-plan-traits",
"lazy_static",
"mime_guess",
"parse-display",
@ -2592,7 +2590,7 @@ dependencies = [
"thiserror",
"tokio",
"tokio-tungstenite",
"toml 0.8.13",
"toml 0.8.14",
"tower-lsp",
"ts-rs",
"url",
@ -2655,28 +2653,6 @@ dependencies = [
"uuid",
]
[[package]]
name = "kittycad-execution-plan-macros"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0611fc9b9786175da21d895ffa0f65039e19c9111e94a41b7af999e3b95f045f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]]
name = "kittycad-execution-plan-traits"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123cb47e2780ea8ef3aa67b4db237a27b388d3d3b96db457e274aa4565723151"
dependencies = [
"serde",
"thiserror",
"uuid",
]
[[package]]
name = "kuchikiki"
version = "0.8.2"
@ -3353,9 +3329,9 @@ dependencies = [
[[package]]
name = "parse-display"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06af5f9333eb47bd9ba8462d612e37a8328a5cb80b13f0af4de4c3b89f52dee5"
checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a"
dependencies = [
"parse-display-derive",
"regex",
@ -3364,9 +3340,9 @@ dependencies = [
[[package]]
name = "parse-display-derive"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc9252f259500ee570c75adcc4e317fa6f57a1e47747d622e0bf838002a7b790"
checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281"
dependencies = [
"proc-macro2",
"quote",
@ -3735,9 +3711,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.83"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [
"unicode-ident",
]
@ -4299,6 +4275,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls"
version = "0.23.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki 0.102.3",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.7.0"
@ -5042,7 +5031,7 @@ dependencies = [
"cfg-expr",
"heck 0.5.0",
"pkg-config",
"toml 0.8.13",
"toml 0.8.14",
"version-compare",
]
@ -5195,7 +5184,7 @@ dependencies = [
"serde_json",
"tauri-utils",
"tauri-winres",
"toml 0.8.13",
"toml 0.8.14",
"walkdir",
]
@ -5253,7 +5242,7 @@ dependencies = [
"serde",
"serde_json",
"tauri-utils",
"toml 0.8.13",
"toml 0.8.14",
"walkdir",
]
@ -5530,7 +5519,7 @@ dependencies = [
"serde_with",
"swift-rs",
"thiserror",
"toml 0.8.13",
"toml 0.8.14",
"url",
"urlpattern",
"walkdir",
@ -5664,9 +5653,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.37.0"
version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"bytes",
@ -5684,9 +5673,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
@ -5715,18 +5704,29 @@ dependencies = [
]
[[package]]
name = "tokio-tungstenite"
version = "0.21.0"
name = "tokio-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.7",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "becd34a233e7e31a3dbf7c7241b38320f57393dcae8e7324b0167d21b8e320b0"
dependencies = [
"futures-util",
"log",
"rustls 0.22.4",
"rustls 0.23.7",
"rustls-native-certs",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.25.0",
"tokio-rustls 0.26.0",
"tungstenite",
]
@ -5758,14 +5758,14 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.13"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba"
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.22.13",
"toml_edit 0.22.14",
]
[[package]]
@ -5814,9 +5814,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.13"
version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
dependencies = [
"indexmap 2.2.6",
"serde",
@ -6016,6 +6016,7 @@ version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#f898578d80d3e2a54080c1c046c45f9eaa2435c3"
dependencies = [
"chrono",
"serde_json",
"thiserror",
"ts-rs-macros",
"url",
@ -6035,9 +6036,9 @@ dependencies = [
[[package]]
name = "tungstenite"
version = "0.21.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8"
dependencies = [
"byteorder",
"bytes",
@ -6046,11 +6047,10 @@ dependencies = [
"httparse",
"log",
"rand 0.8.5",
"rustls 0.22.4",
"rustls 0.23.7",
"rustls-pki-types",
"sha1",
"thiserror",
"url",
"utf-8",
]

View File

@ -74,5 +74,5 @@
}
},
"productName": "Zoo Modeling App",
"version": "0.22.0"
"version": "0.22.1"
}

View File

@ -48,12 +48,14 @@ export type ReactCameraProperties =
type: 'perspective'
fov?: number
position: [number, number, number]
target: [number, number, number]
quaternion: [number, number, number, number]
}
| {
type: 'orthographic'
zoom?: number
position: [number, number, number]
target: [number, number, number]
quaternion: [number, number, number, number]
}
@ -442,7 +444,7 @@ export class CameraControls {
this.handleEnd()
return
}
this.throttledEngCmd({
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
@ -454,11 +456,11 @@ export class CameraControls {
return
}
const isTrackpad = Math.abs(event.deltaY) <= 1 || event.deltaY % 1 === 0
// Else "clientToEngine" (Sketch Mode) or forceUpdate
const zoomSpeed = isTrackpad ? 0.02 : 0.1 // Reduced zoom speed for trackpad
// From onMouseMove zoom handling which seems to be really smooth
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
this.pendingZoom *= 1 + (event.deltaY > 0 ? zoomSpeed : -zoomSpeed)
this.pendingZoom *= 1 + event.deltaY * 0.01
this.handleEnd()
}
@ -773,6 +775,75 @@ export class CameraControls {
})
}
async updateCameraToAxis(
axis: 'x' | 'y' | 'z' | '-x' | '-y' | '-z'
): Promise<void> {
const distance = this.camera.position.distanceTo(this.target)
const vantage = this.target.clone()
let up = { x: 0, y: 0, z: 1 }
if (axis === 'x') {
vantage.x += distance
} else if (axis === 'y') {
vantage.y += distance
} else if (axis === 'z') {
vantage.z += distance
up = { x: -1, y: 0, z: 0 }
} else if (axis === '-x') {
vantage.x -= distance
} else if (axis === '-y') {
vantage.y -= distance
} else if (axis === '-z') {
vantage.z -= distance
up = { x: -1, y: 0, z: 0 }
}
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: this.target,
vantage: vantage,
up: up,
},
})
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
}
async resetCameraPosition(): Promise<void> {
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: this.target,
vantage: {
x: this.target.x,
y: this.target.y - 128,
z: this.target.z + 64,
},
up: { x: 0, y: 0, z: 1 },
},
})
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'zoom_to_fit',
object_ids: [], // leave empty to zoom to all objects
padding: 0.2, // padding around the objects
},
})
}
async tweenCameraToQuaternion(
targetQuaternion: Quaternion,
targetPosition = new Vector3(),
@ -957,6 +1028,11 @@ export class CameraControls {
roundOff(this.camera.position.y, 2),
roundOff(this.camera.position.z, 2),
],
target: [
roundOff(this.target.x, 2),
roundOff(this.target.y, 2),
roundOff(this.target.z, 2),
],
quaternion: [
roundOff(this.camera.quaternion.x, 2),
roundOff(this.camera.quaternion.y, 2),

View File

@ -699,6 +699,15 @@ export const CamDebugSettings = () => {
}
}}
/>
<div>
<button
onClick={() => {
sceneInfra.camControls.resetCameraPosition()
}}
>
Reset Camera Position
</button>
</div>
{camSettings.type === 'perspective' && (
<input
type="range"
@ -816,6 +825,71 @@ export const CamDebugSettings = () => {
</li>
</ul>
</div>
<div>
target
<ul className="flex">
<li>
<span className="pl-2 pr-1">x:</span>
<input
type="number"
step={5}
data-testid="cam-x-target"
value={camSettings.target[0]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.camControls.setCam({
...camSettings,
target: [
parseFloat(e.target.value),
camSettings.target[1],
camSettings.target[2],
],
})
}}
/>
</li>
<li>
<span className="pl-2 pr-1">y:</span>
<input
type="number"
step={5}
data-testid="cam-y-target"
value={camSettings.target[1]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.camControls.setCam({
...camSettings,
target: [
camSettings.target[0],
parseFloat(e.target.value),
camSettings.target[2],
],
})
}}
/>
</li>
<li>
<span className="pl-2 pr-1">z:</span>
<input
type="number"
step={5}
data-testid="cam-z-target"
value={camSettings.target[2]}
className="text-black w-16"
onChange={(e) => {
sceneInfra.camControls.setCam({
...camSettings,
target: [
camSettings.target[0],
camSettings.target[1],
parseFloat(e.target.value),
],
})
}}
/>
</li>
</ul>
</div>
</div>
)
}

View File

@ -51,14 +51,6 @@ function CommandBarSelectionInput({
inputRef.current?.focus()
}, [selection, inputRef])
// Exit engine's edit mode when this input step is active,
// and re-enter it when it's not.
// In future the engine's edit mode will go away and this will be handled differently.
useEffect(() => {
kclManager.exitEditMode()
return () => kclManager.defaultSelectionFilter()
}, [])
// Fast-forward through this arg if it's marked as skippable
// and we have a valid selection already
useEffect(() => {

View File

@ -0,0 +1,199 @@
import toast from 'react-hot-toast'
import { ActionIcon, ActionIconProps } from './ActionIcon'
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { Dialog } from '@headlessui/react'
interface ContextMenuProps
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
items?: React.ReactElement[]
menuTargetElement?: RefObject<HTMLElement>
}
const DefaultContextMenuItems = [
<ContextMenuItemRefresh />,
<ContextMenuItemCopy />,
// add more default context menu items here
]
export function ContextMenu({
items = DefaultContextMenuItems,
menuTargetElement,
className,
...props
}: ContextMenuProps) {
const dialogRef = useRef<HTMLDivElement>(null)
const [open, setOpen] = useState(false)
const [windowSize, setWindowSize] = useState({
width: globalThis?.window?.innerWidth,
height: globalThis?.window?.innerHeight,
})
const [position, setPosition] = useState({ x: 0, y: 0 })
useHotkeys('esc', () => setOpen(false), {
enabled: open,
})
const dialogPositionStyle = useMemo(() => {
if (!dialogRef.current)
return {
top: 0,
left: 0,
right: 'auto',
bottom: 'auto',
}
return {
top:
position.y + dialogRef.current.clientHeight > windowSize.height
? 'auto'
: position.y,
left:
position.x + dialogRef.current.clientWidth > windowSize.width
? 'auto'
: position.x,
right:
position.x + dialogRef.current.clientWidth > windowSize.width
? windowSize.width - position.x
: 'auto',
bottom:
position.y + dialogRef.current.clientHeight > windowSize.height
? windowSize.height - position.y
: 'auto',
}
}, [position, windowSize, dialogRef.current])
// Listen for window resize to update context menu position
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: globalThis?.window?.innerWidth,
height: globalThis?.window?.innerHeight,
})
}
globalThis?.window?.addEventListener('resize', handleResize)
return () => {
globalThis?.window?.removeEventListener('resize', handleResize)
}
}, [])
// Add context menu listener to target once mounted
useEffect(() => {
const handleContextMenu = (e: MouseEvent) => {
console.log('context menu', e)
e.preventDefault()
setPosition({ x: e.x, y: e.y })
setOpen(true)
}
menuTargetElement?.current?.addEventListener(
'contextmenu',
handleContextMenu
)
return () => {
menuTargetElement?.current?.removeEventListener(
'contextmenu',
handleContextMenu
)
}
}, [menuTargetElement?.current])
return (
<Dialog open={open} onClose={() => setOpen(false)}>
<div
className="fixed inset-0 z-50 w-screen h-screen"
onContextMenu={(e) => e.preventDefault()}
>
<Dialog.Backdrop className="fixed z-10 inset-0" />
<Dialog.Panel
ref={dialogRef}
className={`w-48 fixed bg-chalkboard-10 dark:bg-chalkboard-90
border border-solid border-chalkboard-10 dark:border-chalkboard-90 rounded
shadow-lg backdrop:fixed backdrop:inset-0 backdrop:bg-primary ${className}`}
style={{
...dialogPositionStyle,
...props.style,
}}
>
<ul
{...props}
className="relative flex flex-col gap-0.5 items-stretch content-stretch"
onClick={() => setOpen(false)}
>
{...items}
</ul>
</Dialog.Panel>
</div>
</Dialog>
)
}
export function ContextMenuDivider() {
return <hr className="border-chalkboard-20 dark:border-chalkboard-80" />
}
interface ContextMenuItemProps {
children: React.ReactNode
icon?: ActionIconProps['icon']
onClick?: () => void
hotkey?: string
}
export function ContextMenuItem({
children,
icon,
onClick,
hotkey,
}: ContextMenuItemProps) {
return (
<button
className="flex items-center gap-2 py-1 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
onClick={onClick}
>
{icon && <ActionIcon icon={icon} bgClassName="!bg-transparent" />}
<div className="flex-1">{children}</div>
{hotkey && (
<kbd className="px-1.5 py-0.5 rounded bg-primary/10 text-primary dark:bg-chalkboard-80 dark:text-chalkboard-40">
{hotkey}
</kbd>
)}
</button>
)
}
export function ContextMenuItemRefresh() {
return (
<ContextMenuItem
icon="arrowRotateRight"
onClick={() => globalThis?.window?.location.reload()}
>
Refresh
</ContextMenuItem>
)
}
interface ContextMenuItemCopyProps {
toBeCopiedContent?: string
toBeCopiedLabel?: string
}
export function ContextMenuItemCopy({
toBeCopiedContent = globalThis.window?.getSelection()?.toString(),
toBeCopiedLabel = 'selection',
}: ContextMenuItemCopyProps) {
return (
<ContextMenuItem
icon="clipboardPlus"
onClick={() => {
if (toBeCopiedContent) {
globalThis?.navigator?.clipboard
.writeText(toBeCopiedContent)
.then(() => toast.success(`Copied ${toBeCopiedLabel} to clipboard`))
.catch(() =>
toast.error(`Failed to copy ${toBeCopiedLabel} to clipboard`)
)
}
}}
>
Copy
</ContextMenuItem>
)
}

View File

@ -18,6 +18,8 @@ import { useLspContext } from './LspProvider'
import useHotkeyWrapper from 'lib/hotkeyWrapper'
import { useModelingContext } from 'hooks/useModelingContext'
import { DeleteConfirmationDialog } from './ProjectCard/DeleteProjectDialog'
import { ContextMenu, ContextMenuItem } from './ContextMenu'
import usePlatform from 'hooks/usePlatform'
function getIndentationCSS(level: number) {
return `calc(1rem * ${level + 1})`
@ -125,6 +127,7 @@ const FileTreeItem = ({
const navigate = useNavigate()
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
const isCurrentFile = fileOrDir.path === currentFile?.path
const itemRef = useRef(null)
const isRenaming = fileContext.itemsBeingRenamed.includes(fileOrDir.path)
const removeCurrentItemFromRenaming = useCallback(
@ -185,7 +188,7 @@ const FileTreeItem = ({
}
return (
<>
<div className="contents" ref={itemRef}>
{fileOrDir.children === undefined ? (
<li
className={
@ -321,7 +324,41 @@ const FileTreeItem = ({
setIsOpen={setIsConfirmingDelete}
/>
)}
</>
<FileTreeContextMenu
itemRef={itemRef}
onRename={addCurrentItemToRenaming}
onDelete={() => setIsConfirmingDelete(true)}
/>
</div>
)
}
interface FileTreeContextMenuProps {
itemRef: React.RefObject<HTMLElement>
onRename: () => void
onDelete: () => void
}
function FileTreeContextMenu({
itemRef,
onRename,
onDelete,
}: FileTreeContextMenuProps) {
const platform = usePlatform()
const metaKey = platform === 'macos' ? '⌘' : 'Ctrl'
return (
<ContextMenu
menuTargetElement={itemRef}
items={[
<ContextMenuItem onClick={onRename} hotkey="Enter">
Rename
</ContextMenuItem>,
<ContextMenuItem onClick={onDelete} hotkey={metaKey + ' + Del'}>
Delete
</ContextMenuItem>,
]}
/>
)
}

View File

@ -1,5 +1,6 @@
import { SceneInfra } from 'clientSideScene/sceneInfra'
import { sceneInfra } from 'lib/singletons'
import { useEffect, useRef } from 'react'
import { MutableRefObject, useEffect, useRef } from 'react'
import {
WebGLRenderer,
Scene,
@ -12,21 +13,52 @@ import {
Clock,
Quaternion,
ColorRepresentation,
Vector2,
Raycaster,
Camera,
Intersection,
Object3D,
} from 'three'
import {
ContextMenu,
ContextMenuDivider,
ContextMenuItem,
ContextMenuItemRefresh,
} from './ContextMenu'
const CANVAS_SIZE = 80
const FRUSTUM_SIZE = 0.5
const AXIS_LENGTH = 0.35
const AXIS_WIDTH = 0.02
const AXIS_COLORS = {
x: '#fa6668',
y: '#11eb6b',
z: '#6689ef',
gray: '#c6c7c2',
enum AxisColors {
X = '#fa6668',
Y = '#11eb6b',
Z = '#6689ef',
Gray = '#c6c7c2',
}
enum AxisNames {
X = 'x',
Y = 'y',
Z = 'z',
NEG_X = '-x',
NEG_Y = '-y',
NEG_Z = '-z',
}
const axisNamesSemantic: Record<AxisNames, string> = {
[AxisNames.X]: 'Right',
[AxisNames.Y]: 'Back',
[AxisNames.Z]: 'Top',
[AxisNames.NEG_X]: 'Left',
[AxisNames.NEG_Y]: 'Front',
[AxisNames.NEG_Z]: 'Bottom',
}
export default function Gizmo() {
const wrapperRef = useRef<HTMLDivElement | null>(null)
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
const cameraPassiveUpdateTimer = useRef(0)
const raycasterPassiveUpdateTimer = useRef(0)
useEffect(() => {
if (!canvasRef.current) return
@ -41,35 +73,89 @@ export default function Gizmo() {
const { gizmoAxes, gizmoAxisHeads } = createGizmo()
scene.add(...gizmoAxes, ...gizmoAxisHeads)
const raycaster = new Raycaster()
const { mouse, disposeMouseEvents } = initializeMouseEvents(
canvas,
raycasterIntersect,
sceneInfra
)
const raycasterObjects = [...gizmoAxisHeads]
const clock = new Clock()
const clientCamera = sceneInfra.camControls.camera
let currentQuaternion = new Quaternion().copy(clientCamera.quaternion)
const animate = () => {
requestAnimationFrame(animate)
const delta = clock.getDelta()
updateCameraOrientation(
camera,
currentQuaternion,
sceneInfra.camControls.camera.quaternion,
clock.getDelta()
delta,
cameraPassiveUpdateTimer
)
updateRayCaster(
raycasterObjects,
raycaster,
mouse,
camera,
raycasterIntersect,
delta,
raycasterPassiveUpdateTimer
)
renderer.render(scene, camera)
requestAnimationFrame(animate)
}
animate()
return () => {
renderer.dispose()
disposeMouseEvents()
}
}, [])
return (
<div className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-none">
<canvas ref={canvasRef} />
</div>
<>
<div
ref={wrapperRef}
aria-label="View orientation gizmo"
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto"
>
<canvas ref={canvasRef} />
<ContextMenu
menuTargetElement={wrapperRef}
items={[
...Object.entries(axisNamesSemantic).map(
([axisName, axisSemantic]) => (
<ContextMenuItem
key={axisName}
onClick={() => {
sceneInfra.camControls.updateCameraToAxis(
axisName as AxisNames
)
}}
>
{axisSemantic} view
</ContextMenuItem>
)
),
<ContextMenuItem
onClick={() => {
sceneInfra.camControls.resetCameraPosition()
}}
>
Reset view
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItemRefresh />,
]}
/>
</div>
</>
)
}
const createCamera = () => {
const createCamera = (): OrthographicCamera => {
return new OrthographicCamera(
-FRUSTUM_SIZE,
FRUSTUM_SIZE,
@ -82,21 +168,21 @@ const createCamera = () => {
const createGizmo = () => {
const gizmoAxes = [
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.x, 0, 'z'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.y, Math.PI / 2, 'z'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.z, -Math.PI / 2, 'y'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.gray, Math.PI, 'z'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.gray, -Math.PI / 2, 'z'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.gray, Math.PI / 2, 'y'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.X, 0, 'z'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Y, Math.PI / 2, 'z'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Z, -Math.PI / 2, 'y'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Gray, Math.PI, 'z'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Gray, -Math.PI / 2, 'z'),
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Gray, Math.PI / 2, 'y'),
]
const gizmoAxisHeads = [
createAxisHead(AXIS_LENGTH, AXIS_COLORS.x, 0, 'z'),
createAxisHead(AXIS_LENGTH, AXIS_COLORS.y, Math.PI / 2, 'z'),
createAxisHead(AXIS_LENGTH, AXIS_COLORS.z, -Math.PI / 2, 'y'),
createAxisHead(AXIS_LENGTH, AXIS_COLORS.gray, Math.PI, 'z'),
createAxisHead(AXIS_LENGTH, AXIS_COLORS.gray, -Math.PI / 2, 'z'),
createAxisHead(AXIS_LENGTH, AXIS_COLORS.gray, Math.PI / 2, 'y'),
createAxisHead(AxisNames.X, AxisColors.X, [AXIS_LENGTH, 0, 0]),
createAxisHead(AxisNames.Y, AxisColors.Y, [0, AXIS_LENGTH, 0]),
createAxisHead(AxisNames.Z, AxisColors.Z, [0, 0, AXIS_LENGTH]),
createAxisHead(AxisNames.NEG_X, AxisColors.Gray, [-AXIS_LENGTH, 0, 0]),
createAxisHead(AxisNames.NEG_Y, AxisColors.Gray, [0, -AXIS_LENGTH, 0]),
createAxisHead(AxisNames.NEG_Z, AxisColors.Gray, [0, 0, -AXIS_LENGTH]),
]
return { gizmoAxes, gizmoAxisHeads }
@ -108,12 +194,9 @@ const createAxis = (
color: ColorRepresentation,
rotation = 0,
axis = 'x'
) => {
const geometry = new BoxGeometry(length, width, width).translate(
length / 2,
0,
0
)
): Mesh => {
const geometry = new BoxGeometry(length, width, width)
geometry.translate(length / 2, 0, 0)
const material = new MeshBasicMaterial({ color: new Color(color) })
const mesh = new Mesh(geometry, material)
mesh.rotation[axis as 'x' | 'y' | 'z'] = rotation
@ -121,15 +204,17 @@ const createAxis = (
}
const createAxisHead = (
length: number,
name: AxisNames,
color: ColorRepresentation,
rotation = 0,
axis = 'x'
) => {
const geometry = new SphereGeometry(0.065, 16, 8).translate(length, 0, 0)
position: number[]
): Mesh => {
const geometry = new SphereGeometry(0.065, 16, 8)
const material = new MeshBasicMaterial({ color: new Color(color) })
const mesh = new Mesh(geometry, material)
mesh.rotation[axis as 'x' | 'y' | 'z'] = rotation
mesh.position.set(position[0], position[1], position[2])
mesh.updateMatrixWorld()
mesh.name = name
return mesh
}
@ -137,10 +222,97 @@ const updateCameraOrientation = (
camera: OrthographicCamera,
currentQuaternion: Quaternion,
targetQuaternion: Quaternion,
deltaTime: number
deltaTime: number,
cameraPassiveUpdateTimer: MutableRefObject<number>
) => {
const slerpFactor = 1 - Math.exp(-30 * deltaTime)
currentQuaternion.slerp(targetQuaternion, slerpFactor).normalize()
camera.position.set(0, 0, 1).applyQuaternion(currentQuaternion)
camera.quaternion.copy(currentQuaternion)
cameraPassiveUpdateTimer.current += deltaTime
if (
!quaternionsEqual(currentQuaternion, targetQuaternion) ||
cameraPassiveUpdateTimer.current >= 5
) {
const slerpFactor = 1 - Math.exp(-30 * deltaTime)
currentQuaternion.slerp(targetQuaternion, slerpFactor).normalize()
camera.position.set(0, 0, 1).applyQuaternion(currentQuaternion)
camera.quaternion.copy(currentQuaternion)
cameraPassiveUpdateTimer.current = 0
}
}
const quaternionsEqual = (
q1: Quaternion,
q2: Quaternion,
tolerance: number = 0.001
): boolean => {
return (
Math.abs(q1.x - q2.x) < tolerance &&
Math.abs(q1.y - q2.y) < tolerance &&
Math.abs(q1.z - q2.z) < tolerance &&
Math.abs(q1.w - q2.w) < tolerance
)
}
const initializeMouseEvents = (
canvas: HTMLCanvasElement,
raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>,
sceneInfra: SceneInfra
): { mouse: Vector2; disposeMouseEvents: () => void } => {
const mouse = new Vector2()
mouse.x = 1 // fix initial mouse position issue
const handleMouseMove = (event: MouseEvent) => {
const { left, top, width, height } = canvas.getBoundingClientRect()
mouse.x = ((event.clientX - left) / width) * 2 - 1
mouse.y = ((event.clientY - top) / height) * -2 + 1
}
const handleClick = () => {
if (raycasterIntersect.current) {
const axisName = raycasterIntersect.current.object.name as AxisNames
sceneInfra.camControls.updateCameraToAxis(axisName)
}
}
window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('click', handleClick)
const disposeMouseEvents = () => {
window.removeEventListener('mousemove', handleMouseMove)
window.removeEventListener('click', handleClick)
}
return { mouse, disposeMouseEvents }
}
const updateRayCaster = (
objects: Object3D[],
raycaster: Raycaster,
mouse: Vector2,
camera: Camera,
raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>,
deltaTime: number,
raycasterPassiveUpdateTimer: MutableRefObject<number>
) => {
raycasterPassiveUpdateTimer.current += deltaTime
// check if mouse is outside the canvas bounds and stop raycaster
if (raycasterPassiveUpdateTimer.current < 2) {
if (mouse.x < -1 || mouse.x > 1 || mouse.y < -1 || mouse.y > 1) {
raycasterIntersect.current = null
return
}
}
raycaster.setFromCamera(mouse, camera)
const intersects = raycaster.intersectObjects(objects)
objects.forEach((object) => object.scale.set(1, 1, 1))
if (intersects.length) {
intersects[0].object.scale.set(1.5, 1.5, 1.5)
raycasterIntersect.current = intersects[0] // filter first object
} else {
raycasterIntersect.current = null
}
if (raycasterPassiveUpdateTimer.current > 2) {
raycasterPassiveUpdateTimer.current = 0
}
}

View File

@ -365,13 +365,6 @@ export class KclManager {
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
exitEditMode() {
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
}
defaultSelectionFilter() {
defaultSelectionFilter(this.programMemory, this.engineCommandManager)
}
@ -386,24 +379,11 @@ function defaultSelectionFilter(
) as SketchGroup | ExtrudeGroup
firstSketchOrExtrudeGroup &&
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_batch_req',
batch_id: uuidv4(),
responses: false,
requests: [
{
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: firstSketchOrExtrudeGroup.id,
},
},
{
cmd_id: uuidv4(),
cmd: {
type: 'set_selection_filter',
filter: ['face', 'edge', 'solid2d'],
},
},
],
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_selection_filter',
filter: ['face', 'edge', 'solid2d', 'curve'],
},
})
}

View File

@ -147,7 +147,6 @@ export enum ConnectionError {
Unset = 0,
LongLoadingTime,
LostVideoStream,
ICENegotiate,
DataChannelError,
WebSocketError,
@ -168,8 +167,6 @@ export const CONNECTION_ERROR_TEXT: Record<ConnectionError, string> = {
[ConnectionError.Unset]: '',
[ConnectionError.LongLoadingTime]:
'Loading is taking longer than expected...',
[ConnectionError.LostVideoStream]:
'Lost connection to video stream... Reconnecting...',
[ConnectionError.ICENegotiate]: 'ICE negotiation failed.',
[ConnectionError.DataChannelError]: 'The data channel signaled an error.',
[ConnectionError.WebSocketError]: 'The websocket signaled an error.',
@ -315,8 +312,6 @@ class EngineConnection extends EventTarget {
if (next.type === EngineConnectionStateType.Disconnecting) {
const sub = next.value
if (sub.type === DisconnectingType.Error) {
console.log(sub)
// Record the last step we failed at.
// (Check the current state that we're about to override that
// it was a Connecting state.)
@ -759,8 +754,6 @@ class EngineConnection extends EventTarget {
// when assuming we're the only consumer or that all messages will
// be carefully formatted here.
console.log(event)
if (typeof event.data !== 'string') {
return
}
@ -781,7 +774,6 @@ class EngineConnection extends EventTarget {
`Error in response to request ${message.request_id}:\n${errorsString}
failed cmd type was ${artifactThatFailed?.commandType}`
)
console.log(artifactThatFailed)
} else {
console.error(`Error from server:\n${errorsString}`)
}
@ -872,7 +864,6 @@ class EngineConnection extends EventTarget {
this.pc
?.createOffer()
.then((offer: RTCSessionDescriptionInit) => {
console.log(offer)
this.state = {
type: EngineConnectionStateType.Connecting,
value: {
@ -944,7 +935,6 @@ class EngineConnection extends EventTarget {
case 'trickle_ice':
let candidate = resp.data?.candidate
console.log('trickle_ice: using this candidate: ', candidate)
void this.pc?.addIceCandidate(candidate as RTCIceCandidateInit)
break
@ -1347,20 +1337,10 @@ export class EngineCommandManager extends EventTarget {
this.engineConnection?.addEventListener(
EngineConnectionEvents.NewTrack,
(({ detail: { mediaStream } }: CustomEvent<NewTrackArgs>) => {
console.log('received track', mediaStream)
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
if (this.engineConnection) {
this.engineConnection.state = {
type: EngineConnectionStateType.Disconnecting,
value: {
type: DisconnectingType.Error,
value: {
error: ConnectionError.LostVideoStream,
},
},
}
}
console.error(
'video track mute: check webrtc internals -> inbound rtp'
)
})
setMediaStream(mediaStream)
@ -1673,7 +1653,6 @@ export class EngineCommandManager extends EventTarget {
(command.cmd.type === 'highlight_set_entity' ||
command.cmd.type === 'mouse_move' ||
command.cmd.type === 'camera_drag_move' ||
command.cmd.type === 'default_camera_look_at' ||
command.cmd.type === ('default_camera_perspective_settings' as any))
)
) {
@ -1688,7 +1667,6 @@ export class EngineCommandManager extends EventTarget {
command.type === 'modeling_cmd_req' &&
command.cmd.type !== lastMessage
) {
console.log('sending command', command.cmd.type)
lastMessage = command.cmd.type
}
if (command.type === 'modeling_cmd_batch_req') {
@ -1702,7 +1680,6 @@ export class EngineCommandManager extends EventTarget {
if (
(cmd.type === 'camera_drag_move' ||
cmd.type === 'handle_mouse_drag_move' ||
cmd.type === 'default_camera_look_at' ||
cmd.type === ('default_camera_perspective_settings' as any)) &&
this.engineConnection?.unreliableDataChannel &&
!forceWebsocket

View File

@ -25,7 +25,7 @@ import type { Program } from '../wasm-lib/kcl/bindings/Program'
import type { Token } from '../wasm-lib/kcl/bindings/Token'
import { Coords2d } from './std/sketch'
import { fileSystemManager } from 'lang/std/fileSystemManager'
import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo'
import { CoreDumpInfo } from 'wasm-lib/kcl/bindings/CoreDumpInfo'
import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
@ -335,9 +335,9 @@ export function programMemoryInit(): ProgramMemory {
export async function coreDump(
coreDumpManager: CoreDumpManager,
openGithubIssue: boolean = false
): Promise<AppInfo> {
): Promise<CoreDumpInfo> {
try {
const dump: AppInfo = await coredump(coreDumpManager)
const dump: CoreDumpInfo = await coredump(coreDumpManager)
if (openGithubIssue && dump.github_issue_url) {
openWindow(dump.github_issue_url)
}

View File

@ -144,6 +144,282 @@ export class CoreDumpManager {
})
}
// Currently just a placeholder to begin loading singleton and xstate data into
getClientState(): Promise<string> {
/**
* Deep clone a JavaScript Object
* - NOTE: this function thows on parse errors from things like circular references
* - It is also syncronous and could be more performant
* - There is a whole rabbit hole to explore here if you like.
* - This works for our use case.
* @param {object} obj - The object to clone.
*/
const deepClone = (obj: any) => JSON.parse(JSON.stringify(obj))
/**
* Check if a function is private method
*/
const isPrivateMethod = (key: string) => {
return key.length && key[0] === '_'
}
console.warn('CoreDump: Gathering client state')
// Initialize the clientState object
let clientState = {
// singletons
engine_command_manager: {
artifact_map: {},
command_logs: [],
engine_connection: { state: { type: '' } },
default_planes: {},
scene_command_artifacts: {},
},
kcl_manager: {
ast: {},
kcl_errors: [],
},
scene_infra: {},
scene_entities_manager: {},
editor_manager: {},
// xstate
auth_machine: {},
command_bar_machine: {},
file_machine: {},
home_machine: {},
modeling_machine: {},
settings_machine: {},
}
console.log('CoreDump: initialized clientState', clientState)
console.info('CoreDump: globalThis.window', globalThis.window)
try {
// Singletons
// engine_command_manager
console.log('CoreDump: engineCommandManager', this.engineCommandManager)
// artifact map - this.engineCommandManager.artifactMap
if (this.engineCommandManager?.artifactMap) {
console.log(
'CoreDump: Engine Command Manager artifact map',
this.engineCommandManager.artifactMap
)
clientState.engine_command_manager.artifact_map = deepClone(
this.engineCommandManager.artifactMap
)
}
// command logs - this.engineCommandManager.commandLogs
if (this.engineCommandManager?.commandLogs) {
console.log(
'CoreDump: Engine Command Manager command logs',
this.engineCommandManager.commandLogs
)
clientState.engine_command_manager.command_logs = deepClone(
this.engineCommandManager.commandLogs
)
}
// default planes - this.engineCommandManager.defaultPlanes
if (this.engineCommandManager?.defaultPlanes) {
console.log(
'CoreDump: Engine Command Manager default planes',
this.engineCommandManager.defaultPlanes
)
clientState.engine_command_manager.default_planes = deepClone(
this.engineCommandManager.defaultPlanes
)
}
// engine connection state
if (this.engineCommandManager?.engineConnection?.state) {
clientState.engine_command_manager.engine_connection.state =
this.engineCommandManager.engineConnection.state
console.log(
'CoreDump: Engine Command Manager engine connection state',
this.engineCommandManager.engineConnection.state
)
}
// in sequence - this.engineCommandManager.inSequence
if (this.engineCommandManager?.inSequence) {
console.log(
'CoreDump: Engine Command Manager in sequence',
this.engineCommandManager.inSequence
)
clientState.engine_command_manager.in_sequence =
this.engineCommandManager.inSequence
}
// out sequence - this.engineCommandManager.outSequence
if (this.engineCommandManager?.inSequence) {
console.log(
'CoreDump: Engine Command Manager out sequence',
this.engineCommandManager.outSequence
)
clientState.engine_command_manager.out_sequence =
this.engineCommandManager.inSequence
}
// scene command artifacts - this.engineCommandManager.sceneCommandArtifacts
if (this.engineCommandManager?.sceneCommandArtifacts) {
console.log(
'CoreDump: Engine Command Manager scene command artifacts',
this.engineCommandManager.sceneCommandArtifacts
)
clientState.engine_command_manager.scene_command_artifacts = deepClone(
this.engineCommandManager.sceneCommandArtifacts
)
}
// KCL Manager - globalThis?.window?.kclManager
const kclManager = globalThis?.window?.kclManager
console.log('CoreDump: kclManager', kclManager)
if (kclManager) {
// KCL Manager AST
console.log('CoreDump: KCL Manager AST', kclManager?.ast)
if (kclManager?.ast) {
clientState.kcl_manager.ast = deepClone(kclManager.ast)
}
// KCL Errors
console.log('CoreDump: KCL Errors', kclManager?.kclErrors)
if (kclManager?.kclErrors) {
clientState.kcl_manager.kcl_errors = deepClone(kclManager.kclErrors)
}
// KCL isExecuting
console.log('CoreDump: KCL isExecuting', kclManager?.isExecuting)
if (kclManager?.isExecuting) {
clientState.kcl_manager.isExecuting = kclManager.isExecuting
}
// KCL logs
console.log('CoreDump: KCL logs', kclManager?.logs)
if (kclManager?.logs) {
clientState.kcl_manager.logs = deepClone(kclManager.logs)
}
// KCL programMemory
console.log('CoreDump: KCL programMemory', kclManager?.programMemory)
if (kclManager?.programMemory) {
clientState.kcl_manager.programMemory = deepClone(
kclManager.programMemory
)
}
// KCL wasmInitFailed
console.log('CoreDump: KCL wasmInitFailed', kclManager?.wasmInitFailed)
if (kclManager?.wasmInitFailed) {
clientState.kcl_manager.wasmInitFailed = kclManager.wasmInitFailed
}
}
// Scene Infra - globalThis?.window?.sceneInfra
const sceneInfra = globalThis?.window?.sceneInfra
console.log('CoreDump: Scene Infra', sceneInfra)
if (sceneInfra) {
const sceneInfraSkipKeys = ['camControls']
const sceneInfraKeys = Object.keys(sceneInfra)
.sort()
.filter((entry) => {
return (
typeof sceneInfra[entry] !== 'function' &&
!sceneInfraSkipKeys.includes(entry)
)
})
console.log('CoreDump: Scene Infra keys', sceneInfraKeys)
sceneInfraKeys.forEach((key: string) => {
console.log('CoreDump: Scene Infra', key, sceneInfra[key])
try {
clientState.scene_infra[key] = sceneInfra[key]
} catch (error) {
console.error(
'CoreDump: unable to parse Scene Infra ' + key + ' data due to ',
error
)
}
})
}
// Scene Entities Manager - globalThis?.window?.sceneEntitiesManager
const sceneEntitiesManager = globalThis?.window?.sceneEntitiesManager
console.log('CoreDump: sceneEntitiesManager', sceneEntitiesManager)
if (sceneEntitiesManager) {
// Scene Entities Manager active segments
console.log(
'CoreDump: Scene Entities Manager active segments',
sceneEntitiesManager?.activeSegments
)
if (sceneEntitiesManager?.activeSegments) {
clientState.scene_entities_manager.activeSegments = deepClone(
sceneEntitiesManager.activeSegments
)
}
}
// Editor Manager - globalThis?.window?.editorManager
const editorManager = globalThis?.window?.editorManager
console.log('CoreDump: editorManager', editorManager)
if (editorManager) {
const editorManagerSkipKeys = ['camControls']
const editorManagerKeys = Object.keys(editorManager)
.sort()
.filter((entry) => {
return (
typeof editorManager[entry] !== 'function' &&
!isPrivateMethod(entry) &&
!editorManagerSkipKeys.includes(entry)
)
})
console.log('CoreDump: Editor Manager keys', editorManagerKeys)
editorManagerKeys.forEach((key: string) => {
console.log('CoreDump: Editor Manager', key, editorManager[key])
try {
clientState.editor_manager[key] = deepClone(editorManager[key])
} catch (error) {
console.error(
'CoreDump: unable to parse Editor Manager ' +
key +
' data due to ',
error
)
}
})
}
// enableMousePositionLogs - Not coredumped
// See https://github.com/KittyCAD/modeling-app/issues/2338#issuecomment-2136441998
console.log(
'CoreDump: enableMousePositionLogs [not coredumped]',
globalThis?.window?.enableMousePositionLogs
)
// XState Machines
console.log(
'CoreDump: xstate services',
globalThis?.window?.__xstate__?.services
)
console.log('CoreDump: final clientState', clientState)
const clientStateJson = JSON.stringify(clientState)
console.log('CoreDump: final clientState JSON', clientStateJson)
return Promise.resolve(clientStateJson)
} catch (error) {
console.error('CoreDump: unable to return data due to ', error)
return Promise.reject(JSON.stringify(error))
}
}
// Return a data URL (png format) of the screenshot of the current page.
screenshot(): Promise<string> {
return screenshot(this.htmlRef)

View File

@ -1152,9 +1152,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.28"
version = "0.14.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33"
dependencies = [
"bytes",
"futures-channel",
@ -2037,9 +2037,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.4"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
@ -2975,9 +2975,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.13"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba"
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
dependencies = [
"serde",
"serde_spanned",
@ -2996,9 +2996,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.13"
version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
dependencies = [
"indexmap 2.2.5",
"serde",
@ -3158,9 +3158,10 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "ts-rs"
version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#badbac08e61e65b312880aa64e9ece2976f1bbef"
source = "git+https://github.com/Aleph-Alpha/ts-rs#be0349d5fb07a8ccab713887a61e90e3bc773c7a"
dependencies = [
"chrono",
"serde_json",
"thiserror",
"ts-rs-macros",
"url",
@ -3170,7 +3171,7 @@ dependencies = [
[[package]]
name = "ts-rs-macros"
version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#badbac08e61e65b312880aa64e9ece2976f1bbef"
source = "git+https://github.com/Aleph-Alpha/ts-rs#be0349d5fb07a8ccab713887a61e90e3bc773c7a"
dependencies = [
"proc-macro2",
"quote",
@ -3444,6 +3445,7 @@ dependencies = [
"console_error_panic_hook",
"futures",
"gloo-utils",
"hyper",
"image",
"js-sys",
"kcl-lib",

View File

@ -17,13 +17,14 @@ kcl-lib = { path = "kcl" }
kittycad = { workspace = true }
serde_json = "1.0.116"
tokio = { version = "1.38.0", features = ["sync"] }
toml = "0.8.13"
toml = "0.8.14"
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }
wasm-bindgen = "0.2.91"
wasm-bindgen-futures = "0.4.42"
[dev-dependencies]
anyhow = "1"
hyper = { version = "0.14.29", features = ["server", "http1"] }
image = { version = "0.25.1", default-features = false, features = ["png"] }
kittycad = { workspace = true, default-features = true }
pretty_assertions = "1.4.0"

View File

@ -1,24 +0,0 @@
[package]
name = "grackle"
version = "0.1.0"
edition = "2021"
description = "A new executor for KCL which compiles to Execution Plans"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = { version = "0.25.1", default-features = false, features = ["png"] }
kcl-lib = { path = "../kcl" }
kittycad = { workspace = true }
kittycad-execution-plan = { workspace = true }
kittycad-execution-plan-traits = { workspace = true }
kittycad-execution-plan-macros = { workspace = true }
kittycad-modeling-cmds = { workspace = true }
kittycad-modeling-session = { workspace = true }
thiserror = "1.0.61"
tokio = { version = "1.37.0", features = ["macros", "rt"] }
twenty-twenty = "0.8.0"
uuid = "1.8"
[dev-dependencies]
pretty_assertions = "1"
serde_json = "1.0.116"

View File

@ -35,9 +35,9 @@ serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.116"
sha2 = "0.10.8"
thiserror = "1.0.61"
toml = "0.8.13"
toml = "0.8.14"
# TODO: change this to a cargo release once 8.1.1 comes out
ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings"] }
ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
url = { version = "2.5.0", features = ["serde"] }
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }
validator = { version = "0.18.1", features = ["derive"] }

View File

@ -3,6 +3,7 @@
use anyhow::Result;
use crate::coredump::CoreDump;
use serde_json::Value as JValue;
#[derive(Debug, Clone)]
pub struct CoreDumper {}
@ -55,6 +56,10 @@ impl CoreDump for CoreDumper {
Ok(crate::coredump::WebrtcStats::default())
}
async fn get_client_state(&self) -> Result<JValue> {
Ok(JValue::default())
}
async fn screenshot(&self) -> Result<String> {
// Take a screenshot of the engine.
todo!()

View File

@ -9,6 +9,9 @@ use anyhow::Result;
use base64::Engine;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// "Value" would be OK. This is imported as "JValue" throughout the rest of this crate.
use serde_json::Value as JValue;
use uuid::Uuid;
#[async_trait::async_trait(?Send)]
pub trait CoreDump: Clone {
@ -27,6 +30,8 @@ pub trait CoreDump: Clone {
async fn get_webrtc_stats(&self) -> Result<WebrtcStats>;
async fn get_client_state(&self) -> Result<JValue>;
/// Return a screenshot of the app.
async fn screenshot(&self) -> Result<String>;
@ -53,19 +58,21 @@ pub trait CoreDump: Clone {
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
if links.is_empty() {
anyhow::bail!("Failed to upload screenshot");
anyhow::bail!("Failed to upload coredump");
}
Ok(links[0].clone())
}
/// Dump the app info.
async fn dump(&self) -> Result<AppInfo> {
let webrtc_stats = self.get_webrtc_stats().await?;
let os = self.os().await?;
let screenshot_url = self.upload_screenshot().await?;
async fn dump(&self) -> Result<CoreDumpInfo> {
let os: OsInfo = self.os().await?;
let webrtc_stats: WebrtcStats = self.get_webrtc_stats().await?;
let client_state: JValue = self.get_client_state().await?;
let screenshot_url: String = self.upload_screenshot().await?;
let mut app_info = AppInfo {
let mut core_dump_info: CoreDumpInfo = CoreDumpInfo {
id: uuid::Uuid::new_v4(),
version: self.version()?,
git_rev: git_rev::try_revision_string!().map_or_else(|| "unknown".to_string(), |s| s.to_string()),
timestamp: chrono::Utc::now(),
@ -74,18 +81,48 @@ pub trait CoreDump: Clone {
webrtc_stats,
github_issue_url: None,
pool: self.pool()?,
client_state,
};
app_info.set_github_issue_url(&screenshot_url)?;
Ok(app_info)
// (Re)Create the zoo client.
let mut zooClient = kittycad::Client::new(self.token()?);
zooClient.set_base_url(&self.base_api_url()?);
let json = serde_json::to_string_pretty(&core_dump_info).unwrap();
// Upload the coredump.
let result: Vec<String> = zooClient
.meta()
.create_debug_uploads(vec![kittycad::types::multipart::Attachment {
name: "".to_string(),
filename: Some(format!(r#"modeling-app/coredump-{}.json)"#, core_dump_info.id,)),
content_type: Some("application/json".to_string()),
data: json.into(),
}])
.await?;
if result.is_empty() {
anyhow::bail!("Failed to upload coredump");
}
println!("{:?}", result);
let coredump_url: String = result[0].clone();
core_dump_info.set_github_issue_url(&screenshot_url, &coredump_url)?;
Ok(core_dump_info)
}
}
/// The app info structure.
/// The Core Dump Info structure.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct AppInfo {
pub struct CoreDumpInfo {
/// The unique id for the coredump - this helps correlate screenshot with coredump data
pub id: Uuid,
/// The version of the app.
pub version: String,
/// The git revision of the app.
@ -109,11 +146,14 @@ pub struct AppInfo {
/// Engine pool the client is connected to.
pub pool: String,
/// The client state (singletons and xstate)
pub client_state: JValue,
}
impl AppInfo {
impl CoreDumpInfo {
/// Set the github issue url.
pub fn set_github_issue_url(&mut self, screenshot_url: &str) -> Result<()> {
pub fn set_github_issue_url(&mut self, screenshot_url: &str, coredump_url: &str) -> Result<()> {
let tauri_or_browser_label = if self.tauri { "tauri" } else { "browser" };
let labels = ["coredump", "bug", tauri_or_browser_label];
let body = format!(
@ -121,16 +161,11 @@ impl AppInfo {
![Screenshot]({})
<details>
<summary><b>Core Dump</b></summary>
```json
{}
```
</details>
![Coredump]({})
"#,
screenshot_url,
serde_json::to_string_pretty(&self)?
screenshot_url, coredump_url
);
let urlencoded: String = form_urlencoded::byte_serialize(body.as_bytes()).collect();

View File

@ -4,6 +4,7 @@ use anyhow::Result;
use wasm_bindgen::prelude::wasm_bindgen;
use crate::{coredump::CoreDump, wasm::JsFuture};
use serde_json::Value as JValue;
#[wasm_bindgen(module = "/../../lib/coredump.ts")]
extern "C" {
@ -31,6 +32,9 @@ extern "C" {
#[wasm_bindgen(method, js_name = getWebrtcStats, catch)]
fn get_webrtc_stats(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
#[wasm_bindgen(method, js_name = getClientState, catch)]
fn get_client_state(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
#[wasm_bindgen(method, js_name = screenshot, catch)]
fn screenshot(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
}
@ -123,6 +127,27 @@ impl CoreDump for CoreDumper {
Ok(stats)
}
async fn get_client_state(&self) -> Result<JValue> {
let promise = self
.manager
.get_client_state()
.map_err(|e| anyhow::anyhow!("Failed to get promise from get client state: {:?}", e))?;
let value = JsFuture::from(promise)
.await
.map_err(|e| anyhow::anyhow!("Failed to get response from client state: {:?}", e))?;
// Parse the value as a string.
let s = value
.as_string()
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from client stat: `{:?}`", value))?;
let client_state: JValue =
serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse client state: {:?}", e))?;
Ok(client_state)
}
async fn screenshot(&self) -> Result<String> {
let promise = self
.manager

View File

@ -11,7 +11,7 @@ use serde_json::Value as JValue;
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
use crate::{
ast::types::{BodyItem, FunctionExpression, KclNone, Value},
ast::types::{BodyItem, FunctionExpression, KclNone, Program, Value},
engine::EngineManager,
errors::{KclError, KclErrorDetails},
fs::FileManager,
@ -975,6 +975,8 @@ impl Default for PipeInfo {
}
/// The executor context.
/// Cloning will return another handle to the same engine connection/session,
/// as this uses `Arc` under the hood.
#[derive(Debug, Clone)]
pub struct ExecutorContext {
pub engine: Arc<Box<dyn EngineManager>>,
@ -1310,6 +1312,43 @@ impl ExecutorContext {
pub fn update_units(&mut self, units: crate::settings::types::UnitLength) {
self.settings.units = units;
}
/// Execute the program, then get a PNG screenshot.
pub async fn execute_and_prepare_snapshot(&self, program: Program) -> Result<kittycad::types::TakeSnapshot> {
let _ = self.run(program, None).await?;
// Zoom to fit.
self.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await?;
// Send a snapshot request to the engine.
let resp = self
.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::TakeSnapshot {
format: kittycad::types::ImageFormat::Png,
},
)
.await?;
let kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
} = resp
else {
anyhow::bail!("Unexpected response from engine: {:?}", resp);
};
Ok(data)
}
}
/// For each argument given,

View File

@ -200,6 +200,24 @@ pub async fn revolve(args: Args) -> Result<MemoryItem, KclError> {
/// axis: getOppositeEdge('revolveAxis', box)
/// }, %)
/// ```
///
/// ```no_run
/// const sketch001 = startSketchOn('XY')
/// |> startProfileAt([10, 0], %)
/// |> line([5, -5], %)
/// |> line([5, 5], %)
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
/// |> close(%)
///
/// const part001 = revolve({
/// axis: {
/// custom: {
/// axis: [0.0, 1.0, 0.0],
/// origin: [0.0, 0.0, 0.0]
/// }
/// }
/// }, sketch001)
/// ```
#[stdlib {
name = "revolve",
}]

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View File

@ -0,0 +1,316 @@
{
"version": "0.20.1",
"git_rev": "3a05211d306ca045ace2e7bf10b7f8138e1daad5",
"timestamp": "2024-05-07T20:06:34.655Z",
"tauri": false,
"os": {
"platform": "Mac OS",
"version": "10.15.7",
"browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
},
"webrtc_stats": {
"packets_lost": 0,
"frames_received": 672,
"frame_width": 1440.0,
"frame_height": 712.0,
"frame_rate": 58.0,
"key_frames_decoded": 7,
"frames_dropped": 77,
"pause_count": 0,
"total_pauses_duration": 0.0,
"freeze_count": 12,
"total_freezes_duration": 3.057,
"pli_count": 6,
"jitter": 0.011
},
"pool": "",
"client_state": {
"engine_command_manager": {
"artifact_map": {
"ac7a8c52-7437-42e6-ae2a-25c54d0b4a16": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"83a2e866-d8e1-47d9-afae-d55a64cd5b40": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "plane_set_color",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"c92d7e84-a03e-456e-aad0-3e302ae0ffb6": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"a9abb9b8-54b1-4042-8ab3-e67c7ddb4cb3": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "plane_set_color",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"bafb884d-ee93-48f5-a667-91d1bdb8178a": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"6b7f539b-c4ca-4e62-a971-147e1abaca7b": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "plane_set_color",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"6ddaaaa3-b080-4cb3-b08e-8fe277312ccc": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"f1ef28b0-49b3-45f8-852d-772648149785": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"7c532be8-f07d-456b-8643-53808df86823": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "79cc6dfc-7205-41b2-922f-0d0279f5a30d",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"cf38f826-6d15-45f4-9c64-a6e9e4909e75": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"d69e6248-9e0f-4bc8-bc6e-d995931e8886": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "plane_set_color",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"cdb44075-ac6d-4626-b321-26d022f5f7f0": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"44f81391-0f2d-49f3-92b9-7dfc8446d571": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "plane_set_color",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"7b893ecf-8887-49c3-b978-392813d5f625": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"61247d28-04ba-4768-85b0-fbc582151dbd": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "plane_set_color",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"1318f5af-76a9-4161-9f6a-d410831fd098": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"908d264c-5989-4030-b2f4-5dfb52fda71a": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
},
"0de4c253-ee21-4385-93bd-b93264f7eb60": {
"type": "result",
"range": [0, 0],
"pathToNode": [],
"commandType": "make_plane",
"data": { "type": "empty" },
"raw": {
"success": true,
"request_id": "91f1be72-4267-40a4-9923-9c5b5a0ec29c",
"resp": {
"type": "modeling",
"data": { "modeling_response": { "type": "empty" } }
}
}
}
},
"engine_connection": {
"state": {
"type": "connection-established"
}
}
},
"kcl_manager": {},
"scene_infra": {},
"auth_machine": {},
"command_bar_machine": {},
"file_machine": {},
"home_machine": {},
"modeling_machine": {},
"settings_machine": {}
}
}

File diff suppressed because it is too large Load Diff