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

View File

@ -10,10 +10,14 @@ import {
VariableDeclarator, VariableDeclarator,
} from '../wasm' } from '../wasm'
import { import {
EdgeTreatmentType,
getPathToExtrudeForSegmentSelection, getPathToExtrudeForSegmentSelection,
hasValidFilletSelection, hasValidEdgeTreatmentSelection,
isTagUsedInFillet, isTagUsedInEdgeTreatment,
modifyAstWithFilletAndTag, modifyAstWithEdgeTreatmentAndTag,
FilletParameters,
ChamferParameters,
EdgeTreatmentParameters,
} from './addFillet' } from './addFillet'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst' import { createLiteral } from 'lang/modifyAst'
@ -21,7 +25,6 @@ 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'
import { isOverlap } from 'lib/utils' import { isOverlap } from 'lib/utils'
import { codeRefFromRange } from 'lang/std/artifactGraph' import { codeRefFromRange } from 'lang/std/artifactGraph'
@ -253,10 +256,10 @@ extrude003 = extrude(-15, sketch003)`
}) })
}) })
const runModifyAstCloneWithFilletAndTag = async ( const runModifyAstCloneWithEdgeTreatmentAndTag = async (
code: string, code: string,
selectionSnippets: Array<string>, selectionSnippets: Array<string>,
radiusValue: number, parameters: EdgeTreatmentParameters,
expectedCode: string expectedCode: string
) => { ) => {
// ast // ast
@ -274,13 +277,6 @@ const runModifyAstCloneWithFilletAndTag = async (
] ]
) )
// radius
const radius: KclCommandValue = {
valueAst: createLiteral(radiusValue),
valueText: radiusValue.toString(),
valueCalculated: radiusValue.toString(),
}
// executeAst // executeAst
await kclManager.executeAst({ ast }) await kclManager.executeAst({ ast })
const artifactGraph = engineCommandManager.artifactGraph const artifactGraph = engineCommandManager.artifactGraph
@ -299,8 +295,8 @@ const runModifyAstCloneWithFilletAndTag = async (
otherSelections: [], otherSelections: [],
} }
// apply fillet to selection // apply edge treatment to seleciton
const result = modifyAstWithFilletAndTag(ast, selection, radius) const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
if (err(result)) { if (err(result)) {
return result return result
} }
@ -310,9 +306,42 @@ const runModifyAstCloneWithFilletAndTag = async (
expect(newCode).toContain(expectedCode) expect(newCode).toContain(expectedCode)
} }
describe('Testing applyFilletToSelection', () => { const createFilletParameters = (radiusValue: number): FilletParameters => ({
it('should add a fillet to a specific segment', async () => { type: EdgeTreatmentType.Fillet,
const code = `sketch001 = startSketchOn('XY') 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], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, -20], %) |> line([0, -20], %)
@ -320,9 +349,8 @@ describe('Testing applyFilletToSelection', () => {
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001)` extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([0, -20], %)'] const segmentSnippets = ['line([0, -20], %)']
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], %)
|> line([0, -20], %, $seg01) |> line([0, -20], %, $seg01)
@ -330,17 +358,17 @@ 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] }, %)` |> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag( await runModifyAstCloneWithEdgeTreatmentAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, parameters,
expectedCode 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') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, -20], %) |> line([0, -20], %)
@ -348,9 +376,8 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
|> extrude(-15, %)` |> extrude(-15, %)`
const segmentSnippets = ['line([0, -20], %)'] const segmentSnippets = ['line([0, -20], %)']
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], %)
|> line([0, -20], %, $seg01) |> line([0, -20], %, $seg01)
@ -358,17 +385,17 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
|> extrude(-15, %) |> extrude(-15, %)
|> fillet({ radius = 3, tags = [seg01] }, %)` |> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag( await runModifyAstCloneWithEdgeTreatmentAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, parameters,
expectedCode 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') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, -20], %, $seg01) |> line([0, -20], %, $seg01)
@ -376,9 +403,8 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001)` extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([0, -20], %, $seg01)'] const segmentSnippets = ['line([0, -20], %, $seg01)']
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], %)
|> line([0, -20], %, $seg01) |> line([0, -20], %, $seg01)
@ -386,17 +412,17 @@ 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] }, %)` |> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag( await runModifyAstCloneWithEdgeTreatmentAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, parameters,
expectedCode 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') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01) |> line([20, 0], %, $seg01)
|> line([0, -20], %) |> line([0, -20], %)
@ -404,9 +430,8 @@ extrude001 = extrude(-15, sketch001)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001)` extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([-20, 0], %)'] const segmentSnippets = ['line([-20, 0], %)']
const radiusValue = 3 const expectedCode = `sketch001 = startSketchOn('XY')
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01) |> line([20, 0], %, $seg01)
|> line([0, -20], %) |> line([0, -20], %)
@ -414,17 +439,17 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg02] }, %)` |> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag( await runModifyAstCloneWithEdgeTreatmentAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, parameters,
expectedCode 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') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01) |> line([20, 0], %, $seg01)
|> line([0, -20], %) |> line([0, -20], %)
@ -433,9 +458,8 @@ extrude001 = extrude(-15, sketch001)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 5, tags = [seg01] }, %)` |> fillet({ radius = 5, tags = [seg01] }, %)`
const segmentSnippets = ['line([-20, 0], %)'] const segmentSnippets = ['line([-20, 0], %)']
const radiusValue = 3 const expectedCode = `sketch001 = startSketchOn('XY')
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01) |> line([20, 0], %, $seg01)
|> line([0, -20], %) |> line([0, -20], %)
@ -444,27 +468,27 @@ extrude001 = extrude(-15, sketch001)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 5, tags = [seg01] }, %) |> fillet({ radius = 5, tags = [seg01] }, %)
|> fillet({ radius = 3, tags = [seg02] }, %)` |> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag( await runModifyAstCloneWithEdgeTreatmentAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, parameters,
expectedCode 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') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %, $seg01)
|> line([0, -20], %) |> line([0, -20], %)
|> line([-20, 0], %) |> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001)` extrude001 = extrude(-15, sketch001)
const segmentSnippets = ['line([20, 0], %)', 'line([-20, 0], %)'] |> chamfer({ length: 5, tags: [seg01] }, %)`
const radiusValue = 3 const segmentSnippets = ['line([-20, 0], %)']
const expectedCode = `sketch001 = startSketchOn('XY') const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01) |> line([20, 0], %, $seg01)
|> line([0, -20], %) |> line([0, -20], %)
@ -472,17 +496,45 @@ 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, seg02] }, %)` |> chamfer({ length: 5, tags: [seg01] }, %)
|> ${edgeTreatmentType}({ ${parameterName}: 3, tags: [seg02] }, %)`
await runModifyAstCloneWithFilletAndTag( await runModifyAstCloneWithEdgeTreatmentAndTag(
code, code,
segmentSnippets, segmentSnippets,
radiusValue, parameters,
expectedCode expectedCode
) )
}) })
it('should add fillets to two bodies', async () => { it(`should add a ${edgeTreatmentType} to two segments of a single extrusion`, async () => {
const code = `sketch001 = startSketchOn('XY') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)`
const segmentSnippets = ['line([20, 0], %)', '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)
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01, seg02] }, %)`
await runModifyAstCloneWithEdgeTreatmentAndTag(
code,
segmentSnippets,
parameters,
expectedCode
)
})
it(`should add ${edgeTreatmentType}s to two bodies`, async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, -20], %) |> line([0, -20], %)
@ -498,13 +550,12 @@ 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 = [ const segmentSnippets = [
'line([20, 0], %)', 'line([20, 0], %)',
'line([-20, 0], %)', 'line([-20, 0], %)',
'line([0, -15], %)', 'line([0, -15], %)',
] ]
const radiusValue = 3 const expectedCode = `sketch001 = startSketchOn('XY')
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %) |> startProfileAt([-10, 10], %)
|> line([20, 0], %, $seg01) |> line([20, 0], %, $seg01)
|> line([0, -20], %) |> line([0, -20], %)
@ -512,7 +563,7 @@ extrude002 = extrude(-25, sketch002)` // <--- body 2
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> fillet({ radius = 3, tags = [seg01, seg02] }, %) |> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01, seg02] }, %)
sketch002 = startSketchOn('XY') sketch002 = startSketchOn('XY')
|> startProfileAt([30, 10], %) |> startProfileAt([30, 10], %)
|> line([15, 0], %) |> line([15, 0], %)
@ -521,18 +572,20 @@ sketch002 = startSketchOn('XY')
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude002 = extrude(-25, sketch002) 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, code,
segmentSnippets, segmentSnippets,
radiusValue, parameters,
expectedCode expectedCode
) )
}) })
}) })
}
)
describe('Testing isTagUsedInFillet', () => { describe('Testing isTagUsedInEdgeTreatment', () => {
const code = `sketch001 = startSketchOn('XZ') const code = `sketch001 = startSketchOn('XZ')
|> startProfileAt([7.72, 4.13], %) |> startProfileAt([7.72, 4.13], %)
|> line([7.11, 3.48], %, $seg01) |> line([7.11, 3.48], %, $seg01)
@ -565,7 +618,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression' 'CallExpression'
) )
if (err(callExp)) return if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node }) const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual(['getOppositeEdge', 'baseEdge']) expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
}) })
it('should correctly identify getPreviousAdjacentEdge edges', () => { it('should correctly identify getPreviousAdjacentEdge edges', () => {
@ -584,7 +637,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression' 'CallExpression'
) )
if (err(callExp)) return if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node }) const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual(['getPreviousAdjacentEdge']) expect(edges).toEqual(['getPreviousAdjacentEdge'])
}) })
it('should correctly identify no edges', () => { it('should correctly identify no edges', () => {
@ -603,7 +656,7 @@ extrude001 = extrude(-5, sketch001)
'CallExpression' 'CallExpression'
) )
if (err(callExp)) return if (err(callExp)) return
const edges = isTagUsedInFillet({ ast, callExp: callExp.node }) const edges = isTagUsedInEdgeTreatment({ ast, callExp: callExp.node })
expect(edges).toEqual([]) expect(edges).toEqual([])
}) })
}) })
@ -638,7 +691,7 @@ describe('Testing button states', () => {
} }
// state // state
const buttonState = hasValidFilletSelection({ const buttonState = hasValidEdgeTreatmentSelection({
ast, ast,
selectionRanges, selectionRanges,
code, code,

View File

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

View File

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