WIP: global optional arg for all point-and-click transforms and add Scale
				
					
				
			Closes #7615 and #7634
This commit is contained in:
		| @ -409,6 +409,18 @@ const OperationItem = (props: { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   function enterScaleFlow() { | ||||
|     if (props.item.type === 'StdLibCall' || props.item.type === 'GroupBegin') { | ||||
|       props.send({ | ||||
|         type: 'enterScaleFlow', | ||||
|         data: { | ||||
|           targetSourceRange: sourceRangeFromRust(props.item.sourceRange), | ||||
|           currentOperation: props.item, | ||||
|         }, | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   function enterCloneFlow() { | ||||
|     if (props.item.type === 'StdLibCall' || props.item.type === 'GroupBegin') { | ||||
|       props.send({ | ||||
| @ -527,6 +539,16 @@ const OperationItem = (props: { | ||||
|             > | ||||
|               Set rotate | ||||
|             </ContextMenuItem>, | ||||
|             <ContextMenuItem | ||||
|               onClick={enterScaleFlow} | ||||
|               data-testid="context-menu-set-scale" | ||||
|               disabled={ | ||||
|                 props.item.type !== 'GroupBegin' && | ||||
|                 !stdLibMap[props.item.name]?.supportsTransform | ||||
|               } | ||||
|             > | ||||
|               Set scale | ||||
|             </ContextMenuItem>, | ||||
|             <ContextMenuItem | ||||
|               onClick={enterCloneFlow} | ||||
|               data-testid="context-menu-clone" | ||||
|  | ||||
| @ -4,6 +4,7 @@ import { | ||||
|   createCallExpressionStdLibKw, | ||||
|   createExpressionStatement, | ||||
|   createLabeledArg, | ||||
|   createLiteral, | ||||
|   createLocalName, | ||||
|   createPipeExpression, | ||||
| } from '@src/lang/create' | ||||
| @ -31,18 +32,25 @@ export function setTranslate({ | ||||
|   x, | ||||
|   y, | ||||
|   z, | ||||
|   global, | ||||
| }: { | ||||
|   modifiedAst: Node<Program> | ||||
|   pathToNode: PathToNode | ||||
|   x: Expr | ||||
|   y: Expr | ||||
|   z: Expr | ||||
|   global?: boolean | ||||
| }): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } { | ||||
|   // Extra labeled args expression | ||||
|   const globalExpr = global | ||||
|     ? [createLabeledArg('global', createLiteral(global))] | ||||
|     : [] | ||||
|   const noPercentSign = null | ||||
|   const call = createCallExpressionStdLibKw('translate', noPercentSign, [ | ||||
|     createLabeledArg('x', x), | ||||
|     createLabeledArg('y', y), | ||||
|     createLabeledArg('z', z), | ||||
|     ...globalExpr, | ||||
|   ]) | ||||
|  | ||||
|   const potentialPipe = getNodeFromPath<PipeExpression>( | ||||
| @ -71,18 +79,72 @@ export function setRotate({ | ||||
|   roll, | ||||
|   pitch, | ||||
|   yaw, | ||||
|   global, | ||||
| }: { | ||||
|   modifiedAst: Node<Program> | ||||
|   pathToNode: PathToNode | ||||
|   roll: Expr | ||||
|   pitch: Expr | ||||
|   yaw: Expr | ||||
|   global?: boolean | ||||
| }): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } { | ||||
|   // Extra labeled args expression | ||||
|   const globalExpr = global | ||||
|     ? [createLabeledArg('global', createLiteral(global))] | ||||
|     : [] | ||||
|   const noPercentSign = null | ||||
|   const call = createCallExpressionStdLibKw('rotate', noPercentSign, [ | ||||
|     createLabeledArg('roll', roll), | ||||
|     createLabeledArg('pitch', pitch), | ||||
|     createLabeledArg('yaw', yaw), | ||||
|     ...globalExpr, | ||||
|   ]) | ||||
|  | ||||
|   const potentialPipe = getNodeFromPath<PipeExpression>( | ||||
|     modifiedAst, | ||||
|     pathToNode, | ||||
|     ['PipeExpression'] | ||||
|   ) | ||||
|   if (!err(potentialPipe) && potentialPipe.node.type === 'PipeExpression') { | ||||
|     setTransformInPipe(potentialPipe.node, call) | ||||
|   } else { | ||||
|     const error = createPipeWithTransform(modifiedAst, pathToNode, call) | ||||
|     if (err(error)) { | ||||
|       return error | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     modifiedAst, | ||||
|     pathToNode, | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function setScale({ | ||||
|   modifiedAst, | ||||
|   pathToNode, | ||||
|   x, | ||||
|   y, | ||||
|   z, | ||||
|   global, | ||||
| }: { | ||||
|   modifiedAst: Node<Program> | ||||
|   pathToNode: PathToNode | ||||
|   x: Expr | ||||
|   y: Expr | ||||
|   z: Expr | ||||
|   global?: boolean | ||||
| }): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } { | ||||
|   // Extra labeled args expression | ||||
|   const globalExpr = global | ||||
|     ? [createLabeledArg('global', createLiteral(global))] | ||||
|     : [] | ||||
|   const noPercentSign = null | ||||
|   const call = createCallExpressionStdLibKw('scale', noPercentSign, [ | ||||
|     createLabeledArg('x', x), | ||||
|     createLabeledArg('y', y), | ||||
|     createLabeledArg('z', z), | ||||
|     ...globalExpr, | ||||
|   ]) | ||||
|  | ||||
|   const potentialPipe = getNodeFromPath<PipeExpression>( | ||||
|  | ||||
| @ -190,6 +190,7 @@ export type ModelingCommandSchema = { | ||||
|     x: KclCommandValue | ||||
|     y: KclCommandValue | ||||
|     z: KclCommandValue | ||||
|     global?: boolean | ||||
|   } | ||||
|   Rotate: { | ||||
|     nodeToEdit?: PathToNode | ||||
| @ -197,6 +198,15 @@ export type ModelingCommandSchema = { | ||||
|     roll: KclCommandValue | ||||
|     pitch: KclCommandValue | ||||
|     yaw: KclCommandValue | ||||
|     global?: boolean | ||||
|   } | ||||
|   Scale: { | ||||
|     nodeToEdit?: PathToNode | ||||
|     selection: Selections | ||||
|     x: KclCommandValue | ||||
|     y: KclCommandValue | ||||
|     z: KclCommandValue | ||||
|     global?: boolean | ||||
|   } | ||||
|   Clone: { | ||||
|     nodeToEdit?: PathToNode | ||||
| @ -750,14 +760,8 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< | ||||
|         required: false, | ||||
|         displayName: 'CounterClockWise', | ||||
|         options: [ | ||||
|           { | ||||
|             name: 'False', | ||||
|             value: false, | ||||
|           }, | ||||
|           { | ||||
|             name: 'True', | ||||
|             value: true, | ||||
|           }, | ||||
|           { name: 'False', value: false }, | ||||
|           { name: 'True', value: true }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
| @ -1104,6 +1108,14 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< | ||||
|         defaultValue: KCL_DEFAULT_TRANSFORM, | ||||
|         required: true, | ||||
|       }, | ||||
|       global: { | ||||
|         inputType: 'options', | ||||
|         required: false, | ||||
|         options: [ | ||||
|           { name: 'False', value: false }, | ||||
|           { name: 'True', value: true }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   Rotate: { | ||||
| @ -1139,6 +1151,57 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< | ||||
|         defaultValue: KCL_DEFAULT_TRANSFORM, | ||||
|         required: true, | ||||
|       }, | ||||
|       global: { | ||||
|         inputType: 'options', | ||||
|         required: false, | ||||
|         options: [ | ||||
|           { name: 'False', value: false }, | ||||
|           { name: 'True', value: true }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   Scale: { | ||||
|     description: 'Set scale on solid or sketch.', | ||||
|     icon: 'scale', | ||||
|     needsReview: true, | ||||
|     args: { | ||||
|       nodeToEdit: { | ||||
|         ...nodeToEditProps, | ||||
|       }, | ||||
|       selection: { | ||||
|         // selectionMixed allows for feature tree selection of module imports | ||||
|         inputType: 'selectionMixed', | ||||
|         multiple: false, | ||||
|         required: true, | ||||
|         skip: true, | ||||
|         selectionTypes: ['path'], | ||||
|         selectionFilter: ['object'], | ||||
|         hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit), | ||||
|       }, | ||||
|       x: { | ||||
|         inputType: 'kcl', | ||||
|         defaultValue: KCL_DEFAULT_TRANSFORM, | ||||
|         required: true, | ||||
|       }, | ||||
|       y: { | ||||
|         inputType: 'kcl', | ||||
|         defaultValue: KCL_DEFAULT_TRANSFORM, | ||||
|         required: true, | ||||
|       }, | ||||
|       z: { | ||||
|         inputType: 'kcl', | ||||
|         defaultValue: KCL_DEFAULT_TRANSFORM, | ||||
|         required: true, | ||||
|       }, | ||||
|       global: { | ||||
|         inputType: 'options', | ||||
|         required: false, | ||||
|         options: [ | ||||
|           { name: 'False', value: false }, | ||||
|           { name: 'True', value: true }, | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   Clone: { | ||||
|  | ||||
| @ -1179,6 +1179,8 @@ export const stdLibMap: Record<string, StdLibCallInfo> = { | ||||
|   scale: { | ||||
|     label: 'Scale', | ||||
|     icon: 'scale', | ||||
|     prepareToEdit: prepareToEditScale, | ||||
|     supportsTransform: true, | ||||
|   }, | ||||
|   shell: { | ||||
|     label: 'Shell', | ||||
| @ -1540,6 +1542,7 @@ async function prepareToEditTranslate({ operation }: EnterEditFlowProps) { | ||||
|   let x: KclExpression | undefined = undefined | ||||
|   let y: KclExpression | undefined = undefined | ||||
|   let z: KclExpression | undefined = undefined | ||||
|   let global: boolean | undefined | ||||
|   const pipeLookupFromOperation = getNodeFromPath<PipeExpression>( | ||||
|     kclManager.ast, | ||||
|     nodeToEdit, | ||||
| @ -1566,12 +1569,21 @@ async function prepareToEditTranslate({ operation }: EnterEditFlowProps) { | ||||
|       x = await retrieveArgFromPipedCallExpression(translate, 'x') | ||||
|       y = await retrieveArgFromPipedCallExpression(translate, 'y') | ||||
|       z = await retrieveArgFromPipedCallExpression(translate, 'z') | ||||
|  | ||||
|       // optional global argument | ||||
|       const result = await retrieveArgFromPipedCallExpression( | ||||
|         translate, | ||||
|         'global' | ||||
|       ) | ||||
|       if (result) { | ||||
|         global = result.valueText === 'true' | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Won't be used since we provide nodeToEdit | ||||
|   const selection: Selections = { graphSelections: [], otherSelections: [] } | ||||
|   const argDefaultValues = { nodeToEdit, selection, x, y, z } | ||||
|   const argDefaultValues = { nodeToEdit, selection, x, y, z, global } | ||||
|   return { | ||||
|     ...baseCommand, | ||||
|     argDefaultValues, | ||||
| @ -1592,6 +1604,84 @@ export async function enterTranslateFlow({ | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function prepareToEditScale({ operation }: EnterEditFlowProps) { | ||||
|   const baseCommand = { | ||||
|     name: 'Scale', | ||||
|     groupId: 'modeling', | ||||
|   } | ||||
|   const isModuleImport = operation.type === 'GroupBegin' | ||||
|   const isSupportedStdLibCall = | ||||
|     operation.type === 'StdLibCall' && | ||||
|     stdLibMap[operation.name]?.supportsTransform | ||||
|   if (!isModuleImport && !isSupportedStdLibCall) { | ||||
|     return { | ||||
|       reason: 'Unsupported operation type. Please edit in the code editor.', | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const nodeToEdit = pathToNodeFromRustNodePath(operation.nodePath) | ||||
|   let x: KclExpression | undefined = undefined | ||||
|   let y: KclExpression | undefined = undefined | ||||
|   let z: KclExpression | undefined = undefined | ||||
|   let global: boolean | undefined | ||||
|   const pipeLookupFromOperation = getNodeFromPath<PipeExpression>( | ||||
|     kclManager.ast, | ||||
|     nodeToEdit, | ||||
|     'PipeExpression' | ||||
|   ) | ||||
|   let pipe: PipeExpression | undefined | ||||
|   const ast = kclManager.ast | ||||
|   if ( | ||||
|     err(pipeLookupFromOperation) || | ||||
|     pipeLookupFromOperation.node.type !== 'PipeExpression' | ||||
|   ) { | ||||
|     // Look for the last pipe with the import alias and a call to scale | ||||
|     const pipes = findPipesWithImportAlias(ast, nodeToEdit, 'scale') | ||||
|     pipe = pipes.at(-1)?.expression | ||||
|   } else { | ||||
|     pipe = pipeLookupFromOperation.node | ||||
|   } | ||||
|  | ||||
|   if (pipe) { | ||||
|     const scale = pipe.body.find( | ||||
|       (n) => n.type === 'CallExpressionKw' && n.callee.name.name === 'scale' | ||||
|     ) | ||||
|     if (scale?.type === 'CallExpressionKw') { | ||||
|       x = await retrieveArgFromPipedCallExpression(scale, 'x') | ||||
|       y = await retrieveArgFromPipedCallExpression(scale, 'y') | ||||
|       z = await retrieveArgFromPipedCallExpression(scale, 'z') | ||||
|  | ||||
|       // optional global argument | ||||
|       const result = await retrieveArgFromPipedCallExpression(scale, 'global') | ||||
|       if (result) { | ||||
|         global = result.valueText === 'true' | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Won't be used since we provide nodeToEdit | ||||
|   const selection: Selections = { graphSelections: [], otherSelections: [] } | ||||
|   const argDefaultValues = { nodeToEdit, selection, x, y, z, global } | ||||
|   return { | ||||
|     ...baseCommand, | ||||
|     argDefaultValues, | ||||
|   } | ||||
| } | ||||
|  | ||||
| export async function enterScaleFlow({ | ||||
|   operation, | ||||
| }: EnterEditFlowProps): Promise<Error | CommandBarMachineEvent> { | ||||
|   const data = await prepareToEditScale({ operation }) | ||||
|   if ('reason' in data) { | ||||
|     return new Error(data.reason) | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     type: 'Find and select command', | ||||
|     data, | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function prepareToEditRotate({ operation }: EnterEditFlowProps) { | ||||
|   const baseCommand = { | ||||
|     name: 'Rotate', | ||||
|  | ||||
| @ -432,6 +432,24 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = { | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|           { | ||||
|             id: 'scale', | ||||
|             onClick: () => | ||||
|               commandBarActor.send({ | ||||
|                 type: 'Find and select command', | ||||
|                 data: { name: 'Scale', groupId: 'modeling' }, | ||||
|               }), | ||||
|             icon: 'scale', | ||||
|             status: 'available', | ||||
|             title: 'Scale', | ||||
|             description: 'Apply scaling to a solid or sketch.', | ||||
|             links: [ | ||||
|               { | ||||
|                 label: 'API docs', | ||||
|                 url: 'https://zoo.dev/docs/kcl-std/functions/std-transform-scale', | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|           { | ||||
|             id: 'clone', | ||||
|             onClick: () => | ||||
|  | ||||
| @ -18,6 +18,7 @@ import { | ||||
|   enterEditFlow, | ||||
|   enterTranslateFlow, | ||||
|   enterRotateFlow, | ||||
|   enterScaleFlow, | ||||
| } from '@src/lib/operations' | ||||
| import { kclManager } from '@src/lib/singletons' | ||||
| import { err } from '@src/lib/trap' | ||||
| @ -52,6 +53,10 @@ type FeatureTreeEvent = | ||||
|       type: 'enterRotateFlow' | ||||
|       data: { targetSourceRange: SourceRange; currentOperation: Operation } | ||||
|     } | ||||
|   | { | ||||
|       type: 'enterScaleFlow' | ||||
|       data: { targetSourceRange: SourceRange; currentOperation: Operation } | ||||
|     } | ||||
|   | { | ||||
|       type: 'enterCloneFlow' | ||||
|       data: { targetSourceRange: SourceRange; currentOperation: Operation } | ||||
| @ -172,6 +177,29 @@ export const featureTreeMachine = setup({ | ||||
|         }) | ||||
|       } | ||||
|     ), | ||||
|     prepareScaleCommand: fromPromise( | ||||
|       ({ | ||||
|         input, | ||||
|       }: { | ||||
|         input: EnterEditFlowProps & { | ||||
|           commandBarSend: (typeof commandBarActor)['send'] | ||||
|         } | ||||
|       }) => { | ||||
|         return new Promise((resolve, reject) => { | ||||
|           const { commandBarSend, ...editFlowProps } = input | ||||
|           enterScaleFlow(editFlowProps) | ||||
|             .then((result) => { | ||||
|               if (err(result)) { | ||||
|                 reject(result) | ||||
|                 return | ||||
|               } | ||||
|               input.commandBarSend(result) | ||||
|               resolve(result) | ||||
|             }) | ||||
|             .catch(reject) | ||||
|         }) | ||||
|       } | ||||
|     ), | ||||
|     prepareCloneCommand: fromPromise( | ||||
|       ({ | ||||
|         input, | ||||
| @ -293,6 +321,11 @@ export const featureTreeMachine = setup({ | ||||
|           actions: ['saveTargetSourceRange', 'saveCurrentOperation'], | ||||
|         }, | ||||
|  | ||||
|         enterScaleFlow: { | ||||
|           target: 'enteringScaleFlow', | ||||
|           actions: ['saveTargetSourceRange', 'saveCurrentOperation'], | ||||
|         }, | ||||
|  | ||||
|         enterCloneFlow: { | ||||
|           target: 'enteringCloneFlow', | ||||
|           actions: ['saveTargetSourceRange', 'saveCurrentOperation'], | ||||
| @ -571,6 +604,60 @@ export const featureTreeMachine = setup({ | ||||
|       exit: ['clearContext'], | ||||
|     }, | ||||
|  | ||||
|     enteringScaleFlow: { | ||||
|       states: { | ||||
|         selecting: { | ||||
|           on: { | ||||
|             selected: { | ||||
|               target: 'prepareScaleCommand', | ||||
|               reenter: true, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|  | ||||
|         done: { | ||||
|           always: '#featureTree.idle', | ||||
|         }, | ||||
|  | ||||
|         prepareScaleCommand: { | ||||
|           invoke: { | ||||
|             src: 'prepareScaleCommand', | ||||
|             input: ({ context }) => { | ||||
|               const artifact = context.targetSourceRange | ||||
|                 ? (getArtifactFromRange( | ||||
|                     context.targetSourceRange, | ||||
|                     kclManager.artifactGraph | ||||
|                   ) ?? undefined) | ||||
|                 : undefined | ||||
|               return { | ||||
|                 // currentOperation is guaranteed to be defined here | ||||
|                 operation: context.currentOperation!, | ||||
|                 artifact, | ||||
|                 commandBarSend: commandBarActor.send, | ||||
|               } | ||||
|             }, | ||||
|             onDone: { | ||||
|               target: 'done', | ||||
|               reenter: true, | ||||
|             }, | ||||
|             onError: { | ||||
|               target: 'done', | ||||
|               reenter: true, | ||||
|               actions: ({ event }) => { | ||||
|                 if ('error' in event && err(event.error)) { | ||||
|                   toast.error(event.error.message) | ||||
|                 } | ||||
|               }, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|  | ||||
|       initial: 'selecting', | ||||
|       entry: 'sendSelectionEvent', | ||||
|       exit: ['clearContext'], | ||||
|     }, | ||||
|  | ||||
|     enteringCloneFlow: { | ||||
|       states: { | ||||
|         selecting: { | ||||
|  | ||||
| @ -88,6 +88,7 @@ import { | ||||
|   setRotate, | ||||
|   insertExpressionNode, | ||||
|   retrievePathToNodeFromTransformSelection, | ||||
|   setScale, | ||||
| } from '@src/lang/modifyAst/setTransform' | ||||
| import { | ||||
|   getNodeFromPath, | ||||
| @ -403,6 +404,7 @@ export type ModelingMachineEvent = | ||||
|   | { type: 'Appearance'; data: ModelingCommandSchema['Appearance'] } | ||||
|   | { type: 'Translate'; data: ModelingCommandSchema['Translate'] } | ||||
|   | { type: 'Rotate'; data: ModelingCommandSchema['Rotate'] } | ||||
|   | { type: 'Scale'; data: ModelingCommandSchema['Scale'] } | ||||
|   | { type: 'Clone'; data: ModelingCommandSchema['Clone'] } | ||||
|   | { | ||||
|       type: | ||||
| @ -3318,7 +3320,7 @@ export const modelingMachine = setup({ | ||||
|  | ||||
|         const ast = kclManager.ast | ||||
|         const modifiedAst = structuredClone(ast) | ||||
|         const { x, y, z, nodeToEdit, selection } = input | ||||
|         const { x, y, z, global, nodeToEdit, selection } = input | ||||
|         let pathToNode = nodeToEdit | ||||
|         if (!(pathToNode && typeof pathToNode[1][0] === 'number')) { | ||||
|           const result = retrievePathToNodeFromTransformSelection( | ||||
| @ -3368,6 +3370,7 @@ export const modelingMachine = setup({ | ||||
|           x: valueOrVariable(x), | ||||
|           y: valueOrVariable(y), | ||||
|           z: valueOrVariable(z), | ||||
|           global, | ||||
|         }) | ||||
|         if (err(result)) { | ||||
|           return Promise.reject(result) | ||||
| @ -3399,7 +3402,7 @@ export const modelingMachine = setup({ | ||||
|  | ||||
|         const ast = kclManager.ast | ||||
|         const modifiedAst = structuredClone(ast) | ||||
|         const { roll, pitch, yaw, nodeToEdit, selection } = input | ||||
|         const { roll, pitch, yaw, global, nodeToEdit, selection } = input | ||||
|         let pathToNode = nodeToEdit | ||||
|         if (!(pathToNode && typeof pathToNode[1][0] === 'number')) { | ||||
|           const result = retrievePathToNodeFromTransformSelection( | ||||
| @ -3449,6 +3452,89 @@ export const modelingMachine = setup({ | ||||
|           roll: valueOrVariable(roll), | ||||
|           pitch: valueOrVariable(pitch), | ||||
|           yaw: valueOrVariable(yaw), | ||||
|           global, | ||||
|         }) | ||||
|         if (err(result)) { | ||||
|           return Promise.reject(result) | ||||
|         } | ||||
|  | ||||
|         await updateModelingState( | ||||
|           result.modifiedAst, | ||||
|           EXECUTION_TYPE_REAL, | ||||
|           { | ||||
|             kclManager, | ||||
|             editorManager, | ||||
|             codeManager, | ||||
|           }, | ||||
|           { | ||||
|             focusPath: [result.pathToNode], | ||||
|           } | ||||
|         ) | ||||
|       } | ||||
|     ), | ||||
|     scaleAstMod: fromPromise( | ||||
|       async ({ | ||||
|         input, | ||||
|       }: { | ||||
|         input: ModelingCommandSchema['Scale'] | undefined | ||||
|       }) => { | ||||
|         if (!input) { | ||||
|           return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE)) | ||||
|         } | ||||
|  | ||||
|         const ast = kclManager.ast | ||||
|         const modifiedAst = structuredClone(ast) | ||||
|         const { x, y, z, global, nodeToEdit, selection } = input | ||||
|         let pathToNode = nodeToEdit | ||||
|         if (!(pathToNode && typeof pathToNode[1][0] === 'number')) { | ||||
|           const result = retrievePathToNodeFromTransformSelection( | ||||
|             selection, | ||||
|             kclManager.artifactGraph, | ||||
|             ast | ||||
|           ) | ||||
|           if (err(result)) { | ||||
|             return Promise.reject(result) | ||||
|           } | ||||
|  | ||||
|           pathToNode = result | ||||
|         } | ||||
|  | ||||
|         // Look for the last pipe with the import alias and a call to translate, with a fallback to rotate. | ||||
|         // Otherwise create one | ||||
|         const importNodeAndAlias = findImportNodeAndAlias(ast, pathToNode) | ||||
|         if (importNodeAndAlias) { | ||||
|           const pipes = findPipesWithImportAlias(ast, pathToNode, 'translate') | ||||
|           const lastPipe = pipes.at(-1) | ||||
|           if (lastPipe && lastPipe.pathToNode) { | ||||
|             pathToNode = lastPipe.pathToNode | ||||
|           } else { | ||||
|             const otherRelevantPipes = findPipesWithImportAlias( | ||||
|               ast, | ||||
|               pathToNode, | ||||
|               'rotate' | ||||
|             ) | ||||
|             const lastRelevantPipe = otherRelevantPipes.at(-1) | ||||
|             if (lastRelevantPipe && lastRelevantPipe.pathToNode) { | ||||
|               pathToNode = lastRelevantPipe.pathToNode | ||||
|             } else { | ||||
|               pathToNode = insertExpressionNode( | ||||
|                 modifiedAst, | ||||
|                 importNodeAndAlias.alias | ||||
|               ) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         insertVariableAndOffsetPathToNode(x, modifiedAst, pathToNode) | ||||
|         insertVariableAndOffsetPathToNode(y, modifiedAst, pathToNode) | ||||
|         insertVariableAndOffsetPathToNode(z, modifiedAst, pathToNode) | ||||
|         const result = setScale({ | ||||
|           pathToNode, | ||||
|           modifiedAst, | ||||
|           x: valueOrVariable(x), | ||||
|           y: valueOrVariable(y), | ||||
|           z: valueOrVariable(z), | ||||
|           global, | ||||
|         }) | ||||
|         if (err(result)) { | ||||
|           return Promise.reject(result) | ||||
| @ -3863,6 +3949,12 @@ export const modelingMachine = setup({ | ||||
|           guard: 'no kcl errors', | ||||
|         }, | ||||
|  | ||||
|         Scale: { | ||||
|           target: 'Applying scale', | ||||
|           reenter: true, | ||||
|           guard: 'no kcl errors', | ||||
|         }, | ||||
|  | ||||
|         Clone: { | ||||
|           target: 'Applying clone', | ||||
|           reenter: true, | ||||
| @ -5345,6 +5437,22 @@ export const modelingMachine = setup({ | ||||
|       }, | ||||
|     }, | ||||
|  | ||||
|     'Applying scale': { | ||||
|       invoke: { | ||||
|         src: 'scaleAstMod', | ||||
|         id: 'scaleAstMod', | ||||
|         input: ({ event }) => { | ||||
|           if (event.type !== 'Scale') return undefined | ||||
|           return event.data | ||||
|         }, | ||||
|         onDone: ['idle'], | ||||
|         onError: { | ||||
|           target: 'idle', | ||||
|           actions: 'toastError', | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|  | ||||
|     'Applying clone': { | ||||
|       invoke: { | ||||
|         src: 'cloneAstMod', | ||||
|  | ||||
		Reference in New Issue
	
	Block a user