diff --git a/e2e/playwright/code-pane-and-errors.spec.ts b/e2e/playwright/code-pane-and-errors.spec.ts index 0915a2835..be6ca7992 100644 --- a/e2e/playwright/code-pane-and-errors.spec.ts +++ b/e2e/playwright/code-pane-and-errors.spec.ts @@ -134,8 +134,6 @@ extrude001 = extrude(sketch001, length = 5)` await page.setBodyDimensions({ width: 1200, height: 500 }) await homePage.goToModelingScene() - await page.waitForTimeout(1000) - // Ensure badge is present const codePaneButtonHolder = page.locator('#code-button-holder') await expect(codePaneButtonHolder).toContainText('notification', { @@ -183,7 +181,7 @@ extrude001 = extrude(sketch001, length = 5)` await page.setBodyDimensions({ width: 1200, height: 500 }) await homePage.goToModelingScene() - await scene.settled(cmdBar) + // await scene.settled(cmdBar) // Ensure badge is present const codePaneButtonHolder = page.locator('#code-button-holder') diff --git a/e2e/playwright/editor-tests.spec.ts b/e2e/playwright/editor-tests.spec.ts index db5777689..727764944 100644 --- a/e2e/playwright/editor-tests.spec.ts +++ b/e2e/playwright/editor-tests.spec.ts @@ -1533,7 +1533,6 @@ sketch001 = startSketchOn(XZ) await homePage.goToModelingScene() await scene.connectionEstablished() - await scene.settled(cmdBar) await scene.expectPixelColor( TEST_COLORS.DARK_MODE_BKGD, diff --git a/e2e/playwright/point-click.spec.ts b/e2e/playwright/point-click.spec.ts index 302ef46c8..de5320ef7 100644 --- a/e2e/playwright/point-click.spec.ts +++ b/e2e/playwright/point-click.spec.ts @@ -4943,4 +4943,34 @@ path001 = startProfile(sketch001, at = [0, 0]) ) }) }) + + test(`Point and click codemods can't run on KCL errors`, async ({ + context, + page, + homePage, + scene, + editor, + toolbar, + cmdBar, + }) => { + const badCode = `sketch001 = startSketchOn(XZ) +profile001 = circle(sketch001, center = [0, 0], radius = 1) +extrude001 = extrude(profile001 length = 1)` + await context.addInitScript((initialCode) => { + localStorage.setItem('persistCode', initialCode) + }, badCode) + await page.setBodyDimensions({ width: 1000, height: 500 }) + await homePage.goToModelingScene() + await scene.connectionEstablished() + + await test.step(`Start Sketch is disabled`, async () => { + await expect(toolbar.startSketchBtn).not.toBeEnabled() + await editor.expectEditor.toContain(badCode, { shouldNormalise: true }) + }) + + await test.step(`Helix is disabled`, async () => { + await expect(toolbar.helixButton).not.toBeEnabled() + await editor.expectEditor.toContain(badCode, { shouldNormalise: true }) + }) + }) }) diff --git a/e2e/playwright/regression-tests.spec.ts b/e2e/playwright/regression-tests.spec.ts index 3f0555689..6308322cd 100644 --- a/e2e/playwright/regression-tests.spec.ts +++ b/e2e/playwright/regression-tests.spec.ts @@ -19,11 +19,12 @@ test.describe('Regression tests', () => { context, page, homePage, + scene, }) => { // because the model has `line([0,0]..` it is valid code, but the model is invalid // regression test for https://github.com/KittyCAD/modeling-app/issues/3251 // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics - const u = await getUtils(page) + // const u = await getUtils(page) await context.addInitScript(async () => { localStorage.setItem( 'persistCode', @@ -40,7 +41,8 @@ test.describe('Regression tests', () => { await page.setBodyDimensions({ width: 1000, height: 500 }) await homePage.goToModelingScene() - await u.waitForPageLoad() + await scene.connectionEstablished() + // await u.waitForPageLoad() // error in guter await expect(page.locator('.cm-lint-marker-error')).toBeVisible() @@ -188,8 +190,8 @@ extrude001 = extrude(sketch001, length = 50) page.locator('.pretty-json-container >> text=myVar:"67') ).toBeVisible() }) - test('ProgramMemory can be serialised', async ({ page, homePage }) => { - const u = await getUtils(page) + test('ProgramMemory can be serialised', async ({ page, homePage, scene }) => { + // const u = await getUtils(page) await page.addInitScript(async () => { localStorage.setItem( 'persistCode', @@ -214,11 +216,12 @@ extrude001 = extrude(sketch001, length = 50) // Listen for all console events and push the message text to an array page.on('console', (message) => messages.push(message.text())) await homePage.goToModelingScene() - await u.waitForPageLoad() + // await u.waitForPageLoad() + await scene.connectionEstablished() // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') + // await u.openDebugPanel() + // await u.expectCmdLog('[data-message-type="execution-done"]') const forbiddenMessages = ['cannot serialize tagged newtype variant'] forbiddenMessages.forEach((forbiddenMessage) => { @@ -232,6 +235,7 @@ extrude001 = extrude(sketch001, length = 50) context, page, homePage, + scene, }) => { const u = await getUtils(page) // const PUR = 400 / 37.5 //pixeltoUnitRatio @@ -250,11 +254,10 @@ extrude001 = extrude(sketch001, length = 50) shell(exampleSketch, faces = ['end'], thickness = 0.25)` ) }) + await homePage.goToModelingScene() + await scene.connectionEstablished() await expect(async () => { - await homePage.goToModelingScene() - await u.waitForPageLoad() - // error in guter await expect(page.locator('.cm-lint-marker-error')).toBeVisible({ timeout: 1_000, diff --git a/e2e/playwright/sketch-tests.spec.ts b/e2e/playwright/sketch-tests.spec.ts index d02ddd808..e6f256980 100644 --- a/e2e/playwright/sketch-tests.spec.ts +++ b/e2e/playwright/sketch-tests.spec.ts @@ -1365,18 +1365,18 @@ solid001 = subtract([extrude001], tools = [extrude002]) await page.addInitScript(async () => { localStorage.setItem( 'persistCode', - `fn in2mm = (inches) => { + `fn in2mm(@inches) { return inches * 25.4 } - const railTop = in2mm(.748) - const railSide = in2mm(.024) - const railBaseWidth = in2mm(.612) - const railWideWidth = in2mm(.835) - const railBaseLength = in2mm(.200) - const railClampable = in2mm(.200) + railTop = in2mm(.748) + railSide = in2mm(.024) + railBaseWidth = in2mm(.612) + railWideWidth = in2mm(.835) + railBaseLength = in2mm(.200) + railClampable = in2mm(.200) - const rail = startSketchOn(XZ) + rail = startSketchOn(XZ) |> startProfile(at = [-railTop / 2, railClampable + railBaseLength]) |> line(endAbsolute = [ railTop / 2, @@ -3540,7 +3540,6 @@ profile001 = startProfile(sketch001, at = [127.56, 179.02]) await homePage.openProject('multi-file-sketch-test') await scene.connectionEstablished() - await scene.settled(cmdBar) await u.closeDebugPanel() @@ -3555,9 +3554,6 @@ profile001 = startProfile(sketch001, at = [127.56, 179.02]) await toolbar.openFile('error.kcl') - // Ensure filetree is populated - await scene.settled(cmdBar) - await expect( toolbar.featureTreePane.getByRole('button', { name: 'Sketch' }) ).toHaveCount(0) diff --git a/src/Toolbar.tsx b/src/Toolbar.tsx index 424f89f56..2f46a76df 100644 --- a/src/Toolbar.tsx +++ b/src/Toolbar.tsx @@ -158,7 +158,8 @@ export function Toolbar({ const isDisabled = disableAllButtons || !isConfiguredAvailable || - maybeIconConfig.disabled?.(state) === true + maybeIconConfig.disabled?.(state) === true || + kclManager.hasErrors() return { ...maybeIconConfig, @@ -444,6 +445,15 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({ contentClassName={contentClassName} > {children} + {kclManager.hasErrors() && ( +

+ + Fix KCL errors to enable tools +

+ )} ) }) diff --git a/src/components/CommandComboBox.tsx b/src/components/CommandComboBox.tsx index 0eaac2839..f8de2142e 100644 --- a/src/components/CommandComboBox.tsx +++ b/src/components/CommandComboBox.tsx @@ -136,8 +136,9 @@ function optionIsDisabled(option: Command): boolean { option.disabled || ('machineActor' in option && option.machineActor !== undefined && - !getActorNextEvents(option.machineActor.getSnapshot()).includes( + (!getActorNextEvents(option.machineActor.getSnapshot()).includes( option.name - )) + ) || + !option.machineActor?.getSnapshot().can({ type: option.name }))) ) } diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index ca31281e8..89e28022a 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -580,24 +580,23 @@ export const ModelingMachineProvider = ({ selectionRanges ) }, - 'Has exportable geometry': () => { - if (!kclManager.hasErrors() && kclManager.ast.body.length > 0) - return true - else { - let errorMessage = 'Unable to Export ' - if (kclManager.hasErrors()) errorMessage += 'due to KCL Errors' - else if (kclManager.ast.body.length === 0) - errorMessage += 'due to Empty Scene' - console.error(errorMessage) - toast.error(errorMessage) - return false - } - }, + 'Has exportable geometry': () => + !kclManager.hasErrors() && kclManager.ast.body.length > 0, }, actors: { exportFromEngine: fromPromise( async ({ input }: { input?: ModelingCommandSchema['Export'] }) => { - if (!input) { + if (kclManager.hasErrors() || kclManager.ast.body.length === 0) { + let errorMessage = 'Unable to Export ' + if (kclManager.hasErrors()) { + errorMessage += 'due to KCL Errors' + } else if (kclManager.ast.body.length === 0) { + errorMessage += 'due to Empty Scene' + } + console.error(errorMessage) + toast.error(errorMessage) + return new Error(errorMessage) + } else if (!input) { return new Error('No input provided') } diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 2c6a4b36e..acaaaa87e 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -566,6 +566,8 @@ export const modelingMachineDefaultContext: ModelingMachineContext = { planesInitialized: false, } +const NO_INPUT_PROVIDED_MESSAGE = 'No input provided' + export const modelingMachine = setup({ types: { context: {} as ModelingMachineContext, @@ -1357,6 +1359,8 @@ export const modelingMachine = setup({ }, // end actions actors: { + /* Below are all the do-constrain sketch actors, + * which aren't using updateModelingState and don't have the 'no kcl errors' guard yet */ 'do-constrain-remove-constraint': fromPromise( async ({ input: { selectionRanges, sketchDetails, data }, @@ -1694,6 +1698,9 @@ export const modelingMachine = setup({ } } ), + + /* Below are actors being defined in src/components/ModelingMachineProvider.tsx + * which aren't using updateModelingState and don't have the 'no kcl errors' guard yet */ 'Get vertical info': fromPromise( async (_: { input: Pick @@ -1775,76 +1782,167 @@ export const modelingMachine = setup({ return {} as SetSelections } ), - extrudeAstMod: fromPromise< - unknown, - ModelingCommandSchema['Extrude'] | undefined - >(async ({ input }) => { - if (!input) return Promise.reject(new Error('No input provided')) - const { nodeToEdit, sketches, length } = input - const { ast } = kclManager - const astResult = addExtrude({ - ast, - sketches, - length, - nodeToEdit, - }) - if (err(astResult)) { - return Promise.reject(new Error("Couldn't add extrude statement")) - } - - const { modifiedAst, pathToNode } = astResult - await updateModelingState( - modifiedAst, - EXECUTION_TYPE_REAL, - { - kclManager, - editorManager, - codeManager, - }, - { - focusPath: [pathToNode], + 'set-up-draft-circle': fromPromise( + async (_: { + input: Pick & { + data: [x: number, y: number] } - ) - }), - sweepAstMod: fromPromise< - unknown, - ModelingCommandSchema['Sweep'] | undefined - >(async ({ input }) => { - if (!input) return Promise.reject(new Error('No input provided')) - const { nodeToEdit, sketches, path, sectional } = input - const { ast } = kclManager - const astResult = addSweep({ - ast, - sketches, - path, - sectional, - nodeToEdit, - }) - if (err(astResult)) { - return Promise.reject(astResult) + }) => { + return {} as SketchDetailsUpdate } - - const { modifiedAst, pathToNode } = astResult - await updateModelingState( - modifiedAst, - EXECUTION_TYPE_REAL, - { - kclManager, - editorManager, - codeManager, - }, - { - focusPath: [pathToNode], + ), + 'set-up-draft-circle-three-point': fromPromise( + async (_: { + input: Pick & { + data: { p1: [x: number, y: number]; p2: [x: number, y: number] } } - ) - }), + }) => { + return {} as SketchDetailsUpdate + } + ), + 'set-up-draft-rectangle': fromPromise( + async (_: { + input: Pick & { + data: [x: number, y: number] + } + }) => { + return {} as SketchDetailsUpdate + } + ), + 'set-up-draft-center-rectangle': fromPromise( + async (_: { + input: Pick & { + data: [x: number, y: number] + } + }) => { + return {} as SketchDetailsUpdate + } + ), + 'set-up-draft-arc': fromPromise( + async (_: { + input: Pick & { + data: [x: number, y: number] + } + }) => { + return {} as SketchDetailsUpdate + } + ), + 'set-up-draft-arc-three-point': fromPromise( + async (_: { + input: Pick & { + data: [x: number, y: number] + } + }) => { + return {} as SketchDetailsUpdate + } + ), + 'setup-client-side-sketch-segments': fromPromise( + async (_: { + input: Pick + }) => { + return undefined + } + ), + 'split-sketch-pipe-if-needed': fromPromise( + async (_: { input: Pick }) => { + return {} as SketchDetailsUpdate + } + ), + 'submit-prompt-edit': fromPromise( + async ({ + input, + }: { + input: ModelingCommandSchema['Prompt-to-edit'] + }) => {} + ), + + /* Below are recent modeling codemods that are using updateModelinState, + * trigger toastError on Error, and have the 'no kcl errors' guard yet */ + extrudeAstMod: fromPromise( + async ({ + input, + }: { + input: ModelingCommandSchema['Extrude'] | undefined + }) => { + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + + const { nodeToEdit, sketches, length } = input + const { ast } = kclManager + const astResult = addExtrude({ + ast, + sketches, + length, + nodeToEdit, + }) + if (err(astResult)) { + return Promise.reject(new Error("Couldn't add extrude statement")) + } + + const { modifiedAst, pathToNode } = astResult + await updateModelingState( + modifiedAst, + EXECUTION_TYPE_REAL, + { + kclManager, + editorManager, + codeManager, + }, + { + focusPath: [pathToNode], + } + ) + } + ), + sweepAstMod: fromPromise( + async ({ + input, + }: { + input: ModelingCommandSchema['Sweep'] | undefined + }) => { + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + + const { nodeToEdit, sketches, path, sectional } = input + const { ast } = kclManager + const astResult = addSweep({ + ast, + sketches, + path, + sectional, + nodeToEdit, + }) + if (err(astResult)) { + return Promise.reject(astResult) + } + + const { modifiedAst, pathToNode } = astResult + await updateModelingState( + modifiedAst, + EXECUTION_TYPE_REAL, + { + kclManager, + editorManager, + codeManager, + }, + { + focusPath: [pathToNode], + } + ) + } + ), loftAstMod: fromPromise( async ({ input, }: { input: ModelingCommandSchema['Loft'] | undefined }) => { - if (!input) return Promise.reject(new Error('No input provided')) + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + const { sketches } = input const { ast } = kclManager const astResult = addLoft({ ast, sketches }) @@ -1867,47 +1965,56 @@ export const modelingMachine = setup({ ) } ), - revolveAstMod: fromPromise< - unknown, - ModelingCommandSchema['Revolve'] | undefined - >(async ({ input }) => { - if (!input) return Promise.reject(new Error('No input provided')) - const { nodeToEdit, sketches, angle, axis, edge, axisOrEdge } = input - const { ast } = kclManager - const astResult = addRevolve({ - ast, - sketches, - angle, - axisOrEdge, - axis, - edge, - nodeToEdit, - }) - if (err(astResult)) { - return Promise.reject(astResult) - } - - const { modifiedAst, pathToNode } = astResult - await updateModelingState( - modifiedAst, - EXECUTION_TYPE_REAL, - { - kclManager, - editorManager, - codeManager, - }, - { - focusPath: [pathToNode], + revolveAstMod: fromPromise( + async ({ + input, + }: { + input: ModelingCommandSchema['Revolve'] | undefined + }) => { + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) } - ) - }), + + const { nodeToEdit, sketches, angle, axis, edge, axisOrEdge } = input + const { ast } = kclManager + const astResult = addRevolve({ + ast, + sketches, + angle, + axisOrEdge, + axis, + edge, + nodeToEdit, + }) + if (err(astResult)) { + return Promise.reject(astResult) + } + + const { modifiedAst, pathToNode } = astResult + await updateModelingState( + modifiedAst, + EXECUTION_TYPE_REAL, + { + kclManager, + editorManager, + codeManager, + }, + { + focusPath: [pathToNode], + } + ) + } + ), offsetPlaneAstMod: fromPromise( async ({ input, }: { input: ModelingCommandSchema['Offset plane'] | undefined }) => { - if (!input) return new Error('No input provided') + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + // Extract inputs const ast = kclManager.ast const { plane: selection, distance, nodeToEdit } = input @@ -1989,9 +2096,10 @@ export const modelingMachine = setup({ }: { input: ModelingCommandSchema['Helix'] | undefined }) => { - if (!input) return new Error('No input provided') + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } // Extract inputs - console.log('input', input) const ast = kclManager.ast const { mode, @@ -2043,7 +2151,7 @@ export const modelingMachine = setup({ cylinder.graphSelections[0].artifact?.type === 'wall' ) ) { - return new Error('Cylinder argument not valid') + return Promise.reject(new Error('Cylinder argument not valid')) } const clonedAstForGetExtrude = structuredClone(ast) const extrudeLookupResult = getPathToExtrudeForSegmentSelection( @@ -2052,7 +2160,7 @@ export const modelingMachine = setup({ kclManager.artifactGraph ) if (err(extrudeLookupResult)) { - return extrudeLookupResult + return Promise.reject(extrudeLookupResult) } const extrudeNode = getNodeFromPath( ast, @@ -2060,18 +2168,20 @@ export const modelingMachine = setup({ 'VariableDeclaration' ) if (err(extrudeNode)) { - return extrudeNode + return Promise.reject(extrudeNode) } cylinderDeclarator = extrudeNode.node.declaration } else if (mode === 'Axis' || mode === 'Edge') { const getAxisResult = getAxisExpressionAndIndex(mode, axis, edge, ast) if (err(getAxisResult)) { - return getAxisResult + return Promise.reject(getAxisResult) } axisExpression = getAxisResult.generatedAxis } else { - return new Error( - 'Generated axis or cylinder declarator selection is missing.' + return Promise.reject( + new Error( + 'Generated axis or cylinder declarator selection is missing.' + ) ) } @@ -2132,7 +2242,7 @@ export const modelingMachine = setup({ input: ModelingCommandSchema['Shell'] | undefined }) => { if (!input) { - return new Error('No input provided') + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) } // Extract inputs @@ -2173,9 +2283,11 @@ export const modelingMachine = setup({ kclManager.artifactGraph ) if (err(extrudeLookupResult)) { - return new Error( - "Couldn't find extrude paths from getPathToExtrudeForSegmentSelection", - { cause: extrudeLookupResult } + return Promise.reject( + new Error( + "Couldn't find extrude paths from getPathToExtrudeForSegmentSelection", + { cause: extrudeLookupResult } + ) ) } @@ -2196,9 +2308,11 @@ export const modelingMachine = setup({ 'VariableDeclaration' ) if (err(segmentNode)) { - return new Error("Couldn't find segment node from selection", { - cause: segmentNode, - }) + return Promise.reject( + new Error("Couldn't find segment node from selection", { + cause: segmentNode, + }) + ) } if (extrudeNode.node.declaration.init.type === 'CallExpressionKw') { @@ -2208,15 +2322,17 @@ export const modelingMachine = setup({ ) { pathToExtrudeNode = extrudeLookupResult.pathToSegmentNode } else { - return new Error( - "Couldn't find extrude node that was either a call expression or a pipe", - { cause: segmentNode } + return Promise.reject( + new Error( + "Couldn't find extrude node that was either a call expression or a pipe", + { cause: segmentNode } + ) ) } const selectedArtifact = graphSelection.artifact if (!selectedArtifact) { - return new Error('Bad artifact from selection') + return Promise.reject(new Error('Bad artifact from selection')) } // Check on the selection, and handle the wall vs cap cases @@ -2229,20 +2345,22 @@ export const modelingMachine = setup({ extrudeLookupResult.pathToSegmentNode ) if (err(tagResult)) { - return tagResult + return Promise.reject(tagResult) } const { tag } = tagResult expr = createLocalName(tag) } else { - return new Error('Artifact is neither a cap nor a wall') + return Promise.reject( + new Error('Artifact is neither a cap nor a wall') + ) } faces.push(expr) } if (!pathToExtrudeNode) { - return new Error('No path to extrude node found') + return Promise.reject(new Error('No path to extrude node found')) } const extrudeNode = getNodeFromPath( @@ -2251,9 +2369,11 @@ export const modelingMachine = setup({ 'VariableDeclarator' ) if (err(extrudeNode)) { - return new Error("Couldn't find extrude node", { - cause: extrudeNode, - }) + return Promise.reject( + new Error("Couldn't find extrude node", { + cause: extrudeNode, + }) + ) } // Perform the shell op @@ -2308,7 +2428,7 @@ export const modelingMachine = setup({ input: ModelingCommandSchema['Fillet'] | undefined }) => { if (!input) { - return new Error('No input provided') + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) } // Extract inputs @@ -2401,7 +2521,7 @@ export const modelingMachine = setup({ input: ModelingCommandSchema['Chamfer'] | undefined }) => { if (!input) { - return Promise.reject(new Error('No input provided')) + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) } // Extract inputs @@ -2492,10 +2612,13 @@ export const modelingMachine = setup({ }: { input: ModelingCommandSchema['event.parameter.create'] | undefined }) => { - if (!input) return new Error('No input provided') + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + const { value } = input if (!('variableName' in value)) { - return new Error('variable name is required') + return Promise.reject(new Error('variable name is required')) } const newAst = insertNamedConstant({ node: kclManager.ast, @@ -2514,7 +2637,10 @@ export const modelingMachine = setup({ }: { input: ModelingCommandSchema['event.parameter.edit'] | undefined }) => { - if (!input) return new Error('No input provided') + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + // Get the variable AST node to edit const { nodeToEdit, value } = input const newAst = structuredClone(kclManager.ast) @@ -2528,7 +2654,7 @@ export const modelingMachine = setup({ variableNode.node.type !== 'VariableDeclarator' || !variableNode.node ) { - return new Error('No variable found, this is a bug') + return Promise.reject(new Error('No variable found, this is a bug')) } // Mutate the variable's value @@ -2541,79 +2667,6 @@ export const modelingMachine = setup({ }) } ), - 'set-up-draft-circle': fromPromise( - async (_: { - input: Pick & { - data: [x: number, y: number] - } - }) => { - return {} as SketchDetailsUpdate - } - ), - 'set-up-draft-circle-three-point': fromPromise( - async (_: { - input: Pick & { - data: { p1: [x: number, y: number]; p2: [x: number, y: number] } - } - }) => { - return {} as SketchDetailsUpdate - } - ), - 'set-up-draft-rectangle': fromPromise( - async (_: { - input: Pick & { - data: [x: number, y: number] - } - }) => { - return {} as SketchDetailsUpdate - } - ), - 'set-up-draft-center-rectangle': fromPromise( - async (_: { - input: Pick & { - data: [x: number, y: number] - } - }) => { - return {} as SketchDetailsUpdate - } - ), - 'set-up-draft-arc': fromPromise( - async (_: { - input: Pick & { - data: [x: number, y: number] - } - }) => { - return {} as SketchDetailsUpdate - } - ), - 'set-up-draft-arc-three-point': fromPromise( - async (_: { - input: Pick & { - data: [x: number, y: number] - } - }) => { - return {} as SketchDetailsUpdate - } - ), - 'setup-client-side-sketch-segments': fromPromise( - async (_: { - input: Pick - }) => { - return undefined - } - ), - 'split-sketch-pipe-if-needed': fromPromise( - async (_: { input: Pick }) => { - return {} as SketchDetailsUpdate - } - ), - 'submit-prompt-edit': fromPromise( - async ({ - input, - }: { - input: ModelingCommandSchema['Prompt-to-edit'] - }) => {} - ), deleteSelectionAstMod: fromPromise( ({ input: { selectionRanges }, @@ -2648,12 +2701,15 @@ export const modelingMachine = setup({ }: { input: ModelingCommandSchema['Appearance'] | undefined }) => { - if (!input) return new Error('No input provided') + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + // Extract inputs const ast = kclManager.ast const { color, nodeToEdit } = input if (!(nodeToEdit && typeof nodeToEdit[1][0] === 'number')) { - return new Error('Appearance is only an edit flow') + return Promise.reject(new Error('Appearance is only an edit flow')) } const result = setAppearance({ @@ -2663,7 +2719,7 @@ export const modelingMachine = setup({ }) if (err(result)) { - return err(result) + return Promise.reject(err(result)) } await updateModelingState( @@ -2686,7 +2742,10 @@ export const modelingMachine = setup({ }: { input: ModelingCommandSchema['Translate'] | undefined }) => { - if (!input) return Promise.reject(new Error('No input provided')) + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + const ast = kclManager.ast const modifiedAst = structuredClone(ast) const { x, y, z, nodeToEdit, selection } = input @@ -2764,7 +2823,10 @@ export const modelingMachine = setup({ }: { input: ModelingCommandSchema['Rotate'] | undefined }) => { - if (!input) return Promise.reject(new Error('No input provided')) + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + const ast = kclManager.ast const modifiedAst = structuredClone(ast) const { roll, pitch, yaw, nodeToEdit, selection } = input @@ -2842,7 +2904,10 @@ export const modelingMachine = setup({ }: { input: ModelingCommandSchema['Clone'] | undefined }) => { - if (!input) return Promise.reject(new Error('No input provided')) + if (!input) { + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) + } + const ast = kclManager.ast const { nodeToEdit, selection, variableName } = input let pathToNode = nodeToEdit @@ -2933,15 +2998,17 @@ export const modelingMachine = setup({ input: ModelingCommandSchema['Boolean Subtract'] | undefined }) => { if (!input) { - return new Error('No input provided') + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) } + const { target, tool } = input if ( !target.graphSelections[0].artifact || !tool.graphSelections[0].artifact ) { - return new Error('No artifact in selections found') + return Promise.reject(new Error('No artifact in selections found')) } + await applySubtractFromTargetOperatorSelections( target.graphSelections[0], tool.graphSelections[0], @@ -2961,12 +3028,14 @@ export const modelingMachine = setup({ input: ModelingCommandSchema['Boolean Union'] | undefined }) => { if (!input) { - return new Error('No input provided') + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) } + const { solids } = input if (!solids.graphSelections[0].artifact) { - return new Error('No artifact in selections found') + return Promise.reject(new Error('No artifact in selections found')) } + await applyUnionFromTargetOperatorSelections(solids, { kclManager, codeManager, @@ -2982,12 +3051,14 @@ export const modelingMachine = setup({ input: ModelingCommandSchema['Boolean Union'] | undefined }) => { if (!input) { - return new Error('No input provided') + return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) } + const { solids } = input if (!solids.graphSelections[0].artifact) { - return new Error('No artifact in selections found') + return Promise.reject(new Error('No artifact in selections found')) } + await applyIntersectFromTargetOperatorSelections(solids, { kclManager, codeManager, @@ -2996,6 +3067,8 @@ export const modelingMachine = setup({ }) } ), + + /* Pierre: looks like somewhat of a one-off */ 'reeval-node-paths': fromPromise( async ({ input: { sketchDetails }, @@ -3096,50 +3169,74 @@ export const modelingMachine = setup({ target: 'animating to existing sketch', guard: 'Selection is on face', }, - 'Sketch no face', + { + target: 'Sketch no face', + guard: 'no kcl errors', + }, ], Extrude: { target: 'Applying extrude', reenter: true, - }, - - Revolve: { - target: 'Applying revolve', - reenter: true, + guard: 'no kcl errors', }, Sweep: { target: 'Applying sweep', reenter: true, + guard: 'no kcl errors', }, Loft: { target: 'Applying loft', reenter: true, + guard: 'no kcl errors', + }, + + Revolve: { + target: 'Applying revolve', + reenter: true, + guard: 'no kcl errors', + }, + + 'Offset plane': { + target: 'Applying offset plane', + reenter: true, + guard: 'no kcl errors', + }, + + Helix: { + target: 'Applying helix', + reenter: true, + guard: 'no kcl errors', }, Shell: { target: 'Applying shell', reenter: true, + guard: 'no kcl errors', }, Fillet: { target: 'Applying fillet', reenter: true, + guard: 'no kcl errors', }, Chamfer: { target: 'Applying chamfer', reenter: true, + guard: 'no kcl errors', }, 'event.parameter.create': { target: '#Modeling.parameter.creating', + guard: 'no kcl errors', }, 'event.parameter.edit': { target: '#Modeling.parameter.editing', + guard: 'no kcl errors', }, Export: { @@ -3164,41 +3261,44 @@ export const modelingMachine = setup({ actions: ['Submit to Text-to-CAD API'], }, - 'Offset plane': { - target: 'Applying offset plane', - reenter: true, - }, - - Helix: { - target: 'Applying helix', - reenter: true, - }, - 'Prompt-to-edit': 'Applying Prompt-to-edit', Appearance: { target: 'Applying appearance', reenter: true, + guard: 'no kcl errors', }, Translate: { target: 'Applying translate', reenter: true, + guard: 'no kcl errors', }, Rotate: { target: 'Applying rotate', reenter: true, + guard: 'no kcl errors', }, Clone: { target: 'Applying clone', reenter: true, + guard: 'no kcl errors', }, - 'Boolean Subtract': 'Boolean subtracting', - 'Boolean Union': 'Boolean uniting', - 'Boolean Intersect': 'Boolean intersecting', + 'Boolean Subtract': { + target: 'Boolean subtracting', + guard: 'no kcl errors', + }, + 'Boolean Union': { + target: 'Boolean uniting', + guard: 'no kcl errors', + }, + 'Boolean Intersect': { + target: 'Boolean intersecting', + guard: 'no kcl errors', + }, }, entry: 'reset client scene mouse handlers', @@ -4415,6 +4515,38 @@ export const modelingMachine = setup({ }, }, + 'Applying sweep': { + invoke: { + src: 'sweepAstMod', + id: 'sweepAstMod', + input: ({ event }) => { + if (event.type !== 'Sweep') return undefined + return event.data + }, + onDone: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, + }, + }, + + 'Applying loft': { + invoke: { + src: 'loftAstMod', + id: 'loftAstMod', + input: ({ event }) => { + if (event.type !== 'Loft') return undefined + return event.data + }, + onDone: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, + }, + }, + 'Applying revolve': { invoke: { src: 'revolveAstMod', @@ -4440,7 +4572,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['idle'], - onError: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4453,33 +4588,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['idle'], - onError: ['idle'], - }, - }, - - 'Applying sweep': { - invoke: { - src: 'sweepAstMod', - id: 'sweepAstMod', - input: ({ event }) => { - if (event.type !== 'Sweep') return undefined - return event.data + onError: { + target: 'idle', + actions: 'toastError', }, - onDone: ['idle'], - onError: ['idle'], - }, - }, - - 'Applying loft': { - invoke: { - src: 'loftAstMod', - id: 'loftAstMod', - input: ({ event }) => { - if (event.type !== 'Loft') return undefined - return event.data - }, - onDone: ['idle'], - onError: ['idle'], }, }, @@ -4492,7 +4604,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['idle'], - onError: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4505,7 +4620,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['idle'], - onError: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4518,7 +4636,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['idle'], - onError: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4535,7 +4656,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['#Modeling.idle'], - onError: ['#Modeling.idle'], + onError: { + target: '#Modeling.idle', + actions: 'toastError', + }, }, }, editing: { @@ -4547,7 +4671,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['#Modeling.idle'], - onError: ['#Modeling.idle'], + onError: { + target: '#Modeling.idle', + actions: 'toastError', + }, }, }, }, @@ -4604,7 +4731,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['idle'], - onError: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4617,7 +4747,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['idle'], - onError: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4630,7 +4763,10 @@ export const modelingMachine = setup({ return event.data }, onDone: ['idle'], - onError: ['idle'], + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4686,7 +4822,10 @@ export const modelingMachine = setup({ input: ({ event }) => event.type !== 'Boolean Subtract' ? undefined : event.data, onDone: 'idle', - onError: 'idle', + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4697,7 +4836,10 @@ export const modelingMachine = setup({ input: ({ event }) => event.type !== 'Boolean Union' ? undefined : event.data, onDone: 'idle', - onError: 'idle', + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, @@ -4708,7 +4850,10 @@ export const modelingMachine = setup({ input: ({ event }) => event.type !== 'Boolean Intersect' ? undefined : event.data, onDone: 'idle', - onError: 'idle', + onError: { + target: 'idle', + actions: 'toastError', + }, }, }, },