Compare commits
18 Commits
kurt-try-c
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
a867e5904f | |||
0e09affb8f | |||
197a47346a | |||
9d083710e0 | |||
afa7c1dc4e | |||
c74b695a71 | |||
d0c244e05e | |||
a315b77f02 | |||
15c854ff18 | |||
acd3a5717d | |||
8a2555550f | |||
62e75c852a | |||
dd3601ea7b | |||
a5e7782d9a | |||
79b0b70688 | |||
1d134c1be0 | |||
1c58572234 | |||
ecee51e82b |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -180,9 +180,7 @@ jobs:
|
|||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
# TODO: re-enable for Windows builds, see https://github.com/tauri-apps/tauri/issues/9045
|
|
||||||
- name: Setup Rust cache
|
- name: Setup Rust cache
|
||||||
if: matrix.os != 'windows-latest'
|
|
||||||
uses: swatinem/rust-cache@v2
|
uses: swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: './src-tauri -> target'
|
workspaces: './src-tauri -> target'
|
||||||
|
18
.github/workflows/playwright.yml
vendored
18
.github/workflows/playwright.yml
vendored
@ -46,12 +46,18 @@ jobs:
|
|||||||
- uses: KittyCAD/action-install-cli@main
|
- uses: KittyCAD/action-install-cli@main
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
- name: Cache Playwright Browsers
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/ms-playwright/
|
||||||
|
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: yarn playwright install --with-deps
|
run: yarn playwright install --with-deps
|
||||||
- name: Download Wasm Cache
|
- name: Download Wasm Cache
|
||||||
id: download-wasm
|
id: download-wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
uses: dawidd6/action-download-artifact@v4
|
uses: dawidd6/action-download-artifact@v5
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
@ -143,12 +149,20 @@ jobs:
|
|||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
- name: Cache Playwright Browsers
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/ms-playwright
|
||||||
|
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-playwright-
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: yarn playwright install --with-deps
|
run: yarn playwright install --with-deps
|
||||||
- name: Download Wasm Cache
|
- name: Download Wasm Cache
|
||||||
id: download-wasm
|
id: download-wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
uses: dawidd6/action-download-artifact@v4
|
uses: dawidd6/action-download-artifact@v5
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -56095,7 +56095,8 @@
|
|||||||
"const part001 = startSketchOn('XY')\n |> startProfileAt([4, 12], %)\n |> line([2, 0], %)\n |> line([0, -6], %)\n |> line([4, -6], %)\n |> line([0, -6], %)\n |> line([-3.75, -4.5], %)\n |> line([0, -5.5], %)\n |> line([-2, 0], %)\n |> close(%)\n |> revolve({ axis: 'y', angle: 180 }, %)",
|
"const part001 = startSketchOn('XY')\n |> startProfileAt([4, 12], %)\n |> line([2, 0], %)\n |> line([0, -6], %)\n |> line([4, -6], %)\n |> line([0, -6], %)\n |> line([-3.75, -4.5], %)\n |> line([0, -5.5], %)\n |> line([-2, 0], %)\n |> close(%)\n |> revolve({ axis: 'y', angle: 180 }, %)",
|
||||||
"const part001 = startSketchOn('XY')\n |> startProfileAt([4, 12], %)\n |> line([2, 0], %)\n |> line([0, -6], %)\n |> line([4, -6], %)\n |> line([0, -6], %)\n |> line([-3.75, -4.5], %)\n |> line([0, -5.5], %)\n |> line([-2, 0], %)\n |> close(%)\n |> revolve({ axis: 'y', angle: 180 }, %)\nconst part002 = startSketchOn(part001, 'end')\n |> startProfileAt([4.5, -5], %)\n |> line([0, 5], %)\n |> line([5, 0], %)\n |> line([0, -5], %)\n |> close(%)\n |> extrude(5, %)",
|
"const part001 = startSketchOn('XY')\n |> startProfileAt([4, 12], %)\n |> line([2, 0], %)\n |> line([0, -6], %)\n |> line([4, -6], %)\n |> line([0, -6], %)\n |> line([-3.75, -4.5], %)\n |> line([0, -5.5], %)\n |> line([-2, 0], %)\n |> close(%)\n |> revolve({ axis: 'y', angle: 180 }, %)\nconst part002 = startSketchOn(part001, 'end')\n |> startProfileAt([4.5, -5], %)\n |> line([0, 5], %)\n |> line([5, 0], %)\n |> line([0, -5], %)\n |> close(%)\n |> extrude(5, %)",
|
||||||
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 20], %)\n |> line([20, 0], %)\n |> line([0, -20], %)\n |> close(%)\n |> extrude(20, %)\n\nconst sketch001 = startSketchOn(box, \"END\")\n |> circle([10, 10], 4, %)\n |> revolve({ angle: -90, axis: 'y' }, %)",
|
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 20], %)\n |> line([20, 0], %)\n |> line([0, -20], %)\n |> close(%)\n |> extrude(20, %)\n\nconst sketch001 = startSketchOn(box, \"END\")\n |> circle([10, 10], 4, %)\n |> revolve({ angle: -90, axis: 'y' }, %)",
|
||||||
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 20], %)\n |> line([20, 0], %)\n |> line([0, -20], %, 'revolveAxis')\n |> close(%)\n |> extrude(20, %)\n\nconst sketch001 = startSketchOn(box, \"END\")\n |> circle([10, 10], 4, %)\n |> revolve({\n angle: 90,\n axis: getOppositeEdge('revolveAxis', box)\n }, %)"
|
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 20], %)\n |> line([20, 0], %)\n |> line([0, -20], %, 'revolveAxis')\n |> close(%)\n |> extrude(20, %)\n\nconst sketch001 = startSketchOn(box, \"END\")\n |> circle([10, 10], 4, %)\n |> revolve({\n angle: 90,\n axis: getOppositeEdge('revolveAxis', box)\n }, %)",
|
||||||
|
"const sketch001 = startSketchOn('XY')\n |> startProfileAt([10, 0], %)\n |> line([5, -5], %)\n |> line([5, 5], %)\n |> lineTo([profileStartX(%), profileStartY(%)], %)\n |> close(%)\n\nconst part001 = revolve({\n axis: {\n custom: {\n axis: [0.0, 1.0, 0.0],\n origin: [0.0, 0.0, 0.0]\n }\n }\n}, sketch001)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -93,7 +93,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
// select a plane
|
// select a plane
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(u.codeLocator).toHaveText(
|
||||||
`const sketch001 = startSketchOn('XZ')`
|
`const sketch001 = startSketchOn('XZ')`
|
||||||
)
|
)
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -102,29 +102,25 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(u.codeLocator).toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(u.codeLocator).toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)`)
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(u.codeLocator).toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1}], %)`)
|
|> line([0, ${commonPoints.num1}], %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(u.codeLocator).toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
@ -154,8 +150,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Constrain' }).click()
|
await page.getByRole('button', { name: 'Constrain' }).click()
|
||||||
await page.getByRole('button', { name: 'Equal Length' }).click()
|
await page.getByRole('button', { name: 'Equal Length' }).click()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(u.codeLocator).toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %, 'seg01')
|
|> line([${commonPoints.num1}, 0], %, 'seg01')
|
||||||
|> line([0, ${commonPoints.num1}], %)
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
@ -1154,189 +1149,352 @@ test('Onboarding redirects and code updating', async ({ page }) => {
|
|||||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Selections work on fresh and edited sketch', async ({ page }) => {
|
test.describe('Testing selections', () => {
|
||||||
// tests mapping works on fresh sketch and edited sketch
|
test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||||
// tests using hovers which is the same as selections, because if
|
// tests mapping works on fresh sketch and edited sketch
|
||||||
// source ranges are wrong, hovers won't work
|
// tests using hovers which is the same as selections, because if
|
||||||
const u = await getUtils(page)
|
// source ranges are wrong, hovers won't work
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.goto('/')
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await page.goto('/')
|
||||||
await u.openDebugPanel()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const xAxisClick = () =>
|
const xAxisClick = () =>
|
||||||
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
|
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
|
||||||
const emptySpaceClick = () =>
|
const emptySpaceClick = () =>
|
||||||
page.mouse.click(700, 343).then(() => page.waitForTimeout(100))
|
page.mouse.click(700, 343).then(() => page.waitForTimeout(100))
|
||||||
const topHorzSegmentClick = () =>
|
const topHorzSegmentClick = () =>
|
||||||
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
|
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
|
||||||
const bottomHorzSegmentClick = () =>
|
const bottomHorzSegmentClick = () =>
|
||||||
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
// select a plane
|
// select a plane
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
await page.waitForTimeout(700) // wait for animation
|
await page.waitForTimeout(700) // wait for animation
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|
||||||
|> line([${commonPoints.num1}, 0], %)`)
|
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|
||||||
|> line([0, ${commonPoints.num1}], %)`)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|
||||||
|> line([0, ${commonPoints.num1}], %)
|
|
||||||
|> line([-${commonPoints.num2}, 0], %)`)
|
|
||||||
|
|
||||||
// deselect line tool
|
|
||||||
await page.getByRole('button', { name: 'Line' }).click()
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
const selectionSequence = async (isSecondTime = false) => {
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.move(
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
startXPx + PUR * 15,
|
|
||||||
isSecondTime ? 430 : 500 - PUR * 10
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|
|> line([0, ${commonPoints.num1}], %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|
|> line([0, ${commonPoints.num1}], %)
|
||||||
|
|> line([-${commonPoints.num2}, 0], %)`)
|
||||||
|
|
||||||
|
// deselect line tool
|
||||||
|
await page.getByRole('button', { name: 'Line' }).click()
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
const selectionSequence = async (isSecondTime = false) => {
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.move(
|
||||||
|
startXPx + PUR * 15,
|
||||||
|
isSecondTime ? 430 : 500 - PUR * 10
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
// bg-yellow-200 is more brittle than hover-highlight, but is closer to the user experience
|
||||||
|
// and will be an easy fix if it breaks because we change the colour
|
||||||
|
await expect(page.locator('.bg-yellow-200')).toBeVisible()
|
||||||
|
|
||||||
|
// check mousing off, than mousing onto another line
|
||||||
|
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
await page.mouse.move(
|
||||||
|
startXPx + PUR * 10,
|
||||||
|
isSecondTime ? 295 : 500 - PUR * 20
|
||||||
|
) // mouse onto another line
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||||
|
|
||||||
|
// now check clicking works including axis
|
||||||
|
|
||||||
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||||
|
await topHorzSegmentClick()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
const constrainButton = page.getByRole('button', { name: 'Constrain' })
|
||||||
|
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
||||||
|
await constrainButton.click()
|
||||||
|
await expect(absYButton).toBeDisabled()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await xAxisClick()
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
|
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
|
// clear selection by clicking on nothing
|
||||||
|
await emptySpaceClick()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// same selection but click the axis first
|
||||||
|
await xAxisClick()
|
||||||
|
await constrainButton.click()
|
||||||
|
await expect(absYButton).toBeDisabled()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await topHorzSegmentClick()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
|
// clear selection by clicking on nothing
|
||||||
|
await emptySpaceClick()
|
||||||
|
|
||||||
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
|
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
|
await expect(absYButton).toBeDisabled()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await xAxisClick()
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
|
// clear selection by clicking on nothing
|
||||||
|
await emptySpaceClick()
|
||||||
|
|
||||||
|
// select segment in editor than another segment in scene and check there are two cursors
|
||||||
|
// TODO change this back to shift click in the scene, not cmd click in the editor
|
||||||
|
await bottomHorzSegmentClick()
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
await selectionSequence()
|
||||||
|
|
||||||
|
// hovering in fresh sketch worked, lets try exiting and re-entering
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// wait for execution done
|
||||||
|
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// select a line, this verifies that sketches in the scene can be selected outside of sketch mode
|
||||||
|
await topHorzSegmentClick()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// enter sketch again
|
||||||
|
await u.doAndWaitForCmd(
|
||||||
|
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||||
|
'default_camera_get_settings'
|
||||||
)
|
)
|
||||||
|
await page.waitForTimeout(150)
|
||||||
|
|
||||||
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
await page.waitForTimeout(300) // wait for animation
|
||||||
// bg-yellow-200 is more brittle than hover-highlight, but is closer to the user experience
|
|
||||||
// and will be an easy fix if it breaks because we change the colour
|
// hover again and check it works
|
||||||
await expect(page.locator('.bg-yellow-200')).toBeVisible()
|
await selectionSequence(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Hovering over 3d features highlights code', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([20, 0], %)
|
||||||
|
|> line([7.13, 4 + 0], %)
|
||||||
|
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
|
||||||
|
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
||||||
|
|> xLineTo(29 + 0, %)
|
||||||
|
|> yLine(-3.14 + 0, %, 'a')
|
||||||
|
|> xLine(1.63, %)
|
||||||
|
|> angledLineOfXLength({ angle: 3 + 0, length: 3.14 }, %)
|
||||||
|
|> angledLineOfYLength({ angle: 30, length: 3 + 0 }, %)
|
||||||
|
|> angledLineToX({ angle: 22.14 + 0, to: 12 }, %)
|
||||||
|
|> angledLineToY({ angle: 30, to: 11.14 }, %)
|
||||||
|
|> angledLineThatIntersects({
|
||||||
|
angle: 3.14,
|
||||||
|
intersectTag: 'a',
|
||||||
|
offset: 0
|
||||||
|
}, %)
|
||||||
|
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}, KCL_DEFAULT_LENGTH)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
vantage: { x: 0, y: -1250, z: 580 },
|
||||||
|
center: { x: 0, y: 0, z: 0 },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
const extrusionTop: Coords2d = [800, 240]
|
||||||
|
const flatExtrusionFace: Coords2d = [960, 160]
|
||||||
|
const arc: Coords2d = [840, 160]
|
||||||
|
const close: Coords2d = [720, 200]
|
||||||
|
const nothing: Coords2d = [600, 200]
|
||||||
|
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await page.mouse.click(nothing[0], nothing[1])
|
||||||
|
|
||||||
// check mousing off, than mousing onto another line
|
|
||||||
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
await page.mouse.move(
|
await page.waitForTimeout(200)
|
||||||
startXPx + PUR * 10,
|
|
||||||
isSecondTime ? 295 : 500 - PUR * 20
|
await page.mouse.move(extrusionTop[0], extrusionTop[1])
|
||||||
) // mouse onto another line
|
|
||||||
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
// now check clicking works including axis
|
await page.mouse.move(arc[0], arc[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
await page.mouse.move(close[0], close[1])
|
||||||
await topHorzSegmentClick()
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
await page.keyboard.down('Shift')
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
const constrainButton = page.getByRole('button', { name: 'Constrain' })
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
|
||||||
await constrainButton.click()
|
await page.mouse.move(flatExtrusionFace[0], flatExtrusionFace[1])
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(page.getByTestId('hover-highlight')).toHaveCount(5) // multiple lines
|
||||||
|
await page.mouse.move(nothing[0], nothing[1])
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
await page.keyboard.up('Shift')
|
})
|
||||||
await constrainButton.click()
|
|
||||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
|
||||||
await expect(absYButton).not.toBeDisabled()
|
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
test('Testing selections (and hovers) work on sketches when NOT in sketch mode', async ({
|
||||||
await emptySpaceClick()
|
page,
|
||||||
|
}) => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
pos: [694, 185],
|
||||||
|
expectedCode: "line([74.36, 130.4], %, 'seg01')",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pos: [816, 244],
|
||||||
|
expectedCode: "angledLine([segAng('seg01', %), yo], %)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pos: [1107, 161],
|
||||||
|
expectedCode: 'tangentialArcTo([167.95, -28.85], %)',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ cases }) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 79
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> ${cases[0].expectedCode}
|
||||||
|
|> line([-3.19, -138.43], %)
|
||||||
|
|> ${cases[1].expectedCode}
|
||||||
|
|> line([41.19, 28.97 + 5], %)
|
||||||
|
|> ${cases[2].expectedCode}`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ cases }
|
||||||
|
)
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await u.sendCustomCmd({
|
||||||
// same selection but click the axis first
|
type: 'modeling_cmd_req',
|
||||||
await xAxisClick()
|
cmd_id: uuidv4(),
|
||||||
await constrainButton.click()
|
cmd: {
|
||||||
await expect(absYButton).toBeDisabled()
|
type: 'default_camera_look_at',
|
||||||
await page.keyboard.down('Shift')
|
vantage: { x: -449, y: -7503, z: 99 },
|
||||||
await page.waitForTimeout(100)
|
center: { x: -449, y: 0, z: 99 },
|
||||||
await topHorzSegmentClick()
|
up: { x: 0, y: 0, z: 1 },
|
||||||
await page.waitForTimeout(100)
|
},
|
||||||
|
})
|
||||||
|
await u.waitForCmdReceive('default_camera_look_at')
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
await page.keyboard.up('Shift')
|
// end setup, now test hover and selects
|
||||||
await constrainButton.click()
|
for (const { pos, expectedCode } of cases) {
|
||||||
await expect(absYButton).not.toBeDisabled()
|
// hover over segment, check it's content
|
||||||
|
await page.mouse.move(pos[0], pos[1], { steps: 5 })
|
||||||
// clear selection by clicking on nothing
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||||
await emptySpaceClick()
|
await expect(page.getByTestId('hover-highlight').first()).toHaveText(
|
||||||
|
expectedCode
|
||||||
// check the same selection again by putting cursor in code first then selecting axis
|
)
|
||||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
// hover over segment, click it and check the cursor has move to the right place
|
||||||
await page.keyboard.down('Shift')
|
await page.mouse.click(pos[0], pos[1])
|
||||||
await constrainButton.click()
|
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
await expect(absYButton).toBeDisabled()
|
'|> ' + expectedCode
|
||||||
await page.waitForTimeout(100)
|
)
|
||||||
await xAxisClick()
|
}
|
||||||
await page.keyboard.up('Shift')
|
})
|
||||||
await constrainButton.click()
|
|
||||||
await expect(absYButton).not.toBeDisabled()
|
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
|
||||||
await emptySpaceClick()
|
|
||||||
|
|
||||||
// select segment in editor than another segment in scene and check there are two cursors
|
|
||||||
// TODO change this back to shift click in the scene, not cmd click in the editor
|
|
||||||
await bottomHorzSegmentClick()
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
await selectionSequence()
|
|
||||||
|
|
||||||
// hovering in fresh sketch worked, lets try exiting and re-entering
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
// wait for execution done
|
|
||||||
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// select a line
|
|
||||||
// await topHorzSegmentClick()
|
|
||||||
await page.getByText(commonPoints.startAt).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// enter sketch again
|
|
||||||
await u.doAndWaitForCmd(
|
|
||||||
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
|
||||||
'default_camera_get_settings'
|
|
||||||
)
|
|
||||||
await page.waitForTimeout(150)
|
|
||||||
|
|
||||||
await page.waitForTimeout(300) // wait for animation
|
|
||||||
|
|
||||||
// hover again and check it works
|
|
||||||
await selectionSequence(true)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Command bar tests', () => {
|
test.describe('Command bar tests', () => {
|
||||||
@ -1533,7 +1691,7 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||||
const { toSU, click00r, expectCodeToBe } = getMovementUtils({ center, page })
|
const { toSU, click00r } = getMovementUtils({ center, page })
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -1550,25 +1708,25 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
let codeStr = "const sketch001 = startSketchOn('XY')"
|
let codeStr = "const sketch001 = startSketchOn('XY')"
|
||||||
|
|
||||||
await page.mouse.click(center.x, viewportSize.height * 0.55)
|
await page.mouse.click(center.x, viewportSize.height * 0.55)
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
await click00r(0, 0)
|
await click00r(0, 0)
|
||||||
codeStr += ` |> startProfileAt(${toSU([0, 0])}, %)`
|
codeStr += ` |> startProfileAt(${toSU([0, 0])}, %)`
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(50, 0)
|
await click00r(50, 0)
|
||||||
codeStr += ` |> line(${toSU([50, 0])}, %)`
|
codeStr += ` |> line(${toSU([50, 0])}, %)`
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(0, 50)
|
await click00r(0, 50)
|
||||||
codeStr += ` |> line(${toSU([0, 50])}, %)`
|
codeStr += ` |> line(${toSU([0, 50])}, %)`
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(-50, 0)
|
await click00r(-50, 0)
|
||||||
codeStr += ` |> line(${toSU([-50, 0])}, %)`
|
codeStr += ` |> line(${toSU([-50, 0])}, %)`
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
// exit the sketch, reset relative clicker
|
// exit the sketch, reset relative clicker
|
||||||
click00r(undefined, undefined)
|
click00r(undefined, undefined)
|
||||||
@ -1586,24 +1744,24 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
await page.mouse.click(center.x + 30, center.y)
|
await page.mouse.click(center.x + 30, center.y)
|
||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
codeStr += "const sketch002 = startSketchOn('XY')"
|
codeStr += "const sketch002 = startSketchOn('XY')"
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await click00r(30, 0)
|
await click00r(30, 0)
|
||||||
codeStr += ` |> startProfileAt(${toSU([30, 0])}, %)`
|
codeStr += ` |> startProfileAt(${toSU([30, 0])}, %)`
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(30, 0)
|
await click00r(30, 0)
|
||||||
codeStr += ` |> line(${toSU([30 - 0.1 /* imprecision */, 0])}, %)`
|
codeStr += ` |> line(${toSU([30 + 0.1 /* imprecision */, 0])}, %)`
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(0, 30)
|
await click00r(0, 30)
|
||||||
codeStr += ` |> line(${toSU([0, 30])}, %)`
|
codeStr += ` |> line(${toSU([0, 30])}, %)`
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(-30, 0)
|
await click00r(-30, 0)
|
||||||
codeStr += ` |> line(${toSU([-30 + 0.1, 0])}, %)`
|
codeStr += ` |> line(${toSU([-30 - 0.1, 0])}, %)`
|
||||||
await expectCodeToBe(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
click00r(undefined, undefined)
|
click00r(undefined, undefined)
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
@ -1653,98 +1811,6 @@ test('ProgramMemory can be serialised', async ({ page }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Hovering over 3d features highlights code', async ({ page }) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'persistCode',
|
|
||||||
`const part001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([20, 0], %)
|
|
||||||
|> line([7.13, 4 + 0], %)
|
|
||||||
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
|
|
||||||
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
|
||||||
|> xLineTo(29 + 0, %)
|
|
||||||
|> yLine(-3.14 + 0, %, 'a')
|
|
||||||
|> xLine(1.63, %)
|
|
||||||
|> angledLineOfXLength({ angle: 3 + 0, length: 3.14 }, %)
|
|
||||||
|> angledLineOfYLength({ angle: 30, length: 3 + 0 }, %)
|
|
||||||
|> angledLineToX({ angle: 22.14 + 0, to: 12 }, %)
|
|
||||||
|> angledLineToY({ angle: 30, to: 11.14 }, %)
|
|
||||||
|> angledLineThatIntersects({
|
|
||||||
angle: 3.14,
|
|
||||||
intersectTag: 'a',
|
|
||||||
offset: 0
|
|
||||||
}, %)
|
|
||||||
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
|
||||||
|> close(%)
|
|
||||||
|> extrude(5 + 7, %)
|
|
||||||
`
|
|
||||||
)
|
|
||||||
}, KCL_DEFAULT_LENGTH)
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
// wait for execution done
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await u.sendCustomCmd({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
vantage: { x: 0, y: -1250, z: 580 },
|
|
||||||
center: { x: 0, y: 0, z: 0 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await u.sendCustomCmd({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_get_settings',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
const extrusionTop: Coords2d = [800, 240]
|
|
||||||
const flatExtrusionFace: Coords2d = [960, 160]
|
|
||||||
const arc: Coords2d = [840, 160]
|
|
||||||
const close: Coords2d = [720, 200]
|
|
||||||
const nothing: Coords2d = [600, 200]
|
|
||||||
|
|
||||||
await page.mouse.move(nothing[0], nothing[1])
|
|
||||||
await page.mouse.click(nothing[0], nothing[1])
|
|
||||||
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
await page.mouse.move(extrusionTop[0], extrusionTop[1])
|
|
||||||
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
|
||||||
await page.mouse.move(nothing[0], nothing[1])
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
|
|
||||||
await page.mouse.move(arc[0], arc[1])
|
|
||||||
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
|
||||||
await page.mouse.move(nothing[0], nothing[1])
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
|
|
||||||
await page.mouse.move(close[0], close[1])
|
|
||||||
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
|
||||||
await page.mouse.move(nothing[0], nothing[1])
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
|
|
||||||
await page.mouse.move(flatExtrusionFace[0], flatExtrusionFace[1])
|
|
||||||
await expect(page.getByTestId('hover-highlight')).toHaveCount(5) // multiple lines
|
|
||||||
await page.mouse.move(nothing[0], nothing[1])
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@ -4781,6 +4847,159 @@ test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
|
|||||||
).not.toBeVisible()
|
).not.toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test.describe('Testing Gizmo', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
testDescription: 'top view',
|
||||||
|
clickPosition: { x: 951, y: 385 },
|
||||||
|
expectedCameraPosition: { x: 800, y: -152, z: 4886.02 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: 'bottom view',
|
||||||
|
clickPosition: { x: 951, y: 429 },
|
||||||
|
expectedCameraPosition: { x: 800, y: -152, z: -4834.02 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: '+x view',
|
||||||
|
clickPosition: { x: 929, y: 417 },
|
||||||
|
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: '-x view',
|
||||||
|
clickPosition: { x: 974, y: 397 },
|
||||||
|
expectedCameraPosition: { x: -4060.02, y: -152, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: '+y view',
|
||||||
|
clickPosition: { x: 967, y: 421 },
|
||||||
|
expectedCameraPosition: { x: 800, y: 4708.02, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: '-y view',
|
||||||
|
clickPosition: { x: 935, y: 393 },
|
||||||
|
expectedCameraPosition: { x: 800, y: -5012.02, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const {
|
||||||
|
clickPosition,
|
||||||
|
expectedCameraPosition,
|
||||||
|
expectedCameraTarget,
|
||||||
|
testDescription,
|
||||||
|
} of cases) {
|
||||||
|
test(`check ${testDescription}`, async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([20, 0], %)
|
||||||
|
|> line([7.13, 4 + 0], %)
|
||||||
|
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
|
||||||
|
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
||||||
|
|> xLineTo(29 + 0, %)
|
||||||
|
|> yLine(-3.14 + 0, %, 'a')
|
||||||
|
|> xLine(1.63, %)
|
||||||
|
|> angledLineOfXLength({ angle: 3 + 0, length: 3.14 }, %)
|
||||||
|
|> angledLineOfYLength({ angle: 30, length: 3 + 0 }, %)
|
||||||
|
|> angledLineToX({ angle: 22.14 + 0, to: 12 }, %)
|
||||||
|
|> angledLineToY({ angle: 30, to: 11.14 }, %)
|
||||||
|
|> angledLineThatIntersects({
|
||||||
|
angle: 3.14,
|
||||||
|
intersectTag: 'a',
|
||||||
|
offset: 0
|
||||||
|
}, %)
|
||||||
|
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}, KCL_DEFAULT_LENGTH)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
vantage: {
|
||||||
|
x: 3000,
|
||||||
|
y: 3000,
|
||||||
|
z: 3000,
|
||||||
|
},
|
||||||
|
center: {
|
||||||
|
x: 800,
|
||||||
|
y: -152,
|
||||||
|
z: 26,
|
||||||
|
},
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await u.waitForCmdReceive('default_camera_get_settings')
|
||||||
|
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
await page.mouse.move(clickPosition.x, clickPosition.y)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.mouse.click(clickPosition.x, clickPosition.y)
|
||||||
|
await u.waitForCmdReceive('default_camera_look_at')
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await u.waitForCmdReceive('default_camera_get_settings')
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
// position
|
||||||
|
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||||
|
expectedCameraPosition.x.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||||
|
expectedCameraPosition.y.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||||
|
expectedCameraPosition.z.toString()
|
||||||
|
),
|
||||||
|
// target
|
||||||
|
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||||
|
expectedCameraTarget.x.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||||
|
expectedCameraTarget.y.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||||
|
expectedCameraTarget.z.toString()
|
||||||
|
),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test('Successful export shows a success toast', async ({ page }) => {
|
test('Successful export shows a success toast', async ({ page }) => {
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
// And you will need to have the KittyCAD CLI installed
|
// And you will need to have the KittyCAD CLI installed
|
||||||
|
@ -162,12 +162,7 @@ export const getMovementUtils = (opts: any) => {
|
|||||||
return ret.then(() => [last.x, last.y])
|
return ret.then(() => [last.x, last.y])
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectCodeToBe = async (str: string) => {
|
return { toSU, click00r }
|
||||||
await expect(opts.page.locator('.cm-content')).toHaveText(str)
|
|
||||||
await opts.page.waitForTimeout(100)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { toSU, click00r, expectCodeToBe }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUtils(page: Page) {
|
export async function getUtils(page: Page) {
|
||||||
@ -228,6 +223,7 @@ export async function getUtils(page: Page) {
|
|||||||
.locator(locator)
|
.locator(locator)
|
||||||
.boundingBox()
|
.boundingBox()
|
||||||
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
||||||
|
codeLocator: page.locator('.cm-content'),
|
||||||
doAndWaitForCmd: async (
|
doAndWaitForCmd: async (
|
||||||
fn: () => Promise<void>,
|
fn: () => Promise<void>,
|
||||||
commandType: string,
|
commandType: string,
|
||||||
|
@ -152,6 +152,7 @@ describe('ZMA (Tauri)', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('signs out', async () => {
|
it('signs out', async () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
const menuButton = await $('[data-testid="user-sidebar-toggle"]')
|
const menuButton = await $('[data-testid="user-sidebar-toggle"]')
|
||||||
await click(menuButton)
|
await click(menuButton)
|
||||||
const signoutButton = await $('[data-testid="user-sidebar-sign-out"]')
|
const signoutButton = await $('[data-testid="user-sidebar-sign-out"]')
|
||||||
|
@ -48,12 +48,14 @@ export type ReactCameraProperties =
|
|||||||
type: 'perspective'
|
type: 'perspective'
|
||||||
fov?: number
|
fov?: number
|
||||||
position: [number, number, number]
|
position: [number, number, number]
|
||||||
|
target: [number, number, number]
|
||||||
quaternion: [number, number, number, number]
|
quaternion: [number, number, number, number]
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'orthographic'
|
type: 'orthographic'
|
||||||
zoom?: number
|
zoom?: number
|
||||||
position: [number, number, number]
|
position: [number, number, number]
|
||||||
|
target: [number, number, number]
|
||||||
quaternion: [number, number, number, number]
|
quaternion: [number, number, number, number]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -773,6 +775,75 @@ export class CameraControls {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateCameraToAxis(
|
||||||
|
axis: 'x' | 'y' | 'z' | '-x' | '-y' | '-z'
|
||||||
|
): Promise<void> {
|
||||||
|
const distance = this.camera.position.distanceTo(this.target)
|
||||||
|
|
||||||
|
const vantage = this.target.clone()
|
||||||
|
let up = { x: 0, y: 0, z: 1 }
|
||||||
|
|
||||||
|
if (axis === 'x') {
|
||||||
|
vantage.x += distance
|
||||||
|
} else if (axis === 'y') {
|
||||||
|
vantage.y += distance
|
||||||
|
} else if (axis === 'z') {
|
||||||
|
vantage.z += distance
|
||||||
|
up = { x: -1, y: 0, z: 0 }
|
||||||
|
} else if (axis === '-x') {
|
||||||
|
vantage.x -= distance
|
||||||
|
} else if (axis === '-y') {
|
||||||
|
vantage.y -= distance
|
||||||
|
} else if (axis === '-z') {
|
||||||
|
vantage.z -= distance
|
||||||
|
up = { x: -1, y: 0, z: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
center: this.target,
|
||||||
|
vantage: vantage,
|
||||||
|
up: up,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetCameraPosition(): Promise<void> {
|
||||||
|
await this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
center: this.target,
|
||||||
|
vantage: {
|
||||||
|
x: this.target.x,
|
||||||
|
y: this.target.y - 128,
|
||||||
|
z: this.target.z + 64,
|
||||||
|
},
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'zoom_to_fit',
|
||||||
|
object_ids: [], // leave empty to zoom to all objects
|
||||||
|
padding: 0.2, // padding around the objects
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async tweenCameraToQuaternion(
|
async tweenCameraToQuaternion(
|
||||||
targetQuaternion: Quaternion,
|
targetQuaternion: Quaternion,
|
||||||
targetPosition = new Vector3(),
|
targetPosition = new Vector3(),
|
||||||
@ -957,6 +1028,11 @@ export class CameraControls {
|
|||||||
roundOff(this.camera.position.y, 2),
|
roundOff(this.camera.position.y, 2),
|
||||||
roundOff(this.camera.position.z, 2),
|
roundOff(this.camera.position.z, 2),
|
||||||
],
|
],
|
||||||
|
target: [
|
||||||
|
roundOff(this.target.x, 2),
|
||||||
|
roundOff(this.target.y, 2),
|
||||||
|
roundOff(this.target.z, 2),
|
||||||
|
],
|
||||||
quaternion: [
|
quaternion: [
|
||||||
roundOff(this.camera.quaternion.x, 2),
|
roundOff(this.camera.quaternion.x, 2),
|
||||||
roundOff(this.camera.quaternion.y, 2),
|
roundOff(this.camera.quaternion.y, 2),
|
||||||
|
@ -699,6 +699,15 @@ export const CamDebugSettings = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
sceneInfra.camControls.resetCameraPosition()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset Camera Position
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{camSettings.type === 'perspective' && (
|
{camSettings.type === 'perspective' && (
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
@ -816,6 +825,71 @@ export const CamDebugSettings = () => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
target
|
||||||
|
<ul className="flex">
|
||||||
|
<li>
|
||||||
|
<span className="pl-2 pr-1">x:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step={5}
|
||||||
|
data-testid="cam-x-target"
|
||||||
|
value={camSettings.target[0]}
|
||||||
|
className="text-black w-16"
|
||||||
|
onChange={(e) => {
|
||||||
|
sceneInfra.camControls.setCam({
|
||||||
|
...camSettings,
|
||||||
|
target: [
|
||||||
|
parseFloat(e.target.value),
|
||||||
|
camSettings.target[1],
|
||||||
|
camSettings.target[2],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="pl-2 pr-1">y:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step={5}
|
||||||
|
data-testid="cam-y-target"
|
||||||
|
value={camSettings.target[1]}
|
||||||
|
className="text-black w-16"
|
||||||
|
onChange={(e) => {
|
||||||
|
sceneInfra.camControls.setCam({
|
||||||
|
...camSettings,
|
||||||
|
target: [
|
||||||
|
camSettings.target[0],
|
||||||
|
parseFloat(e.target.value),
|
||||||
|
camSettings.target[2],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="pl-2 pr-1">z:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step={5}
|
||||||
|
data-testid="cam-z-target"
|
||||||
|
value={camSettings.target[2]}
|
||||||
|
className="text-black w-16"
|
||||||
|
onChange={(e) => {
|
||||||
|
sceneInfra.camControls.setCam({
|
||||||
|
...camSettings,
|
||||||
|
target: [
|
||||||
|
camSettings.target[0],
|
||||||
|
camSettings.target[1],
|
||||||
|
parseFloat(e.target.value),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -51,14 +51,6 @@ function CommandBarSelectionInput({
|
|||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
}, [selection, inputRef])
|
}, [selection, inputRef])
|
||||||
|
|
||||||
// Exit engine's edit mode when this input step is active,
|
|
||||||
// and re-enter it when it's not.
|
|
||||||
// In future the engine's edit mode will go away and this will be handled differently.
|
|
||||||
useEffect(() => {
|
|
||||||
kclManager.exitEditMode()
|
|
||||||
return () => kclManager.defaultSelectionFilter()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Fast-forward through this arg if it's marked as skippable
|
// Fast-forward through this arg if it's marked as skippable
|
||||||
// and we have a valid selection already
|
// and we have a valid selection already
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
import { sceneInfra } from 'lib/singletons'
|
import { sceneInfra } from 'lib/singletons'
|
||||||
import { useEffect, useRef } from 'react'
|
import { MutableRefObject, useEffect, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
WebGLRenderer,
|
WebGLRenderer,
|
||||||
Scene,
|
Scene,
|
||||||
@ -12,21 +13,37 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
Quaternion,
|
Quaternion,
|
||||||
ColorRepresentation,
|
ColorRepresentation,
|
||||||
|
Vector2,
|
||||||
|
Raycaster,
|
||||||
|
Camera,
|
||||||
|
Intersection,
|
||||||
|
Object3D,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
|
|
||||||
const CANVAS_SIZE = 80
|
const CANVAS_SIZE = 80
|
||||||
const FRUSTUM_SIZE = 0.5
|
const FRUSTUM_SIZE = 0.5
|
||||||
const AXIS_LENGTH = 0.35
|
const AXIS_LENGTH = 0.35
|
||||||
const AXIS_WIDTH = 0.02
|
const AXIS_WIDTH = 0.02
|
||||||
const AXIS_COLORS = {
|
enum AxisColors {
|
||||||
x: '#fa6668',
|
X = '#fa6668',
|
||||||
y: '#11eb6b',
|
Y = '#11eb6b',
|
||||||
z: '#6689ef',
|
Z = '#6689ef',
|
||||||
gray: '#c6c7c2',
|
Gray = '#c6c7c2',
|
||||||
|
}
|
||||||
|
enum AxisNames {
|
||||||
|
X = 'x',
|
||||||
|
Y = 'y',
|
||||||
|
Z = 'z',
|
||||||
|
NEG_X = '-x',
|
||||||
|
NEG_Y = '-y',
|
||||||
|
NEG_Z = '-z',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Gizmo() {
|
export default function Gizmo() {
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||||
|
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
||||||
|
const cameraPassiveUpdateTimer = useRef(0)
|
||||||
|
const raycasterPassiveUpdateTimer = useRef(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvasRef.current) return
|
if (!canvasRef.current) return
|
||||||
@ -41,24 +58,44 @@ export default function Gizmo() {
|
|||||||
const { gizmoAxes, gizmoAxisHeads } = createGizmo()
|
const { gizmoAxes, gizmoAxisHeads } = createGizmo()
|
||||||
scene.add(...gizmoAxes, ...gizmoAxisHeads)
|
scene.add(...gizmoAxes, ...gizmoAxisHeads)
|
||||||
|
|
||||||
|
const raycaster = new Raycaster()
|
||||||
|
const { mouse, disposeMouseEvents } = initializeMouseEvents(
|
||||||
|
canvas,
|
||||||
|
raycasterIntersect,
|
||||||
|
sceneInfra
|
||||||
|
)
|
||||||
|
const raycasterObjects = [...gizmoAxisHeads]
|
||||||
|
|
||||||
const clock = new Clock()
|
const clock = new Clock()
|
||||||
const clientCamera = sceneInfra.camControls.camera
|
const clientCamera = sceneInfra.camControls.camera
|
||||||
let currentQuaternion = new Quaternion().copy(clientCamera.quaternion)
|
let currentQuaternion = new Quaternion().copy(clientCamera.quaternion)
|
||||||
|
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
requestAnimationFrame(animate)
|
const delta = clock.getDelta()
|
||||||
updateCameraOrientation(
|
updateCameraOrientation(
|
||||||
camera,
|
camera,
|
||||||
currentQuaternion,
|
currentQuaternion,
|
||||||
sceneInfra.camControls.camera.quaternion,
|
sceneInfra.camControls.camera.quaternion,
|
||||||
clock.getDelta()
|
delta,
|
||||||
|
cameraPassiveUpdateTimer
|
||||||
|
)
|
||||||
|
updateRayCaster(
|
||||||
|
raycasterObjects,
|
||||||
|
raycaster,
|
||||||
|
mouse,
|
||||||
|
camera,
|
||||||
|
raycasterIntersect,
|
||||||
|
delta,
|
||||||
|
raycasterPassiveUpdateTimer
|
||||||
)
|
)
|
||||||
renderer.render(scene, camera)
|
renderer.render(scene, camera)
|
||||||
|
requestAnimationFrame(animate)
|
||||||
}
|
}
|
||||||
animate()
|
animate()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
renderer.dispose()
|
renderer.dispose()
|
||||||
|
disposeMouseEvents()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -69,7 +106,7 @@ export default function Gizmo() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createCamera = () => {
|
const createCamera = (): OrthographicCamera => {
|
||||||
return new OrthographicCamera(
|
return new OrthographicCamera(
|
||||||
-FRUSTUM_SIZE,
|
-FRUSTUM_SIZE,
|
||||||
FRUSTUM_SIZE,
|
FRUSTUM_SIZE,
|
||||||
@ -82,21 +119,21 @@ const createCamera = () => {
|
|||||||
|
|
||||||
const createGizmo = () => {
|
const createGizmo = () => {
|
||||||
const gizmoAxes = [
|
const gizmoAxes = [
|
||||||
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.x, 0, 'z'),
|
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.X, 0, 'z'),
|
||||||
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.y, Math.PI / 2, 'z'),
|
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Y, Math.PI / 2, 'z'),
|
||||||
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.z, -Math.PI / 2, 'y'),
|
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Z, -Math.PI / 2, 'y'),
|
||||||
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.gray, Math.PI, 'z'),
|
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Gray, Math.PI, 'z'),
|
||||||
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.gray, -Math.PI / 2, 'z'),
|
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Gray, -Math.PI / 2, 'z'),
|
||||||
createAxis(AXIS_LENGTH, AXIS_WIDTH, AXIS_COLORS.gray, Math.PI / 2, 'y'),
|
createAxis(AXIS_LENGTH, AXIS_WIDTH, AxisColors.Gray, Math.PI / 2, 'y'),
|
||||||
]
|
]
|
||||||
|
|
||||||
const gizmoAxisHeads = [
|
const gizmoAxisHeads = [
|
||||||
createAxisHead(AXIS_LENGTH, AXIS_COLORS.x, 0, 'z'),
|
createAxisHead(AxisNames.X, AxisColors.X, [AXIS_LENGTH, 0, 0]),
|
||||||
createAxisHead(AXIS_LENGTH, AXIS_COLORS.y, Math.PI / 2, 'z'),
|
createAxisHead(AxisNames.Y, AxisColors.Y, [0, AXIS_LENGTH, 0]),
|
||||||
createAxisHead(AXIS_LENGTH, AXIS_COLORS.z, -Math.PI / 2, 'y'),
|
createAxisHead(AxisNames.Z, AxisColors.Z, [0, 0, AXIS_LENGTH]),
|
||||||
createAxisHead(AXIS_LENGTH, AXIS_COLORS.gray, Math.PI, 'z'),
|
createAxisHead(AxisNames.NEG_X, AxisColors.Gray, [-AXIS_LENGTH, 0, 0]),
|
||||||
createAxisHead(AXIS_LENGTH, AXIS_COLORS.gray, -Math.PI / 2, 'z'),
|
createAxisHead(AxisNames.NEG_Y, AxisColors.Gray, [0, -AXIS_LENGTH, 0]),
|
||||||
createAxisHead(AXIS_LENGTH, AXIS_COLORS.gray, Math.PI / 2, 'y'),
|
createAxisHead(AxisNames.NEG_Z, AxisColors.Gray, [0, 0, -AXIS_LENGTH]),
|
||||||
]
|
]
|
||||||
|
|
||||||
return { gizmoAxes, gizmoAxisHeads }
|
return { gizmoAxes, gizmoAxisHeads }
|
||||||
@ -108,12 +145,9 @@ const createAxis = (
|
|||||||
color: ColorRepresentation,
|
color: ColorRepresentation,
|
||||||
rotation = 0,
|
rotation = 0,
|
||||||
axis = 'x'
|
axis = 'x'
|
||||||
) => {
|
): Mesh => {
|
||||||
const geometry = new BoxGeometry(length, width, width).translate(
|
const geometry = new BoxGeometry(length, width, width)
|
||||||
length / 2,
|
geometry.translate(length / 2, 0, 0)
|
||||||
0,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
const material = new MeshBasicMaterial({ color: new Color(color) })
|
const material = new MeshBasicMaterial({ color: new Color(color) })
|
||||||
const mesh = new Mesh(geometry, material)
|
const mesh = new Mesh(geometry, material)
|
||||||
mesh.rotation[axis as 'x' | 'y' | 'z'] = rotation
|
mesh.rotation[axis as 'x' | 'y' | 'z'] = rotation
|
||||||
@ -121,15 +155,17 @@ const createAxis = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createAxisHead = (
|
const createAxisHead = (
|
||||||
length: number,
|
name: AxisNames,
|
||||||
color: ColorRepresentation,
|
color: ColorRepresentation,
|
||||||
rotation = 0,
|
position: number[]
|
||||||
axis = 'x'
|
): Mesh => {
|
||||||
) => {
|
const geometry = new SphereGeometry(0.065, 16, 8)
|
||||||
const geometry = new SphereGeometry(0.065, 16, 8).translate(length, 0, 0)
|
|
||||||
const material = new MeshBasicMaterial({ color: new Color(color) })
|
const material = new MeshBasicMaterial({ color: new Color(color) })
|
||||||
const mesh = new Mesh(geometry, material)
|
const mesh = new Mesh(geometry, material)
|
||||||
mesh.rotation[axis as 'x' | 'y' | 'z'] = rotation
|
|
||||||
|
mesh.position.set(position[0], position[1], position[2])
|
||||||
|
mesh.updateMatrixWorld()
|
||||||
|
mesh.name = name
|
||||||
return mesh
|
return mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,10 +173,97 @@ const updateCameraOrientation = (
|
|||||||
camera: OrthographicCamera,
|
camera: OrthographicCamera,
|
||||||
currentQuaternion: Quaternion,
|
currentQuaternion: Quaternion,
|
||||||
targetQuaternion: Quaternion,
|
targetQuaternion: Quaternion,
|
||||||
deltaTime: number
|
deltaTime: number,
|
||||||
|
cameraPassiveUpdateTimer: MutableRefObject<number>
|
||||||
) => {
|
) => {
|
||||||
const slerpFactor = 1 - Math.exp(-30 * deltaTime)
|
cameraPassiveUpdateTimer.current += deltaTime
|
||||||
currentQuaternion.slerp(targetQuaternion, slerpFactor).normalize()
|
if (
|
||||||
camera.position.set(0, 0, 1).applyQuaternion(currentQuaternion)
|
!quaternionsEqual(currentQuaternion, targetQuaternion) ||
|
||||||
camera.quaternion.copy(currentQuaternion)
|
cameraPassiveUpdateTimer.current >= 5
|
||||||
|
) {
|
||||||
|
const slerpFactor = 1 - Math.exp(-30 * deltaTime)
|
||||||
|
currentQuaternion.slerp(targetQuaternion, slerpFactor).normalize()
|
||||||
|
camera.position.set(0, 0, 1).applyQuaternion(currentQuaternion)
|
||||||
|
camera.quaternion.copy(currentQuaternion)
|
||||||
|
cameraPassiveUpdateTimer.current = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const quaternionsEqual = (
|
||||||
|
q1: Quaternion,
|
||||||
|
q2: Quaternion,
|
||||||
|
tolerance: number = 0.001
|
||||||
|
): boolean => {
|
||||||
|
return (
|
||||||
|
Math.abs(q1.x - q2.x) < tolerance &&
|
||||||
|
Math.abs(q1.y - q2.y) < tolerance &&
|
||||||
|
Math.abs(q1.z - q2.z) < tolerance &&
|
||||||
|
Math.abs(q1.w - q2.w) < tolerance
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeMouseEvents = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>,
|
||||||
|
sceneInfra: SceneInfra
|
||||||
|
): { mouse: Vector2; disposeMouseEvents: () => void } => {
|
||||||
|
const mouse = new Vector2()
|
||||||
|
mouse.x = 1 // fix initial mouse position issue
|
||||||
|
|
||||||
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
const { left, top, width, height } = canvas.getBoundingClientRect()
|
||||||
|
mouse.x = ((event.clientX - left) / width) * 2 - 1
|
||||||
|
mouse.y = ((event.clientY - top) / height) * -2 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (raycasterIntersect.current) {
|
||||||
|
const axisName = raycasterIntersect.current.object.name as AxisNames
|
||||||
|
sceneInfra.camControls.updateCameraToAxis(axisName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', handleMouseMove)
|
||||||
|
window.addEventListener('click', handleClick)
|
||||||
|
|
||||||
|
const disposeMouseEvents = () => {
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
window.removeEventListener('click', handleClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mouse, disposeMouseEvents }
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRayCaster = (
|
||||||
|
objects: Object3D[],
|
||||||
|
raycaster: Raycaster,
|
||||||
|
mouse: Vector2,
|
||||||
|
camera: Camera,
|
||||||
|
raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>,
|
||||||
|
deltaTime: number,
|
||||||
|
raycasterPassiveUpdateTimer: MutableRefObject<number>
|
||||||
|
) => {
|
||||||
|
raycasterPassiveUpdateTimer.current += deltaTime
|
||||||
|
|
||||||
|
// check if mouse is outside the canvas bounds and stop raycaster
|
||||||
|
if (raycasterPassiveUpdateTimer.current < 2) {
|
||||||
|
if (mouse.x < -1 || mouse.x > 1 || mouse.y < -1 || mouse.y > 1) {
|
||||||
|
raycasterIntersect.current = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
raycaster.setFromCamera(mouse, camera)
|
||||||
|
const intersects = raycaster.intersectObjects(objects)
|
||||||
|
|
||||||
|
objects.forEach((object) => object.scale.set(1, 1, 1))
|
||||||
|
if (intersects.length) {
|
||||||
|
intersects[0].object.scale.set(1.5, 1.5, 1.5)
|
||||||
|
raycasterIntersect.current = intersects[0] // filter first object
|
||||||
|
} else {
|
||||||
|
raycasterIntersect.current = null
|
||||||
|
}
|
||||||
|
if (raycasterPassiveUpdateTimer.current > 2) {
|
||||||
|
raycasterPassiveUpdateTimer.current = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,13 +365,6 @@ export class KclManager {
|
|||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
|
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
|
||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
|
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
|
||||||
}
|
}
|
||||||
exitEditMode() {
|
|
||||||
this.engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'edit_mode_exit' },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
defaultSelectionFilter() {
|
defaultSelectionFilter() {
|
||||||
defaultSelectionFilter(this.programMemory, this.engineCommandManager)
|
defaultSelectionFilter(this.programMemory, this.engineCommandManager)
|
||||||
}
|
}
|
||||||
@ -386,24 +379,11 @@ function defaultSelectionFilter(
|
|||||||
) as SketchGroup | ExtrudeGroup
|
) as SketchGroup | ExtrudeGroup
|
||||||
firstSketchOrExtrudeGroup &&
|
firstSketchOrExtrudeGroup &&
|
||||||
engineCommandManager.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_batch_req',
|
type: 'modeling_cmd_req',
|
||||||
batch_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
responses: false,
|
cmd: {
|
||||||
requests: [
|
type: 'set_selection_filter',
|
||||||
{
|
filter: ['face', 'edge', 'solid2d', 'curve'],
|
||||||
cmd_id: uuidv4(),
|
},
|
||||||
cmd: {
|
|
||||||
type: 'edit_mode_enter',
|
|
||||||
target: firstSketchOrExtrudeGroup.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'set_selection_filter',
|
|
||||||
filter: ['face', 'edge', 'solid2d'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,6 @@ export enum ConnectionError {
|
|||||||
Unset = 0,
|
Unset = 0,
|
||||||
LongLoadingTime,
|
LongLoadingTime,
|
||||||
|
|
||||||
LostVideoStream,
|
|
||||||
ICENegotiate,
|
ICENegotiate,
|
||||||
DataChannelError,
|
DataChannelError,
|
||||||
WebSocketError,
|
WebSocketError,
|
||||||
@ -168,8 +167,6 @@ export const CONNECTION_ERROR_TEXT: Record<ConnectionError, string> = {
|
|||||||
[ConnectionError.Unset]: '',
|
[ConnectionError.Unset]: '',
|
||||||
[ConnectionError.LongLoadingTime]:
|
[ConnectionError.LongLoadingTime]:
|
||||||
'Loading is taking longer than expected...',
|
'Loading is taking longer than expected...',
|
||||||
[ConnectionError.LostVideoStream]:
|
|
||||||
'Lost connection to video stream... Reconnecting...',
|
|
||||||
[ConnectionError.ICENegotiate]: 'ICE negotiation failed.',
|
[ConnectionError.ICENegotiate]: 'ICE negotiation failed.',
|
||||||
[ConnectionError.DataChannelError]: 'The data channel signaled an error.',
|
[ConnectionError.DataChannelError]: 'The data channel signaled an error.',
|
||||||
[ConnectionError.WebSocketError]: 'The websocket signaled an error.',
|
[ConnectionError.WebSocketError]: 'The websocket signaled an error.',
|
||||||
@ -315,8 +312,6 @@ class EngineConnection extends EventTarget {
|
|||||||
if (next.type === EngineConnectionStateType.Disconnecting) {
|
if (next.type === EngineConnectionStateType.Disconnecting) {
|
||||||
const sub = next.value
|
const sub = next.value
|
||||||
if (sub.type === DisconnectingType.Error) {
|
if (sub.type === DisconnectingType.Error) {
|
||||||
console.log(sub)
|
|
||||||
|
|
||||||
// Record the last step we failed at.
|
// Record the last step we failed at.
|
||||||
// (Check the current state that we're about to override that
|
// (Check the current state that we're about to override that
|
||||||
// it was a Connecting state.)
|
// it was a Connecting state.)
|
||||||
@ -759,8 +754,6 @@ class EngineConnection extends EventTarget {
|
|||||||
// when assuming we're the only consumer or that all messages will
|
// when assuming we're the only consumer or that all messages will
|
||||||
// be carefully formatted here.
|
// be carefully formatted here.
|
||||||
|
|
||||||
console.log(event)
|
|
||||||
|
|
||||||
if (typeof event.data !== 'string') {
|
if (typeof event.data !== 'string') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -781,7 +774,6 @@ class EngineConnection extends EventTarget {
|
|||||||
`Error in response to request ${message.request_id}:\n${errorsString}
|
`Error in response to request ${message.request_id}:\n${errorsString}
|
||||||
failed cmd type was ${artifactThatFailed?.commandType}`
|
failed cmd type was ${artifactThatFailed?.commandType}`
|
||||||
)
|
)
|
||||||
console.log(artifactThatFailed)
|
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error from server:\n${errorsString}`)
|
console.error(`Error from server:\n${errorsString}`)
|
||||||
}
|
}
|
||||||
@ -872,7 +864,6 @@ class EngineConnection extends EventTarget {
|
|||||||
this.pc
|
this.pc
|
||||||
?.createOffer()
|
?.createOffer()
|
||||||
.then((offer: RTCSessionDescriptionInit) => {
|
.then((offer: RTCSessionDescriptionInit) => {
|
||||||
console.log(offer)
|
|
||||||
this.state = {
|
this.state = {
|
||||||
type: EngineConnectionStateType.Connecting,
|
type: EngineConnectionStateType.Connecting,
|
||||||
value: {
|
value: {
|
||||||
@ -944,7 +935,6 @@ class EngineConnection extends EventTarget {
|
|||||||
|
|
||||||
case 'trickle_ice':
|
case 'trickle_ice':
|
||||||
let candidate = resp.data?.candidate
|
let candidate = resp.data?.candidate
|
||||||
console.log('trickle_ice: using this candidate: ', candidate)
|
|
||||||
void this.pc?.addIceCandidate(candidate as RTCIceCandidateInit)
|
void this.pc?.addIceCandidate(candidate as RTCIceCandidateInit)
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -1347,20 +1337,10 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
this.engineConnection?.addEventListener(
|
this.engineConnection?.addEventListener(
|
||||||
EngineConnectionEvents.NewTrack,
|
EngineConnectionEvents.NewTrack,
|
||||||
(({ detail: { mediaStream } }: CustomEvent<NewTrackArgs>) => {
|
(({ detail: { mediaStream } }: CustomEvent<NewTrackArgs>) => {
|
||||||
console.log('received track', mediaStream)
|
|
||||||
|
|
||||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||||
if (this.engineConnection) {
|
console.error(
|
||||||
this.engineConnection.state = {
|
'video track mute: check webrtc internals -> inbound rtp'
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
)
|
||||||
value: {
|
|
||||||
type: DisconnectingType.Error,
|
|
||||||
value: {
|
|
||||||
error: ConnectionError.LostVideoStream,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
setMediaStream(mediaStream)
|
setMediaStream(mediaStream)
|
||||||
@ -1673,7 +1653,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
(command.cmd.type === 'highlight_set_entity' ||
|
(command.cmd.type === 'highlight_set_entity' ||
|
||||||
command.cmd.type === 'mouse_move' ||
|
command.cmd.type === 'mouse_move' ||
|
||||||
command.cmd.type === 'camera_drag_move' ||
|
command.cmd.type === 'camera_drag_move' ||
|
||||||
command.cmd.type === 'default_camera_look_at' ||
|
|
||||||
command.cmd.type === ('default_camera_perspective_settings' as any))
|
command.cmd.type === ('default_camera_perspective_settings' as any))
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@ -1688,7 +1667,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
command.type === 'modeling_cmd_req' &&
|
command.type === 'modeling_cmd_req' &&
|
||||||
command.cmd.type !== lastMessage
|
command.cmd.type !== lastMessage
|
||||||
) {
|
) {
|
||||||
console.log('sending command', command.cmd.type)
|
|
||||||
lastMessage = command.cmd.type
|
lastMessage = command.cmd.type
|
||||||
}
|
}
|
||||||
if (command.type === 'modeling_cmd_batch_req') {
|
if (command.type === 'modeling_cmd_batch_req') {
|
||||||
@ -1702,7 +1680,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
if (
|
if (
|
||||||
(cmd.type === 'camera_drag_move' ||
|
(cmd.type === 'camera_drag_move' ||
|
||||||
cmd.type === 'handle_mouse_drag_move' ||
|
cmd.type === 'handle_mouse_drag_move' ||
|
||||||
cmd.type === 'default_camera_look_at' ||
|
|
||||||
cmd.type === ('default_camera_perspective_settings' as any)) &&
|
cmd.type === ('default_camera_perspective_settings' as any)) &&
|
||||||
this.engineConnection?.unreliableDataChannel &&
|
this.engineConnection?.unreliableDataChannel &&
|
||||||
!forceWebsocket
|
!forceWebsocket
|
||||||
|
17
src/wasm-lib/Cargo.lock
generated
17
src/wasm-lib/Cargo.lock
generated
@ -1152,9 +1152,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.28"
|
version = "0.14.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
|
checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@ -2975,9 +2975,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.13"
|
version = "0.8.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba"
|
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
@ -2996,9 +2996,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.13"
|
version = "0.22.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
|
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.5",
|
"indexmap 2.2.5",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3158,7 +3158,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ts-rs"
|
name = "ts-rs"
|
||||||
version = "8.1.0"
|
version = "8.1.0"
|
||||||
source = "git+https://github.com/Aleph-Alpha/ts-rs#badbac08e61e65b312880aa64e9ece2976f1bbef"
|
source = "git+https://github.com/Aleph-Alpha/ts-rs#be0349d5fb07a8ccab713887a61e90e3bc773c7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@ -3170,7 +3170,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ts-rs-macros"
|
name = "ts-rs-macros"
|
||||||
version = "8.1.0"
|
version = "8.1.0"
|
||||||
source = "git+https://github.com/Aleph-Alpha/ts-rs#badbac08e61e65b312880aa64e9ece2976f1bbef"
|
source = "git+https://github.com/Aleph-Alpha/ts-rs#be0349d5fb07a8ccab713887a61e90e3bc773c7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -3444,6 +3444,7 @@ dependencies = [
|
|||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"futures",
|
"futures",
|
||||||
"gloo-utils",
|
"gloo-utils",
|
||||||
|
"hyper",
|
||||||
"image",
|
"image",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"kcl-lib",
|
"kcl-lib",
|
||||||
|
@ -17,13 +17,14 @@ kcl-lib = { path = "kcl" }
|
|||||||
kittycad = { workspace = true }
|
kittycad = { workspace = true }
|
||||||
serde_json = "1.0.116"
|
serde_json = "1.0.116"
|
||||||
tokio = { version = "1.38.0", features = ["sync"] }
|
tokio = { version = "1.38.0", features = ["sync"] }
|
||||||
toml = "0.8.13"
|
toml = "0.8.14"
|
||||||
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.91"
|
wasm-bindgen = "0.2.91"
|
||||||
wasm-bindgen-futures = "0.4.42"
|
wasm-bindgen-futures = "0.4.42"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
hyper = { version = "0.14.29", features = ["server", "http1"] }
|
||||||
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||||
kittycad = { workspace = true, default-features = true }
|
kittycad = { workspace = true, default-features = true }
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "grackle"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
description = "A new executor for KCL which compiles to Execution Plans"
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
|
||||||
kcl-lib = { path = "../kcl" }
|
|
||||||
kittycad = { workspace = true }
|
|
||||||
kittycad-execution-plan = { workspace = true }
|
|
||||||
kittycad-execution-plan-traits = { workspace = true }
|
|
||||||
kittycad-execution-plan-macros = { workspace = true }
|
|
||||||
kittycad-modeling-cmds = { workspace = true }
|
|
||||||
kittycad-modeling-session = { workspace = true }
|
|
||||||
thiserror = "1.0.61"
|
|
||||||
tokio = { version = "1.37.0", features = ["macros", "rt"] }
|
|
||||||
twenty-twenty = "0.8.0"
|
|
||||||
uuid = "1.8"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pretty_assertions = "1"
|
|
||||||
serde_json = "1.0.116"
|
|
@ -35,7 +35,7 @@ serde = { version = "1.0.203", features = ["derive"] }
|
|||||||
serde_json = "1.0.116"
|
serde_json = "1.0.116"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.61"
|
||||||
toml = "0.8.13"
|
toml = "0.8.14"
|
||||||
# TODO: change this to a cargo release once 8.1.1 comes out
|
# TODO: change this to a cargo release once 8.1.1 comes out
|
||||||
ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings"] }
|
ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings"] }
|
||||||
url = { version = "2.5.0", features = ["serde"] }
|
url = { version = "2.5.0", features = ["serde"] }
|
||||||
|
@ -11,7 +11,7 @@ use serde_json::Value as JValue;
|
|||||||
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{BodyItem, FunctionExpression, KclNone, Value},
|
ast::types::{BodyItem, FunctionExpression, KclNone, Program, Value},
|
||||||
engine::EngineManager,
|
engine::EngineManager,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
fs::FileManager,
|
fs::FileManager,
|
||||||
@ -975,6 +975,8 @@ impl Default for PipeInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The executor context.
|
/// The executor context.
|
||||||
|
/// Cloning will return another handle to the same engine connection/session,
|
||||||
|
/// as this uses `Arc` under the hood.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ExecutorContext {
|
pub struct ExecutorContext {
|
||||||
pub engine: Arc<Box<dyn EngineManager>>,
|
pub engine: Arc<Box<dyn EngineManager>>,
|
||||||
@ -1310,6 +1312,43 @@ impl ExecutorContext {
|
|||||||
pub fn update_units(&mut self, units: crate::settings::types::UnitLength) {
|
pub fn update_units(&mut self, units: crate::settings::types::UnitLength) {
|
||||||
self.settings.units = units;
|
self.settings.units = units;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute the program, then get a PNG screenshot.
|
||||||
|
pub async fn execute_and_prepare_snapshot(&self, program: Program) -> Result<kittycad::types::TakeSnapshot> {
|
||||||
|
let _ = self.run(program, None).await?;
|
||||||
|
|
||||||
|
// Zoom to fit.
|
||||||
|
self.engine
|
||||||
|
.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
crate::executor::SourceRange::default(),
|
||||||
|
kittycad::types::ModelingCmd::ZoomToFit {
|
||||||
|
object_ids: Default::default(),
|
||||||
|
padding: 0.1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Send a snapshot request to the engine.
|
||||||
|
let resp = self
|
||||||
|
.engine
|
||||||
|
.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
crate::executor::SourceRange::default(),
|
||||||
|
kittycad::types::ModelingCmd::TakeSnapshot {
|
||||||
|
format: kittycad::types::ImageFormat::Png,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
||||||
|
} = resp
|
||||||
|
else {
|
||||||
|
anyhow::bail!("Unexpected response from engine: {:?}", resp);
|
||||||
|
};
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each argument given,
|
/// For each argument given,
|
||||||
|
@ -200,6 +200,24 @@ pub async fn revolve(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
/// axis: getOppositeEdge('revolveAxis', box)
|
/// axis: getOppositeEdge('revolveAxis', box)
|
||||||
/// }, %)
|
/// }, %)
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// const sketch001 = startSketchOn('XY')
|
||||||
|
/// |> startProfileAt([10, 0], %)
|
||||||
|
/// |> line([5, -5], %)
|
||||||
|
/// |> line([5, 5], %)
|
||||||
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
/// |> close(%)
|
||||||
|
///
|
||||||
|
/// const part001 = revolve({
|
||||||
|
/// axis: {
|
||||||
|
/// custom: {
|
||||||
|
/// axis: [0.0, 1.0, 0.0],
|
||||||
|
/// origin: [0.0, 0.0, 0.0]
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }, sketch001)
|
||||||
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "revolve",
|
name = "revolve",
|
||||||
}]
|
}]
|
||||||
|
BIN
src/wasm-lib/kcl/tests/outputs/serial_test_example_revolve6.png
Normal file
BIN
src/wasm-lib/kcl/tests/outputs/serial_test_example_revolve6.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 220 KiB |
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user