2024-07-15 19:20:32 +10:00
|
|
|
import {
|
|
|
|
ArrayExpression,
|
|
|
|
CallExpression,
|
|
|
|
ObjectExpression,
|
|
|
|
PathToNode,
|
|
|
|
Program,
|
2024-08-26 08:07:20 +02:00
|
|
|
ProgramMemory,
|
2024-08-12 15:38:42 -05:00
|
|
|
Expr,
|
2024-07-15 19:20:32 +10:00
|
|
|
VariableDeclaration,
|
|
|
|
VariableDeclarator,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketchFromKclValue,
|
2024-07-15 19:20:32 +10:00
|
|
|
} from '../wasm'
|
|
|
|
import {
|
|
|
|
createCallExpressionStdLib,
|
|
|
|
createLiteral,
|
|
|
|
createPipeSubstitution,
|
|
|
|
createObjectExpression,
|
|
|
|
createArrayExpression,
|
|
|
|
createIdentifier,
|
|
|
|
createPipeExpression,
|
|
|
|
} from '../modifyAst'
|
|
|
|
import {
|
|
|
|
getNodeFromPath,
|
|
|
|
getNodePathFromSourceRange,
|
|
|
|
hasSketchPipeBeenExtruded,
|
|
|
|
traverse,
|
|
|
|
} from '../queryAst'
|
|
|
|
import {
|
|
|
|
addTagForSketchOnFace,
|
|
|
|
getTagFromCallExpression,
|
|
|
|
sketchLineHelperMap,
|
|
|
|
} from '../std/sketch'
|
2024-08-26 08:07:20 +02:00
|
|
|
import { err, trap } from 'lib/trap'
|
2024-07-15 19:20:32 +10:00
|
|
|
import { Selections, canFilletSelection } from 'lib/selections'
|
2024-08-26 08:07:20 +02:00
|
|
|
import { KclCommandValue } from 'lib/commandTypes'
|
|
|
|
import {
|
|
|
|
ArtifactGraph,
|
2024-09-17 13:22:53 -05:00
|
|
|
getSweepFromSuspectedPath,
|
2024-08-26 08:07:20 +02:00
|
|
|
} from 'lang/std/artifactGraph'
|
|
|
|
import { kclManager, engineCommandManager, editorManager } from 'lib/singletons'
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply Fillet To Selection
|
|
|
|
*/
|
|
|
|
|
|
|
|
export function applyFilletToSelection(
|
2024-09-12 21:45:26 +02:00
|
|
|
ast: Program,
|
2024-08-26 08:07:20 +02:00
|
|
|
selection: Selections,
|
|
|
|
radius: KclCommandValue
|
|
|
|
): void | Error {
|
2024-09-12 21:45:26 +02:00
|
|
|
// 1. clone ast
|
|
|
|
let clonedAst = structuredClone(ast)
|
2024-09-09 12:15:16 +02:00
|
|
|
|
|
|
|
// 2. modify ast clone with fillet and tag
|
2024-09-12 21:45:26 +02:00
|
|
|
const result = modifyAstWithFilletAndTag(clonedAst, selection, radius)
|
2024-09-09 12:15:16 +02:00
|
|
|
if (err(result)) return result
|
|
|
|
const { modifiedAst, pathToFilletNode } = result
|
|
|
|
|
|
|
|
// 3. update ast
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-09-09 12:15:16 +02:00
|
|
|
updateAstAndFocus(modifiedAst, pathToFilletNode)
|
|
|
|
}
|
|
|
|
|
2024-09-12 21:45:26 +02:00
|
|
|
export function modifyAstWithFilletAndTag(
|
2024-09-09 12:15:16 +02:00
|
|
|
ast: Program,
|
|
|
|
selection: Selections,
|
|
|
|
radius: KclCommandValue
|
2024-09-23 08:07:31 +02:00
|
|
|
): { modifiedAst: Program; pathToFilletNode: Array<PathToNode> } | Error {
|
2024-08-26 08:07:20 +02:00
|
|
|
const astResult = insertRadiusIntoAst(ast, radius)
|
|
|
|
if (err(astResult)) return astResult
|
|
|
|
|
|
|
|
const programMemory = kclManager.programMemory
|
|
|
|
const artifactGraph = engineCommandManager.artifactGraph
|
|
|
|
|
2024-09-12 21:45:26 +02:00
|
|
|
let clonedAst = structuredClone(ast)
|
2024-09-23 08:07:31 +02:00
|
|
|
const clonedAstForGetExtrude = structuredClone(ast)
|
|
|
|
let pathToFilletNodes: Array<PathToNode> = []
|
2024-08-26 08:07:20 +02:00
|
|
|
|
2024-09-12 21:45:26 +02:00
|
|
|
for (const selectionRange of selection.codeBasedSelections) {
|
|
|
|
const singleSelection = {
|
|
|
|
codeBasedSelections: [selectionRange],
|
|
|
|
otherSelections: [],
|
|
|
|
}
|
|
|
|
const getPathToExtrudeForSegmentSelectionResult =
|
|
|
|
getPathToExtrudeForSegmentSelection(
|
2024-09-23 08:07:31 +02:00
|
|
|
clonedAstForGetExtrude,
|
2024-09-12 21:45:26 +02:00
|
|
|
singleSelection,
|
|
|
|
programMemory,
|
|
|
|
artifactGraph
|
|
|
|
)
|
|
|
|
if (err(getPathToExtrudeForSegmentSelectionResult))
|
|
|
|
return getPathToExtrudeForSegmentSelectionResult
|
|
|
|
const { pathToSegmentNode, pathToExtrudeNode } =
|
|
|
|
getPathToExtrudeForSegmentSelectionResult
|
|
|
|
|
|
|
|
const addFilletResult = addFillet(
|
|
|
|
clonedAst,
|
|
|
|
pathToSegmentNode,
|
|
|
|
pathToExtrudeNode,
|
|
|
|
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
|
|
|
|
)
|
|
|
|
if (trap(addFilletResult)) return addFilletResult
|
|
|
|
const { modifiedAst, pathToFilletNode } = addFilletResult
|
|
|
|
clonedAst = modifiedAst
|
2024-09-23 08:07:31 +02:00
|
|
|
pathToFilletNodes.push(pathToFilletNode)
|
2024-09-12 21:45:26 +02:00
|
|
|
}
|
2024-09-23 08:07:31 +02:00
|
|
|
return { modifiedAst: clonedAst, pathToFilletNode: pathToFilletNodes }
|
2024-08-26 08:07:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function insertRadiusIntoAst(
|
|
|
|
ast: Program,
|
|
|
|
radius: KclCommandValue
|
|
|
|
): { 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 }
|
|
|
|
}
|
|
|
|
return { ast }
|
|
|
|
} catch (error) {
|
|
|
|
return new Error(`Failed to handle AST: ${(error as Error).message}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getPathToExtrudeForSegmentSelection(
|
|
|
|
ast: Program,
|
|
|
|
selection: Selections,
|
|
|
|
programMemory: ProgramMemory,
|
|
|
|
artifactGraph: ArtifactGraph
|
|
|
|
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
|
|
|
const pathToSegmentNode = getNodePathFromSourceRange(
|
|
|
|
ast,
|
|
|
|
selection.codeBasedSelections[0].range
|
|
|
|
)
|
|
|
|
|
|
|
|
const varDecNode = getNodeFromPath<VariableDeclaration>(
|
|
|
|
ast,
|
|
|
|
pathToSegmentNode,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (err(varDecNode)) return varDecNode
|
|
|
|
const sketchVar = varDecNode.node.declarations[0].id.name
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
const sketch = sketchFromKclValue(
|
2024-08-26 08:07:20 +02:00
|
|
|
kclManager.programMemory.get(sketchVar),
|
|
|
|
sketchVar
|
|
|
|
)
|
2024-09-27 15:44:44 -07:00
|
|
|
if (trap(sketch)) return sketch
|
2024-08-26 08:07:20 +02:00
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
const extrusion = getSweepFromSuspectedPath(sketch.id, artifactGraph)
|
2024-08-26 08:07:20 +02:00
|
|
|
if (err(extrusion)) return extrusion
|
|
|
|
|
|
|
|
const pathToExtrudeNode = getNodePathFromSourceRange(
|
|
|
|
ast,
|
|
|
|
extrusion.codeRef.range
|
|
|
|
)
|
|
|
|
if (err(pathToExtrudeNode)) return pathToExtrudeNode
|
|
|
|
|
|
|
|
return { pathToSegmentNode, pathToExtrudeNode }
|
|
|
|
}
|
|
|
|
|
|
|
|
async function updateAstAndFocus(
|
|
|
|
modifiedAst: Program,
|
2024-09-23 08:07:31 +02:00
|
|
|
pathToFilletNode: Array<PathToNode>
|
2024-08-26 08:07:20 +02:00
|
|
|
) {
|
|
|
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
|
|
|
focusPath: pathToFilletNode,
|
|
|
|
})
|
|
|
|
if (updatedAst?.selections) {
|
|
|
|
editorManager.selectRange(updatedAst?.selections)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add Fillet
|
|
|
|
*/
|
2024-07-15 19:20:32 +10:00
|
|
|
|
|
|
|
export function addFillet(
|
2024-08-26 08:07:20 +02:00
|
|
|
ast: Program,
|
2024-07-15 19:20:32 +10:00
|
|
|
pathToSegmentNode: PathToNode,
|
|
|
|
pathToExtrudeNode: PathToNode,
|
2024-08-26 08:07:20 +02:00
|
|
|
radius: Expr = createLiteral(5)
|
2024-07-15 19:20:32 +10:00
|
|
|
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
|
2024-08-26 08:07:20 +02:00
|
|
|
// Clone AST to ensure safe mutations
|
|
|
|
const astClone = structuredClone(ast)
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
// Modify AST clone : TAG the sketch segment and retrieve tag
|
|
|
|
const segmentResult = mutateAstWithTagForSketchSegment(
|
|
|
|
astClone,
|
|
|
|
pathToSegmentNode
|
|
|
|
)
|
|
|
|
if (err(segmentResult)) return segmentResult
|
|
|
|
const { tag } = segmentResult
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
// 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(
|
|
|
|
astClone: Program,
|
|
|
|
pathToSegmentNode: PathToNode
|
|
|
|
): { modifiedAst: Program; tag: string } | Error {
|
|
|
|
const segmentNode = getNodeFromPath<CallExpression>(
|
|
|
|
astClone,
|
2024-07-15 19:20:32 +10:00
|
|
|
pathToSegmentNode,
|
|
|
|
'CallExpression'
|
|
|
|
)
|
2024-08-26 08:07:20 +02:00
|
|
|
if (err(segmentNode)) return segmentNode
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
// Check whether selection is a valid segment
|
|
|
|
if (!(segmentNode.node.callee.name in sketchLineHelperMap)) {
|
2024-07-15 19:20:32 +10:00
|
|
|
return new Error('Selection is not a sketch segment')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add tag to the sketch segment or use existing tag
|
2024-08-26 08:07:20 +02:00
|
|
|
// a helper function that creates the updated node and applies the changes to the AST
|
2024-07-15 19:20:32 +10:00
|
|
|
const taggedSegment = addTagForSketchOnFace(
|
|
|
|
{
|
|
|
|
pathToNode: pathToSegmentNode,
|
2024-08-26 08:07:20 +02:00
|
|
|
node: astClone,
|
2024-07-15 19:20:32 +10:00
|
|
|
},
|
2024-09-26 18:25:05 +10:00
|
|
|
segmentNode.node.callee.name,
|
|
|
|
null
|
2024-07-15 19:20:32 +10:00
|
|
|
)
|
|
|
|
if (err(taggedSegment)) return taggedSegment
|
|
|
|
const { tag } = taggedSegment
|
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
return { modifiedAst: astClone, tag }
|
|
|
|
}
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
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
|
2024-07-15 19:20:32 +10:00
|
|
|
|
|
|
|
/**
|
2024-08-26 08:07:20 +02:00
|
|
|
* Prepare changes to the AST
|
2024-07-15 19:20:32 +10:00
|
|
|
*/
|
|
|
|
|
|
|
|
const filletCall = createCallExpressionStdLib('fillet', [
|
|
|
|
createObjectExpression({
|
|
|
|
radius: radius,
|
|
|
|
tags: createArrayExpression([createIdentifier(tag)]),
|
|
|
|
}),
|
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
/**
|
|
|
|
* Mutate the AST
|
|
|
|
*/
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
// CallExpression - no fillet
|
|
|
|
// PipeExpression - fillet exists
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
let pathToFilletNode: PathToNode = []
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
if (extrudeDeclarator.init.type === 'CallExpression') {
|
|
|
|
// 1. case when no fillet exists
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
// modify ast with new fillet call by mutating the extrude node
|
|
|
|
extrudeDeclarator.init = createPipeExpression([
|
|
|
|
extrudeDeclarator.init,
|
|
|
|
filletCall,
|
|
|
|
])
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
// get path to the fillet node
|
|
|
|
pathToFilletNode = getPathToNodeOfFilletLiteral(
|
|
|
|
pathToExtrudeNode,
|
|
|
|
extrudeDeclarator,
|
|
|
|
tag
|
2024-07-15 19:20:32 +10:00
|
|
|
)
|
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
return { modifiedAst: astClone, pathToFilletNode }
|
|
|
|
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
|
|
|
|
// 2. case when fillet exists
|
2024-07-15 19:20:32 +10:00
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
const existingFilletCall = extrudeDeclarator.init.body.find((node) => {
|
2024-07-15 19:20:32 +10:00
|
|
|
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
|
2024-08-26 08:07:20 +02:00
|
|
|
const filletTag = getFilletTag(existingFilletCall)
|
2024-07-15 19:20:32 +10:00
|
|
|
|
|
|
|
if (filletTag !== tag) {
|
2024-08-26 08:07:20 +02:00
|
|
|
// mutate the extrude node with the new fillet call
|
|
|
|
extrudeDeclarator.init.body.push(filletCall)
|
2024-07-15 19:20:32 +10:00
|
|
|
return {
|
2024-08-26 08:07:20 +02:00
|
|
|
modifiedAst: astClone,
|
2024-07-15 19:20:32 +10:00
|
|
|
pathToFilletNode: getPathToNodeOfFilletLiteral(
|
|
|
|
pathToExtrudeNode,
|
|
|
|
extrudeDeclarator,
|
|
|
|
tag
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return new Error('Unsupported extrude type.')
|
|
|
|
}
|
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
return { modifiedAst: astClone, pathToFilletNode }
|
2024-07-15 19:20:32 +10:00
|
|
|
}
|
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
function locateExtrudeDeclarator(
|
|
|
|
node: Program,
|
|
|
|
pathToExtrudeNode: PathToNode
|
|
|
|
): { extrudeDeclarator: VariableDeclarator } | Error {
|
|
|
|
const extrudeChunk = getNodeFromPath<VariableDeclaration>(
|
|
|
|
node,
|
|
|
|
pathToExtrudeNode,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (err(extrudeChunk)) return extrudeChunk
|
|
|
|
|
|
|
|
const { node: extrudeVarDecl } = extrudeChunk
|
|
|
|
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
|
|
|
if (!extrudeDeclarator) {
|
|
|
|
return new Error('Extrude Declarator not found.')
|
|
|
|
}
|
|
|
|
|
|
|
|
const extrudeInit = extrudeDeclarator?.init
|
|
|
|
if (!extrudeInit) {
|
|
|
|
return new Error('Extrude Init not found.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
extrudeInit.type !== 'CallExpression' &&
|
|
|
|
extrudeInit.type !== 'PipeExpression'
|
|
|
|
) {
|
|
|
|
return new Error('Extrude must be a PipeExpression or CallExpression')
|
|
|
|
}
|
|
|
|
|
|
|
|
return { extrudeDeclarator }
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPathToNodeOfFilletLiteral(
|
|
|
|
pathToExtrudeNode: PathToNode,
|
|
|
|
extrudeDeclarator: VariableDeclarator,
|
|
|
|
tag: string
|
|
|
|
): PathToNode {
|
|
|
|
let pathToFilletObj: PathToNode = []
|
|
|
|
let inFillet = false
|
|
|
|
|
|
|
|
traverse(extrudeDeclarator.init, {
|
|
|
|
enter(node, path) {
|
|
|
|
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
|
|
|
inFillet = true
|
|
|
|
}
|
|
|
|
if (inFillet && node.type === 'ObjectExpression') {
|
|
|
|
if (!hasTag(node, tag)) return false
|
|
|
|
pathToFilletObj = getPathToRadiusLiteral(node, path)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
leave(node) {
|
|
|
|
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
|
|
|
inFillet = false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
let indexOfPipeExpression = pathToExtrudeNode.findIndex(
|
|
|
|
(path) => path[1] === 'PipeExpression'
|
|
|
|
)
|
|
|
|
|
|
|
|
indexOfPipeExpression =
|
|
|
|
indexOfPipeExpression === -1
|
|
|
|
? pathToExtrudeNode.length
|
|
|
|
: indexOfPipeExpression
|
|
|
|
|
|
|
|
return [
|
|
|
|
...pathToExtrudeNode.slice(0, indexOfPipeExpression),
|
|
|
|
...pathToFilletObj,
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasTag(node: ObjectExpression, tag: string): boolean {
|
|
|
|
return node.properties.some((prop) => {
|
|
|
|
if (prop.key.name === 'tags' && prop.value.type === 'ArrayExpression') {
|
|
|
|
return prop.value.elements.some(
|
|
|
|
(element) => element.type === 'Identifier' && element.name === tag
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPathToRadiusLiteral(node: ObjectExpression, path: any): PathToNode {
|
|
|
|
let pathToFilletObj = path
|
|
|
|
node.properties.forEach((prop, index) => {
|
|
|
|
if (prop.key.name === 'radius') {
|
|
|
|
pathToFilletObj.push(
|
|
|
|
['properties', 'ObjectExpression'],
|
|
|
|
[index, 'index'],
|
|
|
|
['value', 'Property']
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return pathToFilletObj
|
|
|
|
}
|
|
|
|
|
|
|
|
function getFilletTag(existingFilletCall: CallExpression): string | null {
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2024-07-15 19:20:32 +10:00
|
|
|
export const hasValidFilletSelection = ({
|
|
|
|
selectionRanges,
|
|
|
|
ast,
|
|
|
|
code,
|
|
|
|
}: {
|
|
|
|
selectionRanges: Selections
|
|
|
|
ast: Program
|
|
|
|
code: string
|
|
|
|
}) => {
|
|
|
|
// case 0: check if there is anything filletable in the scene
|
|
|
|
let extrudeExists = false
|
|
|
|
traverse(ast, {
|
|
|
|
enter(node) {
|
|
|
|
if (node.type === 'CallExpression' && node.callee.name === 'extrude') {
|
|
|
|
extrudeExists = true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if (!extrudeExists) return false
|
|
|
|
|
|
|
|
// case 1: nothing selected, test whether the extrusion exists
|
|
|
|
if (selectionRanges) {
|
|
|
|
if (selectionRanges.codeBasedSelections.length === 0) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
const range0 = selectionRanges.codeBasedSelections[0].range[0]
|
|
|
|
const codeLength = code.length
|
|
|
|
if (range0 === codeLength) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// case 2: sketch segment selected, test whether it is extruded
|
|
|
|
// TODO: add loft / sweep check
|
|
|
|
if (selectionRanges.codeBasedSelections.length > 0) {
|
|
|
|
const isExtruded = hasSketchPipeBeenExtruded(
|
|
|
|
selectionRanges.codeBasedSelections[0],
|
|
|
|
ast
|
|
|
|
)
|
|
|
|
if (isExtruded) {
|
|
|
|
const pathToSelectedNode = getNodePathFromSourceRange(
|
|
|
|
ast,
|
|
|
|
selectionRanges.codeBasedSelections[0].range
|
|
|
|
)
|
|
|
|
const segmentNode = getNodeFromPath<CallExpression>(
|
|
|
|
ast,
|
|
|
|
pathToSelectedNode,
|
|
|
|
'CallExpression'
|
|
|
|
)
|
|
|
|
if (err(segmentNode)) return false
|
|
|
|
if (segmentNode.node.type === 'CallExpression') {
|
|
|
|
const segmentName = segmentNode.node.callee.name
|
|
|
|
if (segmentName in sketchLineHelperMap) {
|
2024-08-28 01:53:33 +02:00
|
|
|
// Add check whether the tag exists at all:
|
|
|
|
if (!(segmentNode.node.arguments.length === 3)) return true
|
|
|
|
// If the tag exists, check if it is already filleted
|
2024-07-15 19:20:32 +10:00
|
|
|
const edges = isTagUsedInFillet({
|
|
|
|
ast,
|
|
|
|
callExp: segmentNode.node,
|
|
|
|
})
|
|
|
|
// edge has already been filleted
|
|
|
|
if (
|
|
|
|
['edge', 'default'].includes(
|
|
|
|
selectionRanges.codeBasedSelections[0].type
|
|
|
|
) &&
|
|
|
|
edges.includes('baseEdge')
|
|
|
|
)
|
|
|
|
return false
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return canFilletSelection(selectionRanges)
|
|
|
|
}
|
|
|
|
|
|
|
|
type EdgeTypes =
|
|
|
|
| 'baseEdge'
|
|
|
|
| 'getNextAdjacentEdge'
|
|
|
|
| 'getPreviousAdjacentEdge'
|
|
|
|
| 'getOppositeEdge'
|
|
|
|
|
|
|
|
export const isTagUsedInFillet = ({
|
|
|
|
ast,
|
|
|
|
callExp,
|
|
|
|
}: {
|
|
|
|
ast: Program
|
|
|
|
callExp: CallExpression
|
|
|
|
}): Array<EdgeTypes> => {
|
|
|
|
const tag = getTagFromCallExpression(callExp)
|
|
|
|
if (err(tag)) return []
|
|
|
|
|
|
|
|
let inFillet = 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
|
|
|
|
}
|
|
|
|
if (inFillet && node.type === 'ObjectExpression') {
|
|
|
|
node.properties.forEach((prop) => {
|
|
|
|
if (
|
|
|
|
prop.key.name === 'tags' &&
|
|
|
|
prop.value.type === 'ArrayExpression'
|
|
|
|
) {
|
|
|
|
inObj = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
inObj &&
|
|
|
|
inFillet &&
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
(node.callee.name === 'getOppositeEdge' ||
|
|
|
|
node.callee.name === 'getNextAdjacentEdge' ||
|
|
|
|
node.callee.name === 'getPreviousAdjacentEdge')
|
|
|
|
) {
|
|
|
|
inTagHelper = node.callee.name
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
inObj &&
|
|
|
|
inFillet &&
|
|
|
|
!inTagHelper &&
|
|
|
|
node.type === 'Identifier' &&
|
|
|
|
node.name === tag
|
|
|
|
) {
|
|
|
|
edges.push('baseEdge')
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
inObj &&
|
|
|
|
inFillet &&
|
|
|
|
inTagHelper &&
|
|
|
|
node.type === 'Identifier' &&
|
|
|
|
node.name === tag
|
|
|
|
) {
|
|
|
|
edges.push(inTagHelper)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
leave: (node) => {
|
|
|
|
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
|
|
|
inFillet = false
|
|
|
|
}
|
|
|
|
if (inFillet && node.type === 'ObjectExpression') {
|
|
|
|
node.properties.forEach((prop) => {
|
|
|
|
if (
|
|
|
|
prop.key.name === 'tags' &&
|
|
|
|
prop.value.type === 'ArrayExpression'
|
|
|
|
) {
|
|
|
|
inObj = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
inObj &&
|
|
|
|
inFillet &&
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
(node.callee.name === 'getOppositeEdge' ||
|
|
|
|
node.callee.name === 'getNextAdjacentEdge' ||
|
|
|
|
node.callee.name === 'getPreviousAdjacentEdge')
|
|
|
|
) {
|
|
|
|
inTagHelper = ''
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
return edges
|
|
|
|
}
|