diff --git a/e2e/playwright/flow-tests.spec.ts b/e2e/playwright/flow-tests.spec.ts index 9372a31ef..abb3c973b 100644 --- a/e2e/playwright/flow-tests.spec.ts +++ b/e2e/playwright/flow-tests.spec.ts @@ -1,7 +1,7 @@ import { test, expect, Page } from '@playwright/test' import { makeTemplate, getUtils, doExport } from './test-utils' import waitOn from 'wait-on' -import { roundOff, uuidv4 } from 'lib/utils' +import { XOR, roundOff, uuidv4 } from 'lib/utils' import { SaveSettingsPayload } from 'lib/settings/settingsTypes' import { secrets } from './secrets' import { @@ -2440,6 +2440,245 @@ test('Extrude from command bar selects extrude line after', async ({ }) test.describe('Testing constraints', () => { + test(`Test remove constraints`, async ({ page }) => { + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const yo = 79 +const part001 = startSketchOn('XZ') + |> startProfileAt([-7.54, -26.74], %) + |> line([74.36, 130.4], %, 'seg01') + |> line([78.92, -120.11], %) + |> angledLine([segAng('seg01', %), yo], %) + |> line([41.19, 28.97 + 5], %) +const part002 = startSketchOn('XZ') + |> startProfileAt([299.05, 231.45], %) + |> xLine(-425.34, %, 'seg-what') + |> yLine(-264.06, %) + |> xLine(segLen('seg-what', %), %) + |> lineTo([profileStartX(%), profileStartY(%)], %)` + ) + }) + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + await page.goto('/') + await u.waitForAuthSkipAppStart() + + await page.getByText("line([74.36, 130.4], %, 'seg01')").click() + await page.getByRole('button', { name: 'Edit Sketch' }).click() + + const line3 = await u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`) + + // await page.mouse.click(line1.x, line1.y) + // await page.keyboard.down('Shift') + await page.mouse.click(line3.x, line3.y) + await page.waitForTimeout(100) // this wait is needed for webkit - not sure why + // await page.keyboard.up('Shift') + await page + .getByRole('button', { + name: 'Constrain', + }) + .click() + await page + .getByRole('button', { name: 'remove constraints', exact: true }) + .click() + + const activeLinesContent = await page.locator('.cm-activeLine').all() + await expect(activeLinesContent).toHaveLength(1) + await expect(activeLinesContent[0]).toHaveText('|> line([39.13, 68.63], %)') + + // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state + await expect(page.getByTestId('segment-overlay')).toHaveCount(4) + }) + test.describe('Test perpendicular distance constraint', () => { + const cases = [ + { + testName: 'Add variable', + offset: '-offset001', + }, + { + testName: 'No variable', + offset: '-128.05', + }, + ] as const + for (const { testName, offset } of cases) { + test(`${testName}`, async ({ page }) => { + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const yo = 5 +const part001 = startSketchOn('XZ') + |> startProfileAt([-7.54, -26.74], %) + |> line([74.36, 130.4], %, 'seg01') + |> line([78.92, -120.11], %) + |> angledLine([segAng('seg01', %), 78.33], %) + |> line([41.19, 28.97], %) +const part002 = startSketchOn('XZ') + |> startProfileAt([299.05, 231.45], %) + |> xLine(-425.34, %, 'seg-what') + |> yLine(-264.06, %) + |> xLine(segLen('seg-what', %), %) + |> lineTo([profileStartX(%), profileStartY(%)], %)` + ) + }) + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + await page.goto('/') + await u.waitForAuthSkipAppStart() + + await page.getByText("line([74.36, 130.4], %, 'seg01')").click() + await page.getByRole('button', { name: 'Edit Sketch' }).click() + + const [line1, line3] = await Promise.all([ + u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), + u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), + ]) + + await page.mouse.click(line1.x, line1.y) + await page.keyboard.down('Shift') + await page.mouse.click(line3.x, line3.y) + await page.waitForTimeout(100) // this wait is needed for webkit - not sure why + await page.keyboard.up('Shift') + await page + .getByRole('button', { + name: 'Constrain', + }) + .click() + await page + .getByRole('button', { name: 'perpendicular distance', exact: true }) + .click() + + const createNewVariableCheckbox = page.getByTestId( + 'create-new-variable-checkbox' + ) + const isChecked = await createNewVariableCheckbox.isChecked() + const addVariable = testName === 'Add variable' + XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct + (await createNewVariableCheckbox.click()) + + await page + .getByRole('button', { name: 'Add constraining value' }) + .click() + + const activeLinesContent = await page.locator('.cm-activeLine').all() + await expect(activeLinesContent[0]).toHaveText( + `|> line([74.36, 130.4], %, 'seg01')` + ) + await expect(activeLinesContent[1]).toHaveText(`}, %)`) + await expect(page.locator('.cm-content')).toContainText(`angle: -57,`) + await expect(page.locator('.cm-content')).toContainText( + `offset: ${offset},` + ) + + // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state + await expect(page.getByTestId('segment-overlay')).toHaveCount(4) + }) + } + }) + test.describe('Test distance between constraint', () => { + const cases = [ + { + testName: 'Add variable', + constraint: 'horizontal distance', + value: "segEndX('seg01', %) + xDis001, 61.34", + }, + { + testName: 'No variable', + constraint: 'horizontal distance', + value: "segEndX('seg01', %) + 88.08, 61.34", + }, + { + testName: 'Add variable', + constraint: 'vertical distance', + value: "154.9, segEndY('seg01', %) - yDis001", + }, + { + testName: 'No variable', + constraint: 'vertical distance', + value: "154.9, segEndY('seg01', %) - 42.32", + }, + ] as const + for (const { testName, value, constraint } of cases) { + test(`${constraint} - ${testName}`, async ({ page }) => { + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const yo = 5 +const part001 = startSketchOn('XZ') + |> startProfileAt([-7.54, -26.74], %) + |> line([74.36, 130.4], %) + |> line([78.92, -120.11], %) + |> line([9.16, 77.79], %) + |> line([41.19, 28.97], %) +const part002 = startSketchOn('XZ') + |> startProfileAt([299.05, 231.45], %) + |> xLine(-425.34, %, 'seg-what') + |> yLine(-264.06, %) + |> xLine(segLen('seg-what', %), %) + |> lineTo([profileStartX(%), profileStartY(%)], %)` + ) + }) + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + await page.goto('/') + await u.waitForAuthSkipAppStart() + + await page.getByText('line([74.36, 130.4], %)').click() + await page.getByRole('button', { name: 'Edit Sketch' }).click() + + const [line1, line3] = await Promise.all([ + u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), + u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), + ]) + + await page.mouse.click(line1.x, line1.y) + await page.keyboard.down('Shift') + await page.mouse.click(line3.x, line3.y) + await page.waitForTimeout(100) // this wait is needed for webkit - not sure why + await page.keyboard.up('Shift') + await page + .getByRole('button', { + name: 'Constrain', + }) + .click() + await page + .getByRole('button', { name: constraint, exact: true }) + .click() + + const createNewVariableCheckbox = page.getByTestId( + 'create-new-variable-checkbox' + ) + const isChecked = await createNewVariableCheckbox.isChecked() + const addVariable = testName === 'Add variable' + XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct + (await createNewVariableCheckbox.click()) + + await page + .getByRole('button', { name: 'Add constraining value' }) + .click() + + // checking activeLines assures the cursors are where they should be + const codeAfter = [ + `|> line([74.36, 130.4], %, 'seg01')`, + `|> lineTo([${value}], %)`, + ] + + const activeLinesContent = await page.locator('.cm-activeLine').all() + await Promise.all( + activeLinesContent.map(async (line, i) => { + await expect(page.locator('.cm-content')).toContainText( + codeAfter[i] + ) + // if the code is an active line then the cursor should be on that line + await expect(line).toHaveText(codeAfter[i]) + }) + ) + + // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state + await expect(page.getByTestId('segment-overlay')).toHaveCount(4) + }) + } + }) test.describe('Test ABS distance constraint', () => { const cases = [ { @@ -2521,7 +2760,7 @@ const part002 = startSketchOn('XZ') 'create-new-variable-checkbox' ) const isChecked = await createNewVariableCheckbox.isChecked() - ;((isChecked && !addVariable) || (!isChecked && addVariable)) && + XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct (await createNewVariableCheckbox.click()) await page @@ -2627,7 +2866,7 @@ const part002 = startSketchOn('XZ') 'create-new-variable-checkbox' ) const isChecked = await createNewVariableCheckbox.isChecked() - ;((isChecked && !addVariable) || (!isChecked && addVariable)) && + XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct (await createNewVariableCheckbox.click()) await page @@ -2657,37 +2896,51 @@ const part002 = startSketchOn('XZ') }) } }) - test.describe('Test Angle constraint single selection', () => { + test.describe('Test Angle/Length constraint single selection', () => { const cases = [ { - testName: 'Add variable', + testName: 'Angle - Add variable', addVariable: true, - value: 'angle001', + constraint: 'angle', + value: 'angle001, 78.33', }, { - testName: 'No variable', + testName: 'Angle - No variable', addVariable: false, - value: '83', + constraint: 'angle', + value: '83, 78.33', + }, + { + testName: 'Length - Add variable', + addVariable: true, + constraint: 'length', + value: '83, length001', + }, + { + testName: 'Length - No variable', + addVariable: false, + constraint: 'length', + value: '83, 78.33', }, ] as const - for (const { testName, addVariable, value } of cases) { + for (const { testName, addVariable, value, constraint } of cases) { test(`${testName}`, async ({ page }) => { await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `const yo = 5 - const part001 = startSketchOn('XZ') - |> startProfileAt([-7.54, -26.74], %) - |> line([74.36, 130.4], %) - |> line([78.92, -120.11], %) - |> line([9.16, 77.79], %) - |> line([41.19, 28.97], %) - const part002 = startSketchOn('XZ') - |> startProfileAt([299.05, 231.45], %) - |> xLine(-425.34, %, 'seg-what') - |> yLine(-264.06, %) - |> xLine(segLen('seg-what', %), %) - |> lineTo([profileStartX(%), profileStartY(%)], %)` +const part001 = startSketchOn('XZ') + |> startProfileAt([-7.54, -26.74], %) + |> line([74.36, 130.4], %) + |> line([78.92, -120.11], %) + |> line([9.16, 77.79], %) + |> line([41.19, 28.97], %) +const part002 = startSketchOn('XZ') + |> startProfileAt([299.05, 231.45], %) + |> xLine(-425.34, %, 'seg-what') + |> yLine(-264.06, %) + |> xLine(segLen('seg-what', %), %) + |> lineTo([profileStartX(%), profileStartY(%)], %)` ) }) const u = await getUtils(page) @@ -2708,7 +2961,7 @@ const part002 = startSketchOn('XZ') name: 'Constrain', }) .click() - await page.getByTestId('angle').click() + await page.getByTestId(constraint).click() if (!addVariable) { await page.getByTestId('create-new-variable-checkbox').click() @@ -2717,7 +2970,7 @@ const part002 = startSketchOn('XZ') .getByRole('button', { name: 'Add constraining value' }) .click() - const changedCode = `|> angledLine([${value}, 78.33], %)` + const changedCode = `|> angledLine([${value}], %)` await expect(page.locator('.cm-content')).toContainText(changedCode) // checking active assures the cursor is where it should be await expect(page.locator('.cm-activeLine')).toHaveText(changedCode) @@ -2748,7 +3001,7 @@ const part002 = startSketchOn('XZ') ] as const for (const { codeAfter, constraintName } of cases) { test(`${constraintName}`, async ({ page }) => { - await page.addInitScript(async () => { + await page.addInitScript(async (customCode) => { localStorage.setItem( 'persistCode', `const yo = 5 @@ -2774,15 +3027,21 @@ const part002 = startSketchOn('XZ') await page.getByText('line([74.36, 130.4], %)').click() await page.getByRole('button', { name: 'Edit Sketch' }).click() - const line1 = await u.getBoundingBox(`[data-overlay-index="${0}"]`) - const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`) - const line4 = await u.getBoundingBox(`[data-overlay-index="${3}"]`) + const line1 = await u.getSegmentBodyCoords( + `[data-overlay-index="${0}"]` + ) + const line3 = await u.getSegmentBodyCoords( + `[data-overlay-index="${2}"]` + ) + const line4 = await u.getSegmentBodyCoords( + `[data-overlay-index="${3}"]` + ) // select two segments by holding down shift - await page.mouse.click(line1.x - 20, line1.y + 20) + await page.mouse.click(line1.x, line1.y) await page.keyboard.down('Shift') - await page.mouse.click(line3.x - 3, line3.y + 20) - await page.mouse.click(line4.x - 15, line4.y + 15) + await page.mouse.click(line3.x, line3.y) + await page.mouse.click(line4.x, line4.y) await page.keyboard.up('Shift') const constraintMenuButton = page.getByRole('button', { name: 'Constrain', diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-rectangles-should-look-right-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-rectangles-should-look-right-1-Google-Chrome-linux.png index 9ef452cc5..a5fe57b94 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-rectangles-should-look-right-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-rectangles-should-look-right-1-Google-Chrome-linux.png differ diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 88be84869..5131fba19 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -508,13 +508,26 @@ export const ModelingMachineProvider = ({ }, 'Get horizontal info': async ({ selectionRanges, + sketchDetails, }): Promise => { const { modifiedAst, pathToNodeMap } = await applyConstraintHorzVertDistance({ constraint: 'setHorzDistance', selectionRanges, }) - await kclManager.updateAst(modifiedAst, true) + const _modifiedAst = parse(recast(modifiedAst)) + if (!sketchDetails) throw new Error('No sketch details') + const updatedPathToNode = updatePathToNodeFromMap( + sketchDetails.sketchPathToNode, + pathToNodeMap + ) + await sceneEntitiesManager.updateAstAndRejigSketch( + updatedPathToNode, + _modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin + ) return { selectionType: 'completeSelection', selection: pathMapToSelections( @@ -522,17 +535,31 @@ export const ModelingMachineProvider = ({ selectionRanges, pathToNodeMap ), + updatedPathToNode, } }, 'Get vertical info': async ({ selectionRanges, + sketchDetails, }): Promise => { const { modifiedAst, pathToNodeMap } = await applyConstraintHorzVertDistance({ constraint: 'setVertDistance', selectionRanges, }) - await kclManager.updateAst(modifiedAst, true) + const _modifiedAst = parse(recast(modifiedAst)) + if (!sketchDetails) throw new Error('No sketch details') + const updatedPathToNode = updatePathToNodeFromMap( + sketchDetails.sketchPathToNode, + pathToNodeMap + ) + await sceneEntitiesManager.updateAstAndRejigSketch( + updatedPathToNode, + _modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin + ) return { selectionType: 'completeSelection', selection: pathMapToSelections( @@ -540,6 +567,7 @@ export const ModelingMachineProvider = ({ selectionRanges, pathToNodeMap ), + updatedPathToNode, } }, 'Get angle info': async ({ @@ -581,10 +609,23 @@ export const ModelingMachineProvider = ({ }, 'Get length info': async ({ selectionRanges, + sketchDetails, }): Promise => { const { modifiedAst, pathToNodeMap } = await applyConstraintAngleLength({ selectionRanges }) - await kclManager.updateAst(modifiedAst, true) + const _modifiedAst = parse(recast(modifiedAst)) + if (!sketchDetails) throw new Error('No sketch details') + const updatedPathToNode = updatePathToNodeFromMap( + sketchDetails.sketchPathToNode, + pathToNodeMap + ) + await sceneEntitiesManager.updateAstAndRejigSketch( + updatedPathToNode, + _modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin + ) return { selectionType: 'completeSelection', selection: pathMapToSelections( @@ -592,17 +633,31 @@ export const ModelingMachineProvider = ({ selectionRanges, pathToNodeMap ), + updatedPathToNode, } }, 'Get perpendicular distance info': async ({ selectionRanges, + sketchDetails, }): Promise => { const { modifiedAst, pathToNodeMap } = await applyConstraintIntersect( { selectionRanges, } ) - await kclManager.updateAst(modifiedAst, true) + const _modifiedAst = parse(recast(modifiedAst)) + if (!sketchDetails) throw new Error('No sketch details') + const updatedPathToNode = updatePathToNodeFromMap( + sketchDetails.sketchPathToNode, + pathToNodeMap + ) + await sceneEntitiesManager.updateAstAndRejigSketch( + updatedPathToNode, + _modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin + ) return { selectionType: 'completeSelection', selection: pathMapToSelections( @@ -610,6 +665,7 @@ export const ModelingMachineProvider = ({ selectionRanges, pathToNodeMap ), + updatedPathToNode, } }, 'Get ABS X info': async ({ diff --git a/src/components/Toolbar/Intersect.tsx b/src/components/Toolbar/Intersect.tsx index 6f8eca9cd..4d1ba0c96 100644 --- a/src/components/Toolbar/Intersect.tsx +++ b/src/components/Toolbar/Intersect.tsx @@ -140,7 +140,11 @@ export async function applyConstraintIntersect({ value: valueUsedInTransform, initialVariableName: 'offset', }) - if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) { + if ( + !variableName && + segName === tagInfo?.tag && + Number(value) === valueUsedInTransform + ) { return { modifiedAst, pathToNodeMap, @@ -169,6 +173,10 @@ export async function applyConstraintIntersect({ createVariableDeclaration(variableName, valueNode) ) _modifiedAst.body = newBody + Object.values(_pathToNodeMap).forEach((pathToNode) => { + const index = pathToNode.findIndex((a) => a[0] === 'body') + 1 + pathToNode[index][0] = Number(pathToNode[index][0]) + 1 + }) } return { modifiedAst: _modifiedAst, diff --git a/src/components/Toolbar/SetHorzVertDistance.tsx b/src/components/Toolbar/SetHorzVertDistance.tsx index 2f488a7eb..b7dbf3487 100644 --- a/src/components/Toolbar/SetHorzVertDistance.tsx +++ b/src/components/Toolbar/SetHorzVertDistance.tsx @@ -106,7 +106,11 @@ export async function applyConstraintHorzVertDistance({ value: valueUsedInTransform, initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis', } as any) - if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) { + if ( + !variableName && + segName === tagInfo?.tag && + Number(value) === valueUsedInTransform + ) { return { modifiedAst, pathToNodeMap, @@ -133,6 +137,10 @@ export async function applyConstraintHorzVertDistance({ createVariableDeclaration(variableName, valueNode) ) _modifiedAst.body = newBody + Object.values(pathToNodeMap).forEach((pathToNode) => { + const index = pathToNode.findIndex((a) => a[0] === 'body') + 1 + pathToNode[index][0] = Number(pathToNode[index][0]) + 1 + }) } return { modifiedAst: _modifiedAst, diff --git a/src/lib/rectangleTool.ts b/src/lib/rectangleTool.ts index 544793245..27c494fd0 100644 --- a/src/lib/rectangleTool.ts +++ b/src/lib/rectangleTool.ts @@ -69,6 +69,13 @@ export const getRectangleCallExpressions = ( createPipeSubstitution(), createLiteral(tags[2]), ]), + createCallExpressionStdLib('lineTo', [ + createArrayExpression([ + createCallExpressionStdLib('profileStartX', [createPipeSubstitution()]), + createCallExpressionStdLib('profileStartY', [createPipeSubstitution()]), + ]), + createPipeSubstitution(), + ]), // close the rectangle createCallExpressionStdLib('close', [createPipeSubstitution()]), ] diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 009fe4bc9..4201fc81b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -127,3 +127,7 @@ export function isReducedMotion(): boolean { window.matchMedia('(prefers-reduced-motion)').matches ) } + +export function XOR(bool1: boolean, bool2: boolean): boolean { + return (bool1 || bool2) && !(bool1 && bool2) +} diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index f009d7e04..147ecf28a 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -360,10 +360,8 @@ export const modelingMachine = createMachine( }, 'Constrain remove constraints': { - target: 'SketchIdle', - internal: true, cond: 'Can constrain remove constraints', - actions: ['Constrain remove constraints'], + target: 'Await constrain remove constraints', }, 'Re-execute': { @@ -586,6 +584,16 @@ export const modelingMachine = createMachine( }, }, + 'Await constrain remove constraints': { + invoke: { + src: 'do-constrain-remove-constraint', + id: 'do-constrain-remove-constraint', + onDone: { + target: 'SketchIdle', + actions: 'Set selection', + }, + }, + }, 'Await constrain horizontally': { invoke: { src: 'do-constrain-horizontally', @@ -848,21 +856,6 @@ export const modelingMachine = createMachine( 'set new sketch metadata': assign((_, { data }) => ({ sketchDetails: data, })), - // TODO implement source ranges for all of these constraints - // need to make the async like the modal constraints - 'Constrain remove constraints': ({ selectionRanges, sketchDetails }) => { - const { modifiedAst } = applyRemoveConstrainingValues({ - selectionRanges, - }) - if (!sketchDetails) return - sceneEntitiesManager.updateAstAndRejigSketch( - sketchDetails?.sketchPathToNode || [], - modifiedAst, - sketchDetails.zAxis, - sketchDetails.yAxis, - sketchDetails.origin - ) - }, 'AST extrude': async (_, event) => { if (!event.data) return const { selection, distance } = event.data @@ -1109,6 +1102,38 @@ export const modelingMachine = createMachine( }, // end actions services: { + 'do-constrain-remove-constraint': async ({ + selectionRanges, + sketchDetails, + }) => { + const { modifiedAst, pathToNodeMap } = applyRemoveConstrainingValues({ + selectionRanges, + }) + if (!sketchDetails) return + sceneEntitiesManager.updateAstAndRejigSketch( + sketchDetails?.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin + ) + if (!sketchDetails) return + await sceneEntitiesManager.updateAstAndRejigSketch( + sketchDetails.sketchPathToNode, + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin + ) + return { + selectionType: 'completeSelection', + selection: updateSelections( + pathToNodeMap, + selectionRanges, + parse(recast(modifiedAst)) + ), + } + }, 'do-constrain-horizontally': async ({ selectionRanges, sketchDetails,