Compare commits
16 Commits
dev
...
pierremtb/
| Author | SHA1 | Date | |
|---|---|---|---|
| cd672d52f6 | |||
| 172e01529c | |||
| 5a4a32c044 | |||
| b955184191 | |||
| d7914219da | |||
| ead4c1286b | |||
| a0fe33260e | |||
| 8955b5fcd3 | |||
| 5708b8c64b | |||
| 5b8284e737 | |||
| dd9b0ec5f0 | |||
| c467568ee4 | |||
| cb976ec31b | |||
| cc9eb65456 | |||
| a589f56e73 | |||
| 4c1564e2b0 |
@ -2,9 +2,12 @@ import type { Node } from '@rust/kcl-lib/bindings/Node'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
|
createCallExpressionStdLibKw,
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
|
createLabeledArg,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
createLiteralMaybeSuffix,
|
createLiteralMaybeSuffix,
|
||||||
|
createLocalName,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createPipeExpression,
|
createPipeExpression,
|
||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
@ -14,12 +17,19 @@ import {
|
|||||||
} from '@src/lang/create'
|
} from '@src/lang/create'
|
||||||
import {
|
import {
|
||||||
addSketchTo,
|
addSketchTo,
|
||||||
|
createPathToNodeForLastVariable,
|
||||||
|
createVariableExpressionsArray,
|
||||||
deleteSegmentFromPipeExpression,
|
deleteSegmentFromPipeExpression,
|
||||||
moveValueIntoNewVariable,
|
moveValueIntoNewVariable,
|
||||||
|
setCallInAst,
|
||||||
sketchOnExtrudedFace,
|
sketchOnExtrudedFace,
|
||||||
splitPipedProfile,
|
splitPipedProfile,
|
||||||
} from '@src/lang/modifyAst'
|
} from '@src/lang/modifyAst'
|
||||||
import { findUsesOfTagInPipe } from '@src/lang/queryAst'
|
import {
|
||||||
|
findUsesOfTagInPipe,
|
||||||
|
getNodeFromPath,
|
||||||
|
getVariableExprsFromSelection,
|
||||||
|
} from '@src/lang/queryAst'
|
||||||
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
||||||
import type { Artifact } from '@src/lang/std/artifactGraph'
|
import type { Artifact } from '@src/lang/std/artifactGraph'
|
||||||
import { codeRefFromRange } from '@src/lang/std/artifactGraph'
|
import { codeRefFromRange } from '@src/lang/std/artifactGraph'
|
||||||
@ -31,6 +41,7 @@ import { enginelessExecutor } from '@src/lib/testHelpers'
|
|||||||
import { err } from '@src/lib/trap'
|
import { err } from '@src/lib/trap'
|
||||||
import { deleteFromSelection } from '@src/lang/modifyAst/deleteFromSelection'
|
import { deleteFromSelection } from '@src/lang/modifyAst/deleteFromSelection'
|
||||||
import { assertNotErr } from '@src/unitTestUtils'
|
import { assertNotErr } from '@src/unitTestUtils'
|
||||||
|
import type { Selections } from '@src/lib/selections'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -917,3 +928,212 @@ extrude001 = extrude(part001, length = 5)
|
|||||||
expect(result instanceof Error).toBe(true)
|
expect(result instanceof Error).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Testing createVariableExpressionsArray', () => {
|
||||||
|
it('should return null for any number of pipe substitutions', () => {
|
||||||
|
const onePipe = [createPipeSubstitution()]
|
||||||
|
const twoPipes = [createPipeSubstitution(), createPipeSubstitution()]
|
||||||
|
const threePipes = [
|
||||||
|
createPipeSubstitution(),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]
|
||||||
|
expect(createVariableExpressionsArray(onePipe)).toBeNull()
|
||||||
|
expect(createVariableExpressionsArray(twoPipes)).toBeNull()
|
||||||
|
expect(createVariableExpressionsArray(threePipes)).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a variable expressions for one variable', () => {
|
||||||
|
const oneVariableName = [createLocalName('var1')]
|
||||||
|
const expr = createVariableExpressionsArray(oneVariableName)
|
||||||
|
if (expr?.type !== 'Name') {
|
||||||
|
throw new Error(`Expected Literal type, got ${expr?.type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(expr.name.name).toBe('var1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create an array of variable expressions for two variables', () => {
|
||||||
|
const twoVariableNames = [createLocalName('var1'), createLocalName('var2')]
|
||||||
|
const exprs = createVariableExpressionsArray(twoVariableNames)
|
||||||
|
if (exprs?.type !== 'ArrayExpression') {
|
||||||
|
throw new Error('Expected ArrayExpression type')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(exprs.elements).toHaveLength(2)
|
||||||
|
if (
|
||||||
|
exprs.elements[0].type !== 'Name' ||
|
||||||
|
exprs.elements[1].type !== 'Name'
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected elements to be of type Name, got ${exprs.elements[0].type} and ${exprs.elements[1].type}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
expect(exprs.elements[0].name.name).toBe('var1')
|
||||||
|
expect(exprs.elements[1].name.name).toBe('var2')
|
||||||
|
})
|
||||||
|
|
||||||
|
// This would catch the issue at https://github.com/KittyCAD/modeling-app/issues/7669
|
||||||
|
// TODO: fix function to get this test to pass
|
||||||
|
// it('should create one expr if the array of variable names are the same', () => {
|
||||||
|
// const twoVariableNames = [createLocalName('var1'), createLocalName('var1')]
|
||||||
|
// const expr = createVariableExpressionsArray(twoVariableNames)
|
||||||
|
// if (expr?.type !== 'Name') {
|
||||||
|
// throw new Error(`Expected Literal type, got ${expr?.type}`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// expect(expr.name.name).toBe('var1')
|
||||||
|
// })
|
||||||
|
|
||||||
|
it('should create an array of variable expressions for one variable and a pipe', () => {
|
||||||
|
const oneVarOnePipe = [createPipeSubstitution(), createLocalName('var1')]
|
||||||
|
const exprs = createVariableExpressionsArray(oneVarOnePipe)
|
||||||
|
if (exprs?.type !== 'ArrayExpression') {
|
||||||
|
throw new Error('Expected ArrayExpression type')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(exprs.elements).toHaveLength(2)
|
||||||
|
expect(exprs.elements[0].type).toBe('PipeSubstitution')
|
||||||
|
if (exprs.elements[1].type !== 'Name') {
|
||||||
|
throw new Error(
|
||||||
|
`Expected elements[1] to be of type Name, got ${exprs.elements[1].type}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(exprs.elements[1].name.name).toBe('var1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Testing createPathToNodeForLastVariable', () => {
|
||||||
|
it('should create a path to the last variable in the array', () => {
|
||||||
|
const circleProfileInVar = `sketch001 = startSketchOn(XY)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 1)
|
||||||
|
extrude001 = extrude(profile001, length = 5)
|
||||||
|
`
|
||||||
|
const ast = assertParse(circleProfileInVar)
|
||||||
|
const path = createPathToNodeForLastVariable(ast, false)
|
||||||
|
expect(path.length).toEqual(4)
|
||||||
|
|
||||||
|
// Verify we can get the right node
|
||||||
|
const node = getNodeFromPath<any>(ast, path)
|
||||||
|
if (err(node)) {
|
||||||
|
throw node
|
||||||
|
}
|
||||||
|
// With the expected range
|
||||||
|
const startOfExtrudeIndex = circleProfileInVar.indexOf('extrude(')
|
||||||
|
expect(node.node.start).toEqual(startOfExtrudeIndex)
|
||||||
|
expect(node.node.end).toEqual(circleProfileInVar.length - 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a path to the first kwarg in the last expression', () => {
|
||||||
|
const circleProfileInVar = `sketch001 = startSketchOn(XY)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 1)
|
||||||
|
extrude001 = extrude(profile001, length = 123)
|
||||||
|
`
|
||||||
|
const ast = assertParse(circleProfileInVar)
|
||||||
|
const path = createPathToNodeForLastVariable(ast, true)
|
||||||
|
expect(path.length).toEqual(7)
|
||||||
|
|
||||||
|
// Verify we can get the right node
|
||||||
|
const node = getNodeFromPath<any>(ast, path)
|
||||||
|
if (err(node)) {
|
||||||
|
throw node
|
||||||
|
}
|
||||||
|
// With the expected range
|
||||||
|
const startOfKwargIndex = circleProfileInVar.indexOf('123')
|
||||||
|
expect(node.node.start).toEqual(startOfKwargIndex)
|
||||||
|
expect(node.node.end).toEqual(startOfKwargIndex + 3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Testing setCallInAst', () => {
|
||||||
|
it('should push an extrude call with variable on variable profile', () => {
|
||||||
|
const code = `sketch001 = startSketchOn(XY)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const ast = assertParse(code)
|
||||||
|
const exprs = createVariableExpressionsArray([
|
||||||
|
createLocalName('profile001'),
|
||||||
|
])
|
||||||
|
const call = createCallExpressionStdLibKw('extrude', exprs, [
|
||||||
|
createLabeledArg('length', createLiteral(5)),
|
||||||
|
])
|
||||||
|
const pathToNode = setCallInAst(ast, call)
|
||||||
|
if (err(pathToNode)) {
|
||||||
|
throw pathToNode
|
||||||
|
}
|
||||||
|
const newCode = recast(ast)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`extrude001 = extrude(profile001, length = 5)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should push an extrude call in pipe is selection was in variable-less pipe', async () => {
|
||||||
|
const code = `startSketchOn(XY)
|
||||||
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const ast = assertParse(code)
|
||||||
|
const { artifactGraph } = await enginelessExecutor(ast)
|
||||||
|
const artifact = artifactGraph.values().find((a) => a.type === 'path')
|
||||||
|
if (!artifact) {
|
||||||
|
throw new Error('Artifact not found in the graph')
|
||||||
|
}
|
||||||
|
const selections: Selections = {
|
||||||
|
graphSelections: [
|
||||||
|
{
|
||||||
|
codeRef: artifact.codeRef,
|
||||||
|
artifact,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
const variableExprs = getVariableExprsFromSelection(selections, ast)
|
||||||
|
if (err(variableExprs)) throw variableExprs
|
||||||
|
const exprs = createVariableExpressionsArray(variableExprs.exprs)
|
||||||
|
const call = createCallExpressionStdLibKw('extrude', exprs, [
|
||||||
|
createLabeledArg('length', createLiteral(5)),
|
||||||
|
])
|
||||||
|
const lastPathToNode = variableExprs.paths.pop()
|
||||||
|
const pathToNode = setCallInAst(ast, call, undefined, lastPathToNode)
|
||||||
|
if (err(pathToNode)) {
|
||||||
|
throw pathToNode
|
||||||
|
}
|
||||||
|
const newCode = recast(ast)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`|> extrude(length = 5)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should push an extrude call with variable if selection was in variable pipe', async () => {
|
||||||
|
const code = `profile001 = startSketchOn(XY)
|
||||||
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const ast = assertParse(code)
|
||||||
|
const { artifactGraph } = await enginelessExecutor(ast)
|
||||||
|
const artifact = artifactGraph.values().find((a) => a.type === 'path')
|
||||||
|
if (!artifact) {
|
||||||
|
throw new Error('Artifact not found in the graph')
|
||||||
|
}
|
||||||
|
const selections: Selections = {
|
||||||
|
graphSelections: [
|
||||||
|
{
|
||||||
|
codeRef: artifact.codeRef,
|
||||||
|
artifact,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
const variableExprs = getVariableExprsFromSelection(selections, ast)
|
||||||
|
if (err(variableExprs)) throw variableExprs
|
||||||
|
const exprs = createVariableExpressionsArray(variableExprs.exprs)
|
||||||
|
const call = createCallExpressionStdLibKw('extrude', exprs, [
|
||||||
|
createLabeledArg('length', createLiteral(5)),
|
||||||
|
])
|
||||||
|
const lastPathToNode = variableExprs.paths.pop()
|
||||||
|
const pathToNode = setCallInAst(ast, call, undefined, lastPathToNode)
|
||||||
|
if (err(pathToNode)) {
|
||||||
|
throw pathToNode
|
||||||
|
}
|
||||||
|
const newCode = recast(ast)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`extrude001 = extrude(profile001, length = 5)`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -1209,3 +1209,83 @@ export function insertVariableAndOffsetPathToNode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create an array expression for variables,
|
||||||
|
// or keep it null if all are PipeSubstitutions
|
||||||
|
export function createVariableExpressionsArray(sketches: Expr[]): Expr | null {
|
||||||
|
let exprs: Expr | null = null
|
||||||
|
if (sketches.every((s) => s.type === 'PipeSubstitution')) {
|
||||||
|
// Keeping null so we don't even put it the % sign
|
||||||
|
} else if (sketches.length === 1) {
|
||||||
|
exprs = sketches[0]
|
||||||
|
} else {
|
||||||
|
exprs = createArrayExpression(sketches)
|
||||||
|
}
|
||||||
|
return exprs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a path to node to the last variable declaroator of an ast
|
||||||
|
// Optionally, can point to the first kwarg of the CallExpressionKw
|
||||||
|
export function createPathToNodeForLastVariable(
|
||||||
|
ast: Node<Program>,
|
||||||
|
toFirstKwarg = true
|
||||||
|
): PathToNode {
|
||||||
|
const argIndex = 0 // first kwarg for all sweeps here
|
||||||
|
const pathToCall: PathToNode = [
|
||||||
|
['body', ''],
|
||||||
|
[ast.body.length - 1, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
]
|
||||||
|
if (toFirstKwarg) {
|
||||||
|
pathToCall.push(
|
||||||
|
['arguments', 'CallExpressionKw'],
|
||||||
|
[argIndex, ARG_INDEX_FIELD],
|
||||||
|
['arg', LABELED_ARG_FIELD]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathToCall
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCallInAst(
|
||||||
|
ast: Node<Program>,
|
||||||
|
call: Node<CallExpressionKw>,
|
||||||
|
nodeToEdit?: PathToNode,
|
||||||
|
lastPathToNode?: PathToNode
|
||||||
|
): Error | PathToNode {
|
||||||
|
let pathToNode: PathToNode | undefined
|
||||||
|
if (nodeToEdit) {
|
||||||
|
const result = getNodeFromPath<CallExpressionKw>(
|
||||||
|
ast,
|
||||||
|
nodeToEdit,
|
||||||
|
'CallExpressionKw'
|
||||||
|
)
|
||||||
|
if (err(result)) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(result.node, call)
|
||||||
|
pathToNode = nodeToEdit
|
||||||
|
} else {
|
||||||
|
if (!call.unlabeled && lastPathToNode) {
|
||||||
|
const pipe = getNodeFromPath<PipeExpression>(
|
||||||
|
ast,
|
||||||
|
lastPathToNode,
|
||||||
|
'PipeExpression'
|
||||||
|
)
|
||||||
|
if (err(pipe)) {
|
||||||
|
return pipe
|
||||||
|
}
|
||||||
|
pipe.node.body.push(call)
|
||||||
|
pathToNode = lastPathToNode
|
||||||
|
} else {
|
||||||
|
const name = findUniqueName(ast, call.callee.name.name)
|
||||||
|
const declaration = createVariableDeclaration(name, call)
|
||||||
|
ast.body.push(declaration)
|
||||||
|
pathToNode = createPathToNodeForLastVariable(ast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathToNode
|
||||||
|
}
|
||||||
|
|||||||
252
src/lang/modifyAst/sweeps.test.ts
Normal file
252
src/lang/modifyAst/sweeps.test.ts
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import {
|
||||||
|
type Artifact,
|
||||||
|
assertParse,
|
||||||
|
type CodeRef,
|
||||||
|
type Program,
|
||||||
|
recast,
|
||||||
|
} from '@src/lang/wasm'
|
||||||
|
import type { Selection, Selections } from '@src/lib/selections'
|
||||||
|
import { enginelessExecutor } from '@src/lib/testHelpers'
|
||||||
|
import { err } from '@src/lib/trap'
|
||||||
|
import {
|
||||||
|
addExtrude,
|
||||||
|
addLoft,
|
||||||
|
addRevolve,
|
||||||
|
addSweep,
|
||||||
|
} from '@src/lang/modifyAst/sweeps'
|
||||||
|
import { stringToKclExpression } from '@src/lib/kclHelpers'
|
||||||
|
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||||||
|
|
||||||
|
async function getAstAndArtifactGraph(code: string) {
|
||||||
|
const ast = assertParse(code)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
|
||||||
|
const { artifactGraph } = await enginelessExecutor(ast)
|
||||||
|
return { ast, artifactGraph }
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSelectionFromPathArtifact(
|
||||||
|
artifacts: (Artifact & { codeRef: CodeRef })[]
|
||||||
|
): Selections {
|
||||||
|
const graphSelections = artifacts.map(
|
||||||
|
(artifact) =>
|
||||||
|
({
|
||||||
|
codeRef: artifact.codeRef,
|
||||||
|
artifact,
|
||||||
|
}) as Selection
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
graphSelections,
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAstAndSketchSelections(code: string) {
|
||||||
|
const { ast, artifactGraph } = await getAstAndArtifactGraph(code)
|
||||||
|
const artifacts = [...artifactGraph.values()].filter((a) => a.type === 'path')
|
||||||
|
if (artifacts.length === 0) {
|
||||||
|
throw new Error('Artifact not found in the graph')
|
||||||
|
}
|
||||||
|
const sketches = createSelectionFromPathArtifact(artifacts)
|
||||||
|
return { ast, sketches }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getKclCommandValue(value: string) {
|
||||||
|
const result = await stringToKclExpression(value)
|
||||||
|
if (err(result) || 'errors' in result) {
|
||||||
|
throw new Error(`Couldn't create kcl expression`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runNewAstAndCheckForSweep(ast: Node<Program>) {
|
||||||
|
const { artifactGraph } = await enginelessExecutor(ast)
|
||||||
|
console.log('artifactGraph', artifactGraph)
|
||||||
|
const sweepArtifact = artifactGraph.values().find((a) => a.type === 'sweep')
|
||||||
|
expect(sweepArtifact).toBeDefined()
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Testing addExtrude', () => {
|
||||||
|
it('should push a call in pipe if selection was in variable-less pipe', async () => {
|
||||||
|
const code = `startSketchOn(XY)
|
||||||
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const { ast, sketches } = await getAstAndSketchSelections(code)
|
||||||
|
const length = await getKclCommandValue('1')
|
||||||
|
const result = addExtrude({ ast, sketches, length })
|
||||||
|
if (err(result)) throw result
|
||||||
|
const newCode = recast(result.modifiedAst)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`|> extrude(length = 1)`)
|
||||||
|
await runNewAstAndCheckForSweep(result.modifiedAst)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should push a call with variable if selection was in variable profile', async () => {
|
||||||
|
const code = `sketch001 = startSketchOn(XY)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const { ast, sketches } = await getAstAndSketchSelections(code)
|
||||||
|
const length = await getKclCommandValue('2')
|
||||||
|
const result = addExtrude({ ast, sketches, length })
|
||||||
|
if (err(result)) throw result
|
||||||
|
const newCode = recast(result.modifiedAst)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`extrude001 = extrude(profile001, length = 2)`)
|
||||||
|
await runNewAstAndCheckForSweep(result.modifiedAst)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should push a call with variable if selection was in variable pipe', async () => {
|
||||||
|
const code = `profile001 = startSketchOn(XY)
|
||||||
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const { ast, sketches } = await getAstAndSketchSelections(code)
|
||||||
|
const length = await getKclCommandValue('3')
|
||||||
|
const result = addExtrude({ ast, sketches, length })
|
||||||
|
if (err(result)) throw result
|
||||||
|
await runNewAstAndCheckForSweep(result.modifiedAst)
|
||||||
|
const newCode = recast(result.modifiedAst)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`extrude001 = extrude(profile001, length = 3)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should push a call with many compatible optional args if asked', async () => {
|
||||||
|
const code = `sketch001 = startSketchOn(XY)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const { ast, sketches } = await getAstAndSketchSelections(code)
|
||||||
|
const length = await getKclCommandValue('10')
|
||||||
|
const bidirectionalLength = await getKclCommandValue('20')
|
||||||
|
const twistAngle = await getKclCommandValue('30')
|
||||||
|
const result = addExtrude({
|
||||||
|
ast,
|
||||||
|
sketches,
|
||||||
|
length,
|
||||||
|
bidirectionalLength,
|
||||||
|
twistAngle,
|
||||||
|
})
|
||||||
|
if (err(result)) throw result
|
||||||
|
await runNewAstAndCheckForSweep(result.modifiedAst)
|
||||||
|
const newCode = recast(result.modifiedAst)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`extrude001 = extrude(
|
||||||
|
profile001,
|
||||||
|
length = 10,
|
||||||
|
bidirectionalLength = 20,
|
||||||
|
twistAngle = 30,
|
||||||
|
)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: missing edit flow test
|
||||||
|
|
||||||
|
// TODO: missing multi-profile test
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Testing addSweep', () => {
|
||||||
|
it('should push a call with variable and all compatible optional args', async () => {
|
||||||
|
const code = `sketch001 = startSketchOn(XY)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 1)
|
||||||
|
sketch002 = startSketchOn(XZ)
|
||||||
|
profile002 = startProfile(sketch002, at = [0, 0])
|
||||||
|
|> xLine(length = -5)
|
||||||
|
|> tangentialArc(endAbsolute = [-20, 5])
|
||||||
|
`
|
||||||
|
const { ast, artifactGraph } = await getAstAndArtifactGraph(code)
|
||||||
|
const artifact1 = artifactGraph.values().find((a) => a.type === 'path')
|
||||||
|
const artifact2 = [...artifactGraph.values()].findLast(
|
||||||
|
(a) => a.type === 'path'
|
||||||
|
)
|
||||||
|
if (!artifact1 || !artifact2) {
|
||||||
|
throw new Error('Artifact not found in the graph')
|
||||||
|
}
|
||||||
|
|
||||||
|
const sketches = createSelectionFromPathArtifact([artifact1])
|
||||||
|
const path = createSelectionFromPathArtifact([artifact2])
|
||||||
|
const sectional = true
|
||||||
|
const relativeTo = 'sketchPlane'
|
||||||
|
const result = addSweep({
|
||||||
|
ast,
|
||||||
|
sketches,
|
||||||
|
path,
|
||||||
|
sectional,
|
||||||
|
relativeTo,
|
||||||
|
})
|
||||||
|
if (err(result)) throw result
|
||||||
|
await runNewAstAndCheckForSweep(result.modifiedAst)
|
||||||
|
const newCode = recast(result.modifiedAst)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`sweep001 = sweep(
|
||||||
|
profile001,
|
||||||
|
path = profile002,
|
||||||
|
sectional = true,
|
||||||
|
relativeTo = 'sketchPlane',
|
||||||
|
)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: missing edit flow test
|
||||||
|
|
||||||
|
// TODO: missing multi-profile test
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Testing addLoft', () => {
|
||||||
|
it('should push a call with variable and all optional args if asked', async () => {
|
||||||
|
const code = `sketch001 = startSketchOn(XZ)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 30)
|
||||||
|
plane001 = offsetPlane(XZ, offset = 50)
|
||||||
|
sketch002 = startSketchOn(plane001)
|
||||||
|
profile002 = circle(sketch002, center = [0, 0], radius = 20)
|
||||||
|
`
|
||||||
|
const { ast, sketches } = await getAstAndSketchSelections(code)
|
||||||
|
expect(sketches.graphSelections).toHaveLength(2)
|
||||||
|
const vDegree = await getKclCommandValue('3')
|
||||||
|
const result = addLoft({
|
||||||
|
ast,
|
||||||
|
sketches,
|
||||||
|
vDegree,
|
||||||
|
})
|
||||||
|
if (err(result)) throw result
|
||||||
|
const newCode = recast(result.modifiedAst)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(
|
||||||
|
`loft001 = loft([profile001, profile002], vDegree = 3)`
|
||||||
|
)
|
||||||
|
// Don't think we can find the artifact here for loft?
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: missing edit flow test
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Testing addRevolve', () => {
|
||||||
|
it('should push a call with variable and compatible optional args if asked', async () => {
|
||||||
|
const code = `sketch001 = startSketchOn(XZ)
|
||||||
|
profile001 = circle(sketch001, center = [3, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const { ast, sketches } = await getAstAndSketchSelections(code)
|
||||||
|
expect(sketches.graphSelections).toHaveLength(1)
|
||||||
|
const result = addRevolve({
|
||||||
|
ast,
|
||||||
|
sketches,
|
||||||
|
angle: await getKclCommandValue('1'),
|
||||||
|
axisOrEdge: 'Axis',
|
||||||
|
axis: 'X',
|
||||||
|
edge: undefined,
|
||||||
|
symmetric: false,
|
||||||
|
bidirectionalAngle: await getKclCommandValue('2'),
|
||||||
|
})
|
||||||
|
if (err(result)) throw result
|
||||||
|
await runNewAstAndCheckForSweep(result.modifiedAst)
|
||||||
|
const newCode = recast(result.modifiedAst)
|
||||||
|
console.log(newCode)
|
||||||
|
expect(newCode).toContain(code)
|
||||||
|
expect(newCode).toContain(`revolve001 = revolve(
|
||||||
|
profile001,
|
||||||
|
angle = 1,
|
||||||
|
axis = X,
|
||||||
|
bidirectionalAngle = 2,
|
||||||
|
)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: missing edit flow test
|
||||||
|
|
||||||
|
// TODO: missing multi-profile test
|
||||||
|
})
|
||||||
@ -1,35 +1,28 @@
|
|||||||
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createArrayExpression,
|
|
||||||
createCallExpressionStdLibKw,
|
createCallExpressionStdLibKw,
|
||||||
createLabeledArg,
|
createLabeledArg,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
createLocalName,
|
createLocalName,
|
||||||
createVariableDeclaration,
|
|
||||||
findUniqueName,
|
|
||||||
} from '@src/lang/create'
|
} from '@src/lang/create'
|
||||||
import { insertVariableAndOffsetPathToNode } from '@src/lang/modifyAst'
|
import {
|
||||||
|
createVariableExpressionsArray,
|
||||||
|
insertVariableAndOffsetPathToNode,
|
||||||
|
setCallInAst,
|
||||||
|
} from '@src/lang/modifyAst'
|
||||||
import {
|
import {
|
||||||
getEdgeTagCall,
|
getEdgeTagCall,
|
||||||
mutateAstWithTagForSketchSegment,
|
mutateAstWithTagForSketchSegment,
|
||||||
} from '@src/lang/modifyAst/addEdgeTreatment'
|
} from '@src/lang/modifyAst/addEdgeTreatment'
|
||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getSketchExprsFromSelection,
|
getVariableExprsFromSelection,
|
||||||
valueOrVariable,
|
valueOrVariable,
|
||||||
} from '@src/lang/queryAst'
|
} from '@src/lang/queryAst'
|
||||||
import { ARG_INDEX_FIELD, LABELED_ARG_FIELD } from '@src/lang/queryAstConstants'
|
|
||||||
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
||||||
import type {
|
import type { PathToNode, Program, VariableDeclaration } from '@src/lang/wasm'
|
||||||
CallExpressionKw,
|
|
||||||
Expr,
|
|
||||||
PathToNode,
|
|
||||||
Program,
|
|
||||||
VariableDeclaration,
|
|
||||||
} from '@src/lang/wasm'
|
|
||||||
import type { KclCommandValue } from '@src/lib/commandTypes'
|
import type { KclCommandValue } from '@src/lib/commandTypes'
|
||||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from '@src/lib/constants'
|
|
||||||
import type { Selections } from '@src/lib/selections'
|
import type { Selections } from '@src/lib/selections'
|
||||||
import { err } from '@src/lib/trap'
|
import { err } from '@src/lib/trap'
|
||||||
|
|
||||||
@ -60,13 +53,13 @@ export function addExtrude({
|
|||||||
|
|
||||||
// 2. Prepare unlabeled and labeled arguments
|
// 2. Prepare unlabeled and labeled arguments
|
||||||
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
||||||
const sketchesExprList = getSketchExprsFromSelection(
|
const variableExpressions = getVariableExprsFromSelection(
|
||||||
sketches,
|
sketches,
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
nodeToEdit
|
nodeToEdit
|
||||||
)
|
)
|
||||||
if (err(sketchesExprList)) {
|
if (err(variableExpressions)) {
|
||||||
return sketchesExprList
|
return variableExpressions
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra labeled args expressions
|
// Extra labeled args expressions
|
||||||
@ -85,7 +78,7 @@ export function addExtrude({
|
|||||||
? [createLabeledArg('twistAngle', valueOrVariable(twistAngle))]
|
? [createLabeledArg('twistAngle', valueOrVariable(twistAngle))]
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const sketchesExpr = createSketchExpression(sketchesExprList)
|
const sketchesExpr = createVariableExpressionsArray(variableExpressions.exprs)
|
||||||
const call = createCallExpressionStdLibKw('extrude', sketchesExpr, [
|
const call = createCallExpressionStdLibKw('extrude', sketchesExpr, [
|
||||||
createLabeledArg('length', valueOrVariable(length)),
|
createLabeledArg('length', valueOrVariable(length)),
|
||||||
...symmetricExpr,
|
...symmetricExpr,
|
||||||
@ -114,27 +107,10 @@ export function addExtrude({
|
|||||||
|
|
||||||
// 3. If edit, we assign the new function call declaration to the existing node,
|
// 3. If edit, we assign the new function call declaration to the existing node,
|
||||||
// otherwise just push to the end
|
// otherwise just push to the end
|
||||||
let pathToNode: PathToNode | undefined
|
const lastPath = variableExpressions.paths.pop() // TODO: check if this is correct
|
||||||
if (nodeToEdit) {
|
const pathToNode = setCallInAst(modifiedAst, call, nodeToEdit, lastPath)
|
||||||
const result = getNodeFromPath<CallExpressionKw>(
|
if (err(pathToNode)) {
|
||||||
modifiedAst,
|
return pathToNode
|
||||||
nodeToEdit,
|
|
||||||
'CallExpressionKw'
|
|
||||||
)
|
|
||||||
if (err(result)) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(result.node, call)
|
|
||||||
pathToNode = nodeToEdit
|
|
||||||
} else {
|
|
||||||
const name = findUniqueName(
|
|
||||||
modifiedAst,
|
|
||||||
KCL_DEFAULT_CONSTANT_PREFIXES.EXTRUDE
|
|
||||||
)
|
|
||||||
const declaration = createVariableDeclaration(name, call)
|
|
||||||
modifiedAst.body.push(declaration)
|
|
||||||
pathToNode = createPathToNode(modifiedAst)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -168,13 +144,13 @@ export function addSweep({
|
|||||||
|
|
||||||
// 2. Prepare unlabeled and labeled arguments
|
// 2. Prepare unlabeled and labeled arguments
|
||||||
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
||||||
const sketchesExprList = getSketchExprsFromSelection(
|
const variableExprs = getVariableExprsFromSelection(
|
||||||
sketches,
|
sketches,
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
nodeToEdit
|
nodeToEdit
|
||||||
)
|
)
|
||||||
if (err(sketchesExprList)) {
|
if (err(variableExprs)) {
|
||||||
return sketchesExprList
|
return variableExprs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the path declaration for the labeled argument
|
// Find the path declaration for the labeled argument
|
||||||
@ -196,7 +172,7 @@ export function addSweep({
|
|||||||
? [createLabeledArg('relativeTo', createLiteral(relativeTo))]
|
? [createLabeledArg('relativeTo', createLiteral(relativeTo))]
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const sketchesExpr = createSketchExpression(sketchesExprList)
|
const sketchesExpr = createVariableExpressionsArray(variableExprs.exprs)
|
||||||
const call = createCallExpressionStdLibKw('sweep', sketchesExpr, [
|
const call = createCallExpressionStdLibKw('sweep', sketchesExpr, [
|
||||||
createLabeledArg('path', pathExpr),
|
createLabeledArg('path', pathExpr),
|
||||||
...sectionalExpr,
|
...sectionalExpr,
|
||||||
@ -205,27 +181,10 @@ export function addSweep({
|
|||||||
|
|
||||||
// 3. If edit, we assign the new function call declaration to the existing node,
|
// 3. If edit, we assign the new function call declaration to the existing node,
|
||||||
// otherwise just push to the end
|
// otherwise just push to the end
|
||||||
let pathToNode: PathToNode | undefined
|
const lastPath = variableExprs.paths.pop() // TODO: check if this is correct
|
||||||
if (nodeToEdit) {
|
const pathToNode = setCallInAst(modifiedAst, call, nodeToEdit, lastPath)
|
||||||
const result = getNodeFromPath<CallExpressionKw>(
|
if (err(pathToNode)) {
|
||||||
modifiedAst,
|
return pathToNode
|
||||||
nodeToEdit,
|
|
||||||
'CallExpressionKw'
|
|
||||||
)
|
|
||||||
if (err(result)) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(result.node, call)
|
|
||||||
pathToNode = nodeToEdit
|
|
||||||
} else {
|
|
||||||
const name = findUniqueName(
|
|
||||||
modifiedAst,
|
|
||||||
KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP
|
|
||||||
)
|
|
||||||
const declaration = createVariableDeclaration(name, call)
|
|
||||||
modifiedAst.body.push(declaration)
|
|
||||||
pathToNode = createPathToNode(modifiedAst)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -255,13 +214,13 @@ export function addLoft({
|
|||||||
|
|
||||||
// 2. Prepare unlabeled and labeled arguments
|
// 2. Prepare unlabeled and labeled arguments
|
||||||
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
||||||
const sketchesExprList = getSketchExprsFromSelection(
|
const variableExprs = getVariableExprsFromSelection(
|
||||||
sketches,
|
sketches,
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
nodeToEdit
|
nodeToEdit
|
||||||
)
|
)
|
||||||
if (err(sketchesExprList)) {
|
if (err(variableExprs)) {
|
||||||
return sketchesExprList
|
return variableExprs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra labeled args expressions
|
// Extra labeled args expressions
|
||||||
@ -269,7 +228,7 @@ export function addLoft({
|
|||||||
? [createLabeledArg('vDegree', valueOrVariable(vDegree))]
|
? [createLabeledArg('vDegree', valueOrVariable(vDegree))]
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const sketchesExpr = createSketchExpression(sketchesExprList)
|
const sketchesExpr = createVariableExpressionsArray(variableExprs.exprs)
|
||||||
const call = createCallExpressionStdLibKw('loft', sketchesExpr, [
|
const call = createCallExpressionStdLibKw('loft', sketchesExpr, [
|
||||||
...vDegreeExpr,
|
...vDegreeExpr,
|
||||||
])
|
])
|
||||||
@ -281,25 +240,10 @@ export function addLoft({
|
|||||||
|
|
||||||
// 3. If edit, we assign the new function call declaration to the existing node,
|
// 3. If edit, we assign the new function call declaration to the existing node,
|
||||||
// otherwise just push to the end
|
// otherwise just push to the end
|
||||||
let pathToNode: PathToNode | undefined
|
const lastPath = variableExprs.paths.pop() // TODO: check if this is correct
|
||||||
if (nodeToEdit) {
|
const pathToNode = setCallInAst(modifiedAst, call, nodeToEdit, lastPath)
|
||||||
const result = getNodeFromPath<CallExpressionKw>(
|
if (err(pathToNode)) {
|
||||||
modifiedAst,
|
return pathToNode
|
||||||
nodeToEdit,
|
|
||||||
'CallExpressionKw'
|
|
||||||
)
|
|
||||||
if (err(result)) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(result.node, call)
|
|
||||||
pathToNode = nodeToEdit
|
|
||||||
} else {
|
|
||||||
const name = findUniqueName(modifiedAst, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
|
|
||||||
const declaration = createVariableDeclaration(name, call)
|
|
||||||
modifiedAst.body.push(declaration)
|
|
||||||
const toFirstKwarg = !!vDegree
|
|
||||||
pathToNode = createPathToNode(modifiedAst, toFirstKwarg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -339,13 +283,13 @@ export function addRevolve({
|
|||||||
|
|
||||||
// 2. Prepare unlabeled and labeled arguments
|
// 2. Prepare unlabeled and labeled arguments
|
||||||
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
||||||
const sketchesExprList = getSketchExprsFromSelection(
|
const variableExprs = getVariableExprsFromSelection(
|
||||||
sketches,
|
sketches,
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
nodeToEdit
|
nodeToEdit
|
||||||
)
|
)
|
||||||
if (err(sketchesExprList)) {
|
if (err(variableExprs)) {
|
||||||
return sketchesExprList
|
return variableExprs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve axis expression depending on mode
|
// Retrieve axis expression depending on mode
|
||||||
@ -372,7 +316,7 @@ export function addRevolve({
|
|||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const sketchesExpr = createSketchExpression(sketchesExprList)
|
const sketchesExpr = createVariableExpressionsArray(variableExprs.exprs)
|
||||||
const call = createCallExpressionStdLibKw('revolve', sketchesExpr, [
|
const call = createCallExpressionStdLibKw('revolve', sketchesExpr, [
|
||||||
createLabeledArg('angle', valueOrVariable(angle)),
|
createLabeledArg('angle', valueOrVariable(angle)),
|
||||||
createLabeledArg('axis', getAxisResult.generatedAxis),
|
createLabeledArg('axis', getAxisResult.generatedAxis),
|
||||||
@ -399,27 +343,10 @@ export function addRevolve({
|
|||||||
|
|
||||||
// 3. If edit, we assign the new function call declaration to the existing node,
|
// 3. If edit, we assign the new function call declaration to the existing node,
|
||||||
// otherwise just push to the end
|
// otherwise just push to the end
|
||||||
let pathToNode: PathToNode | undefined
|
const lastPath = variableExprs.paths.pop() // TODO: check if this is correct
|
||||||
if (nodeToEdit) {
|
const pathToNode = setCallInAst(modifiedAst, call, nodeToEdit, lastPath)
|
||||||
const result = getNodeFromPath<CallExpressionKw>(
|
if (err(pathToNode)) {
|
||||||
modifiedAst,
|
return pathToNode
|
||||||
nodeToEdit,
|
|
||||||
'CallExpressionKw'
|
|
||||||
)
|
|
||||||
if (err(result)) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(result.node, call)
|
|
||||||
pathToNode = nodeToEdit
|
|
||||||
} else {
|
|
||||||
const name = findUniqueName(
|
|
||||||
modifiedAst,
|
|
||||||
KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE
|
|
||||||
)
|
|
||||||
const declaration = createVariableDeclaration(name, call)
|
|
||||||
modifiedAst.body.push(declaration)
|
|
||||||
pathToNode = createPathToNode(modifiedAst)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -430,40 +357,6 @@ export function addRevolve({
|
|||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
|
|
||||||
function createSketchExpression(sketches: Expr[]) {
|
|
||||||
let sketchesExpr: Expr | null = null
|
|
||||||
if (sketches.every((s) => s.type === 'PipeSubstitution')) {
|
|
||||||
// Keeping null so we don't even put it the % sign
|
|
||||||
} else if (sketches.length === 1) {
|
|
||||||
sketchesExpr = sketches[0]
|
|
||||||
} else {
|
|
||||||
sketchesExpr = createArrayExpression(sketches)
|
|
||||||
}
|
|
||||||
return sketchesExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPathToNode(
|
|
||||||
modifiedAst: Node<Program>,
|
|
||||||
toFirstKwarg = true
|
|
||||||
): PathToNode {
|
|
||||||
const argIndex = 0 // first kwarg for all sweeps here
|
|
||||||
const pathToCall: PathToNode = [
|
|
||||||
['body', ''],
|
|
||||||
[modifiedAst.body.length - 1, 'index'],
|
|
||||||
['declaration', 'VariableDeclaration'],
|
|
||||||
['init', 'VariableDeclarator'],
|
|
||||||
]
|
|
||||||
if (toFirstKwarg) {
|
|
||||||
pathToCall.push(
|
|
||||||
['arguments', 'CallExpressionKw'],
|
|
||||||
[argIndex, ARG_INDEX_FIELD],
|
|
||||||
['arg', LABELED_ARG_FIELD]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathToCall
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAxisExpressionAndIndex(
|
export function getAxisExpressionAndIndex(
|
||||||
axisOrEdge: 'Axis' | 'Edge',
|
axisOrEdge: 'Axis' | 'Edge',
|
||||||
axis: string | undefined,
|
axis: string | undefined,
|
||||||
@ -15,6 +15,7 @@ import {
|
|||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
findUsesOfTagInPipe,
|
findUsesOfTagInPipe,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
|
getVariableExprsFromSelection,
|
||||||
hasSketchPipeBeenExtruded,
|
hasSketchPipeBeenExtruded,
|
||||||
isCursorInFunctionDefinition,
|
isCursorInFunctionDefinition,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
@ -27,7 +28,7 @@ import { topLevelRange } from '@src/lang/util'
|
|||||||
import type { Identifier, PathToNode } from '@src/lang/wasm'
|
import type { Identifier, PathToNode } from '@src/lang/wasm'
|
||||||
import { assertParse, recast } from '@src/lang/wasm'
|
import { assertParse, recast } from '@src/lang/wasm'
|
||||||
import { initPromise } from '@src/lang/wasmUtils'
|
import { initPromise } from '@src/lang/wasmUtils'
|
||||||
import { type Selection } from '@src/lib/selections'
|
import type { Selections, Selection } from '@src/lib/selections'
|
||||||
import { enginelessExecutor } from '@src/lib/testHelpers'
|
import { enginelessExecutor } from '@src/lib/testHelpers'
|
||||||
import { err } from '@src/lib/trap'
|
import { err } from '@src/lib/trap'
|
||||||
|
|
||||||
@ -778,3 +779,184 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
|
|||||||
expect(result).toEqual(false)
|
expect(result).toEqual(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Testing getVariableExprsFromSelection', () => {
|
||||||
|
it('should find the variable expr in a simple profile selection', async () => {
|
||||||
|
const circleProfileInVar = `sketch001 = startSketchOn(XY)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const ast = assertParse(circleProfileInVar)
|
||||||
|
const { artifactGraph } = await enginelessExecutor(ast)
|
||||||
|
const artifact = artifactGraph.values().find((a) => a.type === 'path')
|
||||||
|
if (!artifact) {
|
||||||
|
throw new Error('Artifact not found in the graph')
|
||||||
|
}
|
||||||
|
const selections: Selections = {
|
||||||
|
graphSelections: [
|
||||||
|
{
|
||||||
|
codeRef: artifact.codeRef,
|
||||||
|
artifact,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
const variableExprs = getVariableExprsFromSelection(selections, ast)
|
||||||
|
if (err(variableExprs)) throw variableExprs
|
||||||
|
|
||||||
|
expect(variableExprs.exprs).toHaveLength(1)
|
||||||
|
if (variableExprs.exprs[0].type !== 'Name') {
|
||||||
|
throw new Error(`Expected Name, got ${variableExprs.exprs[0].type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(variableExprs.exprs[0].name.name).toEqual('profile001')
|
||||||
|
|
||||||
|
expect(variableExprs.paths).toHaveLength(1)
|
||||||
|
expect(variableExprs.paths[0]).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[1, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', ''],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the pipe substitution symbol in a variable-less simple profile selection', async () => {
|
||||||
|
const circleProfileInVar = `startSketchOn(XY)
|
||||||
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|
`
|
||||||
|
const ast = assertParse(circleProfileInVar)
|
||||||
|
const { artifactGraph } = await enginelessExecutor(ast)
|
||||||
|
const artifact = artifactGraph.values().find((a) => a.type === 'path')
|
||||||
|
if (!artifact) {
|
||||||
|
throw new Error('Artifact not found in the graph')
|
||||||
|
}
|
||||||
|
const selections: Selections = {
|
||||||
|
graphSelections: [
|
||||||
|
{
|
||||||
|
codeRef: artifact.codeRef,
|
||||||
|
artifact,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
const variableExprs = getVariableExprsFromSelection(selections, ast)
|
||||||
|
if (err(variableExprs)) throw variableExprs
|
||||||
|
|
||||||
|
expect(variableExprs.exprs).toHaveLength(1)
|
||||||
|
expect(variableExprs.exprs[0].type).toEqual('PipeSubstitution')
|
||||||
|
|
||||||
|
expect(variableExprs.paths).toHaveLength(1)
|
||||||
|
expect(variableExprs.paths[0]).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[0, 'index'],
|
||||||
|
['expression', 'ExpressionStatement'],
|
||||||
|
['body', 'PipeExpression'],
|
||||||
|
[1, 'index'],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should find the variable exprs in a multi profile selection ', async () => {
|
||||||
|
const circleProfileInVar = `sketch001 = startSketchOn(XY)
|
||||||
|
profile001 = circle(sketch001, center = [0, 0], radius = 1)
|
||||||
|
profile002 = circle(sketch001, center = [2, 2], radius = 1)
|
||||||
|
`
|
||||||
|
const ast = assertParse(circleProfileInVar)
|
||||||
|
const { artifactGraph } = await enginelessExecutor(ast)
|
||||||
|
const artifacts = [...artifactGraph.values()].filter(
|
||||||
|
(a) => a.type === 'path'
|
||||||
|
)
|
||||||
|
if (!artifacts || artifacts.length !== 2) {
|
||||||
|
throw new Error('Artifact not found in the graph')
|
||||||
|
}
|
||||||
|
const selections: Selections = {
|
||||||
|
graphSelections: artifacts.map((artifact) => {
|
||||||
|
return {
|
||||||
|
codeRef: artifact.codeRef,
|
||||||
|
artifact,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
const variableExprs = getVariableExprsFromSelection(selections, ast)
|
||||||
|
if (err(variableExprs)) throw variableExprs
|
||||||
|
|
||||||
|
expect(variableExprs.exprs).toHaveLength(2)
|
||||||
|
if (variableExprs.exprs[0].type !== 'Name') {
|
||||||
|
throw new Error(`Expected Name, got ${variableExprs.exprs[0].type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variableExprs.exprs[1].type !== 'Name') {
|
||||||
|
throw new Error(`Expected Name, got ${variableExprs.exprs[1].type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(variableExprs.exprs[0].name.name).toEqual('profile001')
|
||||||
|
expect(variableExprs.exprs[1].name.name).toEqual('profile002')
|
||||||
|
|
||||||
|
expect(variableExprs.paths).toHaveLength(2)
|
||||||
|
expect(variableExprs.paths[0]).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[1, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', ''],
|
||||||
|
])
|
||||||
|
expect(variableExprs.paths[1]).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[2, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', ''],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the pipe substitution symbol and a variable name in a complex multi profile selection', async () => {
|
||||||
|
const circleProfileInVar = `startSketchOn(XY)
|
||||||
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|
profile002 = circle(sketch001, center = [2, 2], radius = 1)
|
||||||
|
`
|
||||||
|
const ast = assertParse(circleProfileInVar)
|
||||||
|
const { artifactGraph } = await enginelessExecutor(ast)
|
||||||
|
const artifacts = [...artifactGraph.values()].filter(
|
||||||
|
(a) => a.type === 'path'
|
||||||
|
)
|
||||||
|
if (!artifacts || artifacts.length !== 2) {
|
||||||
|
throw new Error('Artifact not found in the graph')
|
||||||
|
}
|
||||||
|
const selections: Selections = {
|
||||||
|
graphSelections: artifacts.map((artifact) => {
|
||||||
|
return {
|
||||||
|
codeRef: artifact.codeRef,
|
||||||
|
artifact,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
const variableExprs = getVariableExprsFromSelection(selections, ast)
|
||||||
|
if (err(variableExprs)) throw variableExprs
|
||||||
|
|
||||||
|
expect(variableExprs.exprs).toHaveLength(2)
|
||||||
|
if (variableExprs.exprs[0].type !== 'PipeSubstitution') {
|
||||||
|
throw new Error(
|
||||||
|
`Expected PipeSubstitution, got ${variableExprs.exprs[0].type}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variableExprs.exprs[1].type !== 'Name') {
|
||||||
|
throw new Error(`Expected Name, got ${variableExprs.exprs[1].type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(variableExprs.exprs[1].name.name).toEqual('profile002')
|
||||||
|
|
||||||
|
expect(variableExprs.paths).toHaveLength(2)
|
||||||
|
expect(variableExprs.paths[0]).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[0, 'index'],
|
||||||
|
['expression', 'ExpressionStatement'],
|
||||||
|
['body', 'PipeExpression'],
|
||||||
|
[1, 'index'],
|
||||||
|
])
|
||||||
|
expect(variableExprs.paths[1]).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[1, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', ''],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -1042,19 +1042,21 @@ export const valueOrVariable = (variable: KclCommandValue) => {
|
|||||||
|
|
||||||
// Go from a selection of sketches to a list of KCL expressions that
|
// Go from a selection of sketches to a list of KCL expressions that
|
||||||
// can be used to create KCL sweep call declarations.
|
// can be used to create KCL sweep call declarations.
|
||||||
export function getSketchExprsFromSelection(
|
export function getVariableExprsFromSelection(
|
||||||
selection: Selections,
|
selection: Selections,
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
nodeToEdit?: PathToNode
|
nodeToEdit?: PathToNode
|
||||||
): Error | Expr[] {
|
): Error | { exprs: Expr[]; paths: PathToNode[] } {
|
||||||
const sketches: Expr[] = selection.graphSelections.flatMap((s) => {
|
const paths: PathToNode[] = []
|
||||||
|
const exprs: Expr[] = []
|
||||||
|
for (const s of selection.graphSelections) {
|
||||||
const sketchVariable = getNodeFromPath<VariableDeclarator>(
|
const sketchVariable = getNodeFromPath<VariableDeclarator>(
|
||||||
ast,
|
ast,
|
||||||
s?.codeRef.pathToNode,
|
s?.codeRef.pathToNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
if (err(sketchVariable)) {
|
if (err(sketchVariable)) {
|
||||||
return []
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sketchVariable.node.id) {
|
if (sketchVariable.node.id) {
|
||||||
@ -1071,22 +1073,27 @@ export function getSketchExprsFromSelection(
|
|||||||
name === result.node.id.name
|
name === result.node.id.name
|
||||||
) {
|
) {
|
||||||
// Pointing to same variable case
|
// Pointing to same variable case
|
||||||
return createPipeSubstitution()
|
paths.push(nodeToEdit)
|
||||||
|
exprs.push(createPipeSubstitution())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Pointing to different variable case
|
// Pointing to different variable case
|
||||||
return createLocalName(name)
|
paths.push(sketchVariable.deepPath)
|
||||||
} else {
|
exprs.push(createLocalName(name))
|
||||||
// No variable case
|
continue
|
||||||
return createPipeSubstitution()
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (sketches.length === 0) {
|
// No variable case
|
||||||
|
paths.push(sketchVariable.deepPath)
|
||||||
|
exprs.push(createPipeSubstitution())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exprs.length === 0) {
|
||||||
return new Error("Couldn't map selections to program references")
|
return new Error("Couldn't map selections to program references")
|
||||||
}
|
}
|
||||||
|
|
||||||
return sketches
|
return { exprs, paths }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go from the sketches argument in a KCL sweep call declaration
|
// Go from the sketches argument in a KCL sweep call declaration
|
||||||
|
|||||||
@ -72,7 +72,7 @@ import {
|
|||||||
addRevolve,
|
addRevolve,
|
||||||
addSweep,
|
addSweep,
|
||||||
getAxisExpressionAndIndex,
|
getAxisExpressionAndIndex,
|
||||||
} from '@src/lang/modifyAst/addSweep'
|
} from '@src/lang/modifyAst/sweeps'
|
||||||
import {
|
import {
|
||||||
applyIntersectFromTargetOperatorSelections,
|
applyIntersectFromTargetOperatorSelections,
|
||||||
applySubtractFromTargetOperatorSelections,
|
applySubtractFromTargetOperatorSelections,
|
||||||
|
|||||||
Reference in New Issue
Block a user