Compare commits

...

12 Commits

Author SHA1 Message Date
29070a9b04 Hook up circle tool to client side scene, still missing displaying circle in sketch mode 2024-04-23 14:54:56 -04:00
e3861f9380 Add Circle tool to state machine 2024-04-23 13:40:52 -04:00
2d8d29b345 Bump tauri-plugin-fs from 2.0.0-beta.5 to 2.0.0-beta.6 in /src-tauri (#2205)
Bumps [tauri-plugin-fs](https://github.com/tauri-apps/plugins-workspace) from 2.0.0-beta.5 to 2.0.0-beta.6.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](https://github.com/tauri-apps/plugins-workspace/compare/fs-v2.0.0-beta.5...fs-v2.0.0-beta.6)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-23 16:56:00 +00:00
00da062586 bump kittycad.rs (#2196)
* update lib

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

* fix tests

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-23 03:46:54 +00:00
aafbaf6c50 human speed completions (#2193)
* human speed completions

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

* add slowness

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

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

* empty

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-23 00:21:24 +00:00
2894c84a4e fix recast (#2194)
* fix recast

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-22 17:14:20 -07:00
c01084feb0 Zoom to fit rust side (#2195)
* zoom to fit

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

* zoom to fit

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

* docs

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-22 17:14:10 -07:00
c461db5f54 fix const completion (#2192)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-22 21:53:49 +00:00
03fcb73aca Bump kittycad-modeling-cmds from 0.2.19 to 0.2.20 in /src/wasm-lib (#2186)
Bumps [kittycad-modeling-cmds](https://github.com/KittyCAD/modeling-api) from 0.2.19 to 0.2.20.
- [Commits](https://github.com/KittyCAD/modeling-api/compare/kittycad-modeling-cmds-0.2.19...kittycad-modeling-cmds-0.2.20)

---
updated-dependencies:
- dependency-name: kittycad-modeling-cmds
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-04-22 20:09:53 +00:00
8065e7e51a Bump thiserror from 1.0.58 to 1.0.59 in /src/wasm-lib (#2187)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.58 to 1.0.59.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.58...1.0.59)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-04-22 18:46:00 +00:00
2d0ac249df Cut release v0.18.1 (#2189)
* Cut release v0.18.1

* Fix release script
2024-04-22 09:47:10 -07:00
3d73b82c23 project global origin for sketches and use engine animations (#2113)
* use engine animations for sketch on face, but not default planes

* massage things

* fix type issue

* small problem in playwright test<

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

* some tests fixes

* more test tweaks

* more test tweaks

* clean up

* more tidy

* tests are a pain

* more test stuff

* test stuff again

* fix micro think axes in sketch mode

* more test shit

* more test shit more

* more test tweaks

* more test tweaks

* more test stuff

* trigger ci

* clean up

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-22 20:14:06 +10:00
248 changed files with 1138 additions and 555 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -104,6 +104,7 @@ test('Basic sketch', async ({ page }) => {
|> 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 part001 = startSketchOn('-XZ')
@ -362,7 +363,7 @@ angle: 90
await u.waitForAuthSkipAppStart()
u.openDebugPanel()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
@ -564,7 +565,9 @@ test('Auto complete works', async ({ page }) => {
await page.keyboard.press('Tab')
await page.keyboard.type('12')
await page.waitForTimeout(100)
await page.keyboard.press('Tab')
await page.waitForTimeout(100)
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.press('Enter')
@ -734,7 +737,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await u.openDebugPanel()
const xAxisClick = () =>
page.mouse.click(700, 250).then(() => page.waitForTimeout(100))
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
const emptySpaceClick = () =>
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
const topHorzSegmentClick = () =>
@ -759,6 +762,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
@ -766,12 +770,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|> 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 part001 = 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 part001 = startSketchOn('-XZ')
@ -784,10 +790,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.getByRole('button', { name: 'Line' }).click()
await u.closeDebugPanel()
const selectionSequence = async () => {
const selectionSequence = async (isSecondTime = false) => {
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10)
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
@ -797,7 +807,10 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// 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, 500 - PUR * 20) // mouse onto another line
await page.mouse.move(
startXPx + PUR * 10,
isSecondTime ? 295 : 500 - PUR * 20
) // mouse onto another line
await expect(page.getByTestId('hover-highlight')).toBeVisible()
// now check clicking works including axis
@ -807,6 +820,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.keyboard.down('Shift')
const absYButton = page.getByRole('button', { name: 'ABS Y' })
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await absYButton.and(page.locator(':not([disabled])')).waitFor()
@ -815,10 +829,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// clear selection by clicking on nothing
await emptySpaceClick()
await page.waitForTimeout(100)
// same selection but click the axis first
await xAxisClick()
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.keyboard.up('Shift')
@ -831,6 +847,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await expect(absYButton).not.toBeDisabled()
@ -873,11 +890,16 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.waitForTimeout(100)
// enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click()
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()
await selectionSequence(true)
})
test.describe('Command bar tests', () => {
@ -1063,6 +1085,7 @@ test('Can add multiple sketches', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
@ -1078,24 +1101,33 @@ test('Can add multiple sketches', async ({ page }) => {
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.updateCamPosition([0, 100, 100])
await u.updateCamPosition([100, 100, 100])
await page.waitForTimeout(250)
// start a new sketch
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
await page.mouse.click(673, 384)
await page.waitForTimeout(400)
await page.mouse.click(650, 450)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await u.clearAndCloseDebugPanel()
// on mock os there are issues with getting the camera to update
// it should not be selecting the 'XZ' plane here if the camera updated
// properly, but if we just role with it we can still verify everything
// in the rest of the test
const plane = process.platform === 'darwin' ? 'XZ' : 'XY'
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt2 = '[0.93,-1.25]'
const startAt2 =
process.platform === 'darwin' ? '[9.75, -13.16]' : '[0.93, -1.25]'
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('XY')
const part002 = startSketchOn('${plane}')
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
@ -1104,12 +1136,12 @@ const part002 = startSketchOn('XY')
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num2 = 0.94
const num2 = process.platform === 'darwin' ? 9.84 : 0.94
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('XY')
const part002 = startSketchOn('${plane}')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)`.replace(/\s/g, '')
)
@ -1119,21 +1151,29 @@ const part002 = startSketchOn('XY')
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('XY')
const part002 = startSketchOn('${plane}')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(num2 - 0.01)}], %)`.replace(/\s/g, '')
|> line([0, ${roundOff(
num2 + (process.platform === 'darwin' ? 0.01 : -0.01)
)}], %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('XY')
const part002 = startSketchOn('${plane}')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(num2 - 0.01)}], %)
|> line([-1.87, 0], %)`.replace(/\s/g, '')
|> line([0, ${roundOff(
num2 + (process.platform === 'darwin' ? 0.01 : -0.01)
)}], %)
|> line([-${process.platform === 'darwin' ? 19.59 : 1.87}, 0], %)`.replace(
/\s/g,
''
)
)
})
@ -1337,10 +1377,12 @@ test('Deselecting line tool should mean nothing happens on click', async ({
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(700, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(750, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
@ -1365,16 +1407,16 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
const startPX = [652, 418]
const lineEndPX = [794, 416]
const arcEndPX = [893, 318]
const startPX = [665, 458]
const lineEndPX = [842, 458]
const arcEndPX = [971, 342]
const dragPX = 30
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(100)
await page.waitForTimeout(400)
let prevContent = await page.locator('.cm-content').innerText()
const step5 = { steps: 5 }
@ -1384,7 +1426,7 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
@ -1412,9 +1454,9 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([7.01, -11.79], %)
|> line([14.69, 2.73], %)
|> tangentialArcTo([27.6, -3.25], %)`)
|> startProfileAt([6.44, -12.07], %)
|> line([14.04, 2.03], %)
|> tangentialArcTo([27.19, -4.2], %)`)
})
const doSnapAtDifferentScales = async (
@ -1533,38 +1575,46 @@ test('Sketch on face', async ({ page }) => {
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(300)
let previousCodeContent = await page.locator('.cm-content').innerText()
await page.mouse.click(793, 133)
await u.openAndClearDebugPanel()
await u.doAndWaitForCmd(
() => page.mouse.click(793, 133),
'default_camera_get_settings',
true
)
await page.waitForTimeout(150)
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.waitForTimeout(100)
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.waitForTimeout(100)
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.waitForTimeout(100)
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], %)
|> startProfileAt([-12.83, 6.7], %)
|> line([2.87, -0.23], %)
|> line([-3.05, -1.47], %)
|> close(%)`)
await u.openAndClearDebugPanel()
@ -1574,9 +1624,14 @@ test('Sketch on face', async ({ page }) => {
await u.updateCamPosition([1049, 239, 686])
await u.closeDebugPanel()
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await page.getByText('startProfileAt([-12.83, 6.7], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings',
true
)
await page.waitForTimeout(150)
await page.setViewportSize({ width: 1200, height: 1200 })
await u.openAndClearDebugPanel()
await u.updateCamPosition([452, -152, 1166])
@ -1596,11 +1651,11 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %)
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
|> startProfileAt([-12.83, 6.7], %)
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
process?.env?.CI ? 0.07 : 0.07
}], %)
|> line([-4.44, -2.13], %)
|> line([-3.05, -1.47], %)
|> close(%)`)
// exit sketch
@ -1608,7 +1663,7 @@ test('Sketch on face', async ({ page }) => {
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 page.getByText('startProfileAt([-12.83, 6.7], %)').click()
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
await page.getByRole('button', { name: 'Extrude' }).click()
@ -1622,11 +1677,11 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %)
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
|> startProfileAt([-12.83, 6.7], %)
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
process?.env?.CI ? 0.07 : 0.07
}], %)
|> line([-4.44, -2.13], %)
|> line([-3.05, -1.47], %)
|> close(%)
|> extrude(5 + 7, %)`)
})
@ -1659,11 +1714,11 @@ test('Can code mod a line length', async ({ page }) => {
// enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(300) // wait for animation
await page.waitForTimeout(350) // wait for animation
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.mouse.click(615, 133)
await page.mouse.click(615, 102)
await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByText('Add constraining value').click()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -6,7 +6,7 @@ import { PNG } from 'pngjs'
async function waitForPageLoad(page: Page) {
// wait for 'Loading stream...' spinner
await page.getByTestId('loading-stream').waitFor()
// await page.getByTestId('loading-stream').waitFor()
// wait for all spinners to be gone
await page.getByTestId('loading').waitFor({ state: 'detached' })

View File

@ -57,7 +57,7 @@ echo "New version number without 'v': $new_version_number"
git checkout -b "cut-release-$new_version"
echo "$(jq --arg v "$new_version_number" '.version=$v' package.json --indent 2)" > package.json
echo "$(jq --arg v "$new_version_number" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
echo "$(jq --arg v "$new_version_number" '.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
git add package.json src-tauri/tauri.conf.json
git commit -m "Cut release $new_version"

View File

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

8
src-tauri/Cargo.lock generated
View File

@ -2199,9 +2199,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.67"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc460442c165c8e707b1154551cefd08938d10bb80c78940e10cd9869487c325"
checksum = "ddc922f0da3abc22661bf49423c9bfcc02ce6ae92dae007ede6990874789545b"
dependencies = [
"anyhow",
"async-trait",
@ -4641,9 +4641,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
version = "2.0.0-beta.5"
version = "2.0.0-beta.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c138126392c350aa68554e3461529b02680062c9146ab7b41d3ef97a2deaf93b"
checksum = "609f53d90f08808679ecdd81455d9a4d0053291b92780695569f7400fdba27d5"
dependencies = [
"anyhow",
"glob",

View File

@ -16,13 +16,13 @@ tauri-build = { version = "2.0.0-beta.12", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.67"
kittycad = "0.3.0"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
tauri-plugin-dialog = { version = "2.0.0-beta.5" }
tauri-plugin-fs = { version = "2.0.0-beta.5" }
tauri-plugin-fs = { version = "2.0.0-beta.6" }
tauri-plugin-http = { version = "2.0.0-beta.5" }
tauri-plugin-os = { version = "2.0.0-beta.2" }
tauri-plugin-process = { version = "2.0.0-beta.2" }

View File

@ -55,5 +55,5 @@
}
},
"productName": "Zoo Modeling App",
"version": "0.18.0"
"version": "0.18.1"
}

View File

@ -193,6 +193,35 @@ export const Toolbar = () => {
Rectangle
</ActionButton>
</li>
<li className="contents" key="circle-button">
<ActionButton
className={buttonClassName}
Element="button"
onClick={() =>
state.matches('Sketch.Circle tool')
? send('CancelSketch')
: send('Equip circle tool')
}
aria-pressed={state.matches('Sketch.Circle tool')}
icon={{
icon: 'circle',
iconClassName,
bgClassName,
}}
disabled={
(!state.can('Equip circle tool') &&
!state.matches('Sketch.Circle tool')) ||
disableAllButtons
}
title={
state.can('Equip circle tool')
? 'Circle'
: 'Can only be used when a sketch is empty currently'
}
>
Circle
</ActionButton>
</li>
</>
)}
{state.matches('Sketch.SketchIdle') &&

View File

@ -246,13 +246,31 @@ export class CameraControls {
camSettings.center.y,
camSettings.center.z
)
this.camera.up.set(camSettings.up.x, camSettings.up.y, camSettings.up.z)
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
this.useOrthographicCamera()
}
if (this.camera instanceof OrthographicCamera && !camSettings.ortho) {
this.usePerspectiveCamera()
}
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
this.camera.fov = camSettings.fov_y
} else if (
this.camera instanceof OrthographicCamera &&
camSettings.ortho_scale
) {
this.camera.zoom = camSettings.ortho_scale
const distanceToTarget = new Vector3(
camSettings.pos.x,
camSettings.pos.y,
camSettings.pos.z
).distanceTo(
new Vector3(
camSettings.center.x,
camSettings.center.y,
camSettings.center.z
)
)
this.camera.zoom = (camSettings.ortho_scale * 40) / distanceToTarget
}
this.onCameraChange()
}
@ -965,10 +983,10 @@ export class CameraControls {
// Pure function helpers
function calculateNearFarFromFOV(fov: number) {
const nearFarRatio = (fov - 3) / (45 - 3)
// const nearFarRatio = (fov - 3) / (45 - 3)
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far }
// const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far: 1000 }
}
function convertThreeCamValuesToEngineCam({
@ -1043,3 +1061,62 @@ function _getInteractionType(
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
return state
}
/**
* Tells the engine to fire it's animation waits for it to finish and then requests camera settings
* to ensure the client-side camera is synchronized with the engine's camera state.
*
* @param engineCommandManager Our websocket singleton
* @param entityId - The ID of face or sketchPlane.
*/
export async function letEngineAnimateAndSyncCamAfter(
engineCommandManager: EngineCommandManager,
entityId: string
) {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: true,
animated: !isReducedMotion(),
ortho: false,
entity_id: entityId,
},
})
// wait 600ms (animation takes 500, + 100 for safety)
await new Promise((resolve) =>
setTimeout(resolve, isReducedMotion() ? 100 : 600)
)
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: true,
animated: false,
ortho: true,
entity_id: entityId,
},
})
await new Promise((resolve) => setTimeout(resolve, 50))
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
}

View File

@ -97,6 +97,7 @@ import {
getRectangleCallExpressions,
updateRectangleSketch,
} from 'lib/rectangleTool'
import { circleAsCallExpressions, updateCircleSketch } from 'lib/circleTool'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -215,8 +216,9 @@ export class SceneEntities {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const baseXColor = 0x000055
const baseYColor = 0x550000
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
const yAxisGeometry = new BoxGeometry(0.3, 100000, 0.01)
const axisPixelWidth = 1.6
const xAxisGeometry = new BoxGeometry(100000, axisPixelWidth, 0.01)
const yAxisGeometry = new BoxGeometry(axisPixelWidth, 100000, 0.01)
const xAxisMaterial = new MeshBasicMaterial({
color: baseXColor,
depthTest: false,
@ -579,7 +581,7 @@ export class SceneEntities {
...this.mouseEnterLeaveCallbacks(),
})
}
setupRectangleOriginListener = () => {
setupOriginListener = (type: 'circle' | 'rectangle') => {
sceneInfra.setCallbacks({
onClick: (args) => {
const twoD = args.intersectionPoint?.twoD
@ -588,7 +590,7 @@ export class SceneEntities {
return
}
sceneInfra.modelingSend({
type: 'Add rectangle origin',
type: `Add ${type} origin`,
data: [twoD.x, twoD.y],
})
},
@ -746,6 +748,154 @@ export class SceneEntities {
},
})
}
setupDraftCircle = async (
sketchPathToNode: PathToNode,
forward: [number, number, number],
up: [number, number, number],
sketchOrigin: [number, number, number],
circleOrigin: [x: number, y: number]
) => {
let _ast = JSON.parse(JSON.stringify(kclManager.ast))
const variableDeclarationName =
getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)?.node?.declarations?.[0]?.id?.name || ''
const tags: [string] = [findUniqueName(_ast, 'circle')]
const startSketchOn = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)?.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
startSketchOn[0].init = createPipeExpression([
startSketchOnInit,
...circleAsCallExpressions(circleOrigin, tags),
])
_ast = parse(recast(_ast))
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
forward,
up,
position: sketchOrigin,
maybeModdedAst: _ast,
draftExpressionsIndices: { start: 0, end: 1 },
})
sceneInfra.setCallbacks({
onMove: async (args) => {
// Update the radius of the draft rectangle
const pathToNodeTwo = JSON.parse(JSON.stringify(sketchPathToNode))
pathToNodeTwo[1][0] = 0
const sketchInit = getNodeFromPath<VariableDeclaration>(
truncatedAst,
pathToNodeTwo || [],
'VariableDeclaration'
)?.node?.declarations?.[0]?.init
const x = (args.intersectionPoint.twoD.x || 0) - circleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - circleOrigin[1]
if (sketchInit.type === 'PipeExpression') {
updateCircleSketch(sketchInit, x, y, tags[0])
}
const { programMemory } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.root[
variableDeclarationName
] as SketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(
sketchGroup.start,
0,
0,
_ast,
orthoFactor,
sketchGroup
)
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
)
},
onClick: async (args) => {
// Commit the circle to the full AST/code and return to sketch.idle
const radiusPoint = args.intersectionPoint?.twoD
if (!radiusPoint || args.mouseEvent.button !== 0) return
const x = roundOff((radiusPoint.x || 0) - circleOrigin[0])
const y = roundOff((radiusPoint.y || 0) - circleOrigin[1])
const sketchInit = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)?.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') {
updateCircleSketch(sketchInit, x, y, tags[0])
_ast = parse(recast(_ast))
console.log('onClick', {
sketchInit: sketchInit,
_ast,
x,
y,
truncatedAst,
})
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'CancelSketch' })
const { programMemory } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.root[
variableDeclarationName
] as SketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
// Update the starting segment of the THREEjs scene
this.updateSegment(
sketchGroup.start,
0,
0,
_ast,
orthoFactor,
sketchGroup
)
// Update the rest of the segments of the THREEjs scene
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
)
}
},
})
}
setupSketchIdleCallbacks = ({
pathToNode,
up,
@ -1324,30 +1474,31 @@ export class SceneEntities {
selected.material.color = defaultPlaneColor(type)
},
onClick: async (args) => {
const checkExtrudeFaceClick = async (): Promise<boolean> => {
const checkExtrudeFaceClick = async (): Promise<
['face' | 'plane' | 'other', string]
> => {
const { streamDimensions } = useStore.getState()
const { entity_id } = await sendSelectEventToEngine(
args?.mouseEvent,
document.getElementById('video-stream') as HTMLVideoElement,
streamDimensions
)
if (!entity_id) return false
if (!entity_id) return ['other', '']
if (
engineCommandManager.defaultPlanes?.xy === entity_id ||
engineCommandManager.defaultPlanes?.xz === entity_id ||
engineCommandManager.defaultPlanes?.yz === entity_id
) {
return ['plane', entity_id]
}
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
return ['other', entity_id]
const faceInfo = await getFaceDetails(entity_id)
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
return false
const { z_axis, origin, y_axis } = faceInfo
return ['other', entity_id]
const { z_axis, y_axis, origin } = faceInfo
const pathToNode = getNodePathFromSourceRange(
kclManager.ast,
artifact.range
@ -1367,12 +1518,15 @@ export class SceneEntities {
artifact?.additionalData?.type === 'cap'
? artifact.additionalData.info
: 'none',
faceId: entity_id,
},
})
return true
return ['face', entity_id]
}
if (await checkExtrudeFaceClick()) return
const faceResult = await checkExtrudeFaceClick()
console.log('faceResult', faceResult)
if (faceResult[0] === 'face') return
if (!args || !args.intersects?.[0]) return
if (args.mouseEvent.which !== 1) return
@ -1398,6 +1552,7 @@ export class SceneEntities {
plane: planeString,
zAxis,
yAxis,
planeId: faceResult[1],
},
})
},
@ -1681,7 +1836,7 @@ export async function getSketchOrientationDetails(
sketchPathToNode: PathToNode
): Promise<{
quat: Quaternion
sketchDetails: SketchDetails
sketchDetails: SketchDetails & { faceId?: string }
}> {
const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode,
@ -1697,20 +1852,13 @@ export async function getSketchOrientationDetails(
zAxis: [zAxis.x, zAxis.y, zAxis.z],
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
origin: [0, 0, 0],
faceId: sketchGroup.on.id,
},
}
}
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
const faceInfo = await getFaceDetails(sketchGroup.on.faceId)
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
throw new Error('faceInfo')
const { z_axis, y_axis, origin } = faceInfo
@ -1725,6 +1873,7 @@ export async function getSketchOrientationDetails(
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],
faceId: sketchGroup.on.faceId,
},
}
}
@ -1733,6 +1882,46 @@ export async function getSketchOrientationDetails(
)
}
/**
* Retrieves orientation details for a given entity representing a face (brep face or default plane).
* This function asynchronously fetches and returns the origin, x-axis, y-axis, and z-axis details
* for a specified entity ID. It is primarily used to obtain the orientation of a face in the scene,
* which is essential for calculating the correct positioning and alignment of the client side sketch.
*
* @param entityId - The ID of the entity for which orientation details are being fetched.
* @returns A promise that resolves with the orientation details of the face.
*/
async function getFaceDetails(
entityId: string
): Promise<Models['FaceIsPlanar_type']> {
// TODO mode engine connection to allow batching returns and batch the following
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: false,
animated: false,
ortho: false,
entity_id: entityId,
},
})
// TODO change typing to get_sketch_mode_plane once lib is updated
const faceInfo: Models['FaceIsPlanar_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'get_sketch_mode_plane' },
})
)?.data?.data
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'sketch_mode_disable' },
})
return faceInfo
}
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
const dummyCam = new PerspectiveCamera()
dummyCam.up.set(0, 0, 1)

View File

@ -61,6 +61,16 @@ const CustomIconMap = {
/>
</svg>
),
circle: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 2.5C9.01509 2.5 8.03982 2.69399 7.12988 3.0709C6.21994 3.44781 5.39314 4.00026 4.6967 4.6967C4.00026 5.39314 3.44782 6.21993 3.07091 7.12987C2.694 8.03981 2.5 9.01508 2.5 10C2.5 10.9849 2.69399 11.9602 3.0709 12.8701C3.44781 13.7801 4.00026 14.6069 4.6967 15.3033C5.39314 15.9997 6.21993 16.5522 7.12987 16.9291C8.03982 17.306 9.01509 17.5 10 17.5C10.9849 17.5 11.9602 17.306 12.8701 16.9291C13.7801 16.5522 14.6069 15.9997 15.3033 15.3033C15.9997 14.6069 16.5522 13.7801 16.9291 12.8701C17.306 11.9602 17.5 10.9849 17.5 10C17.5 9.01509 17.306 8.03982 16.9291 7.12988C16.5522 6.21993 15.9997 5.39314 15.3033 4.6967C14.6069 4.00026 13.7801 3.44781 12.8701 3.0709C11.9602 2.69399 10.9849 2.5 10 2.5ZM6.7472 2.14702C7.77847 1.71986 8.88377 1.5 10 1.5C11.1162 1.5 12.2215 1.71986 13.2528 2.14702C14.2841 2.57419 15.2211 3.20029 16.0104 3.98959C16.7997 4.77889 17.4258 5.71592 17.853 6.74719C18.2801 7.77846 18.5 8.88377 18.5 10C18.5 11.1162 18.2801 12.2215 17.853 13.2528C17.4258 14.2841 16.7997 15.2211 16.0104 16.0104C15.2211 16.7997 14.2841 17.4258 13.2528 17.853C12.2215 18.2801 11.1162 18.5 10 18.5C8.88376 18.5 7.77846 18.2801 6.74719 17.853C5.71592 17.4258 4.77889 16.7997 3.98959 16.0104C3.20029 15.2211 2.57419 14.2841 2.14702 13.2528C1.71986 12.2215 1.5 11.1162 1.5 10C1.5 8.88376 1.71986 7.77845 2.14703 6.74719C2.57419 5.71592 3.2003 4.77889 3.9896 3.98959C4.7789 3.20029 5.71593 2.57419 6.7472 2.14702Z"
fill="currentColor"
/>
</svg>
),
clipboardCheckmark: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path

View File

@ -54,10 +54,9 @@ 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'
import { CoreDumpManager } from 'lib/coredump'
import { useHotkeys } from 'react-hotkeys-hook'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -319,16 +318,9 @@ export const ModelingMachineProvider = ({
)
await kclManager.executeAstMock(modifiedAst)
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
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
data.faceId
)
return {
@ -343,6 +335,7 @@ export const ModelingMachineProvider = ({
data.plane
)
await kclManager.updateAst(modifiedAst, false)
sceneInfra.camControls.syncDirection = 'clientToEngine'
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
return {
@ -359,9 +352,9 @@ export const ModelingMachineProvider = ({
sourceRange
)
const info = await getSketchOrientationDetails(sketchPathToNode || [])
await sceneInfra.camControls.tweenCameraToQuaternion(
info.quat,
new Vector3(...info.sketchDetails.origin)
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
info?.sketchDetails?.faceId || ''
)
return {
sketchPathToNode: sketchPathToNode || [],

View File

@ -16,7 +16,6 @@ import {
EditorView,
dropCursor,
drawSelection,
ViewUpdate,
} from '@codemirror/view'
import {
indentWithTab,
@ -191,9 +190,6 @@ export const KclEditorPane = () => {
return extensions
}, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
let debounceTimer: ReturnType<typeof setTimeout> | null = null
const updateDelay = 100
return (
<div
id="code-mirror-override"
@ -206,17 +202,6 @@ export const KclEditorPane = () => {
onCreateEditor={(_editorView) =>
editorManager.setEditorView(_editorView)
}
onUpdate={(view: ViewUpdate) => {
// debounce the view update.
// otherwise it is laggy for typing.
if (debounceTimer) {
clearTimeout(debounceTimer)
}
debounceTimer = setTimeout(() => {
editorManager.handleOnViewUpdate(view)
}, updateDelay)
}}
indentWithTab={false}
basicSetup={false}
/>

View File

@ -39,6 +39,8 @@ const CompletionItemKindMap = Object.fromEntries(
) as Record<CompletionItemKind, string>
const changesDelay = 600
let debounceTimer: ReturnType<typeof setTimeout> | null = null
const updateDelay = 100
export class LanguageServerPlugin implements PluginValue {
public client: LanguageServerClient
@ -47,6 +49,7 @@ export class LanguageServerPlugin implements PluginValue {
public workspaceFolders: LSP.WorkspaceFolder[]
private documentVersion: number
private foldingRanges: LSP.FoldingRange[] | null = null
private viewUpdate: ViewUpdate | null = null
private _defferer = deferExecution((code: string) => {
try {
// Update the state (not the editor) with the new code.
@ -57,8 +60,9 @@ export class LanguageServerPlugin implements PluginValue {
},
contentChanges: [{ text: code }],
})
if (editorManager.editorView) {
//editorManager.handleOnViewUpdate(editorManager.editorView)
if (this.viewUpdate) {
editorManager.handleOnViewUpdate(this.viewUpdate)
}
} catch (e) {
console.error(e)
@ -83,14 +87,27 @@ export class LanguageServerPlugin implements PluginValue {
})
}
update({ docChanged }: ViewUpdate) {
if (!docChanged) return
update(viewUpdate: ViewUpdate) {
this.viewUpdate = viewUpdate
if (!viewUpdate.docChanged) {
// debounce the view update.
// otherwise it is laggy for typing.
if (debounceTimer) {
clearTimeout(debounceTimer)
}
debounceTimer = setTimeout(() => {
editorManager.handleOnViewUpdate(viewUpdate)
}, updateDelay)
return
}
const newCode = this.view.state.doc.toString()
codeManager.code = newCode
codeManager.writeToFile()
kclManager.executeCode()
this.sendChange({
documentText: newCode,
})

View File

@ -347,6 +347,16 @@ export class KclManager {
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
enterEditMode() {
enterEditMode(this.programMemory, this.engineCommandManager)
}
exitEditMode() {
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
}
}
function enterEditMode(

View File

@ -266,9 +266,9 @@ const mySk1 = startSketchAt([0, 0])
|> rx(45, %)
/*
one more for good measure
*/
/*
one more for good measure
*/
`
const { ast } = code2ast(code)
const recasted = recast(ast)
@ -285,7 +285,7 @@ const mySk1 = startSketchAt([0, 0])
// and another with just white space between others below
|> ry(45, %)
|> rx(45, %)
/* one more for good measure */
/* one more for good measure */
`)
})
})

View File

@ -1171,7 +1171,10 @@ export class EngineCommandManager {
type: 'receive-reliable',
data: message,
id,
cmd_type: command?.commandType || this.lastArtifactMap[id]?.commandType,
cmd_type:
command?.commandType ||
this.lastArtifactMap[id]?.commandType ||
sceneCommand?.commandType,
})
Object.values(this.subscriptions[modelingResponse.type] || {}).forEach(
(callback) => callback(modelingResponse)

View File

@ -102,7 +102,7 @@ describe('testing changeSketchArguments', () => {
|> startProfileAt([0, 0], %)
|> ${line}
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
// |> rx(45, %)
`
const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange)

59
src/lib/circleTool.ts Normal file
View File

@ -0,0 +1,59 @@
import {
createArrayExpression,
createBinaryExpression,
createCallExpressionStdLib,
createLiteral,
createPipeSubstitution,
} from 'lang/modifyAst'
import { roundOff } from './utils'
import {
ArrayExpression,
CallExpression,
Literal,
PipeExpression,
} from 'lang/wasm'
/**
* Returns AST expressions for this KCL code:
* const yo = startSketchOn('XY')
* |> circle([0, 0], 0, %)
*/
export const circleAsCallExpressions = (
circleOrigin: [number, number],
tags: [string]
) => [
createCallExpressionStdLib('circle', [
createArrayExpression([
createLiteral(roundOff(circleOrigin[0])),
createLiteral(roundOff(circleOrigin[1])),
]),
createLiteral(10),
createPipeSubstitution(),
createLiteral(tags[0]),
]),
]
/**
* Mutates the pipeExpression to update the circle sketch
* @param pipeExpression
* @param x
* @param y
* @param tag
*/
export function updateCircleSketch(
pipeExpression: PipeExpression,
x: number,
y: number,
tag: string
) {
const circle = pipeExpression.body[1] as CallExpression
const origin = circle.arguments[0] as ArrayExpression
const originX = (origin.elements[0] as Literal).value
const originY = (origin.elements[1] as Literal).value
const radius = roundOff(
Math.sqrt((x - Number(originX)) ** 2 + (y - Number(originY)) ** 2)
)
;(circle.arguments[1] as Literal) = createLiteral(radius)
}

View File

@ -20,3 +20,15 @@ export const sceneEntitiesManager = new SceneEntities(engineCommandManager)
// This needs to be after sceneInfra and engineCommandManager are is created.
export const editorManager = new EditorManager()
if (typeof window !== 'undefined') {
;(window as any).engineCommandManager = engineCommandManager
;(window as any).kclManager = kclManager
;(window as any).sceneInfra = sceneInfra
;(window as any).sceneEntitiesManager = sceneEntitiesManager
;(window as any).editorManager = editorManager
;(window as any).enableMousePositionLogs = () =>
document.addEventListener('mousemove', (e) =>
console.log(`await page.mouse.click(${e.clientX}, ${e.clientY})`)
)
}

File diff suppressed because one or more lines are too long

View File

@ -927,7 +927,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.14"
version = "0.1.16"
dependencies = [
"Inflector",
"anyhow",
@ -1854,7 +1854,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.47"
version = "0.1.49"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1921,9 +1921,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.67"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc460442c165c8e707b1154551cefd08938d10bb80c78940e10cd9869487c325"
checksum = "ddc922f0da3abc22661bf49423c9bfcc02ce6ae92dae007ede6990874789545b"
dependencies = [
"anyhow",
"async-trait",
@ -1959,9 +1959,9 @@ dependencies = [
[[package]]
name = "kittycad-execution-plan"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf8ffb148bd09de8889a8a2b3075a23ee86446c3a6e1c6dcf66b40fdc778158"
checksum = "ae99665cd699f8800da8ea4b01889c0c9c61619d2a9dc62d1d5028f1b21110bd"
dependencies = [
"bytes",
"gltf-json",
@ -2004,9 +2004,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.2.19"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bb4946fa7bbcfd5270448e31834286c62e30e0f4599ef102a5b63e63b1f50b"
checksum = "f93f7904109e445ab3dcfbaa4f0f4396d1df22c701075cdce4a7e491701796af"
dependencies = [
"anyhow",
"chrono",
@ -2044,9 +2044,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-session"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae9bc47fcc3cc30727b35e738c35666b97e1e5f48f3f4c60ddaeccb69b66559"
checksum = "241d45ca828af2953bf538f312a3e9d6fa7fe83055c90f6b6b9ffa76f53b6285"
dependencies = [
"futures",
"kittycad",
@ -3963,18 +3963,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.58"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.58"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
"proc-macro2",
"quote",

View File

@ -61,12 +61,12 @@ members = [
]
[workspace.dependencies]
kittycad = { version = "0.2.67", default-features = false, features = ["js", "requests"] }
kittycad-execution-plan = "0.1.4"
kittycad = { version = "0.3.0", default-features = false, features = ["js", "requests"] }
kittycad-execution-plan = "0.1.5"
kittycad-execution-plan-macros = "0.1.9"
kittycad-execution-plan-traits = "0.1.14"
kittycad-modeling-cmds = "0.2.19"
kittycad-modeling-session = "0.1.3"
kittycad-modeling-cmds = "0.2.20"
kittycad-modeling-session = "0.1.4"
[[test]]
name = "executor"

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.14"
version = "0.1.16"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -788,17 +788,14 @@ fn generate_code_block_test(
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
// Zoom to fit.
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D { x: 0.0, y: 0.0, z: 0.0 },
up: kittycad::types::Point3D { x: 0.0, y: 0.0, z: 1.0 },
vantage: kittycad::types::Point3D { x: 0.0, y: -x, z: y },
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await.unwrap();

View File

@ -33,28 +33,13 @@ mod test_examples_show {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await
@ -125,28 +110,13 @@ mod test_examples_show {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -33,28 +33,13 @@ mod test_examples_show {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -34,28 +34,13 @@ mod test_examples_my_func {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await
@ -126,28 +111,13 @@ mod test_examples_my_func {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -35,28 +35,13 @@ mod test_examples_import {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await
@ -128,28 +113,13 @@ mod test_examples_import {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -34,28 +34,13 @@ mod test_examples_line_to {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await
@ -126,28 +111,13 @@ mod test_examples_line_to {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -33,28 +33,13 @@ mod test_examples_min {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await
@ -125,28 +110,13 @@ mod test_examples_min {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -33,28 +33,13 @@ mod test_examples_show {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -33,28 +33,13 @@ mod test_examples_import {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -33,28 +33,13 @@ mod test_examples_import {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -33,28 +33,13 @@ mod test_examples_import {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -33,28 +33,13 @@ mod test_examples_show {
.await
.unwrap();
ctx.run(program, None).await.unwrap();
let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
crate::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
center: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 0.0,
},
up: kittycad::types::Point3D {
x: 0.0,
y: 0.0,
z: 1.0,
},
vantage: kittycad::types::Point3D {
x: 0.0,
y: -x,
z: y,
},
sequence: None,
kittycad::types::ModelingCmd::ZoomToFit {
object_ids: Default::default(),
padding: 0.1,
},
)
.await

View File

@ -14,7 +14,7 @@ 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.58"
thiserror = "1.0.59"
tokio = { version = "1.37.0", features = ["macros", "rt"] }
twenty-twenty = "0.7.0"
uuid = "1.8"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.1.47"
version = "0.1.49"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -19,7 +19,7 @@ chrono = "0.4.38"
clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], optional = true }
dashmap = "5.5.3"
databake = { version = "0.1.7", features = ["derive"] }
derive-docs = { version = "0.1.14", path = "../derive-docs" }
derive-docs = { version = "0.1.16", path = "../derive-docs" }
form_urlencoded = "1.2.1"
futures = { version = "0.3.30" }
git_rev = "0.1.0"
@ -36,7 +36,7 @@ schemars = { version = "0.8.16", features = ["impl_json_schema", "url", "uuid1"]
serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.116"
sha2 = "0.10.8"
thiserror = "1.0.58"
thiserror = "1.0.59"
ts-rs = { version = "7.1.1", features = ["uuid-impl", "url-impl"] }
url = { version = "2.5.0", features = ["serde"] }
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }

View File

@ -1864,7 +1864,13 @@ impl ObjectExpression {
"{{ {} }}",
self.properties
.iter()
.map(|prop| { format!("{}: {}", prop.key.name, prop.value.recast(options, 0, false)) })
.map(|prop| {
format!(
"{}: {}",
prop.key.name,
prop.value.recast(options, indentation_level + 1, is_in_pipe)
)
})
.collect::<Vec<String>>()
.join(", ")
);
@ -1880,7 +1886,13 @@ impl ObjectExpression {
inner_indentation,
self.properties
.iter()
.map(|prop| { format!("{}: {}", prop.key.name, prop.value.recast(options, 0, false)) })
.map(|prop| {
format!(
"{}: {}",
prop.key.name,
prop.value.recast(options, indentation_level + 1, is_in_pipe)
)
})
.collect::<Vec<String>>()
.join(format!(",\n{}", inner_indentation).as_str()),
if is_in_pipe {
@ -2657,7 +2669,12 @@ impl PipeExpression {
let non_code_meta = self.non_code_meta.clone();
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
for val in non_code_meta_value {
let formatted = val.format(&indentation).trim_end_matches('\n').to_string();
let formatted = if val.end == self.end {
let indentation = options.get_indentation(indentation_level);
val.format(&indentation).trim_end_matches('\n').to_string()
} else {
val.format(&indentation).trim_end_matches('\n').to_string()
};
if let NonCodeValue::BlockComment { .. } = val.value {
s += "\n";
s += &formatted;
@ -3256,6 +3273,244 @@ fn ghi = (x) => {
assert_eq!(recasted, r#""#);
}
#[test]
fn test_recast_large_file() {
let some_program_string = r#"// define constants
const radius = 6.0
const width = 144.0
const length = 83.0
const depth = 45.0
const thk = 5
const hole_diam = 5
// define a rectangular shape func
fn rectShape = (pos, w, l) => {
const rr = startSketchOn('xy')
|> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %)
|> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, "edge1")
|> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, "edge2")
|> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, "edge3")
|> close(%, "edge4")
return rr
}
// build the body of the focusrite scarlett solo gen 4
// only used for visualization
const scarlett_body = rectShape([0, 0], width, length)
|> extrude(depth, %)
|> fillet({
radius: radius,
tags: [
getEdge("edge2", %),
getEdge("edge4", %),
getOppositeEdge("edge2", %),
getOppositeEdge("edge4", %)
]
}, %)
// build the bracket sketch around the body
fn bracketSketch = (w, d, t) => {
const s = startSketchOn({
plane: {
origin: { x: 0, y: length / 2 + thk, z: 0 },
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 0, z: 1 },
z_axis: { x: 0, y: 1, z: 0 }
}
})
|> startProfileAt([-w / 2 - t, d + t], %)
|> lineTo([-w / 2 - t, -t], %, "edge1")
|> lineTo([w / 2 + t, -t], %, "edge2")
|> lineTo([w / 2 + t, d + t], %, "edge3")
|> lineTo([w / 2, d + t], %, "edge4")
|> lineTo([w / 2, 0], %, "edge5")
|> lineTo([-w / 2, 0], %, "edge6")
|> lineTo([-w / 2, d + t], %, "edge7")
|> close(%, "edge8")
return s
}
// build the body of the bracket
const bracket_body = bracketSketch(width, depth, thk)
|> extrude(length + 10, %)
|> fillet({
radius: radius,
tags: [
getNextAdjacentEdge("edge7", %),
getNextAdjacentEdge("edge2", %),
getNextAdjacentEdge("edge3", %),
getNextAdjacentEdge("edge6", %)
]
}, %)
// build the tabs of the mounting bracket (right side)
const tabs_r = startSketchOn({
plane: {
origin: { x: 0, y: 0, z: depth + thk },
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
z_axis: { x: 0, y: 0, z: 1 }
}
})
|> startProfileAt([width / 2 + thk, length / 2 + thk], %)
|> line([10, -5], %)
|> line([0, -10], %)
|> line([-10, -5], %)
|> close(%)
|> hole(circle([
width / 2 + thk + hole_diam,
length / 2 - hole_diam
], hole_diam / 2, %), %)
|> extrude(-thk, %)
|> patternLinear3d({
axis: [0, -1, 0],
repetitions: 1,
distance: length - 10
}, %)
// build the tabs of the mounting bracket (left side)
const tabs_l = startSketchOn({
plane: {
origin: { x: 0, y: 0, z: depth + thk },
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
z_axis: { x: 0, y: 0, z: 1 }
}
})
|> startProfileAt([-width / 2 - thk, length / 2 + thk], %)
|> line([-10, -5], %)
|> line([0, -10], %)
|> line([10, -5], %)
|> close(%)
|> hole(circle([
-width / 2 - thk - hole_diam,
length / 2 - hole_diam
], hole_diam / 2, %), %)
|> extrude(-thk, %)
|> patternLinear3d({
axis: [0, -1, 0],
repetitions: 1,
distance: length - 10
}, %)
"#;
let tokens = crate::token::lexer(some_program_string).unwrap();
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
println!("{:#?}", program);
let recasted = program.recast(&Default::default(), 0);
// Its VERY important this comes back with zero new lines.
assert_eq!(
recasted,
r#"// define constants
const radius = 6.0
const width = 144.0
const length = 83.0
const depth = 45.0
const thk = 5
const hole_diam = 5
// define a rectangular shape func
fn rectShape = (pos, w, l) => {
const rr = startSketchOn('xy')
|> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %)
|> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, "edge1")
|> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, "edge2")
|> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, "edge3")
|> close(%, "edge4")
return rr
}
// build the body of the focusrite scarlett solo gen 4
// only used for visualization
const scarlett_body = rectShape([0, 0], width, length)
|> extrude(depth, %)
|> fillet({
radius: radius,
tags: [
getEdge("edge2", %),
getEdge("edge4", %),
getOppositeEdge("edge2", %),
getOppositeEdge("edge4", %)
]
}, %)
// build the bracket sketch around the body
fn bracketSketch = (w, d, t) => {
const s = startSketchOn({
plane: {
origin: { x: 0, y: length / 2 + thk, z: 0 },
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 0, z: 1 },
z_axis: { x: 0, y: 1, z: 0 }
}
})
|> startProfileAt([-w / 2 - t, d + t], %)
|> lineTo([-w / 2 - t, -t], %, "edge1")
|> lineTo([w / 2 + t, -t], %, "edge2")
|> lineTo([w / 2 + t, d + t], %, "edge3")
|> lineTo([w / 2, d + t], %, "edge4")
|> lineTo([w / 2, 0], %, "edge5")
|> lineTo([-w / 2, 0], %, "edge6")
|> lineTo([-w / 2, d + t], %, "edge7")
|> close(%, "edge8")
return s
}
// build the body of the bracket
const bracket_body = bracketSketch(width, depth, thk)
|> extrude(length + 10, %)
|> fillet({
radius: radius,
tags: [
getNextAdjacentEdge("edge7", %),
getNextAdjacentEdge("edge2", %),
getNextAdjacentEdge("edge3", %),
getNextAdjacentEdge("edge6", %)
]
}, %)
// build the tabs of the mounting bracket (right side)
const tabs_r = startSketchOn({
plane: {
origin: { x: 0, y: 0, z: depth + thk },
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
z_axis: { x: 0, y: 0, z: 1 }
}
})
|> startProfileAt([width / 2 + thk, length / 2 + thk], %)
|> line([10, -5], %)
|> line([0, -10], %)
|> line([-10, -5], %)
|> close(%)
|> hole(circle([
width / 2 + thk + hole_diam,
length / 2 - hole_diam
], hole_diam / 2, %), %)
|> extrude(-thk, %)
|> patternLinear3d({
axis: [0, -1, 0],
repetitions: 1,
distance: length - 10
}, %)
// build the tabs of the mounting bracket (left side)
const tabs_l = startSketchOn({
plane: {
origin: { x: 0, y: 0, z: depth + thk },
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
z_axis: { x: 0, y: 0, z: 1 }
}
})
|> startProfileAt([-width / 2 - thk, length / 2 + thk], %)
|> line([-10, -5], %)
|> line([0, -10], %)
|> line([10, -5], %)
|> close(%)
|> hole(circle([
-width / 2 - thk - hole_diam,
length / 2 - hole_diam
], hole_diam / 2, %), %)
|> extrude(-thk, %)
|> patternLinear3d({
axis: [0, -1, 0],
repetitions: 1,
distance: length - 10
}, %)
"#
);
}
#[test]
fn test_recast_nested_var_declaration_in_fn_body() {
let some_program_string = r#"fn cube = (pos, scale) => {
@ -3543,7 +3798,7 @@ const mySk1 = startSketchOn('XY')
// and another with just white space between others below
|> ry(45, %)
|> rx(45, %)
// one more for good measure
// one more for good measure
"#
);
}

View File

@ -736,10 +736,12 @@ pub fn completion_item_from_enum_schema(
anyhow::bail!("expected at least one enum value: {:#?}", o);
}
let label = enum_values[0].to_string();
let serde_json::Value::String(ref enum_value) = enum_values[0] else {
anyhow::bail!("expected string enum value: {:#?}", enum_values[0]);
};
Ok(CompletionItem {
label,
label: enum_value.to_string(),
label_details: None,
kind: Some(kind),
detail: Some(description.to_string()),

View File

@ -124,7 +124,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
let batched_requests = WebSocketRequest::ModelingCmdBatchReq {
requests,
batch_id: uuid::Uuid::new_v4(),
responses: Some(false),
responses: false,
};
let final_req = if self.batch().lock().unwrap().len() == 1 {
@ -365,6 +365,7 @@ pub fn is_cmd_with_return_values(cmd: &kittycad::types::ModelingCmd) -> bool {
| kittycad::types::ModelingCmd::EntityGetDistance { .. }
| kittycad::types::ModelingCmd::EntityLinearPattern { .. }
| kittycad::types::ModelingCmd::EntityCircularPattern { .. }
| kittycad::types::ModelingCmd::ZoomToFit { .. }
| kittycad::types::ModelingCmd::Solid3DGetExtrusionFaceInfo { .. }) = cmd
else {
return false;

View File

@ -793,6 +793,56 @@ st"#
}
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_completions_const_raw() {
let server = kcl_lsp_server(false).await.unwrap();
// Send open file.
server
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
text_document: tower_lsp::lsp_types::TextDocumentItem {
uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(),
version: 1,
text: r#"con"#.to_string(),
},
})
.await;
// Send completion request.
let completions = server
.completion(tower_lsp::lsp_types::CompletionParams {
text_document_position: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
},
context: None,
partial_result_params: Default::default(),
work_done_progress_params: Default::default(),
})
.await
.unwrap()
.unwrap();
// Check the completions.
if let tower_lsp::lsp_types::CompletionResponse::Array(completions) = completions {
assert!(completions.len() > 10);
// Find the one with label "const".
let const_completion = completions
.iter()
.find(|completion| completion.label == "const")
.unwrap();
assert_eq!(
const_completion.kind,
Some(tower_lsp::lsp_types::CompletionItemKind::KEYWORD)
);
} else {
panic!("Expected array of completions");
}
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_on_hover() {
let server = kcl_lsp_server(false).await.unwrap();

View File

@ -1517,12 +1517,12 @@ async fn inner_bezier_curve(
ModelingCmd::ExtendPath {
path: sketch_group.id,
segment: kittycad::types::PathSegment::Bezier {
control1: Point3D {
control_1: Point3D {
x: data.control1[0],
y: data.control1[1],
z: 0.0,
},
control2: Point3D {
control_2: Point3D {
x: data.control2[0],
y: data.control2[1],
z: 0.0,

View File

@ -838,24 +838,3 @@ pub fn get_tangent_point_from_previous_arc(
tangential_angle.to_radians().sin() * 10.0 + last_arc_end[1],
]
}
fn unit_length_to_mm(base_unit: kittycad::types::UnitLength) -> f64 {
match base_unit {
kittycad::types::UnitLength::Mm => 1.0,
kittycad::types::UnitLength::Cm => 10.0,
kittycad::types::UnitLength::M => 1000.0,
kittycad::types::UnitLength::In => 25.4,
kittycad::types::UnitLength::Ft => 304.8,
kittycad::types::UnitLength::Yd => 914.4,
}
}
pub fn get_camera_zoom_magnitude_per_unit_length(unit: kittycad::types::UnitLength) -> (f64, f64) {
let base_radius = 5.6_f64;
let cam_height_distance_ratio = 0.5_f64;
let length = unit_length_to_mm(unit) * base_radius * 20.0;
let ang = cam_height_distance_ratio.atan();
let x = ang.cos() * length;
let y = ang.sin() * length;
(x, y)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 19 KiB

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