Client sketch scene (#1271)
* updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
* updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
* updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
* make tsc happ
* better error msg
* fix control point issue
* basic code gen working for tangentialArc
* partical fix for move with arcs
* tangential arc move
* fix
* make eslint rules less annoying
* inital refactor of some xstate stuff
* more old tangential arc clean up stuff
* more tweaks
* add testing
* tweak xstate inspect
* temp remove test
* update formating for less conflicts
* fix state machine layout after merge
* shrug, something weird with xstate typegen
* renaming some xstate events
* tweak numbers to make CI playwright happ
* CI hacks
* more CI hacks
* more CI hacks
* new hack strategy
* run tests agian
* make cmd bar less flaky
* ci hacks
* CI hacks
* CI hacks
* CI hacks
* clean up
* fix
* still have constraint stuff to deal with
* progress on move rules
* update source ranges after no execute code-mod
* typo
* mvp working
* hide show sketch overlay
* match scaling
* update arrow head style
* animate line tool
* bypass xstate for animations, much smoother
* add new segment working with refactor needed for setup paper sketch
* refactor setup paper sketch
* tangantialArcTo drag animations working
* tangential arc polish
* cargo fmt
* clippy
* more clippy
* mock canvas
* last of clippy?
* typo
* more clippy stuff
* move util function so they are shareable with typescript
* migrate a bunch to rust and only rust
* add arc center point for draft tangential ac
* clippy tweak
* delete uneeded test
* Rough start to scaling arrow heads.
The tangent arrow heads are basically nuked and replaced while the
straight line sections are just rotated and repositioned, this means they
miss out on updating scaling number after a screen size changes.
Needs fixing
* fix bug with tool tips
* fix draft line start position
Having drag the end of teh path before selecting a tooltip would result in the draft line starting where the path used to end, stale data
* some progress with pan maybe
* fmt
* inital camera sync working
For perspective camera at least
* change three.js to use z-up
* add grid
* orthographic camera working with polish items TODO
* fix zoom level when swapping camera
* fix up camera/orbit changing on cam change (pan wasn't being respected)
* tidy up
* use orbit target instead of assuming scene center
* dynamic fov working
* animate orthographic to perspective and reverse
* fix import
* temp fix for batch commands
* initial client side scene sketch working
* remove hover log
* FOV adjust fix
* fix comment
* tear down sketch and small tweaks
* some progress with camera tweening
* combine dollyZoom engine commands
see
https://github.com/KittyCAD/modeling-api/compare/kurt-perspective-settings?expand=1
and
https://github.com/KittyCAD/engine/compare/kurt-perspective-settings?expand=1
* make tests happy (mocks)
* fix tween to vertical/camera-up bug
* tween to each axis with hacky solutions in there
* fix startSketchOn planes
* tidy startSketchOn
* tweening okay for now I think
* get sketching on default planes working
* allow editing on all default planes
* clean up enter and exit sketch logic
* tidy
* tidy
* remove more default plane stuff
* start of draft line
* remove some annoying parts of the paper.js implementation
* fix drag than equip line bug
* comment
* don't animate on skech tear down since it's used for draft line
* remove more default plane shit
* style draft line
* refine dashed line
* draft line set up and tear down mostly happy
* add on click logic ready for draft lines
* sketch mode with drag and draft mode working solidly now, straight segments only
* default planes match colors, hover and select still TODO
* hover and click logic working for default planes
Now just need the code mode to fire to 'startSketchOn(...)'
* select default planes
* remove some logs
* fix update infinite loop
* start of orbitControls port to Franks control guards
* hiding scenes at different times
* scene hide on camera move should be respected by scroll zoom
* basic hover working
* Hook up user camera settings to ClientSideScene (#1334)
* Refactor to not import utilities from Router.tsx
* Stop tracking changes or formatting *.typegen.ts
* Hook up cameraControls to ClientSideScene
* Remove camera controls toggle from temp debug panel
---------
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
* add select segment moves cursor
* highlight segments yellow on hover
* cursor ranges effect 2d line colors
* fix constrainst i.e. make sure the sketch is rejiged
* selecting nothing should remove selections
* remove hardcoded strings
* update get_tangential_arc_to_info rust util
* initial drawing of tangential arcs in client scene
* fix tangentialArc arrow head direction
* correct userData types for tangential arcs
* get tangential arc updates working
Doesn't include draging the head of the tangential arc itself yet
* spot of clean up
* make selections work with tangential arcs
* get draft tangential segment animated
* fix initial click weirdness for adding new tangential line
* couple tweaks
* add grace pixels /threshold to raycast
* redo arc dashes so that they spawn from the ccenter of the arc
* fix multi drag bug
* fmt
* add temp solution for close
* add default axis hover colors, still needs select logic
* selection of axis works, just with out selection color
* get axis selection colors working
* fix outdate source ranges after drag problem
* update moreNodePathFromSourceRange
* fix ts-rs issue/workaround
* fix default plane weirdness
* fix tangential arc rounding issue
* review clean up part 1
* review clean up part 2
Big state-diagram cull
* clippy
* typo
* clippy
* fix xstate types with typegen
* fix types
* clippy
* catch error
* fix test import issue
Not sure exactly what was happening but guessing circular import that vite didn't like
* add axis/plane info to sketch group tests
* case changes because of rs-ts bug, can probably revert this later
* start of playwright test fixes
* reduce geo complexity for straight segments
* fix cam adjust tests
* Revert "Clean up vite build warnings (#1332)"
This reverts commit c1f661ab52
.
* selection e2e test fixed<
* remove camToggle to allow playwright tests to pass
* remove drag test
too brittle and needs to be redone from the ground up anyway
* trigger CI
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix last test
* clean up part 3
* clean up part 4
* clean up part 5
* clean up sketch enter exit logic
* fix engine side selections
* default plane should not be selected form 'onDragEnd'
i.e. rotating the camera should not mean the user acidently selects a plane
* clean up state diagram around animating to sketch mode
Embracing that the animation is async and puting the interdiate steps in the state diagram clean up some logic and solved some bugs at the same time
* add test for multiple sketches
* typo
* make highlight more robust
* type tweak
* scale segmenst with distance from camera so they have a consistent pixel size/ screenspace size
* Jess's advice
* tsc and fmt
* clean up part 6
remove integer from xstate names
* clean up part 7
* integrate sequency in to camera moves
* fix tests
* update snapshot e2e
* small snapshot change
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* trigger ci
* Fix HomeLoaderData types
* update std stuff
* update kittycad rs client lib
---------
Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime
|
ignore-words-list: crate,everytime,inout
|
||||||
skip: **/target,node_modules,build,**/Cargo.lock
|
skip: **/target,node_modules,build,**/Cargo.lock
|
||||||
|
@ -1 +1,2 @@
|
|||||||
src/wasm-lib/*
|
src/wasm-lib/*
|
||||||
|
*.typegen.ts
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
"never"
|
"never"
|
||||||
],
|
],
|
||||||
"react-hooks/exhaustive-deps": "off",
|
"react-hooks/exhaustive-deps": "off",
|
||||||
"@typescript-eslint/no-floating-promises": "warn"
|
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"@typescript-eslint/no-floating-promises": "warn",
|
||||||
"testing-library/prefer-screen-queries": "off"
|
"testing-library/prefer-screen-queries": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -46,6 +46,7 @@ jobs:
|
|||||||
workspaces: './src/wasm-lib'
|
workspaces: './src/wasm-lib'
|
||||||
|
|
||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
|
- run: yarn xstate:typegen
|
||||||
- run: yarn tsc
|
- run: yarn tsc
|
||||||
|
|
||||||
|
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -50,3 +50,7 @@ e2e/playwright/export-snapshots/*embedded.gltf
|
|||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
|
|
||||||
|
|
||||||
|
## generated files
|
||||||
|
src/**/*.typegen.ts
|
||||||
|
@ -10,4 +10,4 @@ src/wasm-lib/kcl/bindings
|
|||||||
e2e/playwright/export-snapshots
|
e2e/playwright/export-snapshots
|
||||||
|
|
||||||
# XState generated files
|
# XState generated files
|
||||||
src/machines/modelingMachine.typegen.ts
|
src/machines/**.typegen.ts
|
||||||
|
6370
docs/kcl/std.json
6370
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
1029
docs/kcl/std.md
1029
docs/kcl/std.md
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,5 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { secrets } from './secrets'
|
import { secrets } from './secrets'
|
||||||
import { EngineCommand } from '../../src/lang/std/engineConnection'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import waitOn from 'wait-on'
|
import waitOn from 'wait-on'
|
||||||
import { Themes } from '../../src/lib/theme'
|
import { Themes } from '../../src/lib/theme'
|
||||||
@ -53,40 +51,38 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await Promise.all([
|
await u.doAndWaitForImageDiff(
|
||||||
u.doAndWaitForImageDiff(
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
200
|
||||||
200
|
)
|
||||||
),
|
|
||||||
u.waitForDefaultPlanesVisibilityChange(),
|
|
||||||
])
|
|
||||||
|
|
||||||
// select a plane
|
// select a plane
|
||||||
await u.doAndWaitForCmd(() => page.mouse.click(700, 200), 'edit_mode_enter')
|
await page.mouse.click(700, 200)
|
||||||
await u.waitForCmdReceive('set_tool')
|
|
||||||
|
|
||||||
await u.doAndWaitForCmd(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
() => page.getByRole('button', { name: 'Line' }).click(),
|
`const part001 = startSketchOn('-XZ')`
|
||||||
'set_tool'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await u.doAndWaitForCmd(
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
() => page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10),
|
const startAt = '[23.89, -32.23]'
|
||||||
'mouse_click',
|
await expect(page.locator('.cm-content'))
|
||||||
false
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
)
|
|> startProfileAt(${startAt}, %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const startAt = '[18.26, -24.63]'
|
const num = 24.11
|
||||||
const num = '18.43'
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${startAt}, %)
|
||||||
@ -97,27 +93,22 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${num}, 0], %)
|
||||||
|> line([0, ${num}], %)`)
|
|> line([0, ${num + 0.01}], %)`)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${num}, 0], %)
|
||||||
|> line([0, ${num}], %)
|
|> line([0, ${num + 0.01}], %)
|
||||||
|> line([-36.69, 0], %)`)
|
|> line([-48, 0], %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await u.doAndWaitForCmd(
|
await page.getByRole('button', { name: 'Line' }).click()
|
||||||
() => page.getByRole('button', { name: 'Line' }).click(),
|
await page.waitForTimeout(100)
|
||||||
'set_tool'
|
|
||||||
)
|
|
||||||
|
|
||||||
// click between first two clicks to get center of the line
|
// click between first two clicks to get center of the line
|
||||||
await u.doAndWaitForCmd(
|
await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10)
|
||||||
() => page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10),
|
await page.waitForTimeout(100)
|
||||||
'select_with_point'
|
|
||||||
)
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// hold down shift
|
// hold down shift
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
@ -133,7 +124,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${startAt}, %)
|
||||||
|> line({ to: [${num}, 0], tag: 'seg01' }, %)
|
|> line({ to: [${num}, 0], tag: 'seg01' }, %)
|
||||||
|> line([0, ${num}], %)
|
|> line([0, ${num + 0.01}], %)
|
||||||
|> angledLine([180, segLen('seg01', %)], %)`)
|
|> angledLine([180, segLen('seg01', %)], %)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -271,59 +262,41 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
const camCmd: EngineCommand = {
|
const camPos: [number, number, number] = [100, 100, 100]
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
center: { x: 15, y: 0, z: 0 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
vantage: { x: 30, y: 30, z: 30 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestSinglePlane = async ({
|
const TestSinglePlane = async ({
|
||||||
viewCmd,
|
viewCmd,
|
||||||
expectedCode,
|
expectedCode,
|
||||||
clickCoords,
|
clickCoords,
|
||||||
}: {
|
}: {
|
||||||
viewCmd: EngineCommand
|
viewCmd: [number, number, number]
|
||||||
expectedCode: string
|
expectedCode: string
|
||||||
clickCoords: { x: number; y: number }
|
clickCoords: { x: number; y: number }
|
||||||
}) => {
|
}) => {
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.sendCustomCmd(viewCmd)
|
|
||||||
|
await u.updateCamPosition(viewCmd)
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
// await page.waitForTimeout(200)
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(clickCoords.x, clickCoords.y)
|
await page.mouse.click(clickCoords.x, clickCoords.y)
|
||||||
await u.openDebugPanel()
|
await page.waitForTimeout(300) // wait for animation
|
||||||
|
|
||||||
await expect(page.getByRole('button', { name: 'Line' })).toBeVisible()
|
await expect(page.getByRole('button', { name: 'Line' })).toBeVisible()
|
||||||
|
|
||||||
// draw a line
|
// draw a line
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await u.clearCommandLogs()
|
|
||||||
await page.getByRole('button', { name: 'Line' }).click()
|
|
||||||
await u.waitForCmdReceive('set_tool')
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.waitForCmdReceive('mouse_click')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
||||||
await u.openDebugPanel()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(expectedCode)
|
await expect(page.locator('.cm-content')).toHaveText(expectedCode)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Line' }).click()
|
await page.getByRole('button', { name: 'Line' }).click()
|
||||||
await u.clearCommandLogs()
|
await u.openAndClearDebugPanel()
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
@ -333,51 +306,40 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
|
|
||||||
const codeTemplate = (
|
const codeTemplate = (
|
||||||
plane = 'XY',
|
plane = 'XY',
|
||||||
sign = ''
|
rounded = false
|
||||||
) => `const part001 = startSketchOn('${plane}')
|
) => `const part001 = startSketchOn('${plane}')
|
||||||
|> startProfileAt([${sign}6.88, -9.29], %)
|
|> startProfileAt([28.91, -39${rounded ? '' : '.01'}], %)`
|
||||||
|> line([${sign}6.95, 0], %)`
|
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camCmd,
|
viewCmd: camPos,
|
||||||
expectedCode: codeTemplate('XY'),
|
expectedCode: codeTemplate('XY'),
|
||||||
clickCoords: { x: 700, y: 350 }, // red plane
|
clickCoords: { x: 600, y: 388 }, // red plane
|
||||||
|
// clickCoords: { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
|
||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camCmd,
|
viewCmd: camPos,
|
||||||
expectedCode: codeTemplate('YZ'),
|
expectedCode: codeTemplate('YZ', true),
|
||||||
clickCoords: { x: 1000, y: 200 }, // green plane
|
clickCoords: { x: 700, y: 300 }, // green plane
|
||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camCmd,
|
viewCmd: camPos,
|
||||||
expectedCode: codeTemplate('XZ', '-'),
|
expectedCode: codeTemplate('XZ'),
|
||||||
clickCoords: { x: 630, y: 130 }, // blue plane
|
clickCoords: { x: 700, y: 80 }, // blue plane
|
||||||
})
|
})
|
||||||
|
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
|
||||||
// new camera angle to click the back side of all three planes
|
|
||||||
const camCmdBackSide: EngineCommand = {
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
center: { x: -15, y: 0, z: 0 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
vantage: { x: -30, y: -30, z: -30 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camCmdBackSide,
|
viewCmd: camCmdBackSide,
|
||||||
expectedCode: codeTemplate('-XY', '-'),
|
expectedCode: codeTemplate('-XY', true),
|
||||||
clickCoords: { x: 705, y: 136 }, // back of red plane
|
clickCoords: { x: 601, y: 118 }, // back of red plane
|
||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camCmdBackSide,
|
viewCmd: camCmdBackSide,
|
||||||
expectedCode: codeTemplate('-YZ', '-'),
|
expectedCode: codeTemplate('-YZ'),
|
||||||
clickCoords: { x: 1000, y: 350 }, // back of green plane
|
clickCoords: { x: 730, y: 219 }, // back of green plane
|
||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camCmdBackSide,
|
viewCmd: camCmdBackSide,
|
||||||
expectedCode: codeTemplate('-XZ'),
|
expectedCode: codeTemplate('-XZ', true),
|
||||||
clickCoords: { x: 600, y: 400 }, // back of blue plane
|
clickCoords: { x: 680, y: 427 }, // back of blue plane
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -387,7 +349,6 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
// this test might be brittle as we add and remove functions
|
// this test might be brittle as we add and remove functions
|
||||||
// but should also be easy to update.
|
// but should also be easy to update.
|
||||||
@ -478,38 +439,36 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
const xAxisClick = () => page.mouse.click(700, 250)
|
const xAxisClick = () =>
|
||||||
const emptySpaceClick = () => page.mouse.click(700, 300)
|
page.mouse.click(700, 250).then(() => page.waitForTimeout(100))
|
||||||
const topHorzSegmentClick = () => page.mouse.click(700, 285)
|
const emptySpaceClick = () =>
|
||||||
const bottomHorzSegmentClick = () => page.mouse.click(750, 393)
|
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
|
||||||
|
const topHorzSegmentClick = () =>
|
||||||
|
page.mouse.click(709, 289).then(() => page.waitForTimeout(100))
|
||||||
|
const bottomHorzSegmentClick = () =>
|
||||||
|
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
// select a plane
|
// select a plane
|
||||||
await u.doAndWaitForCmd(() => page.mouse.click(700, 200), 'edit_mode_enter')
|
await page.mouse.click(700, 200)
|
||||||
await u.waitForCmdReceive('set_tool')
|
await page.waitForTimeout(700) // wait for animation
|
||||||
|
|
||||||
await u.doAndWaitForCmd(
|
|
||||||
() => page.getByRole('button', { name: 'Line' }).click(),
|
|
||||||
'set_tool'
|
|
||||||
)
|
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await u.doAndWaitForCmd(
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
() => page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10),
|
const startAt = '[23.89, -32.23]'
|
||||||
'mouse_click',
|
await expect(page.locator('.cm-content'))
|
||||||
false
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
)
|
|> startProfileAt(${startAt}, %)`)
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
|
||||||
const startAt = '[18.26, -24.63]'
|
const num = 24.11
|
||||||
const num = '18.43'
|
const num2 = '48'
|
||||||
const num2 = '36.69'
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${startAt}, %)
|
||||||
@ -520,20 +479,17 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${num}, 0], %)
|
||||||
|> line([0, ${num}], %)`)
|
|> line([0, ${num + 0.01}], %)`)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${startAt}, %)
|
|> startProfileAt(${startAt}, %)
|
||||||
|> line([${num}, 0], %)
|
|> line([${num}, 0], %)
|
||||||
|> line([0, ${num}], %)
|
|> line([0, ${num + 0.01}], %)
|
||||||
|> line([-${num2}, 0], %)`)
|
|> line([-${num2}, 0], %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await u.doAndWaitForCmd(
|
await page.getByRole('button', { name: 'Line' }).click()
|
||||||
() => page.getByRole('button', { name: 'Line' }).click(),
|
|
||||||
'set_tool'
|
|
||||||
)
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
const selectionSequence = async () => {
|
const selectionSequence = async () => {
|
||||||
@ -555,79 +511,72 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
// now check clicking works including axis
|
// now check clicking works including axis
|
||||||
|
|
||||||
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||||
await u.doAndWaitForCmd(topHorzSegmentClick, 'select_with_point', false)
|
await topHorzSegmentClick()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await u.doAndWaitForCmd(xAxisClick, 'select_with_point', false)
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
await u.doAndWaitForCmd(emptySpaceClick, 'select_clear', false)
|
await emptySpaceClick()
|
||||||
|
|
||||||
// same selection but click the axis first
|
// same selection but click the axis first
|
||||||
await u.doAndWaitForCmd(xAxisClick, 'select_with_point', false)
|
await xAxisClick()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await u.doAndWaitForCmd(topHorzSegmentClick, 'select_with_point', false)
|
await topHorzSegmentClick()
|
||||||
|
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
await u.doAndWaitForCmd(emptySpaceClick, 'select_clear', false)
|
await emptySpaceClick()
|
||||||
|
|
||||||
// check the same selection again by putting cursor in code first then selecting axis
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
await u.doAndWaitForCmd(
|
await page.getByText(` |> line([-${num2}, 0], %)`).click()
|
||||||
() => page.getByText(` |> line([-${num2}, 0], %)`).click(),
|
|
||||||
'select_clear',
|
|
||||||
false
|
|
||||||
)
|
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await u.doAndWaitForCmd(xAxisClick, 'select_with_point', false)
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
await u.doAndWaitForCmd(emptySpaceClick, 'select_clear', false)
|
await emptySpaceClick()
|
||||||
|
|
||||||
// select segment in editor than another segment in scene and check there are two cursors
|
// select segment in editor than another segment in scene and check there are two cursors
|
||||||
await u.doAndWaitForCmd(
|
await page.getByText(` |> line([-${num2}, 0], %)`).click()
|
||||||
() => page.getByText(` |> line([-${num2}, 0], %)`).click(),
|
await page.waitForTimeout(300)
|
||||||
'select_clear',
|
|
||||||
false
|
|
||||||
)
|
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
||||||
await u.doAndWaitForCmd(bottomHorzSegmentClick, 'select_with_point', false) // another segment, bottom one
|
await bottomHorzSegmentClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
await u.doAndWaitForCmd(emptySpaceClick, 'select_clear', false)
|
await emptySpaceClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
await selectionSequence()
|
await selectionSequence()
|
||||||
|
|
||||||
// hovering in fresh sketch worked, lets try exiting and re-entering
|
// hovering in fresh sketch worked, lets try exiting and re-entering
|
||||||
await u.doAndWaitForCmd(
|
await u.openAndClearDebugPanel()
|
||||||
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
'edit_mode_exit'
|
await page.waitForTimeout(200)
|
||||||
)
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// select a line
|
// select a line
|
||||||
await u.doAndWaitForCmd(topHorzSegmentClick, 'select_clear', false)
|
await topHorzSegmentClick()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
await u.doAndWaitForCmd(
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
await page.waitForTimeout(700) // wait for animation
|
||||||
'edit_mode_enter',
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
// hover again and check it works
|
// hover again and check it works
|
||||||
await selectionSequence()
|
await selectionSequence()
|
||||||
@ -697,6 +646,8 @@ test('Can extrude from the command bar', async ({ page, context }) => {
|
|||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await page.keyboard.press('Meta+K')
|
await page.keyboard.press('Meta+K')
|
||||||
@ -735,3 +686,127 @@ test('Can extrude from the command bar', async ({ page, context }) => {
|
|||||||
|> extrude(5, %)`
|
|> extrude(5, %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Can add multiple sketches', async ({ page }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
||||||
|
|
||||||
|
// click on "Start Sketch" button
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.doAndWaitForImageDiff(
|
||||||
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||||
|
200
|
||||||
|
)
|
||||||
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const part001 = startSketchOn('-XZ')`
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
|
const startXPx = 600
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
const startAt = '[23.89, -32.23]'
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt(${startAt}, %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
const num = 24.11
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt(${startAt}, %)
|
||||||
|
|> line([${num}, 0], %)`)
|
||||||
|
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt(${startAt}, %)
|
||||||
|
|> line([${num}, 0], %)
|
||||||
|
|> line([0, ${num + 0.01}], %)`)
|
||||||
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
|
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt(${startAt}, %)
|
||||||
|
|> line([${num}, 0], %)
|
||||||
|
|> line([0, ${num + 0.01}], %)
|
||||||
|
|> line([-48, 0], %)`
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch)
|
||||||
|
|
||||||
|
// exit the sketch
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
await u.updateCamPosition([0, 100, 100])
|
||||||
|
|
||||||
|
// start a new sketch
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(673, 384)
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
const startAt2 = '[23.61, -31.85]'
|
||||||
|
await expect(
|
||||||
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
|
).toBe(
|
||||||
|
`${finalCodeFirstSketch}
|
||||||
|
const part002 = startSketchOn('XY')
|
||||||
|
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
|
||||||
|
)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
const num2 = 23.83
|
||||||
|
await expect(
|
||||||
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
|
).toBe(
|
||||||
|
`${finalCodeFirstSketch}
|
||||||
|
const part002 = startSketchOn('XY')
|
||||||
|
|> startProfileAt(${startAt2}, %)
|
||||||
|
|> line([${num2}, 0], %)`.replace(/\s/g, '')
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
|
await expect(
|
||||||
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
|
).toBe(
|
||||||
|
`${finalCodeFirstSketch}
|
||||||
|
const part002 = startSketchOn('XY')
|
||||||
|
|> startProfileAt(${startAt2}, %)
|
||||||
|
|> line([${num2}, 0], %)
|
||||||
|
|> line([0, ${num2}], %)`.replace(/\s/g, '')
|
||||||
|
)
|
||||||
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
|
await expect(
|
||||||
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
|
).toBe(
|
||||||
|
`${finalCodeFirstSketch}
|
||||||
|
const part002 = startSketchOn('XY')
|
||||||
|
|> startProfileAt(${startAt2}, %)
|
||||||
|
|> line([${num2}, 0], %)
|
||||||
|
|> line([0, ${num2}], %)
|
||||||
|
|> line([-47.44, 0], %)`.replace(/\s/g, '')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -40,19 +40,8 @@ test('change camera, show planes', async ({ page, context }) => {
|
|||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
const camCmd: EngineCommand = {
|
const camPos: [number, number, number] = [0, 85, 85]
|
||||||
type: 'modeling_cmd_req',
|
await u.updateCamPosition(camPos)
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
center: { x: 0, y: 0, z: 0 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
vantage: { x: 0, y: 85, z: 85 },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
await u.sendCustomCmd(camCmd)
|
|
||||||
await u.waitForCmdReceive('default_camera_look_at')
|
|
||||||
|
|
||||||
// rotate
|
// rotate
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -62,13 +51,11 @@ test('change camera, show planes', async ({ page, context }) => {
|
|||||||
await page.mouse.up({ button: 'right' })
|
await page.mouse.up({ button: 'right' })
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.waitForCmdReceive('camera_drag_end')
|
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
@ -77,10 +64,8 @@ test('change camera, show planes', async ({ page, context }) => {
|
|||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
await u.sendCustomCmd(camCmd)
|
await u.updateCamPosition(camPos)
|
||||||
await u.waitForCmdReceive('default_camera_look_at')
|
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -93,12 +78,10 @@ test('change camera, show planes', async ({ page, context }) => {
|
|||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.waitForCmdReceive('camera_drag_end')
|
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
@ -107,10 +90,8 @@ test('change camera, show planes', async ({ page, context }) => {
|
|||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
|
|
||||||
await u.sendCustomCmd(camCmd)
|
await u.updateCamPosition(camPos)
|
||||||
await u.waitForCmdReceive('default_camera_look_at')
|
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -119,17 +100,15 @@ test('change camera, show planes', async ({ page, context }) => {
|
|||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
await page.mouse.move(700, 400)
|
await page.mouse.move(700, 400)
|
||||||
await page.mouse.down({ button: 'right' })
|
await page.mouse.down({ button: 'right' })
|
||||||
await page.mouse.move(700, 350)
|
await page.mouse.move(700, 300)
|
||||||
await page.mouse.up({ button: 'right' })
|
await page.mouse.up({ button: 'right' })
|
||||||
await page.keyboard.up('Control')
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.waitForCmdReceive('camera_drag_end')
|
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
@ -191,7 +170,6 @@ const part001 = startSketchOn('-XZ')
|
|||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.waitForDefaultPlanesVisibilityChange()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.waitForCmdReceive('extrude')
|
await u.waitForCmdReceive('extrude')
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 85 KiB |
Binary file not shown.
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 65 KiB |
Binary file not shown.
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 87 KiB |
@ -80,10 +80,21 @@ export function getUtils(page: Page) {
|
|||||||
waitForAuthSkipAppStart: () => waitForPageLoad(page),
|
waitForAuthSkipAppStart: () => waitForPageLoad(page),
|
||||||
removeCurrentCode: () => removeCurrentCode(page),
|
removeCurrentCode: () => removeCurrentCode(page),
|
||||||
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
||||||
|
updateCamPosition: async (xyz: [number, number, number]) => {
|
||||||
|
const fillInput = async () => {
|
||||||
|
await page.fill('[data-testid="cam-x-position"]', String(xyz[0]))
|
||||||
|
await page.fill('[data-testid="cam-y-position"]', String(xyz[1]))
|
||||||
|
await page.fill('[data-testid="cam-z-position"]', String(xyz[2]))
|
||||||
|
}
|
||||||
|
await fillInput()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await fillInput()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await fillInput()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
},
|
||||||
clearCommandLogs: () => clearCommandLogs(page),
|
clearCommandLogs: () => clearCommandLogs(page),
|
||||||
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
|
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
|
||||||
waitForDefaultPlanesVisibilityChange: () =>
|
|
||||||
waitForDefaultPlanesToBeVisible(page),
|
|
||||||
openDebugPanel: () => openDebugPanel(page),
|
openDebugPanel: () => openDebugPanel(page),
|
||||||
closeDebugPanel: () => closeDebugPanel(page),
|
closeDebugPanel: () => closeDebugPanel(page),
|
||||||
openAndClearDebugPanel: async () => {
|
openAndClearDebugPanel: async () => {
|
||||||
|
13
package.json
13
package.json
@ -10,7 +10,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.46",
|
"@kittycad/lib": "^0.0.50",
|
||||||
"@lezer/javascript": "^1.4.9",
|
"@lezer/javascript": "^1.4.9",
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
"@open-rpc/client-js": "^1.8.1",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
@ -21,6 +21,7 @@
|
|||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/user-event": "^14.5.1",
|
"@testing-library/user-event": "^14.5.1",
|
||||||
"@ts-stack/markdown": "^1.5.0",
|
"@ts-stack/markdown": "^1.5.0",
|
||||||
|
"@tweenjs/tween.js": "^23.1.1",
|
||||||
"@types/node": "^16.7.13",
|
"@types/node": "^16.7.13",
|
||||||
"@types/react": "^18.2.41",
|
"@types/react": "^18.2.41",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
@ -45,6 +46,7 @@
|
|||||||
"sketch-helpers": "^0.0.4",
|
"sketch-helpers": "^0.0.4",
|
||||||
"swr": "^2.2.2",
|
"swr": "^2.2.2",
|
||||||
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
|
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
|
||||||
|
"three": "^0.160.0",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
@ -82,7 +84,9 @@
|
|||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||||
"lint": "eslint --fix src",
|
"lint": "eslint --fix src",
|
||||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json"
|
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
|
||||||
|
"postinstall": "patch-package && yarn xstate:typegen",
|
||||||
|
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\""
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
@ -113,6 +117,7 @@
|
|||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/react-modal": "^3.16.3",
|
"@types/react-modal": "^3.16.3",
|
||||||
|
"@types/three": "^0.160.0",
|
||||||
"@types/uuid": "^9.0.4",
|
"@types/uuid": "^9.0.4",
|
||||||
"@types/wait-on": "^5.3.4",
|
"@types/wait-on": "^5.3.4",
|
||||||
"@types/wicg-file-system-access": "^2020.9.6",
|
"@types/wicg-file-system-access": "^2020.9.6",
|
||||||
@ -124,15 +129,18 @@
|
|||||||
"@wdio/local-runner": "^8.24.3",
|
"@wdio/local-runner": "^8.24.3",
|
||||||
"@wdio/mocha-framework": "^8.24.3",
|
"@wdio/mocha-framework": "^8.24.3",
|
||||||
"@wdio/spec-reporter": "^8.24.2",
|
"@wdio/spec-reporter": "^8.24.2",
|
||||||
|
"@xstate/cli": "^0.5.17",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.53.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"happy-dom": "^10.8.0",
|
"happy-dom": "^10.8.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
|
"patch-package": "^8.0.0",
|
||||||
"pixelmatch": "^5.3.0",
|
"pixelmatch": "^5.3.0",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.8.0",
|
||||||
"setimmediate": "^1.0.5",
|
"setimmediate": "^1.0.5",
|
||||||
"tailwindcss": "^3.3.6",
|
"tailwindcss": "^3.3.6",
|
||||||
@ -140,6 +148,7 @@
|
|||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.2.1",
|
"vite-tsconfig-paths": "^4.2.1",
|
||||||
|
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||||
"wait-on": "^7.2.0",
|
"wait-on": "^7.2.0",
|
||||||
"yarn": "^1.22.19"
|
"yarn": "^1.22.19"
|
||||||
}
|
}
|
||||||
|
138
patches/three+0.160.0.patch
Normal file
138
patches/three+0.160.0.patch
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
diff --git a/node_modules/three/examples/jsm/controls/OrbitControls.js b/node_modules/three/examples/jsm/controls/OrbitControls.js
|
||||||
|
index f29e7fe..0ef636b 100644
|
||||||
|
--- a/node_modules/three/examples/jsm/controls/OrbitControls.js
|
||||||
|
+++ b/node_modules/three/examples/jsm/controls/OrbitControls.js
|
||||||
|
@@ -113,6 +113,25 @@ class OrbitControls extends EventDispatcher {
|
||||||
|
// public methods
|
||||||
|
//
|
||||||
|
|
||||||
|
+ this.interactionGuards = {
|
||||||
|
+ pan: {
|
||||||
|
+ description: 'Right click + Shift + drag or middle click + drag',
|
||||||
|
+ callback: (e) => e.button === 2 && !e.ctrlKey,
|
||||||
|
+ },
|
||||||
|
+ zoom: {
|
||||||
|
+ description: 'Scroll wheel or Right click + Ctrl + drag',
|
||||||
|
+ dragCallback: (e) => e.button === 2 && e.ctrlKey,
|
||||||
|
+ scrollCallback: () => true,
|
||||||
|
+ },
|
||||||
|
+ rotate: {
|
||||||
|
+ description: 'Right click + drag',
|
||||||
|
+ callback: (e) => e.button === 0,
|
||||||
|
+ },
|
||||||
|
+ }
|
||||||
|
+ this.setMouseGuards = (interactionGuards) => {
|
||||||
|
+ this.interactionGuards = interactionGuards
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
this.getPolarAngle = function () {
|
||||||
|
|
||||||
|
return spherical.phi;
|
||||||
|
@@ -1057,92 +1076,21 @@ class OrbitControls extends EventDispatcher {
|
||||||
|
|
||||||
|
function onMouseDown( event ) {
|
||||||
|
|
||||||
|
- let mouseAction;
|
||||||
|
-
|
||||||
|
- switch ( event.button ) {
|
||||||
|
-
|
||||||
|
- case 0:
|
||||||
|
-
|
||||||
|
- mouseAction = scope.mouseButtons.LEFT;
|
||||||
|
- break;
|
||||||
|
-
|
||||||
|
- case 1:
|
||||||
|
-
|
||||||
|
- mouseAction = scope.mouseButtons.MIDDLE;
|
||||||
|
- break;
|
||||||
|
-
|
||||||
|
- case 2:
|
||||||
|
-
|
||||||
|
- mouseAction = scope.mouseButtons.RIGHT;
|
||||||
|
- break;
|
||||||
|
-
|
||||||
|
- default:
|
||||||
|
-
|
||||||
|
- mouseAction = - 1;
|
||||||
|
-
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- switch ( mouseAction ) {
|
||||||
|
-
|
||||||
|
- case MOUSE.DOLLY:
|
||||||
|
-
|
||||||
|
- if ( scope.enableZoom === false ) return;
|
||||||
|
-
|
||||||
|
- handleMouseDownDolly( event );
|
||||||
|
-
|
||||||
|
- state = STATE.DOLLY;
|
||||||
|
-
|
||||||
|
- break;
|
||||||
|
-
|
||||||
|
- case MOUSE.ROTATE:
|
||||||
|
-
|
||||||
|
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
||||||
|
-
|
||||||
|
- if ( scope.enablePan === false ) return;
|
||||||
|
-
|
||||||
|
- handleMouseDownPan( event );
|
||||||
|
-
|
||||||
|
- state = STATE.PAN;
|
||||||
|
-
|
||||||
|
- } else {
|
||||||
|
-
|
||||||
|
- if ( scope.enableRotate === false ) return;
|
||||||
|
-
|
||||||
|
- handleMouseDownRotate( event );
|
||||||
|
-
|
||||||
|
- state = STATE.ROTATE;
|
||||||
|
-
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- break;
|
||||||
|
-
|
||||||
|
- case MOUSE.PAN:
|
||||||
|
-
|
||||||
|
- if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
||||||
|
-
|
||||||
|
- if ( scope.enableRotate === false ) return;
|
||||||
|
-
|
||||||
|
- handleMouseDownRotate( event );
|
||||||
|
-
|
||||||
|
- state = STATE.ROTATE;
|
||||||
|
-
|
||||||
|
- } else {
|
||||||
|
-
|
||||||
|
- if ( scope.enablePan === false ) return;
|
||||||
|
-
|
||||||
|
- handleMouseDownPan( event );
|
||||||
|
-
|
||||||
|
- state = STATE.PAN;
|
||||||
|
-
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- break;
|
||||||
|
-
|
||||||
|
- default:
|
||||||
|
-
|
||||||
|
- state = STATE.NONE;
|
||||||
|
-
|
||||||
|
- }
|
||||||
|
+ if (scope.interactionGuards.pan.callback(event)) {
|
||||||
|
+ if (scope.enablePan === false) return
|
||||||
|
+ handleMouseDownPan(event)
|
||||||
|
+ state = STATE.PAN
|
||||||
|
+ } else if (scope.interactionGuards.rotate.callback(event)) {
|
||||||
|
+ if (scope.enableRotate === false) return
|
||||||
|
+ handleMouseDownRotate(event)
|
||||||
|
+ state = STATE.ROTATE
|
||||||
|
+ } else if (scope.interactionGuards.zoom.dragCallback(event)) {
|
||||||
|
+ if (scope.enableZoom === false) return
|
||||||
|
+ handleMouseDownDolly(event)
|
||||||
|
+ state = STATE.DOLLY
|
||||||
|
+ } else {
|
||||||
|
+ return
|
||||||
|
+ }
|
||||||
|
|
||||||
|
if ( state !== STATE.NONE ) {
|
||||||
|
|
99
src-tauri/Cargo.lock
generated
99
src-tauri/Cargo.lock
generated
@ -82,6 +82,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-driver",
|
||||||
"tauri-plugin-fs-extra",
|
"tauri-plugin-fs-extra",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.8.2",
|
"toml 0.8.2",
|
||||||
@ -1307,6 +1308,15 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "home"
|
||||||
|
version = "0.5.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.25.2"
|
version = "0.25.2"
|
||||||
@ -2538,6 +2548,12 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pico-args"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@ -3419,6 +3435,37 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-tokio"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"libc",
|
||||||
|
"signal-hook",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@ -3855,6 +3902,25 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-driver"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0a46011e3018146c2a189bf1fa0339f10ef6be065ab8a5e718018121e3a03bf"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"futures",
|
||||||
|
"futures-util",
|
||||||
|
"hyper",
|
||||||
|
"pico-args",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-tokio",
|
||||||
|
"tokio",
|
||||||
|
"which",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
@ -4062,9 +4128,21 @@ dependencies = [
|
|||||||
"num_cpus",
|
"num_cpus",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.5.5",
|
"socket2 0.5.5",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.33",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-native-tls"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -4617,6 +4695,18 @@ dependencies = [
|
|||||||
"windows-metadata",
|
"windows-metadata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "which"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"home",
|
||||||
|
"once_cell",
|
||||||
|
"rustix 0.38.21",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@ -4743,6 +4833,15 @@ dependencies = [
|
|||||||
"windows-targets 0.48.0",
|
"windows-targets 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react'
|
|
||||||
import { App } from './App'
|
|
||||||
import { describe, test, vi } from 'vitest'
|
|
||||||
import {
|
|
||||||
Route,
|
|
||||||
RouterProvider,
|
|
||||||
createMemoryRouter,
|
|
||||||
createRoutesFromElements,
|
|
||||||
} from 'react-router-dom'
|
|
||||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
|
||||||
import CommandBarProvider from 'components/CommandBar/CommandBar'
|
|
||||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
|
||||||
import { BROWSER_FILE_NAME } from 'Router'
|
|
||||||
|
|
||||||
let listener: ((rect: any) => void) | undefined = undefined
|
|
||||||
;(global as any).ResizeObserver = class ResizeObserver {
|
|
||||||
constructor(ls: ((rect: any) => void) | undefined) {
|
|
||||||
listener = ls
|
|
||||||
}
|
|
||||||
observe() {}
|
|
||||||
unobserve() {}
|
|
||||||
disconnect() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('App tests', () => {
|
|
||||||
test('Renders the modeling app screen, including "Variables" pane.', () => {
|
|
||||||
vi.mock('react-router-dom', async () => {
|
|
||||||
const actual = (await vi.importActual('react-router-dom')) as Record<
|
|
||||||
string,
|
|
||||||
any
|
|
||||||
>
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
useParams: () => ({ id: BROWSER_FILE_NAME }),
|
|
||||||
useLoaderData: () => ({ code: null }),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
render(
|
|
||||||
<TestWrap>
|
|
||||||
<App />
|
|
||||||
</TestWrap>
|
|
||||||
)
|
|
||||||
const linkElement = screen.getByText(/Variables/i)
|
|
||||||
expect(linkElement).toBeInTheDocument()
|
|
||||||
|
|
||||||
vi.restoreAllMocks()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function TestWrap({ children }: { children: React.ReactNode }) {
|
|
||||||
// We have to use a memory router in the testing environment,
|
|
||||||
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
|
|
||||||
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
|
|
||||||
const router = createMemoryRouter(
|
|
||||||
createRoutesFromElements(
|
|
||||||
<Route
|
|
||||||
path="/file/:id"
|
|
||||||
element={
|
|
||||||
<CommandBarProvider>
|
|
||||||
<GlobalStateProvider>
|
|
||||||
<ModelingMachineProvider>{children}</ModelingMachineProvider>
|
|
||||||
</GlobalStateProvider>
|
|
||||||
</CommandBarProvider>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
{
|
|
||||||
initialEntries: ['/file/new'],
|
|
||||||
initialIndex: 0,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return <RouterProvider router={router} />
|
|
||||||
}
|
|
53
src/App.tsx
53
src/App.tsx
@ -20,9 +20,9 @@ import {
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { getNormalisedCoordinates } from './lib/utils'
|
import { getNormalisedCoordinates } from './lib/utils'
|
||||||
import { useLoaderData } from 'react-router-dom'
|
import { useLoaderData } from 'react-router-dom'
|
||||||
import { IndexLoaderData } from './Router'
|
import { IndexLoaderData } from 'lib/types'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { onboardingPaths } from 'routes/Onboarding'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { CodeMenu } from 'components/CodeMenu'
|
import { CodeMenu } from 'components/CodeMenu'
|
||||||
@ -31,6 +31,8 @@ import { Themes, getSystemTheme } from 'lib/theme'
|
|||||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||||
import { engineCommandManager } from './lang/std/engineConnection'
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { ClientSideScene } from 'clientSideScene/setup'
|
||||||
|
// import { CamToggle } from 'components/CamToggle'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { project, file } = useLoaderData() as IndexLoaderData
|
const { project, file } = useLoaderData() as IndexLoaderData
|
||||||
@ -83,10 +85,12 @@ export function App() {
|
|||||||
useEngineConnectionSubscriptions()
|
useEngineConnectionSubscriptions()
|
||||||
|
|
||||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||||
void engineCommandManager.sendSceneCommand(message)
|
engineCommandManager.sendSceneCommand(message)
|
||||||
}, 16)
|
}, 1000 / 15)
|
||||||
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
e.nativeEvent.preventDefault()
|
if (state.matches('Sketch')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { x, y } = getNormalisedCoordinates({
|
const { x, y } = getNormalisedCoordinates({
|
||||||
clientX: e.clientX,
|
clientX: e.clientX,
|
||||||
@ -97,37 +101,15 @@ export function App() {
|
|||||||
|
|
||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
if (buttonDownInStream === undefined) {
|
if (buttonDownInStream === undefined) {
|
||||||
if (state.matches('Sketch.Line Tool')) {
|
debounceSocketSend({
|
||||||
debounceSocketSend({
|
type: 'modeling_cmd_req',
|
||||||
type: 'modeling_cmd_req',
|
cmd: {
|
||||||
cmd_id: newCmdId,
|
type: 'highlight_set_entity',
|
||||||
cmd: {
|
selected_at_window: { x, y },
|
||||||
type: 'mouse_move',
|
},
|
||||||
window: { x, y },
|
cmd_id: newCmdId,
|
||||||
},
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
debounceSocketSend({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'highlight_set_entity',
|
|
||||||
selected_at_window: { x, y },
|
|
||||||
},
|
|
||||||
cmd_id: newCmdId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (state.matches('Sketch.Move Tool')) {
|
|
||||||
debounceSocketSend({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: newCmdId,
|
|
||||||
cmd: {
|
|
||||||
type: 'handle_mouse_drag_move',
|
|
||||||
window: { x, y },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
||||||
let interaction: CameraDragInteractionType_type
|
let interaction: CameraDragInteractionType_type
|
||||||
|
|
||||||
@ -238,6 +220,7 @@ export function App() {
|
|||||||
open={openPanes.includes('debug')}
|
open={openPanes.includes('debug')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{/* <CamToggle /> */}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,7 @@ import {
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { ErrorPage } from './components/ErrorPage'
|
import { ErrorPage } from './components/ErrorPage'
|
||||||
import { Settings } from './routes/Settings'
|
import { Settings } from './routes/Settings'
|
||||||
import Onboarding, {
|
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
|
||||||
onboardingRoutes,
|
|
||||||
onboardingPaths,
|
|
||||||
} from './routes/Onboarding'
|
|
||||||
import SignIn from './routes/SignIn'
|
import SignIn from './routes/SignIn'
|
||||||
import { Auth } from './Auth'
|
import { Auth } from './Auth'
|
||||||
import { isTauri } from './lib/isTauri'
|
import { isTauri } from './lib/isTauri'
|
||||||
@ -29,7 +26,7 @@ import {
|
|||||||
isProjectDirectory,
|
isProjectDirectory,
|
||||||
PROJECT_ENTRYPOINT,
|
PROJECT_ENTRYPOINT,
|
||||||
} from './lib/tauriFS'
|
} from './lib/tauriFS'
|
||||||
import { metadata, type Metadata } from 'tauri-plugin-fs-extra-api'
|
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||||
import DownloadAppBanner from './components/DownloadAppBanner'
|
import DownloadAppBanner from './components/DownloadAppBanner'
|
||||||
import { WasmErrBanner } from './components/WasmErrBanner'
|
import { WasmErrBanner } from './components/WasmErrBanner'
|
||||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
||||||
@ -42,9 +39,11 @@ import CommandBarProvider from 'components/CommandBar/CommandBar'
|
|||||||
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||||
import { KclContextProvider, kclManager } from 'lang/KclSinglton'
|
import { KclContextProvider, kclManager } from 'lang/KclSingleton'
|
||||||
import FileMachineProvider from 'components/FileMachineProvider'
|
import FileMachineProvider from 'components/FileMachineProvider'
|
||||||
import { sep } from '@tauri-apps/api/path'
|
import { sep } from '@tauri-apps/api/path'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
|
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
|
||||||
|
|
||||||
if (VITE_KC_SENTRY_DSN && !TEST) {
|
if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
@ -78,43 +77,8 @@ if (VITE_KC_SENTRY_DSN && !TEST) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const prependRoutes =
|
|
||||||
(routesObject: Record<string, string>) => (prepend: string) => {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(routesObject).map(([constName, path]) => [
|
|
||||||
constName,
|
|
||||||
prepend + path,
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const paths = {
|
|
||||||
INDEX: '/',
|
|
||||||
HOME: '/home',
|
|
||||||
FILE: '/file',
|
|
||||||
SETTINGS: '/settings',
|
|
||||||
SIGN_IN: '/signin',
|
|
||||||
ONBOARDING: prependRoutes(onboardingPaths)(
|
|
||||||
'/onboarding'
|
|
||||||
) as typeof onboardingPaths,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BROWSER_FILE_NAME = 'new'
|
export const BROWSER_FILE_NAME = 'new'
|
||||||
|
|
||||||
export type IndexLoaderData = {
|
|
||||||
code: string | null
|
|
||||||
project?: ProjectWithEntryPointMetadata
|
|
||||||
file?: FileEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProjectWithEntryPointMetadata = FileEntry & {
|
|
||||||
entrypointMetadata: Metadata
|
|
||||||
}
|
|
||||||
export type HomeLoaderData = {
|
|
||||||
projects: ProjectWithEntryPointMetadata[]
|
|
||||||
newDefaultDirectory?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]
|
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]
|
||||||
|
|
||||||
const addGlobalContextToElements = (
|
const addGlobalContextToElements = (
|
||||||
@ -146,18 +110,18 @@ const router = createBrowserRouter(
|
|||||||
{
|
{
|
||||||
path: paths.FILE + '/:id',
|
path: paths.FILE + '/:id',
|
||||||
element: (
|
element: (
|
||||||
<Auth>
|
<KclContextProvider>
|
||||||
<FileMachineProvider>
|
<Auth>
|
||||||
<KclContextProvider>
|
<FileMachineProvider>
|
||||||
<ModelingMachineProvider>
|
<ModelingMachineProvider>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<App />
|
<App />
|
||||||
</ModelingMachineProvider>
|
</ModelingMachineProvider>
|
||||||
<WasmErrBanner />
|
<WasmErrBanner />
|
||||||
</KclContextProvider>
|
</FileMachineProvider>
|
||||||
</FileMachineProvider>
|
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
</Auth>
|
||||||
</Auth>
|
</KclContextProvider>
|
||||||
),
|
),
|
||||||
id: paths.FILE,
|
id: paths.FILE,
|
||||||
loader: async ({
|
loader: async ({
|
||||||
@ -249,7 +213,7 @@ const router = createBrowserRouter(
|
|||||||
<Home />
|
<Home />
|
||||||
</Auth>
|
</Auth>
|
||||||
),
|
),
|
||||||
loader: async () => {
|
loader: async (): Promise<HomeLoaderData | Response> => {
|
||||||
if (!isTauri()) {
|
if (!isTauri()) {
|
||||||
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
|
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
@ -89,44 +89,48 @@ export const Toolbar = () => {
|
|||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
{state.matches('Sketch') && !state.matches('idle') && (
|
{state.matches('Sketch') && !state.matches('idle') && (
|
||||||
<li className="contents">
|
<>
|
||||||
<ActionButton
|
<li className="contents" key="line-button">
|
||||||
Element="button"
|
<ActionButton
|
||||||
onClick={() =>
|
Element="button"
|
||||||
state.matches('Sketch.Line Tool')
|
onClick={() =>
|
||||||
? send('CancelSketch')
|
state?.matches('Sketch.Line tool')
|
||||||
: send('Equip tool')
|
? send('CancelSketch')
|
||||||
}
|
: send('Equip Line tool')
|
||||||
aria-pressed={state.matches('Sketch.Line Tool')}
|
}
|
||||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||||
icon={{
|
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||||
icon: 'line',
|
icon={{
|
||||||
bgClassName,
|
icon: 'line',
|
||||||
}}
|
bgClassName,
|
||||||
>
|
}}
|
||||||
Line
|
>
|
||||||
</ActionButton>
|
Line
|
||||||
</li>
|
</ActionButton>
|
||||||
)}
|
</li>
|
||||||
{state.matches('Sketch') && (
|
<li className="contents" key="tangential-arc-button">
|
||||||
<li className="contents">
|
<ActionButton
|
||||||
<ActionButton
|
Element="button"
|
||||||
Element="button"
|
onClick={() =>
|
||||||
onClick={() =>
|
state.matches('Sketch.Tangential arc to')
|
||||||
state.matches('Sketch.Move Tool')
|
? send('CancelSketch')
|
||||||
? send('CancelSketch')
|
: send('Equip tangential arc to')
|
||||||
: send('Equip move tool')
|
}
|
||||||
}
|
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||||
aria-pressed={state.matches('Sketch.Move Tool')}
|
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
icon={{
|
||||||
icon={{
|
icon: 'line',
|
||||||
icon: 'move',
|
bgClassName,
|
||||||
bgClassName,
|
}}
|
||||||
}}
|
disabled={
|
||||||
>
|
!state.can('Equip tangential arc to') &&
|
||||||
Move
|
!state.matches('Sketch.Tangential arc to')
|
||||||
</ActionButton>
|
}
|
||||||
</li>
|
>
|
||||||
|
Tangential Arc
|
||||||
|
</ActionButton>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{state.matches('Sketch.SketchIdle') &&
|
{state.matches('Sketch.SketchIdle') &&
|
||||||
state.nextEvents
|
state.nextEvents
|
||||||
@ -151,7 +155,7 @@ export const Toolbar = () => {
|
|||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
.map((eventName) => (
|
.map((eventName) => (
|
||||||
<li className="contents">
|
<li className="contents" key={eventName}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
|
1007
src/clientSideScene/clientSideScene.ts
Normal file
1007
src/clientSideScene/clientSideScene.ts
Normal file
File diff suppressed because it is too large
Load Diff
33
src/clientSideScene/helpers.ts
Normal file
33
src/clientSideScene/helpers.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
GridHelper,
|
||||||
|
LineBasicMaterial,
|
||||||
|
OrthographicCamera,
|
||||||
|
PerspectiveCamera,
|
||||||
|
Group,
|
||||||
|
Mesh,
|
||||||
|
} from 'three'
|
||||||
|
|
||||||
|
export function createGridHelper({
|
||||||
|
size,
|
||||||
|
divisions,
|
||||||
|
}: {
|
||||||
|
size: number
|
||||||
|
divisions: number
|
||||||
|
}) {
|
||||||
|
const gridHelperMaterial = new LineBasicMaterial({
|
||||||
|
color: 0xaaaaaa,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
depthTest: false,
|
||||||
|
})
|
||||||
|
const gridHelper = new GridHelper(size, divisions, 0x0000ff, 0xffffff)
|
||||||
|
gridHelper.material = gridHelperMaterial
|
||||||
|
gridHelper.rotation.x = Math.PI / 2
|
||||||
|
return gridHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
|
||||||
|
0.55 / cam.zoom
|
||||||
|
|
||||||
|
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
|
||||||
|
(group.position.distanceTo(cam.position) * cam.fov) / 4000
|
358
src/clientSideScene/segments.ts
Normal file
358
src/clientSideScene/segments.ts
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
|
import {
|
||||||
|
BufferGeometry,
|
||||||
|
CatmullRomCurve3,
|
||||||
|
ConeGeometry,
|
||||||
|
CurvePath,
|
||||||
|
EllipseCurve,
|
||||||
|
ExtrudeGeometry,
|
||||||
|
Group,
|
||||||
|
LineCurve3,
|
||||||
|
Mesh,
|
||||||
|
MeshBasicMaterial,
|
||||||
|
NormalBufferAttributes,
|
||||||
|
Shape,
|
||||||
|
SphereGeometry,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
} from 'three'
|
||||||
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
||||||
|
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
|
||||||
|
import {
|
||||||
|
STRAIGHT_SEGMENT,
|
||||||
|
STRAIGHT_SEGMENT_BODY,
|
||||||
|
STRAIGHT_SEGMENT_DASH,
|
||||||
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
|
TANGENTIAL_ARC_TO_SEGMENT_BODY,
|
||||||
|
TANGENTIAL_ARC_TO__SEGMENT_DASH,
|
||||||
|
} from './clientSideScene'
|
||||||
|
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||||
|
import { ARROWHEAD } from './setup'
|
||||||
|
|
||||||
|
export function straightSegment({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
id,
|
||||||
|
pathToNode,
|
||||||
|
isDraftSegment,
|
||||||
|
scale = 1,
|
||||||
|
}: {
|
||||||
|
from: Coords2d
|
||||||
|
to: Coords2d
|
||||||
|
id: string
|
||||||
|
pathToNode: PathToNode
|
||||||
|
isDraftSegment?: boolean
|
||||||
|
scale?: number
|
||||||
|
}): Group {
|
||||||
|
const group = new Group()
|
||||||
|
|
||||||
|
const shape = new Shape()
|
||||||
|
shape.moveTo(0, -0.08 * scale)
|
||||||
|
shape.lineTo(0, 0.08 * scale) // The width of the line
|
||||||
|
|
||||||
|
let geometry
|
||||||
|
if (isDraftSegment) {
|
||||||
|
geometry = dashedStraight(from, to, shape, scale)
|
||||||
|
} else {
|
||||||
|
const line = new LineCurve3(
|
||||||
|
new Vector3(from[0], from[1], 0),
|
||||||
|
new Vector3(to[0], to[1], 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
geometry = new ExtrudeGeometry(shape, {
|
||||||
|
steps: 2,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: line,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
|
const mesh = new Mesh(geometry, body)
|
||||||
|
mesh.userData.type = isDraftSegment
|
||||||
|
? STRAIGHT_SEGMENT_DASH
|
||||||
|
: STRAIGHT_SEGMENT_BODY
|
||||||
|
mesh.name = STRAIGHT_SEGMENT_BODY
|
||||||
|
|
||||||
|
group.userData = {
|
||||||
|
type: STRAIGHT_SEGMENT,
|
||||||
|
id,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
pathToNode,
|
||||||
|
isSelected: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrowGroup = createArrowhead(scale)
|
||||||
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
|
const dir = new Vector3()
|
||||||
|
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
|
||||||
|
.normalize()
|
||||||
|
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
||||||
|
|
||||||
|
group.add(mesh, arrowGroup)
|
||||||
|
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
function createArrowhead(scale = 1): Group {
|
||||||
|
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
|
const arrowheadMesh = new Mesh(new ConeGeometry(0.31, 1.5, 12), arrowMaterial)
|
||||||
|
arrowheadMesh.position.set(0, -0.6, 0)
|
||||||
|
const sphereMesh = new Mesh(new SphereGeometry(0.27, 12, 12), arrowMaterial)
|
||||||
|
|
||||||
|
const arrowGroup = new Group()
|
||||||
|
arrowGroup.userData.type = ARROWHEAD
|
||||||
|
arrowGroup.name = ARROWHEAD
|
||||||
|
arrowGroup.add(arrowheadMesh, sphereMesh)
|
||||||
|
arrowGroup.lookAt(new Vector3(0, 1, 0))
|
||||||
|
arrowGroup.scale.set(scale, scale, scale)
|
||||||
|
return arrowGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tangentialArcToSegment({
|
||||||
|
prevSegment,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
id,
|
||||||
|
pathToNode,
|
||||||
|
isDraftSegment,
|
||||||
|
scale = 1,
|
||||||
|
}: {
|
||||||
|
prevSegment: SketchGroup['value'][number]
|
||||||
|
from: Coords2d
|
||||||
|
to: Coords2d
|
||||||
|
id: string
|
||||||
|
pathToNode: PathToNode
|
||||||
|
isDraftSegment?: boolean
|
||||||
|
scale?: number
|
||||||
|
}): Group {
|
||||||
|
const group = new Group()
|
||||||
|
|
||||||
|
const previousPoint =
|
||||||
|
prevSegment?.type === 'TangentialArcTo'
|
||||||
|
? getTangentPointFromPreviousArc(
|
||||||
|
prevSegment.center,
|
||||||
|
prevSegment.ccw,
|
||||||
|
prevSegment.to
|
||||||
|
)
|
||||||
|
: prevSegment.from
|
||||||
|
|
||||||
|
const { center, radius, startAngle, endAngle, ccw } = getTangentialArcToInfo({
|
||||||
|
arcStartPoint: from,
|
||||||
|
arcEndPoint: to,
|
||||||
|
tanPreviousPoint: previousPoint,
|
||||||
|
obtuse: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const geometry = createArcGeometry({
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
startAngle,
|
||||||
|
endAngle,
|
||||||
|
ccw,
|
||||||
|
isDashed: isDraftSegment,
|
||||||
|
scale,
|
||||||
|
})
|
||||||
|
|
||||||
|
const body = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
|
const mesh = new Mesh(geometry, body)
|
||||||
|
mesh.userData.type = isDraftSegment
|
||||||
|
? TANGENTIAL_ARC_TO__SEGMENT_DASH
|
||||||
|
: TANGENTIAL_ARC_TO_SEGMENT_BODY
|
||||||
|
|
||||||
|
group.userData = {
|
||||||
|
type: TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
|
id,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
prevSegment,
|
||||||
|
pathToNode,
|
||||||
|
isSelected: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrowGroup = createArrowhead(scale)
|
||||||
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
|
const arrowheadAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
|
||||||
|
arrowGroup.quaternion.setFromUnitVectors(
|
||||||
|
new Vector3(0, 1, 0),
|
||||||
|
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
group.add(mesh, arrowGroup)
|
||||||
|
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createArcGeometry({
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
startAngle,
|
||||||
|
endAngle,
|
||||||
|
ccw,
|
||||||
|
isDashed = false,
|
||||||
|
scale = 1,
|
||||||
|
}: {
|
||||||
|
center: Coords2d
|
||||||
|
radius: number
|
||||||
|
startAngle: number
|
||||||
|
endAngle: number
|
||||||
|
ccw: boolean
|
||||||
|
isDashed?: boolean
|
||||||
|
scale?: number
|
||||||
|
}): BufferGeometry {
|
||||||
|
const dashSize = 1.2 * scale
|
||||||
|
const gapSize = 1.2 * scale
|
||||||
|
const arcStart = new EllipseCurve(
|
||||||
|
center[0],
|
||||||
|
center[1],
|
||||||
|
radius,
|
||||||
|
radius,
|
||||||
|
startAngle,
|
||||||
|
endAngle,
|
||||||
|
!ccw,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const arcEnd = new EllipseCurve(
|
||||||
|
center[0],
|
||||||
|
center[1],
|
||||||
|
radius,
|
||||||
|
radius,
|
||||||
|
endAngle,
|
||||||
|
startAngle,
|
||||||
|
ccw,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const shape = new Shape()
|
||||||
|
shape.moveTo(0, -0.08 * scale)
|
||||||
|
shape.lineTo(0, 0.08 * scale) // The width of the line
|
||||||
|
|
||||||
|
if (!isDashed) {
|
||||||
|
const points = arcStart.getPoints(50)
|
||||||
|
const path = new CurvePath<Vector3>()
|
||||||
|
path.add(new CatmullRomCurve3(points.map((p) => new Vector3(p.x, p.y, 0))))
|
||||||
|
|
||||||
|
return new ExtrudeGeometry(shape, {
|
||||||
|
steps: 100,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const length = arcStart.getLength()
|
||||||
|
const totalDashes = length / (dashSize + gapSize) // rounding makes the dashes jittery since the new dash is suddenly appears instead of growing into place
|
||||||
|
const dashesAtEachEnd = Math.min(100, totalDashes / 2) // Assuming we want 50 dashes total, 25 at each end
|
||||||
|
|
||||||
|
const dashGeometries = []
|
||||||
|
|
||||||
|
// Function to create a dash at a specific t value (0 to 1 along the curve)
|
||||||
|
const createDashAt = (t: number, curve: EllipseCurve) => {
|
||||||
|
const startVec = curve.getPoint(t)
|
||||||
|
const endVec = curve.getPoint(Math.min(0.5, t + dashSize / length))
|
||||||
|
const midVec = curve.getPoint(Math.min(0.5, t + dashSize / length / 2))
|
||||||
|
const dashCurve = new CurvePath<Vector3>()
|
||||||
|
dashCurve.add(
|
||||||
|
new CatmullRomCurve3([
|
||||||
|
new Vector3(startVec.x, startVec.y, 0),
|
||||||
|
new Vector3(midVec.x, midVec.y, 0),
|
||||||
|
new Vector3(endVec.x, endVec.y, 0),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
return new ExtrudeGeometry(shape, {
|
||||||
|
steps: 3,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: dashCurve,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create dashes at the start of the arc
|
||||||
|
for (let i = 0; i < dashesAtEachEnd; i++) {
|
||||||
|
const t = i / totalDashes
|
||||||
|
dashGeometries.push(createDashAt(t, arcStart))
|
||||||
|
dashGeometries.push(createDashAt(t, arcEnd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill in the remaining arc
|
||||||
|
const remainingArcLength = length - dashesAtEachEnd * 2 * (dashSize + gapSize)
|
||||||
|
if (remainingArcLength > 0) {
|
||||||
|
const remainingArcStartT = dashesAtEachEnd / totalDashes
|
||||||
|
const remainingArcEndT = 1 - remainingArcStartT
|
||||||
|
const centerVec = new Vector2(center[0], center[1])
|
||||||
|
const remainingArcStartVec = arcStart.getPoint(remainingArcStartT)
|
||||||
|
const remainingArcEndVec = arcStart.getPoint(remainingArcEndT)
|
||||||
|
const remainingArcCurve = new EllipseCurve(
|
||||||
|
arcStart.aX,
|
||||||
|
arcStart.aY,
|
||||||
|
arcStart.xRadius,
|
||||||
|
arcStart.yRadius,
|
||||||
|
new Vector2().subVectors(centerVec, remainingArcStartVec).angle() +
|
||||||
|
Math.PI,
|
||||||
|
new Vector2().subVectors(centerVec, remainingArcEndVec).angle() + Math.PI,
|
||||||
|
!ccw
|
||||||
|
)
|
||||||
|
const remainingArcPoints = remainingArcCurve.getPoints(50)
|
||||||
|
const remainingArcPath = new CurvePath<Vector3>()
|
||||||
|
remainingArcPath.add(
|
||||||
|
new CatmullRomCurve3(
|
||||||
|
remainingArcPoints.map((p) => new Vector3(p.x, p.y, 0))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const remainingArcGeometry = new ExtrudeGeometry(shape, {
|
||||||
|
steps: 50,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: remainingArcPath,
|
||||||
|
})
|
||||||
|
dashGeometries.push(remainingArcGeometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
const geo = dashGeometries.length
|
||||||
|
? mergeGeometries(dashGeometries)
|
||||||
|
: new BufferGeometry()
|
||||||
|
geo.userData.type = 'dashed'
|
||||||
|
return geo
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dashedStraight(
|
||||||
|
from: Coords2d,
|
||||||
|
to: Coords2d,
|
||||||
|
shape: Shape,
|
||||||
|
scale = 1
|
||||||
|
): BufferGeometry<NormalBufferAttributes> {
|
||||||
|
const dashSize = 1.2 * scale
|
||||||
|
const gapSize = 1.2 * scale // todo: gabSize is not respected
|
||||||
|
const dashLine = new LineCurve3(
|
||||||
|
new Vector3(from[0], from[1], 0),
|
||||||
|
new Vector3(to[0], to[1], 0)
|
||||||
|
)
|
||||||
|
const length = dashLine.getLength()
|
||||||
|
const numberOfPoints = (length / (dashSize + gapSize)) * 2
|
||||||
|
const startOfLine = new Vector3(from[0], from[1], 0)
|
||||||
|
const endOfLine = new Vector3(to[0], to[1], 0)
|
||||||
|
const dashGeometries = []
|
||||||
|
const dashComponent = (xOrY: number, pointIndex: number) =>
|
||||||
|
((to[xOrY] - from[xOrY]) / numberOfPoints) * pointIndex + from[xOrY]
|
||||||
|
for (let i = 0; i < numberOfPoints; i += 2) {
|
||||||
|
const dashStart = new Vector3(dashComponent(0, i), dashComponent(1, i), 0)
|
||||||
|
let dashEnd = new Vector3(
|
||||||
|
dashComponent(0, i + 1),
|
||||||
|
dashComponent(1, i + 1),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
if (startOfLine.distanceTo(dashEnd) > startOfLine.distanceTo(endOfLine))
|
||||||
|
dashEnd = endOfLine
|
||||||
|
|
||||||
|
if (dashEnd) {
|
||||||
|
const dashCurve = new LineCurve3(dashStart, dashEnd)
|
||||||
|
const dashGeometry = new ExtrudeGeometry(shape, {
|
||||||
|
steps: 1,
|
||||||
|
bevelEnabled: false,
|
||||||
|
extrudePath: dashCurve,
|
||||||
|
})
|
||||||
|
dashGeometries.push(dashGeometry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const geo = dashGeometries.length
|
||||||
|
? mergeGeometries(dashGeometries)
|
||||||
|
: new BufferGeometry()
|
||||||
|
geo.userData.type = 'dashed'
|
||||||
|
return geo
|
||||||
|
}
|
28
src/clientSideScene/setup.test.ts
Normal file
28
src/clientSideScene/setup.test.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Quaternion } from 'three'
|
||||||
|
import { isQuaternionVertical } from './setup'
|
||||||
|
|
||||||
|
describe('isQuaternionVertical', () => {
|
||||||
|
it('should identify vertical quaternions', () => {
|
||||||
|
const verticalQuaternions = [
|
||||||
|
new Quaternion(1, 0, 0, 0).normalize(), // bottom
|
||||||
|
new Quaternion(-0.7, 0.7, 0, 0).normalize(), // bottom 2
|
||||||
|
new Quaternion(0, 1, 0, 0).normalize(), // bottom 3
|
||||||
|
new Quaternion(0, 0, 0, 1).normalize(), // look from top
|
||||||
|
]
|
||||||
|
verticalQuaternions.forEach((quaternion) => {
|
||||||
|
expect(isQuaternionVertical(quaternion)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should identify non-vertical quaternions', () => {
|
||||||
|
const nonVerticalQuaternions = [
|
||||||
|
new Quaternion(0.7, 0, 0, 0.7).normalize(), // front
|
||||||
|
new Quaternion(0, 0.7, 0.7, 0).normalize(), // back
|
||||||
|
new Quaternion(-0.5, 0.5, 0.5, -0.5).normalize(), // left side
|
||||||
|
new Quaternion(0.5, 0.5, 0.5, 0.5).normalize(), // right side
|
||||||
|
]
|
||||||
|
nonVerticalQuaternions.forEach((quaternion) => {
|
||||||
|
expect(isQuaternionVertical(quaternion)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
1296
src/clientSideScene/setup.tsx
Normal file
1296
src/clientSideScene/setup.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { paths } from '../Router'
|
import { paths } from 'lib/paths'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import type { LinkProps } from 'react-router-dom'
|
import type { LinkProps } from 'react-router-dom'
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Toolbar } from '../Toolbar'
|
import { Toolbar } from '../Toolbar'
|
||||||
import UserSidebarMenu from './UserSidebarMenu'
|
import UserSidebarMenu from './UserSidebarMenu'
|
||||||
import { IndexLoaderData } from '../Router'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from '../lang/modifyAst'
|
} from '../lang/modifyAst'
|
||||||
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { kclManager, useKclContext } from 'lang/KclSinglton'
|
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { executeAst } from 'useStore'
|
import { executeAst } from 'useStore'
|
||||||
|
|
||||||
@ -138,38 +138,37 @@ export function useCalc({
|
|||||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const execAstAndSetResult = async () => {
|
try {
|
||||||
const code = `const __result__ = ${value}`
|
const code = `const __result__ = ${value}`
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const _programMem: any = { root: {}, return: null }
|
const _programMem: any = { root: {}, return: null }
|
||||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||||
})
|
})
|
||||||
const { programMemory } = await executeAst({
|
executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
defaultPlanes: kclManager.defaultPlanes,
|
|
||||||
useFakeExecutor: true,
|
useFakeExecutor: true,
|
||||||
programMemoryOverride: JSON.parse(
|
programMemoryOverride: JSON.parse(
|
||||||
JSON.stringify(kclManager.programMemory)
|
JSON.stringify(kclManager.programMemory)
|
||||||
),
|
),
|
||||||
|
}).then(({ programMemory }) => {
|
||||||
|
const resultDeclaration = ast.body.find(
|
||||||
|
(a) =>
|
||||||
|
a.type === 'VariableDeclaration' &&
|
||||||
|
a.declarations?.[0]?.id?.name === '__result__'
|
||||||
|
)
|
||||||
|
const init =
|
||||||
|
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||||
|
resultDeclaration?.declarations?.[0]?.init
|
||||||
|
const result = programMemory?.root?.__result__?.value
|
||||||
|
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||||
|
init && setValueNode(init)
|
||||||
})
|
})
|
||||||
const resultDeclaration = ast.body.find(
|
} catch (e) {
|
||||||
(a) =>
|
|
||||||
a.type === 'VariableDeclaration' &&
|
|
||||||
a.declarations?.[0]?.id?.name === '__result__'
|
|
||||||
)
|
|
||||||
const init =
|
|
||||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
|
||||||
resultDeclaration?.declarations?.[0]?.init
|
|
||||||
const result = programMemory?.root?.__result__?.value
|
|
||||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
|
||||||
init && setValueNode(init)
|
|
||||||
}
|
|
||||||
execAstAndSetResult().catch(() => {
|
|
||||||
setCalcResult('NAN')
|
setCalcResult('NAN')
|
||||||
setValueNode(null)
|
setValueNode(null)
|
||||||
})
|
}
|
||||||
}, [value, availableVarInfo])
|
}, [value, availableVarInfo])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
75
src/components/CamToggle.tsx
Normal file
75
src/components/CamToggle.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { setupSingleton } from '../clientSideScene/setup'
|
||||||
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
import { throttle, isReducedMotion } from 'lib/utils'
|
||||||
|
|
||||||
|
const updateDollyZoom = throttle(
|
||||||
|
(newFov: number) => setupSingleton.dollyZoom(newFov),
|
||||||
|
1000 / 15
|
||||||
|
)
|
||||||
|
|
||||||
|
export const CamToggle = () => {
|
||||||
|
const [isPerspective, setIsPerspective] = useState(true)
|
||||||
|
const [fov, setFov] = useState(40)
|
||||||
|
const [enableRotate, setEnableRotate] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
engineCommandManager.waitForReady.then(async () => {
|
||||||
|
setupSingleton.dollyZoom(fov)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const toggleCamera = () => {
|
||||||
|
if (isPerspective) {
|
||||||
|
isReducedMotion()
|
||||||
|
? setupSingleton.useOrthographicCamera()
|
||||||
|
: setupSingleton.animateToOrthographic()
|
||||||
|
} else {
|
||||||
|
isReducedMotion()
|
||||||
|
? setupSingleton.usePerspectiveCamera()
|
||||||
|
: setupSingleton.animateToPerspective()
|
||||||
|
}
|
||||||
|
setIsPerspective(!isPerspective)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFovChange = (newFov: number) => {
|
||||||
|
setFov(newFov)
|
||||||
|
updateDollyZoom(newFov)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="absolute right-14 bottom-3">
|
||||||
|
{isPerspective && (
|
||||||
|
<div className="">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="4"
|
||||||
|
max="90"
|
||||||
|
step={0.5}
|
||||||
|
value={fov}
|
||||||
|
onChange={(e) => handleFovChange(Number(e.target.value))}
|
||||||
|
className="w-full cursor-pointer pointer-events-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button onClick={toggleCamera} className="">
|
||||||
|
{isPerspective
|
||||||
|
? 'Switch to Orthographic Camera'
|
||||||
|
: 'Switch to Perspective Camera'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (enableRotate) {
|
||||||
|
setupSingleton.controls.enableRotate = false
|
||||||
|
} else {
|
||||||
|
setupSingleton.controls.enableRotate = true
|
||||||
|
}
|
||||||
|
setEnableRotate(!enableRotate)
|
||||||
|
}}
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
{enableRotate ? 'Disable Rotation' : 'Enable Rotation'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -9,7 +9,7 @@ import styles from './CodeMenu.module.css'
|
|||||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||||
import { editorShortcutMeta } from './TextEditor'
|
import { editorShortcutMeta } from './TextEditor'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { useKclContext } from 'lang/KclSinglton'
|
import { useKclContext } from 'lang/KclSingleton'
|
||||||
import { CommandArgument } from 'lib/commandTypes'
|
import { CommandArgument } from 'lib/commandTypes'
|
||||||
import {
|
import {
|
||||||
ResolvedSelectionType,
|
ResolvedSelectionType,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { AstExplorer } from './AstExplorer'
|
import { AstExplorer } from './AstExplorer'
|
||||||
import { EngineCommands } from './EngineCommands'
|
import { EngineCommands } from './EngineCommands'
|
||||||
|
import { CamDebugSettings } from 'clientSideScene/setup'
|
||||||
|
|
||||||
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
||||||
return (
|
return (
|
||||||
@ -15,6 +16,7 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
|||||||
>
|
>
|
||||||
<section className="p-4 flex flex-col gap-4">
|
<section className="p-4 flex flex-col gap-4">
|
||||||
<EngineCommands />
|
<EngineCommands />
|
||||||
|
<CamDebugSettings />
|
||||||
<div style={{ height: '400px' }} className="overflow-y-auto">
|
<div style={{ height: '400px' }} className="overflow-y-auto">
|
||||||
<AstExplorer />
|
<AstExplorer />
|
||||||
</div>
|
</div>
|
||||||
|
43
src/components/DragWarningToast.tsx
Normal file
43
src/components/DragWarningToast.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { MoveDesc } from 'machines/modelingMachine'
|
||||||
|
|
||||||
|
export const DragWarningToast = (moveDescs: MoveDesc[]) => {
|
||||||
|
if (moveDescs.length === 1) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div>🔒</div>
|
||||||
|
<div className="dark:bg-slate-950/50 bg-slate-400/50 p-1 px-3 rounded-xl text-sm">
|
||||||
|
move disabled: line{' '}
|
||||||
|
<span className="dark:text-energy-20 text-lime-600">
|
||||||
|
{moveDescs[0].line}
|
||||||
|
</span>
|
||||||
|
:{' '}
|
||||||
|
<pre>
|
||||||
|
<code className="dark:text-energy-20 text-lime-600">
|
||||||
|
{moveDescs[0].snippet}
|
||||||
|
</code>
|
||||||
|
</pre>{' '}
|
||||||
|
is fully constrained
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else if (moveDescs.length > 1) {
|
||||||
|
return (
|
||||||
|
<div className="dark:bg-slate-950/50 bg-slate-400/50 p-1 px-3 rounded-xl text-sm">
|
||||||
|
<div>Move disabled as The following lines are constrained</div>
|
||||||
|
{moveDescs.map((desc, i) => {
|
||||||
|
return (
|
||||||
|
<div key={i}>
|
||||||
|
line {desc.line}:{' '}
|
||||||
|
<pre className="inline-block">
|
||||||
|
<code className="dark:text-energy-20 text-lime-600">
|
||||||
|
{moveDescs[0].snippet}
|
||||||
|
</code>
|
||||||
|
</pre>{' '}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
@ -93,7 +93,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
|||||||
if (values.type === 'ply' || values.type === 'stl') {
|
if (values.type === 'ply' || values.type === 'stl') {
|
||||||
values.selection = { type: 'default_scene' }
|
values.selection = { type: 'default_scene' }
|
||||||
}
|
}
|
||||||
void engineCommandManager.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'export',
|
type: 'export',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useMachine } from '@xstate/react'
|
import { useMachine } from '@xstate/react'
|
||||||
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||||
import { IndexLoaderData, paths } from '../Router'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
import React, { createContext } from 'react'
|
import React, { createContext } from 'react'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import {
|
import {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { IndexLoaderData, paths } from 'Router'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
import { FileEntry } from '@tauri-apps/api/fs'
|
import { FileEntry } from '@tauri-apps/api/fs'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useMachine } from '@xstate/react'
|
import { useMachine } from '@xstate/react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { paths } from '../Router'
|
import { paths } from 'lib/paths'
|
||||||
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
|
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
|
||||||
import withBaseUrl from '../lib/withBaseURL'
|
import withBaseUrl from '../lib/withBaseURL'
|
||||||
import React, { createContext, useEffect, useRef } from 'react'
|
import React, { createContext, useEffect, useRef } from 'react'
|
||||||
@ -101,7 +101,7 @@ export const GlobalStateProvider = ({
|
|||||||
goToSignInPage: () => {
|
goToSignInPage: () => {
|
||||||
navigate(paths.SIGN_IN)
|
navigate(paths.SIGN_IN)
|
||||||
|
|
||||||
void logout()
|
logout()
|
||||||
},
|
},
|
||||||
goToIndexPage: () => {
|
goToIndexPage: () => {
|
||||||
if (window.location.pathname.includes(paths.SIGN_IN)) {
|
if (window.location.pathname.includes(paths.SIGN_IN)) {
|
||||||
|
@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
import { useKclContext } from 'lang/KclSinglton'
|
import { useKclContext } from 'lang/KclSingleton'
|
||||||
|
|
||||||
const ReactJsonTypeHack = ReactJson as any
|
const ReactJsonTypeHack = ReactJson as any
|
||||||
|
|
||||||
|
@ -41,9 +41,9 @@ describe('processMemory', () => {
|
|||||||
otherVar: 3,
|
otherVar: 3,
|
||||||
theExtrude: [],
|
theExtrude: [],
|
||||||
theSketch: [
|
theSketch: [
|
||||||
{ type: 'toPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
|
{ type: 'ToPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
|
||||||
{ type: 'toPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
|
{ type: 'ToPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
|
||||||
{ type: 'toPoint', to: [2.15, 4.32], from: [0.98, 5.16], name: '' },
|
{ type: 'ToPoint', to: [2.15, 4.32], from: [0.98, 5.16], name: '' },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@ import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
|
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
|
||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
import { useKclContext } from 'lang/KclSinglton'
|
import { useKclContext } from 'lang/KclSingleton'
|
||||||
|
|
||||||
interface MemoryPanelProps extends CollapsiblePanelProps {
|
interface MemoryPanelProps extends CollapsiblePanelProps {
|
||||||
theme?: Exclude<Themes, Themes.System>
|
theme?: Exclude<Themes, Themes.System>
|
||||||
|
@ -13,23 +13,7 @@ import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
|||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||||
import { addStartSketch } from 'lang/modifyAst'
|
|
||||||
import { roundOff } from 'lib/utils'
|
|
||||||
import {
|
|
||||||
recast,
|
|
||||||
parse,
|
|
||||||
Program,
|
|
||||||
PipeExpression,
|
|
||||||
CallExpression,
|
|
||||||
} from 'lang/wasm'
|
|
||||||
import { getNodeFromPath } from 'lang/queryAst'
|
|
||||||
import {
|
|
||||||
addCloseToPipe,
|
|
||||||
addNewSketchLn,
|
|
||||||
compareVec2Epsilon,
|
|
||||||
} from 'lang/std/sketch'
|
|
||||||
import { kclManager, useKclContext } from 'lang/KclSinglton'
|
|
||||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||||
import {
|
import {
|
||||||
angleBetweenInfo,
|
angleBetweenInfo,
|
||||||
@ -49,6 +33,9 @@ import { applyConstraintIntersect } from './Toolbar/Intersect'
|
|||||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||||
|
import { setupSingleton } from 'clientSideScene/setup'
|
||||||
|
import { getSketchQuaternion } from 'clientSideScene/clientSideScene'
|
||||||
|
import { startSketchOnDefault } from 'lang/modifyAst'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -92,181 +79,10 @@ export const ModelingMachineProvider = ({
|
|||||||
const [modelingState, modelingSend, modelingActor] = useMachine(
|
const [modelingState, modelingSend, modelingActor] = useMachine(
|
||||||
modelingMachine,
|
modelingMachine,
|
||||||
{
|
{
|
||||||
// context: persistedSettings,
|
|
||||||
actions: {
|
actions: {
|
||||||
'Modify AST': () => {},
|
|
||||||
'Update code selection cursors': () => {},
|
|
||||||
'show default planes': () => {
|
|
||||||
void kclManager.showPlanes()
|
|
||||||
},
|
|
||||||
'create path': assign({
|
|
||||||
sketchEnginePathId: () => {
|
|
||||||
const sketchUuid = uuidv4()
|
|
||||||
void engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: sketchUuid,
|
|
||||||
cmd: {
|
|
||||||
type: 'start_path',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
void engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'edit_mode_enter',
|
|
||||||
target: sketchUuid,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return sketchUuid
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'AST start new sketch': assign(
|
|
||||||
({ sketchEnginePathId }, { data: { coords, axis, segmentId } }) => {
|
|
||||||
if (!axis) {
|
|
||||||
// Something really weird must have happened for this to happen.
|
|
||||||
console.error('axis is undefined for starting a new sketch')
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
if (!segmentId) {
|
|
||||||
// Something really weird must have happened for this to happen.
|
|
||||||
console.error('segmentId is undefined for starting a new sketch')
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _addStartSketch = addStartSketch(
|
|
||||||
kclManager.ast,
|
|
||||||
axis,
|
|
||||||
[roundOff(coords[0].x), roundOff(coords[0].y)],
|
|
||||||
[
|
|
||||||
roundOff(coords[1].x - coords[0].x),
|
|
||||||
roundOff(coords[1].y - coords[0].y),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
const _modifiedAst = _addStartSketch.modifiedAst
|
|
||||||
const _pathToNode = _addStartSketch.pathToNode
|
|
||||||
const newCode = recast(_modifiedAst)
|
|
||||||
const astWithUpdatedSource = parse(newCode)
|
|
||||||
const updatedPipeNode = getNodeFromPath<PipeExpression>(
|
|
||||||
astWithUpdatedSource,
|
|
||||||
_pathToNode
|
|
||||||
).node
|
|
||||||
const startProfileAtCallExp = updatedPipeNode.body.find(
|
|
||||||
(exp) =>
|
|
||||||
exp.type === 'CallExpression' &&
|
|
||||||
exp.callee.name === 'startProfileAt'
|
|
||||||
)
|
|
||||||
if (startProfileAtCallExp)
|
|
||||||
engineCommandManager.artifactMap[sketchEnginePathId] = {
|
|
||||||
type: 'result',
|
|
||||||
range: [startProfileAtCallExp.start, startProfileAtCallExp.end],
|
|
||||||
commandType: 'start_path',
|
|
||||||
data: null,
|
|
||||||
raw: {} as any,
|
|
||||||
}
|
|
||||||
const lineCallExp = updatedPipeNode.body.find(
|
|
||||||
(exp) =>
|
|
||||||
exp.type === 'CallExpression' && exp.callee.name === 'line'
|
|
||||||
)
|
|
||||||
if (lineCallExp)
|
|
||||||
engineCommandManager.artifactMap[segmentId] = {
|
|
||||||
type: 'result',
|
|
||||||
range: [lineCallExp.start, lineCallExp.end],
|
|
||||||
commandType: 'extend_path',
|
|
||||||
parentId: sketchEnginePathId,
|
|
||||||
data: null,
|
|
||||||
raw: {} as any,
|
|
||||||
}
|
|
||||||
|
|
||||||
void kclManager.executeAstMock(astWithUpdatedSource, true)
|
|
||||||
|
|
||||||
return {
|
|
||||||
sketchPathToNode: _pathToNode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
'AST add line segment': async (
|
|
||||||
{ sketchPathToNode, sketchEnginePathId },
|
|
||||||
{ data: { coords, segmentId } }
|
|
||||||
) => {
|
|
||||||
if (!sketchPathToNode) return
|
|
||||||
const lastCoord = coords[coords.length - 1]
|
|
||||||
|
|
||||||
const pathInfo = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'path_get_info',
|
|
||||||
path_id: sketchEnginePathId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const firstSegment = pathInfo?.data?.data?.segments.find(
|
|
||||||
(seg: any) => seg.command === 'line_to'
|
|
||||||
)
|
|
||||||
const firstSegCoords = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'curve_get_control_points',
|
|
||||||
curve_id: firstSegment.command_id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const startPathCoord = firstSegCoords?.data?.data?.control_points[0]
|
|
||||||
|
|
||||||
const isClose = compareVec2Epsilon(
|
|
||||||
[startPathCoord.x, startPathCoord.y],
|
|
||||||
[lastCoord.x, lastCoord.y]
|
|
||||||
)
|
|
||||||
|
|
||||||
let _modifiedAst: Program
|
|
||||||
if (!isClose) {
|
|
||||||
const newSketchLn = addNewSketchLn({
|
|
||||||
node: kclManager.ast,
|
|
||||||
programMemory: kclManager.programMemory,
|
|
||||||
to: [lastCoord.x, lastCoord.y],
|
|
||||||
from: [coords[0].x, coords[0].y],
|
|
||||||
fnName: 'line',
|
|
||||||
pathToNode: sketchPathToNode,
|
|
||||||
})
|
|
||||||
const _modifiedAst = newSketchLn.modifiedAst
|
|
||||||
void kclManager.executeAstMock(_modifiedAst, true).then(() => {
|
|
||||||
const lineCallExp = getNodeFromPath<CallExpression>(
|
|
||||||
kclManager.ast,
|
|
||||||
newSketchLn.pathToNode
|
|
||||||
).node
|
|
||||||
if (segmentId)
|
|
||||||
engineCommandManager.artifactMap[segmentId] = {
|
|
||||||
type: 'result',
|
|
||||||
range: [lineCallExp.start, lineCallExp.end],
|
|
||||||
commandType: 'extend_path',
|
|
||||||
parentId: sketchEnginePathId,
|
|
||||||
data: null,
|
|
||||||
raw: {} as any,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
_modifiedAst = addCloseToPipe({
|
|
||||||
node: kclManager.ast,
|
|
||||||
programMemory: kclManager.programMemory,
|
|
||||||
pathToNode: sketchPathToNode,
|
|
||||||
})
|
|
||||||
void engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'edit_mode_exit' },
|
|
||||||
})
|
|
||||||
void engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'default_camera_disable_sketch_mode' },
|
|
||||||
})
|
|
||||||
void kclManager.executeAstMock(_modifiedAst, true)
|
|
||||||
// updateAst(_modifiedAst, true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'sketch exit execute': () => {
|
'sketch exit execute': () => {
|
||||||
void kclManager.executeAst()
|
kclManager.executeAst()
|
||||||
},
|
},
|
||||||
'set tool': () => {}, // TODO
|
|
||||||
'Set selection': assign(({ selectionRanges }, event) => {
|
'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
|
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
|
const setSelections = event.data
|
||||||
@ -274,36 +90,6 @@ export const ModelingMachineProvider = ({
|
|||||||
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
|
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
|
||||||
return { selectionRanges: setSelections.selection }
|
return { selectionRanges: setSelections.selection }
|
||||||
else if (setSelections.selectionType === 'otherSelection') {
|
else if (setSelections.selectionType === 'otherSelection') {
|
||||||
// TODO KittyCAD/engine/issues/1620: send axis highlight when it's working (if that's what we settle on)
|
|
||||||
// const axisAddCmd: EngineCommand = {
|
|
||||||
// type: 'modeling_cmd_req',
|
|
||||||
// cmd: {
|
|
||||||
// type: 'highlight_set_entities',
|
|
||||||
// entities: [
|
|
||||||
// setSelections.selection === 'x-axis'
|
|
||||||
// ? X_AXIS_UUID
|
|
||||||
// : Y_AXIS_UUID,
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// cmd_id: uuidv4(),
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!isShiftDown) {
|
|
||||||
// engineCommandManager
|
|
||||||
// .sendSceneCommand({
|
|
||||||
// type: 'modeling_cmd_req',
|
|
||||||
// cmd: {
|
|
||||||
// type: 'select_clear',
|
|
||||||
// },
|
|
||||||
// cmd_id: uuidv4(),
|
|
||||||
// })
|
|
||||||
// .then(() => {
|
|
||||||
// engineCommandManager.sendSceneCommand(axisAddCmd)
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// engineCommandManager.sendSceneCommand(axisAddCmd)
|
|
||||||
// }
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
codeMirrorSelection,
|
codeMirrorSelection,
|
||||||
selectionRangeTypeMap,
|
selectionRangeTypeMap,
|
||||||
@ -384,12 +170,6 @@ export const ModelingMachineProvider = ({
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
guards: {
|
guards: {
|
||||||
'Selection contains axis': () => true,
|
|
||||||
'Selection contains edge': () => true,
|
|
||||||
'Selection contains face': () => true,
|
|
||||||
'Selection contains line': () => true,
|
|
||||||
'Selection contains point': () => true,
|
|
||||||
'Selection is not empty': () => true,
|
|
||||||
'has valid extrude selection': ({ selectionRanges }) => {
|
'has valid extrude selection': ({ selectionRanges }) => {
|
||||||
// A user can begin extruding if they either have 1+ faces selected or nothing selected
|
// A user can begin extruding if they either have 1+ faces selected or nothing selected
|
||||||
// TODO: I believe this guard only allows for extruding a single face at a time
|
// TODO: I believe this guard only allows for extruding a single face at a time
|
||||||
@ -409,6 +189,29 @@ export const ModelingMachineProvider = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
|
'animate-to-face': async (_, { data: { plane, normal } }) => {
|
||||||
|
const { modifiedAst, pathToNode } = startSketchOnDefault(
|
||||||
|
kclManager.ast,
|
||||||
|
plane
|
||||||
|
)
|
||||||
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
|
const quaternion = getSketchQuaternion(pathToNode, normal)
|
||||||
|
await setupSingleton.tweenCameraToQuaternion(quaternion)
|
||||||
|
return {
|
||||||
|
sketchPathToNode: pathToNode,
|
||||||
|
sketchNormalBackUp: normal,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'animate-to-sketch': async ({
|
||||||
|
sketchPathToNode,
|
||||||
|
sketchNormalBackUp,
|
||||||
|
}) => {
|
||||||
|
const quaternion = getSketchQuaternion(
|
||||||
|
sketchPathToNode || [],
|
||||||
|
sketchNormalBackUp
|
||||||
|
)
|
||||||
|
await setupSingleton.tweenCameraToQuaternion(quaternion)
|
||||||
|
},
|
||||||
'Get horizontal info': async ({
|
'Get horizontal info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
}): Promise<SetSelections> => {
|
}): Promise<SetSelections> => {
|
||||||
@ -542,17 +345,6 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
engineCommandManager.onPlaneSelected((plane_id: string) => {
|
|
||||||
if (modelingState.nextEvents.includes('Select default plane')) {
|
|
||||||
modelingSend({
|
|
||||||
type: 'Select default plane',
|
|
||||||
data: { planeId: plane_id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [modelingSend, modelingState.nextEvents])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
kclManager.registerExecuteCallback(() => {
|
kclManager.registerExecuteCallback(() => {
|
||||||
modelingSend({ type: 'Re-execute' })
|
modelingSend({ type: 'Re-execute' })
|
||||||
@ -565,10 +357,7 @@ export const ModelingMachineProvider = ({
|
|||||||
send: modelingSend,
|
send: modelingSend,
|
||||||
actor: modelingActor,
|
actor: modelingActor,
|
||||||
commandBarConfig: modelingMachineConfig,
|
commandBarConfig: modelingMachineConfig,
|
||||||
onCancel: () => {
|
onCancel: () => modelingSend({ type: 'Cancel' }),
|
||||||
console.log('firing onCancel!!')
|
|
||||||
modelingSend({ type: 'Cancel' })
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { FormEvent, useEffect, useRef, useState } from 'react'
|
import { FormEvent, useEffect, useRef, useState } from 'react'
|
||||||
import { type ProjectWithEntryPointMetadata, paths } from '../Router'
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import {
|
import {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
import { GlobalStateProvider } from './GlobalStateProvider'
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
import CommandBarProvider from './CommandBar/CommandBar'
|
import CommandBarProvider from './CommandBar/CommandBar'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Popover, Transition } from '@headlessui/react'
|
import { Popover, Transition } from '@headlessui/react'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { faHome } from '@fortawesome/free-solid-svg-icons'
|
import { faHome } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { IndexLoaderData, paths } from '../Router'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
import { isTauri } from '../lib/isTauri'
|
import { isTauri } from '../lib/isTauri'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { ExportButton } from './ExportButton'
|
import { ExportButton } from './ExportButton'
|
||||||
|
@ -11,16 +11,13 @@ import { getNormalisedCoordinates, throttle } from '../lib/utils'
|
|||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { getNodeFromPath } from 'lang/queryAst'
|
|
||||||
import { VariableDeclarator, recast, CallExpression } from 'lang/wasm'
|
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { kclManager, useKclContext } from 'lang/KclSinglton'
|
import { useKclContext } from 'lang/KclSingleton'
|
||||||
import { changeSketchArguments } from 'lang/std/sketch'
|
import { ClientSideScene } from 'clientSideScene/setup'
|
||||||
|
|
||||||
export const Stream = ({ className = '' }) => {
|
export const Stream = ({ className = '' }: { className?: string }) => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
|
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
@ -39,7 +36,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
}))
|
}))
|
||||||
const { settings } = useGlobalStateContext()
|
const { settings } = useGlobalStateContext()
|
||||||
const cameraControls = settings?.context?.cameraControls
|
const cameraControls = settings?.context?.cameraControls
|
||||||
const { send, state, context } = useModelingContext()
|
const { state } = useModelingContext()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -53,8 +50,10 @@ export const Stream = ({ className = '' }) => {
|
|||||||
videoRef.current.srcObject = mediaStream
|
videoRef.current.srcObject = mediaStream
|
||||||
}, [mediaStream])
|
}, [mediaStream])
|
||||||
|
|
||||||
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
|
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
|
if (state.matches('Sketch')) return
|
||||||
|
if (state.matches('Sketch no face')) return
|
||||||
const { x, y } = getNormalisedCoordinates({
|
const { x, y } = getNormalisedCoordinates({
|
||||||
clientX: e.clientX,
|
clientX: e.clientX,
|
||||||
clientY: e.clientY,
|
clientY: e.clientY,
|
||||||
@ -62,55 +61,6 @@ export const Stream = ({ className = '' }) => {
|
|||||||
...streamDimensions,
|
...streamDimensions,
|
||||||
})
|
})
|
||||||
|
|
||||||
const newId = uuidv4()
|
|
||||||
|
|
||||||
const interactionGuards = cameraMouseDragGuards[cameraControls]
|
|
||||||
let interaction: CameraDragInteractionType_type = 'rotate'
|
|
||||||
|
|
||||||
if (
|
|
||||||
interactionGuards.pan.callback(e) ||
|
|
||||||
interactionGuards.pan.lenientDragStartButton === e.button
|
|
||||||
) {
|
|
||||||
interaction = 'pan'
|
|
||||||
} else if (
|
|
||||||
interactionGuards.rotate.callback(e) ||
|
|
||||||
interactionGuards.rotate.lenientDragStartButton === e.button
|
|
||||||
) {
|
|
||||||
interaction = 'rotate'
|
|
||||||
} else if (
|
|
||||||
interactionGuards.zoom.dragCallback(e) ||
|
|
||||||
interactionGuards.zoom.lenientDragStartButton === e.button
|
|
||||||
) {
|
|
||||||
interaction = 'zoom'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.matches('Sketch.Move Tool')) {
|
|
||||||
if (
|
|
||||||
state.matches('Sketch.Move Tool.No move') ||
|
|
||||||
state.matches('Sketch.Move Tool.Move with execute')
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
void engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'handle_mouse_drag_start',
|
|
||||||
window: { x, y },
|
|
||||||
},
|
|
||||||
cmd_id: newId,
|
|
||||||
})
|
|
||||||
} else if (!state.matches('Sketch.Line Tool')) {
|
|
||||||
void engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'camera_drag_start',
|
|
||||||
interaction,
|
|
||||||
window: { x, y },
|
|
||||||
},
|
|
||||||
cmd_id: newId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setButtonDownInStream(e.button)
|
setButtonDownInStream(e.button)
|
||||||
setClickCoords({ x, y })
|
setClickCoords({ x, y })
|
||||||
}
|
}
|
||||||
@ -118,7 +68,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const fps = 60
|
const fps = 60
|
||||||
const handleScroll: WheelEventHandler<HTMLVideoElement> = throttle((e) => {
|
const handleScroll: WheelEventHandler<HTMLVideoElement> = throttle((e) => {
|
||||||
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
||||||
void engineCommandManager.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_zoom',
|
type: 'default_camera_zoom',
|
||||||
@ -128,13 +78,15 @@ export const Stream = ({ className = '' }) => {
|
|||||||
})
|
})
|
||||||
}, Math.round(1000 / fps))
|
}, Math.round(1000 / fps))
|
||||||
|
|
||||||
const handleMouseUp: MouseEventHandler<HTMLVideoElement> = ({
|
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
ctrlKey,
|
ctrlKey,
|
||||||
}) => {
|
}) => {
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
setButtonDownInStream(undefined)
|
setButtonDownInStream(undefined)
|
||||||
|
if (state.matches('Sketch')) return
|
||||||
|
if (state.matches('Sketch no face')) return
|
||||||
const { x, y } = getNormalisedCoordinates({
|
const { x, y } = getNormalisedCoordinates({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
@ -155,208 +107,21 @@ export const Stream = ({ className = '' }) => {
|
|||||||
cmd_id: newCmdId,
|
cmd_id: newCmdId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didDragInStream && state.matches('Sketch no face')) {
|
if (!didDragInStream) {
|
||||||
command.cmd = {
|
|
||||||
type: 'select_with_point',
|
|
||||||
selection_type: 'add',
|
|
||||||
selected_at_window: { x, y },
|
|
||||||
}
|
|
||||||
void engineCommandManager.sendSceneCommand(command)
|
|
||||||
} else if (!didDragInStream && state.matches('Sketch.Line Tool')) {
|
|
||||||
command.cmd = {
|
|
||||||
type: 'mouse_click',
|
|
||||||
window: { x, y },
|
|
||||||
}
|
|
||||||
void engineCommandManager.sendSceneCommand(command).then(async (resp) => {
|
|
||||||
const entities_modified = resp?.data?.data?.entities_modified
|
|
||||||
if (!entities_modified) return
|
|
||||||
if (state.matches('Sketch.Line Tool.No Points')) {
|
|
||||||
send('Add point')
|
|
||||||
} else if (state.matches('Sketch.Line Tool.Point Added')) {
|
|
||||||
const curve = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'curve_get_control_points',
|
|
||||||
curve_id: entities_modified[0],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const coords: { x: number; y: number }[] =
|
|
||||||
curve.data.data.control_points
|
|
||||||
// We need the normal for the plane we are on.
|
|
||||||
const plane = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'get_sketch_mode_plane',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const z_axis = plane.data.data.z_axis
|
|
||||||
|
|
||||||
// Get the current axis.
|
|
||||||
let currentAxis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null =
|
|
||||||
null
|
|
||||||
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
|
|
||||||
if (z_axis.z === -1) {
|
|
||||||
currentAxis = '-xy'
|
|
||||||
} else {
|
|
||||||
currentAxis = 'xy'
|
|
||||||
}
|
|
||||||
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
|
|
||||||
if (z_axis.x === -1) {
|
|
||||||
currentAxis = '-yz'
|
|
||||||
} else {
|
|
||||||
currentAxis = 'yz'
|
|
||||||
}
|
|
||||||
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
|
|
||||||
if (z_axis.y === -1) {
|
|
||||||
currentAxis = '-xz'
|
|
||||||
} else {
|
|
||||||
currentAxis = 'xz'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: 'Add point',
|
|
||||||
data: {
|
|
||||||
coords,
|
|
||||||
axis: currentAxis,
|
|
||||||
segmentId: entities_modified[0],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if (state.matches('Sketch.Line Tool.Segment Added')) {
|
|
||||||
const curve = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'curve_get_control_points',
|
|
||||||
curve_id: entities_modified[0],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const coords: { x: number; y: number }[] =
|
|
||||||
curve.data.data.control_points
|
|
||||||
send({
|
|
||||||
type: 'Add point',
|
|
||||||
data: { coords, axis: null, segmentId: entities_modified[0] },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (
|
|
||||||
!didDragInStream &&
|
|
||||||
(state.matches('Sketch.SketchIdle') || state.matches('idle'))
|
|
||||||
) {
|
|
||||||
command.cmd = {
|
command.cmd = {
|
||||||
type: 'select_with_point',
|
type: 'select_with_point',
|
||||||
selected_at_window: { x, y },
|
selected_at_window: { x, y },
|
||||||
selection_type: 'add',
|
selection_type: 'add',
|
||||||
}
|
}
|
||||||
|
engineCommandManager.sendSceneCommand(command)
|
||||||
void engineCommandManager.sendSceneCommand(command)
|
} else if (didDragInStream) {
|
||||||
} else if (!didDragInStream && state.matches('Sketch.Move Tool')) {
|
|
||||||
command.cmd = {
|
|
||||||
type: 'select_with_point',
|
|
||||||
selected_at_window: { x, y },
|
|
||||||
selection_type: 'add',
|
|
||||||
}
|
|
||||||
void engineCommandManager.sendSceneCommand(command)
|
|
||||||
} else if (didDragInStream && state.matches('Sketch.Move Tool')) {
|
|
||||||
command.cmd = {
|
command.cmd = {
|
||||||
type: 'handle_mouse_drag_end',
|
type: 'handle_mouse_drag_end',
|
||||||
window: { x, y },
|
window: { x, y },
|
||||||
}
|
}
|
||||||
void engineCommandManager.sendSceneCommand(command).then(async () => {
|
|
||||||
if (!context.sketchPathToNode) return
|
|
||||||
getNodeFromPath<VariableDeclarator>(
|
|
||||||
kclManager.ast,
|
|
||||||
context.sketchPathToNode,
|
|
||||||
'VariableDeclarator'
|
|
||||||
)
|
|
||||||
// Get the current plane string for plane we are on.
|
|
||||||
let currentPlaneString = ''
|
|
||||||
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
|
|
||||||
currentPlaneString = 'XY'
|
|
||||||
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
|
|
||||||
currentPlaneString = 'YZ'
|
|
||||||
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
|
|
||||||
currentPlaneString = 'XZ'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not supporting editing/moving lines on a non-default plane.
|
|
||||||
// Eventually we can support this but for now we will just throw an
|
|
||||||
// error.
|
|
||||||
if (currentPlaneString === '') return
|
|
||||||
|
|
||||||
const pathInfo = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'path_get_info',
|
|
||||||
path_id: context.sketchEnginePathId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const segmentsWithMappings = (
|
|
||||||
pathInfo?.data?.data?.segments as { command_id: string }[]
|
|
||||||
)
|
|
||||||
.filter(({ command_id }) => {
|
|
||||||
return command_id && engineCommandManager.artifactMap[command_id]
|
|
||||||
})
|
|
||||||
.map(({ command_id }) => command_id)
|
|
||||||
const segment2dInfo = await Promise.all(
|
|
||||||
segmentsWithMappings.map(async (segmentId) => {
|
|
||||||
const response = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'curve_get_control_points',
|
|
||||||
curve_id: segmentId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const controlPoints: [
|
|
||||||
{ x: number; y: number },
|
|
||||||
{ x: number; y: number }
|
|
||||||
] = response.data.data.control_points
|
|
||||||
return {
|
|
||||||
controlPoints,
|
|
||||||
segmentId,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
let modifiedAst = { ...kclManager.ast }
|
|
||||||
let code = kclManager.code
|
|
||||||
for (const controlPoint of segment2dInfo) {
|
|
||||||
const range =
|
|
||||||
engineCommandManager.artifactMap[controlPoint.segmentId].range
|
|
||||||
if (!range) continue
|
|
||||||
const from = controlPoint.controlPoints[0]
|
|
||||||
const to = controlPoint.controlPoints[1]
|
|
||||||
const modded = changeSketchArguments(
|
|
||||||
modifiedAst,
|
|
||||||
kclManager.programMemory,
|
|
||||||
range,
|
|
||||||
[to.x, to.y],
|
|
||||||
[from.x, from.y]
|
|
||||||
)
|
|
||||||
modifiedAst = modded.modifiedAst
|
|
||||||
|
|
||||||
// update artifact map ranges now that we have updated the ast.
|
|
||||||
code = recast(modded.modifiedAst)
|
|
||||||
const astWithCurrentRanges = kclManager.safeParse(code)
|
|
||||||
if (!astWithCurrentRanges) return
|
|
||||||
const updateNode = getNodeFromPath<CallExpression>(
|
|
||||||
astWithCurrentRanges,
|
|
||||||
modded.pathToNode
|
|
||||||
).node
|
|
||||||
engineCommandManager.artifactMap[controlPoint.segmentId].range = [
|
|
||||||
updateNode.start,
|
|
||||||
updateNode.end,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
void kclManager.executeAstMock(modifiedAst, true)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
void engineCommandManager.sendSceneCommand(command)
|
void engineCommandManager.sendSceneCommand(command)
|
||||||
|
} else {
|
||||||
|
engineCommandManager.sendSceneCommand(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
setDidDragInStream(false)
|
setDidDragInStream(false)
|
||||||
@ -364,6 +129,8 @@ export const Stream = ({ className = '' }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseMove: MouseEventHandler<HTMLVideoElement> = (e) => {
|
const handleMouseMove: MouseEventHandler<HTMLVideoElement> = (e) => {
|
||||||
|
if (state.matches('Sketch')) return
|
||||||
|
if (state.matches('Sketch no face')) return
|
||||||
if (!clickCoords) return
|
if (!clickCoords) return
|
||||||
|
|
||||||
const delta =
|
const delta =
|
||||||
@ -376,16 +143,19 @@ export const Stream = ({ className = '' }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="stream" className={className}>
|
<div
|
||||||
|
id="stream"
|
||||||
|
className={className}
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
|
onContextMenuCapture={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
muted
|
muted
|
||||||
autoPlay
|
autoPlay
|
||||||
controls={false}
|
controls={false}
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
|
||||||
onContextMenuCapture={(e) => e.preventDefault()}
|
|
||||||
onWheel={handleScroll}
|
onWheel={handleScroll}
|
||||||
onPlay={() => setIsLoading(false)}
|
onPlay={() => setIsLoading(false)}
|
||||||
onMouseMoveCapture={handleMouseMove}
|
onMouseMoveCapture={handleMouseMove}
|
||||||
@ -393,6 +163,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||||
/>
|
/>
|
||||||
|
<ClientSideScene cameraControls={settings.context.cameraControls} />
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
<Loading>
|
<Loading>
|
||||||
|
@ -11,7 +11,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
|||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||||
import { Themes } from 'lib/theme'
|
import { Themes } from 'lib/theme'
|
||||||
import { useMemo } from 'react'
|
import { useMemo, useRef } from 'react'
|
||||||
import { linter, lintGutter } from '@codemirror/lint'
|
import { linter, lintGutter } from '@codemirror/lint'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import { processCodeMirrorRanges } from 'lib/selections'
|
import { processCodeMirrorRanges } from 'lib/selections'
|
||||||
@ -24,7 +24,9 @@ import { CSSRuleObject } from 'tailwindcss/types/config'
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import interact from '@replit/codemirror-interact'
|
import interact from '@replit/codemirror-interact'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { kclManager, useKclContext } from 'lang/KclSinglton'
|
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||||
|
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
|
import { setupSingleton } from 'clientSideScene/setup'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -56,10 +58,12 @@ export const TextEditor = ({
|
|||||||
isShiftDown: s.isShiftDown,
|
isShiftDown: s.isShiftDown,
|
||||||
}))
|
}))
|
||||||
const { code, errors } = useKclContext()
|
const { code, errors } = useKclContext()
|
||||||
|
const lastEvent = useRef({ event: '', time: Date.now() })
|
||||||
|
|
||||||
const {
|
const {
|
||||||
context: { selectionRanges, selectionRangeTypeMap },
|
context: { selectionRanges, selectionRangeTypeMap },
|
||||||
send,
|
send,
|
||||||
|
state,
|
||||||
} = useModelingContext()
|
} = useModelingContext()
|
||||||
|
|
||||||
const { settings: { context: { textWrapping } = {} } = {} } =
|
const { settings: { context: { textWrapping } = {} } = {} } =
|
||||||
@ -76,12 +80,10 @@ export const TextEditor = ({
|
|||||||
const fromServer: FromServer = FromServer.create()
|
const fromServer: FromServer = FromServer.create()
|
||||||
const client = new Client(fromServer, intoServer)
|
const client = new Client(fromServer, intoServer)
|
||||||
if (!TEST) {
|
if (!TEST) {
|
||||||
Server.initialize(intoServer, fromServer)
|
Server.initialize(intoServer, fromServer).then((lspServer) => {
|
||||||
.then((lspServer) => {
|
lspServer.start()
|
||||||
void lspServer.start()
|
setIsLSPServerReady(true)
|
||||||
setIsLSPServerReady(true)
|
})
|
||||||
})
|
|
||||||
.catch((e) => console.log(e))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const lspClient = new LanguageServerClient({ client })
|
const lspClient = new LanguageServerClient({ client })
|
||||||
@ -117,6 +119,12 @@ export const TextEditor = ({
|
|||||||
if (!editorView) {
|
if (!editorView) {
|
||||||
setEditorView(viewUpdate.view)
|
setEditorView(viewUpdate.view)
|
||||||
}
|
}
|
||||||
|
if (setupSingleton.selected) return // mid drag
|
||||||
|
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||||
|
'Equip Line tool',
|
||||||
|
'Equip tangential arc to',
|
||||||
|
]
|
||||||
|
if (ignoreEvents.includes(state.event.type)) return
|
||||||
const eventInfo = processCodeMirrorRanges({
|
const eventInfo = processCodeMirrorRanges({
|
||||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -124,7 +132,20 @@ export const TextEditor = ({
|
|||||||
isShiftDown,
|
isShiftDown,
|
||||||
})
|
})
|
||||||
if (!eventInfo) return
|
if (!eventInfo) return
|
||||||
|
const deterministicEventInfo = {
|
||||||
|
...eventInfo,
|
||||||
|
engineEvents: eventInfo.engineEvents.map((e) => ({
|
||||||
|
...e,
|
||||||
|
cmd_id: 'static',
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
const stringEvent = JSON.stringify(deterministicEventInfo)
|
||||||
|
if (
|
||||||
|
stringEvent === lastEvent.current.event &&
|
||||||
|
Date.now() - lastEvent.current.time < 500
|
||||||
|
)
|
||||||
|
return // don't repeat events
|
||||||
|
lastEvent.current = { event: stringEvent, time: Date.now() }
|
||||||
send(eventInfo.modelingEvent)
|
send(eventInfo.modelingEvent)
|
||||||
eventInfo.engineEvents.forEach((event) =>
|
eventInfo.engineEvents.forEach((event) =>
|
||||||
engineCommandManager.sendSceneCommand(event)
|
engineCommandManager.sendSceneCommand(event)
|
||||||
@ -153,7 +174,7 @@ export const TextEditor = ({
|
|||||||
key: editorShortcutMeta.convertToVariable.codeMirror,
|
key: editorShortcutMeta.convertToVariable.codeMirror,
|
||||||
run: () => {
|
run: () => {
|
||||||
if (convertEnabled) {
|
if (convertEnabled) {
|
||||||
void convertCallback()
|
convertCallback()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
PathToNodeMap,
|
PathToNodeMap,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export function equalAngleInfo({
|
export function equalAngleInfo({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
PathToNodeMap,
|
PathToNodeMap,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export function setEqualLengthInfo({
|
export function setEqualLengthInfo({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export function horzVertInfo(
|
export function horzVertInfo(
|
||||||
selectionRanges: Selections,
|
selectionRanges: Selections,
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||||
import { createVariableDeclaration } from '../../lang/modifyAst'
|
import { createVariableDeclaration } from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
const getModalInfo = createInfoModal(GetInfoModal)
|
const getModalInfo = createInfoModal(GetInfoModal)
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
getRemoveConstraintsTransforms,
|
getRemoveConstraintsTransforms,
|
||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export function removeConstrainingValuesInfo({
|
export function removeConstrainingValuesInfo({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
createVariableDeclaration,
|
createVariableDeclaration,
|
||||||
} from '../../lang/modifyAst'
|
} from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||||
import { createVariableDeclaration } from '../../lang/modifyAst'
|
import { createVariableDeclaration } from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
const getModalInfo = createInfoModal(GetInfoModal)
|
const getModalInfo = createInfoModal(GetInfoModal)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||||
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
|
|
||||||
const getModalInfo = createInfoModal(GetInfoModal)
|
const getModalInfo = createInfoModal(GetInfoModal)
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
} from '../../lang/modifyAst'
|
} from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { normaliseAngle } from '../../lib/utils'
|
import { normaliseAngle } from '../../lib/utils'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { faBars, faBug, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
|||||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { paths } from '../Router'
|
import { paths } from 'lib/paths'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Dialog } from '@headlessui/react'
|
import { Dialog } from '@headlessui/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { useKclContext } from 'lang/KclSinglton'
|
import { useKclContext } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export function WasmErrBanner() {
|
export function WasmErrBanner() {
|
||||||
const [isBannerDismissed, setBannerDismissed] = useState(false)
|
const [isBannerDismissed, setBannerDismissed] = useState(false)
|
||||||
|
@ -67,7 +67,7 @@ export class LanguageServerClient {
|
|||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
// Start the client in the background.
|
// Start the client in the background.
|
||||||
await this.client.start()
|
this.client.start()
|
||||||
|
|
||||||
this.ready = true
|
this.ready = true
|
||||||
}
|
}
|
||||||
@ -81,12 +81,12 @@ export class LanguageServerClient {
|
|||||||
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
||||||
this.notify('textDocument/didOpen', params)
|
this.notify('textDocument/didOpen', params)
|
||||||
|
|
||||||
void this.updateSemanticTokens(params.textDocument.uri)
|
this.updateSemanticTokens(params.textDocument.uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
textDocumentDidChange(params: LSP.DidChangeTextDocumentParams) {
|
textDocumentDidChange(params: LSP.DidChangeTextDocumentParams) {
|
||||||
this.notify('textDocument/didChange', params)
|
this.notify('textDocument/didChange', params)
|
||||||
void this.updateSemanticTokens(params.textDocument.uri)
|
this.updateSemanticTokens(params.textDocument.uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSemanticTokens(uri: string) {
|
async updateSemanticTokens(uri: string) {
|
||||||
|
@ -62,7 +62,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
|
|
||||||
this.client.attachPlugin(this)
|
this.client.attachPlugin(this)
|
||||||
|
|
||||||
void this.initialize({
|
this.initialize({
|
||||||
documentText: this.view.state.doc.toString(),
|
documentText: this.view.state.doc.toString(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
update({ docChanged }: ViewUpdate) {
|
update({ docChanged }: ViewUpdate) {
|
||||||
if (!docChanged) return
|
if (!docChanged) return
|
||||||
|
|
||||||
void this.sendChange({
|
this.sendChange({
|
||||||
documentText: this.view.state.doc.toString(),
|
documentText: this.view.state.doc.toString(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -127,7 +127,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestDiagnostics(view: EditorView) {
|
requestDiagnostics(view: EditorView) {
|
||||||
void this.sendChange({ documentText: view.state.doc.toString() })
|
this.sendChange({ documentText: view.state.doc.toString() })
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestHoverTooltip(
|
async requestHoverTooltip(
|
||||||
@ -140,7 +140,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
await this.sendChange({ documentText: view.state.doc.toString() })
|
this.sendChange({ documentText: view.state.doc.toString() })
|
||||||
const result = await this.client.textDocumentHover({
|
const result = await this.client.textDocumentHover({
|
||||||
textDocument: { uri: this.documentUri },
|
textDocument: { uri: this.documentUri },
|
||||||
position: { line, character },
|
position: { line, character },
|
||||||
@ -178,7 +178,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
await this.sendChange({
|
this.sendChange({
|
||||||
documentText: context.state.doc.toString(),
|
documentText: context.state.doc.toString(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { BROWSER_FILE_NAME, IndexLoaderData, paths } from 'Router'
|
import { BROWSER_FILE_NAME } from 'Router'
|
||||||
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
import { useRouteLoaderData } from 'react-router-dom'
|
import { useRouteLoaderData } from 'react-router-dom'
|
||||||
|
|
||||||
export function useAbsoluteFilePath() {
|
export function useAbsoluteFilePath() {
|
||||||
|
@ -46,6 +46,6 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
setHighlightRange,
|
setHighlightRange,
|
||||||
highlightRange,
|
highlightRange,
|
||||||
context.sketchEnginePathId,
|
context?.sketchEnginePathId,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { parse } from '../lang/wasm'
|
|||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { deferExecution } from 'lib/utils'
|
import { deferExecution } from 'lib/utils'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
|
|
||||||
export function useSetupEngineManager(
|
export function useSetupEngineManager(
|
||||||
streamRef: React.RefObject<HTMLDivElement>,
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
|
@ -2,7 +2,7 @@ import {
|
|||||||
SetVarNameModal,
|
SetVarNameModal,
|
||||||
createSetVarNameModal,
|
createSetVarNameModal,
|
||||||
} from 'components/SetVarNameModal'
|
} from 'components/SetVarNameModal'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
||||||
import { isNodeSafeToReplace } from 'lang/queryAst'
|
import { isNodeSafeToReplace } from 'lang/queryAst'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@ -39,7 +39,7 @@ export function useConvertToVariable() {
|
|||||||
variableName
|
variableName
|
||||||
)
|
)
|
||||||
|
|
||||||
void kclManager.updateAst(_modifiedAst, true)
|
kclManager.updateAst(_modifiedAst, true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('error', e)
|
console.log('error', e)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import { Router } from './Router'
|
|||||||
import { HotkeysProvider } from 'react-hotkeys-hook'
|
import { HotkeysProvider } from 'react-hotkeys-hook'
|
||||||
|
|
||||||
// uncomment for xstate inspector
|
// uncomment for xstate inspector
|
||||||
|
// import { DEV } from 'env'
|
||||||
|
// import { inspect } from '@xstate/inspect'
|
||||||
// if (DEV)
|
// if (DEV)
|
||||||
// inspect({
|
// inspect({
|
||||||
// iframe: false,
|
// iframe: false,
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
} from './std/engineConnection'
|
} from './std/engineConnection'
|
||||||
import { deferExecution } from 'lib/utils'
|
import { deferExecution } from 'lib/utils'
|
||||||
import {
|
import {
|
||||||
|
CallExpression,
|
||||||
initPromise,
|
initPromise,
|
||||||
parse,
|
parse,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
@ -17,7 +18,7 @@ import {
|
|||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import { createContext, useContext, useEffect, useState } from 'react'
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath } from './queryAst'
|
||||||
import { IndexLoaderData } from 'Router'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import { Params, useLoaderData } from 'react-router-dom'
|
import { Params, useLoaderData } from 'react-router-dom'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { writeTextFile } from '@tauri-apps/api/fs'
|
import { writeTextFile } from '@tauri-apps/api/fs'
|
||||||
@ -59,7 +60,7 @@ class KclManager {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
void this.executeAst(ast)
|
this.executeAst(ast)
|
||||||
}, 600)
|
}, 600)
|
||||||
|
|
||||||
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
||||||
@ -98,7 +99,7 @@ class KclManager {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(PERSIST_CODE_TOKEN, code)
|
localStorage?.setItem(PERSIST_CODE_TOKEN, code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,10 +111,6 @@ class KclManager {
|
|||||||
this._programMemoryCallBack(programMemory)
|
this._programMemoryCallBack(programMemory)
|
||||||
}
|
}
|
||||||
|
|
||||||
get defaultPlanes() {
|
|
||||||
return this?.engineCommandManager?.defaultPlanes
|
|
||||||
}
|
|
||||||
|
|
||||||
get logs() {
|
get logs() {
|
||||||
return this._logs
|
return this._logs
|
||||||
}
|
}
|
||||||
@ -168,11 +165,13 @@ class KclManager {
|
|||||||
zustandStore.state.code = ''
|
zustandStore.state.code = ''
|
||||||
localStorage.setItem('store', JSON.stringify(zustandStore))
|
localStorage.setItem('store', JSON.stringify(zustandStore))
|
||||||
} else if (storedCode === null) {
|
} else if (storedCode === null) {
|
||||||
console.log('stored brack thing')
|
|
||||||
this.code = bracket
|
this.code = bracket
|
||||||
} else {
|
} else {
|
||||||
this.code = storedCode
|
this.code = storedCode
|
||||||
}
|
}
|
||||||
|
this.ensureWasmInit().then(() => {
|
||||||
|
this.ast = this.safeParse(this.code) || this.ast
|
||||||
|
})
|
||||||
}
|
}
|
||||||
registerCallBacks({
|
registerCallBacks({
|
||||||
setCode,
|
setCode,
|
||||||
@ -235,7 +234,6 @@ class KclManager {
|
|||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
defaultPlanes: this.defaultPlanes,
|
|
||||||
})
|
})
|
||||||
this.isExecuting = false
|
this.isExecuting = false
|
||||||
this.logs = logs
|
this.logs = logs
|
||||||
@ -251,13 +249,20 @@ class KclManager {
|
|||||||
data: null,
|
data: null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async executeAstMock(ast: Program = this._ast, updateCode = false) {
|
async executeAstMock(
|
||||||
|
ast: Program = this._ast,
|
||||||
|
{
|
||||||
|
updates,
|
||||||
|
}: {
|
||||||
|
updates: 'none' | 'code' | 'codeAndArtifactRanges'
|
||||||
|
} = { updates: 'none' }
|
||||||
|
) {
|
||||||
await this.ensureWasmInit()
|
await this.ensureWasmInit()
|
||||||
const newCode = recast(ast)
|
const newCode = recast(ast)
|
||||||
const newAst = this.safeParse(newCode)
|
const newAst = this.safeParse(newCode)
|
||||||
if (!newAst) return
|
if (!newAst) return
|
||||||
await this?.engineCommandManager?.waitForReady
|
await this?.engineCommandManager?.waitForReady
|
||||||
if (updateCode) {
|
if (updates !== 'none') {
|
||||||
this.setCode(recast(ast))
|
this.setCode(recast(ast))
|
||||||
}
|
}
|
||||||
this._ast = { ...newAst }
|
this._ast = { ...newAst }
|
||||||
@ -265,22 +270,38 @@ class KclManager {
|
|||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
ast: newAst,
|
ast: newAst,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
defaultPlanes: this.defaultPlanes,
|
|
||||||
useFakeExecutor: true,
|
useFakeExecutor: true,
|
||||||
})
|
})
|
||||||
this._logs = logs
|
this._logs = logs
|
||||||
this._kclErrors = errors
|
this._kclErrors = errors
|
||||||
this._programMemory = programMemory
|
this._programMemory = programMemory
|
||||||
|
if (updates !== 'codeAndArtifactRanges') return
|
||||||
|
Object.entries(engineCommandManager.artifactMap).forEach(
|
||||||
|
([commandId, artifact]) => {
|
||||||
|
if (!artifact.pathToNode) return
|
||||||
|
const node = getNodeFromPath<CallExpression>(
|
||||||
|
kclManager.ast,
|
||||||
|
artifact.pathToNode,
|
||||||
|
'CallExpression'
|
||||||
|
).node
|
||||||
|
if (node.type !== 'CallExpression') return
|
||||||
|
const [oldStart, oldEnd] = artifact.range
|
||||||
|
if (oldStart === 0 && oldEnd === 0) return
|
||||||
|
if (oldStart === node.start && oldEnd === node.end) return
|
||||||
|
engineCommandManager.artifactMap[commandId].range = [
|
||||||
|
node.start,
|
||||||
|
node.end,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
async executeCode(code?: string) {
|
async executeCode(code?: string) {
|
||||||
await this.ensureWasmInit()
|
await this.ensureWasmInit()
|
||||||
await this?.engineCommandManager?.waitForReady
|
await this?.engineCommandManager?.waitForReady
|
||||||
if (!this?.engineCommandManager?.planesInitialized()) return
|
|
||||||
const result = await executeCode({
|
const result = await executeCode({
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
code: code || this._code,
|
code: code || this._code,
|
||||||
lastAst: this._ast,
|
lastAst: this._ast,
|
||||||
defaultPlanes: this.defaultPlanes,
|
|
||||||
force: false,
|
force: false,
|
||||||
})
|
})
|
||||||
if (!result.isChange) return
|
if (!result.isChange) return
|
||||||
@ -366,26 +387,10 @@ class KclManager {
|
|||||||
// When we don't re-execute, we still want to update the program
|
// When we don't re-execute, we still want to update the program
|
||||||
// memory with the new ast. So we will hit the mock executor
|
// memory with the new ast. So we will hit the mock executor
|
||||||
// instead.
|
// instead.
|
||||||
await this.executeAstMock(astWithUpdatedSource, true)
|
await this.executeAstMock(astWithUpdatedSource, { updates: 'code' })
|
||||||
}
|
}
|
||||||
return returnVal
|
return returnVal
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlaneId(axis: 'xy' | 'xz' | 'yz'): string {
|
|
||||||
return this.defaultPlanes[axis]
|
|
||||||
}
|
|
||||||
|
|
||||||
showPlanes() {
|
|
||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, false)
|
|
||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, false)
|
|
||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
hidePlanes() {
|
|
||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, true)
|
|
||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
|
|
||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kclManager = new KclManager(engineCommandManager)
|
export const kclManager = new KclManager(engineCommandManager)
|
@ -33,7 +33,7 @@ show(mySketch001)`
|
|||||||
},
|
},
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
name: '',
|
name: '',
|
||||||
to: [-1.59, -1.54],
|
to: [-1.59, -1.54],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
@ -43,7 +43,7 @@ show(mySketch001)`
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
to: [0.46, -5.82],
|
to: [0.46, -5.82],
|
||||||
from: [-1.59, -1.54],
|
from: [-1.59, -1.54],
|
||||||
name: '',
|
name: '',
|
||||||
@ -55,6 +55,9 @@ show(mySketch001)`
|
|||||||
],
|
],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
|
xAxis: [1, 0, 0],
|
||||||
|
yAxis: [0, 1, 0],
|
||||||
|
zAxis: [0, 0, 1],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
planeId: expect.any(String),
|
planeId: expect.any(String),
|
||||||
__meta: [{ sourceRange: [46, 71] }],
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
|
@ -54,7 +54,7 @@ show(mySketch)
|
|||||||
const minusGeo = root.mySketch.value
|
const minusGeo = root.mySketch.value
|
||||||
expect(minusGeo).toEqual([
|
expect(minusGeo).toEqual([
|
||||||
{
|
{
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
to: [0, 2],
|
to: [0, 2],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
@ -64,7 +64,7 @@ show(mySketch)
|
|||||||
name: 'myPath',
|
name: 'myPath',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
to: [2, 3],
|
to: [2, 3],
|
||||||
from: [0, 2],
|
from: [0, 2],
|
||||||
name: '',
|
name: '',
|
||||||
@ -74,7 +74,7 @@ show(mySketch)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
to: [5, -1],
|
to: [5, -1],
|
||||||
from: [2, 3],
|
from: [2, 3],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
@ -154,7 +154,7 @@ show(mySketch)
|
|||||||
},
|
},
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
to: [1, 1],
|
to: [1, 1],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
name: '',
|
name: '',
|
||||||
@ -164,7 +164,7 @@ show(mySketch)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
to: [0, 1],
|
to: [0, 1],
|
||||||
from: [1, 1],
|
from: [1, 1],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
@ -174,7 +174,7 @@ show(mySketch)
|
|||||||
name: 'myPath',
|
name: 'myPath',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
to: [1, 1],
|
to: [1, 1],
|
||||||
from: [0, 1],
|
from: [0, 1],
|
||||||
name: '',
|
name: '',
|
||||||
@ -186,6 +186,9 @@ show(mySketch)
|
|||||||
],
|
],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
|
xAxis: [1, 0, 0],
|
||||||
|
yAxis: [0, 1, 0],
|
||||||
|
zAxis: [0, 0, 1],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
planeId: expect.any(String),
|
planeId: expect.any(String),
|
||||||
__meta: [{ sourceRange: [39, 63] }],
|
__meta: [{ sourceRange: [39, 63] }],
|
||||||
|
@ -30,41 +30,28 @@ import {
|
|||||||
createFirstArg,
|
createFirstArg,
|
||||||
} from './std/sketch'
|
} from './std/sketch'
|
||||||
import { isLiteralArrayOrStatic } from './std/sketchcombos'
|
import { isLiteralArrayOrStatic } from './std/sketchcombos'
|
||||||
|
import { DefaultPlaneStr } from 'clientSideScene/clientSideScene'
|
||||||
|
import { roundOff } from 'lib/utils'
|
||||||
|
|
||||||
export function addStartSketch(
|
export function startSketchOnDefault(
|
||||||
node: Program,
|
node: Program,
|
||||||
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz',
|
axis: DefaultPlaneStr,
|
||||||
start: [number, number],
|
name = ''
|
||||||
end: [number, number]
|
|
||||||
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const _name = findUniqueName(node, 'part')
|
const _name = name || findUniqueName(node, 'part')
|
||||||
|
|
||||||
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
|
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
|
||||||
createLiteral(axis.toUpperCase()),
|
createLiteral(axis),
|
||||||
])
|
|
||||||
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
|
||||||
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
|
|
||||||
createPipeSubstitution(),
|
|
||||||
])
|
|
||||||
const initialLineTo = createCallExpression('line', [
|
|
||||||
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
|
|
||||||
createPipeSubstitution(),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
|
const variableDeclaration = createVariableDeclaration(_name, startSketchOn)
|
||||||
|
|
||||||
const variableDeclaration = createVariableDeclaration(
|
|
||||||
_name,
|
|
||||||
createPipeExpression(pipeBody)
|
|
||||||
)
|
|
||||||
|
|
||||||
const newIndex = node.body.length
|
|
||||||
_node.body = [...node.body, variableDeclaration]
|
_node.body = [...node.body, variableDeclaration]
|
||||||
|
const sketchIndex = _node.body.length - 1
|
||||||
|
|
||||||
let pathToNode: PathToNode = [
|
let pathToNode: PathToNode = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[newIndex.toString(10), 'index'],
|
[sketchIndex, 'index'],
|
||||||
['declarations', 'VariableDeclaration'],
|
['declarations', 'VariableDeclaration'],
|
||||||
['0', 'index'],
|
['0', 'index'],
|
||||||
['init', 'VariableDeclarator'],
|
['init', 'VariableDeclarator'],
|
||||||
@ -77,6 +64,43 @@ export function addStartSketch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addStartProfileAt(
|
||||||
|
node: Program,
|
||||||
|
pathToNode: PathToNode,
|
||||||
|
at: [number, number]
|
||||||
|
): { modifiedAst: Program; pathToNode: PathToNode } {
|
||||||
|
console.log('addStartProfileAt called')
|
||||||
|
const variableDeclaration = getNodeFromPath<VariableDeclaration>(
|
||||||
|
node,
|
||||||
|
pathToNode,
|
||||||
|
'VariableDeclaration'
|
||||||
|
).node
|
||||||
|
if (variableDeclaration.type !== 'VariableDeclaration') {
|
||||||
|
throw new Error('variableDeclaration.init.type !== PipeExpression')
|
||||||
|
}
|
||||||
|
const _node = { ...node }
|
||||||
|
const init = variableDeclaration.declarations[0].init
|
||||||
|
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||||
|
createArrayExpression([
|
||||||
|
createLiteral(roundOff(at[0])),
|
||||||
|
createLiteral(roundOff(at[1])),
|
||||||
|
]),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
if (init.type === 'PipeExpression') {
|
||||||
|
init.body.splice(1, 0, startProfileAt)
|
||||||
|
} else {
|
||||||
|
variableDeclaration.declarations[0].init = createPipeExpression([
|
||||||
|
init,
|
||||||
|
startProfileAt,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function addSketchTo(
|
export function addSketchTo(
|
||||||
node: Program,
|
node: Program,
|
||||||
axis: 'xy' | 'xz' | 'yz',
|
axis: 'xy' | 'xz' | 'yz',
|
||||||
|
@ -65,13 +65,13 @@ export function getNodeFromPath<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
// console.error(
|
||||||
`Could not find path ${pathItem} in node ${JSON.stringify(
|
// `Could not find path ${pathItem} in node ${JSON.stringify(
|
||||||
currentNode,
|
// currentNode,
|
||||||
null,
|
// null,
|
||||||
2
|
// 2
|
||||||
)}, successful path was ${successfulPaths}`
|
// )}, successful path was ${successfulPaths}`
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -266,6 +266,7 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
||||||
console.error('not implemented: ' + node.type)
|
console.error('not implemented: ' + node.type)
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@ -489,7 +490,7 @@ export function isLinesParallelAndConstrained(
|
|||||||
const constraintLevel = getConstraintLevelFromSourceRange(
|
const constraintLevel = getConstraintLevelFromSourceRange(
|
||||||
secondaryLine.range,
|
secondaryLine.range,
|
||||||
ast
|
ast
|
||||||
)
|
).level
|
||||||
const isConstrained =
|
const isConstrained =
|
||||||
constraintType === 'angle' || constraintLevel === 'full'
|
constraintType === 'angle' || constraintLevel === 'full'
|
||||||
|
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import { SourceRange } from 'lang/wasm'
|
import { PathToNode, Program, SourceRange } from 'lang/wasm'
|
||||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
import { setupSingleton } from 'clientSideScene/setup'
|
||||||
|
|
||||||
let lastMessage = ''
|
let lastMessage = ''
|
||||||
|
|
||||||
interface CommandInfo {
|
interface CommandInfo {
|
||||||
commandType: CommandTypes
|
commandType: CommandTypes
|
||||||
range: SourceRange
|
range: SourceRange
|
||||||
|
pathToNode: PathToNode
|
||||||
parentId?: string
|
parentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,9 +603,14 @@ class EngineConnection {
|
|||||||
})
|
})
|
||||||
.join('\n')
|
.join('\n')
|
||||||
if (message.request_id) {
|
if (message.request_id) {
|
||||||
|
const artifactThatFailed =
|
||||||
|
engineCommandManager.artifactMap[message.request_id] ||
|
||||||
|
engineCommandManager.lastArtifactMap[message.request_id]
|
||||||
console.error(
|
console.error(
|
||||||
`Error in response to request ${message.request_id}:\n${errorsString}`
|
`Error in response to request ${message.request_id}:\n${errorsString}
|
||||||
|
failed cmd type was ${artifactThatFailed?.commandType}`
|
||||||
)
|
)
|
||||||
|
console.log(artifactThatFailed)
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error from server:\n${errorsString}`)
|
console.error(`Error from server:\n${errorsString}`)
|
||||||
}
|
}
|
||||||
@ -618,8 +625,6 @@ class EngineConnection {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('received', resp)
|
|
||||||
|
|
||||||
switch (resp.type) {
|
switch (resp.type) {
|
||||||
case 'ice_server_info':
|
case 'ice_server_info':
|
||||||
let ice_servers = resp.data?.ice_servers
|
let ice_servers = resp.data?.ice_servers
|
||||||
@ -863,10 +868,10 @@ export type CommandLog =
|
|||||||
export class EngineCommandManager {
|
export class EngineCommandManager {
|
||||||
artifactMap: ArtifactMap = {}
|
artifactMap: ArtifactMap = {}
|
||||||
lastArtifactMap: ArtifactMap = {}
|
lastArtifactMap: ArtifactMap = {}
|
||||||
|
private getAst: () => Program = () => ({ start: 0, end: 0, body: [] } as any)
|
||||||
outSequence = 1
|
outSequence = 1
|
||||||
inSequence = 1
|
inSequence = 1
|
||||||
engineConnection?: EngineConnection
|
engineConnection?: EngineConnection
|
||||||
defaultPlanes: DefaultPlanes = { xy: '', yz: '', xz: '' }
|
|
||||||
_commandLogs: CommandLog[] = []
|
_commandLogs: CommandLog[] = []
|
||||||
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
||||||
// Folks should realize that wait for ready does not get called _everytime_
|
// Folks should realize that wait for ready does not get called _everytime_
|
||||||
@ -890,6 +895,11 @@ export class EngineCommandManager {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.engineConnection = undefined
|
this.engineConnection = undefined
|
||||||
|
;(async () => {
|
||||||
|
// circular dependency needs one to be lazy loaded
|
||||||
|
const { kclManager } = await import('lang/KclSingleton')
|
||||||
|
this.getAst = () => kclManager.ast
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
|
|
||||||
start({
|
start({
|
||||||
@ -946,16 +956,9 @@ export class EngineCommandManager {
|
|||||||
gizmo_mode: true,
|
gizmo_mode: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
setupSingleton.onStreamStart()
|
||||||
|
|
||||||
// Initialize the planes.
|
executeCode(undefined, true)
|
||||||
void this.initPlanes().then(() => {
|
|
||||||
// We execute the code here to make sure if the stream was to
|
|
||||||
// restart in a session, we want to make sure to execute the code.
|
|
||||||
// We force it to re-execute the code because we want to make sure
|
|
||||||
// the code is executed everytime the stream is restarted.
|
|
||||||
// We pass undefined for the code so it reads from the current state.
|
|
||||||
executeCode(undefined, true)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
setIsStreamReady(false)
|
setIsStreamReady(false)
|
||||||
@ -1075,6 +1078,7 @@ export class EngineCommandManager {
|
|||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
type: 'result',
|
type: 'result',
|
||||||
range: command.range,
|
range: command.range,
|
||||||
|
pathToNode: command.pathToNode,
|
||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
parentId: command.parentId ? command.parentId : undefined,
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
@ -1092,6 +1096,7 @@ export class EngineCommandManager {
|
|||||||
type: 'result',
|
type: 'result',
|
||||||
commandType: command?.commandType,
|
commandType: command?.commandType,
|
||||||
range: command?.range,
|
range: command?.range,
|
||||||
|
pathToNode: command?.pathToNode,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
raw: message,
|
raw: message,
|
||||||
}
|
}
|
||||||
@ -1109,6 +1114,7 @@ export class EngineCommandManager {
|
|||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
type: 'failed',
|
type: 'failed',
|
||||||
range: command.range,
|
range: command.range,
|
||||||
|
pathToNode: command.pathToNode,
|
||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
parentId: command.parentId ? command.parentId : undefined,
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
errors,
|
errors,
|
||||||
@ -1123,6 +1129,7 @@ export class EngineCommandManager {
|
|||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
type: 'failed',
|
type: 'failed',
|
||||||
range: command.range,
|
range: command.range,
|
||||||
|
pathToNode: command.pathToNode,
|
||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
parentId: command.parentId ? command.parentId : undefined,
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
errors,
|
errors,
|
||||||
@ -1218,7 +1225,10 @@ export class EngineCommandManager {
|
|||||||
registerCommandLogCallback(callback: (command: CommandLog[]) => void) {
|
registerCommandLogCallback(callback: (command: CommandLog[]) => void) {
|
||||||
this._commandLogCallBack = callback
|
this._commandLogCallBack = callback
|
||||||
}
|
}
|
||||||
sendSceneCommand(command: EngineCommand): Promise<any> {
|
sendSceneCommand(
|
||||||
|
command: EngineCommand,
|
||||||
|
forceWebsocket = false
|
||||||
|
): Promise<any> {
|
||||||
if (this.engineConnection === undefined) {
|
if (this.engineConnection === undefined) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
@ -1232,7 +1242,9 @@ export class EngineCommandManager {
|
|||||||
command.type === 'modeling_cmd_req' &&
|
command.type === 'modeling_cmd_req' &&
|
||||||
(command.cmd.type === 'highlight_set_entity' ||
|
(command.cmd.type === 'highlight_set_entity' ||
|
||||||
command.cmd.type === 'mouse_move' ||
|
command.cmd.type === 'mouse_move' ||
|
||||||
command.cmd.type === 'camera_drag_move')
|
command.cmd.type === 'camera_drag_move' ||
|
||||||
|
command.cmd.type === 'default_camera_look_at' ||
|
||||||
|
command.cmd.type === ('default_camera_perspective_settings' as any))
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// highlight_set_entity, mouse_move and camera_drag_move are sent over the unreliable channel and are too noisy
|
// highlight_set_entity, mouse_move and camera_drag_move are sent over the unreliable channel and are too noisy
|
||||||
@ -1249,14 +1261,23 @@ export class EngineCommandManager {
|
|||||||
console.log('sending command', command.cmd.type)
|
console.log('sending command', command.cmd.type)
|
||||||
lastMessage = command.cmd.type
|
lastMessage = command.cmd.type
|
||||||
}
|
}
|
||||||
|
if (command.type === 'modeling_cmd_batch_req') {
|
||||||
|
this.engineConnection?.send(command)
|
||||||
|
// TODO - handlePendingCommands does not handle batch commands
|
||||||
|
// return this.handlePendingCommand(command.requests[0].cmd_id, command.cmd)
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
|
||||||
const cmd = command.cmd
|
const cmd = command.cmd
|
||||||
if (
|
if (
|
||||||
(cmd.type === 'camera_drag_move' ||
|
(cmd.type === 'camera_drag_move' ||
|
||||||
cmd.type === 'handle_mouse_drag_move') &&
|
cmd.type === 'handle_mouse_drag_move' ||
|
||||||
this.engineConnection?.unreliableDataChannel
|
cmd.type === 'default_camera_look_at' ||
|
||||||
|
cmd.type === ('default_camera_perspective_settings' as any)) &&
|
||||||
|
this.engineConnection?.unreliableDataChannel &&
|
||||||
|
!forceWebsocket
|
||||||
) {
|
) {
|
||||||
cmd.sequence = this.outSequence
|
;(cmd as any).sequence = this.outSequence
|
||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableSend(command)
|
this.engineConnection?.unreliableSend(command)
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
@ -1277,6 +1298,12 @@ export class EngineCommandManager {
|
|||||||
this.engineConnection?.unreliableSend(command)
|
this.engineConnection?.unreliableSend(command)
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
command.cmd.type === 'default_camera_look_at' ||
|
||||||
|
command.cmd.type === ('default_camera_perspective_settings' as any)
|
||||||
|
) {
|
||||||
|
;(cmd as any).sequence = this.outSequence++
|
||||||
|
}
|
||||||
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
||||||
this.engineConnection?.send(command)
|
this.engineConnection?.send(command)
|
||||||
return this.handlePendingCommand(command.cmd_id, command.cmd)
|
return this.handlePendingCommand(command.cmd_id, command.cmd)
|
||||||
@ -1285,10 +1312,12 @@ export class EngineCommandManager {
|
|||||||
id,
|
id,
|
||||||
range,
|
range,
|
||||||
command,
|
command,
|
||||||
|
ast,
|
||||||
}: {
|
}: {
|
||||||
id: string
|
id: string
|
||||||
range: SourceRange
|
range: SourceRange
|
||||||
command: EngineCommand | string
|
command: EngineCommand | string
|
||||||
|
ast: Program
|
||||||
}): Promise<any> {
|
}): Promise<any> {
|
||||||
if (this.engineConnection === undefined) {
|
if (this.engineConnection === undefined) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
@ -1310,17 +1339,18 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
this.engineConnection?.send(command)
|
this.engineConnection?.send(command)
|
||||||
if (typeof command !== 'string' && command.type === 'modeling_cmd_req') {
|
if (typeof command !== 'string' && command.type === 'modeling_cmd_req') {
|
||||||
return this.handlePendingCommand(id, command?.cmd, range)
|
return this.handlePendingCommand(id, command?.cmd, ast, range)
|
||||||
} else if (typeof command === 'string') {
|
} else if (typeof command === 'string') {
|
||||||
const parseCommand: EngineCommand = JSON.parse(command)
|
const parseCommand: EngineCommand = JSON.parse(command)
|
||||||
if (parseCommand.type === 'modeling_cmd_req')
|
if (parseCommand.type === 'modeling_cmd_req')
|
||||||
return this.handlePendingCommand(id, parseCommand?.cmd, range)
|
return this.handlePendingCommand(id, parseCommand?.cmd, ast, range)
|
||||||
}
|
}
|
||||||
throw Error('shouldnt reach here')
|
throw Error('shouldnt reach here')
|
||||||
}
|
}
|
||||||
handlePendingCommand(
|
handlePendingCommand(
|
||||||
id: string,
|
id: string,
|
||||||
command: Models['ModelingCmd_type'],
|
command: Models['ModelingCmd_type'],
|
||||||
|
ast?: Program,
|
||||||
range?: SourceRange
|
range?: SourceRange
|
||||||
) {
|
) {
|
||||||
let resolve: (val: any) => void = () => {}
|
let resolve: (val: any) => void = () => {}
|
||||||
@ -1333,8 +1363,12 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
// TODO handle other commands that have a parent
|
// TODO handle other commands that have a parent
|
||||||
}
|
}
|
||||||
|
const pathToNode = ast
|
||||||
|
? getNodePathFromSourceRange(ast, range || [0, 0])
|
||||||
|
: []
|
||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
range: range || [0, 0],
|
range: range || [0, 0],
|
||||||
|
pathToNode,
|
||||||
type: 'pending',
|
type: 'pending',
|
||||||
commandType: command.type,
|
commandType: command.type,
|
||||||
parentId: getParentId(),
|
parentId: getParentId(),
|
||||||
@ -1363,9 +1397,12 @@ export class EngineCommandManager {
|
|||||||
const range: SourceRange = JSON.parse(rangeStr)
|
const range: SourceRange = JSON.parse(rangeStr)
|
||||||
|
|
||||||
// We only care about the modeling command response.
|
// We only care about the modeling command response.
|
||||||
return this.sendModelingCommand({ id, range, command: commandStr }).then(
|
return this.sendModelingCommand({
|
||||||
({ raw }) => JSON.stringify(raw)
|
id,
|
||||||
)
|
range,
|
||||||
|
command: commandStr,
|
||||||
|
ast: this.getAst(),
|
||||||
|
}).then(({ raw }) => JSON.stringify(raw))
|
||||||
}
|
}
|
||||||
commandResult(id: string): Promise<any> {
|
commandResult(id: string): Promise<any> {
|
||||||
const command = this.artifactMap[id]
|
const command = this.artifactMap[id]
|
||||||
@ -1392,102 +1429,6 @@ export class EngineCommandManager {
|
|||||||
artifactMap: this.artifactMap,
|
artifactMap: this.artifactMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async initPlanes() {
|
|
||||||
const [xy, yz, xz] = [
|
|
||||||
await this.createPlane({
|
|
||||||
x_axis: { x: 1, y: 0, z: 0 },
|
|
||||||
y_axis: { x: 0, y: 1, z: 0 },
|
|
||||||
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
|
||||||
}),
|
|
||||||
await this.createPlane({
|
|
||||||
x_axis: { x: 0, y: 1, z: 0 },
|
|
||||||
y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
|
||||||
}),
|
|
||||||
await this.createPlane({
|
|
||||||
x_axis: { x: 1, y: 0, z: 0 },
|
|
||||||
y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
this.defaultPlanes = { xy, yz, xz }
|
|
||||||
|
|
||||||
this.subscribeTo({
|
|
||||||
event: 'select_with_point',
|
|
||||||
callback: ({ data }) => {
|
|
||||||
if (!data?.entity_id) return
|
|
||||||
if (
|
|
||||||
![
|
|
||||||
this.defaultPlanes.xy,
|
|
||||||
this.defaultPlanes.yz,
|
|
||||||
this.defaultPlanes.xz,
|
|
||||||
].includes(data.entity_id)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
this.onPlaneSelectCallback(data.entity_id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
planesInitialized(): boolean {
|
|
||||||
return (
|
|
||||||
this.defaultPlanes.xy !== '' &&
|
|
||||||
this.defaultPlanes.yz !== '' &&
|
|
||||||
this.defaultPlanes.xz !== ''
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onPlaneSelectCallback = (id: string) => {}
|
|
||||||
onPlaneSelected(callback: (id: string) => void) {
|
|
||||||
this.onPlaneSelectCallback = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
async setPlaneHidden(id: string, hidden: boolean): Promise<string> {
|
|
||||||
return await this.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'object_visible',
|
|
||||||
object_id: id,
|
|
||||||
hidden: hidden,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createPlane({
|
|
||||||
x_axis,
|
|
||||||
y_axis,
|
|
||||||
color,
|
|
||||||
}: {
|
|
||||||
x_axis: Models['Point3d_type']
|
|
||||||
y_axis: Models['Point3d_type']
|
|
||||||
color: Models['Color_type']
|
|
||||||
}): Promise<string> {
|
|
||||||
const planeId = uuidv4()
|
|
||||||
await this.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'make_plane',
|
|
||||||
size: 100,
|
|
||||||
origin: { x: 0, y: 0, z: 0 },
|
|
||||||
x_axis,
|
|
||||||
y_axis,
|
|
||||||
clobber: false,
|
|
||||||
hide: true,
|
|
||||||
},
|
|
||||||
cmd_id: planeId,
|
|
||||||
})
|
|
||||||
await this.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'plane_set_color',
|
|
||||||
plane_id: planeId,
|
|
||||||
color,
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
await this.setPlaneHidden(planeId, true)
|
|
||||||
return planeId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const engineCommandManager = new EngineCommandManager()
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
|
@ -45,7 +45,7 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
|
|||||||
} else if (!currentPath) {
|
} else if (!currentPath) {
|
||||||
return [0, 0]
|
return [0, 0]
|
||||||
}
|
}
|
||||||
if (currentPath.type === 'topoint') {
|
if (currentPath.type === 'ToPoint') {
|
||||||
return [currentPath.to[0], currentPath.to[1]]
|
return [currentPath.to[0], currentPath.to[1]]
|
||||||
}
|
}
|
||||||
return [0, 0]
|
return [0, 0]
|
||||||
@ -445,6 +445,85 @@ export const yLine: SketchLineHelper = {
|
|||||||
addTag: addTagWithTo('length'),
|
addTag: addTagWithTo('length'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const tangentialArcTo: SketchLineHelper = {
|
||||||
|
add: ({
|
||||||
|
node,
|
||||||
|
pathToNode,
|
||||||
|
to,
|
||||||
|
createCallback,
|
||||||
|
replaceExisting,
|
||||||
|
referencedSegment,
|
||||||
|
}) => {
|
||||||
|
const _node = { ...node }
|
||||||
|
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||||
|
const { node: pipe } = getNode<PipeExpression | CallExpression>(
|
||||||
|
'PipeExpression'
|
||||||
|
)
|
||||||
|
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||||
|
_node,
|
||||||
|
pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
|
||||||
|
const toX = createLiteral(roundOff(to[0], 2))
|
||||||
|
const toY = createLiteral(roundOff(to[1], 2))
|
||||||
|
|
||||||
|
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
|
||||||
|
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||||
|
const { callExp, valueUsedInTransform } = createCallback(
|
||||||
|
[toX, toY],
|
||||||
|
referencedSegment
|
||||||
|
)
|
||||||
|
pipe.body[callIndex] = callExp
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode,
|
||||||
|
valueUsedInTransform,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newLine = createCallExpression('tangentialArcTo', [
|
||||||
|
createArrayExpression([toX, toY]),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
if (pipe.type === 'PipeExpression') {
|
||||||
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode: [
|
||||||
|
...pathToNode,
|
||||||
|
['body', 'PipeExpression'],
|
||||||
|
[pipe.body.length - 1, 'CallExpression'],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
varDec.init = createPipeExpression([varDec.init, newLine])
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||||
|
const _node = { ...node }
|
||||||
|
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||||
|
_node,
|
||||||
|
pathToNode
|
||||||
|
)
|
||||||
|
const x = createLiteral(roundOff(to[0], 2))
|
||||||
|
const y = createLiteral(roundOff(to[1], 2))
|
||||||
|
|
||||||
|
const firstArg = callExpression.arguments?.[0]
|
||||||
|
if (!mutateArrExp(firstArg, createArrayExpression([x, y]))) {
|
||||||
|
mutateObjExpProp(firstArg, createArrayExpression([x, y]), 'to')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// TODO copy-paste from angledLine
|
||||||
|
addTag: addTagWithTo('angleLength'),
|
||||||
|
}
|
||||||
export const angledLine: SketchLineHelper = {
|
export const angledLine: SketchLineHelper = {
|
||||||
add: ({
|
add: ({
|
||||||
node,
|
node,
|
||||||
@ -900,6 +979,7 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
|||||||
angledLineToX,
|
angledLineToX,
|
||||||
angledLineToY,
|
angledLineToY,
|
||||||
angledLineThatIntersects,
|
angledLineThatIntersects,
|
||||||
|
tangentialArcTo,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function changeSketchArguments(
|
export function changeSketchArguments(
|
||||||
@ -942,14 +1022,28 @@ interface CreateLineFnCallArgs {
|
|||||||
|
|
||||||
export function compareVec2Epsilon(
|
export function compareVec2Epsilon(
|
||||||
vec1: [number, number],
|
vec1: [number, number],
|
||||||
vec2: [number, number]
|
vec2: [number, number],
|
||||||
|
compareEpsilon = 0.015625 // or 2^-6
|
||||||
) {
|
) {
|
||||||
const compareEpsilon = 0.015625 // or 2^-6
|
|
||||||
const xDifference = Math.abs(vec1[0] - vec2[0])
|
const xDifference = Math.abs(vec1[0] - vec2[0])
|
||||||
const yDifference = Math.abs(vec1[1] - vec2[1])
|
const yDifference = Math.abs(vec1[1] - vec2[1])
|
||||||
return xDifference < compareEpsilon && yDifference < compareEpsilon
|
return xDifference < compareEpsilon && yDifference < compareEpsilon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this version uses this distance of the two points instead of comparing x and y separately
|
||||||
|
export function compareVec2Epsilon2(
|
||||||
|
vec1: [number, number],
|
||||||
|
vec2: [number, number],
|
||||||
|
compareEpsilon = 0.015625 // or 2^-6
|
||||||
|
) {
|
||||||
|
const xDifference = Math.abs(vec1[0] - vec2[0])
|
||||||
|
const yDifference = Math.abs(vec1[1] - vec2[1])
|
||||||
|
const distance = Math.sqrt(
|
||||||
|
xDifference * xDifference + yDifference * yDifference
|
||||||
|
)
|
||||||
|
return distance < compareEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
export function addNewSketchLn({
|
export function addNewSketchLn({
|
||||||
node: _node,
|
node: _node,
|
||||||
programMemory: previousProgramMemory,
|
programMemory: previousProgramMemory,
|
||||||
@ -1288,5 +1382,9 @@ export function getFirstArg(callExp: CallExpression): {
|
|||||||
if (['angledLineThatIntersects'].includes(name)) {
|
if (['angledLineThatIntersects'].includes(name)) {
|
||||||
return getAngledLineThatIntersects(callExp)
|
return getAngledLineThatIntersects(callExp)
|
||||||
}
|
}
|
||||||
|
if (['tangentialArcTo'].includes(name)) {
|
||||||
|
// TODO probably needs it's own implementation
|
||||||
|
return getFirstArgValuesForXYFns(callExp)
|
||||||
|
}
|
||||||
throw new Error('unexpected call expression')
|
throw new Error('unexpected call expression')
|
||||||
}
|
}
|
||||||
|
@ -388,7 +388,7 @@ show(part001)`
|
|||||||
[index, index]
|
[index, index]
|
||||||
).segment
|
).segment
|
||||||
expect(segment).toEqual({
|
expect(segment).toEqual({
|
||||||
type: 'toPoint',
|
type: 'ToPoint',
|
||||||
to: [5.62, 1.79],
|
to: [5.62, 1.79],
|
||||||
from: [3.48, 0.44],
|
from: [3.48, 0.44],
|
||||||
name: '',
|
name: '',
|
||||||
@ -405,7 +405,7 @@ show(part001)`
|
|||||||
to: [0, 0.04],
|
to: [0, 0.04],
|
||||||
from: [0, 0.04],
|
from: [0, 0.04],
|
||||||
name: '',
|
name: '',
|
||||||
type: 'base',
|
type: 'Base',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -22,7 +22,7 @@ export function getSketchSegmentFromSourceRange(
|
|||||||
startSourceRange[1] >= rangeEnd &&
|
startSourceRange[1] >= rangeEnd &&
|
||||||
sketchGroup.start
|
sketchGroup.start
|
||||||
)
|
)
|
||||||
return { segment: { ...sketchGroup.start, type: 'base' }, index: -1 }
|
return { segment: { ...sketchGroup.start, type: 'Base' }, index: -1 }
|
||||||
|
|
||||||
const lineIndex = sketchGroup.value.findIndex(
|
const lineIndex = sketchGroup.value.findIndex(
|
||||||
({ __geoMeta: { sourceRange } }: Path) =>
|
({ __geoMeta: { sourceRange } }: Path) =>
|
||||||
|
@ -506,7 +506,7 @@ show(part001)`
|
|||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const constraintLevels: ReturnType<
|
const constraintLevels: ReturnType<
|
||||||
typeof getConstraintLevelFromSourceRange
|
typeof getConstraintLevelFromSourceRange
|
||||||
>[] = ['full', 'partial', 'free']
|
>['level'][] = ['full', 'partial', 'free']
|
||||||
constraintLevels.forEach((constraintLevel) => {
|
constraintLevels.forEach((constraintLevel) => {
|
||||||
const recursivelySeachCommentsAndCheckConstraintLevel = (
|
const recursivelySeachCommentsAndCheckConstraintLevel = (
|
||||||
str: string,
|
str: string,
|
||||||
@ -520,7 +520,7 @@ show(part001)`
|
|||||||
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
|
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
|
||||||
[offsetIndex, offsetIndex],
|
[offsetIndex, offsetIndex],
|
||||||
ast
|
ast
|
||||||
)
|
).level
|
||||||
expect(expectedConstraintLevel).toBe(constraintLevel)
|
expect(expectedConstraintLevel).toBe(constraintLevel)
|
||||||
return recursivelySeachCommentsAndCheckConstraintLevel(
|
return recursivelySeachCommentsAndCheckConstraintLevel(
|
||||||
str,
|
str,
|
||||||
|
@ -1503,20 +1503,21 @@ function getArgLiteralVal(arg: Value): number {
|
|||||||
export function getConstraintLevelFromSourceRange(
|
export function getConstraintLevelFromSourceRange(
|
||||||
cursorRange: Selection['range'],
|
cursorRange: Selection['range'],
|
||||||
ast: Program
|
ast: Program
|
||||||
): 'free' | 'partial' | 'full' {
|
): { range: [number, number]; level: 'free' | 'partial' | 'full' } {
|
||||||
const { node: sketchFnExp } = getNodeFromPath<CallExpression>(
|
const { node: sketchFnExp } = getNodeFromPath<CallExpression>(
|
||||||
ast,
|
ast,
|
||||||
getNodePathFromSourceRange(ast, cursorRange),
|
getNodePathFromSourceRange(ast, cursorRange),
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
)
|
)
|
||||||
const name = sketchFnExp?.callee?.name as ToolTip
|
const name = sketchFnExp?.callee?.name as ToolTip
|
||||||
if (!toolTips.includes(name)) return 'free'
|
const range: [number, number] = [sketchFnExp.start, sketchFnExp.end]
|
||||||
|
if (!toolTips.includes(name)) return { level: 'free', range: range }
|
||||||
|
|
||||||
const firstArg = getFirstArg(sketchFnExp)
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
|
|
||||||
// check if the function is fully constrained
|
// check if the function is fully constrained
|
||||||
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||||
return 'full'
|
return { level: 'full', range: range }
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the function has no constraints
|
// check if the function has no constraints
|
||||||
@ -1525,10 +1526,10 @@ export function getConstraintLevelFromSourceRange(
|
|||||||
const isOneValFree =
|
const isOneValFree =
|
||||||
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
|
||||||
|
|
||||||
if (isTwoValFree) return 'free'
|
if (isTwoValFree) return { level: 'free', range: range }
|
||||||
if (isOneValFree) return 'partial'
|
if (isOneValFree) return { level: 'partial', range: range }
|
||||||
|
|
||||||
return 'partial'
|
return { level: 'partial', range: range }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLiteralArrayOrStatic(
|
export function isLiteralArrayOrStatic(
|
||||||
|
@ -26,15 +26,6 @@ export function pathMapToSelections(
|
|||||||
return newSelections
|
return newSelections
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isReducedMotion(): boolean {
|
|
||||||
return (
|
|
||||||
typeof window !== 'undefined' &&
|
|
||||||
window.matchMedia &&
|
|
||||||
// TODO/Note I (Kurt) think '(prefers-reduced-motion: reduce)' and '(prefers-reduced-motion)' are equivalent, but not 100% sure
|
|
||||||
window.matchMedia('(prefers-reduced-motion)').matches
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCursorInSketchCommandRange(
|
export function isCursorInSketchCommandRange(
|
||||||
artifactMap: ArtifactMap,
|
artifactMap: ArtifactMap,
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
|
@ -4,6 +4,8 @@ import init, {
|
|||||||
execute_wasm,
|
execute_wasm,
|
||||||
lexer_wasm,
|
lexer_wasm,
|
||||||
modify_ast_for_sketch_wasm,
|
modify_ast_for_sketch_wasm,
|
||||||
|
is_points_ccw,
|
||||||
|
get_tangential_arc_to_info,
|
||||||
} from '../wasm-lib/pkg/wasm_lib'
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
@ -12,7 +14,7 @@ import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
|
|||||||
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||||
import type { Program } from '../wasm-lib/kcl/bindings/Program'
|
import type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
||||||
import { DefaultPlanes } from '../wasm-lib/kcl/bindings/DefaultPlanes'
|
import { Coords2d } from './std/sketch'
|
||||||
|
|
||||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||||
@ -118,15 +120,13 @@ export interface ProgramMemory {
|
|||||||
export const executor = async (
|
export const executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {}, return: null },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager
|
||||||
planes: DefaultPlanes
|
|
||||||
): Promise<ProgramMemory> => {
|
): Promise<ProgramMemory> => {
|
||||||
engineCommandManager.startNewSession()
|
engineCommandManager.startNewSession()
|
||||||
const _programMemory = await _executor(
|
const _programMemory = await _executor(
|
||||||
node,
|
node,
|
||||||
programMemory,
|
programMemory,
|
||||||
engineCommandManager,
|
engineCommandManager
|
||||||
planes
|
|
||||||
)
|
)
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands()
|
||||||
|
|
||||||
@ -137,15 +137,13 @@ export const executor = async (
|
|||||||
export const _executor = async (
|
export const _executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {}, return: null },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager
|
||||||
planes: DefaultPlanes
|
|
||||||
): Promise<ProgramMemory> => {
|
): Promise<ProgramMemory> => {
|
||||||
try {
|
try {
|
||||||
const memory: ProgramMemory = await execute_wasm(
|
const memory: ProgramMemory = await execute_wasm(
|
||||||
JSON.stringify(node),
|
JSON.stringify(node),
|
||||||
JSON.stringify(programMemory),
|
JSON.stringify(programMemory),
|
||||||
engineCommandManager,
|
engineCommandManager
|
||||||
JSON.stringify(planes)
|
|
||||||
)
|
)
|
||||||
return memory
|
return memory
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -212,3 +210,44 @@ export const modifyAstForSketch = async (
|
|||||||
throw kclError
|
throw kclError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isPointsCCW(points: Coords2d[]): number {
|
||||||
|
return is_points_ccw(new Float64Array(points.flat()))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTangentialArcToInfo({
|
||||||
|
arcStartPoint,
|
||||||
|
arcEndPoint,
|
||||||
|
tanPreviousPoint,
|
||||||
|
obtuse = true,
|
||||||
|
}: {
|
||||||
|
arcStartPoint: Coords2d
|
||||||
|
arcEndPoint: Coords2d
|
||||||
|
tanPreviousPoint: Coords2d
|
||||||
|
obtuse?: boolean
|
||||||
|
}): {
|
||||||
|
center: Coords2d
|
||||||
|
arcMidPoint: Coords2d
|
||||||
|
radius: number
|
||||||
|
startAngle: number
|
||||||
|
endAngle: number
|
||||||
|
ccw: boolean
|
||||||
|
} {
|
||||||
|
const result = get_tangential_arc_to_info(
|
||||||
|
arcStartPoint[0],
|
||||||
|
arcStartPoint[1],
|
||||||
|
arcEndPoint[0],
|
||||||
|
arcEndPoint[1],
|
||||||
|
tanPreviousPoint[0],
|
||||||
|
tanPreviousPoint[1],
|
||||||
|
obtuse
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
center: [result.center_x, result.center_y],
|
||||||
|
arcMidPoint: [result.arc_mid_point_x, result.arc_mid_point_y],
|
||||||
|
radius: result.radius,
|
||||||
|
startAngle: result.start_angle,
|
||||||
|
endAngle: result.end_angle,
|
||||||
|
ccw: result.ccw > 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -33,7 +33,7 @@ interface MouseGuardZoomHandler {
|
|||||||
lenientDragStartButton?: number
|
lenientDragStartButton?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MouseGuard {
|
export interface MouseGuard {
|
||||||
pan: MouseGuardHandler
|
pan: MouseGuardHandler
|
||||||
zoom: MouseGuardZoomHandler
|
zoom: MouseGuardZoomHandler
|
||||||
rotate: MouseGuardHandler
|
rotate: MouseGuardHandler
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
export function isTauri(): boolean {
|
export function isTauri(): boolean {
|
||||||
return '__TAURI__' in window
|
if (typeof window !== 'undefined') {
|
||||||
|
return '__TAURI__' in window
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
22
src/lib/paths.ts
Normal file
22
src/lib/paths.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
|
|
||||||
|
const prependRoutes =
|
||||||
|
(routesObject: Record<string, string>) => (prepend: string) => {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(routesObject).map(([constName, path]) => [
|
||||||
|
constName,
|
||||||
|
prepend + path,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const paths = {
|
||||||
|
INDEX: '/',
|
||||||
|
HOME: '/home',
|
||||||
|
FILE: '/file',
|
||||||
|
SETTINGS: '/settings',
|
||||||
|
SIGN_IN: '/signin',
|
||||||
|
ONBOARDING: prependRoutes(onboardingPaths)(
|
||||||
|
'/onboarding'
|
||||||
|
) as typeof onboardingPaths,
|
||||||
|
}
|
@ -1,16 +1,24 @@
|
|||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||||
import { SourceRange } from 'lang/wasm'
|
import { CallExpression, SourceRange, parse, recast } from 'lang/wasm'
|
||||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { EditorSelection } from '@codemirror/state'
|
import { EditorSelection } from '@codemirror/state'
|
||||||
import { kclManager } from 'lang/KclSinglton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import { SelectionRange } from '@uiw/react-codemirror'
|
import { SelectionRange } from '@uiw/react-codemirror'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { Program } from 'lang/wasm'
|
import { Program } from 'lang/wasm'
|
||||||
import { doesPipeHaveCallExp } from 'lang/queryAst'
|
import { doesPipeHaveCallExp, getNodeFromPath } from 'lang/queryAst'
|
||||||
import { CommandArgument } from './commandTypes'
|
import { CommandArgument } from './commandTypes'
|
||||||
|
import {
|
||||||
|
STRAIGHT_SEGMENT,
|
||||||
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
|
clientSideScene,
|
||||||
|
getParentGroup,
|
||||||
|
} from 'clientSideScene/clientSideScene'
|
||||||
|
import { Mesh } from 'three'
|
||||||
|
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/setup'
|
||||||
|
|
||||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||||
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||||
@ -173,6 +181,42 @@ export async function getEventForSelectWithPoint(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getEventForSegmentSelection(
|
||||||
|
obj: any
|
||||||
|
): ModelingMachineEvent | null {
|
||||||
|
const group = getParentGroup(obj)
|
||||||
|
const axisGroup = getParentGroup(obj, [AXIS_GROUP])
|
||||||
|
if (!group && !axisGroup) return null
|
||||||
|
if (axisGroup?.userData.type === AXIS_GROUP) {
|
||||||
|
return {
|
||||||
|
type: 'Set selection',
|
||||||
|
data: {
|
||||||
|
selectionType: 'otherSelection',
|
||||||
|
selection: obj?.userData?.type === X_AXIS ? 'x-axis' : 'y-axis',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pathToNode = group?.userData?.pathToNode
|
||||||
|
if (!pathToNode) return null
|
||||||
|
// previous drags don't update ast for efficiency reasons
|
||||||
|
// So we want to make sure we have and updated ast with
|
||||||
|
// accurate source ranges
|
||||||
|
const updatedAst = parse(kclManager.code)
|
||||||
|
const node = getNodeFromPath<CallExpression>(
|
||||||
|
updatedAst,
|
||||||
|
pathToNode,
|
||||||
|
'CallExpression'
|
||||||
|
).node
|
||||||
|
const range: SourceRange = [node.start, node.end]
|
||||||
|
return {
|
||||||
|
type: 'Set selection',
|
||||||
|
data: {
|
||||||
|
selectionType: 'singleCodeCursor',
|
||||||
|
selection: { range, type: 'default' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function handleSelectionBatch({
|
export function handleSelectionBatch({
|
||||||
selections,
|
selections,
|
||||||
}: {
|
}: {
|
||||||
@ -239,7 +283,6 @@ export function handleSelectionWithShift({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (otherSelection) {
|
if (otherSelection) {
|
||||||
console.log('otherSelection in handleSelectionWithShift', otherSelection)
|
|
||||||
return handleSelectionBatch({
|
return handleSelectionBatch({
|
||||||
selections: {
|
selections: {
|
||||||
codeBasedSelections: isShiftDown
|
codeBasedSelections: isShiftDown
|
||||||
@ -335,6 +378,7 @@ export function processCodeMirrorRanges({
|
|||||||
.filter(Boolean) as any
|
.filter(Boolean) as any
|
||||||
|
|
||||||
if (!selectionRanges) return null
|
if (!selectionRanges) return null
|
||||||
|
updateSceneObjectColors(codeBasedSelections)
|
||||||
return {
|
return {
|
||||||
modelingEvent: {
|
modelingEvent: {
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
@ -350,6 +394,41 @@ export function processCodeMirrorRanges({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||||
|
let updated: Program
|
||||||
|
try {
|
||||||
|
updated = parse(recast(kclManager.ast))
|
||||||
|
} catch (e) {
|
||||||
|
console.error('error parsing code in processCodeMirrorRanges', e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Object.values(clientSideScene.activeSegments).forEach((segmentGroup) => {
|
||||||
|
if (
|
||||||
|
![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT].includes(
|
||||||
|
segmentGroup?.userData?.type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
const node = getNodeFromPath<CallExpression>(
|
||||||
|
updated,
|
||||||
|
segmentGroup.userData.pathToNode,
|
||||||
|
'CallExpression'
|
||||||
|
).node
|
||||||
|
const groupHasCursor = codeBasedSelections.some((selection) => {
|
||||||
|
return isOverlap(selection.range, [node.start, node.end])
|
||||||
|
})
|
||||||
|
const color = groupHasCursor ? 0x0000ff : 0xffffff
|
||||||
|
segmentGroup.traverse(
|
||||||
|
(child) => child instanceof Mesh && child.material.color.set(color)
|
||||||
|
)
|
||||||
|
// TODO if we had access to the xstate context and therefore selections
|
||||||
|
// we wouldn't need to set this here,
|
||||||
|
// it would be better to treat xstate context as the source of truth instead of having
|
||||||
|
// extra redundant state floating around
|
||||||
|
segmentGroup.userData.isSelected = groupHasCursor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function resetAndSetEngineEntitySelectionCmds(
|
function resetAndSetEngineEntitySelectionCmds(
|
||||||
selections: SelectionToEngine[]
|
selections: SelectionToEngine[]
|
||||||
): Models['WebSocketRequest_type'][] {
|
): Models['WebSocketRequest_type'][] {
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
faArrowUp,
|
faArrowUp,
|
||||||
faCircle,
|
faCircle,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
|
|
||||||
const DESC = ':desc'
|
const DESC = ':desc'
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
} from '@tauri-apps/api/fs'
|
} from '@tauri-apps/api/fs'
|
||||||
import { documentDir, homeDir, sep } from '@tauri-apps/api/path'
|
import { documentDir, homeDir, sep } from '@tauri-apps/api/path'
|
||||||
import { isTauri } from './isTauri'
|
import { isTauri } from './isTauri'
|
||||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
import { metadata } from 'tauri-plugin-fs-extra-api'
|
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||||
|
|
||||||
const PROJECT_FOLDER = 'zoo-modeling-app-projects'
|
const PROJECT_FOLDER = 'zoo-modeling-app-projects'
|
||||||
|
@ -66,11 +66,7 @@ export async function enginelessExecutor(
|
|||||||
}) as any as EngineCommandManager
|
}) as any as EngineCommandManager
|
||||||
await mockEngineCommandManager.waitForReady
|
await mockEngineCommandManager.waitForReady
|
||||||
mockEngineCommandManager.startNewSession()
|
mockEngineCommandManager.startNewSession()
|
||||||
const programMemory = await _executor(ast, pm, mockEngineCommandManager, {
|
const programMemory = await _executor(ast, pm, mockEngineCommandManager)
|
||||||
xy: uuidv4(),
|
|
||||||
yz: uuidv4(),
|
|
||||||
xz: uuidv4(),
|
|
||||||
})
|
|
||||||
await mockEngineCommandManager.waitForAllCommands()
|
await mockEngineCommandManager.waitForAllCommands()
|
||||||
return programMemory
|
return programMemory
|
||||||
}
|
}
|
||||||
@ -89,11 +85,7 @@ export async function executor(
|
|||||||
})
|
})
|
||||||
await engineCommandManager.waitForReady
|
await engineCommandManager.waitForReady
|
||||||
engineCommandManager.startNewSession()
|
engineCommandManager.startNewSession()
|
||||||
const programMemory = await _executor(ast, pm, engineCommandManager, {
|
const programMemory = await _executor(ast, pm, engineCommandManager)
|
||||||
xy: uuidv4(),
|
|
||||||
yz: uuidv4(),
|
|
||||||
xz: uuidv4(),
|
|
||||||
})
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands()
|
||||||
return programMemory
|
return programMemory
|
||||||
}
|
}
|
||||||
|
16
src/lib/types.ts
Normal file
16
src/lib/types.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { type Metadata } from 'tauri-plugin-fs-extra-api'
|
||||||
|
import { type FileEntry } from '@tauri-apps/api/fs'
|
||||||
|
|
||||||
|
export type IndexLoaderData = {
|
||||||
|
code: string | null
|
||||||
|
project?: ProjectWithEntryPointMetadata
|
||||||
|
file?: FileEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProjectWithEntryPointMetadata = FileEntry & {
|
||||||
|
entrypointMetadata: Metadata
|
||||||
|
}
|
||||||
|
export type HomeLoaderData = {
|
||||||
|
projects: ProjectWithEntryPointMetadata[]
|
||||||
|
newDefaultDirectory?: string
|
||||||
|
}
|
@ -98,3 +98,12 @@ export function getNormalisedCoordinates({
|
|||||||
y: Math.round((browserY / height) * streamHeight),
|
y: Math.round((browserY / height) * streamHeight),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isReducedMotion(): boolean {
|
||||||
|
return (
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
window.matchMedia &&
|
||||||
|
// TODO/Note I (Kurt) think '(prefers-reduced-motion: reduce)' and '(prefers-reduced-motion)' are equivalent, but not 100% sure
|
||||||
|
window.matchMedia('(prefers-reduced-motion)').matches
|
||||||
|
)
|
||||||
|
}
|
||||||
|
19
src/lib/utils2d.test.ts
Normal file
19
src/lib/utils2d.test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
|
import { isPointsCCW, initPromise } from 'lang/wasm'
|
||||||
|
|
||||||
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
|
describe('test isPointsCW', () => {
|
||||||
|
test('basic test', () => {
|
||||||
|
const points: Coords2d[] = [
|
||||||
|
[2, 2],
|
||||||
|
[2, 0],
|
||||||
|
[0, -2],
|
||||||
|
]
|
||||||
|
const pointsRev = [...points].reverse()
|
||||||
|
const CCW = isPointsCCW(pointsRev)
|
||||||
|
const CW = isPointsCCW(points)
|
||||||
|
expect(CCW).toBe(1)
|
||||||
|
expect(CW).toBe(-1)
|
||||||
|
})
|
||||||
|
})
|
19
src/lib/utils2d.ts
Normal file
19
src/lib/utils2d.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
|
import { getAngle } from './utils'
|
||||||
|
|
||||||
|
export function deg2Rad(deg: number): number {
|
||||||
|
return (deg * Math.PI) / 180
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTangentPointFromPreviousArc(
|
||||||
|
lastArcCenter: Coords2d,
|
||||||
|
lastArcCCW: boolean,
|
||||||
|
lastArcEnd: Coords2d
|
||||||
|
): Coords2d {
|
||||||
|
const angleFromOldCenterToArcStart = getAngle(lastArcCenter, lastArcEnd)
|
||||||
|
const tangentialAngle = angleFromOldCenterToArcStart + (lastArcCCW ? -90 : 90)
|
||||||
|
return [
|
||||||
|
Math.cos(deg2Rad(tangentialAngle)) * 10 + lastArcEnd[0],
|
||||||
|
Math.sin(deg2Rad(tangentialAngle)) * 10 + lastArcEnd[1],
|
||||||
|
]
|
||||||
|
}
|
@ -1,74 +0,0 @@
|
|||||||
// This file was automatically generated. Edits will be overwritten
|
|
||||||
|
|
||||||
export interface Typegen0 {
|
|
||||||
'@@xstate/typegen': true
|
|
||||||
internalEvents: {
|
|
||||||
'': { type: '' }
|
|
||||||
'done.invoke.validateArgument': {
|
|
||||||
type: 'done.invoke.validateArgument'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'done.invoke.validateArguments': {
|
|
||||||
type: 'done.invoke.validateArguments'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'error.platform.validateArgument': {
|
|
||||||
type: 'error.platform.validateArgument'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'error.platform.validateArguments': {
|
|
||||||
type: 'error.platform.validateArguments'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'xstate.init': { type: 'xstate.init' }
|
|
||||||
}
|
|
||||||
invokeSrcNameMap: {
|
|
||||||
'Validate all arguments': 'done.invoke.validateArguments'
|
|
||||||
'Validate argument': 'done.invoke.validateArgument'
|
|
||||||
}
|
|
||||||
missingImplementations: {
|
|
||||||
actions:
|
|
||||||
| 'Add arguments'
|
|
||||||
| 'Close dialog'
|
|
||||||
| 'Execute command'
|
|
||||||
| 'Open dialog'
|
|
||||||
delays: never
|
|
||||||
guards: never
|
|
||||||
services: never
|
|
||||||
}
|
|
||||||
eventsCausingActions: {
|
|
||||||
'Add arguments': 'done.invoke.validateArguments'
|
|
||||||
'Add commands': 'Add commands'
|
|
||||||
'Close dialog': 'Close'
|
|
||||||
'Execute command': '' | 'Submit'
|
|
||||||
'Open dialog': 'Open'
|
|
||||||
'Remove argument': 'Remove argument'
|
|
||||||
'Remove commands': 'Remove commands'
|
|
||||||
'Set current argument':
|
|
||||||
| 'Add argument'
|
|
||||||
| 'Edit argument'
|
|
||||||
| 'error.platform.validateArguments'
|
|
||||||
}
|
|
||||||
eventsCausingDelays: {}
|
|
||||||
eventsCausingGuards: {
|
|
||||||
'Arguments are ready': 'done.invoke.validateArguments'
|
|
||||||
'Command has no arguments': ''
|
|
||||||
}
|
|
||||||
eventsCausingServices: {
|
|
||||||
'Validate all arguments': 'done.invoke.validateArgument'
|
|
||||||
'Validate argument': 'Submit'
|
|
||||||
}
|
|
||||||
matchesStates:
|
|
||||||
| 'Checking Arguments'
|
|
||||||
| 'Closed'
|
|
||||||
| 'Command selected'
|
|
||||||
| 'Gathering arguments'
|
|
||||||
| 'Gathering arguments.Awaiting input'
|
|
||||||
| 'Gathering arguments.Validating'
|
|
||||||
| 'Review'
|
|
||||||
| 'Selecting command'
|
|
||||||
| { 'Gathering arguments'?: 'Awaiting input' | 'Validating' }
|
|
||||||
tags: never
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { assign, createMachine } from 'xstate'
|
import { assign, createMachine } from 'xstate'
|
||||||
import { ProjectWithEntryPointMetadata } from 'Router'
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
import { FileEntry } from '@tauri-apps/api/fs'
|
import { FileEntry } from '@tauri-apps/api/fs'
|
||||||
|
|
||||||
export const FILE_PERSIST_KEY = 'Last opened KCL files'
|
export const FILE_PERSIST_KEY = 'Last opened KCL files'
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
// This file was automatically generated. Edits will be overwritten
|
|
||||||
|
|
||||||
export interface Typegen0 {
|
|
||||||
'@@xstate/typegen': true
|
|
||||||
internalEvents: {
|
|
||||||
'done.invoke.create-file': {
|
|
||||||
type: 'done.invoke.create-file'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'done.invoke.delete-file': {
|
|
||||||
type: 'done.invoke.delete-file'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'done.invoke.read-files': {
|
|
||||||
type: 'done.invoke.read-files'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'done.invoke.rename-file': {
|
|
||||||
type: 'done.invoke.rename-file'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'error.platform.create-file': {
|
|
||||||
type: 'error.platform.create-file'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'error.platform.delete-file': {
|
|
||||||
type: 'error.platform.delete-file'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'error.platform.read-files': {
|
|
||||||
type: 'error.platform.read-files'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'error.platform.rename-file': {
|
|
||||||
type: 'error.platform.rename-file'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'xstate.init': { type: 'xstate.init' }
|
|
||||||
}
|
|
||||||
invokeSrcNameMap: {
|
|
||||||
createFile: 'done.invoke.create-file'
|
|
||||||
deleteFile: 'done.invoke.delete-file'
|
|
||||||
readFiles: 'done.invoke.read-files'
|
|
||||||
renameFile: 'done.invoke.rename-file'
|
|
||||||
}
|
|
||||||
missingImplementations: {
|
|
||||||
actions: 'navigateToFile' | 'toastError' | 'toastSuccess'
|
|
||||||
delays: never
|
|
||||||
guards: 'Has at least 1 file'
|
|
||||||
services: 'createFile' | 'deleteFile' | 'readFiles' | 'renameFile'
|
|
||||||
}
|
|
||||||
eventsCausingActions: {
|
|
||||||
navigateToFile: 'Open file'
|
|
||||||
setFiles: 'done.invoke.read-files'
|
|
||||||
setSelectedDirectory: 'Set selected directory'
|
|
||||||
toastError:
|
|
||||||
| 'error.platform.create-file'
|
|
||||||
| 'error.platform.delete-file'
|
|
||||||
| 'error.platform.read-files'
|
|
||||||
| 'error.platform.rename-file'
|
|
||||||
toastSuccess:
|
|
||||||
| 'done.invoke.create-file'
|
|
||||||
| 'done.invoke.delete-file'
|
|
||||||
| 'done.invoke.rename-file'
|
|
||||||
}
|
|
||||||
eventsCausingDelays: {}
|
|
||||||
eventsCausingGuards: {
|
|
||||||
'Has at least 1 file': 'done.invoke.read-files'
|
|
||||||
}
|
|
||||||
eventsCausingServices: {
|
|
||||||
createFile: 'Create file'
|
|
||||||
deleteFile: 'Delete file'
|
|
||||||
readFiles:
|
|
||||||
| 'assign'
|
|
||||||
| 'done.invoke.create-file'
|
|
||||||
| 'done.invoke.delete-file'
|
|
||||||
| 'done.invoke.rename-file'
|
|
||||||
| 'error.platform.create-file'
|
|
||||||
| 'error.platform.rename-file'
|
|
||||||
| 'xstate.init'
|
|
||||||
renameFile: 'Rename file'
|
|
||||||
}
|
|
||||||
matchesStates:
|
|
||||||
| 'Creating file'
|
|
||||||
| 'Deleting file'
|
|
||||||
| 'Has files'
|
|
||||||
| 'Has no files'
|
|
||||||
| 'Opening file'
|
|
||||||
| 'Reading files'
|
|
||||||
| 'Renaming file'
|
|
||||||
tags: never
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { assign, createMachine } from 'xstate'
|
import { assign, createMachine } from 'xstate'
|
||||||
import { ProjectWithEntryPointMetadata } from '../Router'
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
import { HomeCommandSchema } from 'lib/commandBarConfigs/homeCommandConfig'
|
import { HomeCommandSchema } from 'lib/commandBarConfigs/homeCommandConfig'
|
||||||
|
|
||||||
export const homeMachine = createMachine(
|
export const homeMachine = createMachine(
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
// This file was automatically generated. Edits will be overwritten
|
|
||||||
|
|
||||||
export interface Typegen0 {
|
|
||||||
'@@xstate/typegen': true
|
|
||||||
internalEvents: {
|
|
||||||
'done.invoke.create-project': {
|
|
||||||
type: 'done.invoke.create-project'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'done.invoke.delete-project': {
|
|
||||||
type: 'done.invoke.delete-project'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'done.invoke.read-projects': {
|
|
||||||
type: 'done.invoke.read-projects'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'done.invoke.rename-project': {
|
|
||||||
type: 'done.invoke.rename-project'
|
|
||||||
data: unknown
|
|
||||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
|
||||||
}
|
|
||||||
'error.platform.create-project': {
|
|
||||||
type: 'error.platform.create-project'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'error.platform.delete-project': {
|
|
||||||
type: 'error.platform.delete-project'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'error.platform.read-projects': {
|
|
||||||
type: 'error.platform.read-projects'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'error.platform.rename-project': {
|
|
||||||
type: 'error.platform.rename-project'
|
|
||||||
data: unknown
|
|
||||||
}
|
|
||||||
'xstate.init': { type: 'xstate.init' }
|
|
||||||
}
|
|
||||||
invokeSrcNameMap: {
|
|
||||||
createProject: 'done.invoke.create-project'
|
|
||||||
deleteProject: 'done.invoke.delete-project'
|
|
||||||
readProjects: 'done.invoke.read-projects'
|
|
||||||
renameProject: 'done.invoke.rename-project'
|
|
||||||
}
|
|
||||||
missingImplementations: {
|
|
||||||
actions: 'navigateToProject' | 'toastError' | 'toastSuccess'
|
|
||||||
delays: never
|
|
||||||
guards: 'Has at least 1 project'
|
|
||||||
services:
|
|
||||||
| 'createProject'
|
|
||||||
| 'deleteProject'
|
|
||||||
| 'readProjects'
|
|
||||||
| 'renameProject'
|
|
||||||
}
|
|
||||||
eventsCausingActions: {
|
|
||||||
navigateToProject: 'Open project'
|
|
||||||
setProjects: 'done.invoke.read-projects'
|
|
||||||
toastError:
|
|
||||||
| 'error.platform.create-project'
|
|
||||||
| 'error.platform.delete-project'
|
|
||||||
| 'error.platform.read-projects'
|
|
||||||
| 'error.platform.rename-project'
|
|
||||||
toastSuccess:
|
|
||||||
| 'done.invoke.create-project'
|
|
||||||
| 'done.invoke.delete-project'
|
|
||||||
| 'done.invoke.rename-project'
|
|
||||||
}
|
|
||||||
eventsCausingDelays: {}
|
|
||||||
eventsCausingGuards: {
|
|
||||||
'Has at least 1 project': 'done.invoke.read-projects'
|
|
||||||
}
|
|
||||||
eventsCausingServices: {
|
|
||||||
createProject: 'Create project'
|
|
||||||
deleteProject: 'Delete project'
|
|
||||||
readProjects:
|
|
||||||
| 'assign'
|
|
||||||
| 'done.invoke.create-project'
|
|
||||||
| 'done.invoke.delete-project'
|
|
||||||
| 'done.invoke.rename-project'
|
|
||||||
| 'error.platform.create-project'
|
|
||||||
| 'error.platform.rename-project'
|
|
||||||
| 'xstate.init'
|
|
||||||
renameProject: 'Rename project'
|
|
||||||
}
|
|
||||||
matchesStates:
|
|
||||||
| 'Creating project'
|
|
||||||
| 'Deleting project'
|
|
||||||
| 'Has no projects'
|
|
||||||
| 'Has projects'
|
|
||||||
| 'Opening project'
|
|
||||||
| 'Reading projects'
|
|
||||||
| 'Renaming project'
|
|
||||||
tags: never
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@ -1,123 +0,0 @@
|
|||||||
|
|
||||||
// This file was automatically generated. Edits will be overwritten
|
|
||||||
|
|
||||||
export interface Typegen0 {
|
|
||||||
'@@xstate/typegen': true;
|
|
||||||
internalEvents: {
|
|
||||||
"": { type: "" };
|
|
||||||
"done.invoke.get-abs-x-info": { type: "done.invoke.get-abs-x-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
|
||||||
"done.invoke.get-abs-y-info": { type: "done.invoke.get-abs-y-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
|
||||||
"done.invoke.get-angle-info": { type: "done.invoke.get-angle-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
|
||||||
"done.invoke.get-horizontal-info": { type: "done.invoke.get-horizontal-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
|
||||||
"done.invoke.get-length-info": { type: "done.invoke.get-length-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
|
||||||
"done.invoke.get-perpendicular-distance-info": { type: "done.invoke.get-perpendicular-distance-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
|
||||||
"done.invoke.get-vertical-info": { type: "done.invoke.get-vertical-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
|
||||||
"error.platform.get-abs-x-info": { type: "error.platform.get-abs-x-info"; data: unknown };
|
|
||||||
"error.platform.get-abs-y-info": { type: "error.platform.get-abs-y-info"; data: unknown };
|
|
||||||
"error.platform.get-angle-info": { type: "error.platform.get-angle-info"; data: unknown };
|
|
||||||
"error.platform.get-horizontal-info": { type: "error.platform.get-horizontal-info"; data: unknown };
|
|
||||||
"error.platform.get-length-info": { type: "error.platform.get-length-info"; data: unknown };
|
|
||||||
"error.platform.get-perpendicular-distance-info": { type: "error.platform.get-perpendicular-distance-info"; data: unknown };
|
|
||||||
"error.platform.get-vertical-info": { type: "error.platform.get-vertical-info"; data: unknown };
|
|
||||||
"xstate.init": { type: "xstate.init" };
|
|
||||||
"xstate.stop": { type: "xstate.stop" };
|
|
||||||
};
|
|
||||||
invokeSrcNameMap: {
|
|
||||||
"Get ABS X info": "done.invoke.get-abs-x-info";
|
|
||||||
"Get ABS Y info": "done.invoke.get-abs-y-info";
|
|
||||||
"Get angle info": "done.invoke.get-angle-info";
|
|
||||||
"Get horizontal info": "done.invoke.get-horizontal-info";
|
|
||||||
"Get length info": "done.invoke.get-length-info";
|
|
||||||
"Get perpendicular distance info": "done.invoke.get-perpendicular-distance-info";
|
|
||||||
"Get vertical info": "done.invoke.get-vertical-info";
|
|
||||||
};
|
|
||||||
missingImplementations: {
|
|
||||||
actions: "AST add line segment" | "AST start new sketch" | "Modify AST" | "Set selection" | "Update code selection cursors" | "create path" | "set tool" | "show default planes" | "sketch exit execute";
|
|
||||||
delays: never;
|
|
||||||
guards: "Selection contains axis" | "Selection contains edge" | "Selection contains face" | "Selection contains line" | "Selection contains point" | "Selection is not empty" | "Selection is one face" | "has valid extrude selection";
|
|
||||||
services: "Get ABS X info" | "Get ABS Y info" | "Get angle info" | "Get horizontal info" | "Get length info" | "Get perpendicular distance info" | "Get vertical info";
|
|
||||||
};
|
|
||||||
eventsCausingActions: {
|
|
||||||
"AST add line segment": "Add point";
|
|
||||||
"AST extrude": "Extrude";
|
|
||||||
"AST start new sketch": "Add point";
|
|
||||||
"Add to code-based selection": "Deselect point" | "Deselect segment" | "Select all" | "Select edge" | "Select face" | "Select point" | "Select segment";
|
|
||||||
"Add to other selection": "Select axis";
|
|
||||||
"Clear selection": "Deselect all";
|
|
||||||
"Constrain equal length": "Constrain equal length";
|
|
||||||
"Constrain horizontally align": "Constrain horizontally align";
|
|
||||||
"Constrain parallel": "Constrain parallel";
|
|
||||||
"Constrain remove constraints": "Constrain remove constraints";
|
|
||||||
"Constrain snap to X": "Constrain snap to X";
|
|
||||||
"Constrain snap to Y": "Constrain snap to Y";
|
|
||||||
"Constrain vertically align": "Constrain vertically align";
|
|
||||||
"Make selection horizontal": "Make segment horizontal";
|
|
||||||
"Make selection vertical": "Make segment vertical";
|
|
||||||
"Modify AST": "Complete line";
|
|
||||||
"Remove from code-based selection": "Deselect edge" | "Deselect face" | "Deselect point";
|
|
||||||
"Remove from other selection": "Deselect axis";
|
|
||||||
"Set selection": "Set selection" | "done.invoke.get-abs-x-info" | "done.invoke.get-abs-y-info" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-perpendicular-distance-info" | "done.invoke.get-vertical-info";
|
|
||||||
"Update code selection cursors": "Complete line" | "Deselect all" | "Deselect axis" | "Deselect edge" | "Deselect face" | "Deselect point" | "Deselect segment" | "Select edge" | "Select face" | "Select point" | "Select segment";
|
|
||||||
"create path": "Select default plane";
|
|
||||||
"default_camera_disable_sketch_mode": "Cancel";
|
|
||||||
"edit mode enter": "Enter sketch" | "Re-execute";
|
|
||||||
"edit_mode_exit": "Cancel";
|
|
||||||
"equip select": "CancelSketch" | "Enter sketch" | "Select default plane" | "done.invoke.get-abs-x-info" | "done.invoke.get-abs-y-info" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-perpendicular-distance-info" | "done.invoke.get-vertical-info" | "error.platform.get-abs-x-info" | "error.platform.get-abs-y-info" | "error.platform.get-angle-info" | "error.platform.get-horizontal-info" | "error.platform.get-length-info" | "error.platform.get-perpendicular-distance-info" | "error.platform.get-vertical-info";
|
|
||||||
"hide default planes": "Cancel" | "Select default plane" | "Set selection" | "xstate.stop";
|
|
||||||
"reset sketch metadata": "Cancel" | "Select default plane";
|
|
||||||
"set default plane id": "Select default plane";
|
|
||||||
"set sketch metadata": "Enter sketch";
|
|
||||||
"set sketchMetadata from pathToNode": "Re-execute";
|
|
||||||
"set tool": "Equip new tool";
|
|
||||||
"set tool line": "Equip tool";
|
|
||||||
"set tool move": "Equip move tool";
|
|
||||||
"show default planes": "Enter sketch";
|
|
||||||
"sketch exit execute": "Cancel" | "Complete line" | "Set selection" | "xstate.stop";
|
|
||||||
"sketch mode enabled": "Enter sketch" | "Re-execute" | "Select default plane";
|
|
||||||
};
|
|
||||||
eventsCausingDelays: {
|
|
||||||
|
|
||||||
};
|
|
||||||
eventsCausingGuards: {
|
|
||||||
"Can canstrain parallel": "Constrain parallel";
|
|
||||||
"Can constrain ABS X": "Constrain ABS X";
|
|
||||||
"Can constrain ABS Y": "Constrain ABS Y";
|
|
||||||
"Can constrain angle": "Constrain angle";
|
|
||||||
"Can constrain equal length": "Constrain equal length";
|
|
||||||
"Can constrain horizontal distance": "Constrain horizontal distance";
|
|
||||||
"Can constrain horizontally align": "Constrain horizontally align";
|
|
||||||
"Can constrain length": "Constrain length";
|
|
||||||
"Can constrain perpendicular distance": "Constrain perpendicular distance";
|
|
||||||
"Can constrain remove constraints": "Constrain remove constraints";
|
|
||||||
"Can constrain snap to X": "Constrain snap to X";
|
|
||||||
"Can constrain snap to Y": "Constrain snap to Y";
|
|
||||||
"Can constrain vertical distance": "Constrain vertical distance";
|
|
||||||
"Can constrain vertically align": "Constrain vertically align";
|
|
||||||
"Can make selection horizontal": "Make segment horizontal";
|
|
||||||
"Can make selection vertical": "Make segment vertical";
|
|
||||||
"Selection contains axis": "Deselect axis";
|
|
||||||
"Selection contains edge": "Deselect edge";
|
|
||||||
"Selection contains face": "Deselect face";
|
|
||||||
"Selection contains line": "Deselect segment";
|
|
||||||
"Selection contains point": "Deselect point";
|
|
||||||
"Selection is not empty": "Deselect all";
|
|
||||||
"Selection is one face": "Enter sketch";
|
|
||||||
"can move": "";
|
|
||||||
"can move with execute": "";
|
|
||||||
"has valid extrude selection": "Extrude";
|
|
||||||
"is editing existing sketch": "";
|
|
||||||
};
|
|
||||||
eventsCausingServices: {
|
|
||||||
"Get ABS X info": "Constrain ABS X";
|
|
||||||
"Get ABS Y info": "Constrain ABS Y";
|
|
||||||
"Get angle info": "Constrain angle";
|
|
||||||
"Get horizontal info": "Constrain horizontal distance";
|
|
||||||
"Get length info": "Constrain length";
|
|
||||||
"Get perpendicular distance info": "Constrain perpendicular distance";
|
|
||||||
"Get vertical info": "Constrain vertical distance";
|
|
||||||
};
|
|
||||||
matchesStates: "Sketch" | "Sketch no face" | "Sketch.Await ABS X info" | "Sketch.Await ABS Y info" | "Sketch.Await angle info" | "Sketch.Await horizontal distance info" | "Sketch.Await length info" | "Sketch.Await perpendicular distance info" | "Sketch.Await vertical distance info" | "Sketch.Line Tool" | "Sketch.Line Tool.Done" | "Sketch.Line Tool.Init" | "Sketch.Line Tool.No Points" | "Sketch.Line Tool.Point Added" | "Sketch.Line Tool.Segment Added" | "Sketch.Move Tool" | "Sketch.Move Tool.Move init" | "Sketch.Move Tool.Move with execute" | "Sketch.Move Tool.Move without re-execute" | "Sketch.Move Tool.No move" | "Sketch.SketchIdle" | "idle" | { "Sketch"?: "Await ABS X info" | "Await ABS Y info" | "Await angle info" | "Await horizontal distance info" | "Await length info" | "Await perpendicular distance info" | "Await vertical distance info" | "Line Tool" | "Move Tool" | "SketchIdle" | { "Line Tool"?: "Done" | "Init" | "No Points" | "Point Added" | "Segment Added";
|
|
||||||
"Move Tool"?: "Move init" | "Move with execute" | "Move without re-execute" | "No move"; }; };
|
|
||||||
tags: never;
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
|||||||
// This file was automatically generated. Edits will be overwritten
|
|
||||||
|
|
||||||
export interface Typegen0 {
|
|
||||||
'@@xstate/typegen': true
|
|
||||||
internalEvents: {
|
|
||||||
'xstate.init': { type: 'xstate.init' }
|
|
||||||
}
|
|
||||||
invokeSrcNameMap: {}
|
|
||||||
missingImplementations: {
|
|
||||||
actions: 'toastSuccess'
|
|
||||||
delays: never
|
|
||||||
guards: never
|
|
||||||
services: never
|
|
||||||
}
|
|
||||||
eventsCausingActions: {
|
|
||||||
persistSettings:
|
|
||||||
| 'Set Base Unit'
|
|
||||||
| 'Set Camera Controls'
|
|
||||||
| 'Set Default Directory'
|
|
||||||
| 'Set Default Project Name'
|
|
||||||
| 'Set Onboarding Status'
|
|
||||||
| 'Set Text Wrapping'
|
|
||||||
| 'Set Theme'
|
|
||||||
| 'Set Unit System'
|
|
||||||
| 'Toggle Debug Panel'
|
|
||||||
setThemeClass: 'Set Theme' | 'xstate.init'
|
|
||||||
toastSuccess:
|
|
||||||
| 'Set Base Unit'
|
|
||||||
| 'Set Camera Controls'
|
|
||||||
| 'Set Default Directory'
|
|
||||||
| 'Set Default Project Name'
|
|
||||||
| 'Set Text Wrapping'
|
|
||||||
| 'Set Theme'
|
|
||||||
| 'Set Unit System'
|
|
||||||
| 'Toggle Debug Panel'
|
|
||||||
}
|
|
||||||
eventsCausingDelays: {}
|
|
||||||
eventsCausingGuards: {}
|
|
||||||
eventsCausingServices: {}
|
|
||||||
matchesStates: 'idle'
|
|
||||||
tags: never
|
|
||||||
}
|
|
@ -2,15 +2,13 @@ import { ReportHandler } from 'web-vitals'
|
|||||||
|
|
||||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
import('web-vitals')
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
.then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
getCLS(onPerfEntry)
|
||||||
getCLS(onPerfEntry)
|
getFID(onPerfEntry)
|
||||||
getFID(onPerfEntry)
|
getFCP(onPerfEntry)
|
||||||
getFCP(onPerfEntry)
|
getLCP(onPerfEntry)
|
||||||
getLCP(onPerfEntry)
|
getTTFB(onPerfEntry)
|
||||||
getTTFB(onPerfEntry)
|
})
|
||||||
})
|
|
||||||
.catch((e) => console.log(e))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,15 @@ import { AppHeader } from '../components/AppHeader'
|
|||||||
import ProjectCard from '../components/ProjectCard'
|
import ProjectCard from '../components/ProjectCard'
|
||||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { ProjectWithEntryPointMetadata, HomeLoaderData } from '../Router'
|
import {
|
||||||
|
type ProjectWithEntryPointMetadata,
|
||||||
|
type HomeLoaderData,
|
||||||
|
} from 'lib/types'
|
||||||
import Loading from '../components/Loading'
|
import Loading from '../components/Loading'
|
||||||
import { useMachine } from '@xstate/react'
|
import { useMachine } from '@xstate/react'
|
||||||
import { homeMachine } from '../machines/homeMachine'
|
import { homeMachine } from '../machines/homeMachine'
|
||||||
import { ContextFrom, EventFrom } from 'xstate'
|
import { ContextFrom, EventFrom } from 'xstate'
|
||||||
import { paths } from '../Router'
|
import { paths } from 'lib/paths'
|
||||||
import {
|
import {
|
||||||
getNextSearchParams,
|
getNextSearchParams,
|
||||||
getSortFunction,
|
getSortFunction,
|
||||||
@ -45,12 +48,18 @@ const Home = () => {
|
|||||||
send: sendToSettings,
|
send: sendToSettings,
|
||||||
},
|
},
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
if (newDefaultDirectory) {
|
|
||||||
sendToSettings({
|
// Set the default directory if it's been updated
|
||||||
type: 'Set Default Directory',
|
// during the loading of the home page. This is wrapped
|
||||||
data: { defaultDirectory: newDefaultDirectory },
|
// in a single-use effect to avoid a potential infinite loop.
|
||||||
})
|
useEffect(() => {
|
||||||
}
|
if (newDefaultDirectory) {
|
||||||
|
sendToSettings({
|
||||||
|
type: 'Set Default Directory',
|
||||||
|
data: { defaultDirectory: newDefaultDirectory },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const [state, send] = useMachine(homeMachine, {
|
const [state, send] = useMachine(homeMachine, {
|
||||||
context: {
|
context: {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user