Compare commits
	
		
			12 Commits
		
	
	
		
			nightly-v2
			...
			jtran/exec
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 437aacf477 | |||
| c6fb56058b | |||
| ca2fc1bf38 | |||
| 8e62f07d71 | |||
| 55ef40d136 | |||
| 3b7bbc1642 | |||
| 1a569af476 | |||
| e9fe455607 | |||
| 35387bbd7d | |||
| b26a0f98fc | |||
| b697258ad5 | |||
| 6c8aa799b4 | 
| @ -44,6 +44,8 @@ import { | ||||
|   VariableDeclaration, | ||||
|   VariableDeclarator, | ||||
|   sketchGroupFromKclValue, | ||||
|   ExecState, | ||||
|   sketchGroupFromArtifactId, | ||||
| } from 'lang/wasm' | ||||
| import { | ||||
|   engineCommandManager, | ||||
| @ -77,7 +79,11 @@ import { | ||||
|   createPipeSubstitution, | ||||
|   findUniqueName, | ||||
| } from 'lang/modifyAst' | ||||
| import { Selections, getEventForSegmentSelection } from 'lib/selections' | ||||
| import { | ||||
|   Selection, | ||||
|   Selections, | ||||
|   getEventForSegmentSelection, | ||||
| } from 'lib/selections' | ||||
| import { createGridHelper, orthoScale, perspScale } from './helpers' | ||||
| import { Models } from '@kittycad/lib' | ||||
| import { uuidv4 } from 'lib/utils' | ||||
| @ -90,7 +96,12 @@ import { | ||||
| import { getThemeColorForThreeJs, Themes } from 'lib/theme' | ||||
| import { err, reportRejection, trap } from 'lib/trap' | ||||
| import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' | ||||
| import { Point3d } from 'wasm-lib/kcl/bindings/Point3d' | ||||
| import { | ||||
|   ArtifactGraph, | ||||
|   ArtifactId, | ||||
|   getPathFromSelection, | ||||
|   getPlaneOrFaceFromSelection, | ||||
| } from 'lang/std/artifactGraph' | ||||
| import { SegmentInputs } from 'lang/std/stdTypes' | ||||
|  | ||||
| type DraftSegment = 'line' | 'tangentialArcTo' | ||||
| @ -122,8 +133,6 @@ export const SEGMENT_BODIES_PLUS_PROFILE_START = [ | ||||
|   PROFILE_START, | ||||
| ] | ||||
|  | ||||
| type Vec3Array = [number, number, number] | ||||
|  | ||||
| // This singleton Class is responsible for all of the things the user sees and interacts with. | ||||
| // That mostly mean sketch elements. | ||||
| // Cameras, controls, raycasters, etc are handled by sceneInfra | ||||
| @ -391,17 +400,37 @@ export class SceneEntities { | ||||
|     const { truncatedAst, programMemoryOverride, variableDeclarationName } = | ||||
|       prepared | ||||
|  | ||||
|     const { programMemory } = await executeAst({ | ||||
|     const { execState } = await executeAst({ | ||||
|       ast: truncatedAst, | ||||
|       useFakeExecutor: true, | ||||
|       engineCommandManager: this.engineCommandManager, | ||||
|       programMemoryOverride, | ||||
|     }) | ||||
|     const sketchGroup = sketchGroupFromPathToNode({ | ||||
|       pathToNode: sketchPathToNode, | ||||
|       ast: maybeModdedAst, | ||||
|       programMemory, | ||||
|     }) | ||||
|     const programMemory = execState.memory | ||||
|     let sketchGroup: SketchGroup | null | Error = null | ||||
|     if (selectionRanges) { | ||||
|       console.warn('setupSketch looking for sketch from selection') | ||||
|       sketchGroup = sketchGroupFromSelection({ | ||||
|         selections: selectionRanges, | ||||
|         execState: kclManager.execState, | ||||
|         // execState, | ||||
|         artifactGraph: this.engineCommandManager.artifactGraph, | ||||
|       }) | ||||
|       if (sketchGroup) { | ||||
|         console.warn('setupSketch found sketch from selection') | ||||
|       } | ||||
|     } | ||||
|     if (!sketchGroup) { | ||||
|       console.warn( | ||||
|         'setupSketch sketch not found from selection; falling back to program memory' | ||||
|       ) | ||||
|       // Fall back to the sketch group from the program memory. | ||||
|       sketchGroup = sketchGroupFromPathToNode({ | ||||
|         pathToNode: sketchPathToNode, | ||||
|         ast: maybeModdedAst, | ||||
|         programMemory, | ||||
|       }) | ||||
|     } | ||||
|     if (err(sketchGroup)) return Promise.reject(sketchGroup) | ||||
|     if (!sketchGroup) return Promise.reject('sketchGroup not found') | ||||
|  | ||||
| @ -598,11 +627,13 @@ export class SceneEntities { | ||||
|     const _node1 = getNodeFromPath<VariableDeclaration>( | ||||
|       _ast, | ||||
|       sketchPathToNode || [], | ||||
|       'VariableDeclaration' | ||||
|     ) | ||||
|       ['VariableDeclaration', 'ExpressionStatement'] | ||||
|     ) as { node: { type: string } } | Error | ||||
|     if (trap(_node1)) return Promise.reject(_node1) | ||||
|     const variableDeclarationName = | ||||
|       _node1.node?.declarations?.[0]?.id?.name || '' | ||||
|       _node1.node.type === 'VariableDeclaration' | ||||
|         ? (_node1.node as VariableDeclaration).declarations[0]?.id?.name || '' | ||||
|         : '' | ||||
|  | ||||
|     const sg = sketchGroupFromKclValue( | ||||
|       kclManager.programMemory.get(variableDeclarationName), | ||||
| @ -802,12 +833,13 @@ export class SceneEntities { | ||||
|           updateRectangleSketch(sketchInit, x, y, tags[0]) | ||||
|         } | ||||
|  | ||||
|         const { programMemory } = await executeAst({ | ||||
|         const { execState } = await executeAst({ | ||||
|           ast: truncatedAst, | ||||
|           useFakeExecutor: true, | ||||
|           engineCommandManager: this.engineCommandManager, | ||||
|           programMemoryOverride, | ||||
|         }) | ||||
|         const programMemory = execState.memory | ||||
|         this.sceneProgramMemory = programMemory | ||||
|         const sketchGroup = sketchGroupFromKclValue( | ||||
|           programMemory.get(variableDeclarationName), | ||||
| @ -856,12 +888,13 @@ export class SceneEntities { | ||||
|           await kclManager.executeAstMock(_ast) | ||||
|           sceneInfra.modelingSend({ type: 'Finish rectangle' }) | ||||
|  | ||||
|           const { programMemory } = await executeAst({ | ||||
|           const { execState } = await executeAst({ | ||||
|             ast: _ast, | ||||
|             useFakeExecutor: true, | ||||
|             engineCommandManager: this.engineCommandManager, | ||||
|             programMemoryOverride, | ||||
|           }) | ||||
|           const programMemory = execState.memory | ||||
|  | ||||
|           // Prepare to update the THREEjs scene | ||||
|           this.sceneProgramMemory = programMemory | ||||
| @ -980,12 +1013,13 @@ export class SceneEntities { | ||||
|           modded = moddedResult.modifiedAst | ||||
|         } | ||||
|  | ||||
|         const { programMemory } = await executeAst({ | ||||
|         const { execState } = await executeAst({ | ||||
|           ast: modded, | ||||
|           useFakeExecutor: true, | ||||
|           engineCommandManager: this.engineCommandManager, | ||||
|           programMemoryOverride, | ||||
|         }) | ||||
|         const programMemory = execState.memory | ||||
|         this.sceneProgramMemory = programMemory | ||||
|         const sketchGroup = sketchGroupFromKclValue( | ||||
|           programMemory.get(variableDeclarationName), | ||||
| @ -1339,12 +1373,13 @@ export class SceneEntities { | ||||
|         // don't want to mod the user's code yet as they have't committed to the change yet | ||||
|         // plus this would be the truncated ast being recast, it would be wrong | ||||
|         codeManager.updateCodeEditor(code) | ||||
|       const { programMemory } = await executeAst({ | ||||
|       const { execState } = await executeAst({ | ||||
|         ast: truncatedAst, | ||||
|         useFakeExecutor: true, | ||||
|         engineCommandManager: this.engineCommandManager, | ||||
|         programMemoryOverride, | ||||
|       }) | ||||
|       const programMemory = execState.memory | ||||
|       this.sceneProgramMemory = programMemory | ||||
|  | ||||
|       const maybeSketchGroup = programMemory.get(variableDeclarationName) | ||||
| @ -1820,6 +1855,70 @@ export function getParentGroup( | ||||
|   return null | ||||
| } | ||||
|  | ||||
| export async function planeOrFaceFromSelection({ | ||||
|   artifactGraph, | ||||
|   selection, | ||||
| }: { | ||||
|   artifactGraph: ArtifactGraph | ||||
|   selection: Selection | ||||
| }): Promise<{ | ||||
|   id: ArtifactId | ||||
|   faceDetails: Models['GetSketchModePlane_type'] | ||||
| } | null> { | ||||
|   // If the selection doesn't have an artifactId associated with it, we can't | ||||
|   // do it. | ||||
|   if (!selection.artifactId) return null | ||||
|  | ||||
|   const planeOrFace = getPlaneOrFaceFromSelection( | ||||
|     selection.artifactId, | ||||
|     artifactGraph | ||||
|   ) | ||||
|   if (!planeOrFace) return null | ||||
|   if (planeOrFace?.type === 'plane') { | ||||
|     const faceDetails = await getFaceDetails(planeOrFace.id) | ||||
|     return { id: planeOrFace.id, faceDetails } | ||||
|   } | ||||
|   // TODO: Handle wall or cap artifact. | ||||
|   return null | ||||
| } | ||||
|  | ||||
| export function sketchGroupFromSelection({ | ||||
|   selections, | ||||
|   artifactGraph, | ||||
|   execState, | ||||
| }: { | ||||
|   selections: Selections | ||||
|   artifactGraph: ArtifactGraph | ||||
|   execState: ExecState | ||||
| }): SketchGroup | null { | ||||
|   if (selections.codeBasedSelections.length !== 1) { | ||||
|     // Give up if there isn't exactly one selection. | ||||
|     console.warn( | ||||
|       'sketchGroupFromSelection no single selection', | ||||
|       selections.codeBasedSelections.length, | ||||
|       selections | ||||
|     ) | ||||
|     return null | ||||
|   } | ||||
|   const selection = selections.codeBasedSelections[0] | ||||
|   const artifactId = selection.artifactId | ||||
|   if (!artifactId) { | ||||
|     console.warn( | ||||
|       'sketchGroupFromSelection artifact ID not found', | ||||
|       selections.codeBasedSelections.length, | ||||
|       selections | ||||
|     ) | ||||
|   } | ||||
|   if (!artifactId) return null | ||||
|   const path = getPathFromSelection(artifactId, artifactGraph) | ||||
|   if (!path) { | ||||
|     console.warn('sketchGroupFromSelection path not found', artifactId) | ||||
|     return null | ||||
|   } | ||||
|   const sketch = sketchGroupFromArtifactId(execState, path.id) | ||||
|   return sketch | ||||
| } | ||||
|  | ||||
| export function sketchGroupFromPathToNode({ | ||||
|   pathToNode, | ||||
|   ast, | ||||
| @ -1829,6 +1928,7 @@ export function sketchGroupFromPathToNode({ | ||||
|   ast: Program | ||||
|   programMemory: ProgramMemory | ||||
| }): SketchGroup | null | Error { | ||||
|   console.warn('**** sketchGroupFromPathToNode **** Deprecated') | ||||
|   const _varDec = getNodeFromPath<VariableDeclarator>( | ||||
|     kclManager.ast, | ||||
|     pathToNode, | ||||
| @ -1868,27 +1968,55 @@ function colorSegment(object: any, color: number) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function getSketchQuaternion( | ||||
|   sketchPathToNode: PathToNode, | ||||
|   sketchNormalBackUp: [number, number, number] | null | ||||
| ): Quaternion | Error { | ||||
|   const sketchGroup = sketchGroupFromPathToNode({ | ||||
|     pathToNode: sketchPathToNode, | ||||
|     ast: kclManager.ast, | ||||
|     programMemory: kclManager.programMemory, | ||||
|   }) | ||||
|   if (err(sketchGroup)) return sketchGroup | ||||
|   const zAxis = sketchGroup?.on.zAxis || sketchNormalBackUp | ||||
|   if (!zAxis) return Error('SketchGroup zAxis not found') | ||||
|  | ||||
|   return getQuaternionFromZAxis(massageFormats(zAxis)) | ||||
| } | ||||
| export async function getSketchOrientationDetails( | ||||
|   artifactGraph: ArtifactGraph, | ||||
|   selection: Selection, | ||||
|   sketchPathToNode: PathToNode | ||||
| ): Promise<{ | ||||
|   quat: Quaternion | ||||
|   sketchDetails: SketchDetails & { faceId?: string } | ||||
|   sketchDetails: { | ||||
|     zAxis: [number, number, number] | ||||
|     yAxis: [number, number, number] | ||||
|     origin: [number, number, number] | ||||
|     faceId: string | ||||
|   } | ||||
| }> { | ||||
|   const plane = await planeOrFaceFromSelection({ | ||||
|     artifactGraph, | ||||
|     selection, | ||||
|   }) | ||||
|   if (plane) { | ||||
|     const details = plane.faceDetails | ||||
|     console.warn('Found plane', plane) | ||||
|     const zAxis: [number, number, number] = [ | ||||
|       details.z_axis.x, | ||||
|       details.z_axis.y, | ||||
|       details.z_axis.z, | ||||
|     ] | ||||
|     const yAxis: [number, number, number] = [ | ||||
|       details.y_axis.x, | ||||
|       details.y_axis.y, | ||||
|       details.y_axis.z, | ||||
|     ] | ||||
|     const origin: [number, number, number] = [ | ||||
|       details.origin.x, | ||||
|       details.origin.y, | ||||
|       details.origin.z, | ||||
|     ] | ||||
|     return { | ||||
|       sketchDetails: { | ||||
|         zAxis, | ||||
|         yAxis, | ||||
|         origin, | ||||
|         faceId: plane.id, | ||||
|       }, | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // We couldn't find the plane or face, so try to look at the AST, and find it | ||||
|   // through there. | ||||
|   console.warn( | ||||
|     'getSketchOrientationDetails falling back to sketchGroupFromPathToNode' | ||||
|   ) | ||||
|   const sketchGroup = sketchGroupFromPathToNode({ | ||||
|     pathToNode: sketchPathToNode, | ||||
|     ast: kclManager.ast, | ||||
| @ -1900,9 +2028,7 @@ export async function getSketchOrientationDetails( | ||||
|   if (sketchGroup.on.type === 'plane') { | ||||
|     const zAxis = sketchGroup?.on.zAxis | ||||
|     return { | ||||
|       quat: getQuaternionFromZAxis(massageFormats(zAxis)), | ||||
|       sketchDetails: { | ||||
|         sketchPathToNode, | ||||
|         zAxis: [zAxis.x, zAxis.y, zAxis.z], | ||||
|         yAxis: [ | ||||
|           sketchGroup.on.yAxis.x, | ||||
| @ -1921,14 +2047,8 @@ export async function getSketchOrientationDetails( | ||||
|     if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) | ||||
|       return Promise.reject('face info') | ||||
|     const { z_axis, y_axis, origin } = faceInfo | ||||
|     const quaternion = quaternionFromUpNForward( | ||||
|       new Vector3(y_axis.x, y_axis.y, y_axis.z), | ||||
|       new Vector3(z_axis.x, z_axis.y, z_axis.z) | ||||
|     ) | ||||
|     return { | ||||
|       quat: quaternion, | ||||
|       sketchDetails: { | ||||
|         sketchPathToNode, | ||||
|         zAxis: [z_axis.x, z_axis.y, z_axis.z], | ||||
|         yAxis: [y_axis.x, y_axis.y, y_axis.z], | ||||
|         origin: [origin.x, origin.y, origin.z], | ||||
| @ -2003,7 +2123,3 @@ export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion { | ||||
|   } | ||||
|   return quaternion | ||||
| } | ||||
|  | ||||
| function massageFormats(a: Vec3Array | Point3d): Vector3 { | ||||
|   return isArray(a) ? new Vector3(a[0], a[1], a[2]) : new Vector3(a.x, a.y, a.z) | ||||
| } | ||||
|  | ||||
| @ -157,7 +157,7 @@ export function useCalc({ | ||||
|         engineCommandManager, | ||||
|         useFakeExecutor: true, | ||||
|         programMemoryOverride: kclManager.programMemory.clone(), | ||||
|       }).then(({ programMemory }) => { | ||||
|       }).then(({ execState }) => { | ||||
|         const resultDeclaration = ast.body.find( | ||||
|           (a) => | ||||
|             a.type === 'VariableDeclaration' && | ||||
| @ -166,7 +166,7 @@ export function useCalc({ | ||||
|         const init = | ||||
|           resultDeclaration?.type === 'VariableDeclaration' && | ||||
|           resultDeclaration?.declarations?.[0]?.init | ||||
|         const result = programMemory?.get('__result__')?.value | ||||
|         const result = execState.memory?.get('__result__')?.value | ||||
|         setCalcResult(typeof result === 'number' ? String(result) : 'NAN') | ||||
|         init && setValueNode(init) | ||||
|       }) | ||||
|  | ||||
| @ -625,12 +625,15 @@ export const ModelingMachineProvider = ({ | ||||
|         }), | ||||
|         'animate-to-sketch': fromPromise( | ||||
|           async ({ input: { selectionRanges } }) => { | ||||
|             const sourceRange = selectionRanges.codeBasedSelections[0].range | ||||
|             const selection = selectionRanges.codeBasedSelections[0] | ||||
|             const sourceRange = selection.range | ||||
|             const sketchPathToNode = getNodePathFromSourceRange( | ||||
|               kclManager.ast, | ||||
|               sourceRange | ||||
|             ) | ||||
|             const info = await getSketchOrientationDetails( | ||||
|               engineCommandManager.artifactGraph, | ||||
|               selection, | ||||
|               sketchPathToNode || [] | ||||
|             ) | ||||
|             await letEngineAnimateAndSyncCamAfter( | ||||
|  | ||||
| @ -29,8 +29,8 @@ describe('processMemory', () => { | ||||
|     |> lineTo([2.15, 4.32], %) | ||||
|     // |> rx(90, %)` | ||||
|     const ast = parse(code) | ||||
|     const programMemory = await enginelessExecutor(ast, ProgramMemory.empty()) | ||||
|     const output = processMemory(programMemory) | ||||
|     const execState = await enginelessExecutor(ast, ProgramMemory.empty()) | ||||
|     const output = processMemory(execState.memory) | ||||
|     expect(output.myVar).toEqual(5) | ||||
|     expect(output.otherVar).toEqual(3) | ||||
|     expect(output).toEqual({ | ||||
|  | ||||
| @ -8,6 +8,8 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants' | ||||
|  | ||||
| import { | ||||
|   CallExpression, | ||||
|   emptyExecState, | ||||
|   ExecState, | ||||
|   initPromise, | ||||
|   parse, | ||||
|   PathToNode, | ||||
| @ -19,6 +21,8 @@ import { | ||||
| import { getNodeFromPath } from './queryAst' | ||||
| import { codeManager, editorManager, sceneInfra } from 'lib/singletons' | ||||
| import { Diagnostic } from '@codemirror/lint' | ||||
| import { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId' | ||||
| import { Artifact } from 'wasm-lib/kcl/bindings/Artifact' | ||||
|  | ||||
| interface ExecuteArgs { | ||||
|   ast?: Program | ||||
| @ -43,6 +47,7 @@ export class KclManager { | ||||
|     digest: null, | ||||
|   } | ||||
|   private _programMemory: ProgramMemory = ProgramMemory.empty() | ||||
|   private _execState: ExecState = emptyExecState() | ||||
|   private _logs: string[] = [] | ||||
|   private _lints: Diagnostic[] = [] | ||||
|   private _kclErrors: KCLError[] = [] | ||||
| @ -71,11 +76,21 @@ export class KclManager { | ||||
|   get programMemory() { | ||||
|     return this._programMemory | ||||
|   } | ||||
|   set programMemory(programMemory) { | ||||
|   // This is private because callers should be setting the entire execState. | ||||
|   private set programMemory(programMemory) { | ||||
|     this._programMemory = programMemory | ||||
|     this._programMemoryCallBack(programMemory) | ||||
|   } | ||||
|  | ||||
|   set execState(execState) { | ||||
|     this._execState = execState | ||||
|     this.programMemory = execState.memory | ||||
|   } | ||||
|  | ||||
|   get execState() { | ||||
|     return this._execState | ||||
|   } | ||||
|  | ||||
|   get logs() { | ||||
|     return this._logs | ||||
|   } | ||||
| @ -252,7 +267,7 @@ export class KclManager { | ||||
|     // Make sure we clear before starting again. End session will do this. | ||||
|     this.engineCommandManager?.endSession() | ||||
|     await this.ensureWasmInit() | ||||
|     const { logs, errors, programMemory, isInterrupted } = await executeAst({ | ||||
|     const { logs, errors, execState, isInterrupted } = await executeAst({ | ||||
|       ast, | ||||
|       engineCommandManager: this.engineCommandManager, | ||||
|     }) | ||||
| @ -263,7 +278,7 @@ export class KclManager { | ||||
|       this.lints = await lintAst({ ast: ast }) | ||||
|  | ||||
|       sceneInfra.modelingSend({ type: 'code edit during sketch' }) | ||||
|       defaultSelectionFilter(programMemory, this.engineCommandManager) | ||||
|       defaultSelectionFilter(execState.memory, this.engineCommandManager) | ||||
|  | ||||
|       if (args.zoomToFit) { | ||||
|         let zoomObjectId: string | undefined = '' | ||||
| @ -296,7 +311,7 @@ export class KclManager { | ||||
|     this.logs = logs | ||||
|     // Do not add the errors since the program was interrupted and the error is not a real KCL error | ||||
|     this.addKclErrors(isInterrupted ? [] : errors) | ||||
|     this.programMemory = programMemory | ||||
|     this.execState = execState | ||||
|     this.ast = { ...ast } | ||||
|     this._executeCallback() | ||||
|     this.engineCommandManager.addCommandLog({ | ||||
| @ -333,7 +348,7 @@ export class KclManager { | ||||
|     await codeManager.writeToFile() | ||||
|     this._ast = { ...newAst } | ||||
|  | ||||
|     const { logs, errors, programMemory } = await executeAst({ | ||||
|     const { logs, errors, execState } = await executeAst({ | ||||
|       ast: newAst, | ||||
|       engineCommandManager: this.engineCommandManager, | ||||
|       useFakeExecutor: true, | ||||
| @ -341,7 +356,8 @@ export class KclManager { | ||||
|  | ||||
|     this._logs = logs | ||||
|     this._kclErrors = errors | ||||
|     this._programMemory = programMemory | ||||
|     this._execState = execState | ||||
|     this._programMemory = execState.memory | ||||
|     if (updates !== 'artifactRanges') return | ||||
|  | ||||
|     // TODO the below seems like a work around, I wish there's a comment explaining exactly what | ||||
|  | ||||
| @ -445,6 +445,6 @@ async function exe( | ||||
| ) { | ||||
|   const ast = parse(code) | ||||
|  | ||||
|   const result = await enginelessExecutor(ast, programMemory) | ||||
|   return result | ||||
|   const execState = await enginelessExecutor(ast, programMemory) | ||||
|   return execState.memory | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,8 @@ import { | ||||
|   ProgramMemory, | ||||
|   programMemoryInit, | ||||
|   kclLint, | ||||
|   emptyExecState, | ||||
|   ExecState, | ||||
| } from 'lang/wasm' | ||||
| import { enginelessExecutor } from 'lib/testHelpers' | ||||
| import { EngineCommandManager } from 'lang/std/engineConnection' | ||||
| @ -56,7 +58,7 @@ export async function executeAst({ | ||||
| }): Promise<{ | ||||
|   logs: string[] | ||||
|   errors: KCLError[] | ||||
|   programMemory: ProgramMemory | ||||
|   execState: ExecState | ||||
|   isInterrupted: boolean | ||||
| }> { | ||||
|   try { | ||||
| @ -65,7 +67,7 @@ export async function executeAst({ | ||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||||
|       engineCommandManager.startNewSession() | ||||
|     } | ||||
|     const programMemory = await (useFakeExecutor | ||||
|     const execState = await (useFakeExecutor | ||||
|       ? enginelessExecutor(ast, programMemoryOverride || programMemoryInit()) | ||||
|       : _executor(ast, programMemoryInit(), engineCommandManager, false)) | ||||
|  | ||||
| @ -73,7 +75,7 @@ export async function executeAst({ | ||||
|     return { | ||||
|       logs: [], | ||||
|       errors: [], | ||||
|       programMemory, | ||||
|       execState, | ||||
|       isInterrupted: false, | ||||
|     } | ||||
|   } catch (e: any) { | ||||
| @ -89,7 +91,7 @@ export async function executeAst({ | ||||
|       return { | ||||
|         errors: [e], | ||||
|         logs: [], | ||||
|         programMemory: ProgramMemory.empty(), | ||||
|         execState: emptyExecState(), | ||||
|         isInterrupted, | ||||
|       } | ||||
|     } else { | ||||
| @ -97,7 +99,7 @@ export async function executeAst({ | ||||
|       return { | ||||
|         logs: [e], | ||||
|         errors: [], | ||||
|         programMemory: ProgramMemory.empty(), | ||||
|         execState: emptyExecState(), | ||||
|         isInterrupted, | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @ -220,11 +220,11 @@ const yo2 = hmm([identifierGuy + 5])` | ||||
|   it('should move a binary expression into a new variable', async () => { | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) throw ast | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const startIndex = code.indexOf('100 + 100') + 1 | ||||
|     const { modifiedAst } = moveValueIntoNewVariable( | ||||
|       ast, | ||||
|       programMemory, | ||||
|       execState.memory, | ||||
|       [startIndex, startIndex], | ||||
|       'newVar' | ||||
|     ) | ||||
| @ -235,11 +235,11 @@ const yo2 = hmm([identifierGuy + 5])` | ||||
|   it('should move a value into a new variable', async () => { | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) throw ast | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const startIndex = code.indexOf('2.8') + 1 | ||||
|     const { modifiedAst } = moveValueIntoNewVariable( | ||||
|       ast, | ||||
|       programMemory, | ||||
|       execState.memory, | ||||
|       [startIndex, startIndex], | ||||
|       'newVar' | ||||
|     ) | ||||
| @ -250,11 +250,11 @@ const yo2 = hmm([identifierGuy + 5])` | ||||
|   it('should move a callExpression into a new variable', async () => { | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) throw ast | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const startIndex = code.indexOf('def(') | ||||
|     const { modifiedAst } = moveValueIntoNewVariable( | ||||
|       ast, | ||||
|       programMemory, | ||||
|       execState.memory, | ||||
|       [startIndex, startIndex], | ||||
|       'newVar' | ||||
|     ) | ||||
| @ -265,11 +265,11 @@ const yo2 = hmm([identifierGuy + 5])` | ||||
|   it('should move a binary expression with call expression into a new variable', async () => { | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) throw ast | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const startIndex = code.indexOf('jkl(') + 1 | ||||
|     const { modifiedAst } = moveValueIntoNewVariable( | ||||
|       ast, | ||||
|       programMemory, | ||||
|       execState.memory, | ||||
|       [startIndex, startIndex], | ||||
|       'newVar' | ||||
|     ) | ||||
| @ -280,11 +280,11 @@ const yo2 = hmm([identifierGuy + 5])` | ||||
|   it('should move a identifier into a new variable', async () => { | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) throw ast | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const startIndex = code.indexOf('identifierGuy +') + 1 | ||||
|     const { modifiedAst } = moveValueIntoNewVariable( | ||||
|       ast, | ||||
|       programMemory, | ||||
|       execState.memory, | ||||
|       [startIndex, startIndex], | ||||
|       'newVar' | ||||
|     ) | ||||
| @ -465,7 +465,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => { | ||||
|   |> line([306.21, 198.87], %)` | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) throw ast | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const lineOfInterest = 'line([306.21, 198.85], %, $a)' | ||||
|     const range: [number, number] = [ | ||||
|       code.indexOf(lineOfInterest), | ||||
| @ -475,7 +475,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => { | ||||
|     const modifiedAst = deleteSegmentFromPipeExpression( | ||||
|       [], | ||||
|       ast, | ||||
|       programMemory, | ||||
|       execState.memory, | ||||
|       code, | ||||
|       pathToNode | ||||
|     ) | ||||
| @ -543,7 +543,7 @@ ${!replace1 ? `  |> ${line}\n` : ''}  |> angledLine([-65, ${ | ||||
|       const code = makeCode(line) | ||||
|       const ast = parse(code) | ||||
|       if (err(ast)) throw ast | ||||
|       const programMemory = await enginelessExecutor(ast) | ||||
|       const execState = await enginelessExecutor(ast) | ||||
|       const lineOfInterest = line | ||||
|       const range: [number, number] = [ | ||||
|         code.indexOf(lineOfInterest), | ||||
| @ -554,7 +554,7 @@ ${!replace1 ? `  |> ${line}\n` : ''}  |> angledLine([-65, ${ | ||||
|       const modifiedAst = deleteSegmentFromPipeExpression( | ||||
|         dependentSegments, | ||||
|         ast, | ||||
|         programMemory, | ||||
|         execState.memory, | ||||
|         code, | ||||
|         pathToNode | ||||
|       ) | ||||
| @ -632,7 +632,7 @@ describe('Testing removeSingleConstraintInfo', () => { | ||||
|       const ast = parse(code) | ||||
|       if (err(ast)) throw ast | ||||
|  | ||||
|       const programMemory = await enginelessExecutor(ast) | ||||
|       const execState = await enginelessExecutor(ast) | ||||
|       const lineOfInterest = expectedFinish.split('(')[0] + '(' | ||||
|       const range: [number, number] = [ | ||||
|         code.indexOf(lineOfInterest) + 1, | ||||
| @ -661,7 +661,7 @@ describe('Testing removeSingleConstraintInfo', () => { | ||||
|         pathToNode, | ||||
|         argPosition, | ||||
|         ast, | ||||
|         programMemory | ||||
|         execState.memory | ||||
|       ) | ||||
|       if (!mod) return new Error('mod is undefined') | ||||
|       const recastCode = recast(mod.modifiedAst) | ||||
| @ -686,7 +686,7 @@ describe('Testing removeSingleConstraintInfo', () => { | ||||
|       const ast = parse(code) | ||||
|       if (err(ast)) throw ast | ||||
|  | ||||
|       const programMemory = await enginelessExecutor(ast) | ||||
|       const execState = await enginelessExecutor(ast) | ||||
|       const lineOfInterest = expectedFinish.split('(')[0] + '(' | ||||
|       const range: [number, number] = [ | ||||
|         code.indexOf(lineOfInterest) + 1, | ||||
| @ -711,7 +711,7 @@ describe('Testing removeSingleConstraintInfo', () => { | ||||
|         pathToNode, | ||||
|         argPosition, | ||||
|         ast, | ||||
|         programMemory | ||||
|         execState.memory | ||||
|       ) | ||||
|       if (!mod) return new Error('mod is undefined') | ||||
|       const recastCode = recast(mod.modifiedAst) | ||||
| @ -882,7 +882,7 @@ const sketch002 = startSketchOn({ | ||||
|       // const lineOfInterest = 'line([-2.94, 2.7], %)' | ||||
|       const ast = parse(codeBefore) | ||||
|       if (err(ast)) throw ast | ||||
|       const programMemory = await enginelessExecutor(ast) | ||||
|       const execState = await enginelessExecutor(ast) | ||||
|  | ||||
|       // deleteFromSelection | ||||
|       const range: [number, number] = [ | ||||
| @ -895,7 +895,7 @@ const sketch002 = startSketchOn({ | ||||
|           range, | ||||
|           type, | ||||
|         }, | ||||
|         programMemory, | ||||
|         execState.memory, | ||||
|         async () => { | ||||
|           await new Promise((resolve) => setTimeout(resolve, 100)) | ||||
|           return { | ||||
|  | ||||
| @ -18,6 +18,7 @@ import { | ||||
|   ProgramMemory, | ||||
|   SourceRange, | ||||
|   sketchGroupFromKclValue, | ||||
|   ExpressionStatement, | ||||
| } from './wasm' | ||||
| import { | ||||
|   isNodeSafeToReplacePath, | ||||
| @ -80,18 +81,24 @@ export function addStartProfileAt( | ||||
|   pathToNode: PathToNode, | ||||
|   at: [number, number] | ||||
| ): { modifiedAst: Program; pathToNode: PathToNode } | Error { | ||||
|   const _node1 = getNodeFromPath<VariableDeclaration>( | ||||
|     node, | ||||
|     pathToNode, | ||||
|     'VariableDeclaration' | ||||
|   ) | ||||
|   const _node1 = getNodeFromPath<VariableDeclaration>(node, pathToNode, [ | ||||
|     'VariableDeclaration', | ||||
|     'ExpressionStatement', | ||||
|   ]) as { node: { type: string } } | Error | ||||
|   if (err(_node1)) return _node1 | ||||
|   const variableDeclaration = _node1.node | ||||
|   if (variableDeclaration.type !== 'VariableDeclaration') { | ||||
|     return new Error('variableDeclaration.init.type !== PipeExpression') | ||||
|   } | ||||
|   const _node = { ...node } | ||||
|   const init = variableDeclaration.declarations[0].init | ||||
|   let expr: Expr | ||||
|   let variableDeclaration: VariableDeclaration | undefined | ||||
|   if (_node1.node.type === 'VariableDeclaration') { | ||||
|     const node: VariableDeclaration = _node1.node as VariableDeclaration | ||||
|     variableDeclaration = node | ||||
|     expr = node.declarations[0].init | ||||
|   } else if (_node1.node.type === 'ExpressionStatement') { | ||||
|     const node: ExpressionStatement = _node1.node as ExpressionStatement | ||||
|     expr = node.expression | ||||
|   } else { | ||||
|     return new Error(`Unrecognized node type ${_node1.node.type}`) | ||||
|   } | ||||
|   const startProfileAt = createCallExpressionStdLib('startProfileAt', [ | ||||
|     createArrayExpression([ | ||||
|       createLiteral(roundOff(at[0])), | ||||
| @ -99,11 +106,11 @@ export function addStartProfileAt( | ||||
|     ]), | ||||
|     createPipeSubstitution(), | ||||
|   ]) | ||||
|   if (init.type === 'PipeExpression') { | ||||
|     init.body.splice(1, 0, startProfileAt) | ||||
|   } else { | ||||
|   if (expr.type === 'PipeExpression') { | ||||
|     expr.body.splice(1, 0, startProfileAt) | ||||
|   } else if (variableDeclaration) { | ||||
|     variableDeclaration.declarations[0].init = createPipeExpression([ | ||||
|       init, | ||||
|       expr, | ||||
|       startProfileAt, | ||||
|     ]) | ||||
|   } | ||||
|  | ||||
| @ -45,11 +45,11 @@ const variableBelowShouldNotBeIncluded = 3 | ||||
|     const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7 | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) throw ast | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|  | ||||
|     const { variables, bodyPath, insertIndex } = findAllPreviousVariables( | ||||
|       ast, | ||||
|       programMemory, | ||||
|       execState.memory, | ||||
|       [rangeStart, rangeStart] | ||||
|     ) | ||||
|     expect(variables).toEqual([ | ||||
| @ -351,11 +351,11 @@ const part001 = startSketchAt([-1.41, 3.46]) | ||||
|     const ast = parse(exampleCode) | ||||
|     if (err(ast)) throw ast | ||||
|  | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const result = hasExtrudeSketchGroup({ | ||||
|       ast, | ||||
|       selection: { type: 'default', range: [100, 101] }, | ||||
|       programMemory, | ||||
|       programMemory: execState.memory, | ||||
|     }) | ||||
|     expect(result).toEqual(true) | ||||
|   }) | ||||
| @ -370,11 +370,11 @@ const part001 = startSketchAt([-1.41, 3.46]) | ||||
|     const ast = parse(exampleCode) | ||||
|     if (err(ast)) throw ast | ||||
|  | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const result = hasExtrudeSketchGroup({ | ||||
|       ast, | ||||
|       selection: { type: 'default', range: [100, 101] }, | ||||
|       programMemory, | ||||
|       programMemory: execState.memory, | ||||
|     }) | ||||
|     expect(result).toEqual(true) | ||||
|   }) | ||||
| @ -383,11 +383,11 @@ const part001 = startSketchAt([-1.41, 3.46]) | ||||
|     const ast = parse(exampleCode) | ||||
|     if (err(ast)) throw ast | ||||
|  | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const result = hasExtrudeSketchGroup({ | ||||
|       ast, | ||||
|       selection: { type: 'default', range: [10, 11] }, | ||||
|       programMemory, | ||||
|       programMemory: execState.memory, | ||||
|     }) | ||||
|     expect(result).toEqual(false) | ||||
|   }) | ||||
|  | ||||
| @ -795,7 +795,7 @@ export function isSingleCursorInPipe( | ||||
|   const pathToNode = getNodePathFromSourceRange(ast, selection.range) | ||||
|   const nodeTypes = pathToNode.map(([, type]) => type) | ||||
|   if (nodeTypes.includes('FunctionExpression')) return false | ||||
|   if (!nodeTypes.includes('VariableDeclaration')) return false | ||||
|   // if (!nodeTypes.includes('VariableDeclaration')) return false | ||||
|   if (nodeTypes.includes('PipeExpression')) return true | ||||
|   return false | ||||
| } | ||||
|  | ||||
| @ -12,17 +12,20 @@ interface CommonCommandProperties { | ||||
|  | ||||
| export interface PlaneArtifact { | ||||
|   type: 'plane' | ||||
|   id: ArtifactId | ||||
|   pathIds: Array<ArtifactId> | ||||
|   codeRef: CommonCommandProperties | ||||
| } | ||||
| export interface PlaneArtifactRich { | ||||
|   type: 'plane' | ||||
|   id: ArtifactId | ||||
|   paths: Array<PathArtifact> | ||||
|   codeRef: CommonCommandProperties | ||||
| } | ||||
|  | ||||
| export interface PathArtifact { | ||||
|   type: 'path' | ||||
|   id: ArtifactId | ||||
|   planeId: ArtifactId | ||||
|   segIds: Array<ArtifactId> | ||||
|   sweepId: ArtifactId | ||||
| @ -32,10 +35,12 @@ export interface PathArtifact { | ||||
|  | ||||
| interface solid2D { | ||||
|   type: 'solid2D' | ||||
|   id: ArtifactId | ||||
|   pathId: ArtifactId | ||||
| } | ||||
| export interface PathArtifactRich { | ||||
|   type: 'path' | ||||
|   id: ArtifactId | ||||
|   plane: PlaneArtifact | WallArtifact | ||||
|   segments: Array<SegmentArtifact> | ||||
|   sweep: SweepArtifact | ||||
| @ -44,6 +49,7 @@ export interface PathArtifactRich { | ||||
|  | ||||
| export interface SegmentArtifact { | ||||
|   type: 'segment' | ||||
|   id: ArtifactId | ||||
|   pathId: ArtifactId | ||||
|   surfaceId: ArtifactId | ||||
|   edgeIds: Array<ArtifactId> | ||||
| @ -52,6 +58,7 @@ export interface SegmentArtifact { | ||||
| } | ||||
| interface SegmentArtifactRich { | ||||
|   type: 'segment' | ||||
|   id: ArtifactId | ||||
|   path: PathArtifact | ||||
|   surf: WallArtifact | ||||
|   edges: Array<SweepEdge> | ||||
| @ -63,14 +70,16 @@ interface SegmentArtifactRich { | ||||
| interface SweepArtifact { | ||||
|   type: 'sweep' | ||||
|   subType: 'extrusion' | 'revolve' | ||||
|   id: ArtifactId | ||||
|   pathId: string | ||||
|   surfaceIds: Array<string> | ||||
|   edgeIds: Array<string> | ||||
|   surfaceIds: Array<ArtifactId> | ||||
|   edgeIds: Array<ArtifactId> | ||||
|   codeRef: CommonCommandProperties | ||||
| } | ||||
| interface SweepArtifactRich { | ||||
|   type: 'sweep' | ||||
|   subType: 'extrusion' | 'revolve' | ||||
|   id: ArtifactId | ||||
|   path: PathArtifact | ||||
|   surfaces: Array<WallArtifact | CapArtifact> | ||||
|   edges: Array<SweepEdge> | ||||
| @ -79,6 +88,7 @@ interface SweepArtifactRich { | ||||
|  | ||||
| interface WallArtifact { | ||||
|   type: 'wall' | ||||
|   id: ArtifactId | ||||
|   segId: ArtifactId | ||||
|   edgeCutEdgeIds: Array<ArtifactId> | ||||
|   sweepId: ArtifactId | ||||
| @ -86,6 +96,7 @@ interface WallArtifact { | ||||
| } | ||||
| interface CapArtifact { | ||||
|   type: 'cap' | ||||
|   id: ArtifactId | ||||
|   subType: 'start' | 'end' | ||||
|   edgeCutEdgeIds: Array<ArtifactId> | ||||
|   sweepId: ArtifactId | ||||
| @ -94,6 +105,7 @@ interface CapArtifact { | ||||
|  | ||||
| interface SweepEdge { | ||||
|   type: 'sweepEdge' | ||||
|   id: ArtifactId | ||||
|   segId: ArtifactId | ||||
|   sweepId: ArtifactId | ||||
|   subType: 'opposite' | 'adjacent' | ||||
| @ -102,6 +114,7 @@ interface SweepEdge { | ||||
| /** A edgeCut is a more generic term for both fillet or chamfer */ | ||||
| interface EdgeCut { | ||||
|   type: 'edgeCut' | ||||
|   id: ArtifactId | ||||
|   subType: 'fillet' | 'chamfer' | ||||
|   consumedEdgeId: ArtifactId | ||||
|   edgeIds: Array<ArtifactId> | ||||
| @ -111,6 +124,7 @@ interface EdgeCut { | ||||
|  | ||||
| interface EdgeCutEdge { | ||||
|   type: 'edgeCutEdge' | ||||
|   id: ArtifactId | ||||
|   edgeCutId: ArtifactId | ||||
|   surfaceId: ArtifactId | ||||
| } | ||||
| @ -215,7 +229,7 @@ function mergeArtifacts( | ||||
|  * It does not mutate the map directly, but returns an array of artifacts to update | ||||
|  * | ||||
|  * @param currentPlaneId is only needed for `start_path` commands because this command does not have a pathId | ||||
|  * instead it relies on the id used with the `enable_sketch_mode` command, so this much be kept track of | ||||
|  * instead it relies on the id used with the `enable_sketch_mode` command, so this must be kept track of | ||||
|  * outside of this function. It would be good to update the `start_path` command to include the planeId so we | ||||
|  * can remove this. | ||||
|  */ | ||||
| @ -258,6 +272,7 @@ export function getArtifactsToUpdate({ | ||||
|           id: currentPlaneId, | ||||
|           artifact: { | ||||
|             type: 'wall', | ||||
|             id: currentPlaneId, | ||||
|             segId: existingPlane.segId, | ||||
|             edgeCutEdgeIds: existingPlane.edgeCutEdgeIds, | ||||
|             sweepId: existingPlane.sweepId, | ||||
| @ -267,7 +282,10 @@ export function getArtifactsToUpdate({ | ||||
|       ] | ||||
|     } else { | ||||
|       return [ | ||||
|         { id: currentPlaneId, artifact: { type: 'plane', pathIds, codeRef } }, | ||||
|         { | ||||
|           id: currentPlaneId, | ||||
|           artifact: { type: 'plane', id: currentPlaneId, pathIds, codeRef }, | ||||
|         }, | ||||
|       ] | ||||
|     } | ||||
|   } else if (cmd.type === 'start_path') { | ||||
| @ -275,6 +293,7 @@ export function getArtifactsToUpdate({ | ||||
|       id, | ||||
|       artifact: { | ||||
|         type: 'path', | ||||
|         id, | ||||
|         segIds: [], | ||||
|         planeId: currentPlaneId, | ||||
|         sweepId: '', | ||||
| @ -287,7 +306,7 @@ export function getArtifactsToUpdate({ | ||||
|     if (plane?.type === 'plane') { | ||||
|       returnArr.push({ | ||||
|         id: currentPlaneId, | ||||
|         artifact: { type: 'plane', pathIds: [id], codeRef }, | ||||
|         artifact: { type: 'plane', id: currentPlaneId, pathIds: [id], codeRef }, | ||||
|       }) | ||||
|     } | ||||
|     if (plane?.type === 'wall') { | ||||
| @ -295,6 +314,7 @@ export function getArtifactsToUpdate({ | ||||
|         id: currentPlaneId, | ||||
|         artifact: { | ||||
|           type: 'wall', | ||||
|           id: currentPlaneId, | ||||
|           segId: plane.segId, | ||||
|           edgeCutEdgeIds: plane.edgeCutEdgeIds, | ||||
|           sweepId: plane.sweepId, | ||||
| @ -309,6 +329,7 @@ export function getArtifactsToUpdate({ | ||||
|       id, | ||||
|       artifact: { | ||||
|         type: 'segment', | ||||
|         id, | ||||
|         pathId, | ||||
|         surfaceId: '', | ||||
|         edgeIds: [], | ||||
| @ -318,21 +339,22 @@ export function getArtifactsToUpdate({ | ||||
|     const path = getArtifact(pathId) | ||||
|     if (path?.type === 'path') | ||||
|       returnArr.push({ | ||||
|         id: pathId, | ||||
|         id: path.id, | ||||
|         artifact: { ...path, segIds: [id] }, | ||||
|       }) | ||||
|     if ( | ||||
|       response?.type === 'modeling' && | ||||
|       response.data.modeling_response.type === 'close_path' | ||||
|     ) { | ||||
|       const id = response.data.modeling_response.data.face_id | ||||
|       returnArr.push({ | ||||
|         id: response.data.modeling_response.data.face_id, | ||||
|         artifact: { type: 'solid2D', pathId }, | ||||
|         id, | ||||
|         artifact: { type: 'solid2D', id, pathId }, | ||||
|       }) | ||||
|       const path = getArtifact(pathId) | ||||
|       if (path?.type === 'path') | ||||
|         returnArr.push({ | ||||
|           id: pathId, | ||||
|           id: path.id, | ||||
|           artifact: { | ||||
|             ...path, | ||||
|             solid2dId: response.data.modeling_response.data.face_id, | ||||
| @ -347,6 +369,7 @@ export function getArtifactsToUpdate({ | ||||
|       artifact: { | ||||
|         type: 'sweep', | ||||
|         subType: subType, | ||||
|         id, | ||||
|         pathId: cmd.target, | ||||
|         surfaceIds: [], | ||||
|         edgeIds: [], | ||||
| @ -356,7 +379,7 @@ export function getArtifactsToUpdate({ | ||||
|     const path = getArtifact(cmd.target) | ||||
|     if (path?.type === 'path') | ||||
|       returnArr.push({ | ||||
|         id: cmd.target, | ||||
|         id: path.id, | ||||
|         artifact: { ...path, sweepId: id }, | ||||
|       }) | ||||
|     return returnArr | ||||
| @ -378,6 +401,7 @@ export function getArtifactsToUpdate({ | ||||
|               id: face_id, | ||||
|               artifact: { | ||||
|                 type: 'wall', | ||||
|                 id: face_id, | ||||
|                 segId: curve_id, | ||||
|                 edgeCutEdgeIds: [], | ||||
|                 sweepId: path.sweepId, | ||||
| @ -385,7 +409,7 @@ export function getArtifactsToUpdate({ | ||||
|               }, | ||||
|             }) | ||||
|             returnArr.push({ | ||||
|               id: curve_id, | ||||
|               id: seg.id, | ||||
|               artifact: { ...seg, surfaceId: face_id }, | ||||
|             }) | ||||
|             const sweep = getArtifact(path.sweepId) | ||||
| @ -394,6 +418,7 @@ export function getArtifactsToUpdate({ | ||||
|                 id: path.sweepId, | ||||
|                 artifact: { | ||||
|                   ...sweep, | ||||
|                   id: path.sweepId, | ||||
|                   surfaceIds: [face_id], | ||||
|                 }, | ||||
|               }) | ||||
| @ -410,6 +435,7 @@ export function getArtifactsToUpdate({ | ||||
|             id: face_id, | ||||
|             artifact: { | ||||
|               type: 'cap', | ||||
|               id: face_id, | ||||
|               subType: cap === 'bottom' ? 'start' : 'end', | ||||
|               edgeCutEdgeIds: [], | ||||
|               sweepId: path.sweepId, | ||||
| @ -419,7 +445,7 @@ export function getArtifactsToUpdate({ | ||||
|           const sweep = getArtifact(path.sweepId) | ||||
|           if (sweep?.type !== 'sweep') return | ||||
|           returnArr.push({ | ||||
|             id: path.sweepId, | ||||
|             id: sweep.id, | ||||
|             artifact: { | ||||
|               ...sweep, | ||||
|               surfaceIds: [face_id], | ||||
| @ -460,6 +486,7 @@ export function getArtifactsToUpdate({ | ||||
|             cmd.type === 'solid3d_get_next_adjacent_edge' | ||||
|               ? 'adjacent' | ||||
|               : 'opposite', | ||||
|           id: response.data.modeling_response.data.edge, | ||||
|           segId: cmd.edge_id, | ||||
|           sweepId: path.sweepId, | ||||
|         }, | ||||
| @ -468,6 +495,7 @@ export function getArtifactsToUpdate({ | ||||
|         id: cmd.edge_id, | ||||
|         artifact: { | ||||
|           ...segment, | ||||
|           id: cmd.edge_id, | ||||
|           edgeIds: [response.data.modeling_response.data.edge], | ||||
|         }, | ||||
|       }, | ||||
| @ -475,6 +503,7 @@ export function getArtifactsToUpdate({ | ||||
|         id: path.sweepId, | ||||
|         artifact: { | ||||
|           ...sweep, | ||||
|           id: path.sweepId, | ||||
|           edgeIds: [response.data.modeling_response.data.edge], | ||||
|         }, | ||||
|       }, | ||||
| @ -484,6 +513,7 @@ export function getArtifactsToUpdate({ | ||||
|       id, | ||||
|       artifact: { | ||||
|         type: 'edgeCut', | ||||
|         id, | ||||
|         subType: cmd.cut_type, | ||||
|         consumedEdgeId: cmd.edge_id, | ||||
|         edgeIds: [], | ||||
| @ -494,7 +524,7 @@ export function getArtifactsToUpdate({ | ||||
|     const consumedEdge = getArtifact(cmd.edge_id) | ||||
|     if (consumedEdge?.type === 'segment') { | ||||
|       returnArr.push({ | ||||
|         id: cmd.edge_id, | ||||
|         id: consumedEdge.id, | ||||
|         artifact: { ...consumedEdge, edgeCutId: id }, | ||||
|       }) | ||||
|     } | ||||
| @ -574,6 +604,7 @@ export function expandPlane( | ||||
|   ) | ||||
|   return { | ||||
|     type: 'plane', | ||||
|     id: plane.id, | ||||
|     paths: Array.from(paths.values()), | ||||
|     codeRef: plane.codeRef, | ||||
|   } | ||||
| @ -602,6 +633,7 @@ export function expandPath( | ||||
|   if (err(plane)) return plane | ||||
|   return { | ||||
|     type: 'path', | ||||
|     id: path.id, | ||||
|     segments: Array.from(segs.values()), | ||||
|     sweep, | ||||
|     plane, | ||||
| @ -629,6 +661,7 @@ export function expandSweep( | ||||
|   return { | ||||
|     type: 'sweep', | ||||
|     subType: 'extrusion', | ||||
|     id: sweep.id, | ||||
|     surfaces: Array.from(surfs.values()), | ||||
|     edges: Array.from(edges.values()), | ||||
|     path, | ||||
| @ -664,6 +697,7 @@ export function expandSegment( | ||||
|  | ||||
|   return { | ||||
|     type: 'segment', | ||||
|     id: segment.id, | ||||
|     path, | ||||
|     surf, | ||||
|     edges: Array.from(edges.values()), | ||||
| @ -785,3 +819,56 @@ export function getSweepFromSuspectedPath( | ||||
|     artifactGraph | ||||
|   ) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get the plane or face from a selection. | ||||
|  * | ||||
|  * TODO: Handle sketch on face. | ||||
|  */ | ||||
| export function getPlaneOrFaceFromSelection( | ||||
|   id: ArtifactId, | ||||
|   artifactGraph: ArtifactGraph | ||||
| ): PlaneArtifactRich | null { | ||||
|   const selection = artifactGraph.get(id) | ||||
|   if (!selection) return null | ||||
|   if (selection.type === 'solid2D') { | ||||
|     const path = artifactGraph.get(selection.pathId) | ||||
|     if (path?.type !== 'path') return null | ||||
|     const plane = artifactGraph.get(path.planeId) | ||||
|     if (plane?.type !== 'plane') return null | ||||
|     return expandPlane(plane, artifactGraph) | ||||
|   } else if (selection.type === 'wall' || selection.type === 'cap') { | ||||
|     const sweep = artifactGraph.get(selection.sweepId) | ||||
|     if (sweep?.type !== 'sweep') return null | ||||
|     const path = artifactGraph.get(sweep.pathId) | ||||
|     if (path?.type !== 'path') return null | ||||
|     const plane = artifactGraph.get(path.planeId) | ||||
|     // TODO: For sketch on face, this won't be a plane. | ||||
|     if (plane?.type !== 'plane') return null | ||||
|     return expandPlane(plane, artifactGraph) | ||||
|   } | ||||
|   return null | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get the path from a selection. | ||||
|  */ | ||||
| export function getPathFromSelection( | ||||
|   id: ArtifactId, | ||||
|   artifactGraph: ArtifactGraph | ||||
| ): PathArtifact | null { | ||||
|   const selection = artifactGraph.get(id) | ||||
|   if (!selection) return null | ||||
|   if (selection.type === 'solid2D') { | ||||
|     const path = artifactGraph.get(selection.pathId) | ||||
|     if (path?.type !== 'path') return null | ||||
|     return path | ||||
|   } else if (selection.type === 'wall' || selection.type === 'cap') { | ||||
|     const sweep = artifactGraph.get(selection.sweepId) | ||||
|     if (sweep?.type !== 'sweep') return null | ||||
|     const path = artifactGraph.get(sweep.pathId) | ||||
|     if (path?.type !== 'path') return null | ||||
|     return path | ||||
|   } | ||||
|   return null | ||||
| } | ||||
|  | ||||
| @ -117,11 +117,11 @@ describe('testing changeSketchArguments', () => { | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) return ast | ||||
|  | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const sourceStart = code.indexOf(lineToChange) | ||||
|     const changeSketchArgsRetVal = changeSketchArguments( | ||||
|       ast, | ||||
|       programMemory, | ||||
|       execState.memory, | ||||
|       { | ||||
|         type: 'sourceRange', | ||||
|         sourceRange: [sourceStart, sourceStart + lineToChange.length], | ||||
| @ -150,12 +150,12 @@ const mySketch001 = startSketchOn('XY') | ||||
|     const ast = parse(code) | ||||
|     if (err(ast)) return ast | ||||
|  | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const sourceStart = code.indexOf(lineToChange) | ||||
|     expect(sourceStart).toBe(95) | ||||
|     const newSketchLnRetVal = addNewSketchLn({ | ||||
|       node: ast, | ||||
|       programMemory, | ||||
|       programMemory: execState.memory, | ||||
|       input: { | ||||
|         type: 'straight-segment', | ||||
|         from: [0, 0], | ||||
| @ -186,7 +186,7 @@ const mySketch001 = startSketchOn('XY') | ||||
|  | ||||
|     const modifiedAst2 = addCloseToPipe({ | ||||
|       node: ast, | ||||
|       programMemory, | ||||
|       programMemory: execState.memory, | ||||
|       pathToNode: [ | ||||
|         ['body', ''], | ||||
|         [0, 'index'], | ||||
| @ -230,7 +230,7 @@ describe('testing addTagForSketchOnFace', () => { | ||||
|     const pathToNode = getNodePathFromSourceRange(ast, sourceRange) | ||||
|     const sketchOnFaceRetVal = addTagForSketchOnFace( | ||||
|       { | ||||
|         // previousProgramMemory: programMemory, // redundant? | ||||
|         // previousProgramMemory: execState.memory, // redundant? | ||||
|         pathToNode, | ||||
|         node: ast, | ||||
|       }, | ||||
|  | ||||
| @ -40,7 +40,7 @@ async function testingSwapSketchFnCall({ | ||||
|   const ast = parse(inputCode) | ||||
|   if (err(ast)) return Promise.reject(ast) | ||||
|  | ||||
|   const programMemory = await enginelessExecutor(ast) | ||||
|   const execState = await enginelessExecutor(ast) | ||||
|   const selections = { | ||||
|     codeBasedSelections: [range], | ||||
|     otherSelections: [], | ||||
| @ -51,7 +51,7 @@ async function testingSwapSketchFnCall({ | ||||
|     return Promise.reject(new Error('transformInfos undefined')) | ||||
|   const ast2 = transformAstSketchLines({ | ||||
|     ast, | ||||
|     programMemory, | ||||
|     programMemory: execState.memory, | ||||
|     selectionRanges: selections, | ||||
|     transformInfos, | ||||
|     referenceSegName: '', | ||||
| @ -366,10 +366,10 @@ const part001 = startSketchOn('XY') | ||||
|   |> line([2.14, 1.35], %) // normal-segment | ||||
|   |> xLine(3.54, %)` | ||||
|   it('normal case works', async () => { | ||||
|     const programMemory = await enginelessExecutor(parse(code)) | ||||
|     const execState = await enginelessExecutor(parse(code)) | ||||
|     const index = code.indexOf('// normal-segment') - 7 | ||||
|     const sg = sketchGroupFromKclValue( | ||||
|       programMemory.get('part001'), | ||||
|       execState.memory.get('part001'), | ||||
|       'part001' | ||||
|     ) as SketchGroup | ||||
|     const _segment = getSketchSegmentFromSourceRange(sg, [index, index]) | ||||
| @ -383,11 +383,11 @@ const part001 = startSketchOn('XY') | ||||
|     }) | ||||
|   }) | ||||
|   it('verify it works when the segment is in the `start` property', async () => { | ||||
|     const programMemory = await enginelessExecutor(parse(code)) | ||||
|     const execState = await enginelessExecutor(parse(code)) | ||||
|     const index = code.indexOf('// segment-in-start') - 7 | ||||
|     const _segment = getSketchSegmentFromSourceRange( | ||||
|       sketchGroupFromKclValue( | ||||
|         programMemory.get('part001'), | ||||
|         execState.memory.get('part001'), | ||||
|         'part001' | ||||
|       ) as SketchGroup, | ||||
|       [index, index] | ||||
|  | ||||
| @ -220,7 +220,7 @@ const part001 = startSketchOn('XY') | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const transformInfos = getTransformInfos( | ||||
|       makeSelections(selectionRanges.slice(1)), | ||||
|       ast, | ||||
| @ -231,7 +231,7 @@ const part001 = startSketchOn('XY') | ||||
|       ast, | ||||
|       selectionRanges: makeSelections(selectionRanges), | ||||
|       transformInfos, | ||||
|       programMemory, | ||||
|       programMemory: execState.memory, | ||||
|     }) | ||||
|     if (err(newAst)) return Promise.reject(newAst) | ||||
|  | ||||
| @ -311,7 +311,7 @@ const part001 = startSketchOn('XY') | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const transformInfos = getTransformInfos( | ||||
|       makeSelections(selectionRanges), | ||||
|       ast, | ||||
| @ -322,7 +322,7 @@ const part001 = startSketchOn('XY') | ||||
|       ast, | ||||
|       selectionRanges: makeSelections(selectionRanges), | ||||
|       transformInfos, | ||||
|       programMemory, | ||||
|       programMemory: execState.memory, | ||||
|       referenceSegName: '', | ||||
|     }) | ||||
|     if (err(newAst)) return Promise.reject(newAst) | ||||
| @ -373,7 +373,7 @@ const part001 = startSketchOn('XY') | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|     const programMemory = await enginelessExecutor(ast) | ||||
|     const execState = await enginelessExecutor(ast) | ||||
|     const transformInfos = getTransformInfos( | ||||
|       makeSelections(selectionRanges), | ||||
|       ast, | ||||
| @ -384,7 +384,7 @@ const part001 = startSketchOn('XY') | ||||
|       ast, | ||||
|       selectionRanges: makeSelections(selectionRanges), | ||||
|       transformInfos, | ||||
|       programMemory, | ||||
|       programMemory: execState.memory, | ||||
|       referenceSegName: '', | ||||
|     }) | ||||
|     if (err(newAst)) return Promise.reject(newAst) | ||||
| @ -470,7 +470,7 @@ async function helperThing( | ||||
|       } | ||||
|     }) | ||||
|  | ||||
|   const programMemory = await enginelessExecutor(ast) | ||||
|   const execState = await enginelessExecutor(ast) | ||||
|   const transformInfos = getTransformInfos( | ||||
|     makeSelections(selectionRanges.slice(1)), | ||||
|     ast, | ||||
| @ -481,7 +481,7 @@ async function helperThing( | ||||
|     ast, | ||||
|     selectionRanges: makeSelections(selectionRanges), | ||||
|     transformInfos, | ||||
|     programMemory, | ||||
|     programMemory: execState.memory, | ||||
|   }) | ||||
|  | ||||
|   if (err(newAst)) return Promise.reject(newAst) | ||||
|  | ||||
| @ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => { | ||||
|   offset: ${offset}, | ||||
| }, %, $yo2) | ||||
| const intersect = segEndX(yo2)` | ||||
|     const mem = await enginelessExecutor(parse(code('-1'))) | ||||
|     expect(mem.get('intersect')?.value).toBe(1 + Math.sqrt(2)) | ||||
|     const execState = await enginelessExecutor(parse(code('-1'))) | ||||
|     expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2)) | ||||
|     const noOffset = await enginelessExecutor(parse(code('0'))) | ||||
|     expect(noOffset.get('intersect')?.value).toBeCloseTo(1) | ||||
|     expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| @ -37,6 +37,8 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration' | ||||
| import { DeepPartial } from 'lib/types' | ||||
| import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration' | ||||
| import { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup' | ||||
| import { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId' | ||||
| import { Artifact } from 'wasm-lib/kcl/bindings/Artifact' | ||||
|  | ||||
| export type { Program } from '../wasm-lib/kcl/bindings/Program' | ||||
| export type { Expr } from '../wasm-lib/kcl/bindings/Expr' | ||||
| @ -136,6 +138,34 @@ export const parse = (code: string | Error): Program | Error => { | ||||
|  | ||||
| export type PathToNode = [string | number, string][] | ||||
|  | ||||
| interface RawExecState { | ||||
|   memory: RawProgramMemory | ||||
|   artifacts: { [key: ArtifactId]: Artifact } | ||||
| } | ||||
|  | ||||
| export interface ExecState { | ||||
|   memory: ProgramMemory | ||||
|   artifacts: { [key: ArtifactId]: Artifact } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Create an empty ExecState.  This is useful on init to prevent needing an | ||||
|  * Option. | ||||
|  */ | ||||
| export function emptyExecState(): ExecState { | ||||
|   return { | ||||
|     memory: ProgramMemory.empty(), | ||||
|     artifacts: {}, | ||||
|   } | ||||
| } | ||||
|  | ||||
| function execStateFromRaw(raw: RawExecState): ExecState { | ||||
|   return { | ||||
|     memory: ProgramMemory.fromRaw(raw.memory), | ||||
|     artifacts: raw.artifacts, | ||||
|   } | ||||
| } | ||||
|  | ||||
| interface Memory { | ||||
|   [key: string]: KclValue | ||||
| } | ||||
| @ -353,12 +383,49 @@ export function sketchGroupFromKclValue( | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function optionalSketchGroupFromKclValue( | ||||
|   value: KclValue | ||||
| ): SketchGroup | null { | ||||
|   if (value.type === 'UserVal' && value.value.type === 'SketchGroup') | ||||
|     return value.value | ||||
|   return null | ||||
| } | ||||
|  | ||||
| export function kclValueFromArtifactId( | ||||
|   execState: ExecState, | ||||
|   id: ArtifactId | ||||
| ): KclValue | null { | ||||
|   const artifact = execState.artifacts[id] | ||||
|   if (!artifact) { | ||||
|     console.warn('kclValueFromArtifactId id not found', id, execState) | ||||
|   } | ||||
|   if (!artifact) return null | ||||
|   return artifact.value | ||||
| } | ||||
|  | ||||
| export function sketchGroupFromArtifactId( | ||||
|   execState: ExecState, | ||||
|   id: ArtifactId | ||||
| ): SketchGroup | null { | ||||
|   const kclValue = kclValueFromArtifactId(execState, id) | ||||
|   if (!kclValue) { | ||||
|     console.warn('sketchGroupFromArtifactId id not found', id) | ||||
|     return null | ||||
|   } | ||||
|   const sketch = optionalSketchGroupFromKclValue(kclValue) | ||||
|   if (!sketch) { | ||||
|     console.warn('sketchGroupFromArtifactId not a SketchGroup', kclValue) | ||||
|     return null | ||||
|   } | ||||
|   return sketch | ||||
| } | ||||
|  | ||||
| export const executor = async ( | ||||
|   node: Program, | ||||
|   programMemory: ProgramMemory | Error = ProgramMemory.empty(), | ||||
|   engineCommandManager: EngineCommandManager, | ||||
|   isMock: boolean = false | ||||
| ): Promise<ProgramMemory> => { | ||||
| ): Promise<ExecState> => { | ||||
|   if (err(programMemory)) return Promise.reject(programMemory) | ||||
|  | ||||
|   // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||||
| @ -380,7 +447,7 @@ export const _executor = async ( | ||||
|   programMemory: ProgramMemory | Error = ProgramMemory.empty(), | ||||
|   engineCommandManager: EngineCommandManager, | ||||
|   isMock: boolean | ||||
| ): Promise<ProgramMemory> => { | ||||
| ): Promise<ExecState> => { | ||||
|   if (err(programMemory)) return Promise.reject(programMemory) | ||||
|  | ||||
|   try { | ||||
| @ -392,7 +459,7 @@ export const _executor = async ( | ||||
|       baseUnit = | ||||
|         (await getSettingsState)()?.modeling.defaultUnit.current || 'mm' | ||||
|     } | ||||
|     const memory: RawProgramMemory = await execute_wasm( | ||||
|     const execState: RawExecState = await execute_wasm( | ||||
|       JSON.stringify(node), | ||||
|       JSON.stringify(programMemory.toRaw()), | ||||
|       baseUnit, | ||||
| @ -400,7 +467,7 @@ export const _executor = async ( | ||||
|       fileSystemManager, | ||||
|       isMock | ||||
|     ) | ||||
|     return ProgramMemory.fromRaw(memory) | ||||
|     return execStateFromRaw(execState) | ||||
|   } catch (e: any) { | ||||
|     console.log(e) | ||||
|     const parsed: RustKclError = JSON.parse(e.toString()) | ||||
|  | ||||
| @ -58,10 +58,12 @@ export type Selection = | ||||
|         | 'line' | ||||
|         | 'arc' | ||||
|         | 'all' | ||||
|       artifactId?: ArtifactId | ||||
|       range: SourceRange | ||||
|     } | ||||
|   | { | ||||
|       type: 'opposite-edgeCut' | 'adjacent-edgeCut' | 'base-edgeCut' | ||||
|       artifactId?: ArtifactId | ||||
|       range: SourceRange | ||||
|       // TODO this is a temporary measure that well be made redundant with: https://github.com/KittyCAD/modeling-app/pull/3836 | ||||
|       secondaryRange: SourceRange | ||||
| @ -108,7 +110,11 @@ export async function getEventForSelectWithPoint({ | ||||
|       type: 'Set selection', | ||||
|       data: { | ||||
|         selectionType: 'singleCodeCursor', | ||||
|         selection: { range: codeRef.range, type: 'solid2D' }, | ||||
|         selection: { | ||||
|           artifactId: data.entity_id, | ||||
|           range: codeRef.range, | ||||
|           type: 'solid2D', | ||||
|         }, | ||||
|       }, | ||||
|     } | ||||
|   } | ||||
| @ -120,6 +126,7 @@ export async function getEventForSelectWithPoint({ | ||||
|       data: { | ||||
|         selectionType: 'singleCodeCursor', | ||||
|         selection: { | ||||
|           artifactId: data.entity_id, | ||||
|           range: codeRef.range, | ||||
|           type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap', | ||||
|         }, | ||||
| @ -136,7 +143,11 @@ export async function getEventForSelectWithPoint({ | ||||
|       type: 'Set selection', | ||||
|       data: { | ||||
|         selectionType: 'singleCodeCursor', | ||||
|         selection: { range: codeRef.range, type: 'extrude-wall' }, | ||||
|         selection: { | ||||
|           artifactId: data.entity_id, | ||||
|           range: codeRef.range, | ||||
|           type: 'extrude-wall', | ||||
|         }, | ||||
|       }, | ||||
|     } | ||||
|   } | ||||
| @ -145,7 +156,11 @@ export async function getEventForSelectWithPoint({ | ||||
|       type: 'Set selection', | ||||
|       data: { | ||||
|         selectionType: 'singleCodeCursor', | ||||
|         selection: { range: _artifact.codeRef.range, type: 'default' }, | ||||
|         selection: { | ||||
|           artifactId: data.entity_id, | ||||
|           range: _artifact.codeRef.range, | ||||
|           type: 'default', | ||||
|         }, | ||||
|       }, | ||||
|     } | ||||
|   } | ||||
| @ -807,16 +822,18 @@ export function updateSelections( | ||||
|         selection?.type === 'opposite-edgeCut' | ||||
|       ) | ||||
|         return { | ||||
|           artifactId: selection?.artifactId, | ||||
|           range: [node.start, node.end], | ||||
|           type: selection?.type, | ||||
|           secondaryRange: selection?.secondaryRange, | ||||
|         } | ||||
|       return { | ||||
|         artifactId: selection?.artifactId, | ||||
|         range: [node.start, node.end], | ||||
|         type: selection?.type, | ||||
|       } | ||||
|     }) | ||||
|     .filter((x?: Selection) => x !== undefined) as Selection[] | ||||
|     .filter((x?: Selection) => x !== undefined) | ||||
|  | ||||
|   return { | ||||
|     codeBasedSelections: | ||||
|  | ||||
| @ -1,4 +1,10 @@ | ||||
| import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm' | ||||
| import { | ||||
|   Program, | ||||
|   ProgramMemory, | ||||
|   _executor, | ||||
|   SourceRange, | ||||
|   ExecState, | ||||
| } from '../lang/wasm' | ||||
| import { | ||||
|   EngineCommandManager, | ||||
|   EngineCommandManagerEvents, | ||||
| @ -78,7 +84,7 @@ class MockEngineCommandManager { | ||||
| export async function enginelessExecutor( | ||||
|   ast: Program | Error, | ||||
|   pm: ProgramMemory | Error = ProgramMemory.empty() | ||||
| ): Promise<ProgramMemory> { | ||||
| ): Promise<ExecState> { | ||||
|   if (err(ast)) return Promise.reject(ast) | ||||
|   if (err(pm)) return Promise.reject(pm) | ||||
|  | ||||
| @ -88,15 +94,15 @@ export async function enginelessExecutor( | ||||
|   }) as any as EngineCommandManager | ||||
|   // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||||
|   mockEngineCommandManager.startNewSession() | ||||
|   const programMemory = await _executor(ast, pm, mockEngineCommandManager, true) | ||||
|   const execState = await _executor(ast, pm, mockEngineCommandManager, true) | ||||
|   await mockEngineCommandManager.waitForAllCommands() | ||||
|   return programMemory | ||||
|   return execState | ||||
| } | ||||
|  | ||||
| export async function executor( | ||||
|   ast: Program, | ||||
|   pm: ProgramMemory = ProgramMemory.empty() | ||||
| ): Promise<ProgramMemory> { | ||||
| ): Promise<ExecState> { | ||||
|   const engineCommandManager = new EngineCommandManager() | ||||
|   engineCommandManager.start({ | ||||
|     setIsStreamReady: () => {}, | ||||
| @ -117,14 +123,9 @@ export async function executor( | ||||
|       toSync(async () => { | ||||
|         // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||||
|         engineCommandManager.startNewSession() | ||||
|         const programMemory = await _executor( | ||||
|           ast, | ||||
|           pm, | ||||
|           engineCommandManager, | ||||
|           false | ||||
|         ) | ||||
|         const execState = await _executor(ast, pm, engineCommandManager, false) | ||||
|         await engineCommandManager.waitForAllCommands() | ||||
|         resolve(programMemory) | ||||
|         resolve(execState) | ||||
|       }, reportRejection) | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
| @ -97,7 +97,7 @@ export function useCalculateKclExpression({ | ||||
|         }) | ||||
|         if (trap(error, { suppress: true })) return | ||||
|       } | ||||
|       const { programMemory } = await executeAst({ | ||||
|       const { execState } = await executeAst({ | ||||
|         ast, | ||||
|         engineCommandManager, | ||||
|         useFakeExecutor: true, | ||||
| @ -111,7 +111,7 @@ export function useCalculateKclExpression({ | ||||
|       const init = | ||||
|         resultDeclaration?.type === 'VariableDeclaration' && | ||||
|         resultDeclaration?.declarations?.[0]?.init | ||||
|       const result = programMemory?.get('__result__')?.value | ||||
|       const result = execState.memory?.get('__result__')?.value | ||||
|       setCalcResult(typeof result === 'number' ? String(result) : 'NAN') | ||||
|       init && setValueNode(init) | ||||
|     } | ||||
|  | ||||
| @ -1,5 +1,8 @@ | ||||
| import { | ||||
|   Expr, | ||||
|   ExpressionStatement, | ||||
|   PathToNode, | ||||
|   PipeExpression, | ||||
|   VariableDeclaration, | ||||
|   VariableDeclarator, | ||||
|   parse, | ||||
| @ -2116,15 +2119,24 @@ export function isEditingExistingSketch({ | ||||
|   // should check that the variable declaration is a pipeExpression | ||||
|   // and that the pipeExpression contains a "startProfileAt" callExpression | ||||
|   if (!sketchDetails?.sketchPathToNode) return false | ||||
|   const variableDeclaration = getNodeFromPath<VariableDeclarator>( | ||||
|   const _node1 = getNodeFromPath<VariableDeclaration | ExpressionStatement>( | ||||
|     kclManager.ast, | ||||
|     sketchDetails.sketchPathToNode, | ||||
|     'VariableDeclarator' | ||||
|   ) | ||||
|   if (err(variableDeclaration)) return false | ||||
|   if (variableDeclaration.node.type !== 'VariableDeclarator') return false | ||||
|   const pipeExpression = variableDeclaration.node.init | ||||
|   if (pipeExpression.type !== 'PipeExpression') return false | ||||
|     ['VariableDeclaration', 'ExpressionStatement'] | ||||
|   ) as { node: { type: string } } | Error | ||||
|   if (err(_node1)) return false | ||||
|   let expr: Expr | ||||
|   if (_node1.node.type === 'VariableDeclaration') { | ||||
|     const varDecl = _node1.node as VariableDeclaration | ||||
|     expr = varDecl.declarations[0].init | ||||
|   } else if (_node1.node.type === 'ExpressionStatement') { | ||||
|     const exprStatement = _node1.node as ExpressionStatement | ||||
|     expr = exprStatement.expression | ||||
|   } else { | ||||
|     return false | ||||
|   } | ||||
|   if (expr.type !== 'PipeExpression') return false | ||||
|   const pipeExpression: PipeExpression = expr | ||||
|   const hasStartProfileAt = pipeExpression.body.some( | ||||
|     (item) => | ||||
|       item.type === 'CallExpression' && item.callee.name === 'startProfileAt' | ||||
|  | ||||
							
								
								
									
										1
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1430,6 +1430,7 @@ dependencies = [ | ||||
|  "databake", | ||||
|  "derive-docs", | ||||
|  "expectorate", | ||||
|  "fnv", | ||||
|  "form_urlencoded", | ||||
|  "futures", | ||||
|  "git_rev", | ||||
|  | ||||
| @ -21,6 +21,7 @@ convert_case = "0.6.0" | ||||
| dashmap = "6.1.0" | ||||
| databake = { version = "0.1.8", features = ["derive"] } | ||||
| derive-docs = { version = "0.1.28", path = "../derive-docs" } | ||||
| fnv = "1.0.7" | ||||
| form_urlencoded = "1.2.1" | ||||
| futures = { version = "0.3.30" } | ||||
| git_rev = "0.1.0" | ||||
|  | ||||
| @ -4,6 +4,7 @@ use std::{collections::HashMap, sync::Arc}; | ||||
|  | ||||
| use anyhow::Result; | ||||
| use async_recursion::async_recursion; | ||||
| use fnv::FnvHashMap; | ||||
| use kcmc::{ | ||||
|     each_cmd as mcmd, | ||||
|     ok_response::{output::TakeSnapshot, OkModelingCmdResponse}, | ||||
| @ -45,6 +46,16 @@ pub struct ExecState { | ||||
|     /// The current value of the pipe operator returned from the previous | ||||
|     /// expression.  If we're not currently in a pipeline, this will be None. | ||||
|     pub pipe_value: Option<KclValue>, | ||||
|     /// Artifacts created by the program.  It's safe to use a faster hash | ||||
|     /// algorithm since all keys are UUIDs that we generate. | ||||
|     pub artifacts: FnvHashMap<ArtifactId, Artifact>, | ||||
| } | ||||
|  | ||||
| impl ExecState { | ||||
|     /// Insert or update artifact by ID. | ||||
|     pub(crate) fn put_artifact(&mut self, id: ArtifactId, value: KclValue) { | ||||
|         self.artifacts.insert(id, Artifact { id, value }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| @ -396,6 +407,21 @@ impl From<Vec<Box<ExtrudeGroup>>> for KclValue { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema)] | ||||
| pub struct ArtifactId(uuid::Uuid); | ||||
|  | ||||
| impl ArtifactId { | ||||
|     pub(crate) fn new(id: uuid::Uuid) -> Self { | ||||
|         Self(id) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS, JsonSchema)] | ||||
| pub struct Artifact { | ||||
|     pub id: ArtifactId, | ||||
|     pub value: KclValue, | ||||
| } | ||||
|  | ||||
| /// A geometry. | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
|  | ||||
| @ -15,17 +15,17 @@ use uuid::Uuid; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     executor::{ | ||||
|         ExecState, ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, GeoMeta, KclValue, Path, SketchGroup, SketchGroupSet, | ||||
|         SketchSurface, | ||||
|         ArtifactId, ExecState, ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, GeoMeta, KclValue, Path, SketchGroup, | ||||
|         SketchGroupSet, SketchSurface, | ||||
|     }, | ||||
|     std::Args, | ||||
| }; | ||||
|  | ||||
| /// Extrudes by a given amount. | ||||
| pub async fn extrude(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
| pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (length, sketch_group_set) = args.get_number_sketch_group_set()?; | ||||
|  | ||||
|     let result = inner_extrude(length, sketch_group_set, args).await?; | ||||
|     let result = inner_extrude(length, sketch_group_set, exec_state, args).await?; | ||||
|  | ||||
|     Ok(result.into()) | ||||
| } | ||||
| @ -79,7 +79,12 @@ pub async fn extrude(_exec_state: &mut ExecState, args: Args) -> Result<KclValue | ||||
| #[stdlib { | ||||
|     name = "extrude" | ||||
| }] | ||||
| async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args) -> Result<ExtrudeGroupSet, KclError> { | ||||
| async fn inner_extrude( | ||||
|     length: f64, | ||||
|     sketch_group_set: SketchGroupSet, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<ExtrudeGroupSet, KclError> { | ||||
|     let id = uuid::Uuid::new_v4(); | ||||
|  | ||||
|     // Extrude the element(s). | ||||
| @ -120,7 +125,7 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args | ||||
|             ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}), | ||||
|         ) | ||||
|         .await?; | ||||
|         extrude_groups.push(do_post_extrude(sketch_group.clone(), length, args.clone()).await?); | ||||
|         extrude_groups.push(do_post_extrude(sketch_group.clone(), length, exec_state, args.clone()).await?); | ||||
|     } | ||||
|  | ||||
|     Ok(extrude_groups.into()) | ||||
| @ -129,6 +134,7 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args | ||||
| pub(crate) async fn do_post_extrude( | ||||
|     sketch_group: SketchGroup, | ||||
|     length: f64, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Box<ExtrudeGroup>, KclError> { | ||||
|     // Bring the object to the front of the scene. | ||||
| @ -276,7 +282,7 @@ pub(crate) async fn do_post_extrude( | ||||
|         }) | ||||
|         .collect(); | ||||
|  | ||||
|     Ok(Box::new(ExtrudeGroup { | ||||
|     let extrude_group = Box::new(ExtrudeGroup { | ||||
|         // Ok so you would think that the id would be the id of the extrude group, | ||||
|         // that we passed in to the function, but it's actually the id of the | ||||
|         // sketch group. | ||||
| @ -288,7 +294,13 @@ pub(crate) async fn do_post_extrude( | ||||
|         start_cap_id, | ||||
|         end_cap_id, | ||||
|         edge_cuts: vec![], | ||||
|     })) | ||||
|     }); | ||||
|     exec_state.put_artifact( | ||||
|         ArtifactId::new(extrude_group.id), | ||||
|         KclValue::ExtrudeGroup(extrude_group.clone()), | ||||
|     ); | ||||
|  | ||||
|     Ok(extrude_group) | ||||
| } | ||||
|  | ||||
| #[derive(Default)] | ||||
|  | ||||
| @ -50,10 +50,10 @@ impl Default for LoftData { | ||||
| } | ||||
|  | ||||
| /// Create a 3D surface or solid by interpolating between two or more sketches. | ||||
| pub async fn loft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
| pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (sketch_groups, data): (Vec<SketchGroup>, Option<LoftData>) = args.get_sketch_groups_and_data()?; | ||||
|  | ||||
|     let extrude_group = inner_loft(sketch_groups, data, args).await?; | ||||
|     let extrude_group = inner_loft(sketch_groups, data, exec_state, args).await?; | ||||
|     Ok(KclValue::ExtrudeGroup(extrude_group)) | ||||
| } | ||||
|  | ||||
| @ -138,6 +138,7 @@ pub async fn loft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K | ||||
| async fn inner_loft( | ||||
|     sketch_groups: Vec<SketchGroup>, | ||||
|     data: Option<LoftData>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Box<ExtrudeGroup>, KclError> { | ||||
|     // Make sure we have at least two sketches. | ||||
| @ -170,5 +171,5 @@ async fn inner_loft( | ||||
|     .await?; | ||||
|  | ||||
|     // Using the first sketch as the base curve, idk we might want to change this later. | ||||
|     do_post_extrude(sketch_groups[0].clone(), 0.0, args).await | ||||
|     do_post_extrude(sketch_groups[0].clone(), 0.0, exec_state, args).await | ||||
| } | ||||
|  | ||||
| @ -295,7 +295,7 @@ async fn inner_revolve( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     do_post_extrude(sketch_group, 0.0, args).await | ||||
|     do_post_extrude(sketch_group, 0.0, exec_state, args).await | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
|  | ||||
| @ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; | ||||
| use crate::{ | ||||
|     ast::types::TagDeclarator, | ||||
|     errors::KclError, | ||||
|     executor::{BasePath, ExecState, GeoMeta, KclValue, Path, SketchGroup, SketchSurface}, | ||||
|     executor::{ArtifactId, BasePath, ExecState, GeoMeta, KclValue, Path, SketchGroup, SketchSurface}, | ||||
|     std::Args, | ||||
| }; | ||||
|  | ||||
| @ -46,6 +46,11 @@ pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, | ||||
|         args.get_circle_args()?; | ||||
|  | ||||
|     let sketch_group = inner_circle(data, sketch_surface_or_group, tag, exec_state, args).await?; | ||||
|     exec_state.put_artifact( | ||||
|         ArtifactId::new(sketch_group.id), | ||||
|         KclValue::new_user_val(sketch_group.meta.clone(), sketch_group.clone()), | ||||
|     ); | ||||
|  | ||||
|     Ok(KclValue::new_user_val(sketch_group.meta.clone(), sketch_group)) | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -16,8 +16,8 @@ use crate::{ | ||||
|     ast::types::TagDeclarator, | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     executor::{ | ||||
|         BasePath, ExecState, ExtrudeGroup, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d, Point3d, | ||||
|         SketchGroup, SketchGroupSet, SketchSurface, TagEngineInfo, TagIdentifier, UserVal, | ||||
|         ArtifactId, BasePath, ExecState, ExtrudeGroup, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d, | ||||
|         Point3d, SketchGroup, SketchGroupSet, SketchSurface, TagEngineInfo, TagIdentifier, UserVal, | ||||
|     }, | ||||
|     std::{ | ||||
|         utils::{ | ||||
| @ -93,11 +93,16 @@ pub enum StartOrEnd { | ||||
| } | ||||
|  | ||||
| /// Draw a line to a point. | ||||
| pub async fn line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
| pub async fn line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (to, sketch_group, tag): ([f64; 2], SketchGroup, Option<TagDeclarator>) = | ||||
|         args.get_data_and_sketch_group_and_tag()?; | ||||
|  | ||||
|     let new_sketch_group = inner_line_to(to, sketch_group, tag, args).await?; | ||||
|     exec_state.put_artifact( | ||||
|         ArtifactId::new(new_sketch_group.id), | ||||
|         KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group.clone()), | ||||
|     ); | ||||
|  | ||||
|     Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group)) | ||||
| } | ||||
|  | ||||
| @ -248,11 +253,16 @@ async fn inner_y_line_to( | ||||
| } | ||||
|  | ||||
| /// Draw a line. | ||||
| pub async fn line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
| pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (delta, sketch_group, tag): ([f64; 2], SketchGroup, Option<TagDeclarator>) = | ||||
|         args.get_data_and_sketch_group_and_tag()?; | ||||
|  | ||||
|     let new_sketch_group = inner_line(delta, sketch_group, tag, args).await?; | ||||
|     exec_state.put_artifact( | ||||
|         ArtifactId::new(new_sketch_group.id), | ||||
|         KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group.clone()), | ||||
|     ); | ||||
|  | ||||
|     Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group)) | ||||
| } | ||||
|  | ||||
| @ -1410,10 +1420,14 @@ pub(crate) fn inner_profile_start(sketch_group: SketchGroup) -> Result<[f64; 2], | ||||
| } | ||||
|  | ||||
| /// Close the current sketch. | ||||
| pub async fn close(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
| pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (sketch_group, tag): (SketchGroup, Option<TagDeclarator>) = args.get_sketch_group_and_optional_tag()?; | ||||
|  | ||||
|     let new_sketch_group = inner_close(sketch_group, tag, args).await?; | ||||
|     exec_state.put_artifact( | ||||
|         ArtifactId::new(new_sketch_group.id), | ||||
|         KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group.clone()), | ||||
|     ); | ||||
|  | ||||
|     Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group)) | ||||
| } | ||||
|  | ||||
| @ -59,7 +59,7 @@ pub async fn execute_wasm( | ||||
|     // gloo-serialize crate instead. | ||||
|     // DO NOT USE serde_wasm_bindgen::to_value(&memory).map_err(|e| e.to_string()) | ||||
|     // it will break the frontend. | ||||
|     JsValue::from_serde(&exec_state.memory).map_err(|e| e.to_string()) | ||||
|     JsValue::from_serde(&exec_state).map_err(|e| e.to_string()) | ||||
| } | ||||
|  | ||||
| // wasm_bindgen wrapper for execute | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	