Compare commits
7 Commits
paultag/di
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
66638368cc | |||
a2699aa3d1 | |||
6da22b14a6 | |||
79b47a57c5 | |||
3cd1b9a3be | |||
46c6d5bed3 | |||
9cba212f32 |
@ -57,23 +57,26 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await expect(u.codeLocator).toContainText(
|
||||
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||
)
|
||||
}
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
}
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
} else {
|
||||
@ -82,8 +85,10 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
await page.waitForTimeout(200)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
@ -140,8 +145,10 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
|
||||
// Open the code pane.
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %, $seg01)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(-segLen(seg01), %)`)
|
||||
|
@ -44,8 +44,7 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
},
|
||||
}
|
||||
|
||||
const code = `sketch001 = startSketchOn('${plane}')
|
||||
|> startProfileAt([0.9, -1.22], %)`
|
||||
const code = `sketch001 = startSketchOn('${plane}')profile001 = startProfileAt([0.9, -1.22], sketch001)`
|
||||
|
||||
await u.openDebugPanel()
|
||||
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
|
||||
type mouseParams = {
|
||||
pixelDiff?: number
|
||||
shouldDbClick?: boolean
|
||||
}
|
||||
type mouseDragToParams = mouseParams & {
|
||||
fromPoint: { x: number; y: number }
|
||||
@ -75,11 +76,16 @@ export class SceneFixture {
|
||||
if (clickParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
() => this.page.mouse.click(x, y),
|
||||
() =>
|
||||
clickParams?.shouldDbClick
|
||||
? this.page.mouse.dblclick(x, y)
|
||||
: this.page.mouse.click(x, y),
|
||||
clickParams.pixelDiff
|
||||
)
|
||||
}
|
||||
return this.page.mouse.click(x, y)
|
||||
return clickParams?.shouldDbClick
|
||||
? this.page.mouse.dblclick(x, y)
|
||||
: this.page.mouse.click(x, y)
|
||||
},
|
||||
(moveParams?: mouseParams) => {
|
||||
if (moveParams?.pixelDiff) {
|
||||
|
@ -11,7 +11,10 @@ export class ToolbarFixture {
|
||||
offsetPlaneButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
tangentialArcBtn!: Locator
|
||||
circleBtn!: Locator
|
||||
rectangleBtn!: Locator
|
||||
lengthConstraintBtn!: Locator
|
||||
exitSketchBtn!: Locator
|
||||
editSketchBtn!: Locator
|
||||
fileTreeBtn!: Locator
|
||||
@ -33,7 +36,10 @@ export class ToolbarFixture {
|
||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
this.tangentialArcBtn = page.getByTestId('tangential-arc')
|
||||
this.circleBtn = page.getByTestId('circle-center')
|
||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||
this.lengthConstraintBtn = page.getByTestId('constraint-length')
|
||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||
this.editSketchBtn = page.getByText('Edit Sketch')
|
||||
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||
@ -91,4 +97,13 @@ export class ToolbarFixture {
|
||||
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
|
||||
}
|
||||
}
|
||||
selectCenterRectangle = async () => {
|
||||
await this.page
|
||||
.getByRole('button', { name: 'caret down Corner rectangle:' })
|
||||
.click()
|
||||
await expect(
|
||||
this.page.getByTestId('dropdown-center-rectangle')
|
||||
).toBeVisible()
|
||||
await this.page.getByTestId('dropdown-center-rectangle').click()
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,9 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
pixelDiff: 50,
|
||||
})
|
||||
await rectangle2ndClick()
|
||||
await editor.expectEditor.toContain(afterRectangle2ndClickSnippet)
|
||||
await editor.expectEditor.toContain(afterRectangle2ndClickSnippet, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('Clean up so that `_sketchOnAChamfer` util can be called again', async () => {
|
||||
@ -177,18 +179,13 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
|
||||
afterChamferSelectSnippet:
|
||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([205.96, 254.59], sketch002)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,11.39],%,$rectangleSegmentA002)
|
||||
|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)
|
||||
|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)
|
||||
|>lineTo([profileStartX(%),profileStartY(%)],%)
|
||||
|>close(%)`,
|
||||
})
|
||||
|
||||
await sketchOnAChamfer({
|
||||
@ -209,19 +206,15 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
|
||||
afterChamferSelectSnippet:
|
||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
], %, $rectangleSegmentB002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %, $rectangleSegmentC002)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([-209.64, 255.28], sketch003)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,11.56],%,$rectangleSegmentA003)
|
||||
|>angledLine([segAng(rectangleSegmentA003)-90,106.84],%)
|
||||
|>angledLine([segAng(rectangleSegmentA003),-segLen(rectangleSegmentA003)],%)
|
||||
|>lineTo([profileStartX(%),profileStartY(%)],%)
|
||||
|>close(%)`,
|
||||
})
|
||||
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 677, y: 87 },
|
||||
cameraPos: { x: -6200, y: 1500, z: 6200 },
|
||||
@ -234,19 +227,14 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
]
|
||||
}, %)`,
|
||||
afterChamferSelectSnippet:
|
||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
], %, $rectangleSegmentB002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %, $rectangleSegmentC002)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
'sketch004 = startSketchOn(extrude001, seg05)',
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([82.57, 322.96], sketch004)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,11.16],%,$rectangleSegmentA004)
|
||||
|>angledLine([segAng(rectangleSegmentA004)-90,103.07],%)
|
||||
|>angledLine([segAng(rectangleSegmentA004),-segLen(rectangleSegmentA004)],%)
|
||||
|>lineTo([profileStartX(%),profileStartY(%)],%)|
|
||||
>close(%)`,
|
||||
})
|
||||
/// last one
|
||||
await sketchOnAChamfer({
|
||||
@ -259,104 +247,97 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
}, %)`,
|
||||
afterChamferSelectSnippet:
|
||||
'sketch005 = startSketchOn(extrude001, seg06)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005) - 90,
|
||||
84.07
|
||||
], %, $rectangleSegmentB004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005),
|
||||
-segLen(rectangleSegmentA005)
|
||||
], %, $rectangleSegmentC004)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([-23.43, 19.69], sketch005)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,9.1],%,$rectangleSegmentA005)
|
||||
|>angledLine([segAng(rectangleSegmentA005)-90,84.07],%)
|
||||
|>angledLine([segAng(rectangleSegmentA005),-segLen(rectangleSegmentA005)],%)
|
||||
|>lineTo([profileStartX(%),profileStartY(%)],%)
|
||||
|>close(%)`,
|
||||
})
|
||||
|
||||
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
217.26
|
||||
], %, $seg01)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $yo)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %, $seg02)
|
||||
|> close(%)
|
||||
extrude001 = extrude(100, sketch001)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getOppositeEdge(seg01)]
|
||||
}, %, $seg03)
|
||||
|> chamfer({ length = 30, tags = [seg01] }, %, $seg04)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(seg02)]
|
||||
}, %, $seg05)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(yo)]
|
||||
}, %, $seg06)
|
||||
sketch005 = startSketchOn(extrude001, seg06)
|
||||
|> startProfileAt([-23.43, 19.69], %)
|
||||
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005) - 90,
|
||||
84.07
|
||||
], %, $rectangleSegmentB004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005),
|
||||
-segLen(rectangleSegmentA005)
|
||||
], %, $rectangleSegmentC004)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch004 = startSketchOn(extrude001, seg05)
|
||||
|> startProfileAt([82.57, 322.96], %)
|
||||
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004) - 90,
|
||||
103.07
|
||||
], %, $rectangleSegmentB003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004),
|
||||
-segLen(rectangleSegmentA004)
|
||||
], %, $rectangleSegmentC003)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn(extrude001, seg04)
|
||||
|> startProfileAt([-209.64, 255.28], %)
|
||||
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
], %, $rectangleSegmentB002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %, $rectangleSegmentC002)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch002 = startSketchOn(extrude001, seg03)
|
||||
|> startProfileAt([205.96, 254.59], %)
|
||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`,
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
217.26
|
||||
], %, $seg01)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $yo)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %, $seg02)
|
||||
|> close(%)
|
||||
extrude001 = extrude(100, sketch001)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getOppositeEdge(seg01)]
|
||||
}, %, $seg03)
|
||||
|> chamfer({ length = 30, tags = [seg01] }, %, $seg04)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(seg02)]
|
||||
}, %, $seg05)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(yo)]
|
||||
}, %, $seg06)
|
||||
sketch005 = startSketchOn(extrude001, seg06)
|
||||
profile004 = startProfileAt([-23.43, 19.69], sketch005)
|
||||
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005) - 90,
|
||||
84.07
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005),
|
||||
-segLen(rectangleSegmentA005)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch004 = startSketchOn(extrude001, seg05)
|
||||
profile003 = startProfileAt([82.57, 322.96], sketch004)
|
||||
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004) - 90,
|
||||
103.07
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004),
|
||||
-segLen(rectangleSegmentA004)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn(extrude001, seg04)
|
||||
profile002 = startProfileAt([-209.64, 255.28], sketch003)
|
||||
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch002 = startSketchOn(extrude001, seg03)
|
||||
profile001 = startProfileAt([205.96, 254.59], sketch002)
|
||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
})
|
||||
@ -392,18 +373,13 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
beforeChamferSnippetEnd: '}, extrude001)',
|
||||
afterChamferSelectSnippet:
|
||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([205.96, 254.59], sketch002)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,11.39],%,$rectangleSegmentA002)
|
||||
|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)
|
||||
|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)
|
||||
|>lineTo([profileStartX(%),profileStartY(%)],%)
|
||||
|>close(%)`,
|
||||
})
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
@ -433,16 +409,16 @@ chamf = chamfer({
|
||||
]
|
||||
}, %)
|
||||
sketch002 = startSketchOn(extrude001, seg03)
|
||||
|> startProfileAt([205.96, 254.59], %)
|
||||
profile001 = startProfileAt([205.96, 254.59], sketch002)
|
||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %, $rectangleSegmentB001)
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %, $rectangleSegmentC001)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`,
|
||||
@ -504,10 +480,10 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||
|
||||
const expectedCodeSnippets = {
|
||||
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
|
||||
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
|
||||
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], sketch001)`,
|
||||
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
|
||||
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
|
||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
||||
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], sketch001)`,
|
||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], sketch001)`,
|
||||
}
|
||||
|
||||
await app.initialise()
|
||||
@ -692,10 +668,10 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||
profile001 = circle({ center = [0, 0], radius = 30 }, sketch001)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
profile002 = circle({ center = [0, 0], radius = 20 }, sketch002)
|
||||
`
|
||||
await app.initialise(initialCode)
|
||||
|
||||
@ -706,7 +682,7 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
testPoint.x,
|
||||
testPoint.y + 80
|
||||
)
|
||||
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
|
||||
const loftDeclaration = 'loft001 = loft([profile001, profile002])'
|
||||
|
||||
await test.step(`Look for the white of the sketch001 shape`, async () => {
|
||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||
|
@ -114,9 +114,9 @@ test.describe('Sketch tests', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> xLine(12.73, %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)`
|
||||
|> startProfileAt([2.61, -4.01], %)
|
||||
|> xLine(8.73, %)
|
||||
|> tangentialArcTo([8.33, -1.31], %)`
|
||||
)
|
||||
})
|
||||
|
||||
@ -126,7 +126,7 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
await expect(async () => {
|
||||
await page.mouse.click(700, 200)
|
||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
||||
await page.getByText('tangentialArcTo([8.33, -1.31], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeEnabled({ timeout: 1000 })
|
||||
@ -135,7 +135,7 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
await page.waitForTimeout(600) // wait for animation
|
||||
|
||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
||||
await page.getByText('tangentialArcTo([8.33, -1.31], %)').click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
@ -149,17 +149,21 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
await page.waitForTimeout(100)
|
||||
// click start profileAt handle to continue profile
|
||||
await page.mouse.click(702, 407)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(async () => {
|
||||
// click to add segment
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
.toBe(`sketch002 = startSketchOn('XZ')
|
||||
sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|> yLine(12.34, %)
|
||||
|
||||
`)
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
}).toPass({ timeout: 5_000, intervals: [1_000] })
|
||||
})
|
||||
test('Can exit selection of face', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
@ -669,7 +673,7 @@ test.describe('Sketch tests', () => {
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
await click00r(0, 0)
|
||||
codeStr += ` |> startProfileAt(${toSU([0, 0])}, %)`
|
||||
codeStr += `profile001 = startProfileAt(${toSU([0, 0])}, sketch001)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(50, 0)
|
||||
@ -705,7 +709,7 @@ test.describe('Sketch tests', () => {
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await click00r(30, 0)
|
||||
codeStr += ` |> startProfileAt([2.03, 0], %)`
|
||||
codeStr += `profile002 = startProfileAt([2.03, 0], sketch002)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
// TODO: I couldn't use `toSU` here because of some rounding error causing
|
||||
@ -742,7 +746,9 @@ test.describe('Sketch tests', () => {
|
||||
await u.openDebugPanel()
|
||||
|
||||
const code = `sketch001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|
||||
profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
|
||||
scale * 34.8
|
||||
)}], sketch001)
|
||||
|> xLine(${roundOff(scale * 139.19)}, %)
|
||||
|> yLine(-${roundOff(scale * 139.2)}, %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
@ -808,11 +814,17 @@ test.describe('Sketch tests', () => {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
// Assert the tool was unequipped
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const text = await page.locator('.cm-content').innerText()
|
||||
return text.replace(/\s/g, '')
|
||||
})
|
||||
.toBe(code.replace(/\s/g, ''))
|
||||
|
||||
// Assert the tool stays equipped after a profile is closed (ready for the next one)
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'line Line', exact: true })
|
||||
).not.toHaveAttribute('aria-pressed', 'true')
|
||||
).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
@ -1130,11 +1142,17 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
await page.mouse.click(XYPlanePoint.x + 50, XYPlanePoint.y + 50)
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([11.8, 9.09], %)
|
||||
|> line([3.39, -3.39], %)
|
||||
`)
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const text = await u.codeLocator.innerText()
|
||||
return text.replace(/\s/g, '')
|
||||
})
|
||||
.toBe(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
profile001 = startProfileAt([11.8, 9.09], sketch001)
|
||||
|> line([3.39, -3.39], %)
|
||||
`.replace(/\s/g, '')
|
||||
)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -1419,3 +1437,560 @@ test2.describe(`Sketching with offset planes`, () => {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test2.describe('multi-profile sketching', () => {
|
||||
test2(
|
||||
'Can add multiple profiles to a sketch (all tool types)',
|
||||
async ({ app, scene, toolbar, editor }) => {
|
||||
await app.initialise(``)
|
||||
|
||||
const [selectXZPlane] = scene.makeMouseHelpers(650, 150)
|
||||
|
||||
const [startProfile1] = scene.makeMouseHelpers(568, 70)
|
||||
const [endLineStartTanArc] = scene.makeMouseHelpers(701, 78)
|
||||
const [endArcStartLine] = scene.makeMouseHelpers(745, 189)
|
||||
|
||||
const [startProfile2] = scene.makeMouseHelpers(782, 80)
|
||||
const [profile2Point2] = scene.makeMouseHelpers(921, 90)
|
||||
const [profile2Point3] = scene.makeMouseHelpers(953, 178)
|
||||
|
||||
const [circle1Center] = scene.makeMouseHelpers(842, 147)
|
||||
const [circle1Radius] = scene.makeMouseHelpers(870, 171)
|
||||
|
||||
const [circle2Center] = scene.makeMouseHelpers(850, 222)
|
||||
const [circle2Radius] = scene.makeMouseHelpers(843, 230)
|
||||
|
||||
const [crnRect1point1] = scene.makeMouseHelpers(583, 205)
|
||||
const [crnRect1point2] = scene.makeMouseHelpers(618, 320)
|
||||
|
||||
const [crnRect2point1] = scene.makeMouseHelpers(663, 215)
|
||||
const [crnRect2point2] = scene.makeMouseHelpers(744, 276)
|
||||
|
||||
const [cntrRect1point1] = scene.makeMouseHelpers(624, 387)
|
||||
const [cntrRect1point2] = scene.makeMouseHelpers(676, 355)
|
||||
|
||||
const [cntrRect2point1] = scene.makeMouseHelpers(785, 332)
|
||||
const [cntrRect2point2] = scene.makeMouseHelpers(808, 286)
|
||||
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
await selectXZPlane()
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await app.page.waitForTimeout(600)
|
||||
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
|
||||
await test.step('Create a close profile stopping mid profile to equip the tangential arc, and than back to the line tool', async () => {
|
||||
await startProfile1()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile001 = startProfileAt([4.61, 12.21], sketch001)`
|
||||
)
|
||||
|
||||
await endLineStartTanArc()
|
||||
await editor.expectEditor.toContain(`|> line([9.02, -0.55], %)`)
|
||||
await toolbar.tangentialArcBtn.click()
|
||||
await app.page.waitForTimeout(100)
|
||||
await endLineStartTanArc()
|
||||
|
||||
await endArcStartLine()
|
||||
await editor.expectEditor.toContain(
|
||||
`|> tangentialArcTo([16.61, 4.14], %)`
|
||||
)
|
||||
await toolbar.lineBtn.click()
|
||||
await app.page.waitForTimeout(100)
|
||||
await endArcStartLine()
|
||||
|
||||
await app.page.mouse.click(572, 110)
|
||||
await editor.expectEditor.toContain(`|> line([-11.73, 5.35], %)`)
|
||||
await startProfile1()
|
||||
await editor.expectEditor
|
||||
.toContain(`|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`)
|
||||
await app.page.waitForTimeout(100)
|
||||
})
|
||||
|
||||
await test.step('Without unequipping from the last step, make another profile, and one that is not closed', async () => {
|
||||
await startProfile2()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile002 = startProfileAt([19.12, 11.53], sketch001)`
|
||||
)
|
||||
await profile2Point2()
|
||||
await editor.expectEditor.toContain(`|> line([9.43, -0.68], %)`)
|
||||
await profile2Point3()
|
||||
await editor.expectEditor.toContain(`|> line([2.17, -5.97], %)`)
|
||||
})
|
||||
|
||||
await test.step('create two circles in a row without unequip', async () => {
|
||||
await toolbar.circleBtn.click()
|
||||
|
||||
await circle1Center()
|
||||
await app.page.waitForTimeout(100)
|
||||
await circle1Radius()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile003 = circle({ center = [23.19, 6.98], radius = 2.5 }, sketch001)`
|
||||
)
|
||||
|
||||
await test.step('hover in empty space to wait for overlays to get out of the way', async () => {
|
||||
await app.page.mouse.move(951, 223)
|
||||
await app.page.waitForTimeout(1000)
|
||||
})
|
||||
|
||||
await circle2Center()
|
||||
await app.page.waitForTimeout(100)
|
||||
await circle2Radius()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile004 = circle({ center = [23.74, 1.9], radius = 0.72 }, sketch001)`
|
||||
)
|
||||
})
|
||||
await test.step('create two corner rectangles in a row without unequip', async () => {
|
||||
await toolbar.rectangleBtn.click()
|
||||
|
||||
await crnRect1point1()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile005 = startProfileAt([5.63, 3.05], sketch001)`
|
||||
)
|
||||
await crnRect1point2()
|
||||
await editor.expectEditor
|
||||
.toContain(`|> angledLine([0, 2.37], %, $rectangleSegmentA001)
|
||||
|> angledLine([segAng(rectangleSegmentA001) - 90, 7.8], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`)
|
||||
await app.page.waitForTimeout(100)
|
||||
|
||||
await crnRect2point1()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile006 = startProfileAt([11.05, 2.37], sketch001)`
|
||||
)
|
||||
await crnRect2point2()
|
||||
await editor.expectEditor
|
||||
.toContain(`|> angledLine([0, 5.49], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
4.14
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
await test.step('create two center rectangles in a row without unequip', async () => {
|
||||
await toolbar.selectCenterRectangle()
|
||||
|
||||
await cntrRect1point1()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile007 = startProfileAt([8.41, -9.29], sketch001)`
|
||||
)
|
||||
await cntrRect1point2()
|
||||
await editor.expectEditor
|
||||
.toContain(`|> angledLine([0, 7.06], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) + 90,
|
||||
4.34
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`)
|
||||
await app.page.waitForTimeout(100)
|
||||
|
||||
await cntrRect2point1()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile008 = startProfileAt([19.33, -5.56], sketch001)`
|
||||
)
|
||||
await cntrRect2point2()
|
||||
await editor.expectEditor
|
||||
.toContain(`|> angledLine([0, 3.12], %, $rectangleSegmentA004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004) + 90,
|
||||
6.24
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004),
|
||||
-segLen(rectangleSegmentA004)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test2(
|
||||
'Can edit a sketch with multiple profiles, dragging segments to edit them, and adding one new profile',
|
||||
async ({ app, scene, toolbar, editor }) => {
|
||||
await app.initialise(`sketch001 = startSketchOn('XZ')
|
||||
profile001 = startProfileAt([6.24, 4.54], sketch001)
|
||||
|> line([-0.41, 6.99], %)
|
||||
|> line([8.61, 0.74], %)
|
||||
|> line([10.99, -5.22], %)
|
||||
profile002 = startProfileAt([11.19, 5.02], sketch001)
|
||||
|> angledLine([0, 10.78], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
4.14
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
profile003 = circle({ center = [6.92, -4.2], radius = 3.16 }, sketch001)
|
||||
`)
|
||||
|
||||
const [pointOnSegment] = scene.makeMouseHelpers(590, 141)
|
||||
const [profileEnd] = scene.makeMouseHelpers(970, 105)
|
||||
const profileEndMv = scene.makeMouseHelpers(951, 101)[1]
|
||||
const [newProfileEnd] = scene.makeMouseHelpers(764, 104)
|
||||
const dragSegmentTo = scene.makeMouseHelpers(850, 104)[1]
|
||||
|
||||
const rectHandle = scene.makeMouseHelpers(901, 150)[1]
|
||||
const rectDragTo = scene.makeMouseHelpers(901, 180)[1]
|
||||
|
||||
const circleEdge = scene.makeMouseHelpers(691, 331)[1]
|
||||
const dragCircleTo = scene.makeMouseHelpers(720, 331)[1]
|
||||
|
||||
const [rectStart] = scene.makeMouseHelpers(794, 322)
|
||||
const [rectEnd] = scene.makeMouseHelpers(757, 395)
|
||||
|
||||
await test2.step('enter sketch and setup', async () => {
|
||||
await pointOnSegment({ shouldDbClick: true })
|
||||
await app.page.waitForTimeout(600)
|
||||
|
||||
await toolbar.lineBtn.click()
|
||||
await app.page.waitForTimeout(100)
|
||||
})
|
||||
|
||||
await test2.step('extend existing profile', async () => {
|
||||
await profileEnd()
|
||||
await app.page.waitForTimeout(100)
|
||||
await newProfileEnd()
|
||||
await editor.expectEditor.toContain(`|> line([-11.4, 0.71], %)`)
|
||||
await toolbar.lineBtn.click()
|
||||
await app.page.waitForTimeout(100)
|
||||
})
|
||||
|
||||
await test2.step('edit existing profile', async () => {
|
||||
await profileEndMv()
|
||||
await app.page.mouse.down()
|
||||
await dragSegmentTo()
|
||||
await app.page.mouse.up()
|
||||
await editor.expectEditor.toContain(`line([4.16, -4.51], %)`)
|
||||
})
|
||||
|
||||
await test2.step('edit existing rect', async () => {
|
||||
await rectHandle()
|
||||
await app.page.mouse.down()
|
||||
await rectDragTo()
|
||||
await app.page.mouse.up()
|
||||
await editor.expectEditor.toContain(
|
||||
`angledLine([-7, 10.2], %, $rectangleSegmentA001)`
|
||||
)
|
||||
})
|
||||
|
||||
await test2.step('edit existing circl', async () => {
|
||||
await circleEdge()
|
||||
await app.page.mouse.down()
|
||||
await dragCircleTo()
|
||||
await app.page.mouse.up()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile003 = circle({ center = [6.92, -4.2], radius = 4.77 }, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
await test2.step('add new profile', async () => {
|
||||
await toolbar.rectangleBtn.click()
|
||||
await app.page.waitForTimeout(100)
|
||||
await rectStart()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile004 = startProfileAt([15.62, -3.83], sketch001)`
|
||||
)
|
||||
await app.page.waitForTimeout(100)
|
||||
await rectEnd()
|
||||
await editor.expectEditor
|
||||
.toContain(`|> angledLine([180, 1.97], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) + 90,
|
||||
3.88
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
}
|
||||
)
|
||||
test2(
|
||||
'Can delete a profile in the editor while is sketch mode, and sketch mode does not break, can ctrl+z to undo after constraint with variable was added',
|
||||
async ({ app, scene, toolbar, editor, cmdBar }) => {
|
||||
await app.initialise(`sketch001 = startSketchOn('XZ')
|
||||
profile001 = startProfileAt([6.24, 4.54], sketch001)
|
||||
|> line([-0.41, 6.99], %)
|
||||
|> line([8.61, 0.74], %)
|
||||
|> line([10.99, -5.22], %)
|
||||
profile002 = startProfileAt([11.19, 5.02], sketch001)
|
||||
|> angledLine([0, 10.78], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
4.14
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
profile003 = circle({ center = [6.92, -4.2], radius = 3.16 }, sketch001)
|
||||
`)
|
||||
|
||||
const [pointOnSegment] = scene.makeMouseHelpers(590, 141)
|
||||
const [segment1Click] = scene.makeMouseHelpers(616, 131)
|
||||
const sketchIsDrawnProperly = async () => {
|
||||
await test2.step(
|
||||
'check the sketch is still drawn properly',
|
||||
async () => {
|
||||
await app.page.waitForTimeout(200)
|
||||
await scene.expectPixelColor(
|
||||
[255, 255, 255],
|
||||
{ x: 617, y: 163 },
|
||||
15
|
||||
)
|
||||
await scene.expectPixelColor(
|
||||
[255, 255, 255],
|
||||
{ x: 629, y: 331 },
|
||||
15
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
await test2.step('enter sketch and setup', async () => {
|
||||
await pointOnSegment({ shouldDbClick: true })
|
||||
await app.page.waitForTimeout(600)
|
||||
|
||||
await toolbar.lineBtn.click()
|
||||
await app.page.waitForTimeout(100)
|
||||
})
|
||||
|
||||
await test2.step('select and delete code for a profile', async () => {})
|
||||
await app.page.getByText('close(%)').click()
|
||||
await app.page.keyboard.down('Shift')
|
||||
for (let i = 0; i < 11; i++) {
|
||||
await app.page.keyboard.press('ArrowUp')
|
||||
}
|
||||
await app.page.keyboard.press('Home')
|
||||
await app.page.keyboard.up('Shift')
|
||||
await app.page.keyboard.press('Backspace')
|
||||
|
||||
await sketchIsDrawnProperly()
|
||||
|
||||
await test2.step('add random new var between profiles', async () => {
|
||||
await app.page.keyboard.type('myVar = 5')
|
||||
await app.page.keyboard.press('Enter')
|
||||
await app.page.waitForTimeout(600)
|
||||
})
|
||||
|
||||
await sketchIsDrawnProperly()
|
||||
|
||||
await test2.step(
|
||||
'Adding a constraint with a variable, and than ctrl-z-ing which will remove the variable again does not break sketch mode',
|
||||
async () => {
|
||||
await expect(async () => {
|
||||
await segment1Click()
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: ['|>line([-0.41,6.99],%)'],
|
||||
highlightedCode: 'line([-0.41,6.99],%)',
|
||||
})
|
||||
}).toPass({ timeout: 5_000, intervals: [500] })
|
||||
|
||||
await toolbar.lengthConstraintBtn.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await editor.expectEditor.toContain('length001 = 7')
|
||||
|
||||
// wait for execute defer
|
||||
await app.page.waitForTimeout(600)
|
||||
await sketchIsDrawnProperly()
|
||||
|
||||
await app.page.keyboard.down('Meta')
|
||||
await app.page.keyboard.press('KeyZ')
|
||||
await app.page.keyboard.up('Meta')
|
||||
|
||||
await editor.expectEditor.not.toContain('length001 = 7')
|
||||
await sketchIsDrawnProperly()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test2(
|
||||
'can enter sketch when there is an extrude',
|
||||
async ({ app, scene, toolbar }) => {
|
||||
await app.initialise(`sketch001 = startSketchOn('XZ')
|
||||
profile001 = startProfileAt([-63.43, 193.08], sketch001)
|
||||
|> line([168.52, 149.87], %)
|
||||
|> line([190.29, -39.18], %)
|
||||
|> tangentialArcTo([319.63, 129.65], %)
|
||||
|> line([-217.65, -21.76], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
profile003 = startProfileAt([16.79, 38.24], sketch001)
|
||||
|> angledLine([0, 182.82], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
105.71
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
profile004 = circle({
|
||||
center = [280.45, 47.57],
|
||||
radius = 55.26
|
||||
}, sketch001)
|
||||
extrude002 = extrude(50, profile001)
|
||||
extrude001 = extrude(5, profile003)
|
||||
`)
|
||||
const [pointOnSegment] = scene.makeMouseHelpers(574, 207)
|
||||
|
||||
await pointOnSegment()
|
||||
await toolbar.editSketch()
|
||||
// wait for engine animation
|
||||
await app.page.waitForTimeout(600)
|
||||
|
||||
await test2.step('check the sketch is still drawn properly', async () => {
|
||||
await scene.expectPixelColor([255, 255, 255], { x: 591, y: 167 }, 15)
|
||||
await scene.expectPixelColor([255, 255, 255], { x: 638, y: 222 }, 15)
|
||||
await scene.expectPixelColor([255, 255, 255], { x: 756, y: 214 }, 15)
|
||||
})
|
||||
}
|
||||
)
|
||||
test2(
|
||||
'exit new sketch without drawing anything should not be a problem',
|
||||
async ({ app, scene, toolbar, editor, cmdBar }) => {
|
||||
await app.initialise(`myVar = 5`)
|
||||
const [selectXZPlane] = scene.makeMouseHelpers(650, 150)
|
||||
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
await selectXZPlane()
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await app.page.waitForTimeout(600)
|
||||
|
||||
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
|
||||
await toolbar.exitSketchBtn.click()
|
||||
|
||||
await editor.expectEditor.not.toContain(`sketch001 = startSketchOn('XZ')`)
|
||||
|
||||
await test2.step(
|
||||
"still renders code, hasn't got into a weird state",
|
||||
async () => {
|
||||
await editor.replaceCode(
|
||||
'myVar = 5',
|
||||
`myVar = 5
|
||||
sketch001 = startSketchOn('XZ')
|
||||
profile001 = circle({
|
||||
center = [12.41, 3.87],
|
||||
radius = myVar
|
||||
}, sketch001)`
|
||||
)
|
||||
|
||||
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
test2(
|
||||
'A sketch with only "startProfileAt" and no segments should still be able to be continued ',
|
||||
async ({ app, scene, toolbar, editor }) => {
|
||||
await app.initialise(`sketch001 = startSketchOn('XZ')
|
||||
profile001 = startProfileAt([85.19, 338.59], sketch001)
|
||||
|> line([213.3, -94.52], %)
|
||||
|> line([-230.09, -55.34], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch002 = startSketchOn('XY')
|
||||
profile002 = startProfileAt([85.81, 52.55], sketch002)
|
||||
|
||||
`)
|
||||
const [startProfileAt] = scene.makeMouseHelpers(606, 184)
|
||||
const [nextPoint] = scene.makeMouseHelpers(763, 130)
|
||||
await app.page
|
||||
.getByText('startProfileAt([85.81, 52.55], sketch002)')
|
||||
.click()
|
||||
await toolbar.editSketch()
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await app.page.waitForTimeout(600)
|
||||
|
||||
// equip line tool
|
||||
await toolbar.lineBtn.click()
|
||||
await app.page.waitForTimeout(100)
|
||||
await startProfileAt()
|
||||
await app.page.waitForTimeout(100)
|
||||
await nextPoint()
|
||||
await editor.expectEditor.toContain(`|> line([126.05, 44.12], %)`)
|
||||
}
|
||||
)
|
||||
test2(
|
||||
'old style sketch all in one pipe (with extrude) will break up to allow users to add a new profile to the same sketch',
|
||||
async ({ app, scene, toolbar, editor }) => {
|
||||
await app.initialise(`thePart = startSketchOn('XZ')
|
||||
|> startProfileAt([7.53, 10.51], %)
|
||||
|> line([12.54, 1.83], %)
|
||||
|> line([6.65, -6.91], %)
|
||||
|> line([-6.31, -8.69], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(75, thePart)
|
||||
`)
|
||||
const [objClick] = scene.makeMouseHelpers(565, 343)
|
||||
const [profilePoint1] = scene.makeMouseHelpers(609, 289)
|
||||
const [profilePoint2] = scene.makeMouseHelpers(714, 389)
|
||||
|
||||
await test2.step('enter sketch and setup', async () => {
|
||||
await objClick()
|
||||
await toolbar.editSketch()
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await app.page.waitForTimeout(600)
|
||||
})
|
||||
|
||||
await test2.step(
|
||||
'expect code to match initial conditions still',
|
||||
async () => {
|
||||
await editor.expectEditor.toContain(`thePart = startSketchOn('XZ')
|
||||
|> startProfileAt([7.53, 10.51], %)`)
|
||||
}
|
||||
)
|
||||
|
||||
await test2.step(
|
||||
'equiping the line tool should break up the pipe expression',
|
||||
async () => {
|
||||
await toolbar.lineBtn.click()
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')thePart = startProfileAt([7.53, 10.51], sketch001)`
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
await test2.step(
|
||||
'can continue on to add a new profile to this sketch',
|
||||
async () => {
|
||||
await profilePoint1()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile001 = startProfileAt([19.77, -7.08], sketch001)`
|
||||
)
|
||||
await profilePoint2()
|
||||
await editor.expectEditor.toContain(`|> line([19.05, -18.14], %)`)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -446,8 +446,7 @@ test(
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
code += `
|
||||
|> startProfileAt([7.19, -9.7], %)`
|
||||
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -469,6 +468,10 @@ test(
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
|
||||
// click to continue profile
|
||||
await page.mouse.move(813, 392, { steps: 10 })
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
@ -591,8 +594,7 @@ test(
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [14.44, -2.44], radius = 1 }, %)`
|
||||
`sketch001 = startSketchOn('XZ')profile001 = circle({ center = [14.44, -2.44], radius = 1 }, sketch001)`
|
||||
)
|
||||
}
|
||||
)
|
||||
@ -636,8 +638,7 @@ test.describe(
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
code += `
|
||||
|> startProfileAt([7.19, -9.7], %)`
|
||||
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -655,6 +656,10 @@ test.describe(
|
||||
.click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
await page.mouse.click(813, 392)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
|
||||
code += `
|
||||
@ -741,8 +746,7 @@ test.describe(
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
code += `
|
||||
|> startProfileAt([182.59, -246.32], %)`
|
||||
code += `profile001 = startProfileAt([182.59, -246.32], sketch001)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -760,6 +764,10 @@ test.describe(
|
||||
.click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
await page.mouse.click(813, 392)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
|
||||
code += `
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@ -1,6 +1,8 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { commonPoints, getUtils, setup, tearDown } from './test-utils'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
@ -130,17 +132,16 @@ test.describe('Test network and connection issues', () => {
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||
)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
// Expect the network to be up
|
||||
@ -188,7 +189,9 @@ test.describe('Test network and connection issues', () => {
|
||||
await page.mouse.click(100, 100)
|
||||
|
||||
// select a line
|
||||
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
|
||||
await page
|
||||
.getByText(`startProfileAt(${commonPoints.startAt}, sketch001)`)
|
||||
.click()
|
||||
|
||||
// enter sketch again
|
||||
await u.doAndWaitForCmd(
|
||||
@ -202,11 +205,36 @@ test.describe('Test network and connection issues', () => {
|
||||
|
||||
await page.waitForTimeout(150)
|
||||
|
||||
const camCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
center: { x: 109, y: 0, z: -152 },
|
||||
vantage: { x: 115, y: -505, z: -152 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
}
|
||||
const updateCamCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
}
|
||||
await u.sendCustomCmd(camCommand)
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd(updateCamCommand)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
await page.mouse.click(1007, 400)
|
||||
await page.waitForTimeout(100)
|
||||
// Ensure we can continue sketching
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||
|> xLine(12.34, %)
|
||||
|> line([-12.34, 12.34], %)
|
||||
|
||||
@ -216,7 +244,7 @@ test.describe('Test network and connection issues', () => {
|
||||
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||
|> xLine(12.34, %)
|
||||
|> line([-12.34, 12.34], %)
|
||||
|> xLine(-12.34, %)
|
||||
|
@ -17,11 +17,11 @@ test.describe('Testing constraints', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -77,30 +77,31 @@ test.describe('Testing selections', () => {
|
||||
const startXPx = 600
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
@ -330,6 +331,28 @@ part009 = startSketchOn('XY')
|
||||
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
||||
|> close(%)
|
||||
rev = revolve({ axis = 'y' }, part009)
|
||||
sketch006 = startSketchOn('XY')
|
||||
profile001 = circle({
|
||||
center = [42.91, -70.42],
|
||||
radius = 17.96
|
||||
}, sketch006)
|
||||
profile002 = startProfileAt([86.92, -63.81], sketch006)
|
||||
|> angledLine([0, 63.81], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
17.05
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
|> line([26.95, 24.21], %)
|
||||
|> line([20.91, -28.61], %)
|
||||
|> line([32.46, 18.71], %)
|
||||
|
||||
`
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
@ -362,9 +385,10 @@ rev = revolve({ axis = 'y' }, part009)
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const revolve = { x: 646, y: 248 }
|
||||
const revolve = { x: 635, y: 253 }
|
||||
const parentExtrude = { x: 915, y: 133 }
|
||||
const solid2d = { x: 770, y: 167 }
|
||||
const individualProfile = { x: 694, y: 432 }
|
||||
|
||||
// DELETE REVOLVE
|
||||
await page.mouse.click(revolve.x, revolve.y)
|
||||
@ -430,6 +454,20 @@ rev = revolve({ axis = 'y' }, part009)
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(u.codeLocator).not.toContainText(`sketch005 = startSketchOn({`)
|
||||
|
||||
// Delete a single profile
|
||||
await page.mouse.click(individualProfile.x, individualProfile.y)
|
||||
await page.waitForTimeout(100)
|
||||
const codeToBeDeletedSnippet =
|
||||
'profile003 = startProfileAt([40.16, -120.48], sketch006)'
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
' |> line([20.91, -28.61], %)'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
|
||||
})
|
||||
test("Deleting solid that the AST mod can't handle results in a toast message", async ({
|
||||
page,
|
||||
@ -1258,12 +1296,15 @@ extrude001 = extrude(50, sketch001)
|
||||
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
const firstClickCoords = { x: 650, y: 200 } as const
|
||||
// Place a point because the line tool will exit if no points are pressed
|
||||
await page.mouse.click(650, 200)
|
||||
await page.mouse.click(firstClickCoords.x, firstClickCoords.y)
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
// Code before exiting the tool
|
||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
let previousCodeContent = (
|
||||
await page.locator('.cm-content').innerText()
|
||||
).replace(/\s+/g, '')
|
||||
|
||||
// deselect the line tool by clicking it
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
@ -1275,14 +1316,23 @@ extrude001 = extrude(50, sketch001)
|
||||
await page.mouse.click(750, 200)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// expect no change
|
||||
await expect(page.locator('.cm-content')).toHaveText(previousCodeContent)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
let str = await page.locator('.cm-content').innerText()
|
||||
str = str.replace(/\s+/g, '')
|
||||
return str
|
||||
})
|
||||
.toBe(previousCodeContent)
|
||||
|
||||
// select line tool again
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click to continue profile
|
||||
await page.mouse.click(firstClickCoords.x, firstClickCoords.y)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// line tool should work as expected again
|
||||
await page.mouse.click(700, 200)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(
|
||||
|
@ -224,8 +224,13 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
||||
// Draw a line
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.move(800, 250, { steps: 5 })
|
||||
await page.mouse.click(800, 250)
|
||||
|
||||
const secondMousePosition = { x: 800, y: 250 }
|
||||
|
||||
await page.mouse.move(secondMousePosition.x, secondMousePosition.y, {
|
||||
steps: 5,
|
||||
})
|
||||
await page.mouse.click(secondMousePosition.x, secondMousePosition.y)
|
||||
// Unequip line tool
|
||||
await page.keyboard.press('Escape')
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
@ -234,9 +239,17 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
||||
// Equip arc tool
|
||||
await page.keyboard.press('a')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// click in the same position again to continue the profile
|
||||
await page.mouse.move(secondMousePosition.x, secondMousePosition.y, {
|
||||
steps: 5,
|
||||
})
|
||||
await page.mouse.click(secondMousePosition.x, secondMousePosition.y)
|
||||
|
||||
await page.mouse.move(1000, 100, { steps: 5 })
|
||||
await page.mouse.click(1000, 100)
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
@ -537,9 +550,9 @@ test('Sketch on face', async ({ page }) => {
|
||||
|
||||
await expect.poll(u.normalisedEditorCode).toContain(
|
||||
u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([-12.94, 6.6], %)
|
||||
|> line([2.45, -0.2], %)
|
||||
|> line([-2.6, -1.25], %)
|
||||
profile001 = startProfileAt([-12.88, 6.66], sketch002)
|
||||
|> line([2.71, -0.22], %)
|
||||
|> line([-2.87, -1.38], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`)
|
||||
)
|
||||
@ -554,8 +567,7 @@ test('Sketch on face', async ({ page }) => {
|
||||
await page.getByText('startProfileAt([-12').click()
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
await page.waitForTimeout(150)
|
||||
await page.waitForTimeout(500)
|
||||
await page.setViewportSize({ width: 1200, height: 1200 })
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.updateCamPosition([452, -152, 1166])
|
||||
|
@ -6,7 +6,6 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
@ -22,6 +21,7 @@ import {
|
||||
} from 'lib/toolbar'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { isCursorInFunctionDefinition } from 'lang/queryAst'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
@ -38,7 +38,12 @@ export function Toolbar({
|
||||
'!border-transparent hover:!border-chalkboard-20 dark:enabled:hover:!border-primary pressed:!border-primary ui-open:!border-primary'
|
||||
|
||||
const sketchPathId = useMemo(() => {
|
||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
|
||||
if (
|
||||
isCursorInFunctionDefinition(
|
||||
kclManager.ast,
|
||||
context.selectionRanges.graphSelections[0]
|
||||
)
|
||||
)
|
||||
return false
|
||||
return isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactGraph,
|
||||
|
@ -433,6 +433,8 @@ export async function deleteSegment({
|
||||
if (!sketchDetails) return
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
pathToNode,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
|
@ -691,19 +691,21 @@ export function createProfileStartHandle({
|
||||
scale = 1,
|
||||
theme,
|
||||
isSelected,
|
||||
size = 12,
|
||||
...rest
|
||||
}: {
|
||||
from: Coords2d
|
||||
scale?: number
|
||||
theme: Themes
|
||||
isSelected?: boolean
|
||||
size?: number
|
||||
} & (
|
||||
| { isDraft: true }
|
||||
| { isDraft: false; id: string; pathToNode: PathToNode }
|
||||
)) {
|
||||
const group = new Group()
|
||||
|
||||
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
|
||||
const geometry = new BoxGeometry(size, size, size) // in pixels scaled later
|
||||
const baseColor = getThemeColorForThreeJs(theme)
|
||||
const color = isSelected ? 0x0000ff : baseColor
|
||||
const body = new MeshBasicMaterial({ color })
|
||||
|
@ -24,7 +24,7 @@ import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import {
|
||||
isCursorInSketchCommandRange,
|
||||
updatePathToNodeFromMap,
|
||||
updateSketchDetailsNodePaths,
|
||||
} from 'lang/util'
|
||||
import {
|
||||
kclManager,
|
||||
@ -71,14 +71,24 @@ import {
|
||||
replaceValueAtNodePath,
|
||||
sketchOnExtrudedFace,
|
||||
sketchOnOffsetPlane,
|
||||
splitPipedProfile,
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||
import {
|
||||
PathToNode,
|
||||
Program,
|
||||
VariableDeclaration,
|
||||
parse,
|
||||
recast,
|
||||
resultIsOk,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
doesSceneHaveExtrudedSketch,
|
||||
doesSceneHaveSweepableSketch,
|
||||
getNodePathFromSourceRange,
|
||||
isSingleCursorInPipe,
|
||||
doesSketchPipeNeedSplitting,
|
||||
getNodeFromPath,
|
||||
isCursorInFunctionDefinition,
|
||||
traverse,
|
||||
} from 'lang/queryAst'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
@ -86,7 +96,7 @@ import toast from 'react-hot-toast'
|
||||
import { EditorSelection, Transaction } from '@codemirror/state'
|
||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import { err, reportRejection, trap } from 'lib/trap'
|
||||
import { err, reportRejection, trap, reject } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { modelingMachineEvent } from 'editor/manager'
|
||||
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addEdgeTreatment'
|
||||
@ -100,6 +110,10 @@ import { useFileContext } from 'hooks/useFileContext'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { IndexLoaderData } from 'lib/types'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import {
|
||||
getPathsFromArtifact,
|
||||
getPlaneFromArtifact,
|
||||
} from 'lang/std/artifactGraph'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -290,7 +304,7 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
sketchDetails: {
|
||||
...sketchDetails,
|
||||
sketchPathToNode: event.data,
|
||||
sketchEntryNodePath: event.data,
|
||||
},
|
||||
}
|
||||
}),
|
||||
@ -413,9 +427,17 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges: setSelections.selection,
|
||||
sketchDetails: {
|
||||
...sketchDetails,
|
||||
sketchPathToNode:
|
||||
setSelections.updatedPathToNode ||
|
||||
sketchDetails?.sketchPathToNode ||
|
||||
sketchEntryNodePath:
|
||||
setSelections.updatedSketchEntryNodePath ||
|
||||
sketchDetails?.sketchEntryNodePath ||
|
||||
[],
|
||||
sketchNodePaths:
|
||||
setSelections.updatedSketchNodePaths ||
|
||||
sketchDetails?.sketchNodePaths ||
|
||||
[],
|
||||
planeNodePath:
|
||||
setSelections.updatedPlaneNodePath ||
|
||||
sketchDetails?.planeNodePath ||
|
||||
[],
|
||||
},
|
||||
}
|
||||
@ -570,7 +592,6 @@ export const ModelingMachineProvider = ({
|
||||
// BUT only if there's extrudable geometry
|
||||
return doesSceneHaveSweepableSketch(kclManager.ast)
|
||||
}
|
||||
if (!isSketchPipe(selectionRanges)) return false
|
||||
|
||||
const canSweep = canSweepSelection(selectionRanges)
|
||||
if (err(canSweep)) return false
|
||||
@ -625,7 +646,6 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
|
||||
const canShell = canShellSelection(selectionRanges)
|
||||
console.log('canShellSelection', canShellSelection(selectionRanges))
|
||||
if (err(canShell)) return false
|
||||
return canShell
|
||||
},
|
||||
@ -648,7 +668,12 @@ export const ModelingMachineProvider = ({
|
||||
'Selection is on face': ({ context: { selectionRanges }, event }) => {
|
||||
if (event.type !== 'Enter sketch') return false
|
||||
if (event.data?.forceNewSketch) return false
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||
if (
|
||||
isCursorInFunctionDefinition(
|
||||
kclManager.ast,
|
||||
selectionRanges.graphSelections[0]
|
||||
)
|
||||
)
|
||||
return false
|
||||
return !!isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactGraph,
|
||||
@ -679,10 +704,32 @@ export const ModelingMachineProvider = ({
|
||||
// this assumes no changes have been made to the sketch besides what we did when entering the sketch
|
||||
// i.e. doesn't account for user's adding code themselves, maybe we need store a flag userEditedSinceSketchMode?
|
||||
const newAst = structuredClone(kclManager.ast)
|
||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||
const varDecIndex = sketchDetails.planeNodePath[1][0]
|
||||
|
||||
const varDec = getNodeFromPath<VariableDeclaration>(
|
||||
newAst,
|
||||
sketchDetails.planeNodePath,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(varDec)) return reject(new Error('No varDec'))
|
||||
const variableName = varDec.node.declaration.id.name
|
||||
let isIdentifierUsed = false
|
||||
traverse(newAst, {
|
||||
enter: (node) => {
|
||||
if (
|
||||
node.type === 'Identifier' &&
|
||||
node.name === variableName
|
||||
) {
|
||||
isIdentifierUsed = true
|
||||
}
|
||||
},
|
||||
})
|
||||
if (isIdentifierUsed) return
|
||||
|
||||
// remove body item at varDecIndex
|
||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||
await kclManager.executeAstMock(newAst)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(newAst)
|
||||
}
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: () => {},
|
||||
@ -692,7 +739,7 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
),
|
||||
'animate-to-face': fromPromise(async ({ input }) => {
|
||||
if (!input) return undefined
|
||||
if (!input) return null
|
||||
if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
|
||||
const sketched =
|
||||
input.type === 'extrudeFace'
|
||||
@ -719,7 +766,9 @@ export const ModelingMachineProvider = ({
|
||||
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
return {
|
||||
sketchPathToNode: pathToNewSketchNode,
|
||||
sketchEntryNodePath: [],
|
||||
planeNodePath: pathToNewSketchNode,
|
||||
sketchNodePaths: [],
|
||||
zAxis: input.zAxis,
|
||||
yAxis: input.yAxis,
|
||||
origin: input.position,
|
||||
@ -739,7 +788,9 @@ export const ModelingMachineProvider = ({
|
||||
)
|
||||
|
||||
return {
|
||||
sketchPathToNode: pathToNode,
|
||||
sketchEntryNodePath: [],
|
||||
planeNodePath: pathToNode,
|
||||
sketchNodePaths: [],
|
||||
zAxis: input.zAxis,
|
||||
yAxis: input.yAxis,
|
||||
origin: [0, 0, 0],
|
||||
@ -747,12 +798,14 @@ export const ModelingMachineProvider = ({
|
||||
}),
|
||||
'animate-to-sketch': fromPromise(
|
||||
async ({ input: { selectionRanges } }) => {
|
||||
const sourceRange =
|
||||
selectionRanges.graphSelections[0]?.codeRef?.range
|
||||
const sketchPathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRange
|
||||
const sketchPathToNode =
|
||||
selectionRanges.graphSelections[0]?.codeRef?.pathToNode
|
||||
const plane = getPlaneFromArtifact(
|
||||
selectionRanges.graphSelections[0].artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(plane)) return Promise.reject(plane)
|
||||
|
||||
const info = await getSketchOrientationDetails(
|
||||
sketchPathToNode || []
|
||||
)
|
||||
@ -760,8 +813,17 @@ export const ModelingMachineProvider = ({
|
||||
engineCommandManager,
|
||||
info?.sketchDetails?.faceId || ''
|
||||
)
|
||||
return {
|
||||
const sketchPaths = getPathsFromArtifact({
|
||||
artifact: selectionRanges.graphSelections[0].artifact,
|
||||
sketchPathToNode: sketchPathToNode || [],
|
||||
})
|
||||
if (err(sketchPaths)) return Promise.reject(sketchPaths)
|
||||
if (!plane.codeRef)
|
||||
return Promise.reject(new Error('No plane codeRef'))
|
||||
return {
|
||||
sketchEntryNodePath: sketchPathToNode || [],
|
||||
sketchNodePaths: sketchPaths,
|
||||
planeNodePath: plane.codeRef.pathToNode,
|
||||
zAxis: info.sketchDetails.zAxis || null,
|
||||
yAxis: info.sketchDetails.yAxis || null,
|
||||
origin: info.sketchDetails.origin.map(
|
||||
@ -773,7 +835,7 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
'Get horizontal info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintHorzVertDistance({
|
||||
constraint: 'setHorzDistance',
|
||||
selectionRanges,
|
||||
@ -785,13 +847,23 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -812,13 +884,15 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get vertical info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintHorzVertDistance({
|
||||
constraint: 'setVertDistance',
|
||||
selectionRanges,
|
||||
@ -829,13 +903,23 @@ export const ModelingMachineProvider = ({
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -856,7 +940,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -866,14 +952,15 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges,
|
||||
})
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const { modifiedAst, pathToNodeMap } = await (info.enabled
|
||||
? applyConstraintAngleBetween({
|
||||
selectionRanges,
|
||||
})
|
||||
: applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
angleOrLength: 'setAngle',
|
||||
}))
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await (info.enabled
|
||||
? applyConstraintAngleBetween({
|
||||
selectionRanges,
|
||||
})
|
||||
: applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
angleOrLength: 'setAngle',
|
||||
}))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
@ -882,13 +969,23 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -909,7 +1006,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -924,20 +1023,30 @@ export const ModelingMachineProvider = ({
|
||||
length: lengthValue,
|
||||
})
|
||||
if (err(constraintResult)) return Promise.reject(constraintResult)
|
||||
const { modifiedAst, pathToNodeMap } = constraintResult
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
constraintResult
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -958,13 +1067,15 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get perpendicular distance info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintIntersect({
|
||||
selectionRanges,
|
||||
})
|
||||
@ -974,13 +1085,22 @@ export const ModelingMachineProvider = ({
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -1001,13 +1121,15 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get ABS X info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintAbsDistance({
|
||||
constraint: 'xAbs',
|
||||
selectionRanges,
|
||||
@ -1018,13 +1140,22 @@ export const ModelingMachineProvider = ({
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -1045,13 +1176,15 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get ABS Y info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintAbsDistance({
|
||||
constraint: 'yAbs',
|
||||
selectionRanges,
|
||||
@ -1062,13 +1195,22 @@ export const ModelingMachineProvider = ({
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -1089,7 +1231,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -1109,9 +1253,11 @@ export const ModelingMachineProvider = ({
|
||||
let result: {
|
||||
modifiedAst: Node<Program>
|
||||
pathToReplaced: PathToNode | null
|
||||
exprInsertIndex: number
|
||||
} = {
|
||||
modifiedAst: parsed,
|
||||
pathToReplaced: null,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
// If the user provided a constant name,
|
||||
// we need to insert the named constant
|
||||
@ -1141,6 +1287,7 @@ export const ModelingMachineProvider = ({
|
||||
result = {
|
||||
modifiedAst: parseResultAfterInsertion.program,
|
||||
pathToReplaced: astAfterReplacement.pathToReplaced,
|
||||
exprInsertIndex: astAfterReplacement.exprInsertIndex,
|
||||
}
|
||||
} else if ('valueText' in data.namedValue) {
|
||||
// If they didn't provide a constant name,
|
||||
@ -1171,10 +1318,22 @@ export const ModelingMachineProvider = ({
|
||||
parsed = parsed as Node<Program>
|
||||
if (!result.pathToReplaced)
|
||||
return Promise.reject(new Error('No path to replaced node'))
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex: result.exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
result.pathToReplaced || [],
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
parsed,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -1195,7 +1354,140 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode: result.pathToReplaced,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'set-up-draft-circle': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
|
||||
const result = await sceneEntitiesManager.setupDraftCircle(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'set-up-draft-rectangle': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
|
||||
const result = await sceneEntitiesManager.setupDraftRectangle(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'set-up-draft-center-rectangle': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
const result = await sceneEntitiesManager.setupDraftCenterRectangle(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'setup-client-side-sketch-segments': fromPromise(
|
||||
async ({ input: { sketchDetails, selectionRanges } }) => {
|
||||
if (!sketchDetails) return
|
||||
if (!sketchDetails.sketchEntryNodePath.length) return
|
||||
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
}
|
||||
sceneInfra.resetMouseListeners()
|
||||
await sceneEntitiesManager.setupSketch({
|
||||
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
forward: sketchDetails.zAxis,
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
maybeModdedAst: kclManager.ast,
|
||||
selectionRanges,
|
||||
})
|
||||
sceneInfra.resetMouseListeners()
|
||||
|
||||
sceneEntitiesManager.setupSketchIdleCallbacks({
|
||||
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
||||
forward: sketchDetails.zAxis,
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
),
|
||||
'split-sketch-pipe-if-needed': fromPromise(
|
||||
async ({ input: { sketchDetails } }) => {
|
||||
if (!sketchDetails) return reject('No sketch details')
|
||||
const existingSketchInfoNoOp = {
|
||||
updatedEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
updatedSketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
updatedPlaneNodePath: sketchDetails.planeNodePath,
|
||||
} as const
|
||||
if (
|
||||
!sketchDetails.sketchNodePaths.length &&
|
||||
sketchDetails.planeNodePath.length
|
||||
) {
|
||||
// new sketch, no profiles yet
|
||||
return existingSketchInfoNoOp
|
||||
}
|
||||
const doesNeedSplitting = doesSketchPipeNeedSplitting(
|
||||
kclManager.ast,
|
||||
sketchDetails.sketchEntryNodePath
|
||||
)
|
||||
if (err(doesNeedSplitting)) return reject(doesNeedSplitting)
|
||||
if (!doesNeedSplitting) return existingSketchInfoNoOp
|
||||
|
||||
const splitResult = splitPipedProfile(
|
||||
kclManager.ast,
|
||||
sketchDetails.sketchEntryNodePath
|
||||
)
|
||||
if (err(splitResult)) return reject(splitResult)
|
||||
|
||||
await kclManager.executeAstMock(splitResult.modifiedAst)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||
splitResult.modifiedAst
|
||||
)
|
||||
return {
|
||||
updatedEntryNodePath: splitResult.pathToProfile,
|
||||
updatedSketchNodePaths: [splitResult.pathToProfile],
|
||||
updatedPlaneNodePath: sketchDetails.planeNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
|
@ -136,6 +136,7 @@ export async function applyConstraintIntersect({
|
||||
}): Promise<{
|
||||
modifiedAst: Node<Program>
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const info = intersectInfo({
|
||||
selectionRanges,
|
||||
@ -174,6 +175,7 @@ export async function applyConstraintIntersect({
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
}
|
||||
// transform again but forcing certain values
|
||||
@ -192,6 +194,7 @@ export async function applyConstraintIntersect({
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||
transform2
|
||||
|
||||
let exprInsertIndex = -1
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -204,9 +207,11 @@ export async function applyConstraintIntersect({
|
||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||
})
|
||||
exprInsertIndex = newVariableInsertIndex
|
||||
}
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap: _pathToNodeMap,
|
||||
exprInsertIndex,
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export function removeConstrainingValuesInfo({
|
||||
| Error {
|
||||
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
|
||||
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
if (tmp instanceof Error) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
|
@ -93,6 +93,7 @@ export async function applyConstraintAbsDistance({
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const info = absDistanceInfo({
|
||||
selectionRanges,
|
||||
@ -132,6 +133,7 @@ export async function applyConstraintAbsDistance({
|
||||
if (err(transform2)) return Promise.reject(transform2)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transform2
|
||||
|
||||
let exprInsertIndex = -1
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -144,8 +146,9 @@ export async function applyConstraintAbsDistance({
|
||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||
})
|
||||
exprInsertIndex = newVariableInsertIndex
|
||||
}
|
||||
return { modifiedAst: _modifiedAst, pathToNodeMap }
|
||||
return { modifiedAst: _modifiedAst, pathToNodeMap, exprInsertIndex }
|
||||
}
|
||||
|
||||
export function applyConstraintAxisAlign({
|
||||
|
@ -86,6 +86,7 @@ export async function applyConstraintAngleBetween({
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const info = angleBetweenInfo({ selectionRanges })
|
||||
if (err(info)) return Promise.reject(info)
|
||||
@ -122,6 +123,7 @@ export async function applyConstraintAngleBetween({
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +143,7 @@ export async function applyConstraintAngleBetween({
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||
transformed2
|
||||
|
||||
let exprInsertIndex = -1
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -153,9 +156,11 @@ export async function applyConstraintAngleBetween({
|
||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||
})
|
||||
exprInsertIndex = newVariableInsertIndex
|
||||
}
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap: _pathToNodeMap,
|
||||
exprInsertIndex,
|
||||
}
|
||||
}
|
||||
|
@ -87,15 +87,13 @@ export function horzVertDistanceInfo({
|
||||
export async function applyConstraintHorzVertDistance({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
|
||||
isAlign = false,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||
isAlign?: false
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const info = horzVertDistanceInfo({
|
||||
selectionRanges: selectionRanges,
|
||||
@ -133,13 +131,12 @@ export async function applyConstraintHorzVertDistance({
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
} else {
|
||||
if (!isExprBinaryPart(valueNode))
|
||||
return Promise.reject('Invalid valueNode, is not a BinaryPart')
|
||||
let finalValue = isAlign
|
||||
? createLiteral(0)
|
||||
: removeDoubleNegatives(valueNode, sign, variableName)
|
||||
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
|
||||
// transform again but forcing certain values
|
||||
const transformed = transformSecondarySketchLinesTagFirst({
|
||||
ast: kclManager.ast,
|
||||
@ -152,6 +149,7 @@ export async function applyConstraintHorzVertDistance({
|
||||
|
||||
if (err(transformed)) return Promise.reject(transformed)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transformed
|
||||
let exprInsertIndex = -1
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -164,10 +162,12 @@ export async function applyConstraintHorzVertDistance({
|
||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||
})
|
||||
exprInsertIndex = newVariableInsertIndex
|
||||
}
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,10 +70,14 @@ export async function applyConstraintLength({
|
||||
}: {
|
||||
length: KclCommandValue
|
||||
selectionRanges: Selections
|
||||
}) {
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const ast = kclManager.ast
|
||||
const angleLength = angleLengthInfo({ selectionRanges })
|
||||
if (err(angleLength)) return angleLength
|
||||
if (err(angleLength)) return Promise.reject(angleLength)
|
||||
const { transforms } = angleLength
|
||||
|
||||
let distanceExpression: Expr = length.valueAst
|
||||
@ -94,7 +98,7 @@ export async function applyConstraintLength({
|
||||
}
|
||||
|
||||
if (!isExprBinaryPart(distanceExpression)) {
|
||||
return new Error('Invalid valueNode, is not a BinaryPart')
|
||||
return Promise.reject('Invalid valueNode, is not a BinaryPart')
|
||||
}
|
||||
|
||||
const retval = transformAstSketchLines({
|
||||
@ -112,6 +116,12 @@ export async function applyConstraintLength({
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex:
|
||||
'variableName' in length &&
|
||||
length.variableName &&
|
||||
length.insertIndex !== undefined
|
||||
? length.insertIndex
|
||||
: -1,
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +134,7 @@ export async function applyConstraintAngleLength({
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const angleLength = angleLengthInfo({ selectionRanges, angleOrLength })
|
||||
if (err(angleLength)) return Promise.reject(angleLength)
|
||||
@ -208,5 +219,6 @@ export async function applyConstraintAngleLength({
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex: variableName ? newVariableInsertIndex : -1,
|
||||
}
|
||||
}
|
||||
|
@ -311,8 +311,6 @@ export class KclManager {
|
||||
// Do not send send scene commands if the program was interrupted, go to clean up
|
||||
if (!isInterrupted) {
|
||||
this.addDiagnostics(await lintAst({ ast: ast }))
|
||||
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
||||
|
||||
if (args.zoomToFit) {
|
||||
@ -358,7 +356,11 @@ export class KclManager {
|
||||
this.lastSuccessfulProgramMemory = execState.memory
|
||||
}
|
||||
this.ast = { ...ast }
|
||||
// updateArtifactGraph relies on updated executeState/programMemory
|
||||
await this.engineCommandManager.updateArtifactGraph(this.ast)
|
||||
this._executeCallback()
|
||||
if (!isInterrupted)
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
this.engineCommandManager.addCommandLog({
|
||||
type: 'execution-done',
|
||||
data: null,
|
||||
@ -400,6 +402,7 @@ export class KclManager {
|
||||
|
||||
this._logs = logs
|
||||
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
||||
|
||||
this._execState = execState
|
||||
this._programMemory = execState.memory
|
||||
if (!errors.length) {
|
||||
@ -411,7 +414,7 @@ export class KclManager {
|
||||
// problem this solves, but either way we should strive to remove it.
|
||||
Array.from(this.engineCommandManager.artifactGraph).forEach(
|
||||
([commandId, artifact]) => {
|
||||
if (!('codeRef' in artifact)) return
|
||||
if (!('codeRef' in artifact && artifact.codeRef)) return
|
||||
const _node1 = getNodeFromPath<Node<CallExpression>>(
|
||||
this.ast,
|
||||
artifact.codeRef.pathToNode,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {
|
||||
Program,
|
||||
_executor,
|
||||
executor,
|
||||
ProgramMemory,
|
||||
kclLint,
|
||||
emptyExecState,
|
||||
@ -64,12 +64,9 @@ export async function executeAst({
|
||||
try {
|
||||
const execState = await (programMemoryOverride
|
||||
? enginelessExecutor(ast, programMemoryOverride)
|
||||
: _executor(ast, engineCommandManager))
|
||||
|
||||
await engineCommandManager.waitForAllCommands(
|
||||
programMemoryOverride !== undefined
|
||||
)
|
||||
: executor(ast, engineCommandManager))
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
deleteSegmentFromPipeExpression,
|
||||
removeSingleConstraintInfo,
|
||||
deleteFromSelection,
|
||||
splitPipedProfile,
|
||||
} from './modifyAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
|
||||
@ -918,3 +919,63 @@ sketch002 = startSketchOn({
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('Testing splitPipedProfile', () => {
|
||||
it('should split the pipe expression correctly', () => {
|
||||
const codeBefore = `part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([1, 2], %)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
`
|
||||
|
||||
const expectedCodeAfter = `sketch001 = startSketchOn('XZ')
|
||||
part001 = startProfileAt([1, 2], sketch001)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
`
|
||||
|
||||
const ast = assertParse(codeBefore)
|
||||
|
||||
const codeOfInterest = `startSketchOn('XZ')`
|
||||
const range: [number, number, boolean] = [
|
||||
codeBefore.indexOf(codeOfInterest),
|
||||
codeBefore.indexOf(codeOfInterest) + codeOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const pathToPipe = getNodePathFromSourceRange(ast, range)
|
||||
|
||||
const result = splitPipedProfile(ast, pathToPipe)
|
||||
|
||||
if (err(result)) throw result
|
||||
|
||||
const newCode = recast(result.modifiedAst)
|
||||
if (err(newCode)) throw newCode
|
||||
expect(newCode.trim()).toBe(expectedCodeAfter.trim())
|
||||
})
|
||||
it('should return error for already split pipe', () => {
|
||||
const codeBefore = `sketch001 = startSketchOn('XZ')
|
||||
part001 = startProfileAt([1, 2], sketch001)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
`
|
||||
|
||||
const ast = assertParse(codeBefore)
|
||||
|
||||
const codeOfInterest = `startProfileAt([1, 2], sketch001)`
|
||||
const range: [number, number, boolean] = [
|
||||
codeBefore.indexOf(codeOfInterest),
|
||||
codeBefore.indexOf(codeOfInterest) + codeOfInterest.length,
|
||||
true,
|
||||
]
|
||||
const pathToPipe = getNodePathFromSourceRange(ast, range)
|
||||
|
||||
const result = splitPipedProfile(ast, pathToPipe)
|
||||
expect(result instanceof Error).toBe(true)
|
||||
})
|
||||
})
|
||||
|
@ -29,6 +29,8 @@ import {
|
||||
getNodePathFromSourceRange,
|
||||
isNodeSafeToReplace,
|
||||
traverse,
|
||||
getBodyIndex,
|
||||
isCallExprWithName,
|
||||
} from './queryAst'
|
||||
import { addTagForSketchOnFace, getConstraintInfo } from './std/sketch'
|
||||
import {
|
||||
@ -46,6 +48,7 @@ import { Models } from '@kittycad/lib'
|
||||
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||
import { Artifact, getPathsFromArtifact } from './std/artifactGraph'
|
||||
|
||||
export function startSketchOnDefault(
|
||||
node: Node<Program>,
|
||||
@ -78,41 +81,54 @@ export function startSketchOnDefault(
|
||||
}
|
||||
}
|
||||
|
||||
export function addStartProfileAt(
|
||||
export function insertNewStartProfileAt(
|
||||
node: Node<Program>,
|
||||
pathToNode: PathToNode,
|
||||
at: [number, number]
|
||||
): { modifiedAst: Node<Program>; pathToNode: PathToNode } | Error {
|
||||
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||
sketchEntryNodePath: PathToNode,
|
||||
sketchNodePaths: PathToNode[],
|
||||
planeNodePath: PathToNode,
|
||||
at: [number, number],
|
||||
insertType: 'start' | 'end' = 'end'
|
||||
):
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
updatedSketchNodePaths: PathToNode[]
|
||||
updatedEntryNodePath: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
node,
|
||||
pathToNode,
|
||||
'VariableDeclaration'
|
||||
planeNodePath,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(_node1)) return _node1
|
||||
const variableDeclaration = _node1.node
|
||||
if (variableDeclaration.type !== 'VariableDeclaration') {
|
||||
return new Error('variableDeclaration.init.type !== PipeExpression')
|
||||
}
|
||||
const _node = { ...node }
|
||||
const init = variableDeclaration.declaration.init
|
||||
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||
createArrayExpression([
|
||||
createLiteral(roundOff(at[0])),
|
||||
createLiteral(roundOff(at[1])),
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
if (init.type === 'PipeExpression') {
|
||||
init.body.splice(1, 0, startProfileAt)
|
||||
} else {
|
||||
variableDeclaration.declaration.init = createPipeExpression([
|
||||
init,
|
||||
startProfileAt,
|
||||
if (err(varDec)) return varDec
|
||||
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
||||
|
||||
const newExpression = createVariableDeclaration(
|
||||
findUniqueName(node, 'profile'),
|
||||
createCallExpressionStdLib('startProfileAt', [
|
||||
createArrayExpression([
|
||||
createLiteral(roundOff(at[0])),
|
||||
createLiteral(roundOff(at[1])),
|
||||
]),
|
||||
createIdentifier(varDec.node.id.name),
|
||||
])
|
||||
}
|
||||
)
|
||||
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, insertType)
|
||||
|
||||
const _node = structuredClone(node)
|
||||
// TODO the rest of this function will not be robust to work for sketches defined within a function declaration
|
||||
_node.body.splice(insertIndex, 0, newExpression)
|
||||
|
||||
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
||||
updateSketchNodePathsWithInsertIndex({
|
||||
insertIndex,
|
||||
insertType,
|
||||
sketchNodePaths,
|
||||
})
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
updatedSketchNodePaths,
|
||||
updatedEntryNodePath,
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,7 +269,7 @@ export function mutateObjExpProp(
|
||||
export function extrudeSketch(
|
||||
node: Node<Program>,
|
||||
pathToNode: PathToNode,
|
||||
shouldPipe = false,
|
||||
artifact?: Artifact,
|
||||
distance: Expr = createLiteral(4)
|
||||
):
|
||||
| {
|
||||
@ -262,10 +278,14 @@ export function extrudeSketch(
|
||||
pathToExtrudeArg: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const orderedSketchNodePaths = getPathsFromArtifact({
|
||||
artifact: artifact,
|
||||
sketchPathToNode: pathToNode,
|
||||
})
|
||||
if (err(orderedSketchNodePaths)) return orderedSketchNodePaths
|
||||
const _node = structuredClone(node)
|
||||
const _node1 = getNodeFromPath(_node, pathToNode)
|
||||
if (err(_node1)) return _node1
|
||||
const { node: sketchExpression } = _node1
|
||||
|
||||
// determine if sketchExpression is in a pipeExpression or not
|
||||
const _node2 = getNodeFromPath<PipeExpression>(
|
||||
@ -274,9 +294,6 @@ export function extrudeSketch(
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(_node2)) return _node2
|
||||
const { node: pipeExpression } = _node2
|
||||
|
||||
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
|
||||
|
||||
const _node3 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
@ -284,49 +301,23 @@ export function extrudeSketch(
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(_node3)) return _node3
|
||||
const { node: variableDeclarator, shallowPath: pathToDecleration } = _node3
|
||||
const { node: variableDeclarator } = _node3
|
||||
|
||||
const extrudeCall = createCallExpressionStdLib('extrude', [
|
||||
distance,
|
||||
shouldPipe
|
||||
? createPipeSubstitution()
|
||||
: createIdentifier(variableDeclarator.id.name),
|
||||
createIdentifier(variableDeclarator.id.name),
|
||||
])
|
||||
|
||||
if (shouldPipe) {
|
||||
const pipeChain = createPipeExpression(
|
||||
isInPipeExpression
|
||||
? [...pipeExpression.body, extrudeCall]
|
||||
: [sketchExpression as any, extrudeCall]
|
||||
)
|
||||
|
||||
variableDeclarator.init = pipeChain
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
...pathToDecleration,
|
||||
['init', 'VariableDeclarator'],
|
||||
['body', ''],
|
||||
[pipeChain.body.length - 1, 'index'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
pathToExtrudeArg,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 sketchIndexInPathToNode =
|
||||
pathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
||||
const sketchIndexInBody = pathToDecleration[
|
||||
sketchIndexInPathToNode
|
||||
][0] as number
|
||||
const lastSketchNodePath =
|
||||
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
|
||||
|
||||
console.log('lastSketchNodePath', lastSketchNodePath, orderedSketchNodePaths)
|
||||
const sketchIndexInBody = Number(lastSketchNodePath[1][0])
|
||||
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
@ -1295,7 +1286,8 @@ export async function deleteFromSelection(
|
||||
const pipeBody = varDec.node.init.body
|
||||
if (
|
||||
pipeBody[0].type === 'CallExpression' &&
|
||||
pipeBody[0].callee.name === 'startSketchOn'
|
||||
(pipeBody[0].callee.name === 'startSketchOn' ||
|
||||
pipeBody[0].callee.name === 'startProfileAt')
|
||||
) {
|
||||
// remove varDec
|
||||
const varDecIndex = varDec.shallowPath[1][0] as number
|
||||
@ -1310,3 +1302,149 @@ export async function deleteFromSelection(
|
||||
const nonCodeMetaEmpty = () => {
|
||||
return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 }
|
||||
}
|
||||
|
||||
export function getInsertIndex(
|
||||
sketchNodePaths: PathToNode[],
|
||||
planeNodePath: PathToNode,
|
||||
insertType: 'start' | 'end'
|
||||
) {
|
||||
let minIndex = 0
|
||||
let maxIndex = 0
|
||||
for (const path of sketchNodePaths) {
|
||||
const index = Number(path[1][0])
|
||||
if (index < minIndex) minIndex = index
|
||||
if (index > maxIndex) maxIndex = index
|
||||
}
|
||||
|
||||
const insertIndex = !sketchNodePaths.length
|
||||
? Number(planeNodePath[1][0]) + 1
|
||||
: insertType === 'start'
|
||||
? minIndex
|
||||
: maxIndex + 1
|
||||
return insertIndex
|
||||
}
|
||||
|
||||
export function updateSketchNodePathsWithInsertIndex({
|
||||
insertIndex,
|
||||
insertType,
|
||||
sketchNodePaths,
|
||||
}: {
|
||||
insertIndex: number
|
||||
insertType: 'start' | 'end'
|
||||
sketchNodePaths: PathToNode[]
|
||||
}): {
|
||||
updatedEntryNodePath: PathToNode
|
||||
updatedSketchNodePaths: PathToNode[]
|
||||
} {
|
||||
// TODO the rest of this function will not be robust to work for sketches defined within a function declaration
|
||||
const newExpressionPathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[insertIndex, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
let updatedSketchNodePaths = structuredClone(sketchNodePaths)
|
||||
if (insertType === 'start') {
|
||||
updatedSketchNodePaths = updatedSketchNodePaths.map((path) => {
|
||||
path[1][0] = Number(path[1][0]) + 1
|
||||
return path
|
||||
})
|
||||
updatedSketchNodePaths.unshift(newExpressionPathToNode)
|
||||
} else {
|
||||
updatedSketchNodePaths.push(newExpressionPathToNode)
|
||||
}
|
||||
return {
|
||||
updatedSketchNodePaths,
|
||||
updatedEntryNodePath: newExpressionPathToNode,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Split the following pipe expression into
|
||||
* ```ts
|
||||
* part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([1, 2], %)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
```
|
||||
into
|
||||
```ts
|
||||
sketch001 = startSketchOn('XZ')
|
||||
part001 = startProfileAt([1, 2], sketch001)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
```
|
||||
Notice that the `startSketchOn` is what gets the new variable name, this is so part001 still has the same data as before
|
||||
making it safe for later code that uses part001 (the extrude in this example)
|
||||
*
|
||||
*/
|
||||
export function splitPipedProfile(
|
||||
ast: Program,
|
||||
pathToPipe: PathToNode
|
||||
):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToProfile: PathToNode
|
||||
pathToPlane: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const _ast = structuredClone(ast)
|
||||
const varDec = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
pathToPipe,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(varDec)) return varDec
|
||||
if (
|
||||
varDec.node.type !== 'VariableDeclaration' ||
|
||||
varDec.node.declaration.init.type !== 'PipeExpression'
|
||||
) {
|
||||
return new Error('pathToNode does not point to pipe')
|
||||
}
|
||||
const init = varDec.node.declaration.init
|
||||
const firstCall = init.body[0]
|
||||
if (!isCallExprWithName(firstCall, 'startSketchOn'))
|
||||
return new Error('First call is not startSketchOn')
|
||||
const secondCall = init.body[1]
|
||||
if (!isCallExprWithName(secondCall, 'startProfileAt'))
|
||||
return new Error('Second call is not startProfileAt')
|
||||
|
||||
const varName = varDec.node.declaration.id.name
|
||||
const newVarName = findUniqueName(_ast, 'sketch')
|
||||
const secondCallArgs = structuredClone(secondCall.arguments)
|
||||
secondCallArgs[1] = createIdentifier(newVarName)
|
||||
const firstCallOfNewPipe = createCallExpression(
|
||||
'startProfileAt',
|
||||
secondCallArgs
|
||||
)
|
||||
const newSketch = createVariableDeclaration(
|
||||
newVarName,
|
||||
varDec.node.declaration.init.body[0]
|
||||
)
|
||||
const newProfile = createVariableDeclaration(
|
||||
varName,
|
||||
varDec.node.declaration.init.body.length <= 2
|
||||
? firstCallOfNewPipe
|
||||
: createPipeExpression([
|
||||
firstCallOfNewPipe,
|
||||
...varDec.node.declaration.init.body.slice(2),
|
||||
])
|
||||
)
|
||||
const index = getBodyIndex(pathToPipe)
|
||||
if (err(index)) return index
|
||||
_ast.body.splice(index, 1, newSketch, newProfile)
|
||||
const pathToPlane = structuredClone(pathToPipe)
|
||||
const pathToProfile = structuredClone(pathToPipe)
|
||||
pathToProfile[1][0] = index + 1
|
||||
|
||||
return {
|
||||
modifiedAst: _ast,
|
||||
pathToProfile,
|
||||
pathToPlane,
|
||||
}
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
||||
const selection: Selections = {
|
||||
graphSelections: segmentRanges.map((segmentRange) => {
|
||||
const maybeArtifact = [...artifactGraph].find(([, a]) => {
|
||||
if (!('codeRef' in a)) return false
|
||||
if (!('codeRef' in a && a.codeRef)) return false
|
||||
return isOverlap(a.codeRef.range, segmentRange)
|
||||
})
|
||||
return {
|
||||
@ -487,8 +487,8 @@ extrude001 = extrude(-15, sketch001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(-15, sketch001)
|
||||
|> chamfer({ length: 5, tags: [seg01] }, %)
|
||||
|> ${edgeTreatmentType}({ ${parameterName}: 3, tags: [seg02] }, %)`
|
||||
|> chamfer({ length = 5, tags = [seg01] }, %)
|
||||
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
|
||||
|
||||
await runModifyAstCloneWithEdgeTreatmentAndTag(
|
||||
code,
|
||||
|
@ -33,6 +33,7 @@ import { err, Reason } from 'lib/trap'
|
||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { ArtifactGraph, codeRefFromRange } from './std/artifactGraph'
|
||||
import { FunctionExpression } from 'wasm-lib/kcl/bindings/FunctionExpression'
|
||||
|
||||
/**
|
||||
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
||||
@ -596,7 +597,13 @@ export function findAllPreviousVariables(
|
||||
type ReplacerFn = (
|
||||
_ast: Node<Program>,
|
||||
varName: string
|
||||
) => { modifiedAst: Node<Program>; pathToReplaced: PathToNode } | Error
|
||||
) =>
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
pathToReplaced: PathToNode
|
||||
exprInsertIndex: number
|
||||
}
|
||||
| Error
|
||||
|
||||
export function isNodeSafeToReplacePath(
|
||||
ast: Program,
|
||||
@ -648,7 +655,7 @@ export function isNodeSafeToReplacePath(
|
||||
if (err(_nodeToReplace)) return _nodeToReplace
|
||||
const nodeToReplace = _nodeToReplace.node as any
|
||||
nodeToReplace[last[0]] = identifier
|
||||
return { modifiedAst: _ast, pathToReplaced }
|
||||
return { modifiedAst: _ast, pathToReplaced, exprInsertIndex: index }
|
||||
}
|
||||
|
||||
const hasPipeSub = isTypeInValue(finVal as Expr, 'PipeSubstitution')
|
||||
@ -767,8 +774,15 @@ export function isLinesParallelAndConstrained(
|
||||
if (err(_primarySegment)) return _primarySegment
|
||||
const primarySegment = _primarySegment.segment
|
||||
|
||||
const _varDec2 = getNodeFromPath(ast, secondaryPath, 'VariableDeclaration')
|
||||
if (err(_varDec2)) return _varDec2
|
||||
const varDec2 = _varDec2.node
|
||||
const varName2 = (varDec2 as VariableDeclaration)?.declaration.id?.name
|
||||
const sg2 = sketchFromKclValue(programMemory?.get(varName2), varName2)
|
||||
if (err(sg2)) return sg2
|
||||
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
sg,
|
||||
sg2,
|
||||
secondaryLine?.codeRef?.range
|
||||
)
|
||||
if (err(_segment)) return _segment
|
||||
@ -1044,13 +1058,15 @@ export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
|
||||
hasCircle = true
|
||||
}
|
||||
}
|
||||
if (
|
||||
(hasStartProfileAt || hasCircle) &&
|
||||
hasStartSketchOn &&
|
||||
(hasClose || hasCircle)
|
||||
) {
|
||||
if ((hasStartProfileAt && hasClose) || hasCircle) {
|
||||
theMap[node.id.name] = true
|
||||
}
|
||||
} else if (
|
||||
node.type === 'VariableDeclaration' &&
|
||||
node.declaration.init.type === 'CallExpression' &&
|
||||
node.declaration.init.callee.name === 'circle'
|
||||
) {
|
||||
theMap[node.declaration.id.name] = true
|
||||
} else if (
|
||||
node.type === 'CallExpression' &&
|
||||
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
|
||||
@ -1101,3 +1117,57 @@ export function getObjExprProperty(
|
||||
if (index === -1) return null
|
||||
return { expr: node.properties[index].value, index }
|
||||
}
|
||||
|
||||
export function isCursorInFunctionDefinition(
|
||||
ast: Node<Program>,
|
||||
selectionRanges: Selection
|
||||
): boolean {
|
||||
if (!selectionRanges?.codeRef?.pathToNode) return false
|
||||
const node = getNodeFromPath<FunctionExpression>(
|
||||
ast,
|
||||
selectionRanges.codeRef.pathToNode,
|
||||
'FunctionExpression'
|
||||
)
|
||||
if (err(node)) return false
|
||||
if (node.node.type === 'FunctionExpression') return true
|
||||
return false
|
||||
}
|
||||
|
||||
export function getBodyIndex(pathToNode: PathToNode): number | Error {
|
||||
const index = Number(pathToNode[1][0])
|
||||
if (Number.isInteger(index)) return index
|
||||
return new Error('Expected number index')
|
||||
}
|
||||
|
||||
export function isCallExprWithName(
|
||||
expr: Expr | CallExpression,
|
||||
name: string
|
||||
): expr is CallExpression {
|
||||
if (expr.type === 'CallExpression' && expr.callee.type === 'Identifier') {
|
||||
return expr.callee.name === name
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function doesSketchPipeNeedSplitting(
|
||||
ast: Node<Program>,
|
||||
pathToPipe: PathToNode
|
||||
): boolean | Error {
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
ast,
|
||||
pathToPipe,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(varDec)) return varDec
|
||||
if (varDec.node.type !== 'VariableDeclarator') return new Error('Not a var')
|
||||
const pipeExpression = varDec.node.init
|
||||
if (pipeExpression.type !== 'PipeExpression') return false
|
||||
const [firstPipe, secondPipe] = pipeExpression.body
|
||||
if (!firstPipe || !secondPipe) return false
|
||||
if (
|
||||
isCallExprWithName(firstPipe, 'startSketchOn') &&
|
||||
isCallExprWithName(secondPipe, 'startProfileAt')
|
||||
)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
@ -212,6 +212,19 @@ Map {
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-10" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
501,
|
||||
522,
|
||||
0,
|
||||
],
|
||||
},
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { PathToNode, Program, SourceRange } from 'lang/wasm'
|
||||
import { Expr, PathToNode, Program, SourceRange } from 'lang/wasm'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { err } from 'lib/trap'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
|
||||
export type ArtifactId = string
|
||||
|
||||
@ -34,7 +35,7 @@ export interface PathArtifact extends BaseArtifact {
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
interface solid2D extends BaseArtifact {
|
||||
interface Solid2DArtifact extends BaseArtifact {
|
||||
type: 'solid2D'
|
||||
pathId: ArtifactId
|
||||
}
|
||||
@ -61,7 +62,7 @@ interface SegmentArtifactRich extends BaseArtifact {
|
||||
type: 'segment'
|
||||
path: PathArtifact
|
||||
surf: WallArtifact
|
||||
edges: Array<SweepEdge>
|
||||
edges: Array<SweepEdgeArtifact>
|
||||
edgeCut?: EdgeCut
|
||||
codeRef: CodeRef
|
||||
}
|
||||
@ -80,7 +81,7 @@ interface SweepArtifactRich extends BaseArtifact {
|
||||
subType: 'extrusion' | 'revolve'
|
||||
path: PathArtifact
|
||||
surfaces: Array<WallArtifact | CapArtifact>
|
||||
edges: Array<SweepEdge>
|
||||
edges: Array<SweepEdgeArtifact>
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
@ -90,6 +91,9 @@ interface WallArtifact extends BaseArtifact {
|
||||
edgeCutEdgeIds: Array<ArtifactId>
|
||||
sweepId: ArtifactId
|
||||
pathIds: Array<ArtifactId>
|
||||
// codeRef is for the sketchOnFace plane, not for the wall itself
|
||||
// traverse to the extrude and or segment to get the wall's codeRef
|
||||
codeRef?: CodeRef
|
||||
}
|
||||
interface CapArtifact extends BaseArtifact {
|
||||
type: 'cap'
|
||||
@ -99,7 +103,7 @@ interface CapArtifact extends BaseArtifact {
|
||||
pathIds: Array<ArtifactId>
|
||||
}
|
||||
|
||||
interface SweepEdge extends BaseArtifact {
|
||||
interface SweepEdgeArtifact extends BaseArtifact {
|
||||
type: 'sweepEdge'
|
||||
segId: ArtifactId
|
||||
sweepId: ArtifactId
|
||||
@ -129,10 +133,10 @@ export type Artifact =
|
||||
| SweepArtifact
|
||||
| WallArtifact
|
||||
| CapArtifact
|
||||
| SweepEdge
|
||||
| SweepEdgeArtifact
|
||||
| EdgeCut
|
||||
| EdgeCutEdge
|
||||
| solid2D
|
||||
| Solid2DArtifact
|
||||
|
||||
export type ArtifactGraph = Map<ArtifactId, Artifact>
|
||||
|
||||
@ -284,6 +288,7 @@ export function getArtifactsToUpdate({
|
||||
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
|
||||
sweepId: existingPlane.sweepId,
|
||||
pathIds: existingPlane.pathIds,
|
||||
codeRef,
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -733,7 +738,7 @@ export function getCapCodeRef(
|
||||
}
|
||||
|
||||
export function getSolid2dCodeRef(
|
||||
solid2D: solid2D,
|
||||
solid2D: Solid2DArtifact,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CodeRef | Error {
|
||||
const path = getArtifactOfTypes(
|
||||
@ -757,7 +762,7 @@ export function getWallCodeRef(
|
||||
}
|
||||
|
||||
export function getSweepEdgeCodeRef(
|
||||
edge: SweepEdge,
|
||||
edge: SweepEdgeArtifact,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CodeRef | Error {
|
||||
const seg = getArtifactOfTypes(
|
||||
@ -871,3 +876,202 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
|
||||
pathToNode: getNodePathFromSourceRange(ast, range),
|
||||
}
|
||||
}
|
||||
|
||||
function getPlaneFromPath(
|
||||
path: PathArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | Error {
|
||||
const plane = getArtifactOfTypes(
|
||||
{ key: path.planeId, types: ['plane', 'wall'] },
|
||||
graph
|
||||
)
|
||||
if (err(plane)) return plane
|
||||
return plane
|
||||
}
|
||||
|
||||
function getPlaneFromSegment(
|
||||
segment: SegmentArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | Error {
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: segment.pathId, types: ['path'] },
|
||||
graph
|
||||
)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromSolid2D(
|
||||
solid2D: Solid2DArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | Error {
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: solid2D.pathId, types: ['path'] },
|
||||
graph
|
||||
)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromCap(
|
||||
cap: CapArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | Error {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: cap.sweepId, types: ['sweep'] },
|
||||
graph
|
||||
)
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromWall(
|
||||
wall: WallArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | Error {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: wall.sweepId, types: ['sweep'] },
|
||||
graph
|
||||
)
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromSweepEdge(edge: SweepEdgeArtifact, graph: ArtifactGraph) {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: edge.sweepId, types: ['sweep'] },
|
||||
graph
|
||||
)
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
|
||||
export function getPlaneFromArtifact(
|
||||
artifact: Artifact | undefined,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | Error {
|
||||
if (!artifact) return new Error(`Artifact is undefined`)
|
||||
if (artifact.type === 'plane') return artifact
|
||||
if (artifact.type === 'path') return getPlaneFromPath(artifact, graph)
|
||||
if (artifact.type === 'segment') return getPlaneFromSegment(artifact, graph)
|
||||
if (artifact.type === 'solid2D') return getPlaneFromSolid2D(artifact, graph)
|
||||
if (artifact.type === 'cap') return getPlaneFromCap(artifact, graph)
|
||||
if (artifact.type === 'wall') return getPlaneFromWall(artifact, graph)
|
||||
if (artifact.type === 'sweepEdge')
|
||||
return getPlaneFromSweepEdge(artifact, graph)
|
||||
return new Error(`Artifact type ${artifact.type} does not have a plane`)
|
||||
}
|
||||
|
||||
const isExprSafe = (index: number): boolean => {
|
||||
const expr = kclManager.ast.body?.[index]
|
||||
if (!expr) {
|
||||
return false
|
||||
}
|
||||
if (expr.type === 'ImportStatement' || expr.type === 'ReturnStatement') {
|
||||
return false
|
||||
}
|
||||
if (expr.type === 'VariableDeclaration') {
|
||||
const init = expr.declaration?.init
|
||||
if (!init) return false
|
||||
if (init.type === 'CallExpression') {
|
||||
return false
|
||||
}
|
||||
if (init.type === 'BinaryExpression' && isNodeSafe(init)) {
|
||||
return true
|
||||
}
|
||||
if (init.type === 'Literal' || init.type === 'MemberExpression') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const onlyConsecutivePaths = (
|
||||
orderedNodePaths: PathToNode[],
|
||||
originalPath: PathToNode
|
||||
): PathToNode[] => {
|
||||
const originalIndex = Number(
|
||||
orderedNodePaths.find(
|
||||
(path) => path[1][0] === originalPath[1][0]
|
||||
)?.[1]?.[0] || 0
|
||||
)
|
||||
|
||||
const minIndex = Number(orderedNodePaths[0][1][0])
|
||||
const maxIndex = Number(orderedNodePaths[orderedNodePaths.length - 1][1][0])
|
||||
const pathIndexMap: any = {}
|
||||
orderedNodePaths.forEach((path) => {
|
||||
const bodyIndex = Number(path[1][0])
|
||||
pathIndexMap[bodyIndex] = path
|
||||
})
|
||||
const safePaths: PathToNode[] = []
|
||||
|
||||
// traverse expressions in either direction from the profile selected
|
||||
// when the user entered sketch mode
|
||||
for (let i = originalIndex; i <= maxIndex; i++) {
|
||||
if (pathIndexMap[i]) {
|
||||
safePaths.push(pathIndexMap[i])
|
||||
} else if (!isExprSafe(i)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for (let i = originalIndex - 1; i >= minIndex; i--) {
|
||||
if (pathIndexMap[i]) {
|
||||
safePaths.unshift(pathIndexMap[i])
|
||||
} else if (!isExprSafe(i)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return safePaths
|
||||
}
|
||||
|
||||
export function getPathsFromPlaneArtifact(planeArtifact: PlaneArtifact) {
|
||||
const nodePaths: PathToNode[] = []
|
||||
for (const pathId of planeArtifact.pathIds) {
|
||||
const path = engineCommandManager.artifactGraph.get(pathId)
|
||||
if (!path) continue
|
||||
if ('codeRef' in path && path.codeRef) {
|
||||
// TODO should figure out why upstream the path is bad
|
||||
const isNodePathBad = path.codeRef.pathToNode.length < 2
|
||||
nodePaths.push(
|
||||
isNodePathBad
|
||||
? getNodePathFromSourceRange(kclManager.ast, path.codeRef.range)
|
||||
: path.codeRef.pathToNode
|
||||
)
|
||||
}
|
||||
}
|
||||
return onlyConsecutivePaths(nodePaths, nodePaths[0])
|
||||
}
|
||||
|
||||
export function getPathsFromArtifact({
|
||||
sketchPathToNode,
|
||||
artifact,
|
||||
}: {
|
||||
sketchPathToNode: PathToNode
|
||||
artifact?: Artifact
|
||||
}): PathToNode[] | Error {
|
||||
const plane = getPlaneFromArtifact(
|
||||
artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(plane)) return plane
|
||||
const paths = getArtifactsOfTypes(
|
||||
{ keys: plane.pathIds, types: ['path'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
let nodePaths = [...paths.values()]
|
||||
.map((path) => path.codeRef.pathToNode)
|
||||
.sort((a, b) => Number(a[1][0]) - Number(b[1][0]))
|
||||
return onlyConsecutivePaths(nodePaths, sketchPathToNode)
|
||||
}
|
||||
|
||||
function isNodeSafe(node: Expr): boolean {
|
||||
if (node.type === 'Literal' || node.type === 'MemberExpression') {
|
||||
return true
|
||||
}
|
||||
if (node.type === 'BinaryExpression') {
|
||||
return isNodeSafe(node.left) && isNodeSafe(node.right)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defaultSourceRange, SourceRange } from 'lang/wasm'
|
||||
import { defaultSourceRange, SourceRange, Program } from 'lang/wasm'
|
||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { exportSave } from 'lib/exportSave'
|
||||
@ -2099,30 +2099,23 @@ export class EngineCommandManager extends EventTarget {
|
||||
* When an execution takes place we want to wait until we've got replies for all of the commands
|
||||
* When this is done when we build the artifact map synchronously.
|
||||
*/
|
||||
async waitForAllCommands(useFakeExecutor = false) {
|
||||
await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
|
||||
setTimeout(() => {
|
||||
// the ast is wrong without this one tick timeout.
|
||||
// an example is `Solids should be select and deletable` e2e test will fail
|
||||
// because the out of date ast messes with selections
|
||||
// TODO: race condition
|
||||
if (!this?.kclManager) return
|
||||
this.artifactGraph = createArtifactGraph({
|
||||
orderedCommands: this.orderedCommands,
|
||||
responseMap: this.responseMap,
|
||||
ast: this.kclManager.ast,
|
||||
})
|
||||
if (useFakeExecutor) {
|
||||
// mock executions don't produce an artifactGraph, so this will always be empty
|
||||
// skipping the below logic to wait for the next real execution
|
||||
return
|
||||
}
|
||||
if (this.artifactGraph.size) {
|
||||
this.deferredArtifactEmptied(null)
|
||||
} else {
|
||||
this.deferredArtifactPopulated(null)
|
||||
}
|
||||
waitForAllCommands() {
|
||||
return Promise.all(
|
||||
Object.values(this.pendingCommands).map((a) => a.promise)
|
||||
)
|
||||
}
|
||||
updateArtifactGraph(ast: Program) {
|
||||
this.artifactGraph = createArtifactGraph({
|
||||
orderedCommands: this.orderedCommands,
|
||||
responseMap: this.responseMap,
|
||||
ast,
|
||||
})
|
||||
// TODO check if these still need to be deferred once e2e tests are working again.
|
||||
if (this.artifactGraph.size) {
|
||||
this.deferredArtifactEmptied(null)
|
||||
} else {
|
||||
this.deferredArtifactPopulated(null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2209,7 +2202,11 @@ export class EngineCommandManager extends EventTarget {
|
||||
commandTypeToTarget: string
|
||||
): string | undefined {
|
||||
for (const [artifactId, artifact] of this.artifactGraph) {
|
||||
if ('codeRef' in artifact && isOverlap(range, artifact.codeRef.range)) {
|
||||
if (
|
||||
'codeRef' in artifact &&
|
||||
artifact.codeRef &&
|
||||
isOverlap(range, artifact.codeRef.range)
|
||||
) {
|
||||
if (commandTypeToTarget === artifact.type) return artifactId
|
||||
}
|
||||
}
|
||||
|
@ -297,14 +297,20 @@ export const lineTo: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||
const to = segmentInput.to
|
||||
const _node = { ...node }
|
||||
const _node = structuredClone(node)
|
||||
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: pipe } = nodeMeta
|
||||
const varDec = getNodeFromPath<VariableDeclaration>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(varDec)) return varDec
|
||||
const dec = varDec.node.declaration
|
||||
|
||||
const newVals: [Expr, Expr] = [
|
||||
createLiteral(roundOff(to[0], 2)),
|
||||
@ -333,14 +339,20 @@ export const lineTo: SketchLineHelper = {
|
||||
])
|
||||
if (err(result)) return result
|
||||
const { callExp, valueUsedInTransform } = result
|
||||
pipe.body[callIndex] = callExp
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body[callIndex] = callExp
|
||||
} else {
|
||||
dec.init = callExp
|
||||
}
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
valueUsedInTransform: valueUsedInTransform,
|
||||
}
|
||||
} else if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body = [...dec.init.body, newLine]
|
||||
} else {
|
||||
pipe.body = [...pipe.body, newLine]
|
||||
dec.init = createPipeExpression([dec.init, newLine])
|
||||
}
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
@ -663,11 +675,11 @@ export const xLine: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||
const { from, to } = segmentInput
|
||||
const _node = { ...node }
|
||||
const _node = structuredClone(node)
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
const varDec = getNode<VariableDeclaration>('VariableDeclaration')
|
||||
if (err(varDec)) return varDec
|
||||
const dec = varDec.node.declaration
|
||||
|
||||
const newVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||
|
||||
@ -682,7 +694,11 @@ export const xLine: SketchLineHelper = {
|
||||
])
|
||||
if (err(result)) return result
|
||||
const { callExp, valueUsedInTransform } = result
|
||||
pipe.body[callIndex] = callExp
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body[callIndex] = callExp
|
||||
} else {
|
||||
dec.init = callExp
|
||||
}
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
@ -694,7 +710,11 @@ export const xLine: SketchLineHelper = {
|
||||
newVal,
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
pipe.body = [...pipe.body, newLine]
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body = [...dec.init.body, newLine]
|
||||
} else {
|
||||
dec.init = createPipeExpression([dec.init, newLine])
|
||||
}
|
||||
return { modifiedAst: _node, pathToNode }
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, input }) => {
|
||||
@ -731,11 +751,11 @@ export const yLine: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||
const { from, to } = segmentInput
|
||||
const _node = { ...node }
|
||||
const _node = structuredClone(node)
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
const varDec = getNode<VariableDeclaration>('VariableDeclaration')
|
||||
if (err(varDec)) return varDec
|
||||
const dec = varDec.node.declaration
|
||||
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||
if (replaceExistingCallback) {
|
||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||
@ -748,7 +768,11 @@ export const yLine: SketchLineHelper = {
|
||||
])
|
||||
if (err(result)) return result
|
||||
const { callExp, valueUsedInTransform } = result
|
||||
pipe.body[callIndex] = callExp
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body[callIndex] = callExp
|
||||
} else {
|
||||
dec.init = callExp
|
||||
}
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
@ -760,7 +784,11 @@ export const yLine: SketchLineHelper = {
|
||||
newVal,
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
pipe.body = [...pipe.body, newLine]
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body = [...dec.init.body, newLine]
|
||||
} else {
|
||||
dec.init = createPipeExpression([dec.init, newLine])
|
||||
}
|
||||
return { modifiedAst: _node, pathToNode }
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, input }) => {
|
||||
@ -2145,8 +2173,6 @@ function addTagToChamfer(
|
||||
if (err(variableDec)) return variableDec
|
||||
const isPipeExpression = pipeExpr.node.type === 'PipeExpression'
|
||||
|
||||
console.log('pipeExpr', pipeExpr, variableDec)
|
||||
// const callExpr = isPipeExpression ? pipeExpr.node.body[pipeIndex] : variableDec.node.init
|
||||
const callExpr = isPipeExpression
|
||||
? pipeExpr.node.body[pipeIndex]
|
||||
: variableDec.node.init
|
||||
@ -2227,7 +2253,6 @@ function addTagToChamfer(
|
||||
if (isPipeExpression) {
|
||||
pipeExpr.node.body.splice(pipeIndex, 0, newExpressionToInsert)
|
||||
} else {
|
||||
console.log('yo', createPipeExpression([newExpressionToInsert, callExpr]))
|
||||
callExpr.arguments[1] = createPipeSubstitution()
|
||||
variableDec.node.init = createPipeExpression([
|
||||
newExpressionToInsert,
|
||||
|
@ -9,20 +9,47 @@ import {
|
||||
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
|
||||
export function updatePathToNodeFromMap(
|
||||
oldPath: PathToNode,
|
||||
pathToNodeMap: { [key: number]: PathToNode }
|
||||
/**
|
||||
* Updates pathToNode body indices to account for the insertion of an expression
|
||||
* PathToNode expression is after the insertion index, that the body index is incremented
|
||||
* Negative insertion index means no insertion
|
||||
*/
|
||||
export function updatePathToNodePostExprInjection(
|
||||
pathToNode: PathToNode,
|
||||
exprInsertIndex: number
|
||||
): PathToNode {
|
||||
const updatedPathToNode = structuredClone(oldPath)
|
||||
let max = 0
|
||||
Object.values(pathToNodeMap).forEach((path) => {
|
||||
const index = Number(path[1][0])
|
||||
if (index > max) {
|
||||
max = index
|
||||
}
|
||||
})
|
||||
updatedPathToNode[1][0] = max
|
||||
return updatedPathToNode
|
||||
if (exprInsertIndex < 0) return pathToNode
|
||||
const bodyIndex = Number(pathToNode[1][0])
|
||||
if (bodyIndex < exprInsertIndex) return pathToNode
|
||||
const clone = structuredClone(pathToNode)
|
||||
clone[1][0] = bodyIndex + 1
|
||||
return clone
|
||||
}
|
||||
|
||||
export function updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath,
|
||||
sketchNodePaths,
|
||||
planeNodePath,
|
||||
exprInsertIndex,
|
||||
}: {
|
||||
sketchEntryNodePath: PathToNode
|
||||
sketchNodePaths: Array<PathToNode>
|
||||
planeNodePath: PathToNode
|
||||
exprInsertIndex: number
|
||||
}) {
|
||||
return {
|
||||
updatedSketchEntryNodePath: updatePathToNodePostExprInjection(
|
||||
sketchEntryNodePath,
|
||||
exprInsertIndex
|
||||
),
|
||||
updatedSketchNodePaths: sketchNodePaths.map((path) =>
|
||||
updatePathToNodePostExprInjection(path, exprInsertIndex)
|
||||
),
|
||||
updatedPlaneNodePath: updatePathToNodePostExprInjection(
|
||||
planeNodePath,
|
||||
exprInsertIndex
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export function isCursorInSketchCommandRange(
|
||||
@ -31,7 +58,7 @@ export function isCursorInSketchCommandRange(
|
||||
): string | false {
|
||||
const overlappingEntries = filterArtifacts(
|
||||
{
|
||||
types: ['segment', 'path'],
|
||||
types: ['segment', 'path', 'plane'],
|
||||
predicate: (artifact) => {
|
||||
return selectionRanges.graphSelections.some(
|
||||
(selection) =>
|
||||
|
@ -491,26 +491,6 @@ export const executor = async (
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
return Promise.reject(programMemoryOverride)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
engineCommandManager.startNewSession()
|
||||
const _programMemory = await _executor(
|
||||
node,
|
||||
engineCommandManager,
|
||||
programMemoryOverride
|
||||
)
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
|
||||
return _programMemory
|
||||
}
|
||||
|
||||
export const _executor = async (
|
||||
node: Node<Program>,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
programMemoryOverride: ProgramMemory | Error | null = null
|
||||
): Promise<ExecState> => {
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
return Promise.reject(programMemoryOverride)
|
||||
|
||||
try {
|
||||
let jsAppSettings = default_app_settings()
|
||||
if (!TEST) {
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
*/
|
||||
export const getRectangleCallExpressions = (
|
||||
rectangleOrigin: [number, number],
|
||||
tags: [string, string, string]
|
||||
tag: string
|
||||
) => [
|
||||
createCallExpressionStdLib('angledLine', [
|
||||
createArrayExpression([
|
||||
@ -37,30 +37,28 @@ export const getRectangleCallExpressions = (
|
||||
createLiteral(0), // This will be the width of the rectangle
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
createTagDeclarator(tags[0]),
|
||||
createTagDeclarator(tag),
|
||||
]),
|
||||
createCallExpressionStdLib('angledLine', [
|
||||
createArrayExpression([
|
||||
createBinaryExpression([
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tags[0])]),
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
|
||||
'+',
|
||||
createLiteral(90),
|
||||
]), // 90 offset from the previous line
|
||||
createLiteral(0), // This will be the height of the rectangle
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
createTagDeclarator(tags[1]),
|
||||
]),
|
||||
createCallExpressionStdLib('angledLine', [
|
||||
createArrayExpression([
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tags[0])]), // same angle as the first line
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tag)]), // same angle as the first line
|
||||
createUnaryExpression(
|
||||
createCallExpressionStdLib('segLen', [createIdentifier(tags[0])]),
|
||||
createCallExpressionStdLib('segLen', [createIdentifier(tag)]),
|
||||
'-'
|
||||
), // negative height
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
createTagDeclarator(tags[2]),
|
||||
]),
|
||||
createCallExpressionStdLib('lineTo', [
|
||||
createArrayExpression([
|
||||
@ -85,12 +83,12 @@ export function updateRectangleSketch(
|
||||
y: number,
|
||||
tag: string
|
||||
) {
|
||||
;((pipeExpression.body[2] as CallExpression)
|
||||
;((pipeExpression.body[1] as CallExpression)
|
||||
.arguments[0] as ArrayExpression) = createArrayExpression([
|
||||
createLiteral(x >= 0 ? 0 : 180),
|
||||
createLiteral(Math.abs(x)),
|
||||
])
|
||||
;((pipeExpression.body[3] as CallExpression)
|
||||
;((pipeExpression.body[2] as CallExpression)
|
||||
.arguments[0] as ArrayExpression) = createArrayExpression([
|
||||
createBinaryExpression([
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
|
||||
@ -120,7 +118,7 @@ export function updateCenterRectangleSketch(
|
||||
let startY = originY - Math.abs(deltaY)
|
||||
|
||||
// pipeExpression.body[1] is startProfileAt
|
||||
let callExpression = pipeExpression.body[1]
|
||||
let callExpression = pipeExpression.body[0]
|
||||
if (isCallExpression(callExpression)) {
|
||||
const arrayExpression = callExpression.arguments[0]
|
||||
if (isArrayExpression(arrayExpression)) {
|
||||
@ -134,7 +132,7 @@ export function updateCenterRectangleSketch(
|
||||
const twoX = deltaX * 2
|
||||
const twoY = deltaY * 2
|
||||
|
||||
callExpression = pipeExpression.body[2]
|
||||
callExpression = pipeExpression.body[1]
|
||||
if (isCallExpression(callExpression)) {
|
||||
const arrayExpression = callExpression.arguments[0]
|
||||
if (isArrayExpression(arrayExpression)) {
|
||||
@ -148,7 +146,7 @@ export function updateCenterRectangleSketch(
|
||||
}
|
||||
}
|
||||
|
||||
callExpression = pipeExpression.body[3]
|
||||
callExpression = pipeExpression.body[2]
|
||||
if (isCallExpression(callExpression)) {
|
||||
const arrayExpression = callExpression.arguments[0]
|
||||
if (isArrayExpression(arrayExpression)) {
|
||||
|
@ -280,18 +280,19 @@ export function getEventForSegmentSelection(
|
||||
}
|
||||
if (!id || !group) return null
|
||||
const artifact = engineCommandManager.artifactGraph.get(id)
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (!artifact || !codeRefs) return null
|
||||
if (!artifact) return null
|
||||
const node = getNodeFromPath<Expr>(kclManager.ast, group.userData.pathToNode)
|
||||
if (err(node)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
artifact,
|
||||
codeRef: codeRefs[0],
|
||||
codeRef: {
|
||||
pathToNode: group?.userData?.pathToNode,
|
||||
range: [node.node.start, node.node.end, true],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -550,10 +551,18 @@ function nodeHasClose(node: CommonASTNode) {
|
||||
})
|
||||
}
|
||||
function nodeHasCircle(node: CommonASTNode) {
|
||||
return doesPipeHaveCallExp({
|
||||
calleeName: 'circle',
|
||||
...node,
|
||||
})
|
||||
return (
|
||||
doesPipeHaveCallExp({
|
||||
calleeName: 'circle',
|
||||
...node,
|
||||
}) ||
|
||||
node.ast.body.some(
|
||||
(expression) =>
|
||||
expression.type === 'VariableDeclaration' &&
|
||||
expression.declaration.init.type === 'CallExpression' &&
|
||||
expression.declaration.init.callee.name === 'circle'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export function canSweepSelection(selection: Selections) {
|
||||
@ -561,8 +570,6 @@ export function canSweepSelection(selection: Selections) {
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
)
|
||||
return (
|
||||
!!isSketchPipe(selection) &&
|
||||
commonNodes.every((n) => !hasSketchPipeBeenExtruded(n.selection, n.ast)) &&
|
||||
(commonNodes.every((n) => nodeHasClose(n)) ||
|
||||
commonNodes.every((n) => nodeHasCircle(n))) &&
|
||||
commonNodes.every((n) => !nodeHasExtrude(n))
|
||||
@ -675,8 +682,7 @@ export function getSelectionTypeDisplayText(
|
||||
const selectionsByType = getSelectionCountByType(selection)
|
||||
if (selectionsByType === 'none') return null
|
||||
|
||||
return selectionsByType
|
||||
.entries()
|
||||
return [...selectionsByType.entries()]
|
||||
.map(
|
||||
// Hack for showing "face" instead of "extrude-wall" in command bar text
|
||||
([type, count]) =>
|
||||
@ -685,7 +691,6 @@ export function getSelectionTypeDisplayText(
|
||||
.replace('solid2D', 'face')
|
||||
.replace('segment', 'face')}${count > 1 ? 's' : ''}`
|
||||
)
|
||||
.toArray()
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
@ -695,7 +700,7 @@ export function canSubmitSelectionArg(
|
||||
) {
|
||||
return (
|
||||
selectionsByType !== 'none' &&
|
||||
selectionsByType.entries().every(([type, count]) => {
|
||||
[...selectionsByType.entries()].every(([type, count]) => {
|
||||
const foundIndex = argument.selectionTypes.findIndex((s) => s === type)
|
||||
return (
|
||||
foundIndex !== -1 &&
|
||||
@ -718,7 +723,7 @@ export function codeToIdSelections(
|
||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||
const overlappingEntries = Array.from(engineCommandManager.artifactGraph)
|
||||
.map(([id, artifact]) => {
|
||||
if (!('codeRef' in artifact)) return null
|
||||
if (!('codeRef' in artifact && artifact.codeRef)) return null
|
||||
return isOverlap(artifact.codeRef.range, selection.range)
|
||||
? {
|
||||
artifact,
|
||||
@ -961,7 +966,6 @@ export function updateSelections(
|
||||
JSON.stringify(pathToNode)
|
||||
) {
|
||||
artifact = a
|
||||
console.log('found artifact', a)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
import {
|
||||
Program,
|
||||
ProgramMemory,
|
||||
_executor,
|
||||
executor,
|
||||
SourceRange,
|
||||
ExecState,
|
||||
} from '../lang/wasm'
|
||||
import {
|
||||
EngineCommandManager,
|
||||
EngineCommandManagerEvents,
|
||||
} from 'lang/std/engineConnection'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import { err, reportRejection } from 'lib/trap'
|
||||
import { toSync } from './utils'
|
||||
import { err } from 'lib/trap'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
type WebSocketResponse = Models['WebSocketResponse_type']
|
||||
@ -94,36 +90,7 @@ export async function enginelessExecutor(
|
||||
}) as any as EngineCommandManager
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
mockEngineCommandManager.startNewSession()
|
||||
const execState = await _executor(ast, mockEngineCommandManager, pmo)
|
||||
const execState = await executor(ast, mockEngineCommandManager, pmo)
|
||||
await mockEngineCommandManager.waitForAllCommands()
|
||||
return execState
|
||||
}
|
||||
|
||||
export async function executor(
|
||||
ast: Node<Program>,
|
||||
pmo: ProgramMemory = ProgramMemory.empty()
|
||||
): Promise<ExecState> {
|
||||
const engineCommandManager = new EngineCommandManager()
|
||||
engineCommandManager.start({
|
||||
setIsStreamReady: () => {},
|
||||
setMediaStream: () => {},
|
||||
width: 0,
|
||||
height: 0,
|
||||
makeDefaultPlanes: () => {
|
||||
return new Promise((resolve) => resolve(defaultPlanes))
|
||||
},
|
||||
})
|
||||
|
||||
return new Promise((resolve) => {
|
||||
engineCommandManager.addEventListener(
|
||||
EngineCommandManagerEvents.SceneReady,
|
||||
toSync(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
engineCommandManager.startNewSession()
|
||||
const execState = await _executor(ast, engineCommandManager, pmo)
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
resolve(execState)
|
||||
}, reportRejection)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -2,8 +2,6 @@ import { CustomIconName } from 'components/CustomIcon'
|
||||
import { DEV } from 'env'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import {
|
||||
canRectangleOrCircleTool,
|
||||
isClosedSketch,
|
||||
isEditingExistingSketch,
|
||||
modelingMachine,
|
||||
pipeHasCircle,
|
||||
@ -73,7 +71,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
status: 'available',
|
||||
disabled: (state) => !state.matches('idle'),
|
||||
title: ({ sketchPathId }) =>
|
||||
`${sketchPathId ? 'Edit' : 'Start'} Sketch`,
|
||||
sketchPathId ? 'Edit Sketch' : 'Start Sketch',
|
||||
showTitle: true,
|
||||
hotkey: 'S',
|
||||
description: 'Start drawing a 2D sketch',
|
||||
@ -315,22 +313,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
{
|
||||
id: 'line',
|
||||
onClick: ({ modelingState, modelingSend }) => {
|
||||
if (modelingState.matches({ Sketch: { 'Line tool': 'No Points' } })) {
|
||||
// Exit the sketch state if there are no points and they press ESC
|
||||
modelingSend({
|
||||
type: 'Cancel',
|
||||
})
|
||||
} else {
|
||||
// Exit the tool if there are points and they press ESC
|
||||
modelingSend({
|
||||
type: 'change tool',
|
||||
data: {
|
||||
tool: !modelingState.matches({ Sketch: 'Line tool' })
|
||||
? 'line'
|
||||
: 'none',
|
||||
},
|
||||
})
|
||||
}
|
||||
modelingSend({
|
||||
type: 'change tool',
|
||||
data: {
|
||||
tool: !modelingState.matches({ Sketch: 'Line tool' })
|
||||
? 'line'
|
||||
: 'none',
|
||||
},
|
||||
})
|
||||
},
|
||||
icon: 'line',
|
||||
status: 'available',
|
||||
@ -341,8 +331,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}) ||
|
||||
state.matches({
|
||||
Sketch: { 'Circle tool': 'Awaiting Radius' },
|
||||
}) ||
|
||||
isClosedSketch(state.context),
|
||||
}),
|
||||
title: 'Line',
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L',
|
||||
@ -422,10 +411,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
icon: 'circle',
|
||||
status: 'available',
|
||||
title: 'Center circle',
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
(!canRectangleOrCircleTool(state.context) &&
|
||||
!state.matches({ Sketch: 'Circle tool' })),
|
||||
disabled: (state) => state.matches('Sketch no face'),
|
||||
isActive: (state) => state.matches({ Sketch: 'Circle tool' }),
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
||||
@ -464,10 +450,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}),
|
||||
icon: 'rectangle',
|
||||
status: 'available',
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
(!canRectangleOrCircleTool(state.context) &&
|
||||
!state.matches({ Sketch: 'Rectangle tool' })),
|
||||
disabled: (state) => state.matches('Sketch no face'),
|
||||
title: 'Corner rectangle',
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Rectangle tool' }) ? ['Esc', 'R'] : 'R',
|
||||
@ -490,10 +473,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}),
|
||||
icon: 'arc',
|
||||
status: 'available',
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
(!canRectangleOrCircleTool(state.context) &&
|
||||
!state.matches({ Sketch: 'Center Rectangle tool' })),
|
||||
disabled: (state) => state.matches('Sketch no face'),
|
||||
title: 'Center rectangle',
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Center Rectangle tool' })
|
||||
|
@ -97,3 +97,7 @@ export function trap<T>(
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
export function reject(errOrString: Error | string): Promise<never> {
|
||||
return Promise.reject(errOrString)
|
||||
}
|
||||
|