Refactor Fillet Logic to Combine Multiple Tags Within the Same Fillet Expression (#4058)

* combine tags + tests

* delete getFilletTag function

* fix playwright test

* make eslint happy

* delete missed 'const'

* delete const rev2
This commit is contained in:
max
2024-10-03 11:14:02 +02:00
committed by GitHub
parent c84c0b0fef
commit 46d335f916
4 changed files with 226 additions and 457 deletions

View File

@ -80,9 +80,11 @@ extrude001 = extrude(-10, sketch001)`
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Fillet' }).click() await page.getByRole('button', { name: 'Fillet' }).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.keyboard.press('Enter') await page.keyboard.press('Enter') // skip selection
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.keyboard.press('Enter') await page.keyboard.press('Enter') // accept default radius
await page.waitForTimeout(100)
await page.keyboard.press('Enter') // submit
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toContainText( await expect(page.locator('.cm-activeLine')).toContainText(
`fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)` `fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)`

View File

@ -10,11 +10,10 @@ import {
VariableDeclarator, VariableDeclarator,
} from '../wasm' } from '../wasm'
import { import {
addFillet,
getPathToExtrudeForSegmentSelection, getPathToExtrudeForSegmentSelection,
hasValidFilletSelection, hasValidFilletSelection,
isTagUsedInFillet, isTagUsedInFillet,
modifyAstWithFilletAndTag, modifyAstCloneWithFilletAndTag,
} from './addFillet' } from './addFillet'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst' import { createLiteral } from 'lang/modifyAst'
@ -116,16 +115,14 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
otherSelections: [], otherSelections: [],
} }
// programMemory and artifactGraph // executeAst and artifactGraph
await kclManager.executeAst({ ast }) await kclManager.executeAst({ ast })
const programMemory = kclManager.programMemory
const artifactGraph = engineCommandManager.artifactGraph const artifactGraph = engineCommandManager.artifactGraph
// get extrude expression // get extrude expression
const pathResult = getPathToExtrudeForSegmentSelection( const pathResult = getPathToExtrudeForSegmentSelection(
ast, ast,
selection, selection,
programMemory,
artifactGraph artifactGraph
) )
if (err(pathResult)) return pathResult if (err(pathResult)) return pathResult
@ -230,226 +227,7 @@ extrude003 = extrude(-15, sketch003)`
}) })
}) })
const runFilletTest = async ( const runModifyAstCloneWithFilletAndTag = async (
code: string,
segmentSnippet: string,
extrudeSnippet: string,
radius = createLiteral(5),
expectedCode: string
) => {
const astOrError = parse(code)
if (err(astOrError)) {
return new Error('AST not found')
}
const ast = astOrError
const segmentRange: [number, number] = [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
]
const pathToSegmentNode: PathToNode = getNodePathFromSourceRange(
ast,
segmentRange
)
const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
]
const pathToExtrudeNode: PathToNode = getNodePathFromSourceRange(
ast,
extrudeRange
)
if (err(pathToExtrudeNode)) {
return new Error('Path to extrude node not found')
}
const result = addFillet(ast, pathToSegmentNode, pathToExtrudeNode, radius)
if (err(result)) {
return result
}
const { modifiedAst } = result
const newCode = recast(modifiedAst)
expect(newCode).toContain(expectedCode)
}
describe('Testing addFillet', () => {
/**
* 1. Ideal Case
*/
it('should add a fillet to a specific segment after extrusion, clean', async () => {
const code = `
sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %)
|> line([60.04, -55.72], %)
|> line([1.29, -115.74], %)
|> line([-87.24, -47.08], %)
|> tangentialArcTo([56.15, -94.58], %)
|> tangentialArcTo([14.68, -104.52], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
`
const segmentSnippet = `line([60.04, -55.72], %)`
const extrudeSnippet = `extrude001 = extrude(50, sketch001)`
const radius = createLiteral(5)
const expectedCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %)
|> line([60.04, -55.72], %, $seg01)
|> line([1.29, -115.74], %)
|> line([-87.24, -47.08], %)
|> tangentialArcTo([56.15, -94.58], %)
|> tangentialArcTo([14.68, -104.52], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
|> fillet({ radius: 5, tags: [seg01] }, %)`
await runFilletTest(
code,
segmentSnippet,
extrudeSnippet,
radius,
expectedCode
)
})
/**
* 2. Case of existing tag in the other line
*/
it('should add a fillet to a specific segment after extrusion with existing tag in any other line', async () => {
const code = `
sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %)
|> line([60.04, -55.72], %)
|> line([1.29, -115.74], %)
|> line([-87.24, -47.08], %, $seg01)
|> tangentialArcTo([56.15, -94.58], %)
|> tangentialArcTo([14.68, -104.52], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
`
const segmentSnippet = `line([60.04, -55.72], %)`
const extrudeSnippet = `extrude001 = extrude(50, sketch001)`
const radius = createLiteral(5)
const expectedCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %)
|> line([60.04, -55.72], %, $seg02)
|> line([1.29, -115.74], %)
|> line([-87.24, -47.08], %, $seg01)
|> tangentialArcTo([56.15, -94.58], %)
|> tangentialArcTo([14.68, -104.52], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
|> fillet({ radius: 5, tags: [seg02] }, %)`
await runFilletTest(
code,
segmentSnippet,
extrudeSnippet,
radius,
expectedCode
)
})
/**
* 3. Case of existing tag in the fillet line
*/
it('should add a fillet to a specific segment after extrusion with existing tag in that exact line', async () => {
const code = `
sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %)
|> line([60.04, -55.72], %)
|> line([1.29, -115.74], %)
|> line([-87.24, -47.08], %, $seg03)
|> tangentialArcTo([56.15, -94.58], %)
|> tangentialArcTo([14.68, -104.52], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
`
const segmentSnippet = `line([-87.24, -47.08], %, $seg03)`
const extrudeSnippet = `extrude001 = extrude(50, sketch001)`
const radius = createLiteral(5)
const expectedCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %)
|> line([60.04, -55.72], %)
|> line([1.29, -115.74], %)
|> line([-87.24, -47.08], %, $seg03)
|> tangentialArcTo([56.15, -94.58], %)
|> tangentialArcTo([14.68, -104.52], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
|> fillet({ radius: 5, tags: [seg03] }, %)`
await runFilletTest(
code,
segmentSnippet,
extrudeSnippet,
radius,
expectedCode
)
})
/**
* 4. Case of existing fillet on some other segment
*/
it('should add another fillet after the existing fillet', async () => {
const code = `sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %)
|> line([60.04, -55.72], %)
|> line([1.29, -115.74], %)
|> line([-87.24, -47.08], %, $seg03)
|> tangentialArcTo([56.15, -94.58], %)
|> tangentialArcTo([14.68, -104.52], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
|> fillet({ radius: 10, tags: [seg03] }, %)`
const segmentSnippet = `line([60.04, -55.72], %)`
const extrudeSnippet = `extrude001 = extrude(50, sketch001)`
const radius = createLiteral(5)
const expectedCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([2.16, 49.67], %)
|> line([101.49, 139.93], %)
|> line([60.04, -55.72], %, $seg01)
|> line([1.29, -115.74], %)
|> line([-87.24, -47.08], %, $seg03)
|> tangentialArcTo([56.15, -94.58], %)
|> tangentialArcTo([14.68, -104.52], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
|> fillet({ radius: 10, tags: [seg03] }, %)
|> fillet({ radius: 5, tags: [seg01] }, %)`
await runFilletTest(
code,
segmentSnippet,
extrudeSnippet,
radius,
expectedCode
)
})
})
const runModifyAstWithFilletAndTagTest = async (
code: string, code: string,
selectionSnippets: Array<string>, selectionSnippets: Array<string>,
radiusValue: number, radiusValue: number,
@ -484,11 +262,11 @@ const runModifyAstWithFilletAndTagTest = async (
valueCalculated: radiusValue.toString(), valueCalculated: radiusValue.toString(),
} }
// programMemory and artifactGraph // executeAst
await kclManager.executeAst({ ast }) await kclManager.executeAst({ ast })
// apply fillet to selection // apply fillet to selection
const result = modifyAstWithFilletAndTag(ast, selection, radius) const result = modifyAstCloneWithFilletAndTag(ast, selection, radius)
if (err(result)) { if (err(result)) {
return result return result
} }
@ -499,7 +277,7 @@ const runModifyAstWithFilletAndTagTest = async (
expect(newCode).toContain(expectedCode) expect(newCode).toContain(expectedCode)
} }
describe('Testing applyFilletToSelection', () => { describe('Testing applyFilletToSelection', () => {
it('should add a fillet to a specific segment after extrusion', async () => { it('should add a fillet to a specific segment', async () => {
const code = `sketch001 = startSketchOn('XY') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %)
@ -520,14 +298,100 @@ extrude001 = extrude(-15, sketch001)`
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)` |> fillet({ radius: 3, tags: [seg01] }, %)`
await runModifyAstWithFilletAndTagTest( await runModifyAstCloneWithFilletAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, radiusValue,
expectedCode expectedCode
) )
}) })
it('should add a fillet to the 2 segments of a single extrusion', async () => { it('should add a fillet to an already tagged segment', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([0, -20], %, $seg01)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet with existing tag on other segment', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([-20, 0], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
|> line([-20, 0], %, $seg02)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet with existing fillet on other segment', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 5, tags: [seg01] }, %)`
const segmentSnippets = ['line([-20, 0], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
|> line([0, -20], %)
|> line([-20, 0], %, $seg02)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 5, tags: [seg01] }, %)
|> fillet({ radius: 3, tags: [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet to two segments of a single extrusion', async () => {
const code = `sketch001 = startSketchOn('XY') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %)
@ -546,47 +410,16 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %) |> fillet({ radius: 3, tags: [seg01, seg02] }, %)`
|> fillet({ radius: 3, tags: [seg02] }, %)`
await runModifyAstWithFilletAndTagTest( await runModifyAstCloneWithFilletAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, radiusValue,
expectedCode expectedCode
) )
}) })
it('should add a fillet when the extrude variable previously had an fillet', async () => { it('should add fillets to two bodies', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %, $seg01)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
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 = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg02)
|> line([0, -20], %)
|> line([-20, 0], %, $seg01)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
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 = `sketch001 = startSketchOn('XY') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %)
@ -603,28 +436,32 @@ sketch002 = startSketchOn('XY')
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude002 = extrude(-25, sketch002)` // <--- body 2 extrude002 = extrude(-25, sketch002)` // <--- body 2
const segmentSnippets = ['line([0, -20], %)', 'line([0, -15], %)'] const segmentSnippets = [
'line([20, 0], %)',
'line([-20, 0], %)',
'line([0, -15], %)',
]
const radiusValue = 3 const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY') const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %, $seg01)
|> line([0, -20], %, $seg01) |> line([0, -20], %)
|> line([-20, 0], %) |> line([-20, 0], %, $seg02)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %) |> fillet({ radius: 3, tags: [seg01, seg02] }, %)
sketch002 = startSketchOn('XY') sketch002 = startSketchOn('XY')
|> startProfileAt([30, 10], %) |> startProfileAt([30, 10], %)
|> line([15, 0], %) |> line([15, 0], %)
|> line([0, -15], %, $seg02) |> line([0, -15], %, $seg03)
|> line([-15, 0], %) |> line([-15, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude002 = extrude(-25, sketch002) extrude002 = extrude(-25, sketch002)
|> fillet({ radius: 3, tags: [seg02] }, %)` // <-- able to add a new one |> fillet({ radius: 3, tags: [seg03] }, %)` // <-- able to add a new one
await runModifyAstWithFilletAndTagTest( await runModifyAstCloneWithFilletAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, radiusValue,

View File

@ -1,18 +1,14 @@
import { import {
ArrayExpression,
CallExpression, CallExpression,
ObjectExpression, ObjectExpression,
PathToNode, PathToNode,
Program, Program,
ProgramMemory,
Expr,
VariableDeclaration, VariableDeclaration,
VariableDeclarator, VariableDeclarator,
sketchFromKclValue, sketchFromKclValue,
} from '../wasm' } from '../wasm'
import { import {
createCallExpressionStdLib, createCallExpressionStdLib,
createLiteral,
createPipeSubstitution, createPipeSubstitution,
createObjectExpression, createObjectExpression,
createArrayExpression, createArrayExpression,
@ -39,70 +35,142 @@ import {
} from 'lang/std/artifactGraph' } from 'lang/std/artifactGraph'
import { kclManager, engineCommandManager, editorManager } from 'lib/singletons' import { kclManager, engineCommandManager, editorManager } from 'lib/singletons'
/** // Apply Fillet To Selection
* Apply Fillet To Selection
*/
export function applyFilletToSelection( export function applyFilletToSelection(
ast: Program, ast: Program,
selection: Selections, selection: Selections,
radius: KclCommandValue radius: KclCommandValue
): void | Error { ): void | Error {
// 1. clone ast // 1. clone and modify with fillet and tag
let clonedAst = structuredClone(ast) const result = modifyAstCloneWithFilletAndTag(ast, selection, radius)
// 2. modify ast clone with fillet and tag
const result = modifyAstWithFilletAndTag(clonedAst, selection, radius)
if (err(result)) return result if (err(result)) return result
const { modifiedAst, pathToFilletNode } = result const { modifiedAst, pathToFilletNode } = result
// 3. update ast // 2. update ast
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
updateAstAndFocus(modifiedAst, pathToFilletNode) updateAstAndFocus(modifiedAst, pathToFilletNode)
} }
export function modifyAstWithFilletAndTag( export function modifyAstCloneWithFilletAndTag(
ast: Program, ast: Program,
selection: Selections, selection: Selections,
radius: KclCommandValue radius: KclCommandValue
): { modifiedAst: Program; pathToFilletNode: Array<PathToNode> } | Error { ): { modifiedAst: Program; pathToFilletNode: Array<PathToNode> } | Error {
const astResult = insertRadiusIntoAst(ast, radius)
if (err(astResult)) return astResult
const programMemory = kclManager.programMemory
const artifactGraph = engineCommandManager.artifactGraph
let clonedAst = structuredClone(ast) let clonedAst = structuredClone(ast)
const clonedAstForGetExtrude = structuredClone(ast) const clonedAstForGetExtrude = structuredClone(ast)
let pathToFilletNodes: Array<PathToNode> = []
const astResult = insertRadiusIntoAst(clonedAst, radius)
if (err(astResult)) return astResult
const artifactGraph = engineCommandManager.artifactGraph
// Step 1: modify ast with tags and group them by extrude nodes (bodies)
const extrudeToTagsMap: Map<PathToNode, string[]> = new Map()
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
for (const selectionRange of selection.codeBasedSelections) { for (const selectionRange of selection.codeBasedSelections) {
const singleSelection = { const singleSelection = {
codeBasedSelections: [selectionRange], codeBasedSelections: [selectionRange],
otherSelections: [], otherSelections: [],
} }
const getPathToExtrudeForSegmentSelectionResult =
getPathToExtrudeForSegmentSelection( const result = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude, clonedAstForGetExtrude,
singleSelection, singleSelection,
programMemory,
artifactGraph artifactGraph
) )
if (err(getPathToExtrudeForSegmentSelectionResult)) if (err(result)) return result
return getPathToExtrudeForSegmentSelectionResult const { pathToSegmentNode, pathToExtrudeNode } = result
const { pathToSegmentNode, pathToExtrudeNode } =
getPathToExtrudeForSegmentSelectionResult
const addFilletResult = addFillet( const tagResult = mutateAstWithTagForSketchSegment(
clonedAst, clonedAst,
pathToSegmentNode, pathToSegmentNode
pathToExtrudeNode, )
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst if (err(tagResult)) return tagResult
const { tag } = tagResult
// Group tags by their corresponding extrude node
const extrudeKey = JSON.stringify(pathToExtrudeNode)
if (lookupMap.has(extrudeKey)) {
const existingPath = lookupMap.get(extrudeKey)
if (!existingPath) return new Error('Path to extrude node not found.')
extrudeToTagsMap.get(existingPath)?.push(tag)
} else {
lookupMap.set(extrudeKey, pathToExtrudeNode)
extrudeToTagsMap.set(pathToExtrudeNode, [tag])
}
}
// Step 2: Apply fillet(s) for each extrude node (body)
let pathToFilletNodes: Array<PathToNode> = []
for (const [pathToExtrudeNode, tags] of extrudeToTagsMap.entries()) {
// Create a fillet expression with multiple tags
const radiusValue =
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
const filletCall = createCallExpressionStdLib('fillet', [
createObjectExpression({
radius: radiusValue,
tags: createArrayExpression(tags.map((tag) => createIdentifier(tag))),
}),
createPipeSubstitution(),
])
// Locate the extrude call
const locatedExtrudeDeclarator = locateExtrudeDeclarator(
clonedAst,
pathToExtrudeNode
)
if (err(locatedExtrudeDeclarator)) return locatedExtrudeDeclarator
const { extrudeDeclarator } = locatedExtrudeDeclarator
// Modify the extrude expression to include this fillet expression
// CallExpression - no fillet
// PipeExpression - fillet exists
let pathToFilletNode: PathToNode = []
if (extrudeDeclarator.init.type === 'CallExpression') {
// 1. case when no fillet exists
// modify ast with new fillet call by mutating the extrude node
extrudeDeclarator.init = createPipeExpression([
extrudeDeclarator.init,
filletCall,
])
// get path to the fillet node
pathToFilletNode = getPathToNodeOfFilletLiteral(
pathToExtrudeNode,
extrudeDeclarator,
tags[0]
) )
if (trap(addFilletResult)) return addFilletResult
const { modifiedAst, pathToFilletNode } = addFilletResult
clonedAst = modifiedAst
pathToFilletNodes.push(pathToFilletNode) pathToFilletNodes.push(pathToFilletNode)
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
// 2. case when fillet exists
const existingFilletCall = extrudeDeclarator.init.body.find((node) => {
return node.type === 'CallExpression' && node.callee.name === 'fillet'
})
if (!existingFilletCall || existingFilletCall.type !== 'CallExpression') {
return new Error('Fillet CallExpression not found.')
}
// mutate the extrude node with the new fillet call
extrudeDeclarator.init.body.push(filletCall)
// get path to the fillet node
pathToFilletNode = getPathToNodeOfFilletLiteral(
pathToExtrudeNode,
extrudeDeclarator,
tags[0]
)
pathToFilletNodes.push(pathToFilletNode)
} else {
return new Error('Unsupported extrude type.')
}
} }
return { modifiedAst: clonedAst, pathToFilletNode: pathToFilletNodes } return { modifiedAst: clonedAst, pathToFilletNode: pathToFilletNodes }
} }
@ -131,7 +199,6 @@ function insertRadiusIntoAst(
export function getPathToExtrudeForSegmentSelection( export function getPathToExtrudeForSegmentSelection(
ast: Program, ast: Program,
selection: Selections, selection: Selections,
programMemory: ProgramMemory,
artifactGraph: ArtifactGraph artifactGraph: ArtifactGraph
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error { ): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
const pathToSegmentNode = getNodePathFromSourceRange( const pathToSegmentNode = getNodePathFromSourceRange(
@ -177,40 +244,6 @@ async function updateAstAndFocus(
} }
} }
/**
* Add Fillet
*/
export function addFillet(
ast: Program,
pathToSegmentNode: PathToNode,
pathToExtrudeNode: PathToNode,
radius: Expr = createLiteral(5)
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
// Clone AST to ensure safe mutations
const astClone = structuredClone(ast)
// Modify AST clone : TAG the sketch segment and retrieve tag
const segmentResult = mutateAstWithTagForSketchSegment(
astClone,
pathToSegmentNode
)
if (err(segmentResult)) return segmentResult
const { tag } = segmentResult
// Modify AST clone : Insert FILLET node and retrieve path to fillet
const filletResult = mutateAstWithFilletNode(
astClone,
pathToExtrudeNode,
radius,
tag
)
if (err(filletResult)) return filletResult
const { pathToFilletNode } = filletResult
return { modifiedAst: astClone, pathToFilletNode }
}
function mutateAstWithTagForSketchSegment( function mutateAstWithTagForSketchSegment(
astClone: Program, astClone: Program,
pathToSegmentNode: PathToNode pathToSegmentNode: PathToNode
@ -243,91 +276,6 @@ function mutateAstWithTagForSketchSegment(
return { modifiedAst: astClone, tag } return { modifiedAst: astClone, tag }
} }
function mutateAstWithFilletNode(
astClone: Program,
pathToExtrudeNode: PathToNode,
radius: Expr,
tag: string
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
// Locate the extrude call
const locatedExtrudeDeclarator = locateExtrudeDeclarator(
astClone,
pathToExtrudeNode
)
if (err(locatedExtrudeDeclarator)) return locatedExtrudeDeclarator
const { extrudeDeclarator } = locatedExtrudeDeclarator
/**
* Prepare changes to the AST
*/
const filletCall = createCallExpressionStdLib('fillet', [
createObjectExpression({
radius: radius,
tags: createArrayExpression([createIdentifier(tag)]),
}),
createPipeSubstitution(),
])
/**
* Mutate the AST
*/
// CallExpression - no fillet
// PipeExpression - fillet exists
let pathToFilletNode: PathToNode = []
if (extrudeDeclarator.init.type === 'CallExpression') {
// 1. case when no fillet exists
// modify ast with new fillet call by mutating the extrude node
extrudeDeclarator.init = createPipeExpression([
extrudeDeclarator.init,
filletCall,
])
// get path to the fillet node
pathToFilletNode = getPathToNodeOfFilletLiteral(
pathToExtrudeNode,
extrudeDeclarator,
tag
)
return { modifiedAst: astClone, pathToFilletNode }
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
// 2. case when fillet exists
const existingFilletCall = extrudeDeclarator.init.body.find((node) => {
return node.type === 'CallExpression' && node.callee.name === 'fillet'
})
if (!existingFilletCall || existingFilletCall.type !== 'CallExpression') {
return new Error('Fillet CallExpression not found.')
}
// check if the existing fillet has the same tag as the new fillet
const filletTag = getFilletTag(existingFilletCall)
if (filletTag !== tag) {
// mutate the extrude node with the new fillet call
extrudeDeclarator.init.body.push(filletCall)
return {
modifiedAst: astClone,
pathToFilletNode: getPathToNodeOfFilletLiteral(
pathToExtrudeNode,
extrudeDeclarator,
tag
),
}
}
} else {
return new Error('Unsupported extrude type.')
}
return { modifiedAst: astClone, pathToFilletNode }
}
function locateExtrudeDeclarator( function locateExtrudeDeclarator(
node: Program, node: Program,
pathToExtrudeNode: PathToNode pathToExtrudeNode: PathToNode
@ -424,24 +372,7 @@ function getPathToRadiusLiteral(node: ObjectExpression, path: any): PathToNode {
return pathToFilletObj return pathToFilletObj
} }
function getFilletTag(existingFilletCall: CallExpression): string | null { // Button states
if (existingFilletCall.arguments[0].type === 'ObjectExpression') {
const properties = (existingFilletCall.arguments[0] as ObjectExpression)
.properties
const tagsProperty = properties.find((prop) => prop.key.name === 'tags')
if (tagsProperty && tagsProperty.value.type === 'ArrayExpression') {
const elements = (tagsProperty.value as ArrayExpression).elements
if (elements.length > 0 && elements[0].type === 'Identifier') {
return elements[0].name
}
}
}
return null
}
/**
* Button states
*/
export const hasValidFilletSelection = ({ export const hasValidFilletSelection = ({
selectionRanges, selectionRanges,

View File

@ -258,7 +258,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
}, },
Fillet: { Fillet: {
// todo
description: 'Fillet edge', description: 'Fillet edge',
icon: 'fillet', icon: 'fillet',
needsReview: true, needsReview: true,
@ -269,7 +268,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
'default', 'default',
'line-end', 'line-end',
'line-mid', 'line-mid',
'extrude-wall', // to fix: accepts only this selection type 'extrude-wall',
'solid2D', 'solid2D',
'start-cap', 'start-cap',
'end-cap', 'end-cap',
@ -279,9 +278,9 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
'arc', 'arc',
'all', 'all',
], ],
multiple: true, // TODO: multiple selection like in extrude command multiple: true,
required: true, required: true,
skip: true, skip: false,
}, },
radius: { radius: {
inputType: 'kcl', inputType: 'kcl',