Compare commits
69 Commits
kcl_prelud
...
v0.17.0
Author | SHA1 | Date | |
---|---|---|---|
d8cc57b843 | |||
e76db4a621 | |||
027e947bd5 | |||
0983dcca22 | |||
846fc99bbc | |||
c258ede25c | |||
4cc1b3d5ed | |||
be0dd1512d | |||
a5156c3f5d | |||
8038b5d7a3 | |||
54b234360e | |||
465d933d53 | |||
ccd0c619a6 | |||
7b570bf525 | |||
44d1c29801 | |||
0e916cfd5b | |||
e773e932b0 | |||
2d39fd32ce | |||
5a585a6c2d | |||
c09d6ee6bd | |||
09b55259ab | |||
68b61c9832 | |||
469ca94437 | |||
1d3850b46a | |||
0358343285 | |||
38b0603fa2 | |||
e48a8b6c5d | |||
73e573b251 | |||
793409d53d | |||
3e9ab16c4b | |||
ab226bc86f | |||
97677e4474 | |||
37fbc8c9ab | |||
29d61da552 | |||
ad2f669ec6 | |||
d66aad8b5d | |||
d8b8710a0d | |||
8f8ba2dca5 | |||
970b0abb54 | |||
06b464816f | |||
08f7bb2811 | |||
197df9f25d | |||
4d387dfaf7 | |||
912b97bea5 | |||
3e4ce44dc9 | |||
c2058a05fa | |||
7a57965690 | |||
c5b115ba97 | |||
90057c2dda | |||
f3e59690d6 | |||
9642a44a02 | |||
252c7651ac | |||
79edcf3826 | |||
b05ac3a05f | |||
1d01ba454b | |||
bf1d6963fe | |||
176ee63cb9 | |||
1ae8059c2b | |||
93f406d005 | |||
e97833f0ed | |||
35417dd8a6 | |||
cf0560dcfb | |||
3659946653 | |||
156c51484a | |||
dc8dd4bc72 | |||
335add67bd | |||
231794a69d | |||
8e5a6bc6fc | |||
4f82121105 |
4
.github/workflows/check-exampleKcl.yml
vendored
@ -16,10 +16,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Comment on PR
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const message = '`src/lib/exampleKcl.ts` has been updated in this PR, please review and update the `src/routes/onboarding`, if needed.';
|
||||
|
4
.github/workflows/playwright.yml
vendored
@ -9,6 +9,10 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
playwright-ubuntu:
|
||||
timeout-minutes: 60
|
||||
|
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -76,6 +76,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -244,6 +246,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -76,6 +76,8 @@ startSketchOn('XZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -244,6 +246,8 @@ startSketchOn('XZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -77,6 +77,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -245,6 +247,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -82,6 +82,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -250,6 +252,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -76,6 +76,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -244,6 +246,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -75,6 +75,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -243,6 +245,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -86,6 +86,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -254,6 +256,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -79,6 +79,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -247,6 +249,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -71,6 +71,8 @@ const rectangle = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -135,6 +137,8 @@ const rectangle = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -302,6 +306,8 @@ const rectangle = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -72,6 +72,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -240,6 +242,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -68,6 +68,8 @@ const square = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -236,6 +238,8 @@ const square = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -403,6 +407,8 @@ const square = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn('-XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -234,6 +236,8 @@ startSketchOn('-XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -72,6 +72,8 @@ const part = rectShape([0, 0], 20, 20)
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -240,6 +242,8 @@ const part = rectShape([0, 0], 20, 20)
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -80,6 +80,8 @@ const part = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -77,6 +77,8 @@ const part = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -57,6 +57,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -128,6 +130,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -65,6 +65,8 @@ startSketchAt([0, 0])
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -254,6 +254,8 @@ string
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -75,6 +75,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -243,6 +245,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -65,6 +65,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -233,6 +235,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -234,6 +236,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -234,6 +236,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -234,6 +236,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn('XZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -235,6 +237,8 @@ startSketchOn('XZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 221 KiB |
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 221 KiB |
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 221 KiB |
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 221 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 163 KiB |
@ -20,6 +20,8 @@ const commonPoints = {
|
||||
startAt: '[9.06, -12.22]',
|
||||
num1: 9.14,
|
||||
num2: 18.2,
|
||||
// num1: 9.64,
|
||||
// num2: 19.19,
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
@ -76,6 +78,7 @@ test('Basic sketch', async ({ page }) => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
|
||||
@ -86,7 +89,6 @@ test('Basic sketch', async ({ page }) => {
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -472,8 +474,18 @@ test('Auto complete works', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||
// but that doesn't seem to make it to the console for macos/safari :(
|
||||
if (message.text().includes('start kcl lsp')) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await lspStartPromise
|
||||
|
||||
// this test might be brittle as we add and remove functions
|
||||
// but should also be easy to update.
|
||||
@ -625,7 +637,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
const emptySpaceClick = () =>
|
||||
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
|
||||
const topHorzSegmentClick = () =>
|
||||
page.mouse.click(709, 289).then(() => page.waitForTimeout(100))
|
||||
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
|
||||
const bottomHorzSegmentClick = () =>
|
||||
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
||||
|
||||
@ -640,13 +652,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
await page.waitForTimeout(700) // wait for animation
|
||||
|
||||
const startXPx = 600
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
@ -727,13 +738,18 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
await emptySpaceClick()
|
||||
|
||||
// select segment in editor than another segment in scene and check there are two cursors
|
||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||
await page.waitForTimeout(300)
|
||||
await page.keyboard.down('Shift')
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
||||
// TODO change this back to shift click in the scene, not cmd click in the editor
|
||||
await bottomHorzSegmentClick()
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
||||
|
||||
await page.keyboard.down(process.platform === 'linux' ? 'Control' : 'Meta')
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.up(process.platform === 'linux' ? 'Control' : 'Meta')
|
||||
|
||||
// clear selection by clicking on nothing
|
||||
await emptySpaceClick()
|
||||
@ -918,13 +934,13 @@ test('Can add multiple sketches', async ({ page }) => {
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
const startXPx = 600
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -1372,10 +1388,129 @@ test('Snap to close works (at any scale)', async ({ page }) => {
|
||||
) => `const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|
||||
|> line([${roundOff(scale * 175.36)}, 0], %)
|
||||
|> line([0, -${roundOff(scale * 175.37) + fudge}], %)
|
||||
|> line([0, -${roundOff(scale * 175.36) + fudge}], %)
|
||||
|> close(%)`
|
||||
|
||||
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
|
||||
|
||||
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
|
||||
})
|
||||
|
||||
test('Sketch on face', async ({ page, context }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> line([3.75, 0.46], %)
|
||||
|> line([4.99, -0.46], %)
|
||||
|> line([3.3, -2.12], %)
|
||||
|> line([2.16, -3.33], %)
|
||||
|> line([0.85, -3.08], %)
|
||||
|> line([-0.18, -3.36], %)
|
||||
|> line([-3.86, -2.73], %)
|
||||
|> line([-17.67, 0.85], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.click(793, 133)
|
||||
|
||||
const firstClickPosition = [612, 238]
|
||||
const secondClickPosition = [661, 242]
|
||||
const thirdClickPosition = [609, 267]
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([1.03, 1.03], %)
|
||||
|> line([4.18, -0.35], %)
|
||||
|> line([-4.44, -2.13], %)
|
||||
|> close(%)`)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await u.updateCamPosition([1049, 239, 686])
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
const pointToDragFirst = [691, 237]
|
||||
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
|
||||
steps: 5,
|
||||
})
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([1.03, 1.03], %)
|
||||
|> line([2.81, -0.33], %)
|
||||
|> line([-4.44, -2.13], %)
|
||||
|> close(%)`)
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
|
||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([1.03, 1.03], %)
|
||||
|> line([2.81, -0.33], %)
|
||||
|> line([-4.44, -2.13], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`)
|
||||
})
|
||||
|
@ -22,7 +22,7 @@ test.beforeEach(async ({ context, page }) => {
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'system',
|
||||
theme: 'dark',
|
||||
unitSystem: 'imperial',
|
||||
})
|
||||
)
|
||||
@ -397,7 +397,7 @@ test('Draft segments should look right', async ({ page, context }) => {
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'system',
|
||||
theme: 'dark',
|
||||
unitSystem: 'imperial',
|
||||
})
|
||||
)
|
||||
@ -475,7 +475,7 @@ test('Client side scene scale should match engine scale inch', async ({
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'system',
|
||||
theme: 'dark',
|
||||
unitSystem: 'imperial',
|
||||
})
|
||||
)
|
||||
@ -575,7 +575,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
onboardingStatus: 'dismissed',
|
||||
showDebugPanel: true,
|
||||
textWrapping: 'On',
|
||||
theme: 'system',
|
||||
theme: 'dark',
|
||||
unitSystem: 'metric',
|
||||
})
|
||||
)
|
||||
@ -612,7 +612,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.33], %)`)
|
||||
|> startProfileAt([230.03, -310.32], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
@ -622,7 +622,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.33], %)
|
||||
|> startProfileAt([230.03, -310.32], %)
|
||||
|> line([232.2, 0], %)`)
|
||||
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
@ -632,7 +632,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.33], %)
|
||||
|> startProfileAt([230.03, -310.32], %)
|
||||
|> line([232.2, 0], %)
|
||||
|> tangentialArcTo([694.43, -78.12], %)`)
|
||||
|
||||
@ -658,3 +658,48 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test('Sketch on face with none z-up', async ({ page, context }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([1.4, 2.47], %)
|
||||
|> line({ to: [9.31, 10.55], tag: 'seg01' }, %)
|
||||
|> line([11.91, -10.42], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([-2.89, 1.82], %)
|
||||
|> line([4.68, 3.05], %)
|
||||
|> line({ to: [0, -7.79], tag: 'seg02' }, %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// click at 641, 135
|
||||
await page.mouse.click(641, 135)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
await page.waitForTimeout(200)
|
||||
})
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
64
package.json
@ -1,30 +1,30 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.16.0",
|
||||
"version": "0.17.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.10.2",
|
||||
"@codemirror/autocomplete": "^6.15.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@kittycad/lib": "^0.0.55",
|
||||
"@kittycad/lib": "^0.1.0",
|
||||
"@lezer/javascript": "^1.4.9",
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@replit/codemirror-interact": "^6.3.0",
|
||||
"@tauri-apps/api": "^1.5.1",
|
||||
"@tauri-apps/api": "^1.5.3",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@ts-stack/markdown": "^1.5.0",
|
||||
"@tweenjs/tween.js": "^23.1.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/react": "^18.2.41",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@uiw/react-codemirror": "^4.21.20",
|
||||
"@types/node": "^18.19.26",
|
||||
"@types/react": "^18.2.67",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@uiw/react-codemirror": "^4.21.24",
|
||||
"@xstate/inspect": "^0.8.0",
|
||||
"@xstate/react": "^3.2.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
@ -39,27 +39,27 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-hotkeys-hook": "^4.4.1",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-modal-promise": "^1.0.2",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"sketch-helpers": "^0.0.4",
|
||||
"swr": "^2.2.2",
|
||||
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
|
||||
"three": "^0.160.0",
|
||||
"toml": "^3.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3",
|
||||
"uuid": "^9.0.1",
|
||||
"vitest": "^1.3.1",
|
||||
"vitest": "^1.4.0",
|
||||
"vscode-jsonrpc": "^8.1.0",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"wasm-pack": "^0.12.1",
|
||||
"web-vitals": "^3.5.0",
|
||||
"web-vitals": "^3.5.2",
|
||||
"ws": "^8.13.0",
|
||||
"xstate": "^4.38.2",
|
||||
"zustand": "^4.4.5"
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
@ -110,41 +110,41 @@
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.23.3",
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@tauri-apps/cli": "^1.5.6",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/debounce-promise": "^3.1.8",
|
||||
"@tauri-apps/cli": "^1.5.11",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/debounce-promise": "^3.1.9",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@types/three": "^0.160.0",
|
||||
"@types/uuid": "^9.0.4",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/wait-on": "^5.3.4",
|
||||
"@types/wicg-file-system-access": "^2020.9.6",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@wdio/cli": "^8.24.3",
|
||||
"@wdio/globals": "^8.24.3",
|
||||
"@wdio/local-runner": "^8.24.3",
|
||||
"@wdio/mocha-framework": "^8.24.3",
|
||||
"@wdio/spec-reporter": "^8.24.2",
|
||||
"@wdio/local-runner": "^8.35.1",
|
||||
"@wdio/mocha-framework": "^8.35.0",
|
||||
"@wdio/spec-reporter": "^8.32.4",
|
||||
"@xstate/cli": "^0.5.17",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.53.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"happy-dom": "^10.8.0",
|
||||
"husky": "^8.0.3",
|
||||
"happy-dom": "^14.3.1",
|
||||
"husky": "^9.0.11",
|
||||
"pixelmatch": "^5.3.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.8.0",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"vite": "^5.1.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^5.2.2",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.1",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||
"wait-on": "^7.2.0",
|
||||
"yarn": "^1.22.19"
|
||||
|
6
src-tauri/Cargo.lock
generated
@ -1664,9 +1664,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.2.60"
|
||||
version = "0.2.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aa5906d0730bd90f6b3331fe57c04951d00743169a29ee96408767b4060605"
|
||||
checksum = "949555aa013e1cefa68f59671cfe96602a459c903376c53a410aebf3831d291f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -3876,7 +3876,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs-extra"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#6db4320e98a4278d605dd05d4d87e1433939a7cd"
|
||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#8cd4a39864986ae3600be9f23d089afd3988c43a"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
|
@ -16,7 +16,7 @@ tauri-build = { version = "1.5.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
kittycad = "0.2.60"
|
||||
kittycad = "0.2.61"
|
||||
oauth2 = "4.4.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
@ -7,7 +7,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "zoo-modeling-app",
|
||||
"version": "0.16.0"
|
||||
"version": "0.17.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
@ -34,8 +34,11 @@
|
||||
"request": true,
|
||||
"scope": [
|
||||
"https://dev.kittycad.io/*",
|
||||
"https://dev.zoo.dev/*",
|
||||
"https://kittycad.io/*",
|
||||
"https://api.dev.kittycad.io/*"
|
||||
"https://zoo.dev/*",
|
||||
"https://api.dev.kittycad.io/*",
|
||||
"https://api.dev.zoo.dev/*"
|
||||
]
|
||||
},
|
||||
"os": {
|
||||
|
@ -28,7 +28,7 @@ import { CodeMenu } from 'components/CodeMenu'
|
||||
import { TextEditor } from 'components/TextEditor'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||
import { engineCommandManager } from './lang/std/engineConnection'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
|
||||
import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
||||
import LspProvider from 'components/LspProvider'
|
||||
import { KclContextProvider } from 'lang/KclSingleton'
|
||||
import { KclContextProvider } from 'lang/KclProvider'
|
||||
|
||||
export const BROWSER_FILE_NAME = 'new'
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { WheelEvent, useRef, useMemo } from 'react'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { engineCommandManager } from './lang/std/engineConnection'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import {
|
||||
NetworkHealthState,
|
||||
useNetworkStatus,
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
import {
|
||||
EngineCommand,
|
||||
Subscription,
|
||||
engineCommandManager,
|
||||
EngineCommandManager,
|
||||
} from 'lang/std/engineConnection'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { deg2Rad } from 'lib/utils2d'
|
||||
@ -34,10 +34,6 @@ const tempQuaternion = new Quaternion() // just used for maths
|
||||
|
||||
type interactionType = 'pan' | 'rotate' | 'zoom'
|
||||
|
||||
const throttledEngCmd = throttle((cmd: EngineCommand) => {
|
||||
engineCommandManager.sendSceneCommand(cmd)
|
||||
}, 1000 / 15)
|
||||
|
||||
interface ThreeCamValues {
|
||||
position: Vector3
|
||||
quaternion: Quaternion
|
||||
@ -62,68 +58,8 @@ export type ReactCameraProperties =
|
||||
|
||||
const lastCmdDelay = 50
|
||||
|
||||
const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
|
||||
const cmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
...convertThreeCamValuesToEngineCam(threeValues),
|
||||
},
|
||||
}
|
||||
engineCommandManager.sendSceneCommand(cmd)
|
||||
}, 1000 / 15)
|
||||
|
||||
let lastPerspectiveCmd: EngineCommand | null = null
|
||||
let lastPerspectiveCmdTime: number = Date.now()
|
||||
let lastPerspectiveCmdTimeoutId: number | null = null
|
||||
|
||||
const sendLastPerspectiveReliableChannel = () => {
|
||||
if (
|
||||
lastPerspectiveCmd &&
|
||||
Date.now() - lastPerspectiveCmdTime >= lastCmdDelay
|
||||
) {
|
||||
engineCommandManager.sendSceneCommand(lastPerspectiveCmd, true)
|
||||
lastPerspectiveCmdTime = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
const throttledUpdateEngineFov = throttle(
|
||||
(vals: {
|
||||
position: Vector3
|
||||
quaternion: Quaternion
|
||||
zoom: number
|
||||
fov: number
|
||||
target: Vector3
|
||||
}) => {
|
||||
const cmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_perspective_settings',
|
||||
...convertThreeCamValuesToEngineCam({
|
||||
...vals,
|
||||
isPerspective: true,
|
||||
}),
|
||||
fov_y: vals.fov,
|
||||
...calculateNearFarFromFOV(vals.fov),
|
||||
},
|
||||
}
|
||||
engineCommandManager.sendSceneCommand(cmd)
|
||||
lastPerspectiveCmd = cmd
|
||||
lastPerspectiveCmdTime = Date.now()
|
||||
if (lastPerspectiveCmdTimeoutId !== null) {
|
||||
clearTimeout(lastPerspectiveCmdTimeoutId)
|
||||
}
|
||||
lastPerspectiveCmdTimeoutId = setTimeout(
|
||||
sendLastPerspectiveReliableChannel,
|
||||
lastCmdDelay
|
||||
) as any as number
|
||||
},
|
||||
1000 / 30
|
||||
)
|
||||
|
||||
export class CameraControls {
|
||||
engineCommandManager: EngineCommandManager
|
||||
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
|
||||
camera: PerspectiveCamera | OrthographicCamera
|
||||
target: Vector3
|
||||
@ -213,7 +149,77 @@ export class CameraControls {
|
||||
this.update(true)
|
||||
}
|
||||
|
||||
constructor(isOrtho = false, domElement: HTMLCanvasElement) {
|
||||
throttledEngCmd = throttle((cmd: EngineCommand) => {
|
||||
this.engineCommandManager.sendSceneCommand(cmd)
|
||||
}, 1000 / 30)
|
||||
|
||||
throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
|
||||
const cmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
...convertThreeCamValuesToEngineCam(threeValues),
|
||||
},
|
||||
}
|
||||
this.engineCommandManager.sendSceneCommand(cmd)
|
||||
}, 1000 / 15)
|
||||
|
||||
lastPerspectiveCmd: EngineCommand | null = null
|
||||
lastPerspectiveCmdTime: number = Date.now()
|
||||
lastPerspectiveCmdTimeoutId: number | null = null
|
||||
|
||||
sendLastPerspectiveReliableChannel = () => {
|
||||
if (
|
||||
this.lastPerspectiveCmd &&
|
||||
Date.now() - this.lastPerspectiveCmdTime >= lastCmdDelay
|
||||
) {
|
||||
this.engineCommandManager.sendSceneCommand(this.lastPerspectiveCmd, true)
|
||||
this.lastPerspectiveCmdTime = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
throttledUpdateEngineFov = throttle(
|
||||
(vals: {
|
||||
position: Vector3
|
||||
quaternion: Quaternion
|
||||
zoom: number
|
||||
fov: number
|
||||
target: Vector3
|
||||
}) => {
|
||||
const cmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_perspective_settings',
|
||||
...convertThreeCamValuesToEngineCam({
|
||||
...vals,
|
||||
isPerspective: true,
|
||||
}),
|
||||
fov_y: vals.fov,
|
||||
...calculateNearFarFromFOV(vals.fov),
|
||||
},
|
||||
}
|
||||
this.engineCommandManager.sendSceneCommand(cmd)
|
||||
this.lastPerspectiveCmd = cmd
|
||||
this.lastPerspectiveCmdTime = Date.now()
|
||||
if (this.lastPerspectiveCmdTimeoutId !== null) {
|
||||
clearTimeout(this.lastPerspectiveCmdTimeoutId)
|
||||
}
|
||||
this.lastPerspectiveCmdTimeoutId = setTimeout(
|
||||
this.sendLastPerspectiveReliableChannel,
|
||||
lastCmdDelay
|
||||
) as any as number
|
||||
},
|
||||
1000 / 30
|
||||
)
|
||||
|
||||
constructor(
|
||||
isOrtho = false,
|
||||
domElement: HTMLCanvasElement,
|
||||
engineCommandManager: EngineCommandManager
|
||||
) {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
this.camera = isOrtho ? new OrthographicCamera() : new PerspectiveCamera()
|
||||
this.camera.up.set(0, 0, 1)
|
||||
this.camera.far = 20000
|
||||
@ -259,15 +265,15 @@ export class CameraControls {
|
||||
this.onCameraChange()
|
||||
}
|
||||
setTimeout(() => {
|
||||
engineCommandManager.subscribeTo({
|
||||
this.engineCommandManager.subscribeTo({
|
||||
event: 'camera_drag_end',
|
||||
callback: cb,
|
||||
})
|
||||
engineCommandManager.subscribeTo({
|
||||
this.engineCommandManager.subscribeTo({
|
||||
event: 'default_camera_zoom',
|
||||
callback: cb,
|
||||
})
|
||||
engineCommandManager.subscribeTo({
|
||||
this.engineCommandManager.subscribeTo({
|
||||
event: 'default_camera_get_settings',
|
||||
callback: cb,
|
||||
})
|
||||
@ -310,7 +316,7 @@ export class CameraControls {
|
||||
this.handleStart()
|
||||
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
void engineCommandManager.sendSceneCommand({
|
||||
void this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'camera_drag_start',
|
||||
@ -334,7 +340,7 @@ export class CameraControls {
|
||||
if (interaction === 'none') return
|
||||
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
throttledEngCmd({
|
||||
this.throttledEngCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'camera_drag_move',
|
||||
@ -377,7 +383,7 @@ export class CameraControls {
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
const interaction = this.getInteractionType(event)
|
||||
if (interaction === 'none') return
|
||||
void engineCommandManager.sendSceneCommand({
|
||||
void this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'camera_drag_end',
|
||||
@ -401,7 +407,7 @@ export class CameraControls {
|
||||
this.handleEnd()
|
||||
return
|
||||
}
|
||||
throttledEngCmd({
|
||||
this.throttledEngCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'default_camera_zoom',
|
||||
@ -425,6 +431,7 @@ export class CameraControls {
|
||||
if (this.camera instanceof OrthographicCamera) return
|
||||
const { x: px, y: py, z: pz } = this.camera.position
|
||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||
const oldCamUp = this.camera.up.clone()
|
||||
const aspect = window.innerWidth / window.innerHeight
|
||||
this.lastPerspectiveFov = this.camera.fov
|
||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||
@ -436,7 +443,8 @@ export class CameraControls {
|
||||
z_near,
|
||||
z_far
|
||||
)
|
||||
this.camera.up.set(0, 0, 1)
|
||||
|
||||
this.camera.up.copy(oldCamUp)
|
||||
this.camera.layers.enable(SKETCH_LAYER)
|
||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||
@ -447,7 +455,7 @@ export class CameraControls {
|
||||
|
||||
this.camera.quaternion.set(qx, qy, qz, qw)
|
||||
this.camera.updateProjectionMatrix()
|
||||
engineCommandManager.sendSceneCommand({
|
||||
this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
@ -458,13 +466,14 @@ export class CameraControls {
|
||||
}
|
||||
private createPerspectiveCamera = () => {
|
||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||
const previousCamUp = this.camera.up.clone()
|
||||
this.camera = new PerspectiveCamera(
|
||||
this.lastPerspectiveFov,
|
||||
window.innerWidth / window.innerHeight,
|
||||
z_near,
|
||||
z_far
|
||||
)
|
||||
this.camera.up.set(0, 0, 1)
|
||||
this.camera.up.copy(previousCamUp)
|
||||
this.camera.layers.enable(SKETCH_LAYER)
|
||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||
@ -490,7 +499,7 @@ export class CameraControls {
|
||||
}
|
||||
usePerspectiveCamera = () => {
|
||||
this._usePerspectiveCamera()
|
||||
engineCommandManager.sendSceneCommand({
|
||||
this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
@ -558,7 +567,7 @@ export class CameraControls {
|
||||
this.camera.near = z_near
|
||||
this.camera.far = z_far
|
||||
|
||||
throttledUpdateEngineFov({
|
||||
this.throttledUpdateEngineFov({
|
||||
fov: newFov,
|
||||
position: newPosition,
|
||||
quaternion: this.camera.quaternion,
|
||||
@ -618,7 +627,7 @@ export class CameraControls {
|
||||
didChange = true
|
||||
}
|
||||
|
||||
this.safeLookAtTarget()
|
||||
this.safeLookAtTarget(this.camera.up)
|
||||
|
||||
// Update the camera's matrices
|
||||
this.camera.updateMatrixWorld()
|
||||
@ -683,48 +692,48 @@ export class CameraControls {
|
||||
targetAngle = -Math.PI / 2,
|
||||
duration = 500
|
||||
): Promise<void> {
|
||||
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
|
||||
// zPosition should stay the same
|
||||
const xyRadius = Math.sqrt(
|
||||
(this.target.x - this.camera.position.x) ** 2 +
|
||||
(this.target.y - this.camera.position.y) ** 2
|
||||
)
|
||||
const xyAngle = Math.atan2(
|
||||
this.camera.position.y - this.target.y,
|
||||
this.camera.position.x - this.target.x
|
||||
)
|
||||
this._isCamMovingCallback(true, true)
|
||||
return new Promise((resolve) => {
|
||||
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
|
||||
// zPosition should stay the same
|
||||
const xyRadius = Math.sqrt(
|
||||
(this.target.x - this.camera.position.x) ** 2 +
|
||||
(this.target.y - this.camera.position.y) ** 2
|
||||
)
|
||||
const xyAngle = Math.atan2(
|
||||
this.camera.position.y - this.target.y,
|
||||
this.camera.position.x - this.target.x
|
||||
)
|
||||
const camAtTime = (obj: { angle: number }) => {
|
||||
const x = xyRadius * Math.cos(obj.angle)
|
||||
const y = xyRadius * Math.sin(obj.angle)
|
||||
this.camera.position.set(
|
||||
this.target.x + x,
|
||||
this.target.y + y,
|
||||
this.camera.position.z
|
||||
)
|
||||
this.update()
|
||||
this.onCameraChange()
|
||||
}
|
||||
const onComplete = (obj: { angle: number }) => {
|
||||
camAtTime(obj)
|
||||
this._isCamMovingCallback(false, true)
|
||||
|
||||
// resolve after a couple of frames
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => resolve())
|
||||
})
|
||||
}
|
||||
this._isCamMovingCallback(true, true)
|
||||
|
||||
if (isReducedMotion()) {
|
||||
onComplete({ angle: targetAngle })
|
||||
return
|
||||
}
|
||||
|
||||
new TWEEN.Tween({ angle: xyAngle })
|
||||
.to({ angle: targetAngle }, duration)
|
||||
.onUpdate((obj) => {
|
||||
const x = xyRadius * Math.cos(obj.angle)
|
||||
const y = xyRadius * Math.sin(obj.angle)
|
||||
this.camera.position.set(
|
||||
this.target.x + x,
|
||||
this.target.y + y,
|
||||
this.camera.position.z
|
||||
)
|
||||
this.update()
|
||||
this.onCameraChange()
|
||||
})
|
||||
.onComplete((obj) => {
|
||||
const x = xyRadius * Math.cos(obj.angle)
|
||||
const y = xyRadius * Math.sin(obj.angle)
|
||||
this.camera.position.set(
|
||||
this.target.x + x,
|
||||
this.target.y + y,
|
||||
this.camera.position.z
|
||||
)
|
||||
this.update()
|
||||
this.onCameraChange()
|
||||
this._isCamMovingCallback(false, true)
|
||||
|
||||
// resolve after a couple of frames
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => resolve())
|
||||
})
|
||||
})
|
||||
.onUpdate(camAtTime)
|
||||
.onComplete(onComplete)
|
||||
.start()
|
||||
})
|
||||
}
|
||||
@ -778,6 +787,8 @@ export class CameraControls {
|
||||
targetQuaternion,
|
||||
animationProgress
|
||||
)
|
||||
const up = new Vector3(0, 0, 1).applyQuaternion(currentQ)
|
||||
this.camera.up.copy(up)
|
||||
const currentTarget = tempVec.lerpVectors(
|
||||
initialTarget,
|
||||
targetPosition,
|
||||
@ -802,7 +813,7 @@ export class CameraControls {
|
||||
|
||||
const onComplete = async () => {
|
||||
if (isReducedMotion() && toOrthographic) {
|
||||
cameraAtTime(0.99)
|
||||
cameraAtTime(0.9999)
|
||||
this.useOrthographicCamera()
|
||||
} else if (toOrthographic) {
|
||||
await this.animateToOrthographic()
|
||||
@ -863,37 +874,40 @@ export class CameraControls {
|
||||
|
||||
animateFovChange() // Start the animation
|
||||
})
|
||||
animateToPerspective = () =>
|
||||
animateToPerspective = (targetCamUp = new Vector3(0, 0, 1)) =>
|
||||
new Promise((resolve) => {
|
||||
if (this.syncDirection === 'engineToClient')
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
console.warn(
|
||||
'animate To Perspective not design to work with engineToClient syncDirection.'
|
||||
)
|
||||
}
|
||||
this.isFovAnimationInProgress = true
|
||||
// Immediately set the camera to perspective with a very low FOV
|
||||
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||
this.lastPerspectiveFov = 4
|
||||
let currentFov = 4
|
||||
this.camera.updateProjectionMatrix()
|
||||
const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN
|
||||
const initialCameraUp = this.camera.up.clone()
|
||||
this.usePerspectiveCamera()
|
||||
const tempVec = new Vector3()
|
||||
|
||||
const animateFovChange = () => {
|
||||
if (this.camera instanceof OrthographicCamera) return
|
||||
if (this.camera.fov < targetFov) {
|
||||
// Increase the FOV
|
||||
currentFov = Math.min(currentFov + fovAnimationStep, targetFov)
|
||||
// this.camera.fov = currentFov
|
||||
this.camera.updateProjectionMatrix()
|
||||
this.dollyZoom(currentFov)
|
||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
||||
} else {
|
||||
// Set the flag to false as the FOV animation is complete
|
||||
this.isFovAnimationInProgress = false
|
||||
resolve(true)
|
||||
}
|
||||
const cameraAtTime = (t: number) => {
|
||||
currentFov =
|
||||
this.lastPerspectiveFov + (targetFov - this.lastPerspectiveFov) * t
|
||||
const currentUp = tempVec.lerpVectors(initialCameraUp, targetCamUp, t)
|
||||
this.camera.up.copy(currentUp)
|
||||
this.dollyZoom(currentFov)
|
||||
}
|
||||
animateFovChange() // Start the animation
|
||||
|
||||
const onComplete = () => {
|
||||
this.isFovAnimationInProgress = false
|
||||
resolve(true)
|
||||
}
|
||||
|
||||
new TWEEN.Tween({ t: 0 })
|
||||
.to({ t: 1 }, isReducedMotion() ? 50 : FRAMES_TO_ANIMATE_IN * 16) // Assuming 60fps, hence 16ms per frame
|
||||
.easing(TWEEN.Easing.Quadratic.InOut)
|
||||
.onUpdate(({ t }) => cameraAtTime(t))
|
||||
.onComplete(onComplete)
|
||||
.start()
|
||||
})
|
||||
|
||||
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
|
||||
@ -916,7 +930,7 @@ export class CameraControls {
|
||||
}
|
||||
|
||||
if (this.syncDirection === 'clientToEngine' || forceUpdate)
|
||||
throttledUpdateEngineCamera({
|
||||
this.throttledUpdateEngineCamera({
|
||||
quaternion: this.camera.quaternion,
|
||||
position: this.camera.position,
|
||||
zoom: this.camera.zoom,
|
||||
|
@ -4,9 +4,10 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useStore } from 'useStore'
|
||||
import { DEBUG_SHOW_BOTH_SCENES, sceneInfra } from './sceneInfra'
|
||||
import { DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
|
||||
import { ReactCameraProperties } from './CameraControls'
|
||||
import { throttle } from 'lib/utils'
|
||||
import { sceneInfra } from 'lib/singletons'
|
||||
|
||||
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
||||
const [isCamMoving, setIsCamMoving] = useState(false)
|
||||
|
@ -40,3 +40,12 @@ export function isQuaternionVertical(q: Quaternion) {
|
||||
// no x or y components means it's vertical
|
||||
return compareVec2Epsilon2([v.x, v.y], [0, 0])
|
||||
}
|
||||
|
||||
export function quaternionFromUpNForward(up: Vector3, forward: Vector3) {
|
||||
const dummyCam = new PerspectiveCamera()
|
||||
dummyCam.up.copy(up)
|
||||
dummyCam.position.copy(forward)
|
||||
dummyCam.lookAt(0, 0, 0)
|
||||
dummyCam.updateMatrix()
|
||||
return dummyCam.quaternion.clone()
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
Group,
|
||||
Intersection,
|
||||
LineCurve3,
|
||||
Matrix4,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
Object3D,
|
||||
@ -29,7 +28,6 @@ import {
|
||||
INTERSECTION_PLANE_LAYER,
|
||||
OnMouseEnterLeaveArgs,
|
||||
RAYCASTABLE_PLANE,
|
||||
sceneInfra,
|
||||
SKETCH_GROUP_SEGMENTS,
|
||||
SKETCH_LAYER,
|
||||
X_AXIS,
|
||||
@ -37,7 +35,7 @@ import {
|
||||
Y_AXIS,
|
||||
YZ_PLANE,
|
||||
} from './sceneInfra'
|
||||
import { isQuaternionVertical } from './helpers'
|
||||
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
|
||||
import {
|
||||
CallExpression,
|
||||
getTangentialArcToInfo,
|
||||
@ -53,10 +51,9 @@ import {
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
} from 'lang/wasm'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { engineCommandManager, kclManager, sceneInfra } from 'lib/singletons'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { executeAst } from 'useStore'
|
||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||
import { executeAst, useStore } from 'useStore'
|
||||
import {
|
||||
createArcGeometry,
|
||||
dashedStraight,
|
||||
@ -70,16 +67,23 @@ import {
|
||||
changeSketchArguments,
|
||||
updateStartProfileAtArgs,
|
||||
} from 'lang/std/sketch'
|
||||
import { isReducedMotion, throttle } from 'lib/utils'
|
||||
import { throttle } from 'lib/utils'
|
||||
import {
|
||||
createArrayExpression,
|
||||
createCallExpressionStdLib,
|
||||
createLiteral,
|
||||
createPipeSubstitution,
|
||||
} from 'lang/modifyAst'
|
||||
import { getEventForSegmentSelection } from 'lib/selections'
|
||||
import {
|
||||
getEventForSegmentSelection,
|
||||
sendSelectEventToEngine,
|
||||
} from 'lib/selections'
|
||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||
import { createGridHelper, orthoScale, perspScale } from './helpers'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { SketchDetails } from 'machines/modelingMachine'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
|
||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||
|
||||
@ -95,14 +99,16 @@ export const PROFILE_START = 'profile-start'
|
||||
// This singleton Class is responsible for all of the things the user sees and interacts with.
|
||||
// That mostly mean sketch elements.
|
||||
// Cameras, controls, raycasters, etc are handled by sceneInfra
|
||||
class SceneEntities {
|
||||
export class SceneEntities {
|
||||
engineCommandManager: EngineCommandManager
|
||||
scene: Scene
|
||||
sceneProgramMemory: ProgramMemory = { root: {}, return: null }
|
||||
activeSegments: { [key: string]: Group } = {}
|
||||
intersectionPlane: Mesh | null = null
|
||||
axisGroup: Group | null = null
|
||||
currentSketchQuaternion: Quaternion | null = null
|
||||
constructor() {
|
||||
constructor(engineCommandManager: EngineCommandManager) {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
this.scene = sceneInfra?.scene
|
||||
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
|
||||
}
|
||||
@ -164,7 +170,7 @@ class SceneEntities {
|
||||
console.warn('createIntersectionPlane called when it already exists')
|
||||
return
|
||||
}
|
||||
const hundredM = 1000000
|
||||
const hundredM = 100_0000
|
||||
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
|
||||
const planeMaterial = new MeshBasicMaterial({
|
||||
color: 0xff0000,
|
||||
@ -178,7 +184,12 @@ class SceneEntities {
|
||||
this.intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER)
|
||||
this.scene.add(this.intersectionPlane)
|
||||
}
|
||||
createSketchAxis(sketchPathToNode: PathToNode) {
|
||||
createSketchAxis(
|
||||
sketchPathToNode: PathToNode,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number],
|
||||
sketchPosition?: [number, number, number]
|
||||
) {
|
||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||
const baseXColor = 0x000055
|
||||
const baseYColor = 0x550000
|
||||
@ -238,14 +249,12 @@ class SceneEntities {
|
||||
child.layers.set(SKETCH_LAYER)
|
||||
})
|
||||
|
||||
const quat = quaternionFromSketchGroup(
|
||||
sketchGroupFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
const quat = quaternionFromUpNForward(
|
||||
new Vector3(...up),
|
||||
new Vector3(...forward)
|
||||
)
|
||||
this.axisGroup.setRotationFromQuaternion(quat)
|
||||
sketchPosition && this.axisGroup.position.set(...sketchPosition)
|
||||
this.scene.add(this.axisGroup)
|
||||
}
|
||||
removeIntersectionPlane() {
|
||||
@ -255,27 +264,33 @@ class SceneEntities {
|
||||
|
||||
async setupSketch({
|
||||
sketchPathToNode,
|
||||
ast,
|
||||
// is draft line assumes the last segment is a draft line, and mods it as the user moves the mouse
|
||||
draftSegment,
|
||||
forward,
|
||||
up,
|
||||
position,
|
||||
maybeModdedAst,
|
||||
draftExpressionsIndices,
|
||||
}: {
|
||||
sketchPathToNode: PathToNode
|
||||
ast?: Program
|
||||
draftSegment?: DraftSegment
|
||||
}) {
|
||||
maybeModdedAst: Program
|
||||
draftExpressionsIndices?: { start: number; end: number }
|
||||
forward: [number, number, number]
|
||||
up: [number, number, number]
|
||||
position?: [number, number, number]
|
||||
}): Promise<{
|
||||
truncatedAst: Program
|
||||
programMemoryOverride: ProgramMemory
|
||||
sketchGroup: SketchGroup
|
||||
variableDeclarationName: string
|
||||
}> {
|
||||
sceneInfra.resetMouseListeners()
|
||||
this.createIntersectionPlane()
|
||||
|
||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||
this.prepareTruncatedMemoryAndAst(
|
||||
sketchPathToNode || [],
|
||||
kclManager.ast,
|
||||
draftSegment
|
||||
)
|
||||
this.prepareTruncatedMemoryAndAst(sketchPathToNode || [], maybeModdedAst)
|
||||
const { programMemory } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
programMemoryOverride,
|
||||
})
|
||||
const sketchGroup = sketchGroupFromPathToNode({
|
||||
@ -283,9 +298,16 @@ class SceneEntities {
|
||||
ast: kclManager.ast,
|
||||
programMemory,
|
||||
})
|
||||
if (!Array.isArray(sketchGroup?.value)) return
|
||||
if (!Array.isArray(sketchGroup?.value))
|
||||
return {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
sketchGroup,
|
||||
variableDeclarationName,
|
||||
}
|
||||
this.sceneProgramMemory = programMemory
|
||||
const group = new Group()
|
||||
position && group.position.set(...position)
|
||||
group.userData = {
|
||||
type: SKETCH_GROUP_SEGMENTS,
|
||||
pathToNode: sketchPathToNode,
|
||||
@ -325,7 +347,10 @@ class SceneEntities {
|
||||
kclManager.ast,
|
||||
segment.__geoMeta.sourceRange
|
||||
)
|
||||
if (draftSegment && (sketchGroup.value[index - 1] || sketchGroup.start)) {
|
||||
if (
|
||||
draftExpressionsIndices &&
|
||||
(sketchGroup.value[index - 1] || sketchGroup.start)
|
||||
) {
|
||||
const previousSegment =
|
||||
sketchGroup.value[index - 1] || sketchGroup.start
|
||||
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
||||
@ -340,7 +365,9 @@ class SceneEntities {
|
||||
segPathToNode[1][0] = bodyIndex
|
||||
}
|
||||
const isDraftSegment =
|
||||
draftSegment && index === sketchGroup.value.length - 1
|
||||
draftExpressionsIndices &&
|
||||
index <= draftExpressionsIndices.end &&
|
||||
index >= draftExpressionsIndices.start
|
||||
let seg
|
||||
const callExpName = getNodeFromPath<CallExpression>(
|
||||
kclManager.ast,
|
||||
@ -377,122 +404,182 @@ class SceneEntities {
|
||||
this.activeSegments[JSON.stringify(segPathToNode)] = seg
|
||||
})
|
||||
|
||||
this.currentSketchQuaternion = quaternionFromSketchGroup(sketchGroup)
|
||||
this.currentSketchQuaternion = quaternionFromUpNForward(
|
||||
new Vector3(...up),
|
||||
new Vector3(...forward)
|
||||
)
|
||||
group.setRotationFromQuaternion(this.currentSketchQuaternion)
|
||||
this.intersectionPlane &&
|
||||
this.intersectionPlane.setRotationFromQuaternion(
|
||||
this.currentSketchQuaternion
|
||||
)
|
||||
|
||||
this.intersectionPlane &&
|
||||
position &&
|
||||
this.intersectionPlane.position.set(...position)
|
||||
this.scene.add(group)
|
||||
if (!draftSegment) {
|
||||
sceneInfra.setCallbacks({
|
||||
onDrag: ({ selected, intersectionPoint, mouseEvent, intersects }) => {
|
||||
if (mouseEvent.which !== 1) return
|
||||
this.onDragSegment({
|
||||
object: selected,
|
||||
intersection2d: intersectionPoint.twoD,
|
||||
intersects,
|
||||
sketchPathToNode,
|
||||
})
|
||||
},
|
||||
onMove: () => {},
|
||||
onClick: (args) => {
|
||||
if (args?.mouseEvent.which !== 1) return
|
||||
if (!args || !args.selected) {
|
||||
sceneInfra.modelingSend({
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
const { selected } = args
|
||||
const event = getEventForSegmentSelection(selected)
|
||||
if (!event) return
|
||||
sceneInfra.modelingSend(event)
|
||||
},
|
||||
...mouseEnterLeaveCallbacks(),
|
||||
})
|
||||
} else {
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: async (args) => {
|
||||
if (!args) return
|
||||
if (args.mouseEvent.which !== 1) return
|
||||
const { intersectionPoint } = args
|
||||
let intersection2d = intersectionPoint?.twoD
|
||||
const profileStart = args.intersects
|
||||
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
|
||||
.find((a) => a?.name === PROFILE_START)
|
||||
|
||||
let modifiedAst
|
||||
if (profileStart) {
|
||||
modifiedAst = addCloseToPipe({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
} else if (intersection2d) {
|
||||
const lastSegment = sketchGroup.value.slice(-1)[0]
|
||||
modifiedAst = addNewSketchLn({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
to: [intersection2d.x, intersection2d.y],
|
||||
from: [lastSegment.to[0], lastSegment.to[1]],
|
||||
fnName:
|
||||
lastSegment.type === 'TangentialArcTo'
|
||||
? 'tangentialArcTo'
|
||||
: 'line',
|
||||
pathToNode: sketchPathToNode,
|
||||
}).modifiedAst
|
||||
} else {
|
||||
// return early as we didn't modify the ast
|
||||
return
|
||||
}
|
||||
|
||||
kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
this.setupSketch({ sketchPathToNode, draftSegment })
|
||||
},
|
||||
onMove: (args) => {
|
||||
this.onDragSegment({
|
||||
intersection2d: args.intersectionPoint.twoD,
|
||||
object: Object.values(this.activeSegments).slice(-1)[0],
|
||||
intersects: args.intersects,
|
||||
sketchPathToNode,
|
||||
draftInfo: {
|
||||
draftSegment,
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
variableDeclarationName,
|
||||
},
|
||||
})
|
||||
},
|
||||
...mouseEnterLeaveCallbacks(),
|
||||
})
|
||||
}
|
||||
sceneInfra.camControls.enableRotate = false
|
||||
|
||||
return {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
sketchGroup,
|
||||
variableDeclarationName,
|
||||
}
|
||||
}
|
||||
updateAstAndRejigSketch = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
modifiedAst: Program
|
||||
modifiedAst: Program,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number],
|
||||
origin: [number, number, number]
|
||||
) => {
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
this.setupSketch({ sketchPathToNode })
|
||||
await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
up,
|
||||
position: origin,
|
||||
maybeModdedAst: kclManager.ast,
|
||||
})
|
||||
this.setupSketchIdleCallbacks(sketchPathToNode)
|
||||
}
|
||||
setUpDraftArc = async (sketchPathToNode: PathToNode) => {
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
this.setupSketch({ sketchPathToNode, draftSegment: 'tangentialArcTo' })
|
||||
setUpDraftSegment = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number],
|
||||
origin: [number, number, number],
|
||||
segmentName: 'line' | 'tangentialArcTo' = 'line',
|
||||
shouldTearDown = true
|
||||
) => {
|
||||
const _ast = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
|
||||
const variableDeclarationName =
|
||||
getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)?.node?.declarations?.[0]?.id?.name || ''
|
||||
const sg = kclManager.programMemory.root[
|
||||
variableDeclarationName
|
||||
] as SketchGroup
|
||||
const lastSeg = sg.value.slice(-1)[0] || sg.start
|
||||
|
||||
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||
|
||||
let modifiedAst = addNewSketchLn({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
to: [lastSeg.to[0], lastSeg.to[1]],
|
||||
from: [lastSeg.to[0], lastSeg.to[1]],
|
||||
fnName: segmentName,
|
||||
pathToNode: sketchPathToNode,
|
||||
}).modifiedAst
|
||||
modifiedAst = parse(recast(modifiedAst))
|
||||
|
||||
const draftExpressionsIndices = { start: index, end: index }
|
||||
|
||||
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
|
||||
const { truncatedAst, programMemoryOverride, sketchGroup } =
|
||||
await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
up,
|
||||
position: origin,
|
||||
maybeModdedAst: modifiedAst,
|
||||
draftExpressionsIndices,
|
||||
})
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: async (args) => {
|
||||
if (!args) return
|
||||
if (args.mouseEvent.which !== 1) return
|
||||
const { intersectionPoint } = args
|
||||
let intersection2d = intersectionPoint?.twoD
|
||||
const profileStart = args.intersects
|
||||
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
|
||||
.find((a) => a?.name === PROFILE_START)
|
||||
|
||||
let modifiedAst
|
||||
if (profileStart) {
|
||||
modifiedAst = addCloseToPipe({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
} else if (intersection2d) {
|
||||
const lastSegment = sketchGroup.value.slice(-1)[0]
|
||||
modifiedAst = addNewSketchLn({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
to: [intersection2d.x, intersection2d.y],
|
||||
from: [lastSegment.to[0], lastSegment.to[1]],
|
||||
fnName:
|
||||
lastSegment.type === 'TangentialArcTo'
|
||||
? 'tangentialArcTo'
|
||||
: 'line',
|
||||
pathToNode: sketchPathToNode,
|
||||
}).modifiedAst
|
||||
} else {
|
||||
// return early as we didn't modify the ast
|
||||
return
|
||||
}
|
||||
|
||||
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||
this.setUpDraftSegment(
|
||||
sketchPathToNode,
|
||||
forward,
|
||||
up,
|
||||
origin,
|
||||
segmentName
|
||||
)
|
||||
},
|
||||
onMove: (args) => {
|
||||
this.onDragSegment({
|
||||
intersection2d: args.intersectionPoint.twoD,
|
||||
object: Object.values(this.activeSegments).slice(-1)[0],
|
||||
intersects: args.intersects,
|
||||
sketchPathToNode,
|
||||
draftInfo: {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
variableDeclarationName,
|
||||
},
|
||||
})
|
||||
},
|
||||
...mouseEnterLeaveCallbacks(),
|
||||
})
|
||||
}
|
||||
setUpDraftLine = async (sketchPathToNode: PathToNode) => {
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
this.setupSketch({ sketchPathToNode, draftSegment: 'line' })
|
||||
setupSketchIdleCallbacks = (pathToNode: PathToNode) => {
|
||||
sceneInfra.setCallbacks({
|
||||
onDrag: ({ selected, intersectionPoint, mouseEvent, intersects }) => {
|
||||
if (mouseEvent.which !== 1) return
|
||||
this.onDragSegment({
|
||||
object: selected,
|
||||
intersection2d: intersectionPoint.twoD,
|
||||
intersects,
|
||||
sketchPathToNode: pathToNode,
|
||||
})
|
||||
},
|
||||
onMove: () => {},
|
||||
onClick: (args) => {
|
||||
if (args?.mouseEvent.which !== 1) return
|
||||
if (!args || !args.selected) {
|
||||
sceneInfra.modelingSend({
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
const { selected } = args
|
||||
const event = getEventForSegmentSelection(selected)
|
||||
if (!event) return
|
||||
sceneInfra.modelingSend(event)
|
||||
},
|
||||
...mouseEnterLeaveCallbacks(),
|
||||
})
|
||||
}
|
||||
onDraftLineMouseMove = () => {}
|
||||
prepareTruncatedMemoryAndAst = (
|
||||
sketchPathToNode: PathToNode,
|
||||
ast?: Program,
|
||||
@ -516,7 +603,6 @@ class SceneEntities {
|
||||
sketchPathToNode: PathToNode
|
||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||
draftInfo?: {
|
||||
draftSegment: DraftSegment
|
||||
truncatedAst: Program
|
||||
programMemoryOverride: ProgramMemory
|
||||
variableDeclarationName: string
|
||||
@ -595,7 +681,7 @@ class SceneEntities {
|
||||
const { programMemory } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: engineCommandManager,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
programMemoryOverride,
|
||||
})
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -785,10 +871,10 @@ class SceneEntities {
|
||||
}
|
||||
}
|
||||
async animateAfterSketch() {
|
||||
if (isReducedMotion()) {
|
||||
sceneInfra.camControls.usePerspectiveCamera()
|
||||
return
|
||||
}
|
||||
// if (isReducedMotion()) {
|
||||
// sceneInfra.camControls.usePerspectiveCamera()
|
||||
// return
|
||||
// }
|
||||
await sceneInfra.camControls.animateToPerspective()
|
||||
}
|
||||
removeSketchGrid() {
|
||||
@ -853,26 +939,81 @@ class SceneEntities {
|
||||
const type: DefaultPlane = selected.userData.type
|
||||
selected.material.color = defaultPlaneColor(type)
|
||||
},
|
||||
onClick: (args) => {
|
||||
onClick: async (args) => {
|
||||
const checkExtrudeFaceClick = async (): Promise<boolean> => {
|
||||
const { streamDimensions } = useStore.getState()
|
||||
const { entity_id } = await sendSelectEventToEngine(
|
||||
args?.mouseEvent,
|
||||
document.getElementById('video-stream') as HTMLVideoElement,
|
||||
streamDimensions
|
||||
)
|
||||
if (!entity_id) return false
|
||||
const artifact = this.engineCommandManager.artifactMap[entity_id]
|
||||
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
|
||||
return false
|
||||
const faceInfo: Models['FaceIsPlanar_type'] = (
|
||||
await this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'face_is_planar',
|
||||
object_id: entity_id,
|
||||
},
|
||||
})
|
||||
)?.data?.data
|
||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||
return false
|
||||
const { z_axis, origin, y_axis } = faceInfo
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
artifact.range
|
||||
)
|
||||
|
||||
sceneInfra.modelingSend({
|
||||
type: 'Select default plane',
|
||||
data: {
|
||||
type: 'extrudeFace',
|
||||
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
||||
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
||||
position: [origin.x, origin.y, origin.z].map(
|
||||
(num) => num / sceneInfra._baseUnitMultiplier
|
||||
) as [number, number, number],
|
||||
extrudeSegmentPathToNode: pathToNode,
|
||||
cap:
|
||||
artifact?.additionalData?.type === 'cap'
|
||||
? artifact.additionalData.info
|
||||
: 'none',
|
||||
},
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
if (await checkExtrudeFaceClick()) return
|
||||
|
||||
if (!args || !args.intersects?.[0]) return
|
||||
if (args.mouseEvent.which !== 1) return
|
||||
const { intersects } = args
|
||||
const type = intersects?.[0].object.name || ''
|
||||
const posNorm = Number(intersects?.[0]?.normal?.z) > 0
|
||||
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
|
||||
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
||||
let zAxis: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
||||
let yAxis: [number, number, number] = [0, 1, 0]
|
||||
if (type === YZ_PLANE) {
|
||||
planeString = posNorm ? 'YZ' : '-YZ'
|
||||
normal = posNorm ? [1, 0, 0] : [-1, 0, 0]
|
||||
zAxis = posNorm ? [1, 0, 0] : [-1, 0, 0]
|
||||
yAxis = [0, 0, 1]
|
||||
} else if (type === XZ_PLANE) {
|
||||
planeString = posNorm ? 'XZ' : '-XZ'
|
||||
normal = posNorm ? [0, 1, 0] : [0, -1, 0]
|
||||
zAxis = posNorm ? [0, 1, 0] : [0, -1, 0]
|
||||
yAxis = [0, 0, 1]
|
||||
}
|
||||
sceneInfra.modelingSend({
|
||||
type: 'Select default plane',
|
||||
data: {
|
||||
type: 'defaultPlane',
|
||||
plane: planeString,
|
||||
normal,
|
||||
zAxis,
|
||||
yAxis,
|
||||
},
|
||||
})
|
||||
},
|
||||
@ -882,8 +1023,6 @@ class SceneEntities {
|
||||
|
||||
export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ'
|
||||
|
||||
export const sceneEntitiesManager = new SceneEntities()
|
||||
|
||||
// calculations/pure-functions/easy to test so no excuse not to
|
||||
|
||||
function prepareTruncatedMemoryAndAst(
|
||||
@ -1005,30 +1144,6 @@ export function sketchGroupFromPathToNode({
|
||||
return programMemory.root[varDec?.id?.name || ''] as SketchGroup
|
||||
}
|
||||
|
||||
export function quaternionFromSketchGroup(
|
||||
sketchGroup: SketchGroup
|
||||
): Quaternion {
|
||||
// TODO figure what is happening in the executor that it's some times returning
|
||||
// [x,y,z] and sometimes {x,y,z}
|
||||
if (!sketchGroup?.zAxis) {
|
||||
// sometimes sketchGroup is undefined,
|
||||
// I don't quiet understand the circumstances yet
|
||||
// and it's very intermittent so leaving this here for now
|
||||
console.log('no zAxis', sketchGroup)
|
||||
console.trace('no zAxis')
|
||||
}
|
||||
const zAxisVec = massageFormats(sketchGroup?.zAxis)
|
||||
const yAxisVec = massageFormats(sketchGroup?.yAxis)
|
||||
const xAxisVec = new Vector3().crossVectors(yAxisVec, zAxisVec).normalize()
|
||||
|
||||
let yAxisVecNormalized = yAxisVec.clone().normalize()
|
||||
let zAxisVecNormalized = zAxisVec.clone().normalize()
|
||||
|
||||
let rotationMatrix = new Matrix4()
|
||||
rotationMatrix.makeBasis(xAxisVec, yAxisVecNormalized, zAxisVecNormalized)
|
||||
return new Quaternion().setFromRotationMatrix(rotationMatrix)
|
||||
}
|
||||
|
||||
function colorSegment(object: any, color: number) {
|
||||
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
|
||||
if (segmentHead) {
|
||||
@ -1063,10 +1178,68 @@ export function getSketchQuaternion(
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
const zAxis = sketchGroup?.zAxis || sketchNormalBackUp
|
||||
return getQuaternionFromZAxis(massageFormats(zAxis))
|
||||
}
|
||||
export async function getSketchOrientationDetails(
|
||||
sketchPathToNode: PathToNode
|
||||
): Promise<{
|
||||
quat: Quaternion
|
||||
sketchDetails: SketchDetails
|
||||
}> {
|
||||
const sketchGroup = sketchGroupFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (sketchGroup.on.type === 'plane') {
|
||||
const zAxis = sketchGroup?.zAxis
|
||||
return {
|
||||
quat: getQuaternionFromZAxis(massageFormats(zAxis)),
|
||||
sketchDetails: {
|
||||
sketchPathToNode,
|
||||
zAxis: [zAxis.x, zAxis.y, zAxis.z],
|
||||
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
|
||||
origin: [0, 0, 0],
|
||||
},
|
||||
}
|
||||
}
|
||||
if (sketchGroup.on.type === 'face') {
|
||||
const faceInfo: Models['FaceIsPlanar_type'] = (
|
||||
await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'face_is_planar',
|
||||
object_id: sketchGroup.on.faceId,
|
||||
},
|
||||
})
|
||||
)?.data?.data
|
||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||
throw new Error('faceInfo')
|
||||
const { z_axis, y_axis, origin } = faceInfo
|
||||
const quaternion = quaternionFromUpNForward(
|
||||
new Vector3(y_axis.x, y_axis.y, y_axis.z),
|
||||
new Vector3(z_axis.x, z_axis.y, z_axis.z)
|
||||
)
|
||||
return {
|
||||
quat: quaternion,
|
||||
sketchDetails: {
|
||||
sketchPathToNode,
|
||||
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
||||
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
||||
origin: [origin.x, origin.y, origin.z],
|
||||
},
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
'sketchGroup.on.type not recognized, has a new type been added?'
|
||||
)
|
||||
}
|
||||
|
||||
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
|
||||
const dummyCam = new PerspectiveCamera()
|
||||
dummyCam.up.set(0, 0, 1)
|
||||
const _zAxis = massageFormats(zAxis)
|
||||
dummyCam.position.copy(_zAxis)
|
||||
dummyCam.position.copy(zAxis)
|
||||
dummyCam.lookAt(0, 0, 0)
|
||||
dummyCam.updateMatrix()
|
||||
const quaternion = dummyCam.quaternion.clone()
|
||||
@ -1075,7 +1248,7 @@ export function getSketchQuaternion(
|
||||
|
||||
// because vertical quaternions are a gimbal lock, for the orbit controls
|
||||
// it's best to set them explicitly to the vertical position with a known good camera up
|
||||
if (isVert && _zAxis.z < 0) {
|
||||
if (isVert && zAxis.z < 0) {
|
||||
quaternion.set(0, 1, 0, 0)
|
||||
} else if (isVert) {
|
||||
quaternion.set(0, 0, 0, 1)
|
||||
|
@ -27,6 +27,7 @@ import { Axis } from 'lib/selections'
|
||||
import { type BaseUnit } from 'lib/settings/settingsTypes'
|
||||
import { SETTINGS_PERSIST_KEY } from 'lib/constants'
|
||||
import { CameraControls } from './CameraControls'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
|
||||
type SendType = ReturnType<typeof useModelingContext>['send']
|
||||
|
||||
@ -37,8 +38,10 @@ export const ZOOM_MAGIC_NUMBER = 63.5
|
||||
|
||||
export const INTERSECTION_PLANE_LAYER = 1
|
||||
export const SKETCH_LAYER = 2
|
||||
export const DEBUG_SHOW_INTERSECTION_PLANE = false
|
||||
export const DEBUG_SHOW_BOTH_SCENES = false
|
||||
|
||||
// redundant types so that it can be changed temporarily but CI will catch the wrong type
|
||||
export const DEBUG_SHOW_INTERSECTION_PLANE: false = false
|
||||
export const DEBUG_SHOW_BOTH_SCENES: false = false
|
||||
|
||||
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
||||
export const DEFAULT_PLANES = 'default-planes'
|
||||
@ -84,7 +87,7 @@ interface OnMoveCallbackArgs {
|
||||
// This singleton class is responsible for all of the under the hood setup for the client side scene.
|
||||
// That is the cameras and switching between them, raycasters for click mouse events and their abstractions (onClick etc), setting up controls.
|
||||
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
|
||||
class SceneInfra {
|
||||
export class SceneInfra {
|
||||
static instance: SceneInfra
|
||||
scene: Scene
|
||||
renderer: WebGLRenderer
|
||||
@ -97,13 +100,13 @@ class SceneInfra {
|
||||
_baseUnitMultiplier = 1
|
||||
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
|
||||
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
|
||||
onClickCallback: (arg: OnClickCallbackArgs) => void = () => {}
|
||||
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||
setCallbacks = (callbacks: {
|
||||
onDrag?: (arg: OnDragCallbackArgs) => void
|
||||
onMove?: (arg: OnMoveCallbackArgs) => void
|
||||
onClick?: (arg?: OnClickCallbackArgs) => void
|
||||
onClick?: (arg: OnClickCallbackArgs) => void
|
||||
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
|
||||
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
|
||||
}) => {
|
||||
@ -124,7 +127,7 @@ class SceneInfra {
|
||||
)
|
||||
}
|
||||
resetMouseListeners = () => {
|
||||
sceneInfra.setCallbacks({
|
||||
this.setCallbacks({
|
||||
onDrag: () => {},
|
||||
onMove: () => {},
|
||||
onClick: () => {},
|
||||
@ -153,7 +156,7 @@ class SceneInfra {
|
||||
} | null = null
|
||||
mouseDownVector: null | Vector2 = null
|
||||
|
||||
constructor() {
|
||||
constructor(engineCommandManager: EngineCommandManager) {
|
||||
// SCENE
|
||||
this.scene = new Scene()
|
||||
this.scene.background = new Color(0x000000)
|
||||
@ -176,7 +179,11 @@ class SceneInfra {
|
||||
const x = Math.cos(ang) * length
|
||||
const y = Math.sin(ang) * length
|
||||
|
||||
this.camControls = new CameraControls(false, this.renderer.domElement)
|
||||
this.camControls = new CameraControls(
|
||||
false,
|
||||
this.renderer.domElement,
|
||||
engineCommandManager
|
||||
)
|
||||
this.camControls.subscribeToCamChange(() => this.onCameraChange())
|
||||
this.camControls.camera.layers.enable(SKETCH_LAYER)
|
||||
this.camControls.camera.position.set(0, -x, y)
|
||||
@ -219,9 +226,9 @@ class SceneInfra {
|
||||
?.getObjectByName('gridHelper')
|
||||
planesGroup &&
|
||||
planesGroup.scale.set(
|
||||
scale / sceneInfra._baseUnitMultiplier,
|
||||
scale / sceneInfra._baseUnitMultiplier,
|
||||
scale / sceneInfra._baseUnitMultiplier
|
||||
scale / this._baseUnitMultiplier,
|
||||
scale / this._baseUnitMultiplier,
|
||||
scale / this._baseUnitMultiplier
|
||||
)
|
||||
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
||||
}
|
||||
@ -253,7 +260,7 @@ class SceneInfra {
|
||||
} | null => {
|
||||
this.planeRaycaster.setFromCamera(
|
||||
this.currentMouseVector,
|
||||
sceneInfra.camControls.camera
|
||||
this.camControls.camera
|
||||
)
|
||||
const planeIntersects = this.planeRaycaster.intersectObjects(
|
||||
this.scene.children,
|
||||
@ -272,16 +279,19 @@ class SceneInfra {
|
||||
let transformedPoint = intersectPoint.clone()
|
||||
if (transformedPoint) {
|
||||
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
||||
transformedPoint?.sub(
|
||||
new Vector3(...planePosition).applyQuaternion(inversePlaneQuaternion)
|
||||
)
|
||||
}
|
||||
const twoD = new Vector2(
|
||||
// I think the intersection plane doesn't get scale when nearly everything else does, maybe that should change
|
||||
transformedPoint.x / this._baseUnitMultiplier,
|
||||
transformedPoint.y / this._baseUnitMultiplier
|
||||
) // z should be 0
|
||||
const planePositionCorrected = new Vector3(
|
||||
...planePosition
|
||||
).applyQuaternion(inversePlaneQuaternion)
|
||||
twoD.sub(new Vector2(...planePositionCorrected))
|
||||
|
||||
return {
|
||||
twoD: new Vector2(
|
||||
transformedPoint.x / this._baseUnitMultiplier,
|
||||
transformedPoint.y / this._baseUnitMultiplier
|
||||
), // z should be 0
|
||||
twoD,
|
||||
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
|
||||
intersection: planeIntersects[0],
|
||||
}
|
||||
@ -464,7 +474,7 @@ class SceneInfra {
|
||||
intersects,
|
||||
})
|
||||
} else {
|
||||
this.onClickCallback()
|
||||
this.onClickCallback({ mouseEvent, intersects })
|
||||
}
|
||||
// Clear the selected state whether it was dragged or not
|
||||
this.selected = null
|
||||
@ -478,7 +488,7 @@ class SceneInfra {
|
||||
intersects,
|
||||
})
|
||||
} else {
|
||||
this.onClickCallback()
|
||||
this.onClickCallback({ mouseEvent, intersects })
|
||||
}
|
||||
}
|
||||
showDefaultPlanes() {
|
||||
@ -522,9 +532,9 @@ class SceneInfra {
|
||||
this.camControls.target
|
||||
)
|
||||
planesGroup.scale.set(
|
||||
sceneScale / sceneInfra._baseUnitMultiplier,
|
||||
sceneScale / sceneInfra._baseUnitMultiplier,
|
||||
sceneScale / sceneInfra._baseUnitMultiplier
|
||||
sceneScale / this._baseUnitMultiplier,
|
||||
sceneScale / this._baseUnitMultiplier,
|
||||
sceneScale / this._baseUnitMultiplier
|
||||
)
|
||||
this.scene.add(planesGroup)
|
||||
}
|
||||
@ -535,7 +545,7 @@ class SceneInfra {
|
||||
if (planesGroup) this.scene.remove(planesGroup)
|
||||
}
|
||||
updateOtherSelectionColors = (otherSelections: Axis[]) => {
|
||||
const axisGroup = sceneInfra.scene.children.find(
|
||||
const axisGroup = this.scene.children.find(
|
||||
({ userData }) => userData?.type === AXIS_GROUP
|
||||
)
|
||||
const axisMap: { [key: string]: Axis } = {
|
||||
@ -557,8 +567,6 @@ class SceneInfra {
|
||||
}
|
||||
}
|
||||
|
||||
export const sceneInfra = new SceneInfra()
|
||||
|
||||
export function getSceneScale(
|
||||
camera: PerspectiveCamera | OrthographicCamera,
|
||||
target: Vector3
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useStore } from 'useStore'
|
||||
|
@ -7,8 +7,8 @@ import {
|
||||
findUniqueName,
|
||||
} from '../lang/modifyAst'
|
||||
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { executeAst } from 'useStore'
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { sceneInfra } from '../clientSideScene/sceneInfra'
|
||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||
import { engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||
import { throttle, isReducedMotion } from 'lib/utils'
|
||||
|
||||
const updateDollyZoom = throttle(
|
||||
|
@ -6,7 +6,7 @@ import styles from './CodeMenu.module.css'
|
||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||
import { editorShortcutMeta } from './TextEditor'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||
@ -77,7 +77,7 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||
<Menu.Item>
|
||||
<a
|
||||
className={styles.button}
|
||||
href="https://github.com/KittyCAD/kcl-samples"
|
||||
href="https://zoo.dev/docs/kcl-samples"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useKclContext } from 'lang/KclSingleton'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { CommandArgument } from 'lib/commandTypes'
|
||||
import {
|
||||
ResolvedSelectionType,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { CommandLog, engineCommandManager } from 'lang/std/engineConnection'
|
||||
import { CommandLog } from 'lang/std/engineConnection'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
function useEngineCommands(): [CommandLog[], () => void] {
|
||||
|
@ -13,7 +13,7 @@ import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import styles from './FileTree.module.css'
|
||||
import { FILE_EXT, sortProject } from 'lib/tauriFS'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
||||
import { useLspContext } from './LspProvider'
|
||||
|
||||
|
@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
|
||||
import { useEffect } from 'react'
|
||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||
import { Themes } from '../lib/theme'
|
||||
import { useKclContext } from 'lang/KclSingleton'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
|
||||
const ReactJsonTypeHack = ReactJson as any
|
||||
|
||||
|
@ -38,7 +38,26 @@ describe('processMemory', () => {
|
||||
myVar: 5,
|
||||
myFn: undefined,
|
||||
otherVar: 3,
|
||||
theExtrude: [],
|
||||
theExtrude: [
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [170, 194],
|
||||
},
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [202, 230],
|
||||
},
|
||||
],
|
||||
theSketch: [
|
||||
{ type: 'ToPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
|
||||
{ type: 'ToPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
|
||||
|
@ -3,7 +3,7 @@ import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||
import { useMemo } from 'react'
|
||||
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
|
||||
import { Themes } from '../lib/theme'
|
||||
import { useKclContext } from 'lang/KclSingleton'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
|
||||
interface MemoryPanelProps extends CollapsiblePanelProps {
|
||||
theme?: Exclude<Themes, Themes.System>
|
||||
|
@ -12,8 +12,8 @@ import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||
import { kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||
import {
|
||||
angleBetweenInfo,
|
||||
@ -23,9 +23,9 @@ import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import { pathMapToSelections } from 'lang/util'
|
||||
import { useStore } from 'useStore'
|
||||
import {
|
||||
Selections,
|
||||
canExtrudeSelection,
|
||||
handleSelectionBatch,
|
||||
handleSelectionWithShift,
|
||||
isSelectionLastLine,
|
||||
isSketchPipe,
|
||||
} from 'lib/selections'
|
||||
@ -33,15 +33,20 @@ import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
|
||||
import { startSketchOnDefault } from 'lang/modifyAst'
|
||||
import { Program } from 'lang/wasm'
|
||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import {
|
||||
getSketchOrientationDetails,
|
||||
getSketchQuaternion,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
|
||||
import { Program, parse } from 'lang/wasm'
|
||||
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { TEST } from 'env'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import toast from 'react-hot-toast'
|
||||
import { EditorSelection } from '@uiw/react-codemirror'
|
||||
import { Vector3 } from 'three'
|
||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -61,17 +66,23 @@ export const ModelingMachineProvider = ({
|
||||
const {
|
||||
auth,
|
||||
settings: {
|
||||
context: { baseUnit },
|
||||
context: { baseUnit, theme },
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
const { code } = useKclContext()
|
||||
const token = auth?.context?.token
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useSetupEngineManager(streamRef, token)
|
||||
useSetupEngineManager(streamRef, token, theme)
|
||||
|
||||
const { isShiftDown, editorView } = useStore((s) => ({
|
||||
const {
|
||||
isShiftDown,
|
||||
editorView,
|
||||
setLastCodeMirrorSelectionUpdatedFromScene,
|
||||
} = useStore((s) => ({
|
||||
isShiftDown: s.isShiftDown,
|
||||
editorView: s.editorView,
|
||||
setLastCodeMirrorSelectionUpdatedFromScene:
|
||||
s.setLastCodeMirrorSelectionUpdatedFromScene,
|
||||
}))
|
||||
|
||||
// Settings machine setup
|
||||
@ -92,92 +103,98 @@ export const ModelingMachineProvider = ({
|
||||
{
|
||||
actions: {
|
||||
'sketch exit execute': () => {
|
||||
kclManager.executeAst()
|
||||
try {
|
||||
kclManager.executeAst(parse(kclManager.code))
|
||||
} catch (e) {
|
||||
kclManager.executeAst()
|
||||
}
|
||||
},
|
||||
'Set selection': assign(({ selectionRanges }, event) => {
|
||||
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
||||
const setSelections = event.data
|
||||
if (!editorView) return {}
|
||||
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
|
||||
return { selectionRanges: setSelections.selection }
|
||||
else if (setSelections.selectionType === 'otherSelection') {
|
||||
const {
|
||||
codeMirrorSelection,
|
||||
selectionRangeTypeMap,
|
||||
otherSelections,
|
||||
} = handleSelectionWithShift({
|
||||
otherSelection: setSelections.selection,
|
||||
currentSelections: selectionRanges,
|
||||
isShiftDown,
|
||||
})
|
||||
setTimeout(() => {
|
||||
editorView.dispatch({
|
||||
selection: codeMirrorSelection,
|
||||
})
|
||||
})
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
selectionRanges: {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
otherSelections,
|
||||
},
|
||||
}
|
||||
} else if (setSelections.selectionType === 'singleCodeCursor') {
|
||||
// This DOES NOT set the `selectionRanges` in xstate context
|
||||
// instead it updates/dispatches to the editor, which in turn updates the xstate context
|
||||
// I've found this the best way to deal with the editor without causing an infinite loop
|
||||
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
|
||||
// because we want to respect the user manually placing the cursor too.
|
||||
|
||||
// for more details on how selections see `src/lib/selections.ts`.
|
||||
|
||||
const {
|
||||
codeMirrorSelection,
|
||||
selectionRangeTypeMap,
|
||||
otherSelections,
|
||||
} = handleSelectionWithShift({
|
||||
codeSelection: setSelections.selection,
|
||||
currentSelections: selectionRanges,
|
||||
isShiftDown,
|
||||
})
|
||||
if (codeMirrorSelection) {
|
||||
setTimeout(() => {
|
||||
editorView.dispatch({
|
||||
selection: codeMirrorSelection,
|
||||
})
|
||||
})
|
||||
}
|
||||
if (!setSelections.selection) {
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
selectionRanges: {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
otherSelections,
|
||||
},
|
||||
const dispatchSelection = (selection?: EditorSelection) => {
|
||||
if (!selection) return // TODO less of hack for the below please
|
||||
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
|
||||
setTimeout(() => editorView.dispatch({ selection }))
|
||||
}
|
||||
let selections: Selections = {
|
||||
codeBasedSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
if (setSelections.selectionType === 'singleCodeCursor') {
|
||||
if (!setSelections.selection && isShiftDown) {
|
||||
} else if (!setSelections.selection && !isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && !isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [setSelections.selection],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [
|
||||
...selectionRanges.codeBasedSelections,
|
||||
setSelections.selection,
|
||||
],
|
||||
otherSelections: selectionRanges.otherSelections,
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
engineEvents,
|
||||
codeMirrorSelection,
|
||||
updateSceneObjectColors,
|
||||
} = handleSelectionBatch({
|
||||
selections,
|
||||
})
|
||||
codeMirrorSelection && dispatchSelection(codeMirrorSelection)
|
||||
engineEvents &&
|
||||
engineEvents.forEach((event) =>
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
)
|
||||
updateSceneObjectColors()
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
selectionRanges: {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
otherSelections,
|
||||
},
|
||||
selectionRanges: selections,
|
||||
}
|
||||
}
|
||||
// This DOES NOT set the `selectionRanges` in xstate context
|
||||
// same as comment above
|
||||
const { codeMirrorSelection, selectionRangeTypeMap } =
|
||||
handleSelectionBatch({
|
||||
selections: setSelections.selection,
|
||||
})
|
||||
if (codeMirrorSelection) {
|
||||
setTimeout(() => {
|
||||
editorView.dispatch({
|
||||
selection: codeMirrorSelection,
|
||||
})
|
||||
})
|
||||
|
||||
if (setSelections.selectionType === 'mirrorCodeMirrorSelections') {
|
||||
return {
|
||||
selectionRanges: setSelections.selection,
|
||||
}
|
||||
}
|
||||
return { selectionRangeTypeMap }
|
||||
|
||||
if (setSelections.selectionType === 'otherSelection') {
|
||||
if (isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
} else {
|
||||
selections = {
|
||||
codeBasedSelections: [],
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
}
|
||||
const { engineEvents, updateSceneObjectColors } =
|
||||
handleSelectionBatch({
|
||||
selections,
|
||||
})
|
||||
engineEvents &&
|
||||
engineEvents.forEach((event) =>
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
)
|
||||
updateSceneObjectColors()
|
||||
return {
|
||||
selectionRanges: selections,
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}),
|
||||
'Engine export': (_, event) => {
|
||||
if (event.type !== 'Export' || TEST) return
|
||||
@ -255,10 +272,10 @@ export const ModelingMachineProvider = ({
|
||||
kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0,
|
||||
},
|
||||
services: {
|
||||
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
|
||||
if (!sketchPathToNode) return
|
||||
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
const varDecIndex = sketchPathToNode[1][0]
|
||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||
// remove body item at varDecIndex
|
||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||
await kclManager.executeAstMock(newAst, { updates: 'code' })
|
||||
@ -267,28 +284,69 @@ export const ModelingMachineProvider = ({
|
||||
onDrag: () => {},
|
||||
})
|
||||
},
|
||||
'animate-to-face': async (_, { data: { plane, normal } }) => {
|
||||
'animate-to-face': async (_, { data }) => {
|
||||
if (data.type === 'extrudeFace') {
|
||||
const { modifiedAst, pathToNode: pathToNewSketchNode } =
|
||||
sketchOnExtrudedFace(
|
||||
kclManager.ast,
|
||||
data.extrudeSegmentPathToNode,
|
||||
kclManager.programMemory,
|
||||
data.cap
|
||||
)
|
||||
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||
|
||||
const forward = new Vector3(...data.zAxis)
|
||||
const up = new Vector3(...data.yAxis)
|
||||
|
||||
let target = new Vector3(...data.position).multiplyScalar(
|
||||
sceneInfra._baseUnitMultiplier
|
||||
)
|
||||
const quaternion = quaternionFromUpNForward(up, forward)
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(
|
||||
quaternion,
|
||||
target
|
||||
)
|
||||
|
||||
return {
|
||||
sketchPathToNode: pathToNewSketchNode,
|
||||
zAxis: data.zAxis,
|
||||
yAxis: data.yAxis,
|
||||
origin: data.position,
|
||||
}
|
||||
}
|
||||
const { modifiedAst, pathToNode } = startSketchOnDefault(
|
||||
kclManager.ast,
|
||||
plane
|
||||
data.plane
|
||||
)
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
const quaternion = getSketchQuaternion(pathToNode, normal)
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
|
||||
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
|
||||
return {
|
||||
sketchPathToNode: pathToNode,
|
||||
sketchNormalBackUp: normal,
|
||||
zAxis: data.zAxis,
|
||||
yAxis: data.yAxis,
|
||||
origin: [0, 0, 0],
|
||||
}
|
||||
},
|
||||
'animate-to-sketch': async ({
|
||||
sketchPathToNode,
|
||||
sketchNormalBackUp,
|
||||
}) => {
|
||||
const quaternion = getSketchQuaternion(
|
||||
sketchPathToNode || [],
|
||||
sketchNormalBackUp
|
||||
'animate-to-sketch': async ({ selectionRanges }) => {
|
||||
const sourceRange = selectionRanges.codeBasedSelections[0].range
|
||||
const sketchPathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRange
|
||||
)
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
|
||||
const info = await getSketchOrientationDetails(sketchPathToNode || [])
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(
|
||||
info.quat,
|
||||
new Vector3(...info.sketchDetails.origin)
|
||||
)
|
||||
return {
|
||||
sketchPathToNode: sketchPathToNode || [],
|
||||
zAxis: info.sketchDetails.zAxis || null,
|
||||
yAxis: info.sketchDetails.yAxis || null,
|
||||
origin: info.sketchDetails.origin.map(
|
||||
(a) => a / sceneInfra._baseUnitMultiplier
|
||||
) as [number, number, number],
|
||||
}
|
||||
},
|
||||
'Get horizontal info': async ({
|
||||
selectionRanges,
|
||||
|
@ -5,12 +5,12 @@ import {
|
||||
ConnectingType,
|
||||
ConnectingTypeGroup,
|
||||
DisconnectingType,
|
||||
engineCommandManager,
|
||||
EngineConnectionState,
|
||||
EngineConnectionStateType,
|
||||
ErrorType,
|
||||
initialConnectingTypeGroupState,
|
||||
} from '../lang/std/engineConnection'
|
||||
import { engineCommandManager } from '../lib/singletons'
|
||||
import Tooltip from './Tooltip'
|
||||
|
||||
export enum NetworkHealthState {
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
validateSettings,
|
||||
} from 'lib/settings/settingsUtils'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { setThemeClass, Themes } from 'lib/theme'
|
||||
import { getThemeColorForEngine, setThemeClass, Themes } from 'lib/theme'
|
||||
import {
|
||||
AnyStateMachine,
|
||||
ContextFrom,
|
||||
@ -22,8 +22,8 @@ import {
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
|
||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -96,6 +96,16 @@ export const SettingsAuthProviderBase = ({
|
||||
: context.baseUnit
|
||||
sceneInfra.baseUnit = newBaseUnit
|
||||
},
|
||||
setEngineTheme: (context) => {
|
||||
engineCommandManager.sendSceneCommand({
|
||||
cmd_id: uuidv4(),
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'set_background_color',
|
||||
color: getThemeColorForEngine(context.theme),
|
||||
},
|
||||
})
|
||||
},
|
||||
toastSuccess: (context, event) => {
|
||||
const truncatedNewValue =
|
||||
'data' in event && event.data instanceof Object
|
||||
@ -147,7 +157,6 @@ export const SettingsAuthProviderBase = ({
|
||||
actions: {
|
||||
goToSignInPage: () => {
|
||||
navigate(paths.SIGN_IN)
|
||||
|
||||
logout()
|
||||
},
|
||||
goToIndexPage: () => {
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { useStore } from '../useStore'
|
||||
import { getNormalisedCoordinates } from '../lib/utils'
|
||||
import Loading from './Loading'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useKclContext } from 'lang/KclSingleton'
|
||||
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
|
||||
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
|
||||
import { butName } from 'lib/cameraControls'
|
||||
import { sendSelectEventToEngine } from 'lib/selections'
|
||||
|
||||
export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@ -30,7 +28,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
}))
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state } = useModelingContext()
|
||||
const { isExecuting } = useKclContext()
|
||||
const { overallState } = useNetworkStatus()
|
||||
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
||||
|
||||
@ -60,50 +57,14 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
setClickCoords({ x, y })
|
||||
}
|
||||
|
||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
|
||||
clientX,
|
||||
clientY,
|
||||
ctrlKey,
|
||||
}) => {
|
||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
if (!videoRef.current) return
|
||||
setButtonDownInStream(undefined)
|
||||
if (state.matches('Sketch')) return
|
||||
if (state.matches('Sketch no face')) return
|
||||
const { x, y } = getNormalisedCoordinates({
|
||||
clientX,
|
||||
clientY,
|
||||
el: videoRef.current,
|
||||
...streamDimensions,
|
||||
})
|
||||
|
||||
const newCmdId = uuidv4()
|
||||
const interaction = ctrlKey ? 'pan' : 'rotate'
|
||||
|
||||
const command: Models['WebSocketRequest_type'] = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'camera_drag_end',
|
||||
interaction,
|
||||
window: { x, y },
|
||||
},
|
||||
cmd_id: newCmdId,
|
||||
}
|
||||
|
||||
if (!didDragInStream) {
|
||||
command.cmd = {
|
||||
type: 'select_with_point',
|
||||
selected_at_window: { x, y },
|
||||
selection_type: 'add',
|
||||
}
|
||||
engineCommandManager.sendSceneCommand(command)
|
||||
} else if (didDragInStream) {
|
||||
command.cmd = {
|
||||
type: 'handle_mouse_drag_end',
|
||||
window: { x, y },
|
||||
}
|
||||
void engineCommandManager.sendSceneCommand(command)
|
||||
} else {
|
||||
engineCommandManager.sendSceneCommand(command)
|
||||
if (!didDragInStream && butName(e).left) {
|
||||
sendSelectEventToEngine(e, videoRef.current, streamDimensions)
|
||||
}
|
||||
|
||||
setDidDragInStream(false)
|
||||
@ -140,9 +101,10 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
controls={false}
|
||||
onPlay={() => setIsLoading(false)}
|
||||
onMouseMoveCapture={handleMouseMove}
|
||||
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
||||
className="w-full cursor-pointer h-full"
|
||||
disablePictureInPicture
|
||||
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||
id="video-stream"
|
||||
/>
|
||||
<ClientSideScene cameraControls={settings.context?.cameraControls} />
|
||||
{!isNetworkOkay && !isLoading && (
|
||||
|
@ -3,6 +3,7 @@ import ReactCodeMirror, {
|
||||
Extension,
|
||||
ViewUpdate,
|
||||
keymap,
|
||||
SelectionRange,
|
||||
} from '@uiw/react-codemirror'
|
||||
import { TEST } from 'env'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
@ -19,10 +20,9 @@ import { kclErrToDiagnostic } from 'lang/errors'
|
||||
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import interact from '@replit/codemirror-interact'
|
||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||
import { engineCommandManager, sceneInfra, kclManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useLspContext } from './LspProvider'
|
||||
@ -75,7 +75,7 @@ export const TextEditor = ({
|
||||
})
|
||||
|
||||
const {
|
||||
context: { selectionRanges, selectionRangeTypeMap },
|
||||
context: { selectionRanges },
|
||||
send,
|
||||
state,
|
||||
} = useModelingContext()
|
||||
@ -91,10 +91,27 @@ export const TextEditor = ({
|
||||
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
|
||||
else kclManager.setCode(newCode)
|
||||
} //, []);
|
||||
const lastSelection = useRef('')
|
||||
const onUpdate = (viewUpdate: ViewUpdate) => {
|
||||
if (!editorView) {
|
||||
setEditorView(viewUpdate.view)
|
||||
}
|
||||
const selString = stringifyRanges(
|
||||
viewUpdate?.state?.selection?.ranges || []
|
||||
)
|
||||
if (selString === lastSelection.current) {
|
||||
// onUpdate is noisy and is fired a lot by extensions
|
||||
// since we're only interested in selections changes we can ignore most of these.
|
||||
return
|
||||
}
|
||||
lastSelection.current = selString
|
||||
|
||||
if (
|
||||
// TODO find a less lazy way of getting the last
|
||||
Date.now() - useStore.getState().lastCodeMirrorSelectionUpdatedFromScene <
|
||||
150
|
||||
)
|
||||
return // update triggered by scene selection
|
||||
if (sceneInfra.selected) return // mid drag
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||
'Equip Line tool',
|
||||
@ -104,7 +121,6 @@ export const TextEditor = ({
|
||||
const eventInfo = processCodeMirrorRanges({
|
||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||
selectionRanges,
|
||||
selectionRangeTypeMap,
|
||||
isShiftDown,
|
||||
})
|
||||
if (!eventInfo) return
|
||||
@ -226,3 +242,7 @@ export const TextEditor = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function stringifyRanges(ranges: readonly SelectionRange[]): string {
|
||||
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
getTransformInfos,
|
||||
PathToNodeMap,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
export function equalAngleInfo({
|
||||
selectionRanges,
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
getTransformInfos,
|
||||
PathToNodeMap,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
export function setEqualLengthInfo({
|
||||
selectionRanges,
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
getTransformInfos,
|
||||
transformAstSketchLines,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
export function horzVertInfo(
|
||||
selectionRanges: Selections,
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||
import { createVariableDeclaration } from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
const getModalInfo = createInfoModal(GetInfoModal)
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
getRemoveConstraintsTransforms,
|
||||
transformAstSketchLines,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
export function removeConstrainingValuesInfo({
|
||||
selectionRanges,
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
createVariableDeclaration,
|
||||
} from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
||||
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||
import { createVariableDeclaration } from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
const getModalInfo = createInfoModal(GetInfoModal)
|
||||
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { Selections } from 'lib/selections'
|
||||
|
||||
const getModalInfo = createInfoModal(GetInfoModal)
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
} from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { normaliseAngle } from '../../lib/utils'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
|
||||
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { useState } from 'react'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { useKclContext } from 'lang/KclSingleton'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
|
||||
export function WasmErrBanner() {
|
||||
const [isBannerDismissed, setBannerDismissed] = useState(false)
|
||||
|
@ -33,10 +33,12 @@ export default class Server {
|
||||
this.#fromServer,
|
||||
fileSystemManager
|
||||
)
|
||||
if (!token || token === '') {
|
||||
throw new Error(
|
||||
type_ + ': auth token is required for lsp server to start'
|
||||
)
|
||||
}
|
||||
if (type_ === 'copilot') {
|
||||
if (!token || token === '') {
|
||||
throw new Error('auth token is required for copilot')
|
||||
}
|
||||
await copilotLspRun(config, token)
|
||||
} else if (type_ === 'kcl') {
|
||||
await kclLspRun(config, token || '')
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useStore } from 'useStore'
|
||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { useModelingContext } from './useModelingContext'
|
||||
import { getEventForSelectWithPoint } from 'lib/selections'
|
||||
|
||||
|