Compare commits

...

12 Commits

30 changed files with 579 additions and 191 deletions

View File

@ -44,6 +44,8 @@ import {
VariableDeclaration, VariableDeclaration,
VariableDeclarator, VariableDeclarator,
sketchGroupFromKclValue, sketchGroupFromKclValue,
ExecState,
sketchGroupFromArtifactId,
} from 'lang/wasm' } from 'lang/wasm'
import { import {
engineCommandManager, engineCommandManager,
@ -77,7 +79,11 @@ import {
createPipeSubstitution, createPipeSubstitution,
findUniqueName, findUniqueName,
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { Selections, getEventForSegmentSelection } from 'lib/selections' import {
Selection,
Selections,
getEventForSegmentSelection,
} from 'lib/selections'
import { createGridHelper, orthoScale, perspScale } from './helpers' import { createGridHelper, orthoScale, perspScale } from './helpers'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
@ -90,7 +96,12 @@ import {
import { getThemeColorForThreeJs, Themes } from 'lib/theme' import { getThemeColorForThreeJs, Themes } from 'lib/theme'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' 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' import { SegmentInputs } from 'lang/std/stdTypes'
type DraftSegment = 'line' | 'tangentialArcTo' type DraftSegment = 'line' | 'tangentialArcTo'
@ -122,8 +133,6 @@ export const SEGMENT_BODIES_PLUS_PROFILE_START = [
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. // This singleton Class is responsible for all of the things the user sees and interacts with.
// That mostly mean sketch elements. // That mostly mean sketch elements.
// Cameras, controls, raycasters, etc are handled by sceneInfra // Cameras, controls, raycasters, etc are handled by sceneInfra
@ -391,17 +400,37 @@ export class SceneEntities {
const { truncatedAst, programMemoryOverride, variableDeclarationName } = const { truncatedAst, programMemoryOverride, variableDeclarationName } =
prepared prepared
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
}) })
const sketchGroup = sketchGroupFromPathToNode({ const programMemory = execState.memory
pathToNode: sketchPathToNode, let sketchGroup: SketchGroup | null | Error = null
ast: maybeModdedAst, if (selectionRanges) {
programMemory, 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 (err(sketchGroup)) return Promise.reject(sketchGroup)
if (!sketchGroup) return Promise.reject('sketchGroup not found') if (!sketchGroup) return Promise.reject('sketchGroup not found')
@ -598,11 +627,13 @@ export class SceneEntities {
const _node1 = getNodeFromPath<VariableDeclaration>( const _node1 = getNodeFromPath<VariableDeclaration>(
_ast, _ast,
sketchPathToNode || [], sketchPathToNode || [],
'VariableDeclaration' ['VariableDeclaration', 'ExpressionStatement']
) ) as { node: { type: string } } | Error
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || '' _node1.node.type === 'VariableDeclaration'
? (_node1.node as VariableDeclaration).declarations[0]?.id?.name || ''
: ''
const sg = sketchGroupFromKclValue( const sg = sketchGroupFromKclValue(
kclManager.programMemory.get(variableDeclarationName), kclManager.programMemory.get(variableDeclarationName),
@ -802,12 +833,13 @@ export class SceneEntities {
updateRectangleSketch(sketchInit, x, y, tags[0]) updateRectangleSketch(sketchInit, x, y, tags[0])
} }
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
const sketchGroup = sketchGroupFromKclValue( const sketchGroup = sketchGroupFromKclValue(
programMemory.get(variableDeclarationName), programMemory.get(variableDeclarationName),
@ -856,12 +888,13 @@ export class SceneEntities {
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish rectangle' }) sceneInfra.modelingSend({ type: 'Finish rectangle' })
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: _ast, ast: _ast,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
}) })
const programMemory = execState.memory
// Prepare to update the THREEjs scene // Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
@ -980,12 +1013,13 @@ export class SceneEntities {
modded = moddedResult.modifiedAst modded = moddedResult.modifiedAst
} }
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: modded, ast: modded,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
const sketchGroup = sketchGroupFromKclValue( const sketchGroup = sketchGroupFromKclValue(
programMemory.get(variableDeclarationName), 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 // 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 // plus this would be the truncated ast being recast, it would be wrong
codeManager.updateCodeEditor(code) codeManager.updateCodeEditor(code)
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true, useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
programMemoryOverride, programMemoryOverride,
}) })
const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
const maybeSketchGroup = programMemory.get(variableDeclarationName) const maybeSketchGroup = programMemory.get(variableDeclarationName)
@ -1820,6 +1855,70 @@ export function getParentGroup(
return null 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({ export function sketchGroupFromPathToNode({
pathToNode, pathToNode,
ast, ast,
@ -1829,6 +1928,7 @@ export function sketchGroupFromPathToNode({
ast: Program ast: Program
programMemory: ProgramMemory programMemory: ProgramMemory
}): SketchGroup | null | Error { }): SketchGroup | null | Error {
console.warn('**** sketchGroupFromPathToNode **** Deprecated')
const _varDec = getNodeFromPath<VariableDeclarator>( const _varDec = getNodeFromPath<VariableDeclarator>(
kclManager.ast, kclManager.ast,
pathToNode, 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( export async function getSketchOrientationDetails(
artifactGraph: ArtifactGraph,
selection: Selection,
sketchPathToNode: PathToNode sketchPathToNode: PathToNode
): Promise<{ ): Promise<{
quat: Quaternion sketchDetails: {
sketchDetails: SketchDetails & { faceId?: string } 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({ const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
ast: kclManager.ast, ast: kclManager.ast,
@ -1900,9 +2028,7 @@ export async function getSketchOrientationDetails(
if (sketchGroup.on.type === 'plane') { if (sketchGroup.on.type === 'plane') {
const zAxis = sketchGroup?.on.zAxis const zAxis = sketchGroup?.on.zAxis
return { return {
quat: getQuaternionFromZAxis(massageFormats(zAxis)),
sketchDetails: { sketchDetails: {
sketchPathToNode,
zAxis: [zAxis.x, zAxis.y, zAxis.z], zAxis: [zAxis.x, zAxis.y, zAxis.z],
yAxis: [ yAxis: [
sketchGroup.on.yAxis.x, sketchGroup.on.yAxis.x,
@ -1921,14 +2047,8 @@ export async function getSketchOrientationDetails(
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
return Promise.reject('face info') return Promise.reject('face info')
const { z_axis, y_axis, origin } = faceInfo 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 { return {
quat: quaternion,
sketchDetails: { sketchDetails: {
sketchPathToNode,
zAxis: [z_axis.x, z_axis.y, z_axis.z], zAxis: [z_axis.x, z_axis.y, z_axis.z],
yAxis: [y_axis.x, y_axis.y, y_axis.z], yAxis: [y_axis.x, y_axis.y, y_axis.z],
origin: [origin.x, origin.y, origin.z], origin: [origin.x, origin.y, origin.z],
@ -2003,7 +2123,3 @@ export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
} }
return 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)
}

View File

@ -157,7 +157,7 @@ export function useCalc({
engineCommandManager, engineCommandManager,
useFakeExecutor: true, useFakeExecutor: true,
programMemoryOverride: kclManager.programMemory.clone(), programMemoryOverride: kclManager.programMemory.clone(),
}).then(({ programMemory }) => { }).then(({ execState }) => {
const resultDeclaration = ast.body.find( const resultDeclaration = ast.body.find(
(a) => (a) =>
a.type === 'VariableDeclaration' && a.type === 'VariableDeclaration' &&
@ -166,7 +166,7 @@ export function useCalc({
const init = const init =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.get('__result__')?.value const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)
}) })

View File

@ -625,12 +625,15 @@ export const ModelingMachineProvider = ({
}), }),
'animate-to-sketch': fromPromise( 'animate-to-sketch': fromPromise(
async ({ input: { selectionRanges } }) => { async ({ input: { selectionRanges } }) => {
const sourceRange = selectionRanges.codeBasedSelections[0].range const selection = selectionRanges.codeBasedSelections[0]
const sourceRange = selection.range
const sketchPathToNode = getNodePathFromSourceRange( const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast, kclManager.ast,
sourceRange sourceRange
) )
const info = await getSketchOrientationDetails( const info = await getSketchOrientationDetails(
engineCommandManager.artifactGraph,
selection,
sketchPathToNode || [] sketchPathToNode || []
) )
await letEngineAnimateAndSyncCamAfter( await letEngineAnimateAndSyncCamAfter(

View File

@ -29,8 +29,8 @@ describe('processMemory', () => {
|> lineTo([2.15, 4.32], %) |> lineTo([2.15, 4.32], %)
// |> rx(90, %)` // |> rx(90, %)`
const ast = parse(code) const ast = parse(code)
const programMemory = await enginelessExecutor(ast, ProgramMemory.empty()) const execState = await enginelessExecutor(ast, ProgramMemory.empty())
const output = processMemory(programMemory) const output = processMemory(execState.memory)
expect(output.myVar).toEqual(5) expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3) expect(output.otherVar).toEqual(3)
expect(output).toEqual({ expect(output).toEqual({

View File

@ -8,6 +8,8 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { import {
CallExpression, CallExpression,
emptyExecState,
ExecState,
initPromise, initPromise,
parse, parse,
PathToNode, PathToNode,
@ -19,6 +21,8 @@ import {
import { getNodeFromPath } from './queryAst' import { getNodeFromPath } from './queryAst'
import { codeManager, editorManager, sceneInfra } from 'lib/singletons' import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
import { Diagnostic } from '@codemirror/lint' import { Diagnostic } from '@codemirror/lint'
import { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
import { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
interface ExecuteArgs { interface ExecuteArgs {
ast?: Program ast?: Program
@ -43,6 +47,7 @@ export class KclManager {
digest: null, digest: null,
} }
private _programMemory: ProgramMemory = ProgramMemory.empty() private _programMemory: ProgramMemory = ProgramMemory.empty()
private _execState: ExecState = emptyExecState()
private _logs: string[] = [] private _logs: string[] = []
private _lints: Diagnostic[] = [] private _lints: Diagnostic[] = []
private _kclErrors: KCLError[] = [] private _kclErrors: KCLError[] = []
@ -71,11 +76,21 @@ export class KclManager {
get programMemory() { get programMemory() {
return this._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._programMemory = programMemory
this._programMemoryCallBack(programMemory) this._programMemoryCallBack(programMemory)
} }
set execState(execState) {
this._execState = execState
this.programMemory = execState.memory
}
get execState() {
return this._execState
}
get logs() { get logs() {
return this._logs return this._logs
} }
@ -252,7 +267,7 @@ export class KclManager {
// Make sure we clear before starting again. End session will do this. // Make sure we clear before starting again. End session will do this.
this.engineCommandManager?.endSession() this.engineCommandManager?.endSession()
await this.ensureWasmInit() await this.ensureWasmInit()
const { logs, errors, programMemory, isInterrupted } = await executeAst({ const { logs, errors, execState, isInterrupted } = await executeAst({
ast, ast,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
}) })
@ -263,7 +278,7 @@ export class KclManager {
this.lints = await lintAst({ ast: ast }) this.lints = await lintAst({ ast: ast })
sceneInfra.modelingSend({ type: 'code edit during sketch' }) sceneInfra.modelingSend({ type: 'code edit during sketch' })
defaultSelectionFilter(programMemory, this.engineCommandManager) defaultSelectionFilter(execState.memory, this.engineCommandManager)
if (args.zoomToFit) { if (args.zoomToFit) {
let zoomObjectId: string | undefined = '' let zoomObjectId: string | undefined = ''
@ -296,7 +311,7 @@ export class KclManager {
this.logs = logs this.logs = logs
// Do not add the errors since the program was interrupted and the error is not a real KCL error // Do not add the errors since the program was interrupted and the error is not a real KCL error
this.addKclErrors(isInterrupted ? [] : errors) this.addKclErrors(isInterrupted ? [] : errors)
this.programMemory = programMemory this.execState = execState
this.ast = { ...ast } this.ast = { ...ast }
this._executeCallback() this._executeCallback()
this.engineCommandManager.addCommandLog({ this.engineCommandManager.addCommandLog({
@ -333,7 +348,7 @@ export class KclManager {
await codeManager.writeToFile() await codeManager.writeToFile()
this._ast = { ...newAst } this._ast = { ...newAst }
const { logs, errors, programMemory } = await executeAst({ const { logs, errors, execState } = await executeAst({
ast: newAst, ast: newAst,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
useFakeExecutor: true, useFakeExecutor: true,
@ -341,7 +356,8 @@ export class KclManager {
this._logs = logs this._logs = logs
this._kclErrors = errors this._kclErrors = errors
this._programMemory = programMemory this._execState = execState
this._programMemory = execState.memory
if (updates !== 'artifactRanges') return if (updates !== 'artifactRanges') return
// TODO the below seems like a work around, I wish there's a comment explaining exactly what // TODO the below seems like a work around, I wish there's a comment explaining exactly what

View File

@ -445,6 +445,6 @@ async function exe(
) { ) {
const ast = parse(code) const ast = parse(code)
const result = await enginelessExecutor(ast, programMemory) const execState = await enginelessExecutor(ast, programMemory)
return result return execState.memory
} }

View File

@ -4,6 +4,8 @@ import {
ProgramMemory, ProgramMemory,
programMemoryInit, programMemoryInit,
kclLint, kclLint,
emptyExecState,
ExecState,
} from 'lang/wasm' } from 'lang/wasm'
import { enginelessExecutor } from 'lib/testHelpers' import { enginelessExecutor } from 'lib/testHelpers'
import { EngineCommandManager } from 'lang/std/engineConnection' import { EngineCommandManager } from 'lang/std/engineConnection'
@ -56,7 +58,7 @@ export async function executeAst({
}): Promise<{ }): Promise<{
logs: string[] logs: string[]
errors: KCLError[] errors: KCLError[]
programMemory: ProgramMemory execState: ExecState
isInterrupted: boolean isInterrupted: boolean
}> { }> {
try { try {
@ -65,7 +67,7 @@ export async function executeAst({
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession() engineCommandManager.startNewSession()
} }
const programMemory = await (useFakeExecutor const execState = await (useFakeExecutor
? enginelessExecutor(ast, programMemoryOverride || programMemoryInit()) ? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
: _executor(ast, programMemoryInit(), engineCommandManager, false)) : _executor(ast, programMemoryInit(), engineCommandManager, false))
@ -73,7 +75,7 @@ export async function executeAst({
return { return {
logs: [], logs: [],
errors: [], errors: [],
programMemory, execState,
isInterrupted: false, isInterrupted: false,
} }
} catch (e: any) { } catch (e: any) {
@ -89,7 +91,7 @@ export async function executeAst({
return { return {
errors: [e], errors: [e],
logs: [], logs: [],
programMemory: ProgramMemory.empty(), execState: emptyExecState(),
isInterrupted, isInterrupted,
} }
} else { } else {
@ -97,7 +99,7 @@ export async function executeAst({
return { return {
logs: [e], logs: [e],
errors: [], errors: [],
programMemory: ProgramMemory.empty(), execState: emptyExecState(),
isInterrupted, isInterrupted,
} }
} }

View File

@ -220,11 +220,11 @@ const yo2 = hmm([identifierGuy + 5])`
it('should move a binary expression into a new variable', async () => { it('should move a binary expression into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('100 + 100') + 1 const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -235,11 +235,11 @@ const yo2 = hmm([identifierGuy + 5])`
it('should move a value into a new variable', async () => { it('should move a value into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('2.8') + 1 const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -250,11 +250,11 @@ const yo2 = hmm([identifierGuy + 5])`
it('should move a callExpression into a new variable', async () => { it('should move a callExpression into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('def(') const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -265,11 +265,11 @@ const yo2 = hmm([identifierGuy + 5])`
it('should move a binary expression with call expression into a new variable', async () => { it('should move a binary expression with call expression into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('jkl(') + 1 const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -280,11 +280,11 @@ const yo2 = hmm([identifierGuy + 5])`
it('should move a identifier into a new variable', async () => { it('should move a identifier into a new variable', async () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1 const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
programMemory, execState.memory,
[startIndex, startIndex], [startIndex, startIndex],
'newVar' 'newVar'
) )
@ -465,7 +465,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|> line([306.21, 198.87], %)` |> line([306.21, 198.87], %)`
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = 'line([306.21, 198.85], %, $a)' const lineOfInterest = 'line([306.21, 198.85], %, $a)'
const range: [number, number] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
@ -475,7 +475,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
const modifiedAst = deleteSegmentFromPipeExpression( const modifiedAst = deleteSegmentFromPipeExpression(
[], [],
ast, ast,
programMemory, execState.memory,
code, code,
pathToNode pathToNode
) )
@ -543,7 +543,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
const code = makeCode(line) const code = makeCode(line)
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = line const lineOfInterest = line
const range: [number, number] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
@ -554,7 +554,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
const modifiedAst = deleteSegmentFromPipeExpression( const modifiedAst = deleteSegmentFromPipeExpression(
dependentSegments, dependentSegments,
ast, ast,
programMemory, execState.memory,
code, code,
pathToNode pathToNode
) )
@ -632,7 +632,7 @@ describe('Testing removeSingleConstraintInfo', () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '(' const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number] = [ const range: [number, number] = [
code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + 1,
@ -661,7 +661,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode, pathToNode,
argPosition, argPosition,
ast, ast,
programMemory execState.memory
) )
if (!mod) return new Error('mod is undefined') if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst) const recastCode = recast(mod.modifiedAst)
@ -686,7 +686,7 @@ describe('Testing removeSingleConstraintInfo', () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '(' const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number] = [ const range: [number, number] = [
code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + 1,
@ -711,7 +711,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode, pathToNode,
argPosition, argPosition,
ast, ast,
programMemory execState.memory
) )
if (!mod) return new Error('mod is undefined') if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst) const recastCode = recast(mod.modifiedAst)
@ -882,7 +882,7 @@ const sketch002 = startSketchOn({
// const lineOfInterest = 'line([-2.94, 2.7], %)' // const lineOfInterest = 'line([-2.94, 2.7], %)'
const ast = parse(codeBefore) const ast = parse(codeBefore)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
// deleteFromSelection // deleteFromSelection
const range: [number, number] = [ const range: [number, number] = [
@ -895,7 +895,7 @@ const sketch002 = startSketchOn({
range, range,
type, type,
}, },
programMemory, execState.memory,
async () => { async () => {
await new Promise((resolve) => setTimeout(resolve, 100)) await new Promise((resolve) => setTimeout(resolve, 100))
return { return {

View File

@ -18,6 +18,7 @@ import {
ProgramMemory, ProgramMemory,
SourceRange, SourceRange,
sketchGroupFromKclValue, sketchGroupFromKclValue,
ExpressionStatement,
} from './wasm' } from './wasm'
import { import {
isNodeSafeToReplacePath, isNodeSafeToReplacePath,
@ -80,18 +81,24 @@ export function addStartProfileAt(
pathToNode: PathToNode, pathToNode: PathToNode,
at: [number, number] at: [number, number]
): { modifiedAst: Program; pathToNode: PathToNode } | Error { ): { modifiedAst: Program; pathToNode: PathToNode } | Error {
const _node1 = getNodeFromPath<VariableDeclaration>( const _node1 = getNodeFromPath<VariableDeclaration>(node, pathToNode, [
node, 'VariableDeclaration',
pathToNode, 'ExpressionStatement',
'VariableDeclaration' ]) as { node: { type: string } } | Error
)
if (err(_node1)) return _node1 if (err(_node1)) return _node1
const variableDeclaration = _node1.node
if (variableDeclaration.type !== 'VariableDeclaration') {
return new Error('variableDeclaration.init.type !== PipeExpression')
}
const _node = { ...node } 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', [ const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createArrayExpression([ createArrayExpression([
createLiteral(roundOff(at[0])), createLiteral(roundOff(at[0])),
@ -99,11 +106,11 @@ export function addStartProfileAt(
]), ]),
createPipeSubstitution(), createPipeSubstitution(),
]) ])
if (init.type === 'PipeExpression') { if (expr.type === 'PipeExpression') {
init.body.splice(1, 0, startProfileAt) expr.body.splice(1, 0, startProfileAt)
} else { } else if (variableDeclaration) {
variableDeclaration.declarations[0].init = createPipeExpression([ variableDeclaration.declarations[0].init = createPipeExpression([
init, expr,
startProfileAt, startProfileAt,
]) ])
} }

View File

@ -45,11 +45,11 @@ const variableBelowShouldNotBeIncluded = 3
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7 const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const { variables, bodyPath, insertIndex } = findAllPreviousVariables( const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast, ast,
programMemory, execState.memory,
[rangeStart, rangeStart] [rangeStart, rangeStart]
) )
expect(variables).toEqual([ expect(variables).toEqual([
@ -351,11 +351,11 @@ const part001 = startSketchAt([-1.41, 3.46])
const ast = parse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({ const result = hasExtrudeSketchGroup({
ast, ast,
selection: { type: 'default', range: [100, 101] }, selection: { type: 'default', range: [100, 101] },
programMemory, programMemory: execState.memory,
}) })
expect(result).toEqual(true) expect(result).toEqual(true)
}) })
@ -370,11 +370,11 @@ const part001 = startSketchAt([-1.41, 3.46])
const ast = parse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({ const result = hasExtrudeSketchGroup({
ast, ast,
selection: { type: 'default', range: [100, 101] }, selection: { type: 'default', range: [100, 101] },
programMemory, programMemory: execState.memory,
}) })
expect(result).toEqual(true) expect(result).toEqual(true)
}) })
@ -383,11 +383,11 @@ const part001 = startSketchAt([-1.41, 3.46])
const ast = parse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({ const result = hasExtrudeSketchGroup({
ast, ast,
selection: { type: 'default', range: [10, 11] }, selection: { type: 'default', range: [10, 11] },
programMemory, programMemory: execState.memory,
}) })
expect(result).toEqual(false) expect(result).toEqual(false)
}) })

View File

@ -795,7 +795,7 @@ export function isSingleCursorInPipe(
const pathToNode = getNodePathFromSourceRange(ast, selection.range) const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const nodeTypes = pathToNode.map(([, type]) => type) const nodeTypes = pathToNode.map(([, type]) => type)
if (nodeTypes.includes('FunctionExpression')) return false if (nodeTypes.includes('FunctionExpression')) return false
if (!nodeTypes.includes('VariableDeclaration')) return false // if (!nodeTypes.includes('VariableDeclaration')) return false
if (nodeTypes.includes('PipeExpression')) return true if (nodeTypes.includes('PipeExpression')) return true
return false return false
} }

View File

@ -12,17 +12,20 @@ interface CommonCommandProperties {
export interface PlaneArtifact { export interface PlaneArtifact {
type: 'plane' type: 'plane'
id: ArtifactId
pathIds: Array<ArtifactId> pathIds: Array<ArtifactId>
codeRef: CommonCommandProperties codeRef: CommonCommandProperties
} }
export interface PlaneArtifactRich { export interface PlaneArtifactRich {
type: 'plane' type: 'plane'
id: ArtifactId
paths: Array<PathArtifact> paths: Array<PathArtifact>
codeRef: CommonCommandProperties codeRef: CommonCommandProperties
} }
export interface PathArtifact { export interface PathArtifact {
type: 'path' type: 'path'
id: ArtifactId
planeId: ArtifactId planeId: ArtifactId
segIds: Array<ArtifactId> segIds: Array<ArtifactId>
sweepId: ArtifactId sweepId: ArtifactId
@ -32,10 +35,12 @@ export interface PathArtifact {
interface solid2D { interface solid2D {
type: 'solid2D' type: 'solid2D'
id: ArtifactId
pathId: ArtifactId pathId: ArtifactId
} }
export interface PathArtifactRich { export interface PathArtifactRich {
type: 'path' type: 'path'
id: ArtifactId
plane: PlaneArtifact | WallArtifact plane: PlaneArtifact | WallArtifact
segments: Array<SegmentArtifact> segments: Array<SegmentArtifact>
sweep: SweepArtifact sweep: SweepArtifact
@ -44,6 +49,7 @@ export interface PathArtifactRich {
export interface SegmentArtifact { export interface SegmentArtifact {
type: 'segment' type: 'segment'
id: ArtifactId
pathId: ArtifactId pathId: ArtifactId
surfaceId: ArtifactId surfaceId: ArtifactId
edgeIds: Array<ArtifactId> edgeIds: Array<ArtifactId>
@ -52,6 +58,7 @@ export interface SegmentArtifact {
} }
interface SegmentArtifactRich { interface SegmentArtifactRich {
type: 'segment' type: 'segment'
id: ArtifactId
path: PathArtifact path: PathArtifact
surf: WallArtifact surf: WallArtifact
edges: Array<SweepEdge> edges: Array<SweepEdge>
@ -63,14 +70,16 @@ interface SegmentArtifactRich {
interface SweepArtifact { interface SweepArtifact {
type: 'sweep' type: 'sweep'
subType: 'extrusion' | 'revolve' subType: 'extrusion' | 'revolve'
id: ArtifactId
pathId: string pathId: string
surfaceIds: Array<string> surfaceIds: Array<ArtifactId>
edgeIds: Array<string> edgeIds: Array<ArtifactId>
codeRef: CommonCommandProperties codeRef: CommonCommandProperties
} }
interface SweepArtifactRich { interface SweepArtifactRich {
type: 'sweep' type: 'sweep'
subType: 'extrusion' | 'revolve' subType: 'extrusion' | 'revolve'
id: ArtifactId
path: PathArtifact path: PathArtifact
surfaces: Array<WallArtifact | CapArtifact> surfaces: Array<WallArtifact | CapArtifact>
edges: Array<SweepEdge> edges: Array<SweepEdge>
@ -79,6 +88,7 @@ interface SweepArtifactRich {
interface WallArtifact { interface WallArtifact {
type: 'wall' type: 'wall'
id: ArtifactId
segId: ArtifactId segId: ArtifactId
edgeCutEdgeIds: Array<ArtifactId> edgeCutEdgeIds: Array<ArtifactId>
sweepId: ArtifactId sweepId: ArtifactId
@ -86,6 +96,7 @@ interface WallArtifact {
} }
interface CapArtifact { interface CapArtifact {
type: 'cap' type: 'cap'
id: ArtifactId
subType: 'start' | 'end' subType: 'start' | 'end'
edgeCutEdgeIds: Array<ArtifactId> edgeCutEdgeIds: Array<ArtifactId>
sweepId: ArtifactId sweepId: ArtifactId
@ -94,6 +105,7 @@ interface CapArtifact {
interface SweepEdge { interface SweepEdge {
type: 'sweepEdge' type: 'sweepEdge'
id: ArtifactId
segId: ArtifactId segId: ArtifactId
sweepId: ArtifactId sweepId: ArtifactId
subType: 'opposite' | 'adjacent' subType: 'opposite' | 'adjacent'
@ -102,6 +114,7 @@ interface SweepEdge {
/** A edgeCut is a more generic term for both fillet or chamfer */ /** A edgeCut is a more generic term for both fillet or chamfer */
interface EdgeCut { interface EdgeCut {
type: 'edgeCut' type: 'edgeCut'
id: ArtifactId
subType: 'fillet' | 'chamfer' subType: 'fillet' | 'chamfer'
consumedEdgeId: ArtifactId consumedEdgeId: ArtifactId
edgeIds: Array<ArtifactId> edgeIds: Array<ArtifactId>
@ -111,6 +124,7 @@ interface EdgeCut {
interface EdgeCutEdge { interface EdgeCutEdge {
type: 'edgeCutEdge' type: 'edgeCutEdge'
id: ArtifactId
edgeCutId: ArtifactId edgeCutId: ArtifactId
surfaceId: ArtifactId surfaceId: ArtifactId
} }
@ -215,7 +229,7 @@ function mergeArtifacts(
* It does not mutate the map directly, but returns an array of artifacts to update * 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 * @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 * outside of this function. It would be good to update the `start_path` command to include the planeId so we
* can remove this. * can remove this.
*/ */
@ -258,6 +272,7 @@ export function getArtifactsToUpdate({
id: currentPlaneId, id: currentPlaneId,
artifact: { artifact: {
type: 'wall', type: 'wall',
id: currentPlaneId,
segId: existingPlane.segId, segId: existingPlane.segId,
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds, edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
sweepId: existingPlane.sweepId, sweepId: existingPlane.sweepId,
@ -267,7 +282,10 @@ export function getArtifactsToUpdate({
] ]
} else { } else {
return [ return [
{ id: currentPlaneId, artifact: { type: 'plane', pathIds, codeRef } }, {
id: currentPlaneId,
artifact: { type: 'plane', id: currentPlaneId, pathIds, codeRef },
},
] ]
} }
} else if (cmd.type === 'start_path') { } else if (cmd.type === 'start_path') {
@ -275,6 +293,7 @@ export function getArtifactsToUpdate({
id, id,
artifact: { artifact: {
type: 'path', type: 'path',
id,
segIds: [], segIds: [],
planeId: currentPlaneId, planeId: currentPlaneId,
sweepId: '', sweepId: '',
@ -287,7 +306,7 @@ export function getArtifactsToUpdate({
if (plane?.type === 'plane') { if (plane?.type === 'plane') {
returnArr.push({ returnArr.push({
id: currentPlaneId, id: currentPlaneId,
artifact: { type: 'plane', pathIds: [id], codeRef }, artifact: { type: 'plane', id: currentPlaneId, pathIds: [id], codeRef },
}) })
} }
if (plane?.type === 'wall') { if (plane?.type === 'wall') {
@ -295,6 +314,7 @@ export function getArtifactsToUpdate({
id: currentPlaneId, id: currentPlaneId,
artifact: { artifact: {
type: 'wall', type: 'wall',
id: currentPlaneId,
segId: plane.segId, segId: plane.segId,
edgeCutEdgeIds: plane.edgeCutEdgeIds, edgeCutEdgeIds: plane.edgeCutEdgeIds,
sweepId: plane.sweepId, sweepId: plane.sweepId,
@ -309,6 +329,7 @@ export function getArtifactsToUpdate({
id, id,
artifact: { artifact: {
type: 'segment', type: 'segment',
id,
pathId, pathId,
surfaceId: '', surfaceId: '',
edgeIds: [], edgeIds: [],
@ -318,21 +339,22 @@ export function getArtifactsToUpdate({
const path = getArtifact(pathId) const path = getArtifact(pathId)
if (path?.type === 'path') if (path?.type === 'path')
returnArr.push({ returnArr.push({
id: pathId, id: path.id,
artifact: { ...path, segIds: [id] }, artifact: { ...path, segIds: [id] },
}) })
if ( if (
response?.type === 'modeling' && response?.type === 'modeling' &&
response.data.modeling_response.type === 'close_path' response.data.modeling_response.type === 'close_path'
) { ) {
const id = response.data.modeling_response.data.face_id
returnArr.push({ returnArr.push({
id: response.data.modeling_response.data.face_id, id,
artifact: { type: 'solid2D', pathId }, artifact: { type: 'solid2D', id, pathId },
}) })
const path = getArtifact(pathId) const path = getArtifact(pathId)
if (path?.type === 'path') if (path?.type === 'path')
returnArr.push({ returnArr.push({
id: pathId, id: path.id,
artifact: { artifact: {
...path, ...path,
solid2dId: response.data.modeling_response.data.face_id, solid2dId: response.data.modeling_response.data.face_id,
@ -347,6 +369,7 @@ export function getArtifactsToUpdate({
artifact: { artifact: {
type: 'sweep', type: 'sweep',
subType: subType, subType: subType,
id,
pathId: cmd.target, pathId: cmd.target,
surfaceIds: [], surfaceIds: [],
edgeIds: [], edgeIds: [],
@ -356,7 +379,7 @@ export function getArtifactsToUpdate({
const path = getArtifact(cmd.target) const path = getArtifact(cmd.target)
if (path?.type === 'path') if (path?.type === 'path')
returnArr.push({ returnArr.push({
id: cmd.target, id: path.id,
artifact: { ...path, sweepId: id }, artifact: { ...path, sweepId: id },
}) })
return returnArr return returnArr
@ -378,6 +401,7 @@ export function getArtifactsToUpdate({
id: face_id, id: face_id,
artifact: { artifact: {
type: 'wall', type: 'wall',
id: face_id,
segId: curve_id, segId: curve_id,
edgeCutEdgeIds: [], edgeCutEdgeIds: [],
sweepId: path.sweepId, sweepId: path.sweepId,
@ -385,7 +409,7 @@ export function getArtifactsToUpdate({
}, },
}) })
returnArr.push({ returnArr.push({
id: curve_id, id: seg.id,
artifact: { ...seg, surfaceId: face_id }, artifact: { ...seg, surfaceId: face_id },
}) })
const sweep = getArtifact(path.sweepId) const sweep = getArtifact(path.sweepId)
@ -394,6 +418,7 @@ export function getArtifactsToUpdate({
id: path.sweepId, id: path.sweepId,
artifact: { artifact: {
...sweep, ...sweep,
id: path.sweepId,
surfaceIds: [face_id], surfaceIds: [face_id],
}, },
}) })
@ -410,6 +435,7 @@ export function getArtifactsToUpdate({
id: face_id, id: face_id,
artifact: { artifact: {
type: 'cap', type: 'cap',
id: face_id,
subType: cap === 'bottom' ? 'start' : 'end', subType: cap === 'bottom' ? 'start' : 'end',
edgeCutEdgeIds: [], edgeCutEdgeIds: [],
sweepId: path.sweepId, sweepId: path.sweepId,
@ -419,7 +445,7 @@ export function getArtifactsToUpdate({
const sweep = getArtifact(path.sweepId) const sweep = getArtifact(path.sweepId)
if (sweep?.type !== 'sweep') return if (sweep?.type !== 'sweep') return
returnArr.push({ returnArr.push({
id: path.sweepId, id: sweep.id,
artifact: { artifact: {
...sweep, ...sweep,
surfaceIds: [face_id], surfaceIds: [face_id],
@ -460,6 +486,7 @@ export function getArtifactsToUpdate({
cmd.type === 'solid3d_get_next_adjacent_edge' cmd.type === 'solid3d_get_next_adjacent_edge'
? 'adjacent' ? 'adjacent'
: 'opposite', : 'opposite',
id: response.data.modeling_response.data.edge,
segId: cmd.edge_id, segId: cmd.edge_id,
sweepId: path.sweepId, sweepId: path.sweepId,
}, },
@ -468,6 +495,7 @@ export function getArtifactsToUpdate({
id: cmd.edge_id, id: cmd.edge_id,
artifact: { artifact: {
...segment, ...segment,
id: cmd.edge_id,
edgeIds: [response.data.modeling_response.data.edge], edgeIds: [response.data.modeling_response.data.edge],
}, },
}, },
@ -475,6 +503,7 @@ export function getArtifactsToUpdate({
id: path.sweepId, id: path.sweepId,
artifact: { artifact: {
...sweep, ...sweep,
id: path.sweepId,
edgeIds: [response.data.modeling_response.data.edge], edgeIds: [response.data.modeling_response.data.edge],
}, },
}, },
@ -484,6 +513,7 @@ export function getArtifactsToUpdate({
id, id,
artifact: { artifact: {
type: 'edgeCut', type: 'edgeCut',
id,
subType: cmd.cut_type, subType: cmd.cut_type,
consumedEdgeId: cmd.edge_id, consumedEdgeId: cmd.edge_id,
edgeIds: [], edgeIds: [],
@ -494,7 +524,7 @@ export function getArtifactsToUpdate({
const consumedEdge = getArtifact(cmd.edge_id) const consumedEdge = getArtifact(cmd.edge_id)
if (consumedEdge?.type === 'segment') { if (consumedEdge?.type === 'segment') {
returnArr.push({ returnArr.push({
id: cmd.edge_id, id: consumedEdge.id,
artifact: { ...consumedEdge, edgeCutId: id }, artifact: { ...consumedEdge, edgeCutId: id },
}) })
} }
@ -574,6 +604,7 @@ export function expandPlane(
) )
return { return {
type: 'plane', type: 'plane',
id: plane.id,
paths: Array.from(paths.values()), paths: Array.from(paths.values()),
codeRef: plane.codeRef, codeRef: plane.codeRef,
} }
@ -602,6 +633,7 @@ export function expandPath(
if (err(plane)) return plane if (err(plane)) return plane
return { return {
type: 'path', type: 'path',
id: path.id,
segments: Array.from(segs.values()), segments: Array.from(segs.values()),
sweep, sweep,
plane, plane,
@ -629,6 +661,7 @@ export function expandSweep(
return { return {
type: 'sweep', type: 'sweep',
subType: 'extrusion', subType: 'extrusion',
id: sweep.id,
surfaces: Array.from(surfs.values()), surfaces: Array.from(surfs.values()),
edges: Array.from(edges.values()), edges: Array.from(edges.values()),
path, path,
@ -664,6 +697,7 @@ export function expandSegment(
return { return {
type: 'segment', type: 'segment',
id: segment.id,
path, path,
surf, surf,
edges: Array.from(edges.values()), edges: Array.from(edges.values()),
@ -785,3 +819,56 @@ export function getSweepFromSuspectedPath(
artifactGraph 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
}

View File

@ -117,11 +117,11 @@ describe('testing changeSketchArguments', () => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) return ast if (err(ast)) return ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange) const sourceStart = code.indexOf(lineToChange)
const changeSketchArgsRetVal = changeSketchArguments( const changeSketchArgsRetVal = changeSketchArguments(
ast, ast,
programMemory, execState.memory,
{ {
type: 'sourceRange', type: 'sourceRange',
sourceRange: [sourceStart, sourceStart + lineToChange.length], sourceRange: [sourceStart, sourceStart + lineToChange.length],
@ -150,12 +150,12 @@ const mySketch001 = startSketchOn('XY')
const ast = parse(code) const ast = parse(code)
if (err(ast)) return ast if (err(ast)) return ast
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange) const sourceStart = code.indexOf(lineToChange)
expect(sourceStart).toBe(95) expect(sourceStart).toBe(95)
const newSketchLnRetVal = addNewSketchLn({ const newSketchLnRetVal = addNewSketchLn({
node: ast, node: ast,
programMemory, programMemory: execState.memory,
input: { input: {
type: 'straight-segment', type: 'straight-segment',
from: [0, 0], from: [0, 0],
@ -186,7 +186,7 @@ const mySketch001 = startSketchOn('XY')
const modifiedAst2 = addCloseToPipe({ const modifiedAst2 = addCloseToPipe({
node: ast, node: ast,
programMemory, programMemory: execState.memory,
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
@ -230,7 +230,7 @@ describe('testing addTagForSketchOnFace', () => {
const pathToNode = getNodePathFromSourceRange(ast, sourceRange) const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
const sketchOnFaceRetVal = addTagForSketchOnFace( const sketchOnFaceRetVal = addTagForSketchOnFace(
{ {
// previousProgramMemory: programMemory, // redundant? // previousProgramMemory: execState.memory, // redundant?
pathToNode, pathToNode,
node: ast, node: ast,
}, },

View File

@ -40,7 +40,7 @@ async function testingSwapSketchFnCall({
const ast = parse(inputCode) const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast) if (err(ast)) return Promise.reject(ast)
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const selections = { const selections = {
codeBasedSelections: [range], codeBasedSelections: [range],
otherSelections: [], otherSelections: [],
@ -51,7 +51,7 @@ async function testingSwapSketchFnCall({
return Promise.reject(new Error('transformInfos undefined')) return Promise.reject(new Error('transformInfos undefined'))
const ast2 = transformAstSketchLines({ const ast2 = transformAstSketchLines({
ast, ast,
programMemory, programMemory: execState.memory,
selectionRanges: selections, selectionRanges: selections,
transformInfos, transformInfos,
referenceSegName: '', referenceSegName: '',
@ -366,10 +366,10 @@ const part001 = startSketchOn('XY')
|> line([2.14, 1.35], %) // normal-segment |> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)` |> xLine(3.54, %)`
it('normal case works', async () => { 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 index = code.indexOf('// normal-segment') - 7
const sg = sketchGroupFromKclValue( const sg = sketchGroupFromKclValue(
programMemory.get('part001'), execState.memory.get('part001'),
'part001' 'part001'
) as SketchGroup ) as SketchGroup
const _segment = getSketchSegmentFromSourceRange(sg, [index, index]) 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 () => { 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 index = code.indexOf('// segment-in-start') - 7
const _segment = getSketchSegmentFromSourceRange( const _segment = getSketchSegmentFromSourceRange(
sketchGroupFromKclValue( sketchGroupFromKclValue(
programMemory.get('part001'), execState.memory.get('part001'),
'part001' 'part001'
) as SketchGroup, ) as SketchGroup,
[index, index] [index, index]

View File

@ -220,7 +220,7 @@ const part001 = startSketchOn('XY')
} }
}) })
const programMemory = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos( const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)), makeSelections(selectionRanges.slice(1)),
ast, ast,
@ -231,7 +231,7 @@ const part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory, programMemory: execState.memory,
}) })
if (err(newAst)) return Promise.reject(newAst) 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( const transformInfos = getTransformInfos(
makeSelections(selectionRanges), makeSelections(selectionRanges),
ast, ast,
@ -322,7 +322,7 @@ const part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory, programMemory: execState.memory,
referenceSegName: '', referenceSegName: '',
}) })
if (err(newAst)) return Promise.reject(newAst) 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( const transformInfos = getTransformInfos(
makeSelections(selectionRanges), makeSelections(selectionRanges),
ast, ast,
@ -384,7 +384,7 @@ const part001 = startSketchOn('XY')
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory, programMemory: execState.memory,
referenceSegName: '', referenceSegName: '',
}) })
if (err(newAst)) return Promise.reject(newAst) 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( const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)), makeSelections(selectionRanges.slice(1)),
ast, ast,
@ -481,7 +481,7 @@ async function helperThing(
ast, ast,
selectionRanges: makeSelections(selectionRanges), selectionRanges: makeSelections(selectionRanges),
transformInfos, transformInfos,
programMemory, programMemory: execState.memory,
}) })
if (err(newAst)) return Promise.reject(newAst) if (err(newAst)) return Promise.reject(newAst)

View File

@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
offset: ${offset}, offset: ${offset},
}, %, $yo2) }, %, $yo2)
const intersect = segEndX(yo2)` const intersect = segEndX(yo2)`
const mem = await enginelessExecutor(parse(code('-1'))) const execState = await enginelessExecutor(parse(code('-1')))
expect(mem.get('intersect')?.value).toBe(1 + Math.sqrt(2)) expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
const noOffset = await enginelessExecutor(parse(code('0'))) const noOffset = await enginelessExecutor(parse(code('0')))
expect(noOffset.get('intersect')?.value).toBeCloseTo(1) expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
}) })
}) })

View File

@ -37,6 +37,8 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types' import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration' import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup' 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 { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr' 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][] 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 { interface Memory {
[key: string]: KclValue [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 ( export const executor = async (
node: Program, node: Program,
programMemory: ProgramMemory | Error = ProgramMemory.empty(), programMemory: ProgramMemory | Error = ProgramMemory.empty(),
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
isMock: boolean = false isMock: boolean = false
): Promise<ProgramMemory> => { ): Promise<ExecState> => {
if (err(programMemory)) return Promise.reject(programMemory) if (err(programMemory)) return Promise.reject(programMemory)
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
@ -380,7 +447,7 @@ export const _executor = async (
programMemory: ProgramMemory | Error = ProgramMemory.empty(), programMemory: ProgramMemory | Error = ProgramMemory.empty(),
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
isMock: boolean isMock: boolean
): Promise<ProgramMemory> => { ): Promise<ExecState> => {
if (err(programMemory)) return Promise.reject(programMemory) if (err(programMemory)) return Promise.reject(programMemory)
try { try {
@ -392,7 +459,7 @@ export const _executor = async (
baseUnit = baseUnit =
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm' (await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
} }
const memory: RawProgramMemory = await execute_wasm( const execState: RawExecState = await execute_wasm(
JSON.stringify(node), JSON.stringify(node),
JSON.stringify(programMemory.toRaw()), JSON.stringify(programMemory.toRaw()),
baseUnit, baseUnit,
@ -400,7 +467,7 @@ export const _executor = async (
fileSystemManager, fileSystemManager,
isMock isMock
) )
return ProgramMemory.fromRaw(memory) return execStateFromRaw(execState)
} catch (e: any) { } catch (e: any) {
console.log(e) console.log(e)
const parsed: RustKclError = JSON.parse(e.toString()) const parsed: RustKclError = JSON.parse(e.toString())

View File

@ -58,10 +58,12 @@ export type Selection =
| 'line' | 'line'
| 'arc' | 'arc'
| 'all' | 'all'
artifactId?: ArtifactId
range: SourceRange range: SourceRange
} }
| { | {
type: 'opposite-edgeCut' | 'adjacent-edgeCut' | 'base-edgeCut' type: 'opposite-edgeCut' | 'adjacent-edgeCut' | 'base-edgeCut'
artifactId?: ArtifactId
range: SourceRange range: SourceRange
// TODO this is a temporary measure that well be made redundant with: https://github.com/KittyCAD/modeling-app/pull/3836 // TODO this is a temporary measure that well be made redundant with: https://github.com/KittyCAD/modeling-app/pull/3836
secondaryRange: SourceRange secondaryRange: SourceRange
@ -108,7 +110,11 @@ export async function getEventForSelectWithPoint({
type: 'Set selection', type: 'Set selection',
data: { data: {
selectionType: 'singleCodeCursor', 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: { data: {
selectionType: 'singleCodeCursor', selectionType: 'singleCodeCursor',
selection: { selection: {
artifactId: data.entity_id,
range: codeRef.range, range: codeRef.range,
type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap', type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
}, },
@ -136,7 +143,11 @@ export async function getEventForSelectWithPoint({
type: 'Set selection', type: 'Set selection',
data: { data: {
selectionType: 'singleCodeCursor', 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', type: 'Set selection',
data: { data: {
selectionType: 'singleCodeCursor', 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' selection?.type === 'opposite-edgeCut'
) )
return { return {
artifactId: selection?.artifactId,
range: [node.start, node.end], range: [node.start, node.end],
type: selection?.type, type: selection?.type,
secondaryRange: selection?.secondaryRange, secondaryRange: selection?.secondaryRange,
} }
return { return {
artifactId: selection?.artifactId,
range: [node.start, node.end], range: [node.start, node.end],
type: selection?.type, type: selection?.type,
} }
}) })
.filter((x?: Selection) => x !== undefined) as Selection[] .filter((x?: Selection) => x !== undefined)
return { return {
codeBasedSelections: codeBasedSelections:

View File

@ -1,4 +1,10 @@
import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm' import {
Program,
ProgramMemory,
_executor,
SourceRange,
ExecState,
} from '../lang/wasm'
import { import {
EngineCommandManager, EngineCommandManager,
EngineCommandManagerEvents, EngineCommandManagerEvents,
@ -78,7 +84,7 @@ class MockEngineCommandManager {
export async function enginelessExecutor( export async function enginelessExecutor(
ast: Program | Error, ast: Program | Error,
pm: ProgramMemory | Error = ProgramMemory.empty() pm: ProgramMemory | Error = ProgramMemory.empty()
): Promise<ProgramMemory> { ): Promise<ExecState> {
if (err(ast)) return Promise.reject(ast) if (err(ast)) return Promise.reject(ast)
if (err(pm)) return Promise.reject(pm) if (err(pm)) return Promise.reject(pm)
@ -88,15 +94,15 @@ export async function enginelessExecutor(
}) as any as EngineCommandManager }) as any as EngineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
mockEngineCommandManager.startNewSession() mockEngineCommandManager.startNewSession()
const programMemory = await _executor(ast, pm, mockEngineCommandManager, true) const execState = await _executor(ast, pm, mockEngineCommandManager, true)
await mockEngineCommandManager.waitForAllCommands() await mockEngineCommandManager.waitForAllCommands()
return programMemory return execState
} }
export async function executor( export async function executor(
ast: Program, ast: Program,
pm: ProgramMemory = ProgramMemory.empty() pm: ProgramMemory = ProgramMemory.empty()
): Promise<ProgramMemory> { ): Promise<ExecState> {
const engineCommandManager = new EngineCommandManager() const engineCommandManager = new EngineCommandManager()
engineCommandManager.start({ engineCommandManager.start({
setIsStreamReady: () => {}, setIsStreamReady: () => {},
@ -117,14 +123,9 @@ export async function executor(
toSync(async () => { toSync(async () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession() engineCommandManager.startNewSession()
const programMemory = await _executor( const execState = await _executor(ast, pm, engineCommandManager, false)
ast,
pm,
engineCommandManager,
false
)
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands()
resolve(programMemory) resolve(execState)
}, reportRejection) }, reportRejection)
) )
}) })

View File

@ -97,7 +97,7 @@ export function useCalculateKclExpression({
}) })
if (trap(error, { suppress: true })) return if (trap(error, { suppress: true })) return
} }
const { programMemory } = await executeAst({ const { execState } = await executeAst({
ast, ast,
engineCommandManager, engineCommandManager,
useFakeExecutor: true, useFakeExecutor: true,
@ -111,7 +111,7 @@ export function useCalculateKclExpression({
const init = const init =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.get('__result__')?.value const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)
} }

View File

@ -1,5 +1,8 @@
import { import {
Expr,
ExpressionStatement,
PathToNode, PathToNode,
PipeExpression,
VariableDeclaration, VariableDeclaration,
VariableDeclarator, VariableDeclarator,
parse, parse,
@ -2116,15 +2119,24 @@ export function isEditingExistingSketch({
// should check that the variable declaration is a pipeExpression // should check that the variable declaration is a pipeExpression
// and that the pipeExpression contains a "startProfileAt" callExpression // and that the pipeExpression contains a "startProfileAt" callExpression
if (!sketchDetails?.sketchPathToNode) return false if (!sketchDetails?.sketchPathToNode) return false
const variableDeclaration = getNodeFromPath<VariableDeclarator>( const _node1 = getNodeFromPath<VariableDeclaration | ExpressionStatement>(
kclManager.ast, kclManager.ast,
sketchDetails.sketchPathToNode, sketchDetails.sketchPathToNode,
'VariableDeclarator' ['VariableDeclaration', 'ExpressionStatement']
) ) as { node: { type: string } } | Error
if (err(variableDeclaration)) return false if (err(_node1)) return false
if (variableDeclaration.node.type !== 'VariableDeclarator') return false let expr: Expr
const pipeExpression = variableDeclaration.node.init if (_node1.node.type === 'VariableDeclaration') {
if (pipeExpression.type !== 'PipeExpression') return false 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( const hasStartProfileAt = pipeExpression.body.some(
(item) => (item) =>
item.type === 'CallExpression' && item.callee.name === 'startProfileAt' item.type === 'CallExpression' && item.callee.name === 'startProfileAt'

View File

@ -1430,6 +1430,7 @@ dependencies = [
"databake", "databake",
"derive-docs", "derive-docs",
"expectorate", "expectorate",
"fnv",
"form_urlencoded", "form_urlencoded",
"futures", "futures",
"git_rev", "git_rev",

View File

@ -21,6 +21,7 @@ convert_case = "0.6.0"
dashmap = "6.1.0" dashmap = "6.1.0"
databake = { version = "0.1.8", features = ["derive"] } databake = { version = "0.1.8", features = ["derive"] }
derive-docs = { version = "0.1.28", path = "../derive-docs" } derive-docs = { version = "0.1.28", path = "../derive-docs" }
fnv = "1.0.7"
form_urlencoded = "1.2.1" form_urlencoded = "1.2.1"
futures = { version = "0.3.30" } futures = { version = "0.3.30" }
git_rev = "0.1.0" git_rev = "0.1.0"

View File

@ -4,6 +4,7 @@ use std::{collections::HashMap, sync::Arc};
use anyhow::Result; use anyhow::Result;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use fnv::FnvHashMap;
use kcmc::{ use kcmc::{
each_cmd as mcmd, each_cmd as mcmd,
ok_response::{output::TakeSnapshot, OkModelingCmdResponse}, ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
@ -45,6 +46,16 @@ pub struct ExecState {
/// The current value of the pipe operator returned from the previous /// The current value of the pipe operator returned from the previous
/// expression. If we're not currently in a pipeline, this will be None. /// expression. If we're not currently in a pipeline, this will be None.
pub pipe_value: Option<KclValue>, 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)] #[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. /// A geometry.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]

View File

@ -15,17 +15,17 @@ use uuid::Uuid;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ executor::{
ExecState, ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, GeoMeta, KclValue, Path, SketchGroup, SketchGroupSet, ArtifactId, ExecState, ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, GeoMeta, KclValue, Path, SketchGroup,
SketchSurface, SketchGroupSet, SketchSurface,
}, },
std::Args, std::Args,
}; };
/// Extrudes by a given amount. /// 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 (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()) Ok(result.into())
} }
@ -79,7 +79,12 @@ pub async fn extrude(_exec_state: &mut ExecState, args: Args) -> Result<KclValue
#[stdlib { #[stdlib {
name = "extrude" 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(); let id = uuid::Uuid::new_v4();
// Extrude the element(s). // Extrude the element(s).
@ -120,7 +125,7 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}), ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
) )
.await?; .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()) 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( pub(crate) async fn do_post_extrude(
sketch_group: SketchGroup, sketch_group: SketchGroup,
length: f64, length: f64,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Box<ExtrudeGroup>, KclError> { ) -> Result<Box<ExtrudeGroup>, KclError> {
// Bring the object to the front of the scene. // Bring the object to the front of the scene.
@ -276,7 +282,7 @@ pub(crate) async fn do_post_extrude(
}) })
.collect(); .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, // 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 // that we passed in to the function, but it's actually the id of the
// sketch group. // sketch group.
@ -288,7 +294,13 @@ pub(crate) async fn do_post_extrude(
start_cap_id, start_cap_id,
end_cap_id, end_cap_id,
edge_cuts: vec![], edge_cuts: vec![],
})) });
exec_state.put_artifact(
ArtifactId::new(extrude_group.id),
KclValue::ExtrudeGroup(extrude_group.clone()),
);
Ok(extrude_group)
} }
#[derive(Default)] #[derive(Default)]

View File

@ -50,10 +50,10 @@ impl Default for LoftData {
} }
/// Create a 3D surface or solid by interpolating between two or more sketches. /// 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 (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)) 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( async fn inner_loft(
sketch_groups: Vec<SketchGroup>, sketch_groups: Vec<SketchGroup>,
data: Option<LoftData>, data: Option<LoftData>,
exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Box<ExtrudeGroup>, KclError> { ) -> Result<Box<ExtrudeGroup>, KclError> {
// Make sure we have at least two sketches. // Make sure we have at least two sketches.
@ -170,5 +171,5 @@ async fn inner_loft(
.await?; .await?;
// Using the first sketch as the base curve, idk we might want to change this later. // 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
} }

View File

@ -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)] #[cfg(test)]

View File

@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
ast::types::TagDeclarator, ast::types::TagDeclarator,
errors::KclError, errors::KclError,
executor::{BasePath, ExecState, GeoMeta, KclValue, Path, SketchGroup, SketchSurface}, executor::{ArtifactId, BasePath, ExecState, GeoMeta, KclValue, Path, SketchGroup, SketchSurface},
std::Args, std::Args,
}; };
@ -46,6 +46,11 @@ pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
args.get_circle_args()?; args.get_circle_args()?;
let sketch_group = inner_circle(data, sketch_surface_or_group, tag, exec_state, args).await?; 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)) Ok(KclValue::new_user_val(sketch_group.meta.clone(), sketch_group))
} }

View File

@ -16,8 +16,8 @@ use crate::{
ast::types::TagDeclarator, ast::types::TagDeclarator,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ executor::{
BasePath, ExecState, ExtrudeGroup, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d, Point3d, ArtifactId, BasePath, ExecState, ExtrudeGroup, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d,
SketchGroup, SketchGroupSet, SketchSurface, TagEngineInfo, TagIdentifier, UserVal, Point3d, SketchGroup, SketchGroupSet, SketchSurface, TagEngineInfo, TagIdentifier, UserVal,
}, },
std::{ std::{
utils::{ utils::{
@ -93,11 +93,16 @@ pub enum StartOrEnd {
} }
/// Draw a line to a point. /// 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>) = let (to, sketch_group, tag): ([f64; 2], SketchGroup, Option<TagDeclarator>) =
args.get_data_and_sketch_group_and_tag()?; args.get_data_and_sketch_group_and_tag()?;
let new_sketch_group = inner_line_to(to, sketch_group, tag, args).await?; 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)) 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. /// 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>) = let (delta, sketch_group, tag): ([f64; 2], SketchGroup, Option<TagDeclarator>) =
args.get_data_and_sketch_group_and_tag()?; args.get_data_and_sketch_group_and_tag()?;
let new_sketch_group = inner_line(delta, sketch_group, tag, args).await?; 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)) 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. /// 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 (sketch_group, tag): (SketchGroup, Option<TagDeclarator>) = args.get_sketch_group_and_optional_tag()?;
let new_sketch_group = inner_close(sketch_group, tag, args).await?; 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)) Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group))
} }

View File

@ -59,7 +59,7 @@ pub async fn execute_wasm(
// gloo-serialize crate instead. // gloo-serialize crate instead.
// DO NOT USE serde_wasm_bindgen::to_value(&memory).map_err(|e| e.to_string()) // DO NOT USE serde_wasm_bindgen::to_value(&memory).map_err(|e| e.to_string())
// it will break the frontend. // 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 // wasm_bindgen wrapper for execute