Update code mods for extrude so that new top-level constants are created (#2549)

* Make sketch and extrude produce separate top-level constants

* Fix most tests

* Add a breaking test for sketch on face AST mod

* Use `extrude` instead of `part`

* Implement @Irev-Dev's branch changes from https://github.com/KittyCAD/modeling-app/pull/2472

* Get extrude on face working

* Update incorrect sketch on face test

* Update unit tests

* Fix up E2E test changes

* fmt

* Fix a couple of goofed up test updates

* More specific names for paths to node sent to modelingMachine

* Bump down playwright workers for now

* Slightly more explicit type coercion

* Update snapshot tests

* Missed one other new flow test that wasn't updated to use "sketch001"

* Typo

* Damn missed one more sorry

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

* Re-run CI

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

* I think the multiple sketches test reverted from under me

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Frank Noirot
2024-06-04 13:57:01 -04:00
committed by GitHub
parent e46aca4992
commit c9800a58d0
17 changed files with 319 additions and 138 deletions

View File

@ -93,7 +93,7 @@ test('Basic sketch', async ({ page }) => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')` `const sketch001 = startSketchOn('XZ')`
) )
await u.closeDebugPanel() await u.closeDebugPanel()
@ -102,7 +102,7 @@ test('Basic sketch', async ({ page }) => {
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`) |> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -110,20 +110,20 @@ test('Basic sketch', async ({ page }) => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`) |> line([${commonPoints.num1}, 0], %)`)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`) |> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20) await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %) |> line([0, ${commonPoints.num1}], %)
@ -149,7 +149,7 @@ test('Basic sketch', async ({ page }) => {
await page.getByRole('button', { name: 'Equal Length' }).click() await page.getByRole('button', { name: 'Equal Length' }).click()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %, 'seg01') |> line([${commonPoints.num1}, 0], %, 'seg01')
|> line([0, ${commonPoints.num1}], %) |> line([0, ${commonPoints.num1}], %)
@ -331,7 +331,7 @@ test('if you click the format button it formats your code', async ({
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await page.click('.cm-content') await page.click('.cm-content')
await page.keyboard.type(`const part001 = startSketchOn('XY') await page.keyboard.type(`const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -341,7 +341,7 @@ test('if you click the format button it formats your code', async ({
await page.click('button:has-text("Format code")') await page.click('button:has-text("Format code")')
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY') .toHaveText(`const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -356,7 +356,7 @@ test('if you use the format keyboard binding it formats your code', async ({
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('XY') `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -392,7 +392,7 @@ test('if you use the format keyboard binding it formats your code', async ({
await page.keyboard.press('Alt+Shift+KeyF') await page.keyboard.press('Alt+Shift+KeyF')
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY') .toHaveText(`const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -605,7 +605,7 @@ test('executes on load', async ({ page }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('-XZ') `const sketch001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 4.98], %) |> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %) |> line([25.1, 0.41], %)
|> line([0.73, -14.93], %) |> line([0.73, -14.93], %)
@ -623,16 +623,16 @@ test('executes on load', async ({ page }) => {
}) })
await variablesTabButton.click() await variablesTabButton.click()
// can find part001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor) // can find sketch001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor)
// part001 only shows up in the variables summary if it's been executed // sketch001 only shows up in the variables summary if it's been executed
await page.waitForFunction(() => { await page.waitForFunction(() => {
const variablesElement = document.querySelector( const variablesElement = document.querySelector(
'.pretty-json-container' '.pretty-json-container'
) as HTMLDivElement ) as HTMLDivElement
return variablesElement.innerHTML.includes('part001') return variablesElement.innerHTML.includes('sketch001')
}) })
await expect( await expect(
page.locator('.pretty-json-container >> text=part001') page.locator('.pretty-json-container >> text=sketch001')
).toBeVisible() ).toBeVisible()
}) })
@ -698,7 +698,7 @@ const sketchOnPlaneAndBackSideTest = async (
}, },
} }
const code = `const part001 = startSketchOn('${plane}') const code = `const sketch001 = startSketchOn('${plane}')
|> startProfileAt([1.14, -1.54], %)` |> startProfileAt([1.14, -1.54], %)`
await u.openDebugPanel() await u.openDebugPanel()
@ -787,7 +787,7 @@ test('Auto complete works', async ({ page }) => {
// and arrowing down to an option // and arrowing down to an option
await page.click('.cm-content') await page.click('.cm-content')
await page.keyboard.type('const part001 = start') await page.keyboard.type('const sketch001 = start')
// expect there to be six auto complete options // expect there to be six auto complete options
await expect(page.locator('.cm-completionLabel')).toHaveCount(6) await expect(page.locator('.cm-completionLabel')).toHaveCount(6)
@ -827,7 +827,7 @@ test('Auto complete works', async ({ page }) => {
await expect(page.locator('.cm-completionLabel')).not.toBeVisible() await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %) |> startProfileAt([3.14, 12], %)
|> xLine(5, %) // lin`) |> xLine(5, %) // lin`)
}) })
@ -1182,28 +1182,28 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await u.closeDebugPanel() await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`) |> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`) |> line([${commonPoints.num1}, 0], %)`)
await page.waitForTimeout(100) 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 sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`) |> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20) await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %) |> line([0, ${commonPoints.num1}], %)
@ -1433,7 +1433,7 @@ test.describe('Command bar tests', () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const distance = sqrt(20) `const distance = sqrt(20)
const part001 = startSketchOn('XZ') const sketch001 = startSketchOn('XZ')
|> startProfileAt([-6.95, 10.98], %) |> startProfileAt([-6.95, 10.98], %)
|> line([25.1, 0.41], %) |> line([25.1, 0.41], %)
|> line([0.73, -20.93], %) |> line([0.73, -20.93], %)
@ -1503,13 +1503,16 @@ test.describe('Command bar tests', () => {
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const distance = sqrt(20) `const distance = sqrt(20)
const distance001 = ${KCL_DEFAULT_LENGTH} const distance001 = ${KCL_DEFAULT_LENGTH}
const part001 = startSketchOn('XZ') const sketch001 = startSketchOn('XZ')
|> startProfileAt([-6.95, 10.98], %) |> startProfileAt([-6.95, 10.98], %)
|> line([25.1, 0.41], %) |> line([25.1, 0.41], %)
|> line([0.73, -20.93], %) |> line([0.73, -20.93], %)
|> line([-23.44, 0.52], %) |> line([-23.44, 0.52], %)
|> close(%) |> close(%)
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines const extrude001 = extrude(distance001, sketch001)`.replace(
/(\r\n|\n|\r)/gm,
''
) // remove newlines
) )
}) })
}) })
@ -1538,7 +1541,7 @@ test('Can add multiple sketches', async ({ page }) => {
200 200
) )
let codeStr = "const part001 = startSketchOn('XY')" let codeStr = "const sketch001 = startSketchOn('XY')"
await page.mouse.click(center.x, viewportSize.height * 0.55) await page.mouse.click(center.x, viewportSize.height * 0.55)
await expectCodeToBe(codeStr) await expectCodeToBe(codeStr)
@ -1576,7 +1579,7 @@ test('Can add multiple sketches', async ({ page }) => {
// so selecting the plane again is a bit easier. // so selecting the plane again is a bit easier.
await page.mouse.click(center.x + 30, center.y) await page.mouse.click(center.x + 30, center.y)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
codeStr += "const part002 = startSketchOn('XY')" codeStr += "const sketch002 = startSketchOn('XY')"
await expectCodeToBe(codeStr) await expectCodeToBe(codeStr)
await u.closeDebugPanel() await u.closeDebugPanel()
@ -1763,14 +1766,14 @@ test("Various pipe expressions should and shouldn't allow edit and or extrude",
|> line([-27.65, -2.78], %) |> line([-27.65, -2.78], %)
|> close(%) |> close(%)
|> extrude(5, %) |> extrude(5, %)
const part002 = startSketchOn('XZ') const sketch002 = startSketchOn('XZ')
${extrudeAndEditAllowed} ${extrudeAndEditAllowed}
|> line([10.32, 6.47], %) |> line([10.32, 6.47], %)
|> line([9.71, -6.16], %) |> line([9.71, -6.16], %)
|> line([-3.08, -9.86], %) |> line([-3.08, -9.86], %)
|> line([-12.02, -1.54], %) |> line([-12.02, -1.54], %)
|> close(%) |> close(%)
const part003 = startSketchOn('XZ') const sketch003 = startSketchOn('XZ')
${editOnly} ${editOnly}
|> line([27.55, -1.65], %) |> line([27.55, -1.65], %)
|> line([4.95, -8], %) |> line([4.95, -8], %)
@ -1778,7 +1781,7 @@ const part003 = startSketchOn('XZ')
|> line([-15.79, 17.08], %) |> line([-15.79, 17.08], %)
fn yohey = (pos) => { fn yohey = (pos) => {
const part004 = startSketchOn('XZ') const sketch004 = startSketchOn('XZ')
${extrudeAndEditBlockedInFunction} ${extrudeAndEditBlockedInFunction}
|> line([27.55, -1.65], %) |> line([27.55, -1.65], %)
|> line([4.95, -10.53], %) |> line([4.95, -10.53], %)
@ -1833,15 +1836,15 @@ fn yohey = (pos) => {
page.getByRole('button', { name: 'Edit Sketch' }) page.getByRole('button', { name: 'Edit Sketch' })
).not.toBeVisible() ).not.toBeVisible()
// selecting an editable sketch but clicking "start sktech" should start a new sketch and not edit the existing one // selecting an editable sketch but clicking "start sketch" should start a new sketch and not edit the existing one
await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click() await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click()
await page.getByRole('button', { name: 'Start Sketch' }).click() await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.getByTestId('KCL Code').click() await page.getByTestId('KCL Code').click()
await page.mouse.click(734, 134) await page.mouse.click(734, 134)
await page.getByTestId('KCL Code').click() await page.getByTestId('KCL Code').click()
// expect main content to contain `part005` i.e. started a new sketch // expect main content to contain `sketch005` i.e. started a new sketch
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
/part005 = startSketchOn\('XZ'\)/ /sketch001 = startSketchOn\('XZ'\)/
) )
}) })
@ -1869,7 +1872,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')` `const sketch001 = startSketchOn('XZ')`
) )
await page.waitForTimeout(600) await page.waitForTimeout(600)
@ -1997,7 +2000,7 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('XZ') `const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -14.01], %) |> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %) |> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -5.38], %)` |> tangentialArcTo([24.95, -5.38], %)`
@ -2079,7 +2082,7 @@ 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 sketch001 = startSketchOn('XZ')
|> startProfileAt([6.44, -12.07], %) |> startProfileAt([6.44, -12.07], %)
|> line([14.72, 1.97], %) |> line([14.72, 1.97], %)
|> tangentialArcTo([26.92, -3.32], %)`) |> tangentialArcTo([26.92, -3.32], %)`)
@ -2097,7 +2100,7 @@ const doSnapAtDifferentScales = async (
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await u.openDebugPanel()
const code = `const part001 = startSketchOn('-XZ') const code = `const sketch001 = startSketchOn('-XZ')
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %) |> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|> line([${roundOff(scale * 175.36)}, 0], %) |> line([${roundOff(scale * 175.36)}, 0], %)
|> line([0, -${roundOff(scale * 175.36) + fudge}], %) |> line([0, -${roundOff(scale * 175.36) + fudge}], %)
@ -2120,7 +2123,7 @@ const doSnapAtDifferentScales = async (
// select a plane // select a plane
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')` `const sketch001 = startSketchOn('-XZ')`
) )
let prevContent = await page.locator('.cm-content').innerText() let prevContent = await page.locator('.cm-content').innerText()
@ -2182,7 +2185,7 @@ test('Sketch on face', async ({ page }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('XZ') `const sketch001 = startSketchOn('XZ')
|> startProfileAt([3.29, 7.86], %) |> startProfileAt([3.29, 7.86], %)
|> line([2.48, 2.44], %) |> line([2.48, 2.44], %)
|> line([2.66, 1.17], %) |> line([2.66, 1.17], %)
@ -2195,7 +2198,7 @@ test('Sketch on face', async ({ page }) => {
|> line([-3.86, -2.73], %) |> line([-3.86, -2.73], %)
|> line([-17.67, 0.85], %) |> line([-17.67, 0.85], %)
|> close(%) |> close(%)
|> extrude(5 + 7, %)` const extrude001 = extrude(5 + 7, sketch001)`
) )
}) })
@ -2249,7 +2252,7 @@ test('Sketch on face', async ({ page }) => {
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 sketch002 = startSketchOn(extrude001, 'seg01')
|> startProfileAt([-12.94, 6.6], %) |> startProfileAt([-12.94, 6.6], %)
|> line([2.45, -0.2], %) |> line([2.45, -0.2], %)
|> line([-2.6, -1.25], %) |> line([-2.6, -1.25], %)
@ -2288,7 +2291,7 @@ test('Sketch on face', async ({ page }) => {
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()
const result = makeTemplate`const part002 = startSketchOn(part001, 'seg01') const result = makeTemplate`const sketch002 = startSketchOn(extrude001, 'seg01')
|> startProfileAt([-12.83, 6.7], %) |> startProfileAt([-12.83, 6.7], %)
|> line([${[2.28, 2.35]}, -${0.07}], %) |> line([${[2.28, 2.35]}, -${0.07}], %)
|> line([-3.05, -1.47], %) |> line([-3.05, -1.47], %)
@ -2317,7 +2320,7 @@ test('Sketch on face', async ({ page }) => {
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
const result2 = result.genNext` const result2 = result.genNext`
|> extrude(${[5, 5]} + 7, %)` const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
await expect(page.locator('.cm-content')).toHaveText(result2.regExp) await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
}) })
@ -2325,7 +2328,7 @@ test('Can code mod a line length', async ({ page }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('XY') `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2363,7 +2366,7 @@ test('Can code mod a line length', async ({ page }) => {
await page.getByText('Add constraining value').click() await page.getByText('Add constraining value').click()
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const length001 = 20const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)` `const length001 = 20const sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
) )
// Make sure we didn't pop out of sketch mode. // Make sure we didn't pop out of sketch mode.
@ -2385,7 +2388,7 @@ test('Extrude from command bar selects extrude line after', async ({
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('XY') `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
@ -2415,7 +2418,7 @@ test('Extrude from command bar selects extrude line after', async ({
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toHaveText( await expect(page.locator('.cm-activeLine')).toHaveText(
` |> extrude(${KCL_DEFAULT_LENGTH}, %)` `const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
) )
}) })
@ -4663,7 +4666,7 @@ test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')` `const sketch001 = startSketchOn('XZ')`
) )
await u.closeDebugPanel() await u.closeDebugPanel()
@ -4672,7 +4675,7 @@ test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`) |> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -4680,7 +4683,7 @@ test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`) |> line([${commonPoints.num1}, 0], %)`)
@ -4743,14 +4746,14 @@ test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
// Ensure we can continue sketching // Ensure we can continue sketching
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 sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([-11.64, 11.11], %)`) |> line([-11.64, 11.11], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20) await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([-11.64, 11.11], %) |> line([-11.64, 11.11], %)

View File

@ -406,7 +406,7 @@ test('Draft segments should look right', async ({ page, context }) => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')` `const sketch001 = startSketchOn('XZ')`
) )
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
@ -414,7 +414,7 @@ test('Draft segments should look right', async ({ page, context }) => {
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %)`) |> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -428,7 +428,7 @@ test('Draft segments should look right', async ({ page, context }) => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %) |> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`) |> line([9.14, 0], %)`)
@ -465,7 +465,7 @@ test('Draft rectangles should look right', async ({ page, context }) => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')` `const sketch001 = startSketchOn('XZ')`
) )
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
@ -514,7 +514,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')` `const sketch001 = startSketchOn('XZ')`
) )
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
@ -522,7 +522,7 @@ test.describe('Client side scene scale should match engine scale', () => {
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %)`) |> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -532,7 +532,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %) |> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`) |> line([9.14, 0], %)`)
@ -542,7 +542,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %) |> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %) |> line([9.14, 0], %)
|> tangentialArcTo([27.34, -3.08], %)`) |> tangentialArcTo([27.34, -3.08], %)`)
@ -617,7 +617,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')` `const sketch001 = startSketchOn('XZ')`
) )
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
@ -625,7 +625,7 @@ test.describe('Client side scene scale should match engine scale', () => {
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([230.03, -310.32], %)`) |> startProfileAt([230.03, -310.32], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -635,7 +635,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([230.03, -310.32], %) |> startProfileAt([230.03, -310.32], %)
|> line([232.2, 0], %)`) |> line([232.2, 0], %)`)
@ -645,7 +645,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([230.03, -310.32], %) |> startProfileAt([230.03, -310.32], %)
|> line([232.2, 0], %) |> line([232.2, 0], %)
|> tangentialArcTo([694.43, -78.12], %)`) |> tangentialArcTo([694.43, -78.12], %)`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -93,7 +93,10 @@ import { createGridHelper, orthoScale, perspScale } from './helpers'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
import { SegmentOverlayPayload, SketchDetails } from 'machines/modelingMachine' import { SegmentOverlayPayload, SketchDetails } from 'machines/modelingMachine'
import { EngineCommandManager } from 'lang/std/engineConnection' import {
ArtifactMapCommand,
EngineCommandManager,
} from 'lang/std/engineConnection'
import { import {
getRectangleCallExpressions, getRectangleCallExpressions,
updateRectangleSketch, updateRectangleSketch,
@ -759,14 +762,6 @@ export class SceneEntities {
_ast = parse(recast(_ast)) _ast = parse(recast(_ast))
console.log('onClick', {
sketchInit: sketchInit,
_ast,
x,
y,
truncatedAst,
})
// Update the primary AST and unequip the rectangle tool // Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'CancelSketch' }) sceneInfra.modelingSend({ type: 'CancelSketch' })
@ -1422,6 +1417,30 @@ export class SceneEntities {
return ['plane', entity_id] return ['plane', entity_id]
} }
const artifact = this.engineCommandManager.artifactMap[entity_id] const artifact = this.engineCommandManager.artifactMap[entity_id]
// If we clicked on an extrude wall, we climb up the parent Id
// to get the sketch profile's face ID. If we clicked on an endcap,
// we already have it.
const targetId =
'additionalData' in artifact &&
artifact.additionalData?.type === 'cap'
? entity_id
: artifact.parentId
// tsc cannot infer that target can have extrusions
// from the commandType (why?) so we need to cast it
const target = this.engineCommandManager.artifactMap?.[
targetId || ''
] as ArtifactMapCommand & { extrusions?: string[] }
// TODO: We get the first extrusion command ID,
// which is fine while backend systems only support one extrusion.
// but we need to more robustly handle resolving to the correct extrusion
// if there are multiple.
const extrusions =
this.engineCommandManager.artifactMap?.[
target?.extrusions?.[0] || ''
]
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info') if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
return ['other', entity_id] return ['other', entity_id]
@ -1429,10 +1448,13 @@ export class SceneEntities {
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
return ['other', entity_id] return ['other', entity_id]
const { z_axis, y_axis, origin } = faceInfo const { z_axis, y_axis, origin } = faceInfo
const pathToNode = getNodePathFromSourceRange( const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast, kclManager.ast,
artifact.range artifact.range
) )
const extrudePathToNode = extrusions?.range
? getNodePathFromSourceRange(kclManager.ast, extrusions.range)
: []
sceneInfra.modelingSend({ sceneInfra.modelingSend({
type: 'Select default plane', type: 'Select default plane',
@ -1443,7 +1465,8 @@ export class SceneEntities {
position: [origin.x, origin.y, origin.z].map( position: [origin.x, origin.y, origin.z].map(
(num) => num / sceneInfra._baseUnitMultiplier (num) => num / sceneInfra._baseUnitMultiplier
) as [number, number, number], ) as [number, number, number],
extrudeSegmentPathToNode: pathToNode, sketchPathToNode,
extrudePathToNode,
cap: cap:
artifact?.additionalData?.type === 'cap' artifact?.additionalData?.type === 'cap'
? artifact.additionalData.info ? artifact.additionalData.info
@ -1455,7 +1478,6 @@ export class SceneEntities {
} }
const faceResult = await checkExtrudeFaceClick() const faceResult = await checkExtrudeFaceClick()
console.log('faceResult', faceResult)
if (faceResult[0] === 'face') return if (faceResult[0] === 'face') return
if (!args || !args.intersects?.[0]) return if (!args || !args.intersects?.[0]) return

View File

@ -453,7 +453,8 @@ export const ModelingMachineProvider = ({
const { modifiedAst, pathToNode: pathToNewSketchNode } = const { modifiedAst, pathToNode: pathToNewSketchNode } =
sketchOnExtrudedFace( sketchOnExtrudedFace(
kclManager.ast, kclManager.ast,
data.extrudeSegmentPathToNode, data.sketchPathToNode,
data.extrudePathToNode,
kclManager.programMemory, kclManager.programMemory,
data.cap data.cap
) )

View File

@ -125,7 +125,7 @@ describe('Testing addSketchTo', () => {
'yz' 'yz'
) )
const str = recast(result.modifiedAst) const str = recast(result.modifiedAst)
expect(str).toBe(`const part001 = startSketchOn('YZ') expect(str).toBe(`const sketch001 = startSketchOn('YZ')
|> startProfileAt('default', %) |> startProfileAt('default', %)
|> line('default', %) |> line('default', %)
`) `)
@ -291,14 +291,25 @@ describe('testing sketchOnExtrudedFace', () => {
|> extrude(5 + 7, %)` |> extrude(5 + 7, %)`
const ast = parse(code) const ast = parse(code)
const programMemory = await enginelessExecutor(ast) const programMemory = await enginelessExecutor(ast)
const snippet = `line([9.7, 9.19], %)` const segmentSnippet = `line([9.7, 9.19], %)`
const range: [number, number] = [ const segmentRange: [number, number] = [
code.indexOf(snippet), code.indexOf(segmentSnippet),
code.indexOf(snippet) + snippet.length, code.indexOf(segmentSnippet) + segmentSnippet.length,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
const { modifiedAst } = sketchOnExtrudedFace(ast, pathToNode, programMemory) const { modifiedAst } = sketchOnExtrudedFace(
ast,
segmentPathToNode,
extrudePathToNode,
programMemory
)
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
expect(newCode).toContain(`const part001 = startSketchOn('-XZ') expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|> startProfileAt([3.58, 2.06], %) |> startProfileAt([3.58, 2.06], %)
@ -306,7 +317,7 @@ describe('testing sketchOnExtrudedFace', () => {
|> line([8.62, -9.57], %) |> line([8.62, -9.57], %)
|> close(%) |> close(%)
|> extrude(5 + 7, %) |> extrude(5 + 7, %)
const part002 = startSketchOn(part001, 'seg01')`) const sketch001 = startSketchOn(part001, 'seg01')`)
}) })
test('it should be able to extrude on close segments', async () => { test('it should be able to extrude on close segments', async () => {
const code = `const part001 = startSketchOn('-XZ') const code = `const part001 = startSketchOn('-XZ')
@ -317,14 +328,25 @@ const part002 = startSketchOn(part001, 'seg01')`)
|> extrude(5 + 7, %)` |> extrude(5 + 7, %)`
const ast = parse(code) const ast = parse(code)
const programMemory = await enginelessExecutor(ast) const programMemory = await enginelessExecutor(ast)
const snippet = `close(%)` const segmentSnippet = `close(%)`
const range: [number, number] = [ const segmentRange: [number, number] = [
code.indexOf(snippet), code.indexOf(segmentSnippet),
code.indexOf(snippet) + snippet.length, code.indexOf(segmentSnippet) + segmentSnippet.length,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
const { modifiedAst } = sketchOnExtrudedFace(ast, pathToNode, programMemory) const { modifiedAst } = sketchOnExtrudedFace(
ast,
segmentPathToNode,
extrudePathToNode,
programMemory
)
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
expect(newCode).toContain(`const part001 = startSketchOn('-XZ') expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|> startProfileAt([3.58, 2.06], %) |> startProfileAt([3.58, 2.06], %)
@ -332,7 +354,7 @@ const part002 = startSketchOn(part001, 'seg01')`)
|> line([8.62, -9.57], %) |> line([8.62, -9.57], %)
|> close(%, 'seg01') |> close(%, 'seg01')
|> extrude(5 + 7, %) |> extrude(5 + 7, %)
const part002 = startSketchOn(part001, 'seg01')`) const sketch001 = startSketchOn(part001, 'seg01')`)
}) })
test('it should be able to extrude on start-end caps', async () => { test('it should be able to extrude on start-end caps', async () => {
const code = `const part001 = startSketchOn('-XZ') const code = `const part001 = startSketchOn('-XZ')
@ -343,16 +365,23 @@ const part002 = startSketchOn(part001, 'seg01')`)
|> extrude(5 + 7, %)` |> extrude(5 + 7, %)`
const ast = parse(code) const ast = parse(code)
const programMemory = await enginelessExecutor(ast) const programMemory = await enginelessExecutor(ast)
const snippet = `startProfileAt([3.58, 2.06], %)` const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
const range: [number, number] = [ const sketchRange: [number, number] = [
code.indexOf(snippet), code.indexOf(sketchSnippet),
code.indexOf(snippet) + snippet.length, code.indexOf(sketchSnippet) + sketchSnippet.length,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
const { modifiedAst } = sketchOnExtrudedFace( const { modifiedAst } = sketchOnExtrudedFace(
ast, ast,
pathToNode, sketchPathToNode,
extrudePathToNode,
programMemory, programMemory,
'end' 'end'
) )
@ -363,7 +392,47 @@ const part002 = startSketchOn(part001, 'seg01')`)
|> line([8.62, -9.57], %) |> line([8.62, -9.57], %)
|> close(%) |> close(%)
|> extrude(5 + 7, %) |> extrude(5 + 7, %)
const part002 = startSketchOn(part001, 'END')`) const sketch001 = startSketchOn(part001, 'END')`)
})
test('it should ensure that the new sketch is inserted after the extrude', async () => {
const code = `const sketch001 = startSketchOn('-XZ')
|> startProfileAt([3.29, 7.86], %)
|> line([2.48, 2.44], %)
|> line([2.66, 1.17], %)
|> line([3.75, 0.46], %)
|> line([4.99, -0.46], %)
|> line([3.3, -2.12], %)
|> line([2.16, -3.33], %)
|> line([0.85, -3.08], %)
|> line([-0.18, -3.36], %)
|> line([-3.86, -2.73], %)
|> line([-17.67, 0.85], %)
|> close(%)
const part001 = extrude(5 + 7, sketch001)`
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const segmentSnippet = `line([4.99, -0.46], %)`
const segmentRange: [number, number] = [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, sketch001)`
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
const { modifiedAst } = sketchOnExtrudedFace(
ast,
segmentPathToNode,
extrudePathToNode,
programMemory
)
const newCode = recast(modifiedAst)
expect(newCode).toContain(`const part001 = extrude(5 + 7, sketch001)
const sketch002 = startSketchOn(part001, 'seg01')`)
}) })
}) })

View File

@ -34,6 +34,7 @@ import {
} from './std/sketchcombos' } from './std/sketchcombos'
import { DefaultPlaneStr } from 'clientSideScene/sceneEntities' import { DefaultPlaneStr } from 'clientSideScene/sceneEntities'
import { isOverlap, roundOff } from 'lib/utils' import { isOverlap, roundOff } from 'lib/utils'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
import { ConstrainInfo } from './std/stdTypes' import { ConstrainInfo } from './std/stdTypes'
export function startSketchOnDefault( export function startSketchOnDefault(
@ -42,7 +43,8 @@ export function startSketchOnDefault(
name = '' name = ''
): { modifiedAst: Program; id: string; pathToNode: PathToNode } { ): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
const _node = { ...node } const _node = { ...node }
const _name = name || findUniqueName(node, 'part') const _name =
name || findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SKETCH)
const startSketchOn = createCallExpressionStdLib('startSketchOn', [ const startSketchOn = createCallExpressionStdLib('startSketchOn', [
createLiteral(axis), createLiteral(axis),
@ -109,7 +111,8 @@ export function addSketchTo(
name = '' name = ''
): { modifiedAst: Program; id: string; pathToNode: PathToNode } { ): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
const _node = { ...node } const _node = { ...node }
const _name = name || findUniqueName(node, 'part') const _name =
name || findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SKETCH)
const startSketchOn = createCallExpressionStdLib('startSketchOn', [ const startSketchOn = createCallExpressionStdLib('startSketchOn', [
createLiteral(axis.toUpperCase()), createLiteral(axis.toUpperCase()),
@ -242,7 +245,7 @@ export function mutateObjExpProp(
export function extrudeSketch( export function extrudeSketch(
node: Program, node: Program,
pathToNode: PathToNode, pathToNode: PathToNode,
shouldPipe = true, shouldPipe = false,
distance = createLiteral(4) as Value distance = createLiteral(4) as Value
): { ): {
modifiedAst: Program modifiedAst: Program
@ -293,12 +296,22 @@ export function extrudeSketch(
pathToExtrudeArg, pathToExtrudeArg,
} }
} }
const name = findUniqueName(node, 'part')
// We're not creating a pipe expression,
// but rather a separate constant for the extrusion
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.EXTRUDE)
const VariableDeclaration = createVariableDeclaration(name, extrudeCall) const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
_node.body.splice(_node.body.length, 0, VariableDeclaration)
const sketchIndexInPathToNode =
pathToDecleration.findIndex((a) => a[0] === 'body') + 1
const sketchIndexInBody = pathToDecleration[
sketchIndexInPathToNode
][0] as number
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
const pathToExtrudeArg: PathToNode = [ const pathToExtrudeArg: PathToNode = [
['body', ''], ['body', ''],
[_node.body.length, 'index'], [sketchIndexInBody + 1, 'index'],
['declarations', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'], [0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
@ -306,7 +319,7 @@ export function extrudeSketch(
[0, 'index'], [0, 'index'],
] ]
return { return {
modifiedAst: node, modifiedAst: _node,
pathToNode: [...pathToNode.slice(0, -1), [-1, 'index']], pathToNode: [...pathToNode.slice(0, -1), [-1, 'index']],
pathToExtrudeArg, pathToExtrudeArg,
} }
@ -314,31 +327,41 @@ export function extrudeSketch(
export function sketchOnExtrudedFace( export function sketchOnExtrudedFace(
node: Program, node: Program,
pathToNode: PathToNode, sketchPathToNode: PathToNode,
extrudePathToNode: PathToNode,
programMemory: ProgramMemory, programMemory: ProgramMemory,
cap: 'none' | 'start' | 'end' = 'none' cap: 'none' | 'start' | 'end' = 'none'
): { modifiedAst: Program; pathToNode: PathToNode } { ): { modifiedAst: Program; pathToNode: PathToNode } {
let _node = { ...node } let _node = { ...node }
const newSketchName = findUniqueName(node, 'part') const newSketchName = findUniqueName(
node,
KCL_DEFAULT_CONSTANT_PREFIXES.SKETCH
)
const { node: oldSketchNode } = getNodeFromPath<VariableDeclarator>( const { node: oldSketchNode } = getNodeFromPath<VariableDeclarator>(
_node, _node,
pathToNode, sketchPathToNode,
'VariableDeclarator', 'VariableDeclarator',
true true
) )
const oldSketchName = oldSketchNode.id.name const oldSketchName = oldSketchNode.id.name
const { node: expression } = getNodeFromPath<CallExpression>( const { node: expression } = getNodeFromPath<CallExpression>(
_node, _node,
pathToNode, sketchPathToNode,
'CallExpression' 'CallExpression'
) )
const { node: extrudeVarDec } = getNodeFromPath<VariableDeclarator>(
_node,
extrudePathToNode,
'VariableDeclarator'
)
const extrudeName = extrudeVarDec.id?.name
let _tag = '' let _tag = ''
if (cap === 'none') { if (cap === 'none') {
const { modifiedAst, tag } = addTagForSketchOnFace( const { modifiedAst, tag } = addTagForSketchOnFace(
{ {
previousProgramMemory: programMemory, previousProgramMemory: programMemory,
pathToNode, pathToNode: sketchPathToNode,
node: _node, node: _node,
}, },
expression.callee.name expression.callee.name
@ -352,13 +375,16 @@ export function sketchOnExtrudedFace(
const newSketch = createVariableDeclaration( const newSketch = createVariableDeclaration(
newSketchName, newSketchName,
createCallExpressionStdLib('startSketchOn', [ createCallExpressionStdLib('startSketchOn', [
createIdentifier(oldSketchName), createIdentifier(extrudeName ? extrudeName : oldSketchName),
createLiteral(_tag), createLiteral(_tag),
]), ]),
'const' 'const'
) )
const expressionIndex = pathToNode[1][0] as number const expressionIndex = Math.max(
sketchPathToNode[1][0] as number,
extrudePathToNode[1][0] as number
)
_node.body.splice(expressionIndex + 1, 0, newSketch) _node.body.splice(expressionIndex + 1, 0, newSketch)
const newpathToNode: PathToNode = [ const newpathToNode: PathToNode = [
['body', ''], ['body', ''],

View File

@ -12,22 +12,43 @@ let lastMessage = ''
// TODO(paultag): This ought to be tweakable. // TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 10000 const pingIntervalMs = 10000
interface CommandInfo { type CommandTypes = Models['ModelingCmd_type']['type'] | 'batch'
commandType: CommandTypes
range: SourceRange type CommandInfo =
pathToNode: PathToNode | {
parentId?: string commandType: 'extrude'
additionalData?: // commandType: CommandTypes
| { range: SourceRange
type: 'cap' pathToNode: PathToNode
info: 'start' | 'end' /// uuid of the entity to extrude
} target: string
| { parentId?: string
type: 'batch-ids' }
ids: string[] | {
info?: null commandType: 'start_path'
} // commandType: CommandTypes
} range: SourceRange
pathToNode: PathToNode
/// uuid of the entity that have been extruded
extrusions: string[]
parentId?: string
}
| {
commandType: CommandTypes
range: SourceRange
pathToNode: PathToNode
parentId?: string
additionalData?:
| {
type: 'cap'
info: 'start' | 'end'
}
| {
type: 'batch-ids'
ids: string[]
info?: null
}
}
function isHighlightSetEntity_type( function isHighlightSetEntity_type(
data: any data: any
@ -38,13 +59,13 @@ function isHighlightSetEntity_type(
type WebSocketResponse = Models['WebSocketResponse_type'] type WebSocketResponse = Models['WebSocketResponse_type']
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type'] type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
interface ResultCommand extends CommandInfo { type ResultCommand = CommandInfo & {
type: 'result' type: 'result'
data: any data: any
raw: WebSocketResponse raw: WebSocketResponse
headVertexId?: string headVertexId?: string
} }
interface FailedCommand extends CommandInfo { type FailedCommand = CommandInfo & {
type: 'failed' type: 'failed'
errors: Models['FailureWebSocketResponse_type']['errors'] errors: Models['FailureWebSocketResponse_type']['errors']
} }
@ -57,12 +78,14 @@ interface ResolveCommand {
data?: Models['OkModelingCmdResponse_type'] data?: Models['OkModelingCmdResponse_type']
errors?: Models['FailureWebSocketResponse_type']['errors'] errors?: Models['FailureWebSocketResponse_type']['errors']
} }
interface PendingCommand extends CommandInfo { type PendingCommand = CommandInfo & {
type: 'pending' type: 'pending'
promise: Promise<any> promise: Promise<any>
resolve: (val: ResolveCommand) => void resolve: (val: ResolveCommand) => void
} }
export type ArtifactMapCommand = ResultCommand | PendingCommand | FailedCommand
/** /**
* The ArtifactMap is a client-side representation of the artifacts that * The ArtifactMap is a client-side representation of the artifacts that
* have been sent to the server-side engine. It is used to keep track of * have been sent to the server-side engine. It is used to keep track of
@ -72,7 +95,7 @@ interface PendingCommand extends CommandInfo {
* lines of KCL code that generated them. * lines of KCL code that generated them.
*/ */
export interface ArtifactMap { export interface ArtifactMap {
[commandId: string]: ResultCommand | PendingCommand | FailedCommand [commandId: string]: ArtifactMapCommand
} }
interface NewTrackArgs { interface NewTrackArgs {
@ -988,8 +1011,6 @@ class EngineConnection extends EventTarget {
export type EngineCommand = Models['WebSocketRequest_type'] export type EngineCommand = Models['WebSocketRequest_type']
type ModelTypes = Models['OkModelingCmdResponse_type']['type'] type ModelTypes = Models['OkModelingCmdResponse_type']['type']
type CommandTypes = Models['ModelingCmd_type']['type'] | 'batch'
type UnreliableResponses = Extract< type UnreliableResponses = Extract<
Models['OkModelingCmdResponse_type'], Models['OkModelingCmdResponse_type'],
{ type: 'highlight_set_entity' | 'camera_drag_move' } { type: 'highlight_set_entity' | 'camera_drag_move' }
@ -1814,9 +1835,12 @@ export class EngineCommandManager extends EventTarget {
if (command.type === 'solid3d_get_extrusion_face_info') { if (command.type === 'solid3d_get_extrusion_face_info') {
const edgeArtifact = this.artifactMap[command.edge_id] const edgeArtifact = this.artifactMap[command.edge_id]
// edges's parent id is to the original "start_path" artifact // edges's parent id is to the original "start_path" artifact
if (edgeArtifact?.parentId) return edgeArtifact.parentId if (edgeArtifact && edgeArtifact.parentId) {
return edgeArtifact.parentId
}
} }
if (command.type === 'close_path') return command.path_id if (command.type === 'close_path') return command.path_id
if (command.type === 'extrude') return command.target
// handle other commands that have a parent here // handle other commands that have a parent here
} }
const pathToNode = ast const pathToNode = ast
@ -1831,6 +1855,33 @@ export class EngineCommandManager extends EventTarget {
promise, promise,
resolve, resolve,
} }
if (command.type === 'extrude') {
this.artifactMap[id] = {
range: range || [0, 0],
pathToNode,
type: 'pending',
commandType: 'extrude',
parentId: getParentId(),
promise,
target: command.target,
resolve,
}
const target = this.artifactMap[command.target]
if (target.commandType === 'start_path') {
// tsc cannot infer that target can have extrusions
// from the commandType (why?) so we need to cast it
const typedTarget = target as (
| PendingCommand
| ResultCommand
| FailedCommand
) & { extrusions?: string[] }
if (typedTarget?.extrusions?.length) {
typedTarget.extrusions.push(id)
} else {
typedTarget.extrusions = [id]
}
}
}
return promise return promise
} }
async handlePendingBatchCommand( async handlePendingBatchCommand(

View File

@ -44,5 +44,13 @@ export const RELEVANT_FILE_TYPES = [
] as const ] as const
/** The default name for a tutorial project */ /** The default name for a tutorial project */
export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn' export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
/**
* The default starting constant name for various modeling operations.
* These are used to generate unique names for new objects.
* */
export const KCL_DEFAULT_CONSTANT_PREFIXES = {
SKETCH: 'sketch',
EXTRUDE: 'extrude',
} as const
/** The default KCL length expression */ /** The default KCL length expression */
export const KCL_DEFAULT_LENGTH = `5` export const KCL_DEFAULT_LENGTH = `5`

View File

@ -145,7 +145,8 @@ export type ModelingMachineEvent =
| { | {
type: 'extrudeFace' type: 'extrudeFace'
position: [number, number, number] position: [number, number, number]
extrudeSegmentPathToNode: PathToNode sketchPathToNode: PathToNode
extrudePathToNode: PathToNode
cap: 'start' | 'end' | 'none' cap: 'start' | 'end' | 'none'
faceId: string faceId: string
} }
@ -883,7 +884,7 @@ export const modelingMachine = createMachine(
const { modifiedAst, pathToExtrudeArg } = extrudeSketch( const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast, ast,
pathToNode, pathToNode,
true, false,
'variableName' in distance 'variableName' in distance
? distance.variableIdentifierAst ? distance.variableIdentifierAst
: distance.valueAst : distance.valueAst