AST mod for Multi-Edge Fillets (#3724)

* test

* test + selection loop

* wipe as

* multi body multi fillet test

* make eslint happy again

* as fatality

* Revert "make eslint happy again"

This reverts commit 21a966b9b0.

* lint error fix
This commit is contained in:
max
2024-09-12 21:45:26 +02:00
committed by GitHub
parent 88216d4c76
commit 472eb2bafe
3 changed files with 250 additions and 44 deletions

View File

@ -3,7 +3,6 @@ import {
recast, recast,
initPromise, initPromise,
PathToNode, PathToNode,
Expr,
Program, Program,
CallExpression, CallExpression,
makeDefaultPlanes, makeDefaultPlanes,
@ -15,6 +14,7 @@ import {
getPathToExtrudeForSegmentSelection, getPathToExtrudeForSegmentSelection,
hasValidFilletSelection, hasValidFilletSelection,
isTagUsedInFillet, isTagUsedInFillet,
modifyAstWithFilletAndTag,
} from './addFillet' } from './addFillet'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst' import { createLiteral } from 'lang/modifyAst'
@ -22,6 +22,7 @@ import { err } from 'lib/trap'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_DEV_TOKEN } from 'env'
import { KclCommandValue } from 'lib/commandTypes'
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
@ -59,11 +60,14 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
): CallExpression | PipeExpression | undefined | Error { ): CallExpression | PipeExpression | undefined | Error {
if (pathToExtrudeNode.length === 0) return undefined // no extrude node if (pathToExtrudeNode.length === 0) return undefined // no extrude node
const extrudeNodeResult = getNodeFromPath(ast, pathToExtrudeNode) const extrudeNodeResult = getNodeFromPath<CallExpression>(
ast,
pathToExtrudeNode
)
if (err(extrudeNodeResult)) { if (err(extrudeNodeResult)) {
return extrudeNodeResult return extrudeNodeResult
} }
return extrudeNodeResult.node as CallExpression | PipeExpression return extrudeNodeResult.node
} }
function getExpectedExtrudeExpression( function getExpectedExtrudeExpression(
ast: Program, ast: Program,
@ -75,21 +79,27 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length, code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
] ]
const expedtedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange) const expedtedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
const expedtedExtrudeNodeResult = getNodeFromPath(ast, expedtedExtrudePath) const expedtedExtrudeNodeResult = getNodeFromPath<VariableDeclaration>(
ast,
expedtedExtrudePath
)
if (err(expedtedExtrudeNodeResult)) { if (err(expedtedExtrudeNodeResult)) {
return expedtedExtrudeNodeResult return expedtedExtrudeNodeResult
} }
const expectedExtrudeNode = const expectedExtrudeNode = expedtedExtrudeNodeResult.node
expedtedExtrudeNodeResult.node as VariableDeclaration const init = expectedExtrudeNode.declarations[0].init
return expectedExtrudeNode.declarations[0].init as if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') {
| CallExpression return new Error(
| PipeExpression 'Expected extrude expression is not a CallExpression or PipeExpression'
)
}
return init
} }
// ast // ast
const astOrError = parse(code) const astOrError = parse(code)
if (err(astOrError)) return new Error('AST not found') if (err(astOrError)) return new Error('AST not found')
const ast = astOrError as Program const ast = astOrError
// selection // selection
const segmentRange: [number, number] = [ const segmentRange: [number, number] = [
@ -224,7 +234,7 @@ const runFilletTest = async (
code: string, code: string,
segmentSnippet: string, segmentSnippet: string,
extrudeSnippet: string, extrudeSnippet: string,
radius = createLiteral(5) as Expr, radius = createLiteral(5),
expectedCode: string expectedCode: string
) => { ) => {
const astOrError = parse(code) const astOrError = parse(code)
@ -232,7 +242,7 @@ const runFilletTest = async (
return new Error('AST not found') return new Error('AST not found')
} }
const ast = astOrError as Program const ast = astOrError
const segmentRange: [number, number] = [ const segmentRange: [number, number] = [
code.indexOf(segmentSnippet), code.indexOf(segmentSnippet),
@ -286,7 +296,7 @@ describe('Testing addFillet', () => {
` `
const segmentSnippet = `line([60.04, -55.72], %)` const segmentSnippet = `line([60.04, -55.72], %)`
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
const radius = createLiteral(5) as Expr const radius = createLiteral(5)
const expectedCode = `const sketch001 = startSketchOn('XZ') const expectedCode = `const sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %) |> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %) |> line([101.49, 139.93], %)
@ -329,7 +339,7 @@ const extrude001 = extrude(50, sketch001)
` `
const segmentSnippet = `line([60.04, -55.72], %)` const segmentSnippet = `line([60.04, -55.72], %)`
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
const radius = createLiteral(5) as Expr const radius = createLiteral(5)
const expectedCode = `const sketch001 = startSketchOn('XZ') const expectedCode = `const sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %) |> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %) |> line([101.49, 139.93], %)
@ -372,7 +382,7 @@ const extrude001 = extrude(50, sketch001)
` `
const segmentSnippet = `line([-87.24, -47.08], %, $seg03)` const segmentSnippet = `line([-87.24, -47.08], %, $seg03)`
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
const radius = createLiteral(5) as Expr const radius = createLiteral(5)
const expectedCode = `const sketch001 = startSketchOn('XZ') const expectedCode = `const sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %) |> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %) |> line([101.49, 139.93], %)
@ -414,7 +424,7 @@ const extrude001 = extrude(50, sketch001)
|> fillet({ radius: 10, tags: [seg03] }, %)` |> fillet({ radius: 10, tags: [seg03] }, %)`
const segmentSnippet = `line([60.04, -55.72], %)` const segmentSnippet = `line([60.04, -55.72], %)`
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
const radius = createLiteral(5) as Expr const radius = createLiteral(5)
const expectedCode = `const sketch001 = startSketchOn('XZ') const expectedCode = `const sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %) |> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %) |> line([101.49, 139.93], %)
@ -439,6 +449,190 @@ const extrude001 = extrude(50, sketch001)
}) })
}) })
const runModifyAstWithFilletAndTagTest = async (
code: string,
selectionSnippets: Array<string>,
radiusValue: number,
expectedCode: string
) => {
// ast
const astOrError = parse(code)
if (err(astOrError)) {
return new Error('AST not found')
}
const ast = astOrError
// selection
const segmentRanges: Array<[number, number]> = selectionSnippets.map(
(selectionSnippet) => [
code.indexOf(selectionSnippet),
code.indexOf(selectionSnippet) + selectionSnippet.length,
]
)
const selection: Selections = {
codeBasedSelections: segmentRanges.map((segmentRange) => ({
range: segmentRange,
type: 'default',
})),
otherSelections: [],
}
// radius
const radius: KclCommandValue = {
valueAst: createLiteral(radiusValue),
valueText: radiusValue.toString(),
valueCalculated: radiusValue.toString(),
}
// programMemory and artifactGraph
await kclManager.executeAst({ ast })
// apply fillet to selection
const result = modifyAstWithFilletAndTag(ast, selection, radius)
if (err(result)) {
return result
}
const { modifiedAst } = result
const newCode = recast(modifiedAst)
expect(newCode).toContain(expectedCode)
}
describe('Testing applyFilletToSelection', () => {
it('should add a fillet to a specific segment after extrusion', async () => {
const code = `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([0, -20], %)']
const radiusValue = 3
const expectedCode = `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)`
await runModifyAstWithFilletAndTagTest(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet to the 2 segments of a single extrusion', async () => {
const code = `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([20, 0], %)', 'line([-20, 0], %)']
const radiusValue = 3
const expectedCode = `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
|> line([-20, 0], %, $seg02)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)
|> fillet({ radius: 3, tags: [seg02] }, %)`
await runModifyAstWithFilletAndTagTest(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet when the extrude variable previously had an fillet', async () => {
const code = `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %, $seg01)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)` // <--- one fillet already there on input code
const segmentSnippets = ['line([20, 0], %)']
const radiusValue = 3
const expectedCode = `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg02)
|> line([0, -20], %)
|> line([-20, 0], %, $seg01)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)
|> fillet({ radius: 3, tags: [seg02] }, %)` // <-- able to add a new one
await runModifyAstWithFilletAndTagTest(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add the fillets to 2 bodies', async () => {
const code = `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)
const sketch002 = startSketchOn('XY')
|> startProfileAt([30, 10], %)
|> line([15, 0], %)
|> line([0, -15], %)
|> line([-15, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude002 = extrude(-25, sketch002)` // <--- body 2
const segmentSnippets = ['line([0, -20], %)', 'line([0, -15], %)']
const radiusValue = 3
const expectedCode = `const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)
const sketch002 = startSketchOn('XY')
|> startProfileAt([30, 10], %)
|> line([15, 0], %)
|> line([0, -15], %, $seg02)
|> line([-15, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude002 = extrude(-25, sketch002)
|> fillet({ radius: 3, tags: [seg02] }, %)` // <-- able to add a new one
await runModifyAstWithFilletAndTagTest(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
})
describe('Testing isTagUsedInFillet', () => { describe('Testing isTagUsedInFillet', () => {
const code = `const sketch001 = startSketchOn('XZ') const code = `const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.72, 4.13], %) |> startProfileAt([7.72, 4.13], %)
@ -526,7 +720,7 @@ describe('Testing button states', () => {
if (err(astOrError)) { if (err(astOrError)) {
return new Error('AST not found') return new Error('AST not found')
} }
const ast = astOrError as Program const ast = astOrError
// selectionRanges // selectionRanges
const range: [number, number] = segmentSnippet const range: [number, number] = segmentSnippet

View File

@ -44,14 +44,15 @@ import { kclManager, engineCommandManager, editorManager } from 'lib/singletons'
*/ */
export function applyFilletToSelection( export function applyFilletToSelection(
ast: Program,
selection: Selections, selection: Selections,
radius: KclCommandValue radius: KclCommandValue
): void | Error { ): void | Error {
// 1. get AST // 1. clone ast
let ast = kclManager.ast let clonedAst = structuredClone(ast)
// 2. modify ast clone with fillet and tag // 2. modify ast clone with fillet and tag
const result = modifyAstWithFilletAndTag(ast, selection, radius) const result = modifyAstWithFilletAndTag(clonedAst, selection, radius)
if (err(result)) return result if (err(result)) return result
const { modifiedAst, pathToFilletNode } = result const { modifiedAst, pathToFilletNode } = result
@ -60,7 +61,7 @@ export function applyFilletToSelection(
updateAstAndFocus(modifiedAst, pathToFilletNode) updateAstAndFocus(modifiedAst, pathToFilletNode)
} }
function modifyAstWithFilletAndTag( export function modifyAstWithFilletAndTag(
ast: Program, ast: Program,
selection: Selections, selection: Selections,
radius: KclCommandValue radius: KclCommandValue
@ -68,32 +69,41 @@ function modifyAstWithFilletAndTag(
const astResult = insertRadiusIntoAst(ast, radius) const astResult = insertRadiusIntoAst(ast, radius)
if (err(astResult)) return astResult if (err(astResult)) return astResult
// 2. get path
const programMemory = kclManager.programMemory const programMemory = kclManager.programMemory
const artifactGraph = engineCommandManager.artifactGraph const artifactGraph = engineCommandManager.artifactGraph
const getPathToExtrudeForSegmentSelectionResult =
getPathToExtrudeForSegmentSelection( let clonedAst = structuredClone(ast)
ast, let lastPathToFilletNode: PathToNode = []
selection,
programMemory, for (const selectionRange of selection.codeBasedSelections) {
artifactGraph const singleSelection = {
codeBasedSelections: [selectionRange],
otherSelections: [],
}
const getPathToExtrudeForSegmentSelectionResult =
getPathToExtrudeForSegmentSelection(
clonedAst,
singleSelection,
programMemory,
artifactGraph
)
if (err(getPathToExtrudeForSegmentSelectionResult))
return getPathToExtrudeForSegmentSelectionResult
const { pathToSegmentNode, pathToExtrudeNode } =
getPathToExtrudeForSegmentSelectionResult
const addFilletResult = addFillet(
clonedAst,
pathToSegmentNode,
pathToExtrudeNode,
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
) )
if (err(getPathToExtrudeForSegmentSelectionResult)) if (trap(addFilletResult)) return addFilletResult
return getPathToExtrudeForSegmentSelectionResult const { modifiedAst, pathToFilletNode } = addFilletResult
const { pathToSegmentNode, pathToExtrudeNode } = clonedAst = modifiedAst
getPathToExtrudeForSegmentSelectionResult lastPathToFilletNode = pathToFilletNode
}
// 3. add fillet return { modifiedAst: clonedAst, pathToFilletNode: lastPathToFilletNode }
const addFilletResult = addFillet(
ast,
pathToSegmentNode,
pathToExtrudeNode,
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
)
if (trap(addFilletResult)) return addFilletResult
const { modifiedAst, pathToFilletNode } = addFilletResult
return { modifiedAst, pathToFilletNode }
} }
function insertRadiusIntoAst( function insertRadiusIntoAst(

View File

@ -602,10 +602,12 @@ export const modelingMachine = setup({
if (!event.data) return if (!event.data) return
// Extract inputs // Extract inputs
const ast = kclManager.ast
const { selection, radius } = event.data const { selection, radius } = event.data
// Apply fillet to selection // Apply fillet to selection
const applyFilletToSelectionResult = applyFilletToSelection( const applyFilletToSelectionResult = applyFilletToSelection(
ast,
selection, selection,
radius radius
) )