Refactor addFillet into addEdgeTreatment Function Supporting Chamfers (#4593)

* refactor code mod and tests

* tsc

* make lint happy

* remove dumby data

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
This commit is contained in:
max
2024-12-02 21:43:59 +01:00
committed by GitHub
parent c43510732c
commit bed7ae3b8b
4 changed files with 402 additions and 227 deletions

View File

@ -82,7 +82,7 @@ import { getVarNameModal } from 'hooks/useToolbarGuards'
import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { modelingMachineEvent } from 'editor/manager'
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addFillet'
import {
ExportIntent,
EngineConnectionStateType,
@ -576,8 +576,10 @@ export const ModelingMachineProvider = ({
if (selectionRanges.graphSelections.length <= 0) return false
return true
},
'has valid fillet selection': ({ context: { selectionRanges } }) => {
return hasValidFilletSelection({
'has valid edge treatment selection': ({
context: { selectionRanges },
}) => {
return hasValidEdgeTreatmentSelection({
selectionRanges,
ast: kclManager.ast,
code: codeManager.code,

View File

@ -10,10 +10,14 @@ import {
VariableDeclarator,
} from '../wasm'
import {
EdgeTreatmentType,
getPathToExtrudeForSegmentSelection,
hasValidFilletSelection,
isTagUsedInFillet,
modifyAstWithFilletAndTag,
hasValidEdgeTreatmentSelection,
isTagUsedInEdgeTreatment,
modifyAstWithEdgeTreatmentAndTag,
FilletParameters,
ChamferParameters,
EdgeTreatmentParameters,
} from './addFillet'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst'
@ -21,7 +25,6 @@ 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'
import { isOverlap } from 'lib/utils'
import { codeRefFromRange } from 'lang/std/artifactGraph'
@ -253,10 +256,10 @@ extrude003 = extrude(-15, sketch003)`
})
})
const runModifyAstCloneWithFilletAndTag = async (
const runModifyAstCloneWithEdgeTreatmentAndTag = async (
code: string,
selectionSnippets: Array<string>,
radiusValue: number,
parameters: EdgeTreatmentParameters,
expectedCode: string
) => {
// ast
@ -274,13 +277,6 @@ const runModifyAstCloneWithFilletAndTag = async (
]
)
// radius
const radius: KclCommandValue = {
valueAst: createLiteral(radiusValue),
valueText: radiusValue.toString(),
valueCalculated: radiusValue.toString(),
}
// executeAst
await kclManager.executeAst({ ast })
const artifactGraph = engineCommandManager.artifactGraph
@ -299,8 +295,8 @@ const runModifyAstCloneWithFilletAndTag = async (
otherSelections: [],
}
// apply fillet to selection
const result = modifyAstWithFilletAndTag(ast, selection, radius)
// apply edge treatment to seleciton
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
if (err(result)) {
return result
}
@ -310,8 +306,41 @@ const runModifyAstCloneWithFilletAndTag = async (
expect(newCode).toContain(expectedCode)
}
describe('Testing applyFilletToSelection', () => {
it('should add a fillet to a specific segment', async () => {
const createFilletParameters = (radiusValue: number): FilletParameters => ({
type: EdgeTreatmentType.Fillet,
radius: {
valueAst: createLiteral(radiusValue),
valueText: radiusValue.toString(),
valueCalculated: radiusValue.toString(),
},
})
const createChamferParameters = (lengthValue: number): ChamferParameters => ({
type: EdgeTreatmentType.Chamfer,
length: {
valueAst: createLiteral(lengthValue),
valueText: lengthValue.toString(),
valueCalculated: lengthValue.toString(),
},
})
// Iterate tests over all edge treatment types
Object.values(EdgeTreatmentType).forEach(
(edgeTreatmentType: EdgeTreatmentType) => {
// create parameters based on the edge treatment type
let parameterName: string
let parameters: EdgeTreatmentParameters
if (edgeTreatmentType === EdgeTreatmentType.Fillet) {
parameterName = 'radius'
parameters = createFilletParameters(3)
} else if (edgeTreatmentType === EdgeTreatmentType.Chamfer) {
parameterName = 'length'
parameters = createChamferParameters(3)
} else {
// Handle future edge treatments
return new Error(`Unsupported edge treatment type: ${edgeTreatmentType}`)
}
// run tests
describe(`Testing modifyAstCloneWithEdgeTreatmentAndTag with ${edgeTreatmentType}s`, () => {
it(`should add a ${edgeTreatmentType} to a specific segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
@ -321,7 +350,6 @@ describe('Testing applyFilletToSelection', () => {
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([0, -20], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
@ -330,16 +358,16 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
radiusValue,
parameters,
expectedCode
)
})
it('should add a fillet to the sketch pipe', async () => {
it(`should add a ${edgeTreatmentType} to the sketch pipe`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
@ -349,7 +377,6 @@ extrude001 = extrude(-15, sketch001)
|> close(%)
|> extrude(-15, %)`
const segmentSnippets = ['line([0, -20], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
@ -358,16 +385,16 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(-15, %)
|> fillet({ radius = 3, tags = [seg01] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
radiusValue,
parameters,
expectedCode
)
})
it('should add a fillet to an already tagged segment', async () => {
it(`should add a ${edgeTreatmentType} to an already tagged segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
@ -377,7 +404,6 @@ extrude001 = extrude(-15, sketch001)
|> 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], %)
@ -386,16 +412,16 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
radiusValue,
parameters,
expectedCode
)
})
it('should add a fillet with existing tag on other segment', async () => {
it(`should add a ${edgeTreatmentType} with existing tag on other segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
@ -405,7 +431,6 @@ extrude001 = extrude(-15, sketch001)
|> 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)
@ -414,16 +439,16 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg02] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag(
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
radiusValue,
parameters,
expectedCode
)
})
it('should add a fillet with existing fillet on other segment', async () => {
it(`should add a ${edgeTreatmentType} with existing fillet on other segment`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
@ -434,7 +459,6 @@ extrude001 = extrude(-15, sketch001)
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)
@ -444,16 +468,45 @@ extrude001 = extrude(-15, sketch001)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 5, tags = [seg01] }, %)
|> fillet({ radius = 3, tags = [seg02] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag(
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
radiusValue,
parameters,
expectedCode
)
})
it('should add a fillet to two segments of a single extrusion', async () => {
it(`should add a ${edgeTreatmentType} with existing chamfer 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)
|> chamfer({ length: 5, tags: [seg01] }, %)`
const segmentSnippets = ['line([-20, 0], %)']
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)
|> chamfer({ length: 5, tags: [seg01] }, %)
|> ${edgeTreatmentType}({ ${parameterName}: 3, tags: [seg02] }, %)`
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add a ${edgeTreatmentType} to two segments of a single extrusion`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
@ -463,7 +516,6 @@ extrude001 = extrude(-15, sketch001)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([20, 0], %)', 'line([-20, 0], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
@ -472,16 +524,16 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01, seg02] }, %)`
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01, seg02] }, %)`
await runModifyAstCloneWithFilletAndTag(
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
radiusValue,
parameters,
expectedCode
)
})
it('should add fillets to two bodies', async () => {
it(`should add ${edgeTreatmentType}s to two bodies`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
@ -503,7 +555,6 @@ extrude002 = extrude(-25, sketch002)` // <--- body 2
'line([-20, 0], %)',
'line([0, -15], %)',
]
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01)
@ -512,7 +563,7 @@ extrude002 = extrude(-25, sketch002)` // <--- body 2
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01, seg02] }, %)
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01, seg02] }, %)
sketch002 = startSketchOn('XY')
|> startProfileAt([30, 10], %)
|> line([15, 0], %)
@ -521,18 +572,20 @@ sketch002 = startSketchOn('XY')
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude002 = extrude(-25, sketch002)
|> fillet({ radius = 3, tags = [seg03] }, %)` // <-- able to add a new one
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg03] }, %)` // <-- able to add a new one
await runModifyAstCloneWithFilletAndTag(
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
radiusValue,
parameters,
expectedCode
)
})
})
})
}
)
describe('Testing isTagUsedInFillet', () => {
describe('Testing isTagUsedInEdgeTreatment', () => {
const code = `sketch001 = startSketchOn('XZ')
|> startProfileAt([7.72, 4.13], %)
|> line([7.11, 3.48], %, $seg01)
@ -565,7 +618,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression'
)
if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
})
it('should correctly identify getPreviousAdjacentEdge edges', () => {
@ -584,7 +637,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression'
)
if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual(['getPreviousAdjacentEdge'])
})
it('should correctly identify no edges', () => {
@ -603,7 +656,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression'
)
if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual([])
})
})
@ -638,7 +691,7 @@ describe('Testing button states', () => {
}
// state
const buttonState = hasValidFilletSelection({
const buttonState = hasValidEdgeTreatmentSelection({
ast,
selectionRanges,
code,

View File

@ -44,32 +44,49 @@ import {
} from 'lib/singletons'
import { Node } from 'wasm-lib/kcl/bindings/Node'
// Apply Fillet To Selection
// Edge Treatment Types
export enum EdgeTreatmentType {
Chamfer = 'chamfer',
Fillet = 'fillet',
}
export function applyFilletToSelection(
export interface ChamferParameters {
type: EdgeTreatmentType.Chamfer
length: KclCommandValue
}
export interface FilletParameters {
type: EdgeTreatmentType.Fillet
radius: KclCommandValue
}
export type EdgeTreatmentParameters = ChamferParameters | FilletParameters
// Apply Edge Treatment (Fillet or Chamfer) To Selection
export function applyEdgeTreatmentToSelection(
ast: Node<Program>,
selection: Selections,
radius: KclCommandValue
parameters: EdgeTreatmentParameters
): void | Error {
// 1. clone and modify with fillet and tag
const result = modifyAstWithFilletAndTag(ast, selection, radius)
// 1. clone and modify with edge treatment and tag
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
if (err(result)) return result
const { modifiedAst, pathToFilletNode } = result
const { modifiedAst, pathToEdgeTreatmentNode } = result
// 2. update ast
// eslint-disable-next-line @typescript-eslint/no-floating-promises
updateAstAndFocus(modifiedAst, pathToFilletNode)
updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode)
}
export function modifyAstWithFilletAndTag(
export function modifyAstWithEdgeTreatmentAndTag(
ast: Node<Program>,
selections: Selections,
radius: KclCommandValue
): { modifiedAst: Node<Program>; pathToFilletNode: Array<PathToNode> } | Error {
parameters: EdgeTreatmentParameters
):
| { modifiedAst: Node<Program>; pathToEdgeTreatmentNode: Array<PathToNode> }
| Error {
let clonedAst = structuredClone(ast)
const clonedAstForGetExtrude = structuredClone(ast)
const astResult = insertRadiusIntoAst(clonedAst, radius)
const astResult = insertParametersIntoAst(clonedAst, parameters)
if (err(astResult)) return astResult
const artifactGraph = engineCommandManager.artifactGraph
@ -119,21 +136,26 @@ export function modifyAstWithFilletAndTag(
}
}
// Step 2: Apply fillet(s) for each extrude node (body)
let pathToFilletNodes: Array<PathToNode> = []
// Step 2: Apply edge treatments for each extrude node (body)
let pathToEdgeTreatmentNodes: Array<PathToNode> = []
for (const [pathToExtrudeNode, tagInfos] of extrudeToTagsMap.entries()) {
// Create a fillet expression with multiple tags
const radiusValue =
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
// Create an edge treatment expression with multiple tags
// edge treatment parameter
const parameterResult = getParameterNameAndValue(parameters)
if (err(parameterResult)) return parameterResult
const { parameterName, parameterValue } = parameterResult
// tag calls
const tagCalls = tagInfos.map(({ tag, artifact }) => {
return getEdgeTagCall(tag, artifact)
})
const firstTag = tagCalls[0] // can be Identifier or CallExpression (for opposite and adjacent edges)
const filletCall = createCallExpressionStdLib('fillet', [
// edge treatment call
const edgeTreatmentCall = createCallExpressionStdLib(parameters.type, [
createObjectExpression({
radius: radiusValue,
[parameterName]: parameterValue,
tags: createArrayExpression(tagCalls),
}),
createPipeSubstitution(),
@ -147,64 +169,89 @@ export function modifyAstWithFilletAndTag(
if (err(locatedExtrudeDeclarator)) return locatedExtrudeDeclarator
const { extrudeDeclarator } = locatedExtrudeDeclarator
// Modify the extrude expression to include this fillet expression
// CallExpression - no fillet
// PipeExpression - fillet exists or extrude in sketch pipe
// Modify the extrude expression to include this edge treatment expression
// CallExpression - no edge treatment
// PipeExpression - edge treatment exists or body in sketch pipe
let pathToFilletNode: PathToNode = []
let pathToEdgeTreatmentNode: PathToNode
if (extrudeDeclarator.init.type === 'CallExpression') {
// 1. case when no fillet exists
// 1. case when no edge treatment exists
// modify ast with new fillet call by mutating the extrude node
// modify ast with new edge treatment call by mutating the extrude node
extrudeDeclarator.init = createPipeExpression([
extrudeDeclarator.init,
filletCall,
edgeTreatmentCall,
])
// get path to the fillet node
pathToFilletNode = getPathToNodeOfFilletLiteral(
// get path to the edge treatment node
pathToEdgeTreatmentNode = getPathToNodeOfEdgeTreatmentLiteral(
pathToExtrudeNode,
extrudeDeclarator,
firstTag
firstTag,
parameters
)
pathToFilletNodes.push(pathToFilletNode)
pathToEdgeTreatmentNodes.push(pathToEdgeTreatmentNode)
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
// 2. case when fillet exists or extrude in sketch pipe
// 2. case when edge treatment exists or extrude in sketch pipe
// mutate the extrude node with the new fillet call
extrudeDeclarator.init.body.push(filletCall)
// mutate the extrude node with the new edge treatment call
extrudeDeclarator.init.body.push(edgeTreatmentCall)
// get path to the fillet node
pathToFilletNode = getPathToNodeOfFilletLiteral(
// get path to the edge treatment node
pathToEdgeTreatmentNode = getPathToNodeOfEdgeTreatmentLiteral(
pathToExtrudeNode,
extrudeDeclarator,
firstTag
firstTag,
parameters
)
pathToFilletNodes.push(pathToFilletNode)
pathToEdgeTreatmentNodes.push(pathToEdgeTreatmentNode)
} else {
return new Error('Unsupported extrude type.')
}
}
return { modifiedAst: clonedAst, pathToFilletNode: pathToFilletNodes }
return {
modifiedAst: clonedAst,
pathToEdgeTreatmentNode: pathToEdgeTreatmentNodes,
}
}
function insertRadiusIntoAst(
function insertParametersIntoAst(
ast: Program,
radius: KclCommandValue
parameters: EdgeTreatmentParameters
): { ast: Program } | Error {
try {
// Validate and update AST
if (
'variableName' in radius &&
radius.variableName &&
radius.insertIndex !== undefined
) {
const newAst = structuredClone(ast)
newAst.body.splice(radius.insertIndex, 0, radius.variableDeclarationAst)
return { ast: newAst }
// handle radius parameter
if (
parameters.type === EdgeTreatmentType.Fillet &&
'variableName' in parameters.radius &&
parameters.radius.variableName &&
parameters.radius.insertIndex !== undefined
) {
newAst.body.splice(
parameters.radius.insertIndex,
0,
parameters.radius.variableDeclarationAst
)
}
return { ast }
// handle length parameter
if (
parameters.type === EdgeTreatmentType.Chamfer &&
'variableName' in parameters.length &&
parameters.length.variableName &&
parameters.length.insertIndex !== undefined
) {
newAst.body.splice(
parameters.length.insertIndex,
0,
parameters.length.variableDeclarationAst
)
}
// handle upcoming parameters here (for blend, bevel, etc.)
return { ast: newAst }
} catch (error) {
return new Error(`Failed to handle AST: ${(error as Error).message}`)
}
@ -248,10 +295,10 @@ export function getPathToExtrudeForSegmentSelection(
async function updateAstAndFocus(
modifiedAst: Node<Program>,
pathToFilletNode: Array<PathToNode>
pathToEdgeTreatmentNode: Array<PathToNode>
) {
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
focusPath: pathToFilletNode,
focusPath: pathToEdgeTreatmentNode,
})
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
@ -340,27 +387,38 @@ function locateExtrudeDeclarator(
return { extrudeDeclarator }
}
function getPathToNodeOfFilletLiteral(
function getPathToNodeOfEdgeTreatmentLiteral(
pathToExtrudeNode: PathToNode,
extrudeDeclarator: VariableDeclarator,
tag: Identifier | CallExpression
tag: Identifier | CallExpression,
parameters: EdgeTreatmentParameters
): PathToNode {
let pathToFilletObj: PathToNode = []
let inFillet = false
let pathToEdgeTreatmentObj: PathToNode = []
let inEdgeTreatment = false
traverse(extrudeDeclarator.init, {
enter(node, path) {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = true
if (
node.type === 'CallExpression' &&
node.callee.name === parameters.type
) {
inEdgeTreatment = true
}
if (inFillet && node.type === 'ObjectExpression') {
if (inEdgeTreatment && node.type === 'ObjectExpression') {
if (!hasTag(node, tag)) return false
pathToFilletObj = getPathToRadiusLiteral(node, path)
pathToEdgeTreatmentObj = getPathToEdgeTreatmentParameterLiteral(
node,
path,
parameters
)
}
},
leave(node) {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = false
if (
node.type === 'CallExpression' &&
node.callee.name === parameters.type
) {
inEdgeTreatment = false
}
},
})
@ -375,7 +433,7 @@ function getPathToNodeOfFilletLiteral(
return [
...pathToExtrudeNode.slice(0, indexOfPipeExpression),
...pathToFilletObj,
...pathToEdgeTreatmentObj,
]
}
@ -408,23 +466,62 @@ function hasTag(
})
}
function getPathToRadiusLiteral(node: ObjectExpression, path: any): PathToNode {
let pathToFilletObj = path
function getPathToEdgeTreatmentParameterLiteral(
node: ObjectExpression,
path: any,
parameters: EdgeTreatmentParameters
): PathToNode {
let pathToEdgeTreatmentObj = path
const parameterResult = getParameterNameAndValue(parameters)
if (err(parameterResult)) return pathToEdgeTreatmentObj
const { parameterName } = parameterResult
node.properties.forEach((prop, index) => {
if (prop.key.name === 'radius') {
pathToFilletObj.push(
if (prop.key.name === parameterName) {
pathToEdgeTreatmentObj.push(
['properties', 'ObjectExpression'],
[index, 'index'],
['value', 'Property']
)
}
})
return pathToFilletObj
return pathToEdgeTreatmentObj
}
function getParameterNameAndValue(
parameters: EdgeTreatmentParameters
): { parameterName: string; parameterValue: Expr } | Error {
if (parameters.type === EdgeTreatmentType.Fillet) {
const parameterValue =
'variableName' in parameters.radius
? parameters.radius.variableIdentifierAst
: parameters.radius.valueAst
return { parameterName: 'radius', parameterValue }
} else if (parameters.type === EdgeTreatmentType.Chamfer) {
const parameterValue =
'variableName' in parameters.length
? parameters.length.variableIdentifierAst
: parameters.length.valueAst
return { parameterName: 'length', parameterValue }
} else {
return new Error('Unsupported edge treatment type}')
}
}
// Type Guards
function isEdgeTreatmentType(name: string): name is EdgeTreatmentType {
return name === EdgeTreatmentType.Chamfer || name === EdgeTreatmentType.Fillet
}
function isEdgeType(name: string): name is EdgeTypes {
return (
name === 'getNextAdjacentEdge' ||
name === 'getPreviousAdjacentEdge' ||
name === 'getOppositeEdge'
)
}
// Button states
export const hasValidFilletSelection = ({
export const hasValidEdgeTreatmentSelection = ({
selectionRanges,
ast,
code,
@ -433,11 +530,14 @@ export const hasValidFilletSelection = ({
ast: Node<Program>
code: string
}) => {
// check if there is anything filletable in the scene
// check if there is anything valid for the edge treatment in the scene
let extrudeExists = false
traverse(ast, {
enter(node) {
if (node.type === 'CallExpression' && node.callee.name === 'extrude') {
if (
node.type === 'CallExpression' &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve')
) {
extrudeExists = true
}
},
@ -494,32 +594,39 @@ export const hasValidFilletSelection = ({
},
})
// check if tag is used in fillet
// check if tag is used in edge treatment
if (tagExists && selection.artifact) {
// create tag call
let tagCall: Expr = getEdgeTagCall(tag, selection.artifact)
// check if tag is used in fillet
let inFillet = false
let tagUsedInFillet = false
// check if tag is used in edge treatment
let inEdgeTreatment = false
let tagUsedInEdgeTreatment = false
traverse(ast, {
enter(node) {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = true
if (
node.type === 'CallExpression' &&
isEdgeTreatmentType(node.callee.name)
) {
inEdgeTreatment = true
}
if (inFillet && node.type === 'ObjectExpression') {
if (inEdgeTreatment && node.type === 'ObjectExpression') {
if (hasTag(node, tagCall)) {
tagUsedInFillet = true
tagUsedInEdgeTreatment = true
}
}
},
leave(node) {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = false
if (
node.type === 'CallExpression' &&
isEdgeTreatmentType(node.callee.name)
) {
inEdgeTreatment = false
}
},
})
if (tagUsedInFillet) {
if (tagUsedInEdgeTreatment) {
return false
}
}
@ -533,7 +640,7 @@ type EdgeTypes =
| 'getPreviousAdjacentEdge'
| 'getOppositeEdge'
export const isTagUsedInFillet = ({
export const isTagUsedInEdgeTreatment = ({
ast,
callExp,
}: {
@ -543,16 +650,21 @@ export const isTagUsedInFillet = ({
const tag = getTagFromCallExpression(callExp)
if (err(tag)) return []
let inFillet = false
let inEdgeTreatment = false
let inObj = false
let inTagHelper: EdgeTypes | '' = ''
const edges: Array<EdgeTypes> = []
traverse(ast, {
enter: (node) => {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = true
// Check if we are entering an edge treatment call
if (
node.type === 'CallExpression' &&
isEdgeTreatmentType(node.callee.name)
) {
inEdgeTreatment = true
}
if (inFillet && node.type === 'ObjectExpression') {
if (inEdgeTreatment && node.type === 'ObjectExpression') {
node.properties.forEach((prop) => {
if (
prop.key.name === 'tags' &&
@ -564,17 +676,15 @@ export const isTagUsedInFillet = ({
}
if (
inObj &&
inFillet &&
inEdgeTreatment &&
node.type === 'CallExpression' &&
(node.callee.name === 'getOppositeEdge' ||
node.callee.name === 'getNextAdjacentEdge' ||
node.callee.name === 'getPreviousAdjacentEdge')
isEdgeType(node.callee.name)
) {
inTagHelper = node.callee.name
}
if (
inObj &&
inFillet &&
inEdgeTreatment &&
!inTagHelper &&
node.type === 'Identifier' &&
node.name === tag
@ -583,7 +693,7 @@ export const isTagUsedInFillet = ({
}
if (
inObj &&
inFillet &&
inEdgeTreatment &&
inTagHelper &&
node.type === 'Identifier' &&
node.name === tag
@ -592,10 +702,13 @@ export const isTagUsedInFillet = ({
}
},
leave: (node) => {
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
inFillet = false
if (
node.type === 'CallExpression' &&
isEdgeTreatmentType(node.callee.name)
) {
inEdgeTreatment = false
}
if (inFillet && node.type === 'ObjectExpression') {
if (inEdgeTreatment && node.type === 'ObjectExpression') {
node.properties.forEach((prop) => {
if (
prop.key.name === 'tags' &&
@ -607,11 +720,9 @@ export const isTagUsedInFillet = ({
}
if (
inObj &&
inFillet &&
inEdgeTreatment &&
node.type === 'CallExpression' &&
(node.callee.name === 'getOppositeEdge' ||
node.callee.name === 'getNextAdjacentEdge' ||
node.callee.name === 'getPreviousAdjacentEdge')
isEdgeType(node.callee.name)
) {
inTagHelper = ''
}

View File

@ -46,7 +46,11 @@ import {
extrudeSketch,
revolveSketch,
} from 'lang/modifyAst'
import { applyFilletToSelection } from 'lang/modifyAst/addFillet'
import {
applyEdgeTreatmentToSelection,
EdgeTreatmentType,
FilletParameters,
} from 'lang/modifyAst/addFillet'
import { getNodeFromPath } from '../lang/queryAst'
import {
applyConstraintEqualAngle,
@ -383,7 +387,7 @@ export const modelingMachine = setup({
guards: {
'Selection is on face': () => false,
'has valid sweep selection': () => false,
'has valid fillet selection': () => false,
'has valid edge treatment selection': () => false,
'Has exportable geometry': () => false,
'has valid selection for deletion': () => false,
'has made first point': ({ context }) => {
@ -739,14 +743,19 @@ export const modelingMachine = setup({
// Extract inputs
const ast = kclManager.ast
const { selection, radius } = event.data
const parameters: FilletParameters = {
type: EdgeTreatmentType.Fillet,
radius,
}
// Apply fillet to selection
const applyFilletToSelectionResult = applyFilletToSelection(
const applyEdgeTreatmentToSelectionResult = applyEdgeTreatmentToSelection(
ast,
selection,
radius
parameters
)
if (err(applyFilletToSelectionResult)) return applyFilletToSelectionResult
if (err(applyEdgeTreatmentToSelectionResult))
return applyEdgeTreatmentToSelectionResult
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
@ -1563,7 +1572,7 @@ export const modelingMachine = setup({
Fillet: {
target: 'idle',
guard: 'has valid fillet selection', // TODO: fix selections
guard: 'has valid edge treatment selection',
actions: ['AST fillet'],
reenter: false,
},