diff --git a/e2e/playwright/testing-constraints.spec.ts b/e2e/playwright/testing-constraints.spec.ts index c4644257b..026568781 100644 --- a/e2e/playwright/testing-constraints.spec.ts +++ b/e2e/playwright/testing-constraints.spec.ts @@ -26,7 +26,17 @@ test.describe('Testing constraints', () => { }) const u = await getUtils(page) - const PUR = 400 / 37.5 //pixeltoUnitRatio + // constants and locators + const lengthValue = { + old: '20', + new: '25', + } + const cmdBarKclInput = page + .getByTestId('cmd-bar-arg-value') + .getByRole('textbox') + const cmdBarSubmitButton = page.getByRole('button', { + name: 'arrow right Continue', + }) await page.setViewportSize({ width: 1200, height: 500 }) await u.waitForAuthSkipAppStart() @@ -36,26 +46,26 @@ test.describe('Testing constraints', () => { await u.closeDebugPanel() // Click the line of code for line. - await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick() + // TODO remove this and reinstate `await topHorzSegmentClick()` + await page.getByText(`line([0, ${lengthValue.old}], %)`).click() await page.waitForTimeout(100) // enter sketch again await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.waitForTimeout(500) // wait for animation - - const startXPx = 500 - await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10) - await page.keyboard.down('Shift') - await page.mouse.click(834, 244) - await page.keyboard.up('Shift') - await page .getByRole('button', { name: 'dimension Length', exact: true }) .click() - await page.getByText('Add constraining value').click() + await expect(cmdBarKclInput).toHaveText('20') + await cmdBarKclInput.fill(lengthValue.new) + await expect( + page.getByText(`Can't calculate`), + `Something went wrong with the KCL expression evaluation` + ).not.toBeVisible() + await cmdBarSubmitButton.click() await expect(page.locator('.cm-content')).toHaveText( - `length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)` + `length001 = ${lengthValue.new}sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)` ) // Make sure we didn't pop out of sketch mode. @@ -66,7 +76,6 @@ test.describe('Testing constraints', () => { await page.waitForTimeout(500) // wait for animation // Exit sketch - await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10) await page.keyboard.press('Escape') await expect( page.getByRole('button', { name: 'Exit Sketch' }) @@ -524,7 +533,7 @@ part002 = startSketchOn('XZ') }) } }) - test.describe('Test Angle/Length constraint single selection', () => { + test.describe('Test Angle constraint single selection', () => { const cases = [ { testName: 'Angle - Add variable', @@ -538,18 +547,6 @@ part002 = startSketchOn('XZ') 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, constraint } of cases) { test(`${testName}`, async ({ page }) => { @@ -608,6 +605,90 @@ part002 = startSketchOn('XZ') }) } }) + test.describe('Test Length constraint single selection', () => { + const cases = [ + { + 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, constraint } of cases) { + test(`${testName}`, async ({ page }) => { + // constants and locators + const cmdBarKclInput = page + .getByTestId('cmd-bar-arg-value') + .getByRole('textbox') + const cmdBarKclVariableNameInput = + page.getByPlaceholder('Variable name') + const cmdBarSubmitButton = page.getByRole('button', { + name: 'arrow right Continue', + }) + + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `yo = 5 +part001 = startSketchOn('XZ') + |> startProfileAt([-7.54, -26.74], %) + |> line([74.36, 130.4], %) + |> line([78.92, -120.11], %) + |> line([9.16, 77.79], %) + |> line([51.19, 48.97], %) +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 u.waitForAuthSkipAppStart() + + await page.getByText('line([74.36, 130.4], %)').click() + await page.getByRole('button', { name: 'Edit Sketch' }).click() + + const line3 = await u.getSegmentBodyCoords( + `[data-overlay-index="${2}"]` + ) + + await page.mouse.click(line3.x, line3.y) + await page + .getByRole('button', { + name: 'Length: open menu', + }) + .click() + await page.getByTestId('dropdown-constraint-' + constraint).click() + + if (!addVariable) { + await test.step(`Clear the variable input`, async () => { + await cmdBarKclVariableNameInput.clear() + await cmdBarKclVariableNameInput.press('Backspace') + }) + } + await expect(cmdBarKclInput).toHaveText('78.33') + await cmdBarSubmitButton.click() + + 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) + + // 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('Many segments - no modal constraints', () => { const cases = [ { @@ -868,6 +949,15 @@ part002 = startSketchOn('XZ') |> line([3.13, -2.4], %)` ) }) + + // constants and locators + const cmdBarKclInput = page + .getByTestId('cmd-bar-arg-value') + .getByRole('textbox') + const cmdBarSubmitButton = page.getByRole('button', { + name: 'arrow right Continue', + }) + const u = await getUtils(page) await page.setViewportSize({ width: 1200, height: 500 }) @@ -928,8 +1018,8 @@ part002 = startSketchOn('XZ') // await page.getByRole('button', { name: 'length', exact: true }).click() await page.getByTestId('dropdown-constraint-length').click() - await page.getByLabel('length Value').fill('10') - await page.getByRole('button', { name: 'Add constraining value' }).click() + await cmdBarKclInput.fill('10') + await cmdBarSubmitButton.click() activeLinesContent = await page.locator('.cm-activeLine').all() await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`) diff --git a/e2e/playwright/testing-segment-overlays.spec.ts b/e2e/playwright/testing-segment-overlays.spec.ts index 50c3ce9ff..cc8a369d5 100644 --- a/e2e/playwright/testing-segment-overlays.spec.ts +++ b/e2e/playwright/testing-segment-overlays.spec.ts @@ -91,7 +91,14 @@ test.describe('Testing segment overlays', () => { await page.getByTestId('constraint-symbol-popover').count() ).toBeGreaterThan(0) await unconstrainedLocator.click() - await page.getByText('Add variable').click() + await expect( + page.getByTestId('cmd-bar-arg-value').getByRole('textbox') + ).toBeFocused() + await page + .getByRole('button', { + name: 'arrow right Continue', + }) + .click() await expect(page.locator('.cm-content')).toContainText(expectFinal) } @@ -151,7 +158,14 @@ test.describe('Testing segment overlays', () => { await page.getByTestId('constraint-symbol-popover').count() ).toBeGreaterThan(0) await unconstrainedLocator.click() - await page.getByText('Add variable').click() + await expect( + page.getByTestId('cmd-bar-arg-value').getByRole('textbox') + ).toBeFocused() + await page + .getByRole('button', { + name: 'arrow right Continue', + }) + .click() await expect(page.locator('.cm-content')).toContainText( expectAfterUnconstrained ) diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index f83be4740..b8031e3f8 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -505,7 +505,8 @@ const ConstraintSymbol = ({ constrainInfo: ConstrainInfo verticalPosition: 'top' | 'bottom' }) => { - const { context, send } = useModelingContext() + const { commandBarSend } = useCommandsContext() + const { context } = useModelingContext() const varNameMap: { [key in ConstrainInfo['type']]: { varName: string @@ -624,11 +625,18 @@ const ConstraintSymbol = ({ // disabled={implicitDesc} TODO why does this change styles that are hard to override? onClick={toSync(async () => { if (!isConstrained) { - send({ - type: 'Convert to variable', + commandBarSend({ + type: 'Find and select command', data: { - pathToNode, - variableName: varName, + name: 'Constrain with named value', + groupId: 'modeling', + argDefaultValues: { + currentValue: { + pathToNode, + variableName: varName, + valueText: value, + }, + }, }, }) } else if (isConstrained) { diff --git a/src/components/CommandBar/CommandBarKclInput.tsx b/src/components/CommandBar/CommandBarKclInput.tsx index 78e66aa2b..54c47e4d4 100644 --- a/src/components/CommandBar/CommandBarKclInput.tsx +++ b/src/components/CommandBar/CommandBarKclInput.tsx @@ -8,11 +8,16 @@ import { getSystemTheme } from 'lib/theme' import { useCalculateKclExpression } from 'lib/useCalculateKclExpression' import { roundOff } from 'lib/utils' import { varMentions } from 'lib/varCompletionExtension' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useHotkeys } from 'react-hotkeys-hook' import styles from './CommandBarKclInput.module.css' import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst' import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor' +import { useSelector } from '@xstate/react' + +const machineContextSelector = (snapshot?: { + context: Record +}) => snapshot?.context function CommandBarKclInput({ arg, @@ -31,12 +36,44 @@ function CommandBarKclInput({ arg.name ] as KclCommandValue | undefined const { settings } = useSettingsAuthContext() - const defaultValue = (arg.defaultValue as string) || '' + const argMachineContext = useSelector( + arg.machineActor, + machineContextSelector + ) + const defaultValue = useMemo( + () => + arg.defaultValue + ? arg.defaultValue instanceof Function + ? arg.defaultValue(commandBarState.context, argMachineContext) + : arg.defaultValue + : '', + [arg.defaultValue, commandBarState.context, argMachineContext] + ) + const initialVariableName = useMemo(() => { + // Use the configured variable name if it exists + if (arg.variableName !== undefined) { + return arg.variableName instanceof Function + ? arg.variableName(commandBarState.context, argMachineContext) + : arg.variableName + } + // or derive it from the previously set value or the argument name + return previouslySetValue && 'variableName' in previouslySetValue + ? previouslySetValue.variableName + : arg.name + }, [ + arg.variableName, + commandBarState.context, + argMachineContext, + arg.name, + previouslySetValue, + ]) const [value, setValue] = useState( previouslySetValue?.valueText || defaultValue || '' ) const [createNewVariable, setCreateNewVariable] = useState( - previouslySetValue && 'variableName' in previouslySetValue + (previouslySetValue && 'variableName' in previouslySetValue) || + arg.createVariableByDefault || + false ) const [canSubmit, setCanSubmit] = useState(true) useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) @@ -52,10 +89,7 @@ function CommandBarKclInput({ isNewVariableNameUnique, } = useCalculateKclExpression({ value, - initialVariableName: - previouslySetValue && 'variableName' in previouslySetValue - ? previouslySetValue.variableName - : arg.name, + initialVariableName, }) const varMentionData: Completion[] = prevVariables.map((v) => ({ label: v.key, diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 66ab960ff..144345c1d 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -41,7 +41,10 @@ import { angleBetweenInfo, applyConstraintAngleBetween, } from './Toolbar/SetAngleBetween' -import { applyConstraintAngleLength } from './Toolbar/setAngleLength' +import { + applyConstraintAngleLength, + applyConstraintLength, +} from './Toolbar/setAngleLength' import { canSweepSelection, handleSelectionBatch, @@ -63,12 +66,13 @@ import { getSketchOrientationDetails, } from 'clientSideScene/sceneEntities' import { - moveValueIntoNewVariablePath, + insertNamedConstant, + replaceValueAtNodePath, sketchOnExtrudedFace, sketchOnOffsetPlane, startSketchOnDefault, } from 'lang/modifyAst' -import { Program, parse, recast, resultIsOk } from 'lang/wasm' +import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm' import { doesSceneHaveExtrudedSketch, doesSceneHaveSweepableSketch, @@ -81,7 +85,6 @@ 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 { getVarNameModal } from 'hooks/useToolbarGuards' import { err, reportRejection, trap } from 'lib/trap' import { useCommandsContext } from 'hooks/useCommandsContext' import { modelingMachineEvent } from 'editor/manager' @@ -889,12 +892,18 @@ export const ModelingMachineProvider = ({ } } ), - 'Get length info': fromPromise( - async ({ input: { selectionRanges, sketchDetails } }) => { - const { modifiedAst, pathToNodeMap } = - await applyConstraintAngleLength({ - selectionRanges, - }) + astConstrainLength: fromPromise( + async ({ + input: { selectionRanges, sketchDetails, lengthValue }, + }) => { + if (!lengthValue) + return Promise.reject(new Error('No length value')) + const constraintResult = await applyConstraintLength({ + selectionRanges, + length: lengthValue, + }) + if (err(constraintResult)) return Promise.reject(constraintResult) + const { modifiedAst, pathToNodeMap } = constraintResult const pResult = parse(recast(modifiedAst)) if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(new Error('Unexpected compilation error')) @@ -1063,38 +1072,88 @@ export const ModelingMachineProvider = ({ } } ), - 'Get convert to variable info': fromPromise( + 'Apply named value constraint': fromPromise( async ({ input: { selectionRanges, sketchDetails, data } }) => { - if (!sketchDetails) + if (!sketchDetails) { return Promise.reject(new Error('No sketch details')) - const { variableName } = await getVarNameModal({ - valueName: data?.variableName || 'var', - }) + } + if (!data) { + return Promise.reject(new Error('No data from command flow')) + } let pResult = parse(recast(kclManager.ast)) if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(new Error('Unexpected compilation error')) let parsed = pResult.program - const { modifiedAst: _modifiedAst, pathToReplacedNode } = - moveValueIntoNewVariablePath( - parsed, - kclManager.programMemory, - data?.pathToNode || [], - variableName + let result: { + modifiedAst: Node + pathToReplaced: PathToNode | null + } = { + modifiedAst: parsed, + pathToReplaced: null, + } + // If the user provided a constant name, + // we need to insert the named constant + // and then replace the node with the constant's name. + if ('variableName' in data.namedValue) { + const astAfterReplacement = replaceValueAtNodePath({ + ast: parsed, + pathToNode: data.currentValue.pathToNode, + newExpressionString: data.namedValue.variableName, + }) + if (trap(astAfterReplacement)) { + return Promise.reject(astAfterReplacement) + } + const parseResultAfterInsertion = parse( + recast( + insertNamedConstant({ + node: astAfterReplacement.modifiedAst, + newExpression: data.namedValue, + }) + ) ) - pResult = parse(recast(_modifiedAst)) + if ( + trap(parseResultAfterInsertion) || + !resultIsOk(parseResultAfterInsertion) + ) + return Promise.reject(parseResultAfterInsertion) + result = { + modifiedAst: parseResultAfterInsertion.program, + pathToReplaced: astAfterReplacement.pathToReplaced, + } + } else if ('valueText' in data.namedValue) { + // If they didn't provide a constant name, + // just replace the node with the value. + const astAfterReplacement = replaceValueAtNodePath({ + ast: parsed, + pathToNode: data.currentValue.pathToNode, + newExpressionString: data.namedValue.valueText, + }) + if (trap(astAfterReplacement)) { + return Promise.reject(astAfterReplacement) + } + // The `replacer` function returns a pathToNode that assumes + // an identifier is also being inserted into the AST, creating an off-by-one error. + // This corrects that error, but TODO we should fix this upstream + // to avoid this kind of error in the future. + astAfterReplacement.pathToReplaced[1][0] = + (astAfterReplacement.pathToReplaced[1][0] as number) - 1 + result = astAfterReplacement + } + + pResult = parse(recast(result.modifiedAst)) if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(new Error('Unexpected compilation error')) parsed = pResult.program if (trap(parsed)) return Promise.reject(parsed) parsed = parsed as Node - if (!pathToReplacedNode) + if (!result.pathToReplaced) return Promise.reject(new Error('No path to replaced node')) const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( - pathToReplacedNode || [], + result.pathToReplaced || [], parsed, sketchDetails.zAxis, sketchDetails.yAxis, @@ -1107,7 +1166,7 @@ export const ModelingMachineProvider = ({ ) const selection = updateSelections( - { 0: pathToReplacedNode }, + { 0: result.pathToReplaced }, selectionRanges, updatedAst.newAst ) @@ -1115,7 +1174,7 @@ export const ModelingMachineProvider = ({ return { selectionType: 'completeSelection', selection, - updatedPathToNode: pathToReplacedNode, + updatedPathToNode: result.pathToReplaced, } } ), diff --git a/src/components/Toolbar/setAngleLength.tsx b/src/components/Toolbar/setAngleLength.tsx index a0744735d..5453ef684 100644 --- a/src/components/Toolbar/setAngleLength.tsx +++ b/src/components/Toolbar/setAngleLength.tsx @@ -22,6 +22,7 @@ import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { normaliseAngle } from '../../lib/utils' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' +import { KclCommandValue } from 'lib/commandTypes' const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal) @@ -63,6 +64,57 @@ export function angleLengthInfo({ return { enabled, transforms } } +export async function applyConstraintLength({ + length, + selectionRanges, +}: { + length: KclCommandValue + selectionRanges: Selections +}) { + const ast = kclManager.ast + const angleLength = angleLengthInfo({ selectionRanges }) + if (err(angleLength)) return angleLength + const { transforms } = angleLength + + let distanceExpression: Expr = length.valueAst + + /** + * To be "constrained", the value must be a binary expression, a named value, or a function call. + * If it has a variable name, we need to insert a variable declaration at the correct index. + */ + if ( + 'variableName' in length && + length.variableName && + length.insertIndex !== undefined + ) { + const newBody = [...ast.body] + newBody.splice(length.insertIndex, 0, length.variableDeclarationAst) + ast.body = newBody + distanceExpression = createIdentifier(length.variableName) + } + + if (!isExprBinaryPart(distanceExpression)) { + return new Error('Invalid valueNode, is not a BinaryPart') + } + + const retval = transformAstSketchLines({ + ast, + selectionRanges, + transformInfos: transforms, + programMemory: kclManager.programMemory, + referenceSegName: '', + forceValueUsedInTransform: distanceExpression, + }) + if (err(retval)) return Promise.reject(retval) + + const { modifiedAst: _modifiedAst, pathToNodeMap } = retval + + return { + modifiedAst: _modifiedAst, + pathToNodeMap, + } +} + export async function applyConstraintAngleLength({ selectionRanges, angleOrLength = 'setLength', diff --git a/src/hooks/useToolbarGuards.ts b/src/hooks/useToolbarGuards.ts index 497a32e27..36c99eff7 100644 --- a/src/hooks/useToolbarGuards.ts +++ b/src/hooks/useToolbarGuards.ts @@ -24,6 +24,8 @@ export function useConvertToVariable(range?: SourceRange) { }, [enable]) useEffect(() => { + // Return early if there are no selection ranges for whatever reason + if (!context.selectionRanges) return const parsed = ast const meta = isNodeSafeToReplace( diff --git a/src/lang/modifyAst.ts b/src/lang/modifyAst.ts index 1a55428c3..6a1ae6345 100644 --- a/src/lang/modifyAst.ts +++ b/src/lang/modifyAst.ts @@ -45,6 +45,7 @@ import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator' import { Models } from '@kittycad/lib' import { ExtrudeFacePlane } from 'machines/modelingMachine' import { Node } from 'wasm-lib/kcl/bindings/Node' +import { KclExpressionWithVariable } from 'lib/commandTypes' export function startSketchOnDefault( node: Node, @@ -590,6 +591,25 @@ export function addOffsetPlane({ } } +/** + * Return a modified clone of an AST with a named constant inserted into the body + */ +export function insertNamedConstant({ + node, + newExpression, +}: { + node: Node + newExpression: KclExpressionWithVariable +}): Node { + const ast = structuredClone(node) + ast.body.splice( + newExpression.insertIndex, + 0, + newExpression.variableDeclarationAst + ) + return ast +} + /** * Modify the AST to create a new sketch using the variable declaration * of an offset plane. The new sketch just has to come after the offset @@ -933,6 +953,31 @@ export function giveSketchFnCallTag( } } +/** + * Replace a + */ +export function replaceValueAtNodePath({ + ast, + pathToNode, + newExpressionString, +}: { + ast: Node + pathToNode: PathToNode + newExpressionString: string +}) { + const replaceCheckResult = isNodeSafeToReplacePath(ast, pathToNode) + if (err(replaceCheckResult)) { + return replaceCheckResult + } + const { isSafe, value, replacer } = replaceCheckResult + + if (!isSafe || value.type === 'Identifier') { + return new Error('Not safe to replace') + } + + return replacer(ast, newExpressionString) +} + export function moveValueIntoNewVariablePath( ast: Node, programMemory: ProgramMemory, diff --git a/src/lib/commandBarConfigs/modelingCommandConfig.ts b/src/lib/commandBarConfigs/modelingCommandConfig.ts index cc2c6e3dd..81dad7db6 100644 --- a/src/lib/commandBarConfigs/modelingCommandConfig.ts +++ b/src/lib/commandBarConfigs/modelingCommandConfig.ts @@ -1,8 +1,13 @@ import { Models } from '@kittycad/lib' +import { angleLengthInfo } from 'components/Toolbar/setAngleLength' +import { transformAstSketchLines } from 'lang/std/sketchcombos' +import { PathToNode } from 'lang/wasm' import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes' import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants' import { components } from 'lib/machine-api' import { Selections } from 'lib/selections' +import { kclManager } from 'lib/singletons' +import { err } from 'lib/trap' import { modelingMachine, SketchTool } from 'machines/modelingMachine' type OutputFormat = Models['OutputFormat_type'] @@ -54,6 +59,18 @@ export type ModelingCommandSchema = { 'change tool': { tool: SketchTool } + 'Constrain length': { + selection: Selections + length: KclCommandValue + } + 'Constrain with named value': { + currentValue: { + valueText: string + pathToNode: PathToNode + variableName: string + } + namedValue: KclCommandValue + } 'Text-to-CAD': { prompt: string } @@ -360,6 +377,88 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< }, }, }, + 'Constrain length': { + description: 'Constrain the length of one or more segments.', + icon: 'dimension', + args: { + selection: { + inputType: 'selection', + selectionTypes: ['segment'], + multiple: false, + required: true, + skip: true, + }, + length: { + inputType: 'kcl', + required: true, + createVariableByDefault: true, + defaultValue(_, machineContext) { + const selectionRanges = machineContext?.selectionRanges + if (!selectionRanges) return KCL_DEFAULT_LENGTH + const angleLength = angleLengthInfo({ + selectionRanges, + angleOrLength: 'setLength', + }) + if (err(angleLength)) return KCL_DEFAULT_LENGTH + const { transforms } = angleLength + + // QUESTION: is it okay to reference kclManager here? will its state be up to date? + const sketched = transformAstSketchLines({ + ast: structuredClone(kclManager.ast), + selectionRanges, + transformInfos: transforms, + programMemory: kclManager.programMemory, + referenceSegName: '', + }) + if (err(sketched)) return KCL_DEFAULT_LENGTH + const { valueUsedInTransform } = sketched + return valueUsedInTransform?.toString() || KCL_DEFAULT_LENGTH + }, + }, + }, + }, + 'Constrain with named value': { + description: 'Constrain a value by making it a named constant.', + icon: 'make-variable', + args: { + currentValue: { + description: + 'Path to the node in the AST to constrain. This is never shown to the user.', + inputType: 'text', + required: false, + skip: true, + }, + namedValue: { + inputType: 'kcl', + required: true, + createVariableByDefault: true, + variableName(commandBarContext, machineContext) { + const { currentValue } = commandBarContext.argumentsToSubmit + if ( + !currentValue || + !(currentValue instanceof Object) || + !('variableName' in currentValue) || + typeof currentValue.variableName !== 'string' + ) { + return 'value' + } + return currentValue.variableName + }, + defaultValue: (commandBarContext) => { + const { currentValue } = commandBarContext.argumentsToSubmit + if ( + !currentValue || + !(currentValue instanceof Object) || + !('valueText' in currentValue) || + typeof currentValue.valueText !== 'string' + ) { + return KCL_DEFAULT_LENGTH + } + return currentValue.valueText + }, + }, + }, + }, 'Text-to-CAD': { description: 'Use the Zoo Text-to-CAD API to generate part starters.', icon: 'chat', diff --git a/src/lib/commandTypes.ts b/src/lib/commandTypes.ts index 3ab58d550..6717d672e 100644 --- a/src/lib/commandTypes.ts +++ b/src/lib/commandTypes.ts @@ -148,7 +148,22 @@ export type CommandArgumentConfig< selectionTypes: Artifact['type'][] multiple: boolean } - | { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values + | { + inputType: 'kcl' + createVariableByDefault?: boolean + variableName?: + | string + | (( + commandBarContext: ContextFrom, + machineContext?: C + ) => string) + defaultValue?: + | string + | (( + commandBarContext: ContextFrom, + machineContext?: C + ) => string) + } | { inputType: 'string' defaultValue?: @@ -222,7 +237,22 @@ export type CommandArgument< selectionTypes: Artifact['type'][] multiple: boolean } - | { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value + | { + inputType: 'kcl' + createVariableByDefault?: boolean + variableName?: + | string + | (( + commandBarContext: ContextFrom, + machineContext?: ContextFrom + ) => string) + defaultValue?: + | string + | (( + commandBarContext: ContextFrom, + machineContext?: ContextFrom + ) => string) + } | { inputType: 'string' defaultValue?: diff --git a/src/lib/createMachineCommand.ts b/src/lib/createMachineCommand.ts index 203ef7b3c..a543e0bcf 100644 --- a/src/lib/createMachineCommand.ts +++ b/src/lib/createMachineCommand.ts @@ -185,6 +185,8 @@ export function buildCommandArgument< } else if (arg.inputType === 'kcl') { return { inputType: arg.inputType, + createVariableByDefault: arg.createVariableByDefault, + variableName: arg.variableName, defaultValue: arg.defaultValue, ...baseCommandArgument, } satisfies CommandArgument & { inputType: 'kcl' } diff --git a/src/lib/selections.ts b/src/lib/selections.ts index 870cc3522..a586155cf 100644 --- a/src/lib/selections.ts +++ b/src/lib/selections.ts @@ -630,12 +630,29 @@ export function getSelectionCountByType( } }) - selection.graphSelections.forEach((selection) => { - if (!selection.artifact) { - incrementOrInitializeSelectionType('other') - return + selection.graphSelections.forEach((graphSelection) => { + if (!graphSelection.artifact) { + /** + * TODO: remove this heuristic-based selection type detection. + * Currently, if you've created a sketch and have not left sketch mode, + * the selection will be a segment selection with no artifact. + * This is because the mock execution does not update the artifact graph. + * Once we move the artifactGraph creation to WASM, we can remove this, + * as the artifactGraph will always be up-to-date. + */ + if (isSingleCursorInPipe(selection, kclManager.ast)) { + incrementOrInitializeSelectionType('segment') + return + } else { + console.warn( + 'Selection is outside of a sketch but has no artifact. Sketch segment selections are the only kind that can have a valid selection with no artifact.', + JSON.stringify(graphSelection) + ) + incrementOrInitializeSelectionType('other') + return + } } - incrementOrInitializeSelectionType(selection.artifact.type) + incrementOrInitializeSelectionType(graphSelection.artifact.type) }) return selectionsByType diff --git a/src/lib/toolbar.ts b/src/lib/toolbar.ts index 81cefc06d..e5512cf74 100644 --- a/src/lib/toolbar.ts +++ b/src/lib/toolbar.ts @@ -540,13 +540,15 @@ export const toolbarConfig: Record = { [ { id: 'constraint-length', - disabled: (state) => - !( - state.matches({ Sketch: 'SketchIdle' }) && - state.can({ type: 'Constrain length' }) - ), - onClick: ({ modelingSend }) => - modelingSend({ type: 'Constrain length' }), + disabled: (state) => !state.matches({ Sketch: 'SketchIdle' }), + onClick: ({ commandBarSend }) => + commandBarSend({ + type: 'Find and select command', + data: { + name: 'Constrain length', + groupId: 'modeling', + }, + }), icon: 'dimension', status: 'available', title: 'Length', diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 907564840..ebeae4c2a 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -81,6 +81,7 @@ import { quaternionFromUpNForward } from 'clientSideScene/helpers' import { Vector3 } from 'three' import { MachineManager } from 'components/MachineManagerProvider' import { addShell } from 'lang/modifyAst/addShell' +import { KclCommandValue } from 'lib/commandTypes' export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY' @@ -252,7 +253,10 @@ export type ModelingMachineEvent = | { type: 'Constrain vertically align' } | { type: 'Constrain snap to X' } | { type: 'Constrain snap to Y' } - | { type: 'Constrain length' } + | { + type: 'Constrain length' + data: ModelingCommandSchema['Constrain length'] + } | { type: 'Constrain equal length' } | { type: 'Constrain parallel' } | { type: 'Constrain remove constraints'; data?: PathToNode } @@ -301,11 +305,8 @@ export type ModelingMachineEvent = type: 'code edit during sketch' } | { - type: 'Convert to variable' - data: { - pathToNode: PathToNode - variableName: string - } + type: 'Constrain with named value' + data: ModelingCommandSchema['Constrain with named value'] } | { type: 'change tool' @@ -543,14 +544,15 @@ export const modelingMachine = setup({ if (trap(info)) return false return info.enabled }, - 'Can convert to variable': ({ event }) => { - if (event.type !== 'Convert to variable') return false + 'Can convert to named value': ({ event }) => { + if (event.type !== 'Constrain with named value') return false if (!event.data) return false const ast = parse(recast(kclManager.ast)) if (err(ast) || !ast.program || ast.errors.length > 0) return false const isSafeRetVal = isNodeSafeToReplacePath( ast.program, - event.data.pathToNode + + event.data.currentValue.pathToNode ) if (err(isSafeRetVal)) return false return isSafeRetVal.isSafe @@ -1467,23 +1469,25 @@ export const modelingMachine = setup({ return {} as SetSelections } ), - 'Get length info': fromPromise( - async (_: { - input: Pick - }) => { - return {} as SetSelections - } - ), - 'Get convert to variable info': fromPromise( + astConstrainLength: fromPromise( async (_: { input: Pick< ModelingMachineContext, 'sketchDetails' | 'selectionRanges' > & { - data?: { - variableName: string - pathToNode: PathToNode - } + lengthValue?: KclCommandValue + } + }) => { + return {} as SetSelections + } + ), + 'Apply named value constraint': fromPromise( + async (_: { + input: Pick< + ModelingMachineContext, + 'sketchDetails' | 'selectionRanges' + > & { + data?: ModelingCommandSchema['Constrain with named value'] } }) => { return {} as SetSelections @@ -1655,7 +1659,7 @@ export const modelingMachine = setup({ }, // end services }).createMachine({ - /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANhoBWAHQAOAMwB2KQEY5AFgCcGqWqkAaEAE9Ew0RLEqa64TIBMKmTUXCAvk71oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEEpYSkJOUUaOWsxeylrWzk9QwQClQkrVOsZNWExFItnVxB3LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5KkaCWspZfMM+XE1YsQZMQWxNQtxao0ZeRc3dDavTv9ybhG+djGoibjeWZSZCRUxJa1flTCNTWLYIYS2CT2RSKdRyGhZawWc4tS6eDp+fy+KBdMC4AIAeQAbmAAE6YEj6WDPZisSbcd5CYHCSFqOQ1NSKWRiMRA0HqSTKblSX48+pKZGtNHEXEjEm3Eg4kkkfzcQLBUJTanRWlvKIlQQ86zSKwcmjCOS7FTLPSJGQpRQSTkHVkOLL7CWo9oSbAQTBgAgAUTxpMCAGs-OQABZa15TBlJHIO9QyYSZTm2YFiUHZNTpKQKfMKYQqRQ5D0eL0+v2B4Ny2Dh9hRqiKSI02JxhJCMp56xVRSs5Z1EEGIyp0zc6HFsRybQc8tXKDe33+gOPEm9DAxnUdmZCadGuTgqfwtQl06gwVpHaibTzApu+dopfVgBKYEJqEwxK37fpnaS04Or8+TzFaKgKDIF7CmkuxqDscFSHY1SPpWy4EAAYtgmB+k89DjNuf67gBbISKeyxmua9g0AUF4qOY6Q5FkMilnayHNJKqHVquLAkrhrbar+0z8HuqiQoo042OCNT2MOJSOPUiw0FoM6IjYGiKCh+DPv6yAkOGP50kJszTpIPxWqyvyiNykEjgg4mAhUqhWGaPzyKommLlW-oACLBCMap+hq4R4S8BFGSJXxKPCh5WNCoEXmoCkqNYcGHoiNDAnBHnaQQAAqYCPII7CoIIRAAILeQZupEQaokKNoig1HRMjJReLULFomb1DydEltlXkEPiABmQ1BAETDkrgowhW2hnxrVkg0PY052FokkqKCiLgtIsjmEpwr9o1-XLhIkY+mAAAKk1wAQZW8dgQ0kKE-hQEqTCRv4LBML05IjBAVU7sJSRWRUVpZOBFj5NkF4uZCqQKDCuSprYx1+hIsCRqgADuV1kDdd2cI9z2vSQ73+GAXRMJwkAA4RQMGto6QrdYcgZL2cKyYgpachUFiJcC7Nsk0FwVlp3gNlGxBkJQmDixG0YzQJc3-ol5TGCWCgZPUGSbLZpbVBUiJCzsBR1DI2Vy42kYEL5OFgGq2IyrT4UIEoXwFKzh7mMlHI2SUBwSA44EZt7+TFhbEvW3cGDkxAHD+BAvQku0Yby878bQo1pHw8x4iZMWoKskayMaEtShWtYGnsZ6YuRxIltRgAkmhunhg7OJ4v4xL3eQJCYOn-6ZPs6Q1NOsj9rs060dy0gOIl4jjwcUgR-L9eR831at-bQSO53mPJwAXvcfcD0RmR2I6Zs7Ko+SAm1ZiB+BR6pDCR4r1ba-yxv-pENwsDsEqPA-h97YCPniPuCdsD-2ltNfisY6aJEyAUUwpdvZwhqBaTaq1xyqByLBX4S135Rk-lbb+xA-4AJIEA7unBe6YEgdAigsD8KCQzktOQpgMr1A9nUKwm1mLMmoqmDKUNKJEMjCQpuaFf64H-oA3A-gyoACFvD+AABqnyBkPL4Ghko-B5OaDIm0NAcJ+PmcQWQrDzDkOIyRkYyEyLkVQhRyjVEAE1NGIPEF8Gcyw4LiWhDsUEzFmKLBZk-OoHIxBHWrqLRcDcJEJIcRQ+R-gyBQD9J4rmmdSLmG5MCRqdRWabTMLmQ8FilgIiyLYpJ0iUnOP8H6fA7AFZwLChnIepECinFZIYhCm1jAOhZo1H2iVbAqBqevOpsjKFAKYKSeZuA47kB+iQOUcdGGUCyQgExgceQWHsPIHpG1bJLGnGE9MLMb7ckmV-aZTigEgLAUMbC+g0k4CgLgbZmRQkll+OJbQ+0sx6yFOkVkrMrkzjhDY2JC47F2OSTM1JNDsB0MwG8vu2BPnfIsBw2cwiAn5hqBeYwaQMrKGqGyY5MKRZwoSQi+5syFGwFwKTfwxV1E4qUNIREuRaiNQyAoTaSlyjZAtByXhSxbmkMZakllbKOUeMVvAl2mRuVWHsFaeQOQQKbX7AsCxdQMrmnzMoaVUjqyOKZeTAAjr0CBTSoAtJxdtLIASlqrBsCcuSELSI2AKNEs0vThYojifC2plr6lzLWX3P0-dlXtMHlw0wsgWqDnArBBKAdM6ngynaSwEzYVPnpRGn+UaFEkgpqgYktxy3sCpAm1hSblCXw5CkDktR5BQRItElYZhdoIXNfYtCb5BAFRCL0EY3z2rfD5pycSWQKIw0QoHUeuwWqNW5CGjitdV6loINHe2kB46J2TvgVOVtvnQnKEtbQcJqKWlLDDFMFQbDGzsIK7dNd4l133TImh7LUBdzWdgEgAAjTJjblZn25mE4UM5jX7mBSUL1uYOTDPUPMGSX6w0lqmdWKM6T7bFU-NsgoEIMyIXAvo2QF5OSSCsJXJaCJshWFsWVLGVCAhPOPvQjZQwmH+DwENVABAIDcDAN6XAH5wwSBgOwQQPHwGYEEMJ1AZGmKByWjCdhZgeRYO2jsaj+R+zq3Y5x+OSmXkMIE5QITuARMEFJCSVAJIJATWGCJkkXQ5N+EU650BvHVMOfU1B6qQMjZfHNNe-s1jUjesQLYCEZgcg5HMAIxC5muNd1JLQiB-GYH2cc+JqaUmZOSfk4IFFdDgsiY0-MCop4zCZCEVrfhvZISs2UBkLkM4xBZfjtV-LUDbP2zU05kkLm3MefYF5nzlWhsqbU-Vh0KZbBaCUI1fsm0lhq3NL8VQdogTTgGwEVx6iiuiZK5JvA5XfMKfA7AQQfBauhbaU2oiiJhRiSJX8A43VgnwjVslDdWqPU0tDXSuuHHsvnbUZdibU33O-Tm-dwQj3nuvfq5IUuqZkq9iEchxLiU0gNDHuJaJPVTuKJUf4NxCPrtldQLJyrGP9BY7C4DRIX3yjoatLwq0uRgnQgNfCfMPJTiAhhNT879PxvOdc8jzzrn5t+bZxz970GItKUkPUOw5oDjpiWsErapgtZEocKzLKRavT0ph-HdJfoGcSaZyztX+A-Qa5YVr7nOvpBjzqP8H2uhTn7FzEhZK4IzQs36zb3dH97cBEd2NkLiPFczdR6zj3YAvehQ+9r-MixRDQQtFYOERPSj5IqPZRKVE4KFtpcW6HFmAiOpac70rt3mcVb823yMufZrhd97IUiWhSxg1qEoCvLMWamCHHo7mE9qd94Rwr6bKOVdo77wPpWQ-EsioqNOfaWHEqKAGd9xwdFEa9k5ObOPP7V6J8+gs3EyzVnrJG4V8bjOu9u4U-MkkRZN-ckEkQQArJhHfFVeMVSICBdbkHVRwRqTaFIDhIlGoS-E4FmanAAoA1Fd-GzL-VPNfJXWbTfSrHA1-PAkAsAz-CA5bTnBBffUJSncQEUJQVMP2RLJQXMNAiVdQOCLA+-eFAAGTwGI1QE-APUjCI0A1IwYJdmqFn1qGMBSnakymEELldTDnPhakY2XiEPpVEKmlkMwAkEblwA4AIA03EBZABHzBSjZBDxKGyB8XMBSDcO4O0FsSMPEM-DMIsPYCsJbG9z31KAcC+BqFUgNw2xolslkD2FOEty4QtEIQMLrh8JMIkAADkgMLpUA8B61boIAIBBg1lxp8i8QyMSwHQXDEYmpLQNDbI4RBFlBA8zAgRRAG9Icm9V4MiSNTCcj-A8iCjYApYmF41NdQjECOF6gORSw2Qlp5AK9DtSIGpb9GNoVbFzDLDtkFBFo9EWpbAzRlgz89YY9s5EpokRdotY9G9bc65tjAjmxJiudEB8gFg6JDwTZzQrQ2oWpFh7CMhWjDdbFcoiM8RQN6E1lyBAMpCZD+jtlDiOFDx6gMo3R1hOCEAVJFJmJWYlIPYq47j49iFeglkgNoFeIEl8RcAxMXdf9JMypvBcpBBSTxNBAKT2AqSvl5CM4UwgJCxK4zEw8EssTolL4zJ4NEIZwuid0H8P43xQhk8TC4T8BfCJiQjXjSgTMzduQ+tKJO0YYrBHRVBgQQIUpOQIdZT4UFSBMMk1SJBE8U4gg7glla0SQpoSQMI8AoEPpK1FTs9ESYQb1HCeRxVDxTj-YbCTiDk7RmtbEbSlT+iHSW8U4AsoA8AiiSi-TbSnc0y8BAyDZlAUxwVjhlBswL5n4lhWRg99CiS5TiEEzs9MjMILCMZIB-BGy7SrCeTB5koajHBUhxcrlOYdlWRvgUjUxUgdcfhbEiAZQQxOynd+iVSYATDNF9RGpIpjBlDCghRGjEh9FViCF8xzBqg6JZz5y5RFz7THTz07h3TSQvTWyPpKBax-Bszk9ESoQWRK5RQIUl0QUYR0go8viHBiwpU0jV45y3zrzMjbyoAVRk50yaSypijbhLz3yQgcz7Y8zuSXjGCEA1JJBulchHANArlaJjBgKjZnJL8IK6z4VoLZQOysLEyJDTCWyfT2ymKFzWLs9uz8KFDiwHRwQtBVh0scgLxoVZ43V10WZjBbFyA-QyBAhfp-Qr0R9HACgx8eR-EnDskZ5dgsMtU7BtAcModH8W9a1pNctANgNk5wNqwf9pNu80cXSaEioSpCQQNHKc96DBLoCDkKhzQygSw6hTyTcxwgRhQWo+VchrBqd3LbKOVvKHKIN-RiCM8yC-MkreJPKqsfL0rIDE1PsNhV06JyMt0KdglTzIQUoYz7R8hEry1MKega0XSHk8RRjnK7s2SOqmVBBK02qc9+r5E+INSCLERkpSJdgtKfgUoSwRzORzQi82QdYvVMNmqkUGkrNY19BaTO8XLZM+ry1-ND5eN0UNM4RHQkYDgy4tAlqWpygtoxdsh9hWYtrOqcse49qDqbsjrJMTrtq8AqtctUU9qrrUDzJ5hcUdggk9Yr8ukrQYRwl2jPrrVdrXl3ksUaSerXKgbOqzrAtlN0V0cPk8KJqFCzQHQEQcgpIORYQLwJxvhARDQw97wErIKE8rLRqGlFt0VsbPk-rXdAaSpeaQb+b2dMVsUezSqL5XQxLKhoRWQSUHIUhfgDgHCtA6h0a5VWUmA7K1Fhb6SJACaBr5UmB8q+ANMgRHRgReoYR8hbBhVWZDYP1jjWQYkGK7ceaWqLa7K3FjaAbTaxbTqLb8r9ArrFolhRAYQ7RTw9VUgeU+yLQbAjlbjuj7jLLstxaFEwA7UHVcQnVrY8bjrQ7gbcAx0C6VM+8baPjy4TNTgUw6IpKFIhxrIeogRwJdaGkmAY1sIsAg7ery7Ca+6lQB71S88fd99XbAQdgtt0Mp49ZAUR5-FbBIlexZzpDVT-Bcp2KBLKboDAQjQ7ASwwLoQ2Y6MchFg4J9hrEA02Jva64iBt7Vy97JDnjD7-xewX15IQqx47wr7iLb7AVshUs78n6oLX77Z37MArDrAArv7zEzdEJfgagTNdY5JSxgH4J77ExazM7iSJEX6ZDYGrCZBEGiIelcxJxRBwRuo2QgGb7cHaa0Gt7SH96qAVBKGIssNIRXIzQ4RnQUw6N8gmY1gdYx53IubiESGd6yGqAxAeHuc+HYr7BzQ0S4JGi5IjiwlBHYoBDN6ZHiHsASQlK1SVy1TtlDyNBjzVBqInrQRBB51jQOjwQM16hzKeiP4iBTHzG4KUzz1cLMzbg-HcykL8zZagY8hddolDteotAshhVGYLTwRgQtYIJZywmbzAmEKXwSA45+gnyfTQmzHINlHtg81Fg5LGochMFTleVViUohw4RAkM6rT6VfGyn7TOK2ySiunzGD6p7Qi6aHQOQZxVYet1GpKsgZKtYlp5KvGs6rZ-BcAgMiZ-RfBAoAgMBHofpxprptlnHEpxwODahWbBSBlmR2YdhuR-kWYUxsoyBsAuhhgU4OUPMpoh7XLnnXmRh8qNnrGUhSJMhxndEJ5MT2ZgK1rsHc5w4hDfm3nz0Pnro0919ldvMJBEX-nipBBAWonEFr7exwIFAy4FrDxgkxyrAKrYthkjGGLsX3mgMCoRsnTI5vnZNsWc9cX6w04CXEB+cQWuEopxJtztHRwjRDholERUt5h2nv0HSmAJp9BUyRoxpPpUXS7JNUA1W-BcYpoyp-53AjnEDvgCcZxUg21qIs0RLSxtMgRqJAQlmtIyolX0VVXRoMRPmMrJt08N9MWdXPX2B9WwBDX2BjX+WEArRFhr4FnEIMFMGuZ1Jq8YCHX1679mg1mMB4AohZSv6apy9A44J0H0p-t9KAIV1TU850EbBMshCvJ836Yiki2el3i0T6hQQx9vhEYCFZxAQCGrSvJTpzoQ2c3B9NSRAMoW2S2TwF46N+xpAVJUggQlhGo1BUZJMMZsZR3G3ZgTY8xdEy57AJmYYiWp9qIXRNZObIGrZd2BXZnbGvt7GzyRTBBYQKhZB5hoR0oMpEoh1v4727JlAHRhRkpf30pPGLxqhmRixmIt1EIRcB2FWfbstMaCDBM1NAPHAX0TEhG00MwsF-ipSA0dYTVnX6yJEn9Ft0O7NMPhnNT5IFgMgfiNZgSigGngRTAb8zRoljs62b3iEn84dLssPBlX1tAw8LILXjESIScgSKJWbyP4UhPac5cQtROxwdc5qagTV2OUNMhkTwkw51BoPqclS6Px2CKBzyhlrWZ0s9py28FVs6n5hA9tNl8i728LPd8GPA9s4L2pwwIhdTkDdmH0McxPhLTkPm9ssKClkqC1kaOU8RNRPr64bjhDgKrkCKNK4FAwOo8IHCGKOJA+j2KsPTVX0HBECWpdC9OjAeZ4jVhO1wLr2iuRCxDMjHisOO1KvJwRlavsx-jSVexJ8mtzzjGSuOuky1nvM+5APTZBF2p3rxIYbxWsSL5NA4oUw31qkJvSu-DBjhiur5uCgQP0HNY7Q2s4iLA8xDl8g7RWClP6Uuv6OCKQJFJfzARHC6u7JulDYmIrBfhexchQTwTOAIFoTANAOnrTIilNAJVORQRKIwloP+w07TNbFWTyShhKTI5qSsOzEi3TvwQ1hr1QQxRs581ag3V-F4y+K7STD5uIIb6ChTwfip8RSbwmZahK4qh9gQeJvYKkz4K1QXSSj7yPT5vx4WeFr2f7JO2fgWbbAWIYbla6f-SGfhfcnEKsU8ApejTxA6gkFSyfuVjKzlIaz1fsLmzvS+mWKNe-QpeZ4i4UDoVqhjdbJc0zXolThdgf65wJueKrz6elyyvXuFC71HQddewtpAk-ijR-hsHg9HWLyYKQ+cmuMU4JfSRoeloBRo+o9UwkDALRUQLaLwKkPcNn6MKhf2LkzM+gmImhIoD-xDl8-hQY-qbi+5Jixcw0t0MqJlBK+LKfGa-0+bfnzuKx+HewBc+dgo+O-C+4+9ZmfVA7P8g70va2v6VzGVLYA1KsOtKi2i5NZCwLA6MzRvh3HZr7xqytqAMUrCrHfw+M56HIRAI-ENGHDIqE+hQrdqfBCAnSjr7QrqtVq09sXOvWlE6lhvguQcQM1BiwV56MpkOCJPmvQrAe6jyALM8j2rQCT6cAswHYGvSPUbA3wGwLsCDSblCS2-GLvHFzrfU8sryPAbAOWgICUatEeiAImTCwgiBmAhRJjQFrS1m+JVLRMjBYFok84hYJmjUEcjLRGolA+VlX2zp0CWqktQWsIPzyIIxBAjO6r2lZhrdsOi0cZmDBSKFcOmtAgIPQP9oco1EqXSQLGzz6SQTYwqOoP7h1yoD7Gw-bxoJxAFfVrBQGNxHYPf73p0ugPEcrlw6hqF8kpYWoHwNtT2p6EfeZgToPhB6CfuRZNDP6iwwzgKU67Cbk-noFj1Y0WAVLj4gVrpZTgywdIZPmzj7ArQ8RNaOw3kZh9LOChT4tnBUL14MozTURjgzvqsNH6NAqCtk0Z4v9v6nsMgZRD6wUpjACUYsHVSyBKBZAqYMwGYOi7DDumATBvghVwpS9r6+uNaqflYxrdEQKTfMGk2GRjw1hSgnxiMK17bCOyBTbAP0D2GmQUwhwuJr2BOGKEP2jQA7FVx2BZNNhSZXppGG4rZNXhkwj4TMJOHvDjSiRUsMu3UDiJVm6zJ6LPzGE1RUswVftLglUhwRKWW5eKkcH7DkVFBcKRlsiyAzetAOheKSOCDjqpA1skLVwcmHtoaAikgIJ5hYT+ZMtyYfAVluel5a3tMRQMGcGCn0Skpa2YVJHsC3vAsRVggSbKK62VYet1WNI0UcVCYD74H4bIFmLUBrYd89AfoIaOwCECOA0gy7Fbp2hFCnEwMqAdgMVC6CJYkEI8fURo0qG-A9ASFSMGaKSDRsrRsxMIZmhABYwfQLSLmIiD0BgisUvogViHlmAWiP2Kw7WibGiTCpdRb6A0YsQ74uAXAQAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANhoBWAHQAOAMwB2KQEY5AFgCcGqWqkAaEAE9Ew0RLEqa64TIBMKmTUXCAvk71oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEEpYSkJOUUaOWsxeylrWzk9QwQClQkrVOsZNWExFItnVxB3LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5KkaCWspZfMM+XE1YsQZMQWxNQtxao0ZeRc3dDavTv9ybhG+djGoibjeWZSZCRUxJa1flTCNTWLYIYS2CT2RSKdRyGhZawWc4tS6eDp+fy+KBdMC4AIAeQAbmAAE6YEj6WDPZisSbcd5CYHCSFqOQ1NSKWRiMRA0HqSTKblSX48+pKZGtNHEXEjEm3Eg4kkkfzcQLBUJTanRWlvKIlQQ86zSKwcmjCOS7FTLPSJGQpRQSTkHVkOLL7CWo9oSbAQTBgAgAUTxpMCAGs-OQABZa15TBlJHIO9QyYSZTm2YFiUHZNTpKQKfMKYQqRQ5D0eL0+v2B4Ny2Dh9hRqiKSI02JxhJCMp56xVRSs5Z1EEGIyp0zc6HFsRybQc8tXKDe33+gOPEm9DAxnUdmZCadGuTgqfwtQl06gwVpHaibTzApu+dopfVgBKYEJqEwxK37fpnaS04Or8+TzFaKgKDIF7CmkuxqDscFSHY1SPpWy4EAAYtgmB+k89DjNuf67gBbISKeyxmua9g0AUF4qOY6Q5FkMilnayHNJKqHVquLAkrhrbar+0z8HuqiQoo042OCNT2MOJSOPUiw0FoM6IjYGiKCh+DPv6yAkOGP50kJszTpIPxWqyvyiNykEjgg4mAhUqhWGaPzyKommLlW-oACLBCMap+hq4R4S8BFGSJXxKPCh5WNCoEXmoCkqNYcGHoiNDAnBHnaQQAAqYCPII7CoIIRAAILeQZupEQaokKNoig1HRMjJReLULFomb1DydEltlXkEPiABmQ1BAETDkrgowhW2hnxrVkg0PY052FokkqKCiLgtIsjmEpwr9o1-XLhIkY+mAAAKk1wAQZW8dgQ0kKE-hQEqTCRv4LBML05IjBAVU7sJSRWRUVpZOBFj5NkF4uZCqQKDCuSprYx1+hIsCRqgADuV1kDdd2cI9z2vSQ73+GAXRMJwkAA4RQMGto6QrdYcgZL2cKyYgpachUFiJcC7Nsk0FwVlp3gNlGxBkJQmDixG0YzQJc3-ol5TGCWCgZPUGSbLZpbVBUiJCzsBR1DI2Vy42kYEL5OFgGq2IyrT4V2T8EgufmLMHPeNn6tUkhLPsS21KzBT5hbEvW3cGDkxAHD+BAvQku0Yby878bQo1pHw8x4iZMWoKskayMaEtShWtYGnsZ6YuRxIltRgAkmhunhg7OJ4v4xL3eQJCYOn-6ZPs6Q1NOsj9rs060dy0gOIl4jjwcUgR-L9eR831at-bQSO53mPJwAXvcfcD0RmR2I6Zs7Ko+SAm1Zju+BR6pDCR4r1ba-yxv-pENwsDsEqPA-h97YCPniPuCdsD-2ltNfisY6aJEyAUUwpdzAQxqBaTaq1xyqByLBX4S135Rk-lbb+xA-4AJIEA7unBe6YEgdAigsD8KCQzktOQpgMr1AKIeHkvtEDVEcO7XsmRtAs0okQyMJCm5oV-rgf+gDcD+DKgAIW8P4AAGqfIGQ8vgaGSj8Hk5oMibQ0Bwn4+ZxBZCsPMOQkjpGRjIXIhRVClGqPUQATW0Yg8QXwZzLDguJaEOxQTMWYosFmT86gcjEEdauotFwNykUkpxFDFH+DIFAP03iuaZ1IuYbkwJGp1FZptMwuZDxWKWAiLI9iUmyLSa4-wfp8DsAVnAsKGch6kQKKcVkxiEKbWMA6FmjVkoaABCoOp68GnyMoUApgpJFm4DjuQH6JA5Rx0YZQHJCAzHux5BYew8g+kbVsoHDhBR0wsxvtyaZX9ZkuKASAsBQxsL6AyTgKAuBdmZHCSWX44ltD7SzHrIU6RWSh1ZjOOEdj4kLgcQ41Jcz0k0OwHQzAHy+7YG+b8iwHDZypgcLE-MNQLzGDSBlZQ1Q2SnLhSLBFSSkWPPmUo2AuBSb+GKpovFShpCIlyLURqGQFCbSUuUbIFoOR1HBMveFT4mX1OrM41lgQOVMC5agfwXjFbwJdpkPlVh7BWnkDkECm1+wLCsXUDK5p8zKHuaQll6SwAAEdegQJaVANpeLtpZCCUtVYNgzlyVDqRGwBRYlmn6cLFECTEVKp-o0hZGy+5+n7rqzpg8uGmFkC1Qc4FYIJQOI6aEp4Mp2ksFM+VXpFUzOVcmpRJIKaoGJLcRt7AqSZtYdm5Ql8OQpA5LUeQUESIksQmYXaCFHUyNfGAQQBUQi9BGL89q3w+acnElkCiMNELu1HrsFqjVuSxo4rXVeiaCDR3tpAeOidk74FTlbX50JyjB0sdRS0pYYYpgqDYY2dgRWnprokuul65E0M1V3DZ2ASAACNsnduVmfbmEThQzltfuUFJRg25g5KM9Q8wZLAfjXWh51YoyZPtsVT8uyCgQgzIhcChjZAXk5JIKwlcloImyFYexZUsZUICC84+9CtlDCYf4PAQ1UAEAgNwMA3pcAfnDBIGA7BBAifAZgQQ0nUB0aYu7JaMJ2FmB5Fg7aOxmP5H7Orfjgn45abeQwiTlApO4BkwQUkJJUAkgkBNYYMmSRdDU34TTvnQGid0x5-TSHqpAyNl8c0r7+y2NSCGgRBjTDJRFOYZi6X7NCa7qSWhEDxMwPc55+TU0lMqcU+pwQaK6HRZkwZ+YFRTxmEyNRZL2GBGSUhKzZQGQuQzjEIV+OTWytQNc-bPTXmSQ+b8wF9gQWQsNamzpvTbWHQplsFoJQjV+ybSWGrc0vxVB2iBNOCbAR3GaMq7J6rim8B1dCxp+DsBBB8Ba7FjpPaiKImFGJUlfwDjdVCfCNWyUj0msDfSuNjK64CaK-djRj2FtLf879Nb73BCfe+79trkhS6pmSr2HrfXSiJTSA0Me4lYk9Vu8otR2qMfPdq6gVTDWCf6CJ3FwGiQgflHw1aGVVpcihOhFa+E+Y+EtTJ8z+7HiMfed89jwLvn1thd5-z-7yGEtKUkPUOw5oDjpiWqErapgtakocKzLKNbz0fxR-HTJfp2cKc59znX+A-R65YQboXRvpBjzqP8cZuhzn7FzEhZK4IzQs3G070Dq9XcBHd3NmLmP1crdxzzv387tsC4QQIpSlzRDQQtFYOEVO8G5lTOU3IJ4Wr8aYBND5Xq2ntpRa49gcmveva54psq3hcqCDuL3vAggu-tMD-F4PshSJaFLGDWoSg6+StMEOAx3MJ5t47803E3qPqT6eXiHPy2cda4kKP8fZ-WUz+Pz6kvLthcVGnPtIjiVFBDOB44OiRGERWQZnRZEkZZVZdZTZGbCrebDnIfH3DTMAiA9FKAwQcrJhAPUKAHQ3cJRncQEUJQVMfhUoFIDhUlGoAAk4FmUApZXESA8kaA7ZLPTzNXK-TXYLPHZA+g1Axg9AmAzA4vfXBfMvPArdbkM1RwRqTaJQXMCg6VdQOCGglPRFAAGTwGo1QE-CvUjCo01Vo1f3jGqBZmzmMBSnakymEELj9XyCJTsEBBsDlQZQVTrnUKmn0MwAkEblwA4AIAM3EBZABE9lZDtGzANlUCUmLH2n7G0HsTcM0M-C8J8P72bGEMFzLyzhqFUjNwOxolslkD2FOHty4QtEIRUKZXiI8IkAADktULpUA8BO1boIAIBBgNlxoGi8Q6MSwHRshmJXJOtGhswzR3ZlBw8zAgRRBq1nDa1XCNCqjaj-B6jGjYApYmEM00jS87JUwOF6gORSw2Qlp5AqdLtSIGpOQUx-1alyi65vDfDdkFBFoDEWpbAzRlhf89Yk9s5EpYlpdet7E7iUiWx590iEB8gFg6JDwTZzQrQ2oWpFhPYMgxjzd7FcoqM8RYN6ENlyBNUdC9CaMNiQStiXiOFeF5gDgLR1gSCVJFJmJWYlIeEq4ZjndiFegVktVoFeIkl8RcAB8asECR8x9BA2T5NBBOT2BuSflDDB4UwgJCxK4LEY8MsEALQHQADJ0x5wJTx7E3xQhM8PC8T8AEjCTsCg8BEbMbduQxtKJh0YYrBHRVBgQQIUpOQEcz1U8P5dSJMsljTb8HNOBH0ghJ9Wi7gSQpoSQMI8AoEPpm09TC9dl+i302RhRzt+kPiShwcdpz5ThhQ+objV4vT9SCS-ShMU4IsoA8BmjWjYzvSPdyy8AEyWIHSUxIVjhlBswL5n4lhWRI8nDEcXCCyQhazfTMIfCMZIB-BCzC8-DpSz5kpejHBUg5cblOY9lWRvhSjUxUgjcfh7EiAZQQwpyfSDTKMjSPDtF9RGpIpjBahwRC16grCgZDEziCF8xzBqg6I9yDy5QjyPdiz08U5QzwzIyxzT9vz-AazM8EyoQWRK5RRQ4d0wUYR0gE8oSHBiwlgvzaxJyhyiytDPCALH16zeSyoWjbhwLILC8VRk4KypTNiXY1IA5-Z8sNAblaJjAUKjZnIADML8yP59zsLfyRyozxzWiBLZQcK4yfSZz6KjDiwHRZVy0IibAqdlAshZ5-VD1xESMkdV5yA-QyBAhfp-QX0l9HAw4YkgRmIo85IGdOEiMTU7BtAdKBz+LuA0VH1uUOUcRWjCQ+5eh-R4DlNh8JASB-4VVFFqiFRIAAA1fy5hU0kQ0oI5Coc0MoEsOod8q3McIEYUFqQVXIawPc9ykrFOLy6K3y+Ky-DXVbG-MK9gCK1xKKnyuKzAAKgzYeQNcnPBRvKndqI0NSI5K7dMZnB-dJZtHoNtMavvVYoKt7UU6a6fCa1tedRaro2cw3ZKUiXYcyn4FKEsVczkc0RYSiHWYNQjUaxtYBCLV5NNfQPkl7YK1TBaxtcLQ+UTTFAzOER0JGA4MuLQQ6lqcoLaWXbIfYVmS6qfJRTbTFB673RTF6qGxrUqjFfQL68g8yeYfFHYEJPWQAnpK0GESJCYyG8-a6967TTFT5HFXkuakKxG8-N6yLSmvnbFXFDa4PMcBEHIKSDkWEC8Ccb4QEQ0GPe8Iqvi4hdPHvMmmGrFL5WmwfJ6hGkqNa5GnuO6-HeWgzC+V0LQBw1MfsIoPWYsZkFIX4b2AcWoZPZkj0yW-06W1VdlTlblDROGgUiQBmx-J2pgIqEqPgAzIER0YEXqGEfIWwMVVmQ2QDN41kOJG2xFKWtatVZ2rVDxN2pWj2lW167232wQNGjmsvC0YRBoGEO0U8C1VIflecykmlHYUm1VN1D1ehWfdO+arOpGxuvuJ-VpOfRK0EkG74cuGzU4FMOiC8C0NIIcayHqIEcCeu9JJgVNbCLAVu+m9uxmxepUZek02aJKo2cxKwWJZifDKePWYFEeQJWwaJXsPc3Qs83KfCmSokt-QEI0ewtMA2tmNjHIRYOCfYWxSNNieOplIgO+mAfwB+7Q1I5+uS39eSNKseO8b+gOP+4FbIHIIB-s2Y1eUBvQyBzAPw6wWS-8JYb6zU34GoGzXWWyn+8w-+7mklW+vBx+qgGQYhoiPpXMScUQcEbqNkZB3++CABjB82CWqRXB++lhlQdhhLIjSEVyM0OEZ0FMNjfIJmNYHWMedyMRiQCR8B-BvwsQGRoXOR-K+wc0DKCtR8uSV4iJRR2KJQm+nRogbAEkfS40w08Bgk3ZZ8iZIHCIj85UwQTdY0SYu8i0B8vc1x9xqowiqAainFSs0ikM6JusmihsguhAPIY3WJS7XqLQLIMVRmV08EYELWCCKJtxv8-CksyYR9F8EgOOfoEC6M24VJhK3e0E+wOCRYLSxqHITBc5AVM4lKIcOEYJa2rBlk8R9pqo0c6MiclxqpkyzJnmh0DkGcVWEbcx8e9SrGrWJabSyRfwXALVImf0XwQKAIDAR6H6caa6XZYJxKccYg2oYWhUoZZkdmHYbkQFFmFMbKMgbALoYYMqrVALKaVe1TIFkFkYXO85nxlIUiURZQfRCeEg9mFCtkNmXIfOaYqZxcGF0Fzy8F66aqvPOqnw2F+dYqQQBFzJ0sAOYsVmIjfIU8Q8UJdcqwOiSuMRNmFyr0IlgM+J7lAqGbFOeseWKFxTIVmlkqSV59TJsXZFrhKKcSG86x0cI0Q4WJREDB+YSZ902-dvTFMskaMaT6Mlum1TVAc1vwXGKaMqf+dwR5qQ74CnGcVIAdaiYtBS0sYzIEaiQEAVrSMqE1-QM10aDECF-0Ngmq3HW1qN9gB1sAJ19gF1pVtIf2OERwpymcX1iof1lioNvjZEU5jAeAKId0mB-8QQWvd2OCSh9KcHGyvcPde1aNT4cEUsVGMAGtmqEpBtvpcEyx+oUEFfb4V+BwaEXIPIXt06c6FNytzprYkQDKIdptk8BeNjfsaQFSVIIEJYRqNQedjGbGJd-t+mE2PMfRMuewTZmGWhjfaiF0TWcW4ByOS9xIVYF8-x98oG0EQQWECoWQGcDWJYGwGdRxZcL93JDIUPZKDKYEeEB8i8aoZkYsZiE9RCaXPso1plKWpzabZgx7WD7Y39MxJR-NDMLBeExCdBwCEOFIZnTbFzWAmLMj+SBYDIGEjWZEo2koAoYEUwERM0WJa7RCJXVndHPTTj4ZP9bQGPCyT10xEiGnJEiiYWkN22qRKW5XUjvurYxch0I3XamoO1AT7YTIUkyJWw9QdD5nfU2Twz-VdWR0Mg5SvaVt0oQWlShQMTojJkglhO8No-Huh2xRdgOThSWoF9qcMCSXc5M3QR-DHMT4N0kDBO+27glZXgjZNjyTZzld1zn+nG44Q4HlmQhjSuBQRDhPURj91eSogkzj+1P9ad4g+w80UEVMWnY5ECHkZQHtnR5rmpwEzjoddrycMZBXSzlU+EilXsdfTrT8kb+Y4s054LPuMj02ZkQNDYW3eETVlUi+TQOKS4lma4xrj+UbxIxY5YvEZdpWPeq5aQShzWO0LWcdiwPMfr6oA1nkAE5IsjkCRSOCwEZMub-WJLRLQ+02XIVE9EzgCBbEzVMjoG0yEpTQaVTkHrkiejWUpQ4+7TxFEUjkoYLkyOHkzjixBtq5cENYV9UEMUbOStWLs6oL-DuuISjwnbiCX+oTiHwmswHr9dqVSVOwMzTnzLplHn-8-0iVkIbgEM3zcMnb8eAX-amEjfZUidIW2wFiLGstHU3Cqi+X0soi9JoSPVIwz4A5OoJBNsub04rs5SXsk3qS6pxI+Z0SyS4c9XmeIuDzhEY5QueiHLK83YXsSgrCiSuX-Cnb7QDhA1SyLaYJOEo0f4I4ksMpw1mXuucSw8034883up+JoC0kdHpaAUI3XsNP6QpCiVVC7ijCvD-PnB8C+PxIuJhJ2iqvnYR0WvhPA2kgqg3p9QFieEZQNv0jAvzv4vr3zwn3yMRZ+fz3vtlz+MY5Gv4UOvs0dPvWfn1QVmCuJPuO4Lpldxwy2AYyzj8yhtouTWQsCwNjEY1+n4Bwe8Hs4q5TUqklk5iqtBjaob9iuGcXhpCEAgBILGKUObpJEz5CgHcsXZQtdztpFYk6y1Kah2ie429B4yMb4M3jMB2AUsqlYUKZDgjr5X0Kweek0iI7vI5OpYfActGaivpAaNgb4CpSO4chTg0vWfmnntpJ1Za9At+gQOYFE1aI9EfLMmFhBEDqBzyG6h9Tlo00hBjAyxnnELAC0agjkZaI1GjTiRZB0NFGndWprfJlBCjP6iSlZjHdHAA-KlDfDdBlEUBunfgVdW9pQYNEcnUrp-mr6SQTYYqOoKHiNzkCIiM-XSi7mcFQ1k6GqblB4g8GSBr48IMrnDwtTfVdoXCfYKWFqD6DyY7qT1M-kjCmDm85g10lDzZB4YI0RGGcNShPY6NE6V1TemmiwAeC-EutPLDmXzAJRK6VtMiAUTWhMNJGn4HbpCVMKlMLCozVRigyEYMNMGXPHBrMxa6b8SG0KdgadR-y8YrBbzSEIiDoiWgeQBwEniAzmE1Me+xFdXj-VNzYtVhvYY7oiGKb5hSmoyMeA1wv4F9Dh3fBXvU0abYB+gpw0yCmAuG5MrhJ2NgduXebjNj0lTGJsWWX6LN2mPw5Yf8OpTGBNofw5slCF7ApB1AxzU5v4HOZkdBAGDVKpOlwSqQ4InLa8oVSOAxFMwgLKlsSxFaks8YIPDhFJG7am5ZAUkIZGQKyKKESkgIGkcCzpFQYxW-8CVp+wWFEQZwEKQxBSlzaOAeuSLe8CxFWDBJsoYbDvJGwtYxtL2xUJgGXgfhsgvYFjHMr8D0B+ghoUXJII4Enr2g9icPWJHoDgyoB2AxULoAIiQQjxDRObO0CaJAA0VIwFooDtaMbxaA7RGUPQFjB9BtIuYiIPQCvxxT+jEAsJemFaJA7Bjh0IodMnqIlT-oQ4RxXfi4BcBAA */ id: 'Modeling', context: ({ input }) => ({ @@ -1809,7 +1813,7 @@ export const modelingMachine = setup({ }, 'Constrain length': { - target: 'Await length info', + target: 'Apply length constraint', guard: 'Can constrain length', }, @@ -1861,9 +1865,9 @@ export const modelingMachine = setup({ 'code edit during sketch': 'clean slate', - 'Convert to variable': { - target: 'Await convert to variable', - guard: 'Can convert to variable', + 'Constrain with named value': { + target: 'Converting to named value', + guard: 'Can convert to named value', }, 'change tool': { @@ -1954,14 +1958,19 @@ export const modelingMachine = setup({ }, }, - 'Await length info': { + 'Apply length constraint': { invoke: { - src: 'Get length info', - id: 'get-length-info', - input: ({ context: { selectionRanges, sketchDetails } }) => ({ - selectionRanges, - sketchDetails, - }), + src: 'astConstrainLength', + id: 'AST-constrain-length', + input: ({ context: { selectionRanges, sketchDetails }, event }) => { + const data = + event.type === 'Constrain length' ? event.data : undefined + return { + selectionRanges, + sketchDetails, + lengthValue: data?.length, + } + }, onDone: { target: 'SketchIdle', actions: 'Set selection', @@ -2129,12 +2138,12 @@ export const modelingMachine = setup({ always: 'SketchIdle', }, - 'Await convert to variable': { + 'Converting to named value': { invoke: { - src: 'Get convert to variable info', - id: 'get-convert-to-variable-info', + src: 'Apply named value constraint', + id: 'astConstrainNamedValue', input: ({ context: { selectionRanges, sketchDetails }, event }) => { - if (event.type !== 'Convert to variable') { + if (event.type !== 'Constrain with named value') { return { selectionRanges, sketchDetails,