From 472eb2bafe5fbebe441dce3e03e90d3700802205 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 12 Sep 2024 21:45:26 +0200 Subject: [PATCH] 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 21a966b9b0d045607d1943f68329619f41c77f2b. * lint error fix --- src/lang/modifyAst/addFillet.test.ts | 228 +++++++++++++++++++++++++-- src/lang/modifyAst/addFillet.ts | 62 +++++--- src/machines/modelingMachine.ts | 2 + 3 files changed, 249 insertions(+), 43 deletions(-) diff --git a/src/lang/modifyAst/addFillet.test.ts b/src/lang/modifyAst/addFillet.test.ts index 27f4c78947..f23d687371 100644 --- a/src/lang/modifyAst/addFillet.test.ts +++ b/src/lang/modifyAst/addFillet.test.ts @@ -3,7 +3,6 @@ import { recast, initPromise, PathToNode, - Expr, Program, CallExpression, makeDefaultPlanes, @@ -15,6 +14,7 @@ import { getPathToExtrudeForSegmentSelection, hasValidFilletSelection, isTagUsedInFillet, + modifyAstWithFilletAndTag, } from './addFillet' import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst' import { createLiteral } from 'lang/modifyAst' @@ -22,6 +22,7 @@ import { err } from 'lib/trap' import { Selections } from 'lib/selections' import { engineCommandManager, kclManager } from 'lib/singletons' import { VITE_KC_DEV_TOKEN } from 'env' +import { KclCommandValue } from 'lib/commandTypes' beforeAll(async () => { await initPromise @@ -59,11 +60,14 @@ const runGetPathToExtrudeForSegmentSelectionTest = async ( ): CallExpression | PipeExpression | undefined | Error { if (pathToExtrudeNode.length === 0) return undefined // no extrude node - const extrudeNodeResult = getNodeFromPath(ast, pathToExtrudeNode) + const extrudeNodeResult = getNodeFromPath( + ast, + pathToExtrudeNode + ) if (err(extrudeNodeResult)) { return extrudeNodeResult } - return extrudeNodeResult.node as CallExpression | PipeExpression + return extrudeNodeResult.node } function getExpectedExtrudeExpression( ast: Program, @@ -75,21 +79,27 @@ const runGetPathToExtrudeForSegmentSelectionTest = async ( code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length, ] const expedtedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange) - const expedtedExtrudeNodeResult = getNodeFromPath(ast, expedtedExtrudePath) + const expedtedExtrudeNodeResult = getNodeFromPath( + ast, + expedtedExtrudePath + ) if (err(expedtedExtrudeNodeResult)) { return expedtedExtrudeNodeResult } - const expectedExtrudeNode = - expedtedExtrudeNodeResult.node as VariableDeclaration - return expectedExtrudeNode.declarations[0].init as - | CallExpression - | PipeExpression + const expectedExtrudeNode = expedtedExtrudeNodeResult.node + const init = expectedExtrudeNode.declarations[0].init + if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') { + return new Error( + 'Expected extrude expression is not a CallExpression or PipeExpression' + ) + } + return init } // ast const astOrError = parse(code) if (err(astOrError)) return new Error('AST not found') - const ast = astOrError as Program + const ast = astOrError // selection const segmentRange: [number, number] = [ @@ -224,7 +234,7 @@ const runFilletTest = async ( code: string, segmentSnippet: string, extrudeSnippet: string, - radius = createLiteral(5) as Expr, + radius = createLiteral(5), expectedCode: string ) => { const astOrError = parse(code) @@ -232,7 +242,7 @@ const runFilletTest = async ( return new Error('AST not found') } - const ast = astOrError as Program + const ast = astOrError const segmentRange: [number, number] = [ code.indexOf(segmentSnippet), @@ -286,7 +296,7 @@ describe('Testing addFillet', () => { ` const segmentSnippet = `line([60.04, -55.72], %)` const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` - const radius = createLiteral(5) as Expr + const radius = createLiteral(5) const expectedCode = `const sketch001 = startSketchOn('XZ') |> startProfileAt([2.16, 49.67], %) |> line([101.49, 139.93], %) @@ -329,7 +339,7 @@ const extrude001 = extrude(50, sketch001) ` const segmentSnippet = `line([60.04, -55.72], %)` const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` - const radius = createLiteral(5) as Expr + const radius = createLiteral(5) const expectedCode = `const sketch001 = startSketchOn('XZ') |> startProfileAt([2.16, 49.67], %) |> line([101.49, 139.93], %) @@ -372,7 +382,7 @@ const extrude001 = extrude(50, sketch001) ` const segmentSnippet = `line([-87.24, -47.08], %, $seg03)` const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` - const radius = createLiteral(5) as Expr + const radius = createLiteral(5) const expectedCode = `const sketch001 = startSketchOn('XZ') |> startProfileAt([2.16, 49.67], %) |> line([101.49, 139.93], %) @@ -414,7 +424,7 @@ const extrude001 = extrude(50, sketch001) |> fillet({ radius: 10, tags: [seg03] }, %)` const segmentSnippet = `line([60.04, -55.72], %)` const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` - const radius = createLiteral(5) as Expr + const radius = createLiteral(5) const expectedCode = `const sketch001 = startSketchOn('XZ') |> startProfileAt([2.16, 49.67], %) |> line([101.49, 139.93], %) @@ -439,6 +449,190 @@ const extrude001 = extrude(50, sketch001) }) }) +const runModifyAstWithFilletAndTagTest = async ( + code: string, + selectionSnippets: Array, + 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', () => { const code = `const sketch001 = startSketchOn('XZ') |> startProfileAt([7.72, 4.13], %) @@ -526,7 +720,7 @@ describe('Testing button states', () => { if (err(astOrError)) { return new Error('AST not found') } - const ast = astOrError as Program + const ast = astOrError // selectionRanges const range: [number, number] = segmentSnippet diff --git a/src/lang/modifyAst/addFillet.ts b/src/lang/modifyAst/addFillet.ts index 5855172390..53b0980616 100644 --- a/src/lang/modifyAst/addFillet.ts +++ b/src/lang/modifyAst/addFillet.ts @@ -44,14 +44,15 @@ import { kclManager, engineCommandManager, editorManager } from 'lib/singletons' */ export function applyFilletToSelection( + ast: Program, selection: Selections, radius: KclCommandValue ): void | Error { - // 1. get AST - let ast = kclManager.ast + // 1. clone ast + let clonedAst = structuredClone(ast) // 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 const { modifiedAst, pathToFilletNode } = result @@ -60,7 +61,7 @@ export function applyFilletToSelection( updateAstAndFocus(modifiedAst, pathToFilletNode) } -function modifyAstWithFilletAndTag( +export function modifyAstWithFilletAndTag( ast: Program, selection: Selections, radius: KclCommandValue @@ -68,32 +69,41 @@ function modifyAstWithFilletAndTag( const astResult = insertRadiusIntoAst(ast, radius) if (err(astResult)) return astResult - // 2. get path const programMemory = kclManager.programMemory const artifactGraph = engineCommandManager.artifactGraph - const getPathToExtrudeForSegmentSelectionResult = - getPathToExtrudeForSegmentSelection( - ast, - selection, - programMemory, - artifactGraph - ) - if (err(getPathToExtrudeForSegmentSelectionResult)) - return getPathToExtrudeForSegmentSelectionResult - const { pathToSegmentNode, pathToExtrudeNode } = - getPathToExtrudeForSegmentSelectionResult - // 3. add fillet - const addFilletResult = addFillet( - ast, - pathToSegmentNode, - pathToExtrudeNode, - 'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst - ) - if (trap(addFilletResult)) return addFilletResult - const { modifiedAst, pathToFilletNode } = addFilletResult + let clonedAst = structuredClone(ast) + let lastPathToFilletNode: PathToNode = [] - return { modifiedAst, pathToFilletNode } + for (const selectionRange of selection.codeBasedSelections) { + 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 (trap(addFilletResult)) return addFilletResult + const { modifiedAst, pathToFilletNode } = addFilletResult + clonedAst = modifiedAst + lastPathToFilletNode = pathToFilletNode + } + return { modifiedAst: clonedAst, pathToFilletNode: lastPathToFilletNode } } function insertRadiusIntoAst( diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 7bed9d3c0b..b712f771e8 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -602,10 +602,12 @@ export const modelingMachine = setup({ if (!event.data) return // Extract inputs + const ast = kclManager.ast const { selection, radius } = event.data // Apply fillet to selection const applyFilletToSelectionResult = applyFilletToSelection( + ast, selection, radius )