Compare commits
17 Commits
parser-in-
...
franknoiro
| Author | SHA1 | Date | |
|---|---|---|---|
| 29070a9b04 | |||
| e3861f9380 | |||
| 2d8d29b345 | |||
| 00da062586 | |||
| aafbaf6c50 | |||
| 2894c84a4e | |||
| c01084feb0 | |||
| c461db5f54 | |||
| 03fcb73aca | |||
| 8065e7e51a | |||
| 2d0ac249df | |||
| 3d73b82c23 | |||
| 0b235dc1cd | |||
| 0857415793 | |||
| 1da4fd03ef | |||
| 39d84c12ab | |||
| 537d86c8ff |
@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@babel/preset-env"],
|
||||
presets: ['@babel/preset-env'],
|
||||
}
|
||||
|
||||
@ -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')
|
||||
@ -328,9 +329,7 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
/* Ignore this test for now since its causing engine to crash
|
||||
*
|
||||
* test('if your kcl gets an error from the engine it is inlined', async ({
|
||||
test('if your kcl gets an error from the engine it is inlined', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = getUtils(page)
|
||||
@ -349,7 +348,7 @@ const sketch001 = startSketchOn(box, "revolveAxis")
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> line([2, 0], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> close(%)
|
||||
|> revolve({
|
||||
axis: getEdge('revolveAxis', box),
|
||||
@ -364,7 +363,7 @@ angle: 90
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
u.openDebugPanel()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
@ -378,7 +377,7 @@ angle: 90
|
||||
'sketch profile must lie entirely on one side of the revolution axis'
|
||||
)
|
||||
).toBeVisible()
|
||||
})*/
|
||||
})
|
||||
|
||||
test('executes on load', async ({ page }) => {
|
||||
const u = getUtils(page)
|
||||
@ -566,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')
|
||||
@ -736,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 = () =>
|
||||
@ -761,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'))
|
||||
@ -768,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')
|
||||
@ -786,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
|
||||
@ -799,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
|
||||
@ -809,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()
|
||||
@ -817,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')
|
||||
@ -833,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()
|
||||
@ -875,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', () => {
|
||||
@ -1065,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}, %)
|
||||
@ -1080,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)
|
||||
@ -1106,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, '')
|
||||
)
|
||||
@ -1121,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,
|
||||
''
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
@ -1339,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()
|
||||
@ -1367,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 }
|
||||
@ -1386,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()
|
||||
|
||||
@ -1414,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 (
|
||||
@ -1535,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()
|
||||
@ -1576,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])
|
||||
@ -1598,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
|
||||
@ -1610,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()
|
||||
@ -1624,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, %)`)
|
||||
})
|
||||
@ -1661,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()
|
||||
|
||||
@ -1673,3 +1726,42 @@ test('Can code mod a line length', async ({ page }) => {
|
||||
`const length001 = 20const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> xLine(-length001, %)`
|
||||
)
|
||||
})
|
||||
|
||||
test('Extrude from command bar selects extrude line after', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click the line of code for xLine.
|
||||
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
` |> extrude(5 + 7, %)`
|
||||
)
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
@ -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' })
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.17.3",
|
||||
"version": "0.18.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.16.0",
|
||||
@ -8,7 +8,7 @@
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@headlessui/react": "^1.7.19",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@kittycad/lib": "^0.0.58",
|
||||
"@lezer/javascript": "^1.4.9",
|
||||
@ -84,8 +84,8 @@
|
||||
"test:e2e:tauri": "E2E_TAURI_ENABLED=true TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' wdio run wdio.conf.ts",
|
||||
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||
"fmt": "prettier --write ./src && prettier --write ./e2e",
|
||||
"fmt-check": "prettier --check ./src && prettier --check ./e2e",
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e",
|
||||
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
||||
@ -132,6 +132,7 @@
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/web-worker": "^1.5.0",
|
||||
"@wdio/cli": "^8.24.3",
|
||||
"@wdio/globals": "^8.36.0",
|
||||
"@wdio/local-runner": "^8.36.0",
|
||||
|
||||
@ -49,8 +49,6 @@ export default defineConfig({
|
||||
// use: { ...devices['Desktop Chrome'] },
|
||||
// },
|
||||
|
||||
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
@ -78,4 +76,4 @@ export default defineConfig({
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
8
src-tauri/Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -55,5 +55,5 @@
|
||||
}
|
||||
},
|
||||
"productName": "Zoo Modeling App",
|
||||
"version": "0.17.3"
|
||||
"version": "0.18.1"
|
||||
}
|
||||
|
||||
@ -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') &&
|
||||
|
||||
@ -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',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useStore } from 'useStore'
|
||||
import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
|
||||
import { ReactCameraProperties } from './CameraControls'
|
||||
import { throttle } from 'lib/utils'
|
||||
@ -47,10 +46,6 @@ export const ClientSideScene = ({
|
||||
const canvasRef = useRef<HTMLDivElement>(null)
|
||||
const { state, send, context } = useModelingContext()
|
||||
const { hideClient, hideServer } = useShouldHideScene()
|
||||
const { setHighlightRange } = useStore((s) => ({
|
||||
setHighlightRange: s.setHighlightRange,
|
||||
highlightRange: s.highlightRange,
|
||||
}))
|
||||
|
||||
// Listen for changes to the camera controls setting
|
||||
// and update the client-side scene's controls accordingly.
|
||||
@ -69,7 +64,6 @@ export const ClientSideScene = ({
|
||||
const canvas = canvasRef.current
|
||||
canvas.appendChild(sceneInfra.renderer.domElement)
|
||||
sceneInfra.animate()
|
||||
sceneInfra.setHighlightCallback(setHighlightRange)
|
||||
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
|
||||
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
||||
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)
|
||||
|
||||
@ -57,6 +57,7 @@ import {
|
||||
kclManager,
|
||||
sceneInfra,
|
||||
codeManager,
|
||||
editorManager,
|
||||
} from 'lib/singletons'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { executeAst, useStore } from 'useStore'
|
||||
@ -96,6 +97,7 @@ import {
|
||||
getRectangleCallExpressions,
|
||||
updateRectangleSketch,
|
||||
} from 'lib/rectangleTool'
|
||||
import { circleAsCallExpressions, updateCircleSketch } from 'lib/circleTool'
|
||||
|
||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||
|
||||
@ -214,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,
|
||||
@ -578,7 +581,7 @@ export class SceneEntities {
|
||||
...this.mouseEnterLeaveCallbacks(),
|
||||
})
|
||||
}
|
||||
setupRectangleOriginListener = () => {
|
||||
setupOriginListener = (type: 'circle' | 'rectangle') => {
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: (args) => {
|
||||
const twoD = args.intersectionPoint?.twoD
|
||||
@ -587,7 +590,7 @@ export class SceneEntities {
|
||||
return
|
||||
}
|
||||
sceneInfra.modelingSend({
|
||||
type: 'Add rectangle origin',
|
||||
type: `Add ${type} origin`,
|
||||
data: [twoD.x, twoD.y],
|
||||
})
|
||||
},
|
||||
@ -745,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,
|
||||
@ -1323,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
|
||||
@ -1366,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
|
||||
@ -1397,6 +1552,7 @@ export class SceneEntities {
|
||||
plane: planeString,
|
||||
zAxis,
|
||||
yAxis,
|
||||
planeId: faceResult[1],
|
||||
},
|
||||
})
|
||||
},
|
||||
@ -1423,7 +1579,7 @@ export class SceneEntities {
|
||||
parent.userData.pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
sceneInfra.highlightCallback([node.start, node.end])
|
||||
editorManager.setHighlightRange([node.start, node.end])
|
||||
const yellow = 0xffff00
|
||||
colorSegment(selected, yellow)
|
||||
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||
@ -1459,10 +1615,10 @@ export class SceneEntities {
|
||||
}
|
||||
return
|
||||
}
|
||||
sceneInfra.highlightCallback([0, 0])
|
||||
editorManager.setHighlightRange([0, 0])
|
||||
},
|
||||
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
||||
sceneInfra.highlightCallback([0, 0])
|
||||
editorManager.setHighlightRange([0, 0])
|
||||
const parent = getParentGroup(selected, [
|
||||
STRAIGHT_SEGMENT,
|
||||
TANGENTIAL_ARC_TO_SEGMENT,
|
||||
@ -1680,7 +1836,7 @@ export async function getSketchOrientationDetails(
|
||||
sketchPathToNode: PathToNode
|
||||
): Promise<{
|
||||
quat: Quaternion
|
||||
sketchDetails: SketchDetails
|
||||
sketchDetails: SketchDetails & { faceId?: string }
|
||||
}> {
|
||||
const sketchGroup = sketchGroupFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
@ -1696,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
|
||||
@ -1724,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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1732,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)
|
||||
|
||||
@ -24,7 +24,6 @@ import {
|
||||
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import * as TWEEN from '@tweenjs/tween.js'
|
||||
import { SourceRange } from 'lang/wasm'
|
||||
import { Axis } from 'lib/selections'
|
||||
import { type BaseUnit } from 'lib/settings/settingsTypes'
|
||||
import { CameraControls } from './CameraControls'
|
||||
@ -149,10 +148,6 @@ export class SceneInfra {
|
||||
onMouseLeave: () => {},
|
||||
})
|
||||
}
|
||||
highlightCallback: (a: SourceRange) => void = () => {}
|
||||
setHighlightCallback(cb: (a: SourceRange) => void) {
|
||||
this.highlightCallback = cb
|
||||
}
|
||||
|
||||
modelingSend: SendType = (() => {}) as any
|
||||
setSend(send: SendType) {
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { editorManager, kclManager } from 'lib/singletons'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useStore } from 'useStore'
|
||||
|
||||
export function AstExplorer() {
|
||||
const setHighlightRange = useStore((s) => s.setHighlightRange)
|
||||
const { context } = useModelingContext()
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
// TODO maybe need to have callback to make sure it stays in sync
|
||||
@ -42,7 +40,7 @@ export function AstExplorer() {
|
||||
<div
|
||||
className="h-full relative"
|
||||
onMouseLeave={(e) => {
|
||||
setHighlightRange([0, 0])
|
||||
editorManager.setHighlightRange([0, 0])
|
||||
}}
|
||||
>
|
||||
<pre className="text-xs">
|
||||
@ -88,7 +86,6 @@ function DisplayObj({
|
||||
filterKeys: string[]
|
||||
node: any
|
||||
}) {
|
||||
const setHighlightRange = useStore((s) => s.setHighlightRange)
|
||||
const { send } = useModelingContext()
|
||||
const ref = useRef<HTMLPreElement>(null)
|
||||
const [hasCursor, setHasCursor] = useState(false)
|
||||
@ -112,12 +109,12 @@ function DisplayObj({
|
||||
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
||||
}`}
|
||||
onMouseEnter={(e) => {
|
||||
setHighlightRange([obj?.start || 0, obj.end])
|
||||
editorManager.setHighlightRange([obj?.start || 0, obj.end])
|
||||
e.stopPropagation()
|
||||
}}
|
||||
onMouseMove={(e) => {
|
||||
e.stopPropagation()
|
||||
setHighlightRange([obj?.start || 0, obj.end])
|
||||
editorManager.setHighlightRange([obj?.start || 0, obj.end])
|
||||
}}
|
||||
onClick={(e) => {
|
||||
send({
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useMachine } from '@xstate/react'
|
||||
import { editorManager } from 'lib/singletons'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { createContext } from 'react'
|
||||
import { createContext, useEffect } from 'react'
|
||||
import { EventFrom, StateFrom } from 'xstate'
|
||||
|
||||
type CommandsContextType = {
|
||||
@ -30,6 +31,10 @@ export const CommandBarProvider = ({
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
editorManager.setCommandBarSend(commandBarSend)
|
||||
})
|
||||
|
||||
return (
|
||||
<CommandsContext.Provider
|
||||
value={{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
sceneInfra,
|
||||
engineCommandManager,
|
||||
codeManager,
|
||||
editorManager,
|
||||
} from 'lib/singletons'
|
||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||
import {
|
||||
@ -53,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>
|
||||
@ -98,17 +98,6 @@ export const ModelingMachineProvider = ({
|
||||
)
|
||||
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
|
||||
|
||||
const {
|
||||
isShiftDown,
|
||||
editorView,
|
||||
setLastCodeMirrorSelectionUpdatedFromScene,
|
||||
} = useStore((s) => ({
|
||||
isShiftDown: s.isShiftDown,
|
||||
editorView: s.editorView,
|
||||
setLastCodeMirrorSelectionUpdatedFromScene:
|
||||
s.setLastCodeMirrorSelectionUpdatedFromScene,
|
||||
}))
|
||||
|
||||
// Settings machine setup
|
||||
// const retrievedSettings = useRef(
|
||||
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
||||
@ -135,29 +124,33 @@ export const ModelingMachineProvider = ({
|
||||
'Set selection': assign(({ selectionRanges }, event) => {
|
||||
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
||||
const setSelections = event.data
|
||||
if (!editorView) return {}
|
||||
if (!editorManager.editorView) return {}
|
||||
const dispatchSelection = (selection?: EditorSelection) => {
|
||||
if (!selection) return // TODO less of hack for the below please
|
||||
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
|
||||
setTimeout(() => editorView.dispatch({ selection }))
|
||||
editorManager.lastSelectionEvent = Date.now()
|
||||
setTimeout(() => {
|
||||
if (editorManager.editorView) {
|
||||
editorManager.editorView.dispatch({ selection })
|
||||
}
|
||||
})
|
||||
}
|
||||
let selections: Selections = {
|
||||
codeBasedSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
if (setSelections.selectionType === 'singleCodeCursor') {
|
||||
if (!setSelections.selection && isShiftDown) {
|
||||
} else if (!setSelections.selection && !isShiftDown) {
|
||||
if (!setSelections.selection && editorManager.isShiftDown) {
|
||||
} else if (!setSelections.selection && !editorManager.isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && !isShiftDown) {
|
||||
} else if (setSelections.selection && !editorManager.isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [setSelections.selection],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && isShiftDown) {
|
||||
} else if (setSelections.selection && editorManager.isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [
|
||||
...selectionRanges.codeBasedSelections,
|
||||
@ -180,6 +173,7 @@ export const ModelingMachineProvider = ({
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
)
|
||||
updateSceneObjectColors()
|
||||
|
||||
return {
|
||||
selectionRanges: selections,
|
||||
}
|
||||
@ -192,7 +186,7 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
|
||||
if (setSelections.selectionType === 'otherSelection') {
|
||||
if (isShiftDown) {
|
||||
if (editorManager.isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
otherSelections: [setSelections.selection],
|
||||
@ -324,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 {
|
||||
@ -348,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 {
|
||||
@ -364,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 || [],
|
||||
@ -516,6 +504,19 @@ export const ModelingMachineProvider = ({
|
||||
})
|
||||
}, [modelingSend])
|
||||
|
||||
// Give the state back to the editorManager.
|
||||
useEffect(() => {
|
||||
editorManager.modelingSend = modelingSend
|
||||
}, [modelingSend])
|
||||
|
||||
useEffect(() => {
|
||||
editorManager.modelingEvent = modelingState.event
|
||||
}, [modelingState.event])
|
||||
|
||||
useEffect(() => {
|
||||
editorManager.selectionRanges = modelingState.context.selectionRanges
|
||||
}, [modelingState.context.selectionRanges])
|
||||
|
||||
useStateMachineCommands({
|
||||
machineId: 'modeling',
|
||||
state: modelingState,
|
||||
|
||||
@ -1,13 +1,8 @@
|
||||
import { undo, redo } from '@codemirror/commands'
|
||||
import ReactCodeMirror from '@uiw/react-codemirror'
|
||||
import { TEST } from 'env'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useStore } from 'useStore'
|
||||
import { processCodeMirrorRanges } from 'lib/selections'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
|
||||
import { lineHighlightField } from 'editor/highlightextension'
|
||||
import { roundOff } from 'lib/utils'
|
||||
@ -21,7 +16,6 @@ import {
|
||||
EditorView,
|
||||
dropCursor,
|
||||
drawSelection,
|
||||
ViewUpdate,
|
||||
} from '@codemirror/view'
|
||||
import {
|
||||
indentWithTab,
|
||||
@ -29,7 +23,7 @@ import {
|
||||
historyKeymap,
|
||||
history,
|
||||
} from '@codemirror/commands'
|
||||
import { lintGutter, lintKeymap, linter } from '@codemirror/lint'
|
||||
import { lintGutter, lintKeymap } from '@codemirror/lint'
|
||||
import {
|
||||
foldGutter,
|
||||
foldKeymap,
|
||||
@ -39,25 +33,20 @@ import {
|
||||
syntaxHighlighting,
|
||||
defaultHighlightStyle,
|
||||
} from '@codemirror/language'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import interact from '@replit/codemirror-interact'
|
||||
import { engineCommandManager, sceneInfra, kclManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { kclManager, editorManager, codeManager } from 'lib/singletons'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { paths } from 'lib/paths'
|
||||
import makeUrlPathRelative from 'lib/makeUrlPathRelative'
|
||||
import { useLspContext } from 'components/LspProvider'
|
||||
import { Prec, EditorState, Extension, SelectionRange } from '@codemirror/state'
|
||||
import { Prec, EditorState, Extension } from '@codemirror/state'
|
||||
import {
|
||||
closeBrackets,
|
||||
closeBracketsKeymap,
|
||||
completionKeymap,
|
||||
hasNextSnippetField,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { kclErrorsToDiagnostics } from 'lang/errors'
|
||||
|
||||
export const editorShortcutMeta = {
|
||||
formatCode: {
|
||||
@ -77,13 +66,6 @@ export const KclEditorPane = () => {
|
||||
context.app.theme.current === Themes.System
|
||||
? getSystemTheme()
|
||||
: context.app.theme.current
|
||||
const { editorView, setEditorView, isShiftDown } = useStore((s) => ({
|
||||
editorView: s.editorView,
|
||||
setEditorView: s.setEditorView,
|
||||
isShiftDown: s.isShiftDown,
|
||||
}))
|
||||
const { editorCode, errors } = useKclContext()
|
||||
const lastEvent = useRef({ event: '', time: Date.now() })
|
||||
const { copilotLSP, kclLSP } = useLspContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
@ -96,90 +78,15 @@ export const KclEditorPane = () => {
|
||||
|
||||
useHotkeys('mod+z', (e) => {
|
||||
e.preventDefault()
|
||||
if (editorView) {
|
||||
undo(editorView)
|
||||
}
|
||||
editorManager.undo()
|
||||
})
|
||||
useHotkeys('mod+shift+z', (e) => {
|
||||
e.preventDefault()
|
||||
if (editorView) {
|
||||
redo(editorView)
|
||||
}
|
||||
editorManager.redo()
|
||||
})
|
||||
|
||||
const {
|
||||
context: { selectionRanges },
|
||||
send,
|
||||
state,
|
||||
} = useModelingContext()
|
||||
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const textWrapping = settings.context.textEditor.textWrapping
|
||||
const cursorBlinking = settings.context.textEditor.blinkingCursor
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { enable: convertEnabled, handleClick: convertCallback } =
|
||||
useConvertToVariable()
|
||||
|
||||
const lastSelection = useRef('')
|
||||
const onUpdate = (viewUpdate: ViewUpdate) => {
|
||||
// If we are just fucking around in a snippet, return early and don't
|
||||
// trigger stuff below that might cause the component to re-render.
|
||||
// Otherwise we will not be able to tab thru the snippet portions.
|
||||
// We explicitly dont check HasPrevSnippetField because we always add
|
||||
// a ${} to the end of the function so that's fine.
|
||||
if (hasNextSnippetField(viewUpdate.view.state)) {
|
||||
return
|
||||
}
|
||||
if (!editorView) {
|
||||
setEditorView(viewUpdate.view)
|
||||
}
|
||||
const selString = stringifyRanges(
|
||||
viewUpdate?.state?.selection?.ranges || []
|
||||
)
|
||||
if (selString === lastSelection.current) {
|
||||
// onUpdate is noisy and is fired a lot by extensions
|
||||
// since we're only interested in selections changes we can ignore most of these.
|
||||
return
|
||||
}
|
||||
lastSelection.current = selString
|
||||
|
||||
if (
|
||||
// TODO find a less lazy way of getting the last
|
||||
Date.now() - useStore.getState().lastCodeMirrorSelectionUpdatedFromScene <
|
||||
150
|
||||
)
|
||||
return // update triggered by scene selection
|
||||
if (sceneInfra.selected) return // mid drag
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||
'Equip Line tool',
|
||||
'Equip tangential arc to',
|
||||
]
|
||||
if (ignoreEvents.includes(state.event.type)) return
|
||||
const eventInfo = processCodeMirrorRanges({
|
||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||
selectionRanges,
|
||||
isShiftDown,
|
||||
})
|
||||
if (!eventInfo) return
|
||||
const deterministicEventInfo = {
|
||||
...eventInfo,
|
||||
engineEvents: eventInfo.engineEvents.map((e) => ({
|
||||
...e,
|
||||
cmd_id: 'static',
|
||||
})),
|
||||
}
|
||||
const stringEvent = JSON.stringify(deterministicEventInfo)
|
||||
if (
|
||||
stringEvent === lastEvent.current.event &&
|
||||
Date.now() - lastEvent.current.time < 500
|
||||
)
|
||||
return // don't repeat events
|
||||
lastEvent.current = { event: stringEvent, time: Date.now() }
|
||||
send(eventInfo.modelingEvent)
|
||||
eventInfo.engineEvents.forEach((event) =>
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
)
|
||||
}
|
||||
const textWrapping = context.textEditor.textWrapping
|
||||
const cursorBlinking = context.textEditor.blinkingCursor
|
||||
|
||||
const editorExtensions = useMemo(() => {
|
||||
const extensions = [
|
||||
@ -202,7 +109,7 @@ export const KclEditorPane = () => {
|
||||
{
|
||||
key: 'Meta-k',
|
||||
run: () => {
|
||||
commandBarSend({ type: 'Open' })
|
||||
editorManager.commandBarSend({ type: 'Open' })
|
||||
return false
|
||||
},
|
||||
},
|
||||
@ -216,11 +123,7 @@ export const KclEditorPane = () => {
|
||||
{
|
||||
key: editorShortcutMeta.convertToVariable.codeMirror,
|
||||
run: () => {
|
||||
if (convertEnabled) {
|
||||
convertCallback()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return editorManager.convertToVariable()
|
||||
},
|
||||
},
|
||||
]),
|
||||
@ -233,9 +136,6 @@ export const KclEditorPane = () => {
|
||||
if (!TEST) {
|
||||
extensions.push(
|
||||
lintGutter(),
|
||||
linter((_view: EditorView) => {
|
||||
return kclErrorsToDiagnostics(errors)
|
||||
}),
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
@ -288,13 +188,7 @@ export const KclEditorPane = () => {
|
||||
}
|
||||
|
||||
return extensions
|
||||
}, [
|
||||
kclLSP,
|
||||
copilotLSP,
|
||||
textWrapping.current,
|
||||
cursorBlinking.current,
|
||||
convertCallback,
|
||||
])
|
||||
}, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -302,18 +196,15 @@ export const KclEditorPane = () => {
|
||||
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
||||
>
|
||||
<ReactCodeMirror
|
||||
value={editorCode}
|
||||
value={codeManager.code}
|
||||
extensions={editorExtensions}
|
||||
onUpdate={onUpdate}
|
||||
theme={theme}
|
||||
onCreateEditor={(_editorView) => setEditorView(_editorView)}
|
||||
onCreateEditor={(_editorView) =>
|
||||
editorManager.setEditorView(_editorView)
|
||||
}
|
||||
indentWithTab={false}
|
||||
basicSetup={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function stringifyRanges(ranges: readonly SelectionRange[]): string {
|
||||
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
|
||||
}
|
||||
|
||||
@ -2,7 +2,9 @@ import { processMemory } from './MemoryPane'
|
||||
import { enginelessExecutor } from '../../../lib/testHelpers'
|
||||
import { initPromise, parse } from '../../../lang/wasm'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('processMemory', () => {
|
||||
it('should grab the values and remove and geo data', async () => {
|
||||
|
||||
238
src/editor/manager.ts
Normal file
@ -0,0 +1,238 @@
|
||||
import { hasNextSnippetField } from '@codemirror/autocomplete'
|
||||
import { EditorView, ViewUpdate } from '@codemirror/view'
|
||||
import { EditorSelection, SelectionRange } from '@codemirror/state'
|
||||
import { engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections'
|
||||
import { undo, redo } from '@codemirror/commands'
|
||||
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
|
||||
import { addLineHighlight } from './highlightextension'
|
||||
import { setDiagnostics, Diagnostic } from '@codemirror/lint'
|
||||
|
||||
export default class EditorManager {
|
||||
private _editorView: EditorView | null = null
|
||||
|
||||
private _isShiftDown: boolean = false
|
||||
private _selectionRanges: Selections = {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
}
|
||||
|
||||
private _lastSelectionEvent: number | null = null
|
||||
private _lastSelection: string = ''
|
||||
private _lastEvent: { event: string; time: number } | null = null
|
||||
|
||||
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
|
||||
private _modelingEvent: ModelingMachineEvent | null = null
|
||||
|
||||
private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void =
|
||||
() => {}
|
||||
|
||||
private _convertToVariableEnabled: boolean = false
|
||||
private _convertToVariableCallback: () => void = () => {}
|
||||
|
||||
private _highlightRange: [number, number] = [0, 0]
|
||||
|
||||
setEditorView(editorView: EditorView) {
|
||||
this._editorView = editorView
|
||||
}
|
||||
|
||||
get editorView(): EditorView | null {
|
||||
return this._editorView
|
||||
}
|
||||
|
||||
get isShiftDown(): boolean {
|
||||
return this._isShiftDown
|
||||
}
|
||||
|
||||
setIsShiftDown(isShiftDown: boolean) {
|
||||
this._isShiftDown = isShiftDown
|
||||
}
|
||||
|
||||
set selectionRanges(selectionRanges: Selections) {
|
||||
this._selectionRanges = selectionRanges
|
||||
}
|
||||
|
||||
set lastSelectionEvent(time: number) {
|
||||
this._lastSelectionEvent = time
|
||||
}
|
||||
|
||||
set modelingSend(send: (eventInfo: ModelingMachineEvent) => void) {
|
||||
this._modelingSend = send
|
||||
}
|
||||
|
||||
set modelingEvent(event: ModelingMachineEvent) {
|
||||
this._modelingEvent = event
|
||||
}
|
||||
|
||||
setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) {
|
||||
this._commandBarSend = send
|
||||
}
|
||||
|
||||
commandBarSend(eventInfo: CommandBarMachineEvent): void {
|
||||
return this._commandBarSend(eventInfo)
|
||||
}
|
||||
|
||||
get highlightRange(): [number, number] {
|
||||
return this._highlightRange
|
||||
}
|
||||
|
||||
setHighlightRange(selection: Selection['range']): void {
|
||||
this._highlightRange = selection
|
||||
const editorView = this.editorView
|
||||
const safeEnd = Math.min(
|
||||
selection[1],
|
||||
editorView?.state.doc.length || selection[1]
|
||||
)
|
||||
if (editorView) {
|
||||
editorView.dispatch({
|
||||
effects: addLineHighlight.of([selection[0], safeEnd]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setDiagnostics(diagnostics: Diagnostic[]): void {
|
||||
if (!this.editorView) return
|
||||
this.editorView.dispatch(setDiagnostics(this.editorView.state, diagnostics))
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this._editorView) {
|
||||
undo(this._editorView)
|
||||
}
|
||||
}
|
||||
|
||||
redo() {
|
||||
if (this._editorView) {
|
||||
redo(this._editorView)
|
||||
}
|
||||
}
|
||||
|
||||
set convertToVariableEnabled(enabled: boolean) {
|
||||
this._convertToVariableEnabled = enabled
|
||||
}
|
||||
|
||||
set convertToVariableCallback(callback: () => void) {
|
||||
this._convertToVariableCallback = callback
|
||||
}
|
||||
|
||||
convertToVariable() {
|
||||
if (this._convertToVariableEnabled) {
|
||||
this._convertToVariableCallback()
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
selectRange(selections: Selections) {
|
||||
if (selections.codeBasedSelections.length === 0) {
|
||||
return
|
||||
}
|
||||
if (!this.editorView) {
|
||||
return
|
||||
}
|
||||
let codeBasedSelections = []
|
||||
for (const selection of selections.codeBasedSelections) {
|
||||
codeBasedSelections.push(
|
||||
EditorSelection.range(selection.range[0], selection.range[1])
|
||||
)
|
||||
}
|
||||
|
||||
codeBasedSelections.push(
|
||||
EditorSelection.cursor(
|
||||
selections.codeBasedSelections[
|
||||
selections.codeBasedSelections.length - 1
|
||||
].range[1]
|
||||
)
|
||||
)
|
||||
this.editorView.dispatch({
|
||||
selection: EditorSelection.create(codeBasedSelections, 1),
|
||||
})
|
||||
}
|
||||
|
||||
handleOnViewUpdate(viewUpdate: ViewUpdate): void {
|
||||
// If we are just fucking around in a snippet, return early and don't
|
||||
// trigger stuff below that might cause the component to re-render.
|
||||
// Otherwise we will not be able to tab thru the snippet portions.
|
||||
// We explicitly dont check HasPrevSnippetField because we always add
|
||||
// a ${} to the end of the function so that's fine.
|
||||
if (hasNextSnippetField(viewUpdate.view.state)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.editorView === null) {
|
||||
this.setEditorView(viewUpdate.view)
|
||||
}
|
||||
const selString = stringifyRanges(
|
||||
viewUpdate?.state?.selection?.ranges || []
|
||||
)
|
||||
|
||||
if (selString === this._lastSelection) {
|
||||
// onUpdate is noisy and is fired a lot by extensions
|
||||
// since we're only interested in selections changes we can ignore most of these.
|
||||
return
|
||||
}
|
||||
this._lastSelection = selString
|
||||
|
||||
if (
|
||||
this._lastSelectionEvent &&
|
||||
Date.now() - this._lastSelectionEvent < 150
|
||||
) {
|
||||
return // update triggered by scene selection
|
||||
}
|
||||
|
||||
if (sceneInfra.selected) {
|
||||
return // mid drag
|
||||
}
|
||||
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||
'Equip Line tool',
|
||||
'Equip tangential arc to',
|
||||
]
|
||||
|
||||
if (!this._modelingEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
if (ignoreEvents.includes(this._modelingEvent.type)) {
|
||||
return
|
||||
}
|
||||
|
||||
const eventInfo = processCodeMirrorRanges({
|
||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||
selectionRanges: this._selectionRanges,
|
||||
isShiftDown: this._isShiftDown,
|
||||
})
|
||||
|
||||
if (!eventInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
const deterministicEventInfo = {
|
||||
...eventInfo,
|
||||
engineEvents: eventInfo.engineEvents.map((e) => ({
|
||||
...e,
|
||||
cmd_id: 'static',
|
||||
})),
|
||||
}
|
||||
const stringEvent = JSON.stringify(deterministicEventInfo)
|
||||
if (
|
||||
this._lastEvent &&
|
||||
stringEvent === this._lastEvent.event &&
|
||||
Date.now() - this._lastEvent.time < 500
|
||||
) {
|
||||
return // don't repeat events
|
||||
}
|
||||
|
||||
this._lastEvent = { event: stringEvent, time: Date.now() }
|
||||
this._modelingSend(eventInfo.modelingEvent)
|
||||
eventInfo.engineEvents.forEach((event) =>
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyRanges(ranges: readonly SelectionRange[]): string {
|
||||
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
|
||||
}
|
||||
@ -21,7 +21,7 @@ import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||
import { Marked } from '@ts-stack/markdown'
|
||||
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||
import { Program, ProgramMemory } from 'lang/wasm'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { codeManager, editorManager, kclManager } from 'lib/singletons'
|
||||
import type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
|
||||
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
||||
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
||||
@ -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,6 +60,10 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
},
|
||||
contentChanges: [{ text: code }],
|
||||
})
|
||||
|
||||
if (this.viewUpdate) {
|
||||
editorManager.handleOnViewUpdate(this.viewUpdate)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@ -80,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,
|
||||
})
|
||||
@ -357,15 +377,9 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
try {
|
||||
switch (notification.method) {
|
||||
case 'textDocument/publishDiagnostics':
|
||||
const params = notification.params as PublishDiagnosticsParams
|
||||
this.processDiagnostics(params)
|
||||
// Update the kcl errors pane.
|
||||
/*if (!kclManager.isExecuting) {
|
||||
kclManager.kclErrors = lspDiagnosticsToKclErrors(
|
||||
this.view.state.doc,
|
||||
params.diagnostics
|
||||
)
|
||||
}*/
|
||||
//const params = notification.params as PublishDiagnosticsParams
|
||||
// this is sometimes slower than our actual typing.
|
||||
//this.processDiagnostics(params)
|
||||
break
|
||||
case 'window/logMessage':
|
||||
console.log(
|
||||
@ -385,17 +399,6 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
// The server has updated the AST, we should update elsewhere.
|
||||
let updatedAst = notification.params as Program
|
||||
console.log('[lsp]: Updated AST', updatedAst)
|
||||
// Update the ast when we are not already executing.
|
||||
/* if (!kclManager.isExecuting) {
|
||||
kclManager.ast = updatedAst
|
||||
// Execute the ast.
|
||||
console.log('[lsp]: executing ast')
|
||||
await kclManager.executeAst(updatedAst)
|
||||
console.log('[lsp]: executed ast', kclManager.kclErrors)
|
||||
let diagnostics = kclErrorsToDiagnostics(kclManager.kclErrors)
|
||||
this.view.dispatch(setDiagnostics(this.view.state, diagnostics))
|
||||
console.log('[lsp]: updated diagnostics')
|
||||
}*/
|
||||
|
||||
// Update the folding ranges, since the AST has changed.
|
||||
// This is a hack since codemirror does not support async foldService.
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useStore } from 'useStore'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { editorManager, engineCommandManager } from 'lib/singletons'
|
||||
import { useModelingContext } from './useModelingContext'
|
||||
import { getEventForSelectWithPoint } from 'lib/selections'
|
||||
|
||||
export function useEngineConnectionSubscriptions() {
|
||||
const { setHighlightRange, highlightRange } = useStore((s) => ({
|
||||
setHighlightRange: s.setHighlightRange,
|
||||
highlightRange: s.highlightRange,
|
||||
}))
|
||||
const { send, context } = useModelingContext()
|
||||
|
||||
useEffect(() => {
|
||||
@ -21,12 +16,13 @@ export function useEngineConnectionSubscriptions() {
|
||||
if (data?.entity_id) {
|
||||
const sourceRange =
|
||||
engineCommandManager.artifactMap?.[data.entity_id]?.range
|
||||
setHighlightRange(sourceRange)
|
||||
editorManager.setHighlightRange(sourceRange)
|
||||
} else if (
|
||||
!highlightRange ||
|
||||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
|
||||
!editorManager.highlightRange ||
|
||||
(editorManager.highlightRange[0] !== 0 &&
|
||||
editorManager.highlightRange[1] !== 0)
|
||||
) {
|
||||
setHighlightRange([0, 0])
|
||||
editorManager.setHighlightRange([0, 0])
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -43,10 +39,5 @@ export function useEngineConnectionSubscriptions() {
|
||||
unSubHover()
|
||||
unSubClick()
|
||||
}
|
||||
}, [
|
||||
engineCommandManager,
|
||||
setHighlightRange,
|
||||
highlightRange,
|
||||
context?.sketchEnginePathId,
|
||||
])
|
||||
}, [engineCommandManager, context?.sketchEnginePathId])
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useStore } from '../useStore'
|
||||
import { editorManager } from 'lib/singletons'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
// Kurt's note: codeMirror styling overrides were needed to make this work
|
||||
@ -6,20 +6,17 @@ import { useEffect } from 'react'
|
||||
// search for code-mirror-override in the repo to find the relevant styles
|
||||
|
||||
export function useHotKeyListener() {
|
||||
const { setIsShiftDown } = useStore((s) => ({
|
||||
setIsShiftDown: s.setIsShiftDown,
|
||||
}))
|
||||
const keyName = 'Shift'
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) =>
|
||||
event.key === keyName && setIsShiftDown(true)
|
||||
event.key === keyName && editorManager.setIsShiftDown(true)
|
||||
const handleKeyUp = (event: KeyboardEvent) =>
|
||||
event.key === keyName && setIsShiftDown(false)
|
||||
event.key === keyName && editorManager.setIsShiftDown(false)
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
window.addEventListener('keyup', handleKeyUp)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
window.removeEventListener('keyup', handleKeyUp)
|
||||
}
|
||||
}, [setIsShiftDown])
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import {
|
||||
SetVarNameModal,
|
||||
createSetVarNameModal,
|
||||
} from 'components/SetVarNameModal'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { editorManager, kclManager } from 'lib/singletons'
|
||||
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
||||
import { isNodeSafeToReplace } from 'lang/queryAst'
|
||||
import { useEffect, useState } from 'react'
|
||||
@ -13,6 +13,11 @@ const getModalInfo = createSetVarNameModal(SetVarNameModal)
|
||||
export function useConvertToVariable() {
|
||||
const { context } = useModelingContext()
|
||||
const [enable, setEnabled] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
editorManager.convertToVariableEnabled = enable
|
||||
}, [enable])
|
||||
|
||||
useEffect(() => {
|
||||
const { isSafe, value } = isNodeSafeToReplace(
|
||||
kclManager.ast,
|
||||
@ -45,5 +50,7 @@ export function useConvertToVariable() {
|
||||
}
|
||||
}
|
||||
|
||||
editorManager.convertToVariableCallback = handleClick
|
||||
|
||||
return { enable, handleClick }
|
||||
}
|
||||
|
||||
@ -2,12 +2,10 @@ import { KCLError } from './errors'
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
editorCode: codeManager?.code || '',
|
||||
programMemory: kclManager?.programMemory,
|
||||
ast: kclManager?.ast,
|
||||
isExecuting: kclManager?.isExecuting,
|
||||
@ -30,7 +28,6 @@ export function KclContextProvider({
|
||||
const { code: loadedCode } = useLoaderData() as IndexLoaderData
|
||||
// Both the code state and the editor state start off with the same code.
|
||||
const [code, setCode] = useState(loadedCode || codeManager.code)
|
||||
const [editorCode, setEditorCode] = useState(code)
|
||||
|
||||
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
||||
const [ast, setAst] = useState(kclManager.ast)
|
||||
@ -42,7 +39,6 @@ export function KclContextProvider({
|
||||
useEffect(() => {
|
||||
codeManager.registerCallBacks({
|
||||
setCode,
|
||||
setEditorCode,
|
||||
})
|
||||
kclManager.registerCallBacks({
|
||||
setProgramMemory,
|
||||
@ -54,15 +50,10 @@ export function KclContextProvider({
|
||||
})
|
||||
}, [])
|
||||
|
||||
const params = useParams()
|
||||
useEffect(() => {
|
||||
codeManager.setParams(params)
|
||||
}, [params])
|
||||
return (
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
code,
|
||||
editorCode,
|
||||
programMemory,
|
||||
ast,
|
||||
isExecuting,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { executeAst } from 'useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { KCLError } from './errors'
|
||||
import { KCLError, kclErrorsToDiagnostics } from './errors'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EngineCommandManager } from './std/engineConnection'
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
ExtrudeGroup,
|
||||
} from 'lang/wasm'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
import { codeManager, editorManager } from 'lib/singletons'
|
||||
|
||||
export class KclManager {
|
||||
private _ast: Program = {
|
||||
@ -90,6 +90,8 @@ export class KclManager {
|
||||
}
|
||||
set kclErrors(kclErrors) {
|
||||
this._kclErrors = kclErrors
|
||||
let diagnostics = kclErrorsToDiagnostics(kclErrors)
|
||||
editorManager.setDiagnostics(diagnostics)
|
||||
this._kclErrorsCallBack(kclErrors)
|
||||
}
|
||||
|
||||
@ -345,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(
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { KCLError } from './errors'
|
||||
import { initPromise, parse } from './wasm'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('testing AST', () => {
|
||||
test('5 + 6', () => {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { parse, initPromise } from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('testing artifacts', () => {
|
||||
// Enable rotations #152
|
||||
|
||||
@ -5,7 +5,7 @@ import { bracket } from 'lib/exampleKcl'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
||||
import toast from 'react-hot-toast'
|
||||
import { Params } from 'react-router-dom'
|
||||
import { editorManager } from 'lib/singletons'
|
||||
|
||||
const PERSIST_CODE_TOKEN = 'persistCode'
|
||||
|
||||
@ -13,7 +13,7 @@ export default class CodeManager {
|
||||
private _code: string = bracket
|
||||
private _updateState: (arg: string) => void = () => {}
|
||||
private _updateEditor: (arg: string) => void = () => {}
|
||||
private _params: Params<string> = {}
|
||||
private _currentFilePath: string | null = null
|
||||
|
||||
constructor() {
|
||||
if (isTauri()) {
|
||||
@ -45,19 +45,12 @@ export default class CodeManager {
|
||||
return this._code
|
||||
}
|
||||
|
||||
registerCallBacks({
|
||||
setCode,
|
||||
setEditorCode,
|
||||
}: {
|
||||
setCode: (arg: string) => void
|
||||
setEditorCode: (arg: string) => void
|
||||
}) {
|
||||
registerCallBacks({ setCode }: { setCode: (arg: string) => void }) {
|
||||
this._updateState = setCode
|
||||
this._updateEditor = setEditorCode
|
||||
}
|
||||
|
||||
setParams(params: Params<string>) {
|
||||
this._params = params
|
||||
updateCurrentFilePath(path: string) {
|
||||
this._currentFilePath = path
|
||||
}
|
||||
|
||||
// This updates the code state and calls the updateState function.
|
||||
@ -70,11 +63,14 @@ export default class CodeManager {
|
||||
|
||||
// Update the code in the editor.
|
||||
updateCodeEditor(code: string): void {
|
||||
if (this._code !== code) {
|
||||
this.code = code
|
||||
this._updateEditor(code)
|
||||
}
|
||||
const lastCode = this._code
|
||||
this.code = code
|
||||
this._updateEditor(code)
|
||||
if (editorManager.editorView) {
|
||||
editorManager.editorView.dispatch({
|
||||
changes: { from: 0, to: lastCode.length, insert: code },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Update the code, state, and the code the code mirror editor sees.
|
||||
@ -91,8 +87,8 @@ export default class CodeManager {
|
||||
setTimeout(() => {
|
||||
// Wait one event loop to give a chance for params to be set
|
||||
// Save the file to disk
|
||||
this._params.id &&
|
||||
writeTextFile(this._params.id, this.code).catch((err) => {
|
||||
this._currentFilePath &&
|
||||
writeTextFile(this._currentFilePath, this.code).catch((err) => {
|
||||
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
||||
console.error('error saving file', err)
|
||||
toast.error('Error saving file, please check file permissions')
|
||||
|
||||
@ -4,7 +4,9 @@ import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { KCLError } from './errors'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('test executor', () => {
|
||||
it('test assigning two variables, the second summing with the first', async () => {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||
import { Identifier, parse, initPromise, Parameter } from './wasm'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('testing getNodePathFromSourceRange', () => {
|
||||
it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', () => {
|
||||
|
||||
@ -17,7 +17,9 @@ import {
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { getNodePathFromSourceRange } from './queryAst'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('Testing createLiteral', () => {
|
||||
it('should create a literal', () => {
|
||||
|
||||
@ -15,7 +15,9 @@ import {
|
||||
createPipeSubstitution,
|
||||
} from './modifyAst'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('findAllPreviousVariables', () => {
|
||||
it('should find all previous variables', async () => {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { parse, Program, recast, initPromise } from './wasm'
|
||||
import fs from 'node:fs'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('recast', () => {
|
||||
it('recasts a simple program', () => {
|
||||
@ -264,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)
|
||||
@ -283,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 */
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -25,7 +25,9 @@ const eachQuad: [number, [number, number]][] = [
|
||||
[675, [1, -1]],
|
||||
]
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('testing getYComponent', () => {
|
||||
it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
|
||||
@ -100,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)
|
||||
|
||||
@ -8,7 +8,9 @@ import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||
import { Selection } from 'lib/selections'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
// testing helper function
|
||||
async function testingSwapSketchFnCall({
|
||||
|
||||
@ -11,7 +11,9 @@ import { ToolTip } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('testing getConstraintType', () => {
|
||||
const helper = getConstraintTypeFromSourceHelper
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { parse, initPromise } from '../wasm'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('testing angledLineThatIntersects', () => {
|
||||
it('angledLineThatIntersects should intersect with another line', async () => {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { lexer, initPromise } from './wasm'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('testing lexer', () => {
|
||||
it('async lexer works too', async () => {
|
||||
|
||||
@ -25,6 +25,7 @@ import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import { TEST } from 'env'
|
||||
|
||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||
@ -95,10 +96,15 @@ export const wasmUrl = () => {
|
||||
|
||||
// Initialise the wasm module.
|
||||
const initialise = async () => {
|
||||
const fullUrl = wasmUrl()
|
||||
const input = await fetch(fullUrl)
|
||||
const buffer = await input.arrayBuffer()
|
||||
return init(buffer)
|
||||
try {
|
||||
const fullUrl = wasmUrl()
|
||||
const input = await fetch(fullUrl)
|
||||
const buffer = await input.arrayBuffer()
|
||||
return await init(buffer)
|
||||
} catch (e) {
|
||||
console.log('Error initialising WASM', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export const initPromise = initialise()
|
||||
@ -153,10 +159,6 @@ export const executor = async (
|
||||
return _programMemory
|
||||
}
|
||||
|
||||
const getSettingsState = import('components/SettingsAuthProvider').then(
|
||||
(module) => module.getSettingsState
|
||||
)
|
||||
|
||||
export const _executor = async (
|
||||
node: Program,
|
||||
programMemory: ProgramMemory = { root: {}, return: null },
|
||||
@ -164,8 +166,14 @@ export const _executor = async (
|
||||
isMock: boolean
|
||||
): Promise<ProgramMemory> => {
|
||||
try {
|
||||
const baseUnit =
|
||||
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
|
||||
let baseUnit = 'mm'
|
||||
if (!TEST) {
|
||||
const getSettingsState = import('components/SettingsAuthProvider').then(
|
||||
(module) => module.getSettingsState
|
||||
)
|
||||
baseUnit =
|
||||
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
|
||||
}
|
||||
const memory: ProgramMemory = await execute_wasm(
|
||||
JSON.stringify(node),
|
||||
JSON.stringify(programMemory),
|
||||
|
||||
59
src/lib/circleTool.ts
Normal 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)
|
||||
}
|
||||
@ -103,6 +103,7 @@ export const fileLoader: LoaderFunction = async ({
|
||||
// Update both the state and the editor's code.
|
||||
// We explicitly do not write to the file here since we are loading from
|
||||
// the file system and not the editor.
|
||||
codeManager.updateCurrentFilePath(currentFilePath)
|
||||
codeManager.updateCodeStateEditor(code)
|
||||
kclManager.executeCode(true)
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { SceneEntities } from 'clientSideScene/sceneEntities'
|
||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import EditorManager from 'editor/manager'
|
||||
import { KclManager } from 'lang/KclSingleton'
|
||||
import CodeManager from 'lang/codeManager'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
@ -8,6 +9,7 @@ export const codeManager = new CodeManager()
|
||||
|
||||
export const engineCommandManager = new EngineCommandManager()
|
||||
|
||||
// This needs to be after codeManager is created.
|
||||
export const kclManager = new KclManager(engineCommandManager)
|
||||
engineCommandManager.getAstCb = () => kclManager.ast
|
||||
|
||||
@ -15,3 +17,18 @@ export const sceneInfra = new SceneInfra(engineCommandManager)
|
||||
engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange
|
||||
|
||||
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})`)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { Coords2d } from 'lang/std/sketch'
|
||||
import { isPointsCCW, initPromise } from 'lang/wasm'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('test isPointsCW', () => {
|
||||
test('basic test', () => {
|
||||
|
||||
@ -16,6 +16,54 @@ export type CommandBarContext = {
|
||||
argumentsToSubmit: { [x: string]: unknown }
|
||||
}
|
||||
|
||||
export type CommandBarMachineEvent =
|
||||
| { type: 'Open' }
|
||||
| { type: 'Close' }
|
||||
| { type: 'Clear' }
|
||||
| {
|
||||
type: 'Select command'
|
||||
data: { command: Command }
|
||||
}
|
||||
| { type: 'Deselect command' }
|
||||
| { type: 'Submit command'; data: { [x: string]: unknown } }
|
||||
| {
|
||||
type: 'Add argument'
|
||||
data: { argument: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Remove argument'
|
||||
data: { [x: string]: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Edit argument'
|
||||
data: { arg: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Add commands'
|
||||
data: { commands: Command[] }
|
||||
}
|
||||
| {
|
||||
type: 'Remove commands'
|
||||
data: { commands: Command[] }
|
||||
}
|
||||
| { type: 'Submit argument'; data: { [x: string]: unknown } }
|
||||
| {
|
||||
type: 'done.invoke.validateArguments'
|
||||
data: { [x: string]: unknown }
|
||||
}
|
||||
| {
|
||||
type: 'error.platform.validateArguments'
|
||||
data: { message: string; arg: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Find and select command'
|
||||
data: { name: string; ownerMachine: string }
|
||||
}
|
||||
| {
|
||||
type: 'Change current argument'
|
||||
data: { [x: string]: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
|
||||
export const commandBarMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22Ow5wozosyLUiVNMSg5ytVKmfrIipzO564z2otPVpI1vKd18SAOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSksextGkNJBRXFUOh-f9AOA0CIKgmCABE4E3D5oyTE1-lww8clKBjZF2Bh5SFZwXUUyRVDzJwNE2LQzzYwNJE4gCgJwKNeMw6DJHJAB3LAYlM-AHjYMDuFjMCACN0B4NCMKgnD927dMkWSUs8yY8RISfWQshvcsrDlapshULJPHfZsDKM7iHPM-jJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MByXQvisP8sY8ISZwbCsDllBLSwz1qRFBQsRZ5WRIVkui-TG0M39jJ4jqLNgfLmpK3hTLIQCSFQIM2EoX8ADMjvQSQmqKna2vWvyJL3HrD1SEoyzSdJUkUm9dnUtQn10aK6hkxbiU1HVODAay3M87zE1+J6UwCtN8IQUauR0OZ0ksFwUUqRFPWmUdouPXR3TBjoIahmHKQIHKuqRrtUYSaVShhLF+TkeTzBFQosVKDRM2RVInEdSmg2p6GyE1bU9R8zrsKZqSexFqQHWlHNXHzCbFhnX1+UydFkVxiXJClmGAFEIG8hmld3ZGXp7TR5C5RSCxdjlQV1tR9bqaVdhsSFxDN5AALeOrgPa3ysJgiqcCqmr6sa7bWujxXjQd5nesQZYthsblIQYJx6hUic9AsdEFT2HQzByVLmgDJaw-eSOHPTjbysq6qcFqhrrtT9sO-4ugd1NFGc4QXEPo5eVLGsFxlnKREXGnZI9HcT1mOikO0s-S5w7bqNh9j-bkKOyQTvOy6B9utOHtj7qD1V8pSyrHJZ3STY4QmjQ0R2Ww0oSjuF3u+HAqAIBwEEOlRs48nZBVENkKQNprC42qBoJ0LothaXMJ9aElRXDLj3k3VUpJIBwOfgg4oMx8y41SPUOY8p5DFk2GiKoxsoRVmhGoM2cY2zAQRngChgU0a7HZtFH0ilRp1D5usVh6JXCKD2CUdI8lQ7rlbB8chkkJ6HkxFsBeulbQZAUCwrYCiqzIhyE+fqZtMomTMg-aCwiWYEV2NOBUCghQ6EFGzYsvpwQUT5MxawFECx2JWllRxMdLI2TsrtKMTkXIuMnmeCiZYwrePMFWCKv1XZKVGroWwmxNARK4g4hWG0tp3wSSk16lQpC41ULjV8uxUjSBvFCNEDTdCOkWCY44xCGzgzAJDaGdTnaZHBADYcqg5DpCovzeRno5izA0BeUOh8o5OPgDo+BaNvqV0xNFaE7gdYTnnvkmUChdKEMyF4LwQA */
|
||||
@ -227,53 +275,7 @@ export const commandBarMachine = createMachine(
|
||||
},
|
||||
},
|
||||
schema: {
|
||||
events: {} as
|
||||
| { type: 'Open' }
|
||||
| { type: 'Close' }
|
||||
| { type: 'Clear' }
|
||||
| {
|
||||
type: 'Select command'
|
||||
data: { command: Command }
|
||||
}
|
||||
| { type: 'Deselect command' }
|
||||
| { type: 'Submit command'; data: { [x: string]: unknown } }
|
||||
| {
|
||||
type: 'Add argument'
|
||||
data: { argument: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Remove argument'
|
||||
data: { [x: string]: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Edit argument'
|
||||
data: { arg: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Add commands'
|
||||
data: { commands: Command[] }
|
||||
}
|
||||
| {
|
||||
type: 'Remove commands'
|
||||
data: { commands: Command[] }
|
||||
}
|
||||
| { type: 'Submit argument'; data: { [x: string]: unknown } }
|
||||
| {
|
||||
type: 'done.invoke.validateArguments'
|
||||
data: { [x: string]: unknown }
|
||||
}
|
||||
| {
|
||||
type: 'error.platform.validateArguments'
|
||||
data: { message: string; arg: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| {
|
||||
type: 'Find and select command'
|
||||
data: { name: string; ownerMachine: string }
|
||||
}
|
||||
| {
|
||||
type: 'Change current argument'
|
||||
data: { [x: string]: CommandArgumentWithName<unknown> }
|
||||
},
|
||||
events: {} as CommandBarMachineEvent,
|
||||
},
|
||||
preserveActionOrder: true,
|
||||
},
|
||||
|
||||