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 = {
|
module.exports = {
|
||||||
presets: ["@babel/preset-env"],
|
presets: ['@babel/preset-env'],
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1}], %)`)
|
|> line([0, ${commonPoints.num1}], %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
@ -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()
|
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,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
@ -349,7 +348,7 @@ const sketch001 = startSketchOn(box, "revolveAxis")
|
|||||||
|> startProfileAt([5, 10], %)
|
|> startProfileAt([5, 10], %)
|
||||||
|> line([0, -10], %)
|
|> line([0, -10], %)
|
||||||
|> line([2, 0], %)
|
|> line([2, 0], %)
|
||||||
|> line([0, 10], %)
|
|> line([0, -10], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> revolve({
|
|> revolve({
|
||||||
axis: getEdge('revolveAxis', box),
|
axis: getEdge('revolveAxis', box),
|
||||||
@ -364,7 +363,7 @@ angle: 90
|
|||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
@ -378,7 +377,7 @@ angle: 90
|
|||||||
'sketch profile must lie entirely on one side of the revolution axis'
|
'sketch profile must lie entirely on one side of the revolution axis'
|
||||||
)
|
)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})*/
|
})
|
||||||
|
|
||||||
test('executes on load', async ({ page }) => {
|
test('executes on load', async ({ page }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
@ -566,7 +565,9 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
|
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.keyboard.type('12')
|
await page.keyboard.type('12')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
@ -736,7 +737,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const xAxisClick = () =>
|
const xAxisClick = () =>
|
||||||
page.mouse.click(700, 250).then(() => page.waitForTimeout(100))
|
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
|
||||||
const emptySpaceClick = () =>
|
const emptySpaceClick = () =>
|
||||||
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
|
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
|
||||||
const topHorzSegmentClick = () =>
|
const topHorzSegmentClick = () =>
|
||||||
@ -761,6 +762,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
@ -768,12 +770,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)`)
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1}], %)`)
|
|> line([0, ${commonPoints.num1}], %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
@ -786,10 +790,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Line' }).click()
|
await page.getByRole('button', { name: 'Line' }).click()
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
const selectionSequence = async () => {
|
const selectionSequence = async (isSecondTime = false) => {
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
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()
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
// bg-yellow-200 is more brittle than hover-highlight, but is closer to the user experience
|
// 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
|
// check mousing off, than mousing onto another line
|
||||||
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
|
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
await page.mouse.move(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()
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
||||||
|
|
||||||
// now check clicking works including axis
|
// 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')
|
await page.keyboard.down('Shift')
|
||||||
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
@ -817,10 +829,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
// same selection but click the axis first
|
// same selection but click the axis first
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
|
|
||||||
await page.keyboard.up('Shift')
|
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.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
@ -875,11 +890,16 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// enter sketch again
|
// 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
|
await page.waitForTimeout(300) // wait for animation
|
||||||
|
|
||||||
// hover again and check it works
|
// hover again and check it works
|
||||||
await selectionSequence()
|
await selectionSequence(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Command bar tests', () => {
|
test.describe('Command bar tests', () => {
|
||||||
@ -1065,6 +1085,7 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([0, ${commonPoints.num1}], %)`)
|
|> line([0, ${commonPoints.num1}], %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
@ -1080,24 +1101,33 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
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
|
// start a new sketch
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(400)
|
||||||
await page.mouse.click(673, 384)
|
await page.mouse.click(650, 450)
|
||||||
|
|
||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
await u.clearAndCloseDebugPanel()
|
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)
|
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 expect(
|
||||||
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
).toBe(
|
).toBe(
|
||||||
`${finalCodeFirstSketch}
|
`${finalCodeFirstSketch}
|
||||||
const part002 = startSketchOn('XY')
|
const part002 = startSketchOn('${plane}')
|
||||||
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
|
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
|
||||||
)
|
)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
@ -1106,12 +1136,12 @@ const part002 = startSketchOn('XY')
|
|||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const num2 = 0.94
|
const num2 = process.platform === 'darwin' ? 9.84 : 0.94
|
||||||
await expect(
|
await expect(
|
||||||
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
).toBe(
|
).toBe(
|
||||||
`${finalCodeFirstSketch}
|
`${finalCodeFirstSketch}
|
||||||
const part002 = startSketchOn('XY')
|
const part002 = startSketchOn('${plane}')
|
||||||
|> startProfileAt(${startAt2}, %)
|
|> startProfileAt(${startAt2}, %)
|
||||||
|> line([${num2}, 0], %)`.replace(/\s/g, '')
|
|> line([${num2}, 0], %)`.replace(/\s/g, '')
|
||||||
)
|
)
|
||||||
@ -1121,21 +1151,29 @@ const part002 = startSketchOn('XY')
|
|||||||
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
).toBe(
|
).toBe(
|
||||||
`${finalCodeFirstSketch}
|
`${finalCodeFirstSketch}
|
||||||
const part002 = startSketchOn('XY')
|
const part002 = startSketchOn('${plane}')
|
||||||
|> startProfileAt(${startAt2}, %)
|
|> startProfileAt(${startAt2}, %)
|
||||||
|> line([${num2}, 0], %)
|
|> 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 page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(
|
await expect(
|
||||||
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
).toBe(
|
).toBe(
|
||||||
`${finalCodeFirstSketch}
|
`${finalCodeFirstSketch}
|
||||||
const part002 = startSketchOn('XY')
|
const part002 = startSketchOn('${plane}')
|
||||||
|> startProfileAt(${startAt2}, %)
|
|> startProfileAt(${startAt2}, %)
|
||||||
|> line([${num2}, 0], %)
|
|> line([${num2}, 0], %)
|
||||||
|> line([0, ${roundOff(num2 - 0.01)}], %)
|
|> line([0, ${roundOff(
|
||||||
|> line([-1.87, 0], %)`.replace(/\s/g, '')
|
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)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(700, 300)
|
await page.mouse.click(700, 300)
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(750, 300)
|
await page.mouse.click(750, 300)
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
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' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
|
||||||
const startPX = [652, 418]
|
const startPX = [665, 458]
|
||||||
const lineEndPX = [794, 416]
|
const lineEndPX = [842, 458]
|
||||||
const arcEndPX = [893, 318]
|
const arcEndPX = [971, 342]
|
||||||
|
|
||||||
const dragPX = 30
|
const dragPX = 30
|
||||||
|
|
||||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(400)
|
||||||
let prevContent = await page.locator('.cm-content').innerText()
|
let prevContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
const step5 = { steps: 5 }
|
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.down()
|
||||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||||
await page.mouse.up()
|
await page.mouse.up()
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
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
|
// expect the code to have changed
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([7.01, -11.79], %)
|
|> startProfileAt([6.44, -12.07], %)
|
||||||
|> line([14.69, 2.73], %)
|
|> line([14.04, 2.03], %)
|
||||||
|> tangentialArcTo([27.6, -3.25], %)`)
|
|> tangentialArcTo([27.19, -4.2], %)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
const doSnapAtDifferentScales = async (
|
const doSnapAtDifferentScales = async (
|
||||||
@ -1535,38 +1575,46 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
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 firstClickPosition = [612, 238]
|
||||||
const secondClickPosition = [661, 242]
|
const secondClickPosition = [661, 242]
|
||||||
const thirdClickPosition = [609, 267]
|
const thirdClickPosition = [609, 267]
|
||||||
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
|
|
||||||
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
|
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
|
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|> startProfileAt([1.03, 1.03], %)
|
|> startProfileAt([-12.83, 6.7], %)
|
||||||
|> line([4.18, -0.35], %)
|
|> line([2.87, -0.23], %)
|
||||||
|> line([-4.44, -2.13], %)
|
|> line([-3.05, -1.47], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
@ -1576,9 +1624,14 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
await u.updateCamPosition([1049, 239, 686])
|
await u.updateCamPosition([1049, 239, 686])
|
||||||
await u.closeDebugPanel()
|
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 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 page.setViewportSize({ width: 1200, height: 1200 })
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.updateCamPosition([452, -152, 1166])
|
await u.updateCamPosition([452, -152, 1166])
|
||||||
@ -1598,11 +1651,11 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|> startProfileAt([1.03, 1.03], %)
|
|> startProfileAt([-12.83, 6.7], %)
|
||||||
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
|
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
|
||||||
process?.env?.CI ? 0.24 : 0.2
|
process?.env?.CI ? 0.07 : 0.07
|
||||||
}], %)
|
}], %)
|
||||||
|> line([-4.44, -2.13], %)
|
|> line([-3.05, -1.47], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
// exit sketch
|
// exit sketch
|
||||||
@ -1610,7 +1663,7 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
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 expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
|
||||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||||
@ -1624,11 +1677,11 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|> startProfileAt([1.03, 1.03], %)
|
|> startProfileAt([-12.83, 6.7], %)
|
||||||
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
|
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
|
||||||
process?.env?.CI ? 0.24 : 0.2
|
process?.env?.CI ? 0.07 : 0.07
|
||||||
}], %)
|
}], %)
|
||||||
|> line([-4.44, -2.13], %)
|
|> line([-3.05, -1.47], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5 + 7, %)`)
|
|> extrude(5 + 7, %)`)
|
||||||
})
|
})
|
||||||
@ -1661,11 +1714,11 @@ test('Can code mod a line length', async ({ page }) => {
|
|||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
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
|
const startXPx = 500
|
||||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
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.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
await page.getByText('Add constraining value').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, %)`
|
`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) {
|
async function waitForPageLoad(page: Page) {
|
||||||
// wait for 'Loading stream...' spinner
|
// wait for 'Loading stream...' spinner
|
||||||
await page.getByTestId('loading-stream').waitFor()
|
// await page.getByTestId('loading-stream').waitFor()
|
||||||
// wait for all spinners to be gone
|
// wait for all spinners to be gone
|
||||||
await page.getByTestId('loading').waitFor({ state: 'detached' })
|
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"
|
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" '.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 add package.json src-tauri/tauri.conf.json
|
||||||
git commit -m "Cut release $new_version"
|
git commit -m "Cut release $new_version"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.17.3",
|
"version": "0.18.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.0",
|
||||||
@ -8,7 +8,7 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.18",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.58",
|
"@kittycad/lib": "^0.0.58",
|
||||||
"@lezer/javascript": "^1.4.9",
|
"@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",
|
"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:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||||
"simpleserver": "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": "prettier --write ./src *.ts *.json *.js ./e2e",
|
||||||
"fmt-check": "prettier --check ./src && prettier --check ./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-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": "(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",
|
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
||||||
@ -132,6 +132,7 @@
|
|||||||
"@types/wicg-file-system-access": "^2023.10.5",
|
"@types/wicg-file-system-access": "^2023.10.5",
|
||||||
"@types/ws": "^8.5.10",
|
"@types/ws": "^8.5.10",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"@vitest/web-worker": "^1.5.0",
|
||||||
"@wdio/cli": "^8.24.3",
|
"@wdio/cli": "^8.24.3",
|
||||||
"@wdio/globals": "^8.36.0",
|
"@wdio/globals": "^8.36.0",
|
||||||
"@wdio/local-runner": "^8.36.0",
|
"@wdio/local-runner": "^8.36.0",
|
||||||
|
@ -49,8 +49,6 @@ export default defineConfig({
|
|||||||
// use: { ...devices['Desktop Chrome'] },
|
// use: { ...devices['Desktop Chrome'] },
|
||||||
// },
|
// },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
/* Test against mobile viewports. */
|
||||||
// {
|
// {
|
||||||
// name: 'Mobile Chrome',
|
// name: 'Mobile Chrome',
|
||||||
@ -78,4 +76,4 @@ export default defineConfig({
|
|||||||
// url: 'http://127.0.0.1:3000',
|
// url: 'http://127.0.0.1:3000',
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
8
src-tauri/Cargo.lock
generated
@ -2199,9 +2199,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.2.67"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc460442c165c8e707b1154551cefd08938d10bb80c78940e10cd9869487c325"
|
checksum = "ddc922f0da3abc22661bf49423c9bfcc02ce6ae92dae007ede6990874789545b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -4641,9 +4641,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs"
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c138126392c350aa68554e3461529b02680062c9146ab7b41d3ef97a2deaf93b"
|
checksum = "609f53d90f08808679ecdd81455d9a4d0053291b92780695569f7400fdba27d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"glob",
|
"glob",
|
||||||
|
@ -16,13 +16,13 @@ tauri-build = { version = "2.0.0-beta.12", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
kittycad = "0.2.67"
|
kittycad = "0.3.0"
|
||||||
oauth2 = "4.4.2"
|
oauth2 = "4.4.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
|
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
|
||||||
tauri-plugin-dialog = { version = "2.0.0-beta.5" }
|
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-http = { version = "2.0.0-beta.5" }
|
||||||
tauri-plugin-os = { version = "2.0.0-beta.2" }
|
tauri-plugin-os = { version = "2.0.0-beta.2" }
|
||||||
tauri-plugin-process = { version = "2.0.0-beta.2" }
|
tauri-plugin-process = { version = "2.0.0-beta.2" }
|
||||||
|
@ -55,5 +55,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.17.3"
|
"version": "0.18.1"
|
||||||
}
|
}
|
||||||
|
@ -193,6 +193,35 @@ export const Toolbar = () => {
|
|||||||
Rectangle
|
Rectangle
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</li>
|
</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') &&
|
{state.matches('Sketch.SketchIdle') &&
|
||||||
|
@ -246,13 +246,31 @@ export class CameraControls {
|
|||||||
camSettings.center.y,
|
camSettings.center.y,
|
||||||
camSettings.center.z
|
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) {
|
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
|
||||||
this.camera.fov = camSettings.fov_y
|
this.camera.fov = camSettings.fov_y
|
||||||
} else if (
|
} else if (
|
||||||
this.camera instanceof OrthographicCamera &&
|
this.camera instanceof OrthographicCamera &&
|
||||||
camSettings.ortho_scale
|
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()
|
this.onCameraChange()
|
||||||
}
|
}
|
||||||
@ -965,10 +983,10 @@ export class CameraControls {
|
|||||||
// Pure function helpers
|
// Pure function helpers
|
||||||
|
|
||||||
function calculateNearFarFromFOV(fov: number) {
|
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_near = 0.1 + nearFarRatio * (5 - 0.1)
|
||||||
const z_far = 1000 + nearFarRatio * (100000 - 1000)
|
// const z_far = 1000 + nearFarRatio * (100000 - 1000)
|
||||||
return { z_near: 0.1, z_far }
|
return { z_near: 0.1, z_far: 1000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertThreeCamValuesToEngineCam({
|
function convertThreeCamValuesToEngineCam({
|
||||||
@ -1043,3 +1061,62 @@ function _getInteractionType(
|
|||||||
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
|
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
|
||||||
return state
|
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 { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { useStore } from 'useStore'
|
|
||||||
import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
|
import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
|
||||||
import { ReactCameraProperties } from './CameraControls'
|
import { ReactCameraProperties } from './CameraControls'
|
||||||
import { throttle } from 'lib/utils'
|
import { throttle } from 'lib/utils'
|
||||||
@ -47,10 +46,6 @@ export const ClientSideScene = ({
|
|||||||
const canvasRef = useRef<HTMLDivElement>(null)
|
const canvasRef = useRef<HTMLDivElement>(null)
|
||||||
const { state, send, context } = useModelingContext()
|
const { state, send, context } = useModelingContext()
|
||||||
const { hideClient, hideServer } = useShouldHideScene()
|
const { hideClient, hideServer } = useShouldHideScene()
|
||||||
const { setHighlightRange } = useStore((s) => ({
|
|
||||||
setHighlightRange: s.setHighlightRange,
|
|
||||||
highlightRange: s.highlightRange,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Listen for changes to the camera controls setting
|
// Listen for changes to the camera controls setting
|
||||||
// and update the client-side scene's controls accordingly.
|
// and update the client-side scene's controls accordingly.
|
||||||
@ -69,7 +64,6 @@ export const ClientSideScene = ({
|
|||||||
const canvas = canvasRef.current
|
const canvas = canvasRef.current
|
||||||
canvas.appendChild(sceneInfra.renderer.domElement)
|
canvas.appendChild(sceneInfra.renderer.domElement)
|
||||||
sceneInfra.animate()
|
sceneInfra.animate()
|
||||||
sceneInfra.setHighlightCallback(setHighlightRange)
|
|
||||||
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
|
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
|
||||||
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
||||||
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)
|
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)
|
||||||
|
@ -57,6 +57,7 @@ import {
|
|||||||
kclManager,
|
kclManager,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
codeManager,
|
codeManager,
|
||||||
|
editorManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { executeAst, useStore } from 'useStore'
|
import { executeAst, useStore } from 'useStore'
|
||||||
@ -96,6 +97,7 @@ import {
|
|||||||
getRectangleCallExpressions,
|
getRectangleCallExpressions,
|
||||||
updateRectangleSketch,
|
updateRectangleSketch,
|
||||||
} from 'lib/rectangleTool'
|
} from 'lib/rectangleTool'
|
||||||
|
import { circleAsCallExpressions, updateCircleSketch } from 'lib/circleTool'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
@ -214,8 +216,9 @@ export class SceneEntities {
|
|||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
const baseXColor = 0x000055
|
const baseXColor = 0x000055
|
||||||
const baseYColor = 0x550000
|
const baseYColor = 0x550000
|
||||||
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
|
const axisPixelWidth = 1.6
|
||||||
const yAxisGeometry = new BoxGeometry(0.3, 100000, 0.01)
|
const xAxisGeometry = new BoxGeometry(100000, axisPixelWidth, 0.01)
|
||||||
|
const yAxisGeometry = new BoxGeometry(axisPixelWidth, 100000, 0.01)
|
||||||
const xAxisMaterial = new MeshBasicMaterial({
|
const xAxisMaterial = new MeshBasicMaterial({
|
||||||
color: baseXColor,
|
color: baseXColor,
|
||||||
depthTest: false,
|
depthTest: false,
|
||||||
@ -578,7 +581,7 @@ export class SceneEntities {
|
|||||||
...this.mouseEnterLeaveCallbacks(),
|
...this.mouseEnterLeaveCallbacks(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setupRectangleOriginListener = () => {
|
setupOriginListener = (type: 'circle' | 'rectangle') => {
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onClick: (args) => {
|
onClick: (args) => {
|
||||||
const twoD = args.intersectionPoint?.twoD
|
const twoD = args.intersectionPoint?.twoD
|
||||||
@ -587,7 +590,7 @@ export class SceneEntities {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
sceneInfra.modelingSend({
|
sceneInfra.modelingSend({
|
||||||
type: 'Add rectangle origin',
|
type: `Add ${type} origin`,
|
||||||
data: [twoD.x, twoD.y],
|
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 = ({
|
setupSketchIdleCallbacks = ({
|
||||||
pathToNode,
|
pathToNode,
|
||||||
up,
|
up,
|
||||||
@ -1323,30 +1474,31 @@ export class SceneEntities {
|
|||||||
selected.material.color = defaultPlaneColor(type)
|
selected.material.color = defaultPlaneColor(type)
|
||||||
},
|
},
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
const checkExtrudeFaceClick = async (): Promise<boolean> => {
|
const checkExtrudeFaceClick = async (): Promise<
|
||||||
|
['face' | 'plane' | 'other', string]
|
||||||
|
> => {
|
||||||
const { streamDimensions } = useStore.getState()
|
const { streamDimensions } = useStore.getState()
|
||||||
const { entity_id } = await sendSelectEventToEngine(
|
const { entity_id } = await sendSelectEventToEngine(
|
||||||
args?.mouseEvent,
|
args?.mouseEvent,
|
||||||
document.getElementById('video-stream') as HTMLVideoElement,
|
document.getElementById('video-stream') as HTMLVideoElement,
|
||||||
streamDimensions
|
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]
|
const artifact = this.engineCommandManager.artifactMap[entity_id]
|
||||||
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
|
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
|
||||||
return false
|
return ['other', entity_id]
|
||||||
const faceInfo: Models['FaceIsPlanar_type'] = (
|
|
||||||
await this.engineCommandManager.sendSceneCommand({
|
const faceInfo = await getFaceDetails(entity_id)
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'face_is_planar',
|
|
||||||
object_id: entity_id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)?.data?.data
|
|
||||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||||
return false
|
return ['other', entity_id]
|
||||||
const { z_axis, origin, y_axis } = faceInfo
|
const { z_axis, y_axis, origin } = faceInfo
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
artifact.range
|
artifact.range
|
||||||
@ -1366,12 +1518,15 @@ export class SceneEntities {
|
|||||||
artifact?.additionalData?.type === 'cap'
|
artifact?.additionalData?.type === 'cap'
|
||||||
? artifact.additionalData.info
|
? artifact.additionalData.info
|
||||||
: 'none',
|
: '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 || !args.intersects?.[0]) return
|
||||||
if (args.mouseEvent.which !== 1) return
|
if (args.mouseEvent.which !== 1) return
|
||||||
@ -1397,6 +1552,7 @@ export class SceneEntities {
|
|||||||
plane: planeString,
|
plane: planeString,
|
||||||
zAxis,
|
zAxis,
|
||||||
yAxis,
|
yAxis,
|
||||||
|
planeId: faceResult[1],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -1423,7 +1579,7 @@ export class SceneEntities {
|
|||||||
parent.userData.pathToNode,
|
parent.userData.pathToNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
).node
|
).node
|
||||||
sceneInfra.highlightCallback([node.start, node.end])
|
editorManager.setHighlightRange([node.start, node.end])
|
||||||
const yellow = 0xffff00
|
const yellow = 0xffff00
|
||||||
colorSegment(selected, yellow)
|
colorSegment(selected, yellow)
|
||||||
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
@ -1459,10 +1615,10 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sceneInfra.highlightCallback([0, 0])
|
editorManager.setHighlightRange([0, 0])
|
||||||
},
|
},
|
||||||
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
||||||
sceneInfra.highlightCallback([0, 0])
|
editorManager.setHighlightRange([0, 0])
|
||||||
const parent = getParentGroup(selected, [
|
const parent = getParentGroup(selected, [
|
||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
@ -1680,7 +1836,7 @@ export async function getSketchOrientationDetails(
|
|||||||
sketchPathToNode: PathToNode
|
sketchPathToNode: PathToNode
|
||||||
): Promise<{
|
): Promise<{
|
||||||
quat: Quaternion
|
quat: Quaternion
|
||||||
sketchDetails: SketchDetails
|
sketchDetails: SketchDetails & { faceId?: string }
|
||||||
}> {
|
}> {
|
||||||
const sketchGroup = sketchGroupFromPathToNode({
|
const sketchGroup = sketchGroupFromPathToNode({
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
@ -1696,20 +1852,13 @@ export async function getSketchOrientationDetails(
|
|||||||
zAxis: [zAxis.x, zAxis.y, zAxis.z],
|
zAxis: [zAxis.x, zAxis.y, zAxis.z],
|
||||||
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
|
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
|
||||||
origin: [0, 0, 0],
|
origin: [0, 0, 0],
|
||||||
|
faceId: sketchGroup.on.id,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sketchGroup.on.type === 'face') {
|
if (sketchGroup.on.type === 'face') {
|
||||||
const faceInfo: Models['FaceIsPlanar_type'] = (
|
const faceInfo = await getFaceDetails(sketchGroup.on.faceId)
|
||||||
await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'face_is_planar',
|
|
||||||
object_id: sketchGroup.on.faceId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)?.data?.data
|
|
||||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||||
throw new Error('faceInfo')
|
throw new Error('faceInfo')
|
||||||
const { z_axis, y_axis, origin } = 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],
|
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
||||||
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
||||||
origin: [origin.x, origin.y, origin.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 {
|
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
|
||||||
const dummyCam = new PerspectiveCamera()
|
const dummyCam = new PerspectiveCamera()
|
||||||
dummyCam.up.set(0, 0, 1)
|
dummyCam.up.set(0, 0, 1)
|
||||||
|
@ -24,7 +24,6 @@ import {
|
|||||||
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import * as TWEEN from '@tweenjs/tween.js'
|
import * as TWEEN from '@tweenjs/tween.js'
|
||||||
import { SourceRange } from 'lang/wasm'
|
|
||||||
import { Axis } from 'lib/selections'
|
import { Axis } from 'lib/selections'
|
||||||
import { type BaseUnit } from 'lib/settings/settingsTypes'
|
import { type BaseUnit } from 'lib/settings/settingsTypes'
|
||||||
import { CameraControls } from './CameraControls'
|
import { CameraControls } from './CameraControls'
|
||||||
@ -149,10 +148,6 @@ export class SceneInfra {
|
|||||||
onMouseLeave: () => {},
|
onMouseLeave: () => {},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
highlightCallback: (a: SourceRange) => void = () => {}
|
|
||||||
setHighlightCallback(cb: (a: SourceRange) => void) {
|
|
||||||
this.highlightCallback = cb
|
|
||||||
}
|
|
||||||
|
|
||||||
modelingSend: SendType = (() => {}) as any
|
modelingSend: SendType = (() => {}) as any
|
||||||
setSend(send: SendType) {
|
setSend(send: SendType) {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { editorManager, kclManager } from 'lib/singletons'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useStore } from 'useStore'
|
|
||||||
|
|
||||||
export function AstExplorer() {
|
export function AstExplorer() {
|
||||||
const setHighlightRange = useStore((s) => s.setHighlightRange)
|
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
// TODO maybe need to have callback to make sure it stays in sync
|
// TODO maybe need to have callback to make sure it stays in sync
|
||||||
@ -42,7 +40,7 @@ export function AstExplorer() {
|
|||||||
<div
|
<div
|
||||||
className="h-full relative"
|
className="h-full relative"
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
setHighlightRange([0, 0])
|
editorManager.setHighlightRange([0, 0])
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<pre className="text-xs">
|
<pre className="text-xs">
|
||||||
@ -88,7 +86,6 @@ function DisplayObj({
|
|||||||
filterKeys: string[]
|
filterKeys: string[]
|
||||||
node: any
|
node: any
|
||||||
}) {
|
}) {
|
||||||
const setHighlightRange = useStore((s) => s.setHighlightRange)
|
|
||||||
const { send } = useModelingContext()
|
const { send } = useModelingContext()
|
||||||
const ref = useRef<HTMLPreElement>(null)
|
const ref = useRef<HTMLPreElement>(null)
|
||||||
const [hasCursor, setHasCursor] = useState(false)
|
const [hasCursor, setHasCursor] = useState(false)
|
||||||
@ -112,12 +109,12 @@ function DisplayObj({
|
|||||||
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
||||||
}`}
|
}`}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
setHighlightRange([obj?.start || 0, obj.end])
|
editorManager.setHighlightRange([obj?.start || 0, obj.end])
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}}
|
}}
|
||||||
onMouseMove={(e) => {
|
onMouseMove={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setHighlightRange([obj?.start || 0, obj.end])
|
editorManager.setHighlightRange([obj?.start || 0, obj.end])
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
send({
|
send({
|
||||||
|
@ -137,34 +137,33 @@ export function useCalc({
|
|||||||
setAvailableVarInfo(varInfo)
|
setAvailableVarInfo(varInfo)
|
||||||
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
}, [kclManager.ast, kclManager.programMemory, selectionRange])
|
||||||
|
|
||||||
useEffect(async () => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const code = `const __result__ = ${value}`
|
const code = `const __result__ = ${value}`
|
||||||
parse(code).then((ast) => {
|
const ast = parse(code)
|
||||||
const _programMem: any = { root: {}, return: null }
|
const _programMem: any = { root: {}, return: null }
|
||||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||||
})
|
})
|
||||||
executeAst({
|
executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
useFakeExecutor: true,
|
useFakeExecutor: true,
|
||||||
programMemoryOverride: JSON.parse(
|
programMemoryOverride: JSON.parse(
|
||||||
JSON.stringify(kclManager.programMemory)
|
JSON.stringify(kclManager.programMemory)
|
||||||
),
|
),
|
||||||
}).then(({ programMemory }) => {
|
}).then(({ programMemory }) => {
|
||||||
const resultDeclaration = ast.body.find(
|
const resultDeclaration = ast.body.find(
|
||||||
(a) =>
|
(a) =>
|
||||||
a.type === 'VariableDeclaration' &&
|
a.type === 'VariableDeclaration' &&
|
||||||
a.declarations?.[0]?.id?.name === '__result__'
|
a.declarations?.[0]?.id?.name === '__result__'
|
||||||
)
|
)
|
||||||
const init =
|
const init =
|
||||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||||
resultDeclaration?.declarations?.[0]?.init
|
resultDeclaration?.declarations?.[0]?.init
|
||||||
const result = programMemory?.root?.__result__?.value
|
const result = programMemory?.root?.__result__?.value
|
||||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||||
init && setValueNode(init)
|
init && setValueNode(init)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setCalcResult('NAN')
|
setCalcResult('NAN')
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useMachine } from '@xstate/react'
|
import { useMachine } from '@xstate/react'
|
||||||
|
import { editorManager } from 'lib/singletons'
|
||||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||||
import { createContext } from 'react'
|
import { createContext, useEffect } from 'react'
|
||||||
import { EventFrom, StateFrom } from 'xstate'
|
import { EventFrom, StateFrom } from 'xstate'
|
||||||
|
|
||||||
type CommandsContextType = {
|
type CommandsContextType = {
|
||||||
@ -30,6 +31,10 @@ export const CommandBarProvider = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
editorManager.setCommandBarSend(commandBarSend)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandsContext.Provider
|
<CommandsContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -61,6 +61,16 @@ const CustomIconMap = {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</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: (
|
clipboardCheckmark: (
|
||||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
sceneInfra,
|
sceneInfra,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
codeManager,
|
codeManager,
|
||||||
|
editorManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||||
import {
|
import {
|
||||||
@ -53,10 +54,9 @@ import { exportFromEngine } from 'lib/exportFromEngine'
|
|||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { EditorSelection } from '@uiw/react-codemirror'
|
import { EditorSelection } from '@uiw/react-codemirror'
|
||||||
import { Vector3 } from 'three'
|
|
||||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -98,17 +98,6 @@ export const ModelingMachineProvider = ({
|
|||||||
)
|
)
|
||||||
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
|
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
|
||||||
|
|
||||||
const {
|
|
||||||
isShiftDown,
|
|
||||||
editorView,
|
|
||||||
setLastCodeMirrorSelectionUpdatedFromScene,
|
|
||||||
} = useStore((s) => ({
|
|
||||||
isShiftDown: s.isShiftDown,
|
|
||||||
editorView: s.editorView,
|
|
||||||
setLastCodeMirrorSelectionUpdatedFromScene:
|
|
||||||
s.setLastCodeMirrorSelectionUpdatedFromScene,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Settings machine setup
|
// Settings machine setup
|
||||||
// const retrievedSettings = useRef(
|
// const retrievedSettings = useRef(
|
||||||
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
||||||
@ -135,29 +124,33 @@ export const ModelingMachineProvider = ({
|
|||||||
'Set selection': assign(({ selectionRanges }, event) => {
|
'Set selection': assign(({ selectionRanges }, event) => {
|
||||||
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
||||||
const setSelections = event.data
|
const setSelections = event.data
|
||||||
if (!editorView) return {}
|
if (!editorManager.editorView) return {}
|
||||||
const dispatchSelection = (selection?: EditorSelection) => {
|
const dispatchSelection = (selection?: EditorSelection) => {
|
||||||
if (!selection) return // TODO less of hack for the below please
|
if (!selection) return // TODO less of hack for the below please
|
||||||
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
|
editorManager.lastSelectionEvent = Date.now()
|
||||||
setTimeout(() => editorView.dispatch({ selection }))
|
setTimeout(() => {
|
||||||
|
if (editorManager.editorView) {
|
||||||
|
editorManager.editorView.dispatch({ selection })
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
let selections: Selections = {
|
let selections: Selections = {
|
||||||
codeBasedSelections: [],
|
codeBasedSelections: [],
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
}
|
}
|
||||||
if (setSelections.selectionType === 'singleCodeCursor') {
|
if (setSelections.selectionType === 'singleCodeCursor') {
|
||||||
if (!setSelections.selection && isShiftDown) {
|
if (!setSelections.selection && editorManager.isShiftDown) {
|
||||||
} else if (!setSelections.selection && !isShiftDown) {
|
} else if (!setSelections.selection && !editorManager.isShiftDown) {
|
||||||
selections = {
|
selections = {
|
||||||
codeBasedSelections: [],
|
codeBasedSelections: [],
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
}
|
}
|
||||||
} else if (setSelections.selection && !isShiftDown) {
|
} else if (setSelections.selection && !editorManager.isShiftDown) {
|
||||||
selections = {
|
selections = {
|
||||||
codeBasedSelections: [setSelections.selection],
|
codeBasedSelections: [setSelections.selection],
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
}
|
}
|
||||||
} else if (setSelections.selection && isShiftDown) {
|
} else if (setSelections.selection && editorManager.isShiftDown) {
|
||||||
selections = {
|
selections = {
|
||||||
codeBasedSelections: [
|
codeBasedSelections: [
|
||||||
...selectionRanges.codeBasedSelections,
|
...selectionRanges.codeBasedSelections,
|
||||||
@ -180,6 +173,7 @@ export const ModelingMachineProvider = ({
|
|||||||
engineCommandManager.sendSceneCommand(event)
|
engineCommandManager.sendSceneCommand(event)
|
||||||
)
|
)
|
||||||
updateSceneObjectColors()
|
updateSceneObjectColors()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectionRanges: selections,
|
selectionRanges: selections,
|
||||||
}
|
}
|
||||||
@ -192,7 +186,7 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setSelections.selectionType === 'otherSelection') {
|
if (setSelections.selectionType === 'otherSelection') {
|
||||||
if (isShiftDown) {
|
if (editorManager.isShiftDown) {
|
||||||
selections = {
|
selections = {
|
||||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||||
otherSelections: [setSelections.selection],
|
otherSelections: [setSelections.selection],
|
||||||
@ -324,16 +318,9 @@ export const ModelingMachineProvider = ({
|
|||||||
)
|
)
|
||||||
await kclManager.executeAstMock(modifiedAst)
|
await kclManager.executeAstMock(modifiedAst)
|
||||||
|
|
||||||
const forward = new Vector3(...data.zAxis)
|
await letEngineAnimateAndSyncCamAfter(
|
||||||
const up = new Vector3(...data.yAxis)
|
engineCommandManager,
|
||||||
|
data.faceId
|
||||||
let target = new Vector3(...data.position).multiplyScalar(
|
|
||||||
sceneInfra._baseUnitMultiplier
|
|
||||||
)
|
|
||||||
const quaternion = quaternionFromUpNForward(up, forward)
|
|
||||||
await sceneInfra.camControls.tweenCameraToQuaternion(
|
|
||||||
quaternion,
|
|
||||||
target
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -348,6 +335,7 @@ export const ModelingMachineProvider = ({
|
|||||||
data.plane
|
data.plane
|
||||||
)
|
)
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
|
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||||
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
|
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
|
||||||
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
|
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
|
||||||
return {
|
return {
|
||||||
@ -364,9 +352,9 @@ export const ModelingMachineProvider = ({
|
|||||||
sourceRange
|
sourceRange
|
||||||
)
|
)
|
||||||
const info = await getSketchOrientationDetails(sketchPathToNode || [])
|
const info = await getSketchOrientationDetails(sketchPathToNode || [])
|
||||||
await sceneInfra.camControls.tweenCameraToQuaternion(
|
await letEngineAnimateAndSyncCamAfter(
|
||||||
info.quat,
|
engineCommandManager,
|
||||||
new Vector3(...info.sketchDetails.origin)
|
info?.sketchDetails?.faceId || ''
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
sketchPathToNode: sketchPathToNode || [],
|
sketchPathToNode: sketchPathToNode || [],
|
||||||
@ -516,6 +504,19 @@ export const ModelingMachineProvider = ({
|
|||||||
})
|
})
|
||||||
}, [modelingSend])
|
}, [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({
|
useStateMachineCommands({
|
||||||
machineId: 'modeling',
|
machineId: 'modeling',
|
||||||
state: modelingState,
|
state: modelingState,
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import { undo, redo } from '@codemirror/commands'
|
|
||||||
import ReactCodeMirror from '@uiw/react-codemirror'
|
import ReactCodeMirror from '@uiw/react-codemirror'
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { useEffect, useMemo, useRef } from 'react'
|
import { useEffect, useMemo } from 'react'
|
||||||
import { useStore } from 'useStore'
|
|
||||||
import { processCodeMirrorRanges } from 'lib/selections'
|
|
||||||
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
|
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
|
||||||
import { lineHighlightField } from 'editor/highlightextension'
|
import { lineHighlightField } from 'editor/highlightextension'
|
||||||
import { roundOff } from 'lib/utils'
|
import { roundOff } from 'lib/utils'
|
||||||
@ -21,7 +16,6 @@ import {
|
|||||||
EditorView,
|
EditorView,
|
||||||
dropCursor,
|
dropCursor,
|
||||||
drawSelection,
|
drawSelection,
|
||||||
ViewUpdate,
|
|
||||||
} from '@codemirror/view'
|
} from '@codemirror/view'
|
||||||
import {
|
import {
|
||||||
indentWithTab,
|
indentWithTab,
|
||||||
@ -29,7 +23,7 @@ import {
|
|||||||
historyKeymap,
|
historyKeymap,
|
||||||
history,
|
history,
|
||||||
} from '@codemirror/commands'
|
} from '@codemirror/commands'
|
||||||
import { lintGutter, lintKeymap, linter } from '@codemirror/lint'
|
import { lintGutter, lintKeymap } from '@codemirror/lint'
|
||||||
import {
|
import {
|
||||||
foldGutter,
|
foldGutter,
|
||||||
foldKeymap,
|
foldKeymap,
|
||||||
@ -39,25 +33,20 @@ import {
|
|||||||
syntaxHighlighting,
|
syntaxHighlighting,
|
||||||
defaultHighlightStyle,
|
defaultHighlightStyle,
|
||||||
} from '@codemirror/language'
|
} from '@codemirror/language'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
|
||||||
import interact from '@replit/codemirror-interact'
|
import interact from '@replit/codemirror-interact'
|
||||||
import { engineCommandManager, sceneInfra, kclManager } from 'lib/singletons'
|
import { kclManager, editorManager, codeManager } from 'lib/singletons'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
|
||||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import makeUrlPathRelative from 'lib/makeUrlPathRelative'
|
import makeUrlPathRelative from 'lib/makeUrlPathRelative'
|
||||||
import { useLspContext } from 'components/LspProvider'
|
import { useLspContext } from 'components/LspProvider'
|
||||||
import { Prec, EditorState, Extension, SelectionRange } from '@codemirror/state'
|
import { Prec, EditorState, Extension } from '@codemirror/state'
|
||||||
import {
|
import {
|
||||||
closeBrackets,
|
closeBrackets,
|
||||||
closeBracketsKeymap,
|
closeBracketsKeymap,
|
||||||
completionKeymap,
|
completionKeymap,
|
||||||
hasNextSnippetField,
|
|
||||||
} from '@codemirror/autocomplete'
|
} from '@codemirror/autocomplete'
|
||||||
import { kclErrorsToDiagnostics } from 'lang/errors'
|
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -77,13 +66,6 @@ export const KclEditorPane = () => {
|
|||||||
context.app.theme.current === Themes.System
|
context.app.theme.current === Themes.System
|
||||||
? getSystemTheme()
|
? getSystemTheme()
|
||||||
: context.app.theme.current
|
: 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 { copilotLSP, kclLSP } = useLspContext()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
@ -96,90 +78,15 @@ export const KclEditorPane = () => {
|
|||||||
|
|
||||||
useHotkeys('mod+z', (e) => {
|
useHotkeys('mod+z', (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (editorView) {
|
editorManager.undo()
|
||||||
undo(editorView)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
useHotkeys('mod+shift+z', (e) => {
|
useHotkeys('mod+shift+z', (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (editorView) {
|
editorManager.redo()
|
||||||
redo(editorView)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const textWrapping = context.textEditor.textWrapping
|
||||||
context: { selectionRanges },
|
const cursorBlinking = context.textEditor.blinkingCursor
|
||||||
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 editorExtensions = useMemo(() => {
|
const editorExtensions = useMemo(() => {
|
||||||
const extensions = [
|
const extensions = [
|
||||||
@ -202,7 +109,7 @@ export const KclEditorPane = () => {
|
|||||||
{
|
{
|
||||||
key: 'Meta-k',
|
key: 'Meta-k',
|
||||||
run: () => {
|
run: () => {
|
||||||
commandBarSend({ type: 'Open' })
|
editorManager.commandBarSend({ type: 'Open' })
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -216,11 +123,7 @@ export const KclEditorPane = () => {
|
|||||||
{
|
{
|
||||||
key: editorShortcutMeta.convertToVariable.codeMirror,
|
key: editorShortcutMeta.convertToVariable.codeMirror,
|
||||||
run: () => {
|
run: () => {
|
||||||
if (convertEnabled) {
|
return editorManager.convertToVariable()
|
||||||
convertCallback()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
@ -233,9 +136,6 @@ export const KclEditorPane = () => {
|
|||||||
if (!TEST) {
|
if (!TEST) {
|
||||||
extensions.push(
|
extensions.push(
|
||||||
lintGutter(),
|
lintGutter(),
|
||||||
linter((_view: EditorView) => {
|
|
||||||
return kclErrorsToDiagnostics(errors)
|
|
||||||
}),
|
|
||||||
lineNumbers(),
|
lineNumbers(),
|
||||||
highlightActiveLineGutter(),
|
highlightActiveLineGutter(),
|
||||||
highlightSpecialChars(),
|
highlightSpecialChars(),
|
||||||
@ -288,13 +188,7 @@ export const KclEditorPane = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return extensions
|
return extensions
|
||||||
}, [
|
}, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
|
||||||
kclLSP,
|
|
||||||
copilotLSP,
|
|
||||||
textWrapping.current,
|
|
||||||
cursorBlinking.current,
|
|
||||||
convertCallback,
|
|
||||||
])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -302,18 +196,15 @@ export const KclEditorPane = () => {
|
|||||||
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
|
||||||
>
|
>
|
||||||
<ReactCodeMirror
|
<ReactCodeMirror
|
||||||
value={editorCode}
|
value={codeManager.code}
|
||||||
extensions={editorExtensions}
|
extensions={editorExtensions}
|
||||||
onUpdate={onUpdate}
|
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onCreateEditor={(_editorView) => setEditorView(_editorView)}
|
onCreateEditor={(_editorView) =>
|
||||||
|
editorManager.setEditorView(_editorView)
|
||||||
|
}
|
||||||
indentWithTab={false}
|
indentWithTab={false}
|
||||||
basicSetup={false}
|
basicSetup={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 { enginelessExecutor } from '../../../lib/testHelpers'
|
||||||
import { initPromise, parse } from '../../../lang/wasm'
|
import { initPromise, parse } from '../../../lang/wasm'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('processMemory', () => {
|
describe('processMemory', () => {
|
||||||
it('should grab the values and remove and geo data', async () => {
|
it('should grab the values and remove and geo data', async () => {
|
||||||
@ -26,7 +28,7 @@ describe('processMemory', () => {
|
|||||||
|> lineTo([0.98, 5.16], %)
|
|> lineTo([0.98, 5.16], %)
|
||||||
|> lineTo([2.15, 4.32], %)
|
|> lineTo([2.15, 4.32], %)
|
||||||
// |> rx(90, %)`
|
// |> rx(90, %)`
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast, {
|
const programMemory = await enginelessExecutor(ast, {
|
||||||
root: {},
|
root: {},
|
||||||
return: null,
|
return: null,
|
||||||
|
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 { Marked } from '@ts-stack/markdown'
|
||||||
import { posToOffset } from 'editor/plugins/lsp/util'
|
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||||
import { Program, ProgramMemory } from 'lang/wasm'
|
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 type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
|
||||||
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
||||||
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
||||||
@ -39,6 +39,8 @@ const CompletionItemKindMap = Object.fromEntries(
|
|||||||
) as Record<CompletionItemKind, string>
|
) as Record<CompletionItemKind, string>
|
||||||
|
|
||||||
const changesDelay = 600
|
const changesDelay = 600
|
||||||
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
const updateDelay = 100
|
||||||
|
|
||||||
export class LanguageServerPlugin implements PluginValue {
|
export class LanguageServerPlugin implements PluginValue {
|
||||||
public client: LanguageServerClient
|
public client: LanguageServerClient
|
||||||
@ -47,6 +49,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
public workspaceFolders: LSP.WorkspaceFolder[]
|
public workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
private documentVersion: number
|
private documentVersion: number
|
||||||
private foldingRanges: LSP.FoldingRange[] | null = null
|
private foldingRanges: LSP.FoldingRange[] | null = null
|
||||||
|
private viewUpdate: ViewUpdate | null = null
|
||||||
private _defferer = deferExecution((code: string) => {
|
private _defferer = deferExecution((code: string) => {
|
||||||
try {
|
try {
|
||||||
// Update the state (not the editor) with the new code.
|
// Update the state (not the editor) with the new code.
|
||||||
@ -57,6 +60,10 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
},
|
},
|
||||||
contentChanges: [{ text: code }],
|
contentChanges: [{ text: code }],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (this.viewUpdate) {
|
||||||
|
editorManager.handleOnViewUpdate(this.viewUpdate)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
@ -80,14 +87,27 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
update({ docChanged }: ViewUpdate) {
|
update(viewUpdate: ViewUpdate) {
|
||||||
if (!docChanged) return
|
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()
|
const newCode = this.view.state.doc.toString()
|
||||||
|
|
||||||
codeManager.code = newCode
|
codeManager.code = newCode
|
||||||
codeManager.writeToFile()
|
codeManager.writeToFile()
|
||||||
kclManager.executeCode()
|
kclManager.executeCode()
|
||||||
|
|
||||||
this.sendChange({
|
this.sendChange({
|
||||||
documentText: newCode,
|
documentText: newCode,
|
||||||
})
|
})
|
||||||
@ -357,15 +377,9 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
try {
|
try {
|
||||||
switch (notification.method) {
|
switch (notification.method) {
|
||||||
case 'textDocument/publishDiagnostics':
|
case 'textDocument/publishDiagnostics':
|
||||||
const params = notification.params as PublishDiagnosticsParams
|
//const params = notification.params as PublishDiagnosticsParams
|
||||||
this.processDiagnostics(params)
|
// this is sometimes slower than our actual typing.
|
||||||
// Update the kcl errors pane.
|
//this.processDiagnostics(params)
|
||||||
/*if (!kclManager.isExecuting) {
|
|
||||||
kclManager.kclErrors = lspDiagnosticsToKclErrors(
|
|
||||||
this.view.state.doc,
|
|
||||||
params.diagnostics
|
|
||||||
)
|
|
||||||
}*/
|
|
||||||
break
|
break
|
||||||
case 'window/logMessage':
|
case 'window/logMessage':
|
||||||
console.log(
|
console.log(
|
||||||
@ -385,17 +399,6 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
// The server has updated the AST, we should update elsewhere.
|
// The server has updated the AST, we should update elsewhere.
|
||||||
let updatedAst = notification.params as Program
|
let updatedAst = notification.params as Program
|
||||||
console.log('[lsp]: Updated AST', updatedAst)
|
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.
|
// Update the folding ranges, since the AST has changed.
|
||||||
// This is a hack since codemirror does not support async foldService.
|
// This is a hack since codemirror does not support async foldService.
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useStore } from 'useStore'
|
import { editorManager, engineCommandManager } from 'lib/singletons'
|
||||||
import { engineCommandManager } from 'lib/singletons'
|
|
||||||
import { useModelingContext } from './useModelingContext'
|
import { useModelingContext } from './useModelingContext'
|
||||||
import { getEventForSelectWithPoint } from 'lib/selections'
|
import { getEventForSelectWithPoint } from 'lib/selections'
|
||||||
|
|
||||||
export function useEngineConnectionSubscriptions() {
|
export function useEngineConnectionSubscriptions() {
|
||||||
const { setHighlightRange, highlightRange } = useStore((s) => ({
|
|
||||||
setHighlightRange: s.setHighlightRange,
|
|
||||||
highlightRange: s.highlightRange,
|
|
||||||
}))
|
|
||||||
const { send, context } = useModelingContext()
|
const { send, context } = useModelingContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -21,12 +16,13 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
if (data?.entity_id) {
|
if (data?.entity_id) {
|
||||||
const sourceRange =
|
const sourceRange =
|
||||||
engineCommandManager.artifactMap?.[data.entity_id]?.range
|
engineCommandManager.artifactMap?.[data.entity_id]?.range
|
||||||
setHighlightRange(sourceRange)
|
editorManager.setHighlightRange(sourceRange)
|
||||||
} else if (
|
} else if (
|
||||||
!highlightRange ||
|
!editorManager.highlightRange ||
|
||||||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
|
(editorManager.highlightRange[0] !== 0 &&
|
||||||
|
editorManager.highlightRange[1] !== 0)
|
||||||
) {
|
) {
|
||||||
setHighlightRange([0, 0])
|
editorManager.setHighlightRange([0, 0])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -43,10 +39,5 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
unSubHover()
|
unSubHover()
|
||||||
unSubClick()
|
unSubClick()
|
||||||
}
|
}
|
||||||
}, [
|
}, [engineCommandManager, context?.sketchEnginePathId])
|
||||||
engineCommandManager,
|
|
||||||
setHighlightRange,
|
|
||||||
highlightRange,
|
|
||||||
context?.sketchEnginePathId,
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useStore } from '../useStore'
|
import { editorManager } from 'lib/singletons'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
// Kurt's note: codeMirror styling overrides were needed to make this work
|
// 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
|
// search for code-mirror-override in the repo to find the relevant styles
|
||||||
|
|
||||||
export function useHotKeyListener() {
|
export function useHotKeyListener() {
|
||||||
const { setIsShiftDown } = useStore((s) => ({
|
|
||||||
setIsShiftDown: s.setIsShiftDown,
|
|
||||||
}))
|
|
||||||
const keyName = 'Shift'
|
const keyName = 'Shift'
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) =>
|
const handleKeyDown = (event: KeyboardEvent) =>
|
||||||
event.key === keyName && setIsShiftDown(true)
|
event.key === keyName && editorManager.setIsShiftDown(true)
|
||||||
const handleKeyUp = (event: KeyboardEvent) =>
|
const handleKeyUp = (event: KeyboardEvent) =>
|
||||||
event.key === keyName && setIsShiftDown(false)
|
event.key === keyName && editorManager.setIsShiftDown(false)
|
||||||
window.addEventListener('keydown', handleKeyDown)
|
window.addEventListener('keydown', handleKeyDown)
|
||||||
window.addEventListener('keyup', handleKeyUp)
|
window.addEventListener('keyup', handleKeyUp)
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', handleKeyDown)
|
window.removeEventListener('keydown', handleKeyDown)
|
||||||
window.removeEventListener('keyup', handleKeyUp)
|
window.removeEventListener('keyup', handleKeyUp)
|
||||||
}
|
}
|
||||||
}, [setIsShiftDown])
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import {
|
|||||||
SetVarNameModal,
|
SetVarNameModal,
|
||||||
createSetVarNameModal,
|
createSetVarNameModal,
|
||||||
} from 'components/SetVarNameModal'
|
} from 'components/SetVarNameModal'
|
||||||
import { kclManager } from 'lib/singletons'
|
import { editorManager, kclManager } from 'lib/singletons'
|
||||||
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
||||||
import { isNodeSafeToReplace } from 'lang/queryAst'
|
import { isNodeSafeToReplace } from 'lang/queryAst'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@ -13,6 +13,11 @@ const getModalInfo = createSetVarNameModal(SetVarNameModal)
|
|||||||
export function useConvertToVariable() {
|
export function useConvertToVariable() {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
const [enable, setEnabled] = useState(false)
|
const [enable, setEnabled] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
editorManager.convertToVariableEnabled = enable
|
||||||
|
}, [enable])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { isSafe, value } = isNodeSafeToReplace(
|
const { isSafe, value } = isNodeSafeToReplace(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
@ -45,5 +50,7 @@ export function useConvertToVariable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editorManager.convertToVariableCallback = handleClick
|
||||||
|
|
||||||
return { enable, handleClick }
|
return { enable, handleClick }
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,10 @@ import { KCLError } from './errors'
|
|||||||
import { createContext, useContext, useEffect, useState } from 'react'
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import { useLoaderData } from 'react-router-dom'
|
import { useLoaderData } from 'react-router-dom'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
import { codeManager, kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
|
|
||||||
const KclContext = createContext({
|
const KclContext = createContext({
|
||||||
code: codeManager?.code || '',
|
code: codeManager?.code || '',
|
||||||
editorCode: codeManager?.code || '',
|
|
||||||
programMemory: kclManager?.programMemory,
|
programMemory: kclManager?.programMemory,
|
||||||
ast: kclManager?.ast,
|
ast: kclManager?.ast,
|
||||||
isExecuting: kclManager?.isExecuting,
|
isExecuting: kclManager?.isExecuting,
|
||||||
@ -30,7 +28,6 @@ export function KclContextProvider({
|
|||||||
const { code: loadedCode } = useLoaderData() as IndexLoaderData
|
const { code: loadedCode } = useLoaderData() as IndexLoaderData
|
||||||
// Both the code state and the editor state start off with the same code.
|
// Both the code state and the editor state start off with the same code.
|
||||||
const [code, setCode] = useState(loadedCode || codeManager.code)
|
const [code, setCode] = useState(loadedCode || codeManager.code)
|
||||||
const [editorCode, setEditorCode] = useState(code)
|
|
||||||
|
|
||||||
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
||||||
const [ast, setAst] = useState(kclManager.ast)
|
const [ast, setAst] = useState(kclManager.ast)
|
||||||
@ -42,7 +39,6 @@ export function KclContextProvider({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
codeManager.registerCallBacks({
|
codeManager.registerCallBacks({
|
||||||
setCode,
|
setCode,
|
||||||
setEditorCode,
|
|
||||||
})
|
})
|
||||||
kclManager.registerCallBacks({
|
kclManager.registerCallBacks({
|
||||||
setProgramMemory,
|
setProgramMemory,
|
||||||
@ -54,15 +50,10 @@ export function KclContextProvider({
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const params = useParams()
|
|
||||||
useEffect(() => {
|
|
||||||
codeManager.setParams(params)
|
|
||||||
}, [params])
|
|
||||||
return (
|
return (
|
||||||
<KclContext.Provider
|
<KclContext.Provider
|
||||||
value={{
|
value={{
|
||||||
code,
|
code,
|
||||||
editorCode,
|
|
||||||
programMemory,
|
programMemory,
|
||||||
ast,
|
ast,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { executeAst } from 'useStore'
|
import { executeAst } from 'useStore'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { KCLError } from './errors'
|
import { KCLError, kclErrorsToDiagnostics } from './errors'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { EngineCommandManager } from './std/engineConnection'
|
import { EngineCommandManager } from './std/engineConnection'
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
ExtrudeGroup,
|
ExtrudeGroup,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath } from './queryAst'
|
||||||
import { codeManager } from 'lib/singletons'
|
import { codeManager, editorManager } from 'lib/singletons'
|
||||||
|
|
||||||
export class KclManager {
|
export class KclManager {
|
||||||
private _ast: Program = {
|
private _ast: Program = {
|
||||||
@ -43,16 +43,14 @@ export class KclManager {
|
|||||||
const ast = this.safeParse(code)
|
const ast = this.safeParse(code)
|
||||||
if (!ast) return
|
if (!ast) return
|
||||||
try {
|
try {
|
||||||
parse(recast(ast)).then((newAst) => {
|
|
||||||
const fmtAndStringify = (ast: Program) =>
|
const fmtAndStringify = (ast: Program) =>
|
||||||
JSON.stringify(newAst)
|
JSON.stringify(parse(recast(ast)))
|
||||||
const isAstTheSame = fmtAndStringify(ast) === fmtAndStringify(this._ast)
|
const isAstTheSame = fmtAndStringify(ast) === fmtAndStringify(this._ast)
|
||||||
if (isAstTheSame) return
|
if (isAstTheSame) return
|
||||||
this.executeAst(ast)
|
|
||||||
})
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
this.executeAst(ast)
|
||||||
}, 600)
|
}, 600)
|
||||||
|
|
||||||
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
||||||
@ -92,6 +90,8 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
set kclErrors(kclErrors) {
|
set kclErrors(kclErrors) {
|
||||||
this._kclErrors = kclErrors
|
this._kclErrors = kclErrors
|
||||||
|
let diagnostics = kclErrorsToDiagnostics(kclErrors)
|
||||||
|
editorManager.setDiagnostics(diagnostics)
|
||||||
this._kclErrorsCallBack(kclErrors)
|
this._kclErrorsCallBack(kclErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,9 +145,9 @@ export class KclManager {
|
|||||||
this._executeCallback = callback
|
this._executeCallback = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
async safeParse(code: string): Promise<Program | null> {
|
safeParse(code: string): Program | null {
|
||||||
try {
|
try {
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
this.kclErrors = []
|
this.kclErrors = []
|
||||||
return ast
|
return ast
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -347,6 +347,16 @@ export class KclManager {
|
|||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
|
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
|
||||||
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
|
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
|
||||||
}
|
}
|
||||||
|
enterEditMode() {
|
||||||
|
enterEditMode(this.programMemory, this.engineCommandManager)
|
||||||
|
}
|
||||||
|
exitEditMode() {
|
||||||
|
this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'edit_mode_exit' },
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enterEditMode(
|
function enterEditMode(
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { initPromise, parse } from './wasm'
|
import { initPromise, parse } from './wasm'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('testing AST', () => {
|
describe('testing AST', () => {
|
||||||
test('5 + 6', async () => {
|
test('5 + 6', () => {
|
||||||
const result = await parse('5 +6')
|
const result = parse('5 +6')
|
||||||
delete (result as any).nonCodeMeta
|
delete (result as any).nonCodeMeta
|
||||||
expect(result.body).toEqual([
|
expect(result.body).toEqual([
|
||||||
{
|
{
|
||||||
@ -35,8 +37,8 @@ describe('testing AST', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('const myVar = 5', async () => {
|
test('const myVar = 5', () => {
|
||||||
const { body } = await parse('const myVar = 5')
|
const { body } = parse('const myVar = 5')
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -66,11 +68,11 @@ describe('testing AST', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('multi-line', async () => {
|
test('multi-line', () => {
|
||||||
const code = `const myVar = 5
|
const code = `const myVar = 5
|
||||||
const newVar = myVar + 1
|
const newVar = myVar + 1
|
||||||
`
|
`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -141,8 +143,8 @@ const newVar = myVar + 1
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('testing function declaration', () => {
|
describe('testing function declaration', () => {
|
||||||
test('fn funcN = (a, b) => {return a + b}', async () => {
|
test('fn funcN = (a, b) => {return a + b}', () => {
|
||||||
const { body } = await parse(
|
const { body } = parse(
|
||||||
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
|
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
|
||||||
)
|
)
|
||||||
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
||||||
@ -224,10 +226,10 @@ describe('testing function declaration', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('call expression assignment', async () => {
|
test('call expression assignment', () => {
|
||||||
const code = `fn funcN = (a, b) => { return a + b }
|
const code = `fn funcN = (a, b) => { return a + b }
|
||||||
const myVar = funcN(1, 2)`
|
const myVar = funcN(1, 2)`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
@ -357,14 +359,14 @@ const myVar = funcN(1, 2)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('testing pipe operator special', () => {
|
describe('testing pipe operator special', () => {
|
||||||
test('pipe operator with sketch', async () => {
|
test('pipe operator with sketch', () => {
|
||||||
let code = `const mySketch = startSketchAt([0, 0])
|
let code = `const mySketch = startSketchAt([0, 0])
|
||||||
|> lineTo([2, 3], %)
|
|> lineTo([2, 3], %)
|
||||||
|> lineTo([0, 1], %, "myPath")
|
|> lineTo([0, 1], %, "myPath")
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
`
|
`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
delete (body[0] as any).declarations[0].init.nonCodeMeta
|
delete (body[0] as any).declarations[0].init.nonCodeMeta
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
@ -562,9 +564,9 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('pipe operator with binary expression', async () => {
|
test('pipe operator with binary expression', () => {
|
||||||
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
|
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
delete (body as any)[0].declarations[0].init.nonCodeMeta
|
delete (body as any)[0].declarations[0].init.nonCodeMeta
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
@ -641,9 +643,9 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('array expression', async () => {
|
test('array expression', () => {
|
||||||
let code = `const yo = [1, '2', three, 4 + 5]`
|
let code = `const yo = [1, '2', three, 4 + 5]`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -713,12 +715,12 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('object expression ast', async () => {
|
test('object expression ast', () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const three = 3',
|
'const three = 3',
|
||||||
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -858,11 +860,11 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('nested object expression ast', async () => {
|
test('nested object expression ast', () => {
|
||||||
const code = `const yo = {key: {
|
const code = `const yo = {key: {
|
||||||
key2: 'value'
|
key2: 'value'
|
||||||
}}`
|
}}`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -928,9 +930,9 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('object expression with array ast', async () => {
|
test('object expression with array ast', () => {
|
||||||
const code = `const yo = {key: [1, '2']}`
|
const code = `const yo = {key: [1, '2']}`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -992,9 +994,9 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('object memberExpression simple', async () => {
|
test('object memberExpression simple', () => {
|
||||||
const code = `const prop = yo.one.two`
|
const code = `const prop = yo.one.two`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -1047,9 +1049,9 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('object memberExpression with square braces', async () => {
|
test('object memberExpression with square braces', () => {
|
||||||
const code = `const prop = yo.one["two"]`
|
const code = `const prop = yo.one["two"]`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -1103,9 +1105,9 @@ describe('testing pipe operator special', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('object memberExpression with two square braces literal and identifier', async () => {
|
test('object memberExpression with two square braces literal and identifier', () => {
|
||||||
const code = `const prop = yo["one"][two]`
|
const code = `const prop = yo["one"][two]`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body).toEqual([
|
expect(body).toEqual([
|
||||||
{
|
{
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
@ -1162,9 +1164,9 @@ describe('testing pipe operator special', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('nests binary expressions correctly', () => {
|
describe('nests binary expressions correctly', () => {
|
||||||
it('works with the simple case', async () => {
|
it('works with the simple case', () => {
|
||||||
const code = `const yo = 1 + 2`
|
const code = `const yo = 1 + 2`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body[0]).toEqual({
|
expect(body[0]).toEqual({
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
start: 0,
|
start: 0,
|
||||||
@ -1205,10 +1207,10 @@ describe('nests binary expressions correctly', () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should nest according to precedence with multiply first', async () => {
|
it('should nest according to precedence with multiply first', () => {
|
||||||
// should be binExp { binExp { lit-1 * lit-2 } + lit}
|
// should be binExp { binExp { lit-1 * lit-2 } + lit}
|
||||||
const code = `const yo = 1 * 2 + 3`
|
const code = `const yo = 1 * 2 + 3`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body[0]).toEqual({
|
expect(body[0]).toEqual({
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
start: 0,
|
start: 0,
|
||||||
@ -1262,10 +1264,10 @@ describe('nests binary expressions correctly', () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should nest according to precedence with sum first', async () => {
|
it('should nest according to precedence with sum first', () => {
|
||||||
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
|
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
|
||||||
const code = `const yo = 1 + 2 * 3`
|
const code = `const yo = 1 + 2 * 3`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect(body[0]).toEqual({
|
expect(body[0]).toEqual({
|
||||||
type: 'VariableDeclaration',
|
type: 'VariableDeclaration',
|
||||||
start: 0,
|
start: 0,
|
||||||
@ -1319,9 +1321,9 @@ describe('nests binary expressions correctly', () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should nest properly with two operators of equal precedence', async () => {
|
it('should nest properly with two operators of equal precedence', () => {
|
||||||
const code = `const yo = 1 + 2 - 3`
|
const code = `const yo = 1 + 2 - 3`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect((body[0] as any).declarations[0].init).toEqual({
|
expect((body[0] as any).declarations[0].init).toEqual({
|
||||||
type: 'BinaryExpression',
|
type: 'BinaryExpression',
|
||||||
start: 11,
|
start: 11,
|
||||||
@ -1356,9 +1358,9 @@ describe('nests binary expressions correctly', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should nest properly with two operators of equal (but higher) precedence', async () => {
|
it('should nest properly with two operators of equal (but higher) precedence', () => {
|
||||||
const code = `const yo = 1 * 2 / 3`
|
const code = `const yo = 1 * 2 / 3`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
expect((body[0] as any).declarations[0].init).toEqual({
|
expect((body[0] as any).declarations[0].init).toEqual({
|
||||||
type: 'BinaryExpression',
|
type: 'BinaryExpression',
|
||||||
start: 11,
|
start: 11,
|
||||||
@ -1393,9 +1395,9 @@ describe('nests binary expressions correctly', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should nest properly with longer example', async () => {
|
it('should nest properly with longer example', () => {
|
||||||
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
|
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
const init = (body[0] as any).declarations[0].init
|
const init = (body[0] as any).declarations[0].init
|
||||||
expect(init).toEqual({
|
expect(init).toEqual({
|
||||||
type: 'BinaryExpression',
|
type: 'BinaryExpression',
|
||||||
@ -1443,7 +1445,7 @@ describe('nests binary expressions correctly', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('check nonCodeMeta data is attached to the AST correctly', () => {
|
describe('check nonCodeMeta data is attached to the AST correctly', () => {
|
||||||
it('comments between expressions', async () => {
|
it('comments between expressions', () => {
|
||||||
const code = `
|
const code = `
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
// this is a comment
|
// this is a comment
|
||||||
@ -1458,14 +1460,12 @@ const key = 'c'`
|
|||||||
value: 'this is a comment',
|
value: 'this is a comment',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const { nonCodeMeta } = await parse(code)
|
const { nonCodeMeta } = parse(code)
|
||||||
expect(nonCodeMeta.nonCodeNodes[0][0]).toEqual(nonCodeMetaInstance)
|
expect(nonCodeMeta.nonCodeNodes[0][0]).toEqual(nonCodeMetaInstance)
|
||||||
|
|
||||||
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
||||||
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
||||||
const { nonCodeMeta: nonCodeMeta2 } = await parse(
|
const { nonCodeMeta: nonCodeMeta2 } = parse(codeWithExtraStartWhitespace)
|
||||||
codeWithExtraStartWhitespace
|
|
||||||
)
|
|
||||||
expect(nonCodeMeta2.nonCodeNodes[0][0].value).toStrictEqual(
|
expect(nonCodeMeta2.nonCodeNodes[0][0].value).toStrictEqual(
|
||||||
nonCodeMetaInstance.value
|
nonCodeMetaInstance.value
|
||||||
)
|
)
|
||||||
@ -1473,7 +1473,7 @@ const key = 'c'`
|
|||||||
nonCodeMetaInstance.start
|
nonCodeMetaInstance.start
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
it('comments nested within a block statement', async () => {
|
it('comments nested within a block statement', () => {
|
||||||
const code = `const mySketch = startSketchAt([0,0])
|
const code = `const mySketch = startSketchAt([0,0])
|
||||||
|> lineTo([0, 1], %, 'myPath')
|
|> lineTo([0, 1], %, 'myPath')
|
||||||
|> lineTo([1, 1], %) /* this is
|
|> lineTo([1, 1], %) /* this is
|
||||||
@ -1483,7 +1483,7 @@ const key = 'c'`
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
|
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
const indexOfSecondLineToExpression = 2
|
const indexOfSecondLineToExpression = 2
|
||||||
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
||||||
.nonCodeNodes
|
.nonCodeNodes
|
||||||
@ -1498,7 +1498,7 @@ const key = 'c'`
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('comments in a pipe expression', async () => {
|
it('comments in a pipe expression', () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const mySk1 = startSketchAt([0, 0])',
|
'const mySk1 = startSketchAt([0, 0])',
|
||||||
' |> lineTo([1, 1], %)',
|
' |> lineTo([1, 1], %)',
|
||||||
@ -1508,7 +1508,7 @@ const key = 'c'`
|
|||||||
' |> rx(90, %)',
|
' |> rx(90, %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
||||||
.nonCodeNodes[3][0]
|
.nonCodeNodes[3][0]
|
||||||
expect(sketchNonCodeMeta).toEqual({
|
expect(sketchNonCodeMeta).toEqual({
|
||||||
@ -1525,9 +1525,9 @@ const key = 'c'`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('test UnaryExpression', () => {
|
describe('test UnaryExpression', () => {
|
||||||
it('should parse a unary expression in simple var dec situation', async () => {
|
it('should parse a unary expression in simple var dec situation', () => {
|
||||||
const code = `const myVar = -min(4, 100)`
|
const code = `const myVar = -min(4, 100)`
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
||||||
expect(myVarInit).toEqual({
|
expect(myVarInit).toEqual({
|
||||||
type: 'UnaryExpression',
|
type: 'UnaryExpression',
|
||||||
@ -1550,9 +1550,9 @@ describe('test UnaryExpression', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('testing nested call expressions', () => {
|
describe('testing nested call expressions', () => {
|
||||||
it('callExp in a binExp in a callExp', async () => {
|
it('callExp in a binExp in a callExp', () => {
|
||||||
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
|
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
||||||
expect(myVarInit).toEqual({
|
expect(myVarInit).toEqual({
|
||||||
type: 'CallExpression',
|
type: 'CallExpression',
|
||||||
@ -1587,8 +1587,8 @@ describe('testing nested call expressions', () => {
|
|||||||
|
|
||||||
describe('should recognise callExpresions in binaryExpressions', () => {
|
describe('should recognise callExpresions in binaryExpressions', () => {
|
||||||
const code = "xLineTo(segEndX('seg02', %) + 1, %)"
|
const code = "xLineTo(segEndX('seg02', %) + 1, %)"
|
||||||
it('should recognise the callExp', async () => {
|
it('should recognise the callExp', () => {
|
||||||
const { body } = await parse(code)
|
const { body } = parse(code)
|
||||||
const callExpArgs = (body?.[0] as any).expression?.arguments
|
const callExpArgs = (body?.[0] as any).expression?.arguments
|
||||||
expect(callExpArgs).toEqual([
|
expect(callExpArgs).toEqual([
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { parse, initPromise } from './wasm'
|
import { parse, initPromise } from './wasm'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('testing artifacts', () => {
|
describe('testing artifacts', () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
@ -12,7 +14,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)`
|
// |> rx(45, %)`
|
||||||
const programMemory = await enginelessExecutor(await parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = programMemory?.root?.mySketch001
|
const sketch001 = programMemory?.root?.mySketch001
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
@ -68,7 +70,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> extrude(2, %)`
|
|> extrude(2, %)`
|
||||||
const programMemory = await enginelessExecutor(await parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = programMemory?.root?.mySketch001
|
const sketch001 = programMemory?.root?.mySketch001
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
@ -150,7 +152,7 @@ const sk2 = startSketchOn('XY')
|
|||||||
|> extrude(2, %)
|
|> extrude(2, %)
|
||||||
|
|
||||||
`
|
`
|
||||||
const programMemory = await enginelessExecutor(await parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const geos = [programMemory?.root?.theExtrude, programMemory?.root?.sk2]
|
const geos = [programMemory?.root?.theExtrude, programMemory?.root?.sk2]
|
||||||
expect(geos).toEqual([
|
expect(geos).toEqual([
|
||||||
|
@ -5,7 +5,7 @@ import { bracket } from 'lib/exampleKcl'
|
|||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { Params } from 'react-router-dom'
|
import { editorManager } from 'lib/singletons'
|
||||||
|
|
||||||
const PERSIST_CODE_TOKEN = 'persistCode'
|
const PERSIST_CODE_TOKEN = 'persistCode'
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ export default class CodeManager {
|
|||||||
private _code: string = bracket
|
private _code: string = bracket
|
||||||
private _updateState: (arg: string) => void = () => {}
|
private _updateState: (arg: string) => void = () => {}
|
||||||
private _updateEditor: (arg: string) => void = () => {}
|
private _updateEditor: (arg: string) => void = () => {}
|
||||||
private _params: Params<string> = {}
|
private _currentFilePath: string | null = null
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
@ -45,19 +45,12 @@ export default class CodeManager {
|
|||||||
return this._code
|
return this._code
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCallBacks({
|
registerCallBacks({ setCode }: { setCode: (arg: string) => void }) {
|
||||||
setCode,
|
|
||||||
setEditorCode,
|
|
||||||
}: {
|
|
||||||
setCode: (arg: string) => void
|
|
||||||
setEditorCode: (arg: string) => void
|
|
||||||
}) {
|
|
||||||
this._updateState = setCode
|
this._updateState = setCode
|
||||||
this._updateEditor = setEditorCode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setParams(params: Params<string>) {
|
updateCurrentFilePath(path: string) {
|
||||||
this._params = params
|
this._currentFilePath = path
|
||||||
}
|
}
|
||||||
|
|
||||||
// This updates the code state and calls the updateState function.
|
// This updates the code state and calls the updateState function.
|
||||||
@ -70,11 +63,14 @@ export default class CodeManager {
|
|||||||
|
|
||||||
// Update the code in the editor.
|
// Update the code in the editor.
|
||||||
updateCodeEditor(code: string): void {
|
updateCodeEditor(code: string): void {
|
||||||
if (this._code !== code) {
|
const lastCode = this._code
|
||||||
this.code = code
|
this.code = code
|
||||||
this._updateEditor(code)
|
|
||||||
}
|
|
||||||
this._updateEditor(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.
|
// Update the code, state, and the code the code mirror editor sees.
|
||||||
@ -91,8 +87,8 @@ export default class CodeManager {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Wait one event loop to give a chance for params to be set
|
// Wait one event loop to give a chance for params to be set
|
||||||
// Save the file to disk
|
// Save the file to disk
|
||||||
this._params.id &&
|
this._currentFilePath &&
|
||||||
writeTextFile(this._params.id, this.code).catch((err) => {
|
writeTextFile(this._currentFilePath, this.code).catch((err) => {
|
||||||
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
||||||
console.error('error saving file', err)
|
console.error('error saving file', err)
|
||||||
toast.error('Error saving file, please check file permissions')
|
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 { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('test executor', () => {
|
describe('test executor', () => {
|
||||||
it('test assigning two variables, the second summing with the first', async () => {
|
it('test assigning two variables, the second summing with the first', async () => {
|
||||||
@ -398,7 +400,7 @@ async function exe(
|
|||||||
code: string,
|
code: string,
|
||||||
programMemory: ProgramMemory = { root: {}, return: null }
|
programMemory: ProgramMemory = { root: {}, return: null }
|
||||||
) {
|
) {
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
|
|
||||||
const result = await enginelessExecutor(ast, programMemory)
|
const result = await enginelessExecutor(ast, programMemory)
|
||||||
return result
|
return result
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||||
import { Identifier, parse, initPromise, Parameter } from './wasm'
|
import { Identifier, parse, initPromise, Parameter } from './wasm'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('testing getNodePathFromSourceRange', () => {
|
describe('testing getNodePathFromSourceRange', () => {
|
||||||
it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', async () => {
|
it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', () => {
|
||||||
const code = `
|
const code = `
|
||||||
const myVar = 5
|
const myVar = 5
|
||||||
const sk3 = startSketchAt([0, 0])
|
const sk3 = startSketchAt([0, 0])
|
||||||
@ -19,14 +21,14 @@ const sk3 = startSketchAt([0, 0])
|
|||||||
lineToSubstringIndex + subStr.length,
|
lineToSubstringIndex + subStr.length,
|
||||||
]
|
]
|
||||||
|
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const { node } = getNodeFromPath<any>(ast, nodePath)
|
const { node } = getNodeFromPath<any>(ast, nodePath)
|
||||||
|
|
||||||
expect([node.start, node.end]).toEqual(sourceRange)
|
expect([node.start, node.end]).toEqual(sourceRange)
|
||||||
expect(node.type).toBe('CallExpression')
|
expect(node.type).toBe('CallExpression')
|
||||||
})
|
})
|
||||||
it('gets path right for function definition params', async () => {
|
it('gets path right for function definition params', () => {
|
||||||
const code = `fn cube = (pos, scale) => {
|
const code = `fn cube = (pos, scale) => {
|
||||||
const sg = startSketchAt(pos)
|
const sg = startSketchAt(pos)
|
||||||
|> line([0, scale], %)
|
|> line([0, scale], %)
|
||||||
@ -44,7 +46,7 @@ const b1 = cube([0,0], 10)`
|
|||||||
subStrIndex + 'pos'.length,
|
subStrIndex + 'pos'.length,
|
||||||
]
|
]
|
||||||
|
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const node = getNodeFromPath<Parameter>(ast, nodePath).node
|
const node = getNodeFromPath<Parameter>(ast, nodePath).node
|
||||||
|
|
||||||
@ -60,7 +62,7 @@ const b1 = cube([0,0], 10)`
|
|||||||
expect(node.type).toBe('Parameter')
|
expect(node.type).toBe('Parameter')
|
||||||
expect(node.identifier.name).toBe('pos')
|
expect(node.identifier.name).toBe('pos')
|
||||||
})
|
})
|
||||||
it('gets path right for deep within function definition body', async () => {
|
it('gets path right for deep within function definition body', () => {
|
||||||
const code = `fn cube = (pos, scale) => {
|
const code = `fn cube = (pos, scale) => {
|
||||||
const sg = startSketchAt(pos)
|
const sg = startSketchAt(pos)
|
||||||
|> line([0, scale], %)
|
|> line([0, scale], %)
|
||||||
@ -78,7 +80,7 @@ const b1 = cube([0,0], 10)`
|
|||||||
subStrIndex + 'scale'.length,
|
subStrIndex + 'scale'.length,
|
||||||
]
|
]
|
||||||
|
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const node = getNodeFromPath<Identifier>(ast, nodePath).node
|
const node = getNodeFromPath<Identifier>(ast, nodePath).node
|
||||||
expect(nodePath).toEqual([
|
expect(nodePath).toEqual([
|
||||||
|
@ -17,7 +17,9 @@ import {
|
|||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { getNodePathFromSourceRange } from './queryAst'
|
import { getNodePathFromSourceRange } from './queryAst'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('Testing createLiteral', () => {
|
describe('Testing createLiteral', () => {
|
||||||
it('should create a literal', () => {
|
it('should create a literal', () => {
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
import {
|
|
||||||
ParserWorkerResponse,
|
|
||||||
WasmWorker,
|
|
||||||
WasmWorkerEventType,
|
|
||||||
ParserWorkerCallResponse,
|
|
||||||
} from 'lang/workers/types'
|
|
||||||
import Worker from 'lang/workers/parser?worker'
|
|
||||||
import { Program, wasmUrl } from 'lang/wasm'
|
|
||||||
import { KCLError } from 'lang/errors'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
|
|
||||||
export default class Parser {
|
|
||||||
worker: any = new Worker({ name: 'parse' })
|
|
||||||
intitalized: boolean = false
|
|
||||||
mappings: Map<string, Program | KCLError> = new Map()
|
|
||||||
|
|
||||||
async parse(code: string): Promise<Program> {
|
|
||||||
this.ensureWorker()
|
|
||||||
const uuid = uuidv4()
|
|
||||||
this.worker.postMessage({
|
|
||||||
worker: WasmWorker.Parser,
|
|
||||||
eventType: WasmWorkerEventType.Call,
|
|
||||||
eventData: {
|
|
||||||
uuid,
|
|
||||||
code,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
let result = await this.waitForResult(uuid)
|
|
||||||
if (result instanceof KCLError) {
|
|
||||||
throw result
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForResult(uuid: string): Promise<Program | KCLError> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const result = this.mappings.get(uuid)
|
|
||||||
if (result) {
|
|
||||||
this.mappings.delete(uuid)
|
|
||||||
resolve(result)
|
|
||||||
} else {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve(this.waitForResult(uuid))
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureWorker() {
|
|
||||||
if (!this.intitalized) {
|
|
||||||
this.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the worker.
|
|
||||||
start() {
|
|
||||||
if (this.intitalized) {
|
|
||||||
console.log('Worker already initialized')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.worker.postMessage({
|
|
||||||
worker: WasmWorker.Parser,
|
|
||||||
eventType: WasmWorkerEventType.Init,
|
|
||||||
eventData: {
|
|
||||||
wasmUrl: wasmUrl(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
this.worker.onmessage = function (r: ParserWorkerResponse) {
|
|
||||||
switch (r.eventType) {
|
|
||||||
case WasmWorkerEventType.Init:
|
|
||||||
this.intitalized = true
|
|
||||||
break
|
|
||||||
case WasmWorkerEventType.Call:
|
|
||||||
const c = r.response as ParserWorkerCallResponse
|
|
||||||
this.mappings.set(c.uuid, c.response)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,9 @@ import {
|
|||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('findAllPreviousVariables', () => {
|
describe('findAllPreviousVariables', () => {
|
||||||
it('should find all previous variables', async () => {
|
it('should find all previous variables', async () => {
|
||||||
|
@ -1,56 +1,58 @@
|
|||||||
import { parse, Program, recast, initPromise } from './wasm'
|
import { parse, Program, recast, initPromise } from './wasm'
|
||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('recast', () => {
|
describe('recast', () => {
|
||||||
it('recasts a simple program', async () => {
|
it('recasts a simple program', () => {
|
||||||
const code = '1 + 2'
|
const code = '1 + 2'
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('variable declaration', async () => {
|
it('variable declaration', () => {
|
||||||
const code = 'const myVar = 5'
|
const code = 'const myVar = 5'
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it("variable declaration that's binary with string", async () => {
|
it("variable declaration that's binary with string", () => {
|
||||||
const code = "const myVar = 5 + 'yo'"
|
const code = "const myVar = 5 + 'yo'"
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
const codeWithOtherQuotes = 'const myVar = 5 + "yo"'
|
const codeWithOtherQuotes = 'const myVar = 5 + "yo"'
|
||||||
const { ast: ast2 } = await code2ast(codeWithOtherQuotes)
|
const { ast: ast2 } = code2ast(codeWithOtherQuotes)
|
||||||
expect(recast(ast2).trim()).toBe(codeWithOtherQuotes)
|
expect(recast(ast2).trim()).toBe(codeWithOtherQuotes)
|
||||||
})
|
})
|
||||||
it('test assigning two variables, the second summing with the first', async () => {
|
it('test assigning two variables, the second summing with the first', () => {
|
||||||
const code = `const myVar = 5
|
const code = `const myVar = 5
|
||||||
const newVar = myVar + 1
|
const newVar = myVar + 1
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('test assigning a var by cont concatenating two strings string', async () => {
|
it('test assigning a var by cont concatenating two strings string', () => {
|
||||||
const code = fs.readFileSync(
|
const code = fs.readFileSync(
|
||||||
'./src/lang/testExamples/variableDeclaration.cado',
|
'./src/lang/testExamples/variableDeclaration.cado',
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('test with function call', async () => {
|
it('test with function call', () => {
|
||||||
const code = `const myVar = "hello"
|
const code = `const myVar = "hello"
|
||||||
log(5, myVar)
|
log(5, myVar)
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('function declaration with call', async () => {
|
it('function declaration with call', () => {
|
||||||
const code = [
|
const code = [
|
||||||
'fn funcN = (a, b) => {',
|
'fn funcN = (a, b) => {',
|
||||||
' return a + b',
|
' return a + b',
|
||||||
@ -58,22 +60,22 @@ log(5, myVar)
|
|||||||
'const theVar = 60',
|
'const theVar = 60',
|
||||||
'const magicNum = funcN(9, theVar)',
|
'const magicNum = funcN(9, theVar)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast sketch declaration', async () => {
|
it('recast sketch declaration', () => {
|
||||||
let code = `const mySketch = startSketchAt([0, 0])
|
let code = `const mySketch = startSketchAt([0, 0])
|
||||||
|> lineTo([0, 1], %, "myPath")
|
|> lineTo([0, 1], %, "myPath")
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> lineTo([1, 0], %, "rightPath")
|
|> lineTo([1, 0], %, "rightPath")
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('sketch piped into callExpression', async () => {
|
it('sketch piped into callExpression', () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const mySk1 = startSketchAt([0, 0])',
|
'const mySk1 = startSketchAt([0, 0])',
|
||||||
' |> lineTo([1, 1], %)',
|
' |> lineTo([1, 1], %)',
|
||||||
@ -81,11 +83,11 @@ log(5, myVar)
|
|||||||
' |> lineTo([1, 1], %)',
|
' |> lineTo([1, 1], %)',
|
||||||
' |> rx(90, %)',
|
' |> rx(90, %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast BinaryExpression piped into CallExpression', async () => {
|
it('recast BinaryExpression piped into CallExpression', () => {
|
||||||
const code = [
|
const code = [
|
||||||
'fn myFn = (a) => {',
|
'fn myFn = (a) => {',
|
||||||
' return a + 1',
|
' return a + 1',
|
||||||
@ -93,49 +95,49 @@ log(5, myVar)
|
|||||||
'const myVar = 5 + 1',
|
'const myVar = 5 + 1',
|
||||||
' |> myFn(%)',
|
' |> myFn(%)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast nested binary expression', async () => {
|
it('recast nested binary expression', () => {
|
||||||
const code = ['const myVar = 1 + 2 * 5'].join('\n')
|
const code = ['const myVar = 1 + 2 * 5'].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast nested binary expression with parans', async () => {
|
it('recast nested binary expression with parans', () => {
|
||||||
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
|
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('unnecessary paran wrap will be remove', async () => {
|
it('unnecessary paran wrap will be remove', () => {
|
||||||
const code = ['const myVar = 1 + (2 * 5)'].join('\n')
|
const code = ['const myVar = 1 + (2 * 5)'].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.replace('(', '').replace(')', ''))
|
expect(recasted.trim()).toBe(code.replace('(', '').replace(')', ''))
|
||||||
})
|
})
|
||||||
it('complex nested binary expression', async () => {
|
it('complex nested binary expression', () => {
|
||||||
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
|
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('multiplied paren expressions', async () => {
|
it('multiplied paren expressions', () => {
|
||||||
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
|
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast array declaration', async () => {
|
it('recast array declaration', () => {
|
||||||
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
|
||||||
'\n'
|
'\n'
|
||||||
)
|
)
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast long array declaration', async () => {
|
it('recast long array declaration', () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const three = 3',
|
'const three = 3',
|
||||||
'const yo = [',
|
'const yo = [',
|
||||||
@ -146,11 +148,11 @@ log(5, myVar)
|
|||||||
" 'hey oooooo really long long long'",
|
" 'hey oooooo really long long long'",
|
||||||
']',
|
']',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast long object execution', async () => {
|
it('recast long object execution', () => {
|
||||||
const code = `const three = 3
|
const code = `const three = 3
|
||||||
const yo = {
|
const yo = {
|
||||||
aStr: 'str',
|
aStr: 'str',
|
||||||
@ -159,43 +161,43 @@ const yo = {
|
|||||||
binExp: 4 + 5
|
binExp: 4 + 5
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast short object execution', async () => {
|
it('recast short object execution', () => {
|
||||||
const code = `const yo = { key: 'val' }
|
const code = `const yo = { key: 'val' }
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast object execution with member expression', async () => {
|
it('recast object execution with member expression', () => {
|
||||||
const code = `const yo = { a: { b: { c: '123' } } }
|
const code = `const yo = { a: { b: { c: '123' } } }
|
||||||
const key = 'c'
|
const key = 'c'
|
||||||
const myVar = yo.a['b'][key]
|
const myVar = yo.a['b'][key]
|
||||||
const key2 = 'b'
|
const key2 = 'b'
|
||||||
const myVar2 = yo['a'][key2].c
|
const myVar2 = yo['a'][key2].c
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('testing recasting with comments and whitespace', () => {
|
describe('testing recasting with comments and whitespace', () => {
|
||||||
it('code with comments', async () => {
|
it('code with comments', () => {
|
||||||
const code = `const yo = { a: { b: { c: '123' } } }
|
const code = `const yo = { a: { b: { c: '123' } } }
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const key = 'c'
|
const key = 'c'
|
||||||
`
|
`
|
||||||
|
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
|
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('code with comment and extra lines', async () => {
|
it('code with comment and extra lines', () => {
|
||||||
const code = `const yo = 'c'
|
const code = `const yo = 'c'
|
||||||
|
|
||||||
/* this is
|
/* this is
|
||||||
@ -203,23 +205,23 @@ a
|
|||||||
comment */
|
comment */
|
||||||
const yo = 'bing'
|
const yo = 'bing'
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('comments at the start and end', async () => {
|
it('comments at the start and end', () => {
|
||||||
const code = `// this is a comment
|
const code = `// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
const key = 'c'
|
const key = 'c'
|
||||||
|
|
||||||
// this is also a comment
|
// this is also a comment
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('comments in a fn block', async () => {
|
it('comments in a fn block', () => {
|
||||||
const code = `fn myFn = async () => {
|
const code = `fn myFn = () => {
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } }
|
||||||
|
|
||||||
@ -229,11 +231,11 @@ const key = 'c'
|
|||||||
// this is also a comment
|
// this is also a comment
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('comments in a pipe expression', async () => {
|
it('comments in a pipe expression', () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const mySk1 = startSketchAt([0, 0])',
|
'const mySk1 = startSketchAt([0, 0])',
|
||||||
' |> lineTo([1, 1], %)',
|
' |> lineTo([1, 1], %)',
|
||||||
@ -242,11 +244,11 @@ const key = 'c'
|
|||||||
' // a comment',
|
' // a comment',
|
||||||
' |> rx(90, %)',
|
' |> rx(90, %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('comments sprinkled in all over the place', async () => {
|
it('comments sprinkled in all over the place', () => {
|
||||||
const code = `
|
const code = `
|
||||||
/* comment at start */
|
/* comment at start */
|
||||||
|
|
||||||
@ -264,11 +266,11 @@ const mySk1 = startSketchAt([0, 0])
|
|||||||
|
|
||||||
|
|
||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
/*
|
/*
|
||||||
one more for good measure
|
one more for good measure
|
||||||
*/
|
*/
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(`/* comment at start */
|
expect(recasted).toBe(`/* comment at start */
|
||||||
|
|
||||||
@ -283,43 +285,43 @@ const mySk1 = startSketchAt([0, 0])
|
|||||||
// and another with just white space between others below
|
// and another with just white space between others below
|
||||||
|> ry(45, %)
|
|> ry(45, %)
|
||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
/* one more for good measure */
|
/* one more for good measure */
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('testing call Expressions in BinaryExpressions and UnaryExpressions', () => {
|
describe('testing call Expressions in BinaryExpressions and UnaryExpressions', () => {
|
||||||
it('nested callExpression in binaryExpression', async () => {
|
it('nested callExpression in binaryExpression', () => {
|
||||||
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('nested callExpression in unaryExpression', async () => {
|
it('nested callExpression in unaryExpression', () => {
|
||||||
const code = 'const myVar = -min(100, legLen(5, 3))'
|
const code = 'const myVar = -min(100, legLen(5, 3))'
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('with unaryExpression in callExpression', async () => {
|
it('with unaryExpression in callExpression', () => {
|
||||||
const code = 'const myVar = min(5, -legLen(5, 4))'
|
const code = 'const myVar = min(5, -legLen(5, 4))'
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
it('with unaryExpression in sketch situation', async () => {
|
it('with unaryExpression in sketch situation', () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const part001 = startSketchAt([0, 0])',
|
'const part001 = startSketchAt([0, 0])',
|
||||||
' |> line([-2.21, -legLen(5, min(3, 999))], %)',
|
' |> line([-2.21, -legLen(5, min(3, 999))], %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code)
|
expect(recasted.trim()).toBe(code)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('it recasts wrapped object expressions in pipe bodies with correct indentation', () => {
|
describe('it recasts wrapped object expressions in pipe bodies with correct indentation', () => {
|
||||||
it('with a single line', async () => {
|
it('with a single line', () => {
|
||||||
const code = `const part001 = startSketchAt([-0.01, -0.08])
|
const code = `const part001 = startSketchAt([-0.01, -0.08])
|
||||||
|> line([0.62, 4.15], %, 'seg01')
|
|> line([0.62, 4.15], %, 'seg01')
|
||||||
|> line([2.77, -1.24], %)
|
|> line([2.77, -1.24], %)
|
||||||
@ -330,35 +332,35 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
|
|||||||
}, %)
|
}, %)
|
||||||
|> line([-0.42, -1.72], %)
|
|> line([-0.42, -1.72], %)
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('recasts wrapped object expressions NOT in pipe body correctly', async () => {
|
it('recasts wrapped object expressions NOT in pipe body correctly', () => {
|
||||||
const code = `angledLineThatIntersects({
|
const code = `angledLineThatIntersects({
|
||||||
angle: 201,
|
angle: 201,
|
||||||
offset: -1.35,
|
offset: -1.35,
|
||||||
intersectTag: 'seg01'
|
intersectTag: 'seg01'
|
||||||
}, %)
|
}, %)
|
||||||
`
|
`
|
||||||
const { ast } = await code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('it recasts binary expression using brackets where needed', () => {
|
describe('it recasts binary expression using brackets where needed', () => {
|
||||||
it('when there are two minus in a row', async () => {
|
it('when there are two minus in a row', () => {
|
||||||
const code = `const part001 = 1 - (def - abc)
|
const code = `const part001 = 1 - (def - abc)
|
||||||
`
|
`
|
||||||
const recasted = recast((await code2ast(code)).ast)
|
const recasted = recast(code2ast(code).ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
async function code2ast(code: string): Promise<{ ast: Program }> {
|
function code2ast(code: string): { ast: Program } {
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
return { ast }
|
return { ast }
|
||||||
}
|
}
|
||||||
|
@ -1171,7 +1171,10 @@ export class EngineCommandManager {
|
|||||||
type: 'receive-reliable',
|
type: 'receive-reliable',
|
||||||
data: message,
|
data: message,
|
||||||
id,
|
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(
|
Object.values(this.subscriptions[modelingResponse.type] || {}).forEach(
|
||||||
(callback) => callback(modelingResponse)
|
(callback) => callback(modelingResponse)
|
||||||
|
@ -25,7 +25,9 @@ const eachQuad: [number, [number, number]][] = [
|
|||||||
[675, [1, -1]],
|
[675, [1, -1]],
|
||||||
]
|
]
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('testing getYComponent', () => {
|
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)', () => {
|
it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
|
||||||
@ -100,11 +102,11 @@ describe('testing changeSketchArguments', () => {
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
`
|
`
|
||||||
const code = genCode(lineToChange)
|
const code = genCode(lineToChange)
|
||||||
const expectedCode = genCode(lineAfterChange)
|
const expectedCode = genCode(lineAfterChange)
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(lineToChange)
|
const sourceStart = code.indexOf(lineToChange)
|
||||||
const { modifiedAst } = changeSketchArguments(
|
const { modifiedAst } = changeSketchArguments(
|
||||||
@ -128,7 +130,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)`
|
|> lineTo([0.46, -5.82], %)`
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(lineToChange)
|
const sourceStart = code.indexOf(lineToChange)
|
||||||
expect(sourceStart).toBe(95)
|
expect(sourceStart).toBe(95)
|
||||||
@ -190,7 +192,7 @@ describe('testing addTagForSketchOnFace', () => {
|
|||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
`
|
`
|
||||||
const code = genCode(originalLine)
|
const code = genCode(originalLine)
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(originalLine)
|
const sourceStart = code.indexOf(originalLine)
|
||||||
const sourceRange: [number, number] = [
|
const sourceRange: [number, number] = [
|
||||||
|
@ -8,7 +8,9 @@ import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
|||||||
import { Selection } from 'lib/selections'
|
import { Selection } from 'lib/selections'
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
// testing helper function
|
// testing helper function
|
||||||
async function testingSwapSketchFnCall({
|
async function testingSwapSketchFnCall({
|
||||||
@ -28,7 +30,7 @@ async function testingSwapSketchFnCall({
|
|||||||
type: 'default',
|
type: 'default',
|
||||||
range: [startIndex, startIndex + callToSwap.length],
|
range: [startIndex, startIndex + callToSwap.length],
|
||||||
}
|
}
|
||||||
const ast = await parse(inputCode)
|
const ast = parse(inputCode)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const selections = {
|
const selections = {
|
||||||
codeBasedSelections: [range],
|
codeBasedSelections: [range],
|
||||||
@ -351,7 +353,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> line([2.14, 1.35], %) // normal-segment
|
|> line([2.14, 1.35], %) // normal-segment
|
||||||
|> xLine(3.54, %)`
|
|> xLine(3.54, %)`
|
||||||
it('normal case works', async () => {
|
it('normal case works', async () => {
|
||||||
const programMemory = await enginelessExecutor(await parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
const index = code.indexOf('// normal-segment') - 7
|
const index = code.indexOf('// normal-segment') - 7
|
||||||
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
||||||
programMemory.root['part001'] as SketchGroup,
|
programMemory.root['part001'] as SketchGroup,
|
||||||
@ -365,7 +367,7 @@ const part001 = startSketchOn('XY')
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('verify it works when the segment is in the `start` property', async () => {
|
it('verify it works when the segment is in the `start` property', async () => {
|
||||||
const programMemory = await enginelessExecutor(await parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
const index = code.indexOf('// segment-in-start') - 7
|
const index = code.indexOf('// segment-in-start') - 7
|
||||||
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
||||||
programMemory.root['part001'] as SketchGroup,
|
programMemory.root['part001'] as SketchGroup,
|
||||||
|
@ -11,57 +11,59 @@ import { ToolTip } from '../../useStore'
|
|||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('testing getConstraintType', () => {
|
describe('testing getConstraintType', () => {
|
||||||
const helper = getConstraintTypeFromSourceHelper
|
const helper = getConstraintTypeFromSourceHelper
|
||||||
it('testing line', async () => {
|
it('testing line', () => {
|
||||||
expect(await helper(`line([5, myVar], %)`)).toBe('yRelative')
|
expect(helper(`line([5, myVar], %)`)).toBe('yRelative')
|
||||||
expect(await helper(`line([myVar, 5], %)`)).toBe('xRelative')
|
expect(helper(`line([myVar, 5], %)`)).toBe('xRelative')
|
||||||
})
|
})
|
||||||
it('testing lineTo', async () => {
|
it('testing lineTo', () => {
|
||||||
expect(await helper(`lineTo([5, myVar], %)`)).toBe('yAbsolute')
|
expect(helper(`lineTo([5, myVar], %)`)).toBe('yAbsolute')
|
||||||
expect(await helper(`lineTo([myVar, 5], %)`)).toBe('xAbsolute')
|
expect(helper(`lineTo([myVar, 5], %)`)).toBe('xAbsolute')
|
||||||
})
|
})
|
||||||
it('testing angledLine', async () => {
|
it('testing angledLine', () => {
|
||||||
expect(await helper(`angledLine([5, myVar], %)`)).toBe('length')
|
expect(helper(`angledLine([5, myVar], %)`)).toBe('length')
|
||||||
expect(await helper(`angledLine([myVar, 5], %)`)).toBe('angle')
|
expect(helper(`angledLine([myVar, 5], %)`)).toBe('angle')
|
||||||
})
|
})
|
||||||
it('testing angledLineOfXLength', async () => {
|
it('testing angledLineOfXLength', () => {
|
||||||
expect(await helper(`angledLineOfXLength([5, myVar], %)`)).toBe('xRelative')
|
expect(helper(`angledLineOfXLength([5, myVar], %)`)).toBe('xRelative')
|
||||||
expect(await helper(`angledLineOfXLength([myVar, 5], %)`)).toBe('angle')
|
expect(helper(`angledLineOfXLength([myVar, 5], %)`)).toBe('angle')
|
||||||
})
|
})
|
||||||
it('testing angledLineToX', async () => {
|
it('testing angledLineToX', () => {
|
||||||
expect(await helper(`angledLineToX([5, myVar], %)`)).toBe('xAbsolute')
|
expect(helper(`angledLineToX([5, myVar], %)`)).toBe('xAbsolute')
|
||||||
expect(await helper(`angledLineToX([myVar, 5], %)`)).toBe('angle')
|
expect(helper(`angledLineToX([myVar, 5], %)`)).toBe('angle')
|
||||||
})
|
})
|
||||||
it('testing angledLineOfYLength', async () => {
|
it('testing angledLineOfYLength', () => {
|
||||||
expect(await helper(`angledLineOfYLength([5, myVar], %)`)).toBe('yRelative')
|
expect(helper(`angledLineOfYLength([5, myVar], %)`)).toBe('yRelative')
|
||||||
expect(await helper(`angledLineOfYLength([myVar, 5], %)`)).toBe('angle')
|
expect(helper(`angledLineOfYLength([myVar, 5], %)`)).toBe('angle')
|
||||||
})
|
})
|
||||||
it('testing angledLineToY', async () => {
|
it('testing angledLineToY', () => {
|
||||||
expect(await helper(`angledLineToY([5, myVar], %)`)).toBe('yAbsolute')
|
expect(helper(`angledLineToY([5, myVar], %)`)).toBe('yAbsolute')
|
||||||
expect(await helper(`angledLineToY([myVar, 5], %)`)).toBe('angle')
|
expect(helper(`angledLineToY([myVar, 5], %)`)).toBe('angle')
|
||||||
})
|
})
|
||||||
const helper2 = getConstraintTypeFromSourceHelper2
|
const helper2 = getConstraintTypeFromSourceHelper2
|
||||||
it('testing xLine', async () => {
|
it('testing xLine', () => {
|
||||||
expect(await helper2(`xLine(5, %)`)).toBe('yRelative')
|
expect(helper2(`xLine(5, %)`)).toBe('yRelative')
|
||||||
})
|
})
|
||||||
it('testing yLine', async () => {
|
it('testing yLine', () => {
|
||||||
expect(await helper2(`yLine(5, %)`)).toBe('xRelative')
|
expect(helper2(`yLine(5, %)`)).toBe('xRelative')
|
||||||
})
|
})
|
||||||
it('testing xLineTo', async () => {
|
it('testing xLineTo', () => {
|
||||||
expect(await helper2(`xLineTo(5, %)`)).toBe('yAbsolute')
|
expect(helper2(`xLineTo(5, %)`)).toBe('yAbsolute')
|
||||||
})
|
})
|
||||||
it('testing yLineTo', async () => {
|
it('testing yLineTo', () => {
|
||||||
expect(await helper2(`yLineTo(5, %)`)).toBe('xAbsolute')
|
expect(helper2(`yLineTo(5, %)`)).toBe('xAbsolute')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getConstraintTypeFromSourceHelper(
|
function getConstraintTypeFromSourceHelper(
|
||||||
code: string
|
code: string
|
||||||
): Promise<ReturnType<typeof getConstraintType>> {
|
): ReturnType<typeof getConstraintType> {
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const args = (ast.body[0] as any).expression.arguments[0].elements as [
|
const args = (ast.body[0] as any).expression.arguments[0].elements as [
|
||||||
Value,
|
Value,
|
||||||
Value
|
Value
|
||||||
@ -69,10 +71,10 @@ async function getConstraintTypeFromSourceHelper(
|
|||||||
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||||
return getConstraintType(args, fnName)
|
return getConstraintType(args, fnName)
|
||||||
}
|
}
|
||||||
async function getConstraintTypeFromSourceHelper2(
|
function getConstraintTypeFromSourceHelper2(
|
||||||
code: string
|
code: string
|
||||||
): Promise<ReturnType<typeof getConstraintType>> {
|
): ReturnType<typeof getConstraintType> {
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const arg = (ast.body[0] as any).expression.arguments[0] as Value
|
const arg = (ast.body[0] as any).expression.arguments[0] as Value
|
||||||
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||||
return getConstraintType(arg, fnName)
|
return getConstraintType(arg, fnName)
|
||||||
@ -197,7 +199,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
|
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
|
||||||
`
|
`
|
||||||
it('should transform the ast', async () => {
|
it('should transform the ast', async () => {
|
||||||
const ast = await parse(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((ln) => ln.includes('//'))
|
.filter((ln) => ln.includes('//'))
|
||||||
@ -284,7 +286,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|
||||||
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||||
`
|
`
|
||||||
const ast = await parse(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((ln) => ln.includes('// select for horizontal constraint'))
|
.filter((ln) => ln.includes('// select for horizontal constraint'))
|
||||||
@ -342,7 +344,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
||||||
|> yLineTo(myVar, %) // select for vertical constraint 10
|
|> yLineTo(myVar, %) // select for vertical constraint 10
|
||||||
`
|
`
|
||||||
const ast = await parse(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((ln) => ln.includes('// select for vertical constraint'))
|
.filter((ln) => ln.includes('// select for vertical constraint'))
|
||||||
@ -433,7 +435,7 @@ async function helperThing(
|
|||||||
linesOfInterest: string[],
|
linesOfInterest: string[],
|
||||||
constraint: ConstraintType
|
constraint: ConstraintType
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const ast = await parse(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((ln) =>
|
.filter((ln) =>
|
||||||
@ -465,7 +467,7 @@ async function helperThing(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('testing getConstraintLevelFromSourceRange', () => {
|
describe('testing getConstraintLevelFromSourceRange', () => {
|
||||||
it('should divide up lines into free, partial and fully contrained', async () => {
|
it('should divide up lines into free, partial and fully contrained', () => {
|
||||||
const code = `const baseLength = 3
|
const code = `const baseLength = 3
|
||||||
const baseThick = 1
|
const baseThick = 1
|
||||||
const armThick = 0.5
|
const armThick = 0.5
|
||||||
@ -495,7 +497,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> line([-1.49, 1.06], %) // free
|
|> line([-1.49, 1.06], %) // free
|
||||||
|> xLine(-3.43 + 0, %) // full
|
|> xLine(-3.43 + 0, %) // full
|
||||||
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
|
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
|
||||||
const ast = await parse(code)
|
const ast = parse(code)
|
||||||
const constraintLevels: ReturnType<
|
const constraintLevels: ReturnType<
|
||||||
typeof getConstraintLevelFromSourceRange
|
typeof getConstraintLevelFromSourceRange
|
||||||
>['level'][] = ['full', 'partial', 'free']
|
>['level'][] = ['full', 'partial', 'free']
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { parse, initPromise } from '../wasm'
|
import { parse, initPromise } from '../wasm'
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('testing angledLineThatIntersects', () => {
|
describe('testing angledLineThatIntersects', () => {
|
||||||
it('angledLineThatIntersects should intersect with another line', async () => {
|
it('angledLineThatIntersects should intersect with another line', async () => {
|
||||||
@ -15,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
|
|||||||
offset: ${offset},
|
offset: ${offset},
|
||||||
}, %, "yo2")
|
}, %, "yo2")
|
||||||
const intersect = segEndX('yo2', part001)`
|
const intersect = segEndX('yo2', part001)`
|
||||||
const { root } = await enginelessExecutor(await parse(code('-1')))
|
const { root } = await enginelessExecutor(parse(code('-1')))
|
||||||
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
|
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
|
||||||
const { root: noOffset } = await enginelessExecutor(await parse(code('0')))
|
const { root: noOffset } = await enginelessExecutor(parse(code('0')))
|
||||||
expect(noOffset.intersect.value).toBeCloseTo(1)
|
expect(noOffset.intersect.value).toBeCloseTo(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { lexer, initPromise } from './wasm'
|
import { lexer, initPromise } from './wasm'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(async () => {
|
||||||
|
await initPromise
|
||||||
|
})
|
||||||
|
|
||||||
describe('testing lexer', () => {
|
describe('testing lexer', () => {
|
||||||
it('async lexer works too', async () => {
|
it('async lexer works too', async () => {
|
||||||
|
@ -25,8 +25,7 @@ import { AppInfo } from 'wasm-lib/kcl/bindings/AppInfo'
|
|||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import openWindow from 'lib/openWindow'
|
import openWindow from 'lib/openWindow'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
import { rangeTypeFix } from './workers/types'
|
import { TEST } from 'env'
|
||||||
import { parser } from 'lib/singletons'
|
|
||||||
|
|
||||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||||
@ -97,26 +96,23 @@ export const wasmUrl = () => {
|
|||||||
|
|
||||||
// Initialise the wasm module.
|
// Initialise the wasm module.
|
||||||
const initialise = async () => {
|
const initialise = async () => {
|
||||||
const fullUrl = wasmUrl()
|
try {
|
||||||
const input = await fetch(fullUrl)
|
const fullUrl = wasmUrl()
|
||||||
const buffer = await input.arrayBuffer()
|
const input = await fetch(fullUrl)
|
||||||
return init(buffer)
|
const buffer = await input.arrayBuffer()
|
||||||
|
return await init(buffer)
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error initialising WASM', e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initPromise = initialise()
|
export const initPromise = initialise()
|
||||||
|
|
||||||
export type PathToNode = [string | number, string][]
|
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
|
||||||
|
ranges.map(([start, end]) => [start, end])
|
||||||
|
|
||||||
interface Memory {
|
export const parse = (code: string): Program => {
|
||||||
[key: string]: MemoryItem
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProgramMemory {
|
|
||||||
root: Memory
|
|
||||||
return: ProgramReturn | null
|
|
||||||
}
|
|
||||||
|
|
||||||
/*export const parse = (code: string): Program => {
|
|
||||||
try {
|
try {
|
||||||
const program: Program = parse_wasm(code)
|
const program: Program = parse_wasm(code)
|
||||||
return program
|
return program
|
||||||
@ -131,10 +127,17 @@ export interface ProgramMemory {
|
|||||||
console.log(kclError)
|
console.log(kclError)
|
||||||
throw kclError
|
throw kclError
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
export const parse = async (code: string): Promise<Program> => {
|
export type PathToNode = [string | number, string][]
|
||||||
return parser.parse(code)
|
|
||||||
|
interface Memory {
|
||||||
|
[key: string]: MemoryItem
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgramMemory {
|
||||||
|
root: Memory
|
||||||
|
return: ProgramReturn | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executor = async (
|
export const executor = async (
|
||||||
@ -156,10 +159,6 @@ export const executor = async (
|
|||||||
return _programMemory
|
return _programMemory
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSettingsState = import('components/SettingsAuthProvider').then(
|
|
||||||
(module) => module.getSettingsState
|
|
||||||
)
|
|
||||||
|
|
||||||
export const _executor = async (
|
export const _executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {}, return: null },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
@ -167,8 +166,14 @@ export const _executor = async (
|
|||||||
isMock: boolean
|
isMock: boolean
|
||||||
): Promise<ProgramMemory> => {
|
): Promise<ProgramMemory> => {
|
||||||
try {
|
try {
|
||||||
const baseUnit =
|
let baseUnit = 'mm'
|
||||||
(await getSettingsState)()?.modeling.defaultUnit.current || '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(
|
const memory: ProgramMemory = await execute_wasm(
|
||||||
JSON.stringify(node),
|
JSON.stringify(node),
|
||||||
JSON.stringify(programMemory),
|
JSON.stringify(programMemory),
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
import init, { parse_wasm } from 'wasm-lib/pkg/wasm_lib'
|
|
||||||
import { Program } from 'lang/wasm'
|
|
||||||
import { KclError as RustKclError } from 'wasm-lib/kcl/bindings/KclError'
|
|
||||||
import { KCLError } from 'lang/errors'
|
|
||||||
import {
|
|
||||||
WasmWorkerEventType,
|
|
||||||
WasmWorkerEvent,
|
|
||||||
WasmWorkerOptions,
|
|
||||||
ParserWorkerCall,
|
|
||||||
rangeTypeFix,
|
|
||||||
} from 'lang/workers/types'
|
|
||||||
|
|
||||||
// Initialise the wasm module.
|
|
||||||
const initialise = async (wasmUrl: string) => {
|
|
||||||
const input = await fetch(wasmUrl)
|
|
||||||
const buffer = await input.arrayBuffer()
|
|
||||||
return init(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
onmessage = function (event) {
|
|
||||||
const { worker, eventType, eventData }: WasmWorkerEvent = event.data
|
|
||||||
|
|
||||||
switch (eventType) {
|
|
||||||
case WasmWorkerEventType.Init:
|
|
||||||
let { wasmUrl }: WasmWorkerOptions = eventData as WasmWorkerOptions
|
|
||||||
initialise(wasmUrl)
|
|
||||||
.then((instantiatedModule) => {
|
|
||||||
console.log('Worker: WASM module loaded', worker, instantiatedModule)
|
|
||||||
postMessage({
|
|
||||||
eventType: WasmWorkerEventType.Init,
|
|
||||||
response: { worker: worker, initialized: true },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Worker: Error loading wasm module', worker, error)
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case WasmWorkerEventType.Call:
|
|
||||||
const data = eventData as ParserWorkerCall
|
|
||||||
try {
|
|
||||||
const program: Program = parse_wasm(data.code)
|
|
||||||
postMessage({ uuid: data.uuid, response: program })
|
|
||||||
} catch (e: any) {
|
|
||||||
const parsed: RustKclError = JSON.parse(e.toString())
|
|
||||||
const kclError = new KCLError(
|
|
||||||
parsed.kind,
|
|
||||||
parsed.msg,
|
|
||||||
rangeTypeFix(parsed.sourceRanges)
|
|
||||||
)
|
|
||||||
|
|
||||||
postMessage({
|
|
||||||
eventType: WasmWorkerEventType.Call,
|
|
||||||
response: { uuid: data.uuid, response: kclError },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
console.error('Worker: Unknown message type', worker, eventType)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
import { KCLError } from 'lang/errors'
|
|
||||||
import { Program } from 'lang/wasm'
|
|
||||||
|
|
||||||
export enum WasmWorker {
|
|
||||||
Parser = 'parser',
|
|
||||||
}
|
|
||||||
export enum WasmWorkerEventType {
|
|
||||||
Init = 'init',
|
|
||||||
Call = 'call',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WasmWorkerOptions {
|
|
||||||
wasmUrl: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParserWorkerCall {
|
|
||||||
uuid: string
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParserWorkerInitResponse {
|
|
||||||
worker: WasmWorker
|
|
||||||
initialized: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParserWorkerCallResponse {
|
|
||||||
uuid: string
|
|
||||||
response: Program | KCLError
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParserWorkerResponse {
|
|
||||||
eventType: WasmWorkerEventType
|
|
||||||
response: ParserWorkerInitResponse | ParserWorkerCallResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WasmWorkerEvent {
|
|
||||||
eventType: WasmWorkerEventType
|
|
||||||
eventData: Uint8Array | WasmWorkerOptions | ParserWorkerCall
|
|
||||||
worker: WasmWorker
|
|
||||||
}
|
|
||||||
|
|
||||||
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
|
|
||||||
ranges.map(([start, end]) => [start, end])
|
|
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)
|
||||||
|
}
|