2024-07-15 19:20:32 +10:00
|
|
|
import {
|
|
|
|
CallExpression,
|
2024-10-11 23:25:51 +02:00
|
|
|
Expr,
|
|
|
|
Identifier,
|
2024-07-15 19:20:32 +10:00
|
|
|
ObjectExpression,
|
|
|
|
PathToNode,
|
|
|
|
Program,
|
|
|
|
VariableDeclaration,
|
|
|
|
VariableDeclarator,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketchFromKclValue,
|
2024-07-15 19:20:32 +10:00
|
|
|
} from '../wasm'
|
|
|
|
import {
|
|
|
|
createCallExpressionStdLib,
|
|
|
|
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-12-09 15:20:48 -05:00
|
|
|
import { Selection, Selections } from 'lib/selections'
|
2024-08-26 08:07:20 +02:00
|
|
|
import { KclCommandValue } from 'lib/commandTypes'
|
|
|
|
import {
|
2024-11-21 15:04:30 +11:00
|
|
|
Artifact,
|
2024-08-26 08:07:20 +02:00
|
|
|
ArtifactGraph,
|
2024-09-17 13:22:53 -05:00
|
|
|
getSweepFromSuspectedPath,
|
2024-08-26 08:07:20 +02:00
|
|
|
} from 'lang/std/artifactGraph'
|
2024-11-16 16:49:44 -05:00
|
|
|
import {
|
|
|
|
kclManager,
|
|
|
|
engineCommandManager,
|
|
|
|
editorManager,
|
|
|
|
codeManager,
|
|
|
|
} from 'lib/singletons'
|
2024-10-30 16:52:17 -04:00
|
|
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
2024-08-26 08:07:20 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// Edge Treatment Types
|
|
|
|
export enum EdgeTreatmentType {
|
|
|
|
Chamfer = 'chamfer',
|
|
|
|
Fillet = 'fillet',
|
|
|
|
}
|
2024-08-26 08:07:20 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
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
|
2025-01-08 16:05:24 +01:00
|
|
|
export async function applyEdgeTreatmentToSelection(
|
2024-10-30 16:52:17 -04:00
|
|
|
ast: Node<Program>,
|
2024-08-26 08:07:20 +02:00
|
|
|
selection: Selections,
|
2024-12-02 21:43:59 +01:00
|
|
|
parameters: EdgeTreatmentParameters
|
2025-01-08 16:05:24 +01:00
|
|
|
): Promise<void | Error> {
|
2024-12-02 21:43:59 +01:00
|
|
|
// 1. clone and modify with edge treatment and tag
|
|
|
|
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
|
2024-09-09 12:15:16 +02:00
|
|
|
if (err(result)) return result
|
2024-12-02 21:43:59 +01:00
|
|
|
const { modifiedAst, pathToEdgeTreatmentNode } = result
|
2024-09-09 12:15:16 +02:00
|
|
|
|
2024-10-03 11:14:02 +02:00
|
|
|
// 2. update ast
|
2025-01-08 16:05:24 +01:00
|
|
|
await updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode)
|
2024-09-09 12:15:16 +02:00
|
|
|
}
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
export function modifyAstWithEdgeTreatmentAndTag(
|
2024-10-30 16:52:17 -04:00
|
|
|
ast: Node<Program>,
|
2024-11-21 15:04:30 +11:00
|
|
|
selections: Selections,
|
2024-12-02 21:43:59 +01:00
|
|
|
parameters: EdgeTreatmentParameters
|
|
|
|
):
|
|
|
|
| { modifiedAst: Node<Program>; pathToEdgeTreatmentNode: Array<PathToNode> }
|
|
|
|
| Error {
|
2024-10-03 11:14:02 +02:00
|
|
|
let clonedAst = structuredClone(ast)
|
|
|
|
const clonedAstForGetExtrude = structuredClone(ast)
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
const astResult = insertParametersIntoAst(clonedAst, parameters)
|
2024-08-26 08:07:20 +02:00
|
|
|
if (err(astResult)) return astResult
|
|
|
|
|
|
|
|
const artifactGraph = engineCommandManager.artifactGraph
|
|
|
|
|
2024-10-03 11:14:02 +02:00
|
|
|
// Step 1: modify ast with tags and group them by extrude nodes (bodies)
|
2024-10-11 23:25:51 +02:00
|
|
|
const extrudeToTagsMap: Map<
|
|
|
|
PathToNode,
|
2024-11-21 15:04:30 +11:00
|
|
|
Array<{ tag: string; artifact: Artifact }>
|
2024-10-11 23:25:51 +02:00
|
|
|
> = new Map()
|
2024-10-03 11:14:02 +02:00
|
|
|
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
|
2024-08-26 08:07:20 +02:00
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
for (const selection of selections.graphSelections) {
|
2024-10-03 11:14:02 +02:00
|
|
|
const result = getPathToExtrudeForSegmentSelection(
|
|
|
|
clonedAstForGetExtrude,
|
2024-12-09 15:20:48 -05:00
|
|
|
selection,
|
2024-10-03 11:14:02 +02:00
|
|
|
artifactGraph
|
|
|
|
)
|
|
|
|
if (err(result)) return result
|
|
|
|
const { pathToSegmentNode, pathToExtrudeNode } = result
|
|
|
|
|
|
|
|
const tagResult = mutateAstWithTagForSketchSegment(
|
2024-09-12 21:45:26 +02:00
|
|
|
clonedAst,
|
2024-10-03 11:14:02 +02:00
|
|
|
pathToSegmentNode
|
|
|
|
)
|
|
|
|
if (err(tagResult)) return tagResult
|
|
|
|
const { tag } = tagResult
|
|
|
|
|
|
|
|
// Group tags by their corresponding extrude node
|
|
|
|
const extrudeKey = JSON.stringify(pathToExtrudeNode)
|
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
if (lookupMap.has(extrudeKey) && selection.artifact) {
|
2024-10-03 11:14:02 +02:00
|
|
|
const existingPath = lookupMap.get(extrudeKey)
|
|
|
|
if (!existingPath) return new Error('Path to extrude node not found.')
|
2024-11-21 15:04:30 +11:00
|
|
|
extrudeToTagsMap
|
|
|
|
.get(existingPath)
|
|
|
|
?.push({ tag, artifact: selection.artifact } as const)
|
|
|
|
} else if (selection.artifact) {
|
2024-10-03 11:14:02 +02:00
|
|
|
lookupMap.set(extrudeKey, pathToExtrudeNode)
|
2024-11-21 15:04:30 +11:00
|
|
|
extrudeToTagsMap.set(pathToExtrudeNode, [
|
|
|
|
{ tag, artifact: selection.artifact } as const,
|
|
|
|
])
|
2024-10-03 11:14:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// Step 2: Apply edge treatments for each extrude node (body)
|
|
|
|
let pathToEdgeTreatmentNodes: Array<PathToNode> = []
|
2024-10-11 23:25:51 +02:00
|
|
|
for (const [pathToExtrudeNode, tagInfos] of extrudeToTagsMap.entries()) {
|
2024-12-02 21:43:59 +01:00
|
|
|
// Create an edge treatment expression with multiple tags
|
2024-10-11 23:25:51 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// edge treatment parameter
|
|
|
|
const parameterResult = getParameterNameAndValue(parameters)
|
|
|
|
if (err(parameterResult)) return parameterResult
|
|
|
|
const { parameterName, parameterValue } = parameterResult
|
|
|
|
|
|
|
|
// tag calls
|
2024-11-21 15:04:30 +11:00
|
|
|
const tagCalls = tagInfos.map(({ tag, artifact }) => {
|
|
|
|
return getEdgeTagCall(tag, artifact)
|
2024-10-11 23:25:51 +02:00
|
|
|
})
|
|
|
|
const firstTag = tagCalls[0] // can be Identifier or CallExpression (for opposite and adjacent edges)
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// edge treatment call
|
|
|
|
const edgeTreatmentCall = createCallExpressionStdLib(parameters.type, [
|
2024-10-03 11:14:02 +02:00
|
|
|
createObjectExpression({
|
2024-12-02 21:43:59 +01:00
|
|
|
[parameterName]: parameterValue,
|
2024-10-11 23:25:51 +02:00
|
|
|
tags: createArrayExpression(tagCalls),
|
2024-10-03 11:14:02 +02:00
|
|
|
}),
|
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
|
|
|
|
|
|
|
// Locate the extrude call
|
|
|
|
const locatedExtrudeDeclarator = locateExtrudeDeclarator(
|
|
|
|
clonedAst,
|
|
|
|
pathToExtrudeNode
|
2024-09-12 21:45:26 +02:00
|
|
|
)
|
2024-10-03 11:14:02 +02:00
|
|
|
if (err(locatedExtrudeDeclarator)) return locatedExtrudeDeclarator
|
|
|
|
const { extrudeDeclarator } = locatedExtrudeDeclarator
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// Modify the extrude expression to include this edge treatment expression
|
|
|
|
// CallExpression - no edge treatment
|
|
|
|
// PipeExpression - edge treatment exists or body in sketch pipe
|
2024-10-03 11:14:02 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
let pathToEdgeTreatmentNode: PathToNode
|
2024-10-03 11:14:02 +02:00
|
|
|
|
|
|
|
if (extrudeDeclarator.init.type === 'CallExpression') {
|
2024-12-02 21:43:59 +01:00
|
|
|
// 1. case when no edge treatment exists
|
2024-10-03 11:14:02 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// modify ast with new edge treatment call by mutating the extrude node
|
2024-10-03 11:14:02 +02:00
|
|
|
extrudeDeclarator.init = createPipeExpression([
|
|
|
|
extrudeDeclarator.init,
|
2024-12-02 21:43:59 +01:00
|
|
|
edgeTreatmentCall,
|
2024-10-03 11:14:02 +02:00
|
|
|
])
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// get path to the edge treatment node
|
|
|
|
pathToEdgeTreatmentNode = getPathToNodeOfEdgeTreatmentLiteral(
|
2024-10-03 11:14:02 +02:00
|
|
|
pathToExtrudeNode,
|
|
|
|
extrudeDeclarator,
|
2024-12-02 21:43:59 +01:00
|
|
|
firstTag,
|
|
|
|
parameters
|
2024-10-03 11:14:02 +02:00
|
|
|
)
|
2024-12-02 21:43:59 +01:00
|
|
|
pathToEdgeTreatmentNodes.push(pathToEdgeTreatmentNode)
|
2024-10-03 11:14:02 +02:00
|
|
|
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
|
2024-12-02 21:43:59 +01:00
|
|
|
// 2. case when edge treatment exists or extrude in sketch pipe
|
2024-10-03 11:14:02 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// mutate the extrude node with the new edge treatment call
|
|
|
|
extrudeDeclarator.init.body.push(edgeTreatmentCall)
|
2024-10-03 11:14:02 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// get path to the edge treatment node
|
|
|
|
pathToEdgeTreatmentNode = getPathToNodeOfEdgeTreatmentLiteral(
|
2024-10-03 11:14:02 +02:00
|
|
|
pathToExtrudeNode,
|
|
|
|
extrudeDeclarator,
|
2024-12-02 21:43:59 +01:00
|
|
|
firstTag,
|
|
|
|
parameters
|
2024-10-03 11:14:02 +02:00
|
|
|
)
|
2024-12-02 21:43:59 +01:00
|
|
|
pathToEdgeTreatmentNodes.push(pathToEdgeTreatmentNode)
|
2024-10-03 11:14:02 +02:00
|
|
|
} else {
|
|
|
|
return new Error('Unsupported extrude type.')
|
|
|
|
}
|
2024-09-12 21:45:26 +02:00
|
|
|
}
|
2024-12-02 21:43:59 +01:00
|
|
|
return {
|
|
|
|
modifiedAst: clonedAst,
|
|
|
|
pathToEdgeTreatmentNode: pathToEdgeTreatmentNodes,
|
|
|
|
}
|
2024-08-26 08:07:20 +02:00
|
|
|
}
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
function insertParametersIntoAst(
|
2024-08-26 08:07:20 +02:00
|
|
|
ast: Program,
|
2024-12-02 21:43:59 +01:00
|
|
|
parameters: EdgeTreatmentParameters
|
2024-08-26 08:07:20 +02:00
|
|
|
): { ast: Program } | Error {
|
|
|
|
try {
|
2024-12-02 21:43:59 +01:00
|
|
|
const newAst = structuredClone(ast)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// handle length parameter
|
2024-08-26 08:07:20 +02:00
|
|
|
if (
|
2024-12-02 21:43:59 +01:00
|
|
|
parameters.type === EdgeTreatmentType.Chamfer &&
|
|
|
|
'variableName' in parameters.length &&
|
|
|
|
parameters.length.variableName &&
|
|
|
|
parameters.length.insertIndex !== undefined
|
2024-08-26 08:07:20 +02:00
|
|
|
) {
|
2024-12-02 21:43:59 +01:00
|
|
|
newAst.body.splice(
|
|
|
|
parameters.length.insertIndex,
|
|
|
|
0,
|
|
|
|
parameters.length.variableDeclarationAst
|
|
|
|
)
|
2024-08-26 08:07:20 +02:00
|
|
|
}
|
2024-12-02 21:43:59 +01:00
|
|
|
|
|
|
|
// handle upcoming parameters here (for blend, bevel, etc.)
|
|
|
|
return { ast: newAst }
|
2024-08-26 08:07:20 +02:00
|
|
|
} catch (error) {
|
|
|
|
return new Error(`Failed to handle AST: ${(error as Error).message}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getPathToExtrudeForSegmentSelection(
|
|
|
|
ast: Program,
|
2024-12-09 15:20:48 -05:00
|
|
|
selection: Selection,
|
2024-08-26 08:07:20 +02:00
|
|
|
artifactGraph: ArtifactGraph
|
|
|
|
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
|
|
|
const pathToSegmentNode = getNodePathFromSourceRange(
|
|
|
|
ast,
|
2024-12-09 15:20:48 -05:00
|
|
|
selection.codeRef?.range
|
2024-08-26 08:07:20 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const varDecNode = getNodeFromPath<VariableDeclaration>(
|
|
|
|
ast,
|
|
|
|
pathToSegmentNode,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (err(varDecNode)) return varDecNode
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchVar = varDecNode.node.declaration.id.name
|
2024-08-26 08:07:20 +02:00
|
|
|
|
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(
|
2024-10-30 16:52:17 -04:00
|
|
|
modifiedAst: Node<Program>,
|
2024-12-02 21:43:59 +01:00
|
|
|
pathToEdgeTreatmentNode: Array<PathToNode>
|
2025-01-08 16:05:24 +01:00
|
|
|
): Promise<void> {
|
2024-08-26 08:07:20 +02:00
|
|
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
2024-12-02 21:43:59 +01:00
|
|
|
focusPath: pathToEdgeTreatmentNode,
|
2024-08-26 08:07:20 +02:00
|
|
|
})
|
2024-11-16 16:49:44 -05:00
|
|
|
|
|
|
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
if (updatedAst?.selections) {
|
|
|
|
editorManager.selectRange(updatedAst?.selections)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-09 15:20:48 -05:00
|
|
|
export function mutateAstWithTagForSketchSegment(
|
2024-10-30 16:52:17 -04:00
|
|
|
astClone: Node<Program>,
|
2024-08-26 08:07:20 +02:00
|
|
|
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-12-10 12:11:01 -05:00
|
|
|
export function getEdgeTagCall(
|
2024-10-11 23:25:51 +02:00
|
|
|
tag: string,
|
2024-11-21 15:04:30 +11:00
|
|
|
artifact: Artifact
|
2024-10-30 16:52:17 -04:00
|
|
|
): Node<Identifier | CallExpression> {
|
2024-10-11 23:25:51 +02:00
|
|
|
let tagCall: Expr = createIdentifier(tag)
|
|
|
|
|
|
|
|
// Modify the tag based on selectionType
|
2024-11-21 15:04:30 +11:00
|
|
|
if (artifact.type === 'sweepEdge' && artifact.subType === 'opposite') {
|
2024-10-11 23:25:51 +02:00
|
|
|
tagCall = createCallExpressionStdLib('getOppositeEdge', [tagCall])
|
2024-11-21 15:04:30 +11:00
|
|
|
} else if (artifact.type === 'sweepEdge' && artifact.subType === 'adjacent') {
|
2024-10-11 23:25:51 +02:00
|
|
|
tagCall = createCallExpressionStdLib('getNextAdjacentEdge', [tagCall])
|
|
|
|
}
|
|
|
|
return tagCall
|
|
|
|
}
|
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
function locateExtrudeDeclarator(
|
|
|
|
node: Program,
|
|
|
|
pathToExtrudeNode: PathToNode
|
|
|
|
): { extrudeDeclarator: VariableDeclarator } | Error {
|
2024-11-19 21:30:26 +01:00
|
|
|
const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>(
|
2024-08-26 08:07:20 +02:00
|
|
|
node,
|
|
|
|
pathToExtrudeNode,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
2024-11-19 21:30:26 +01:00
|
|
|
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
|
2024-08-26 08:07:20 +02:00
|
|
|
|
2024-11-19 21:30:26 +01:00
|
|
|
const { node: extrudeVarDecl } = nodeOfExtrudeCall
|
2024-12-07 07:16:04 +13:00
|
|
|
const extrudeDeclarator = extrudeVarDecl.declaration
|
2024-08-26 08:07:20 +02:00
|
|
|
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 }
|
|
|
|
}
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
function getPathToNodeOfEdgeTreatmentLiteral(
|
2024-08-26 08:07:20 +02:00
|
|
|
pathToExtrudeNode: PathToNode,
|
|
|
|
extrudeDeclarator: VariableDeclarator,
|
2024-12-02 21:43:59 +01:00
|
|
|
tag: Identifier | CallExpression,
|
|
|
|
parameters: EdgeTreatmentParameters
|
2024-08-26 08:07:20 +02:00
|
|
|
): PathToNode {
|
2024-12-02 21:43:59 +01:00
|
|
|
let pathToEdgeTreatmentObj: PathToNode = []
|
|
|
|
let inEdgeTreatment = false
|
2024-08-26 08:07:20 +02:00
|
|
|
|
|
|
|
traverse(extrudeDeclarator.init, {
|
|
|
|
enter(node, path) {
|
2024-12-02 21:43:59 +01:00
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
node.callee.name === parameters.type
|
|
|
|
) {
|
|
|
|
inEdgeTreatment = true
|
2024-08-26 08:07:20 +02:00
|
|
|
}
|
2024-12-02 21:43:59 +01:00
|
|
|
if (inEdgeTreatment && node.type === 'ObjectExpression') {
|
2024-08-26 08:07:20 +02:00
|
|
|
if (!hasTag(node, tag)) return false
|
2024-12-02 21:43:59 +01:00
|
|
|
pathToEdgeTreatmentObj = getPathToEdgeTreatmentParameterLiteral(
|
|
|
|
node,
|
|
|
|
path,
|
|
|
|
parameters
|
|
|
|
)
|
2024-08-26 08:07:20 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
leave(node) {
|
2024-12-02 21:43:59 +01:00
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
node.callee.name === parameters.type
|
|
|
|
) {
|
|
|
|
inEdgeTreatment = false
|
2024-08-26 08:07:20 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
let indexOfPipeExpression = pathToExtrudeNode.findIndex(
|
|
|
|
(path) => path[1] === 'PipeExpression'
|
|
|
|
)
|
|
|
|
|
|
|
|
indexOfPipeExpression =
|
|
|
|
indexOfPipeExpression === -1
|
|
|
|
? pathToExtrudeNode.length
|
|
|
|
: indexOfPipeExpression
|
|
|
|
|
|
|
|
return [
|
|
|
|
...pathToExtrudeNode.slice(0, indexOfPipeExpression),
|
2024-12-02 21:43:59 +01:00
|
|
|
...pathToEdgeTreatmentObj,
|
2024-08-26 08:07:20 +02:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2024-10-11 23:25:51 +02:00
|
|
|
function hasTag(
|
|
|
|
node: ObjectExpression,
|
|
|
|
tag: Identifier | CallExpression
|
|
|
|
): boolean {
|
2024-08-26 08:07:20 +02:00
|
|
|
return node.properties.some((prop) => {
|
|
|
|
if (prop.key.name === 'tags' && prop.value.type === 'ArrayExpression') {
|
2024-10-11 23:25:51 +02:00
|
|
|
// if selection is a base edge:
|
|
|
|
if (tag.type === 'Identifier') {
|
|
|
|
return prop.value.elements.some(
|
|
|
|
(element) =>
|
|
|
|
element.type === 'Identifier' && element.name === tag.name
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// if selection is an adjacent or opposite edge:
|
|
|
|
if (tag.type === 'CallExpression') {
|
|
|
|
return prop.value.elements.some(
|
|
|
|
(element) =>
|
|
|
|
element.type === 'CallExpression' &&
|
|
|
|
element.callee.name === tag.callee.name && // edge location
|
|
|
|
element.arguments[0].type === 'Identifier' &&
|
|
|
|
tag.arguments[0].type === 'Identifier' &&
|
|
|
|
element.arguments[0].name === tag.arguments[0].name // tag name
|
|
|
|
)
|
|
|
|
}
|
2024-08-26 08:07:20 +02:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
function getPathToEdgeTreatmentParameterLiteral(
|
|
|
|
node: ObjectExpression,
|
|
|
|
path: any,
|
|
|
|
parameters: EdgeTreatmentParameters
|
|
|
|
): PathToNode {
|
|
|
|
let pathToEdgeTreatmentObj = path
|
|
|
|
const parameterResult = getParameterNameAndValue(parameters)
|
|
|
|
if (err(parameterResult)) return pathToEdgeTreatmentObj
|
|
|
|
const { parameterName } = parameterResult
|
|
|
|
|
2024-08-26 08:07:20 +02:00
|
|
|
node.properties.forEach((prop, index) => {
|
2024-12-02 21:43:59 +01:00
|
|
|
if (prop.key.name === parameterName) {
|
|
|
|
pathToEdgeTreatmentObj.push(
|
2024-08-26 08:07:20 +02:00
|
|
|
['properties', 'ObjectExpression'],
|
|
|
|
[index, 'index'],
|
|
|
|
['value', 'Property']
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
2024-12-02 21:43:59 +01:00
|
|
|
return pathToEdgeTreatmentObj
|
2024-08-26 08:07:20 +02:00
|
|
|
}
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
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}')
|
|
|
|
}
|
|
|
|
}
|
2024-08-26 08:07:20 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// 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 hasValidEdgeTreatmentSelection = ({
|
2024-07-15 19:20:32 +10:00
|
|
|
selectionRanges,
|
|
|
|
ast,
|
|
|
|
code,
|
|
|
|
}: {
|
|
|
|
selectionRanges: Selections
|
2024-10-30 16:52:17 -04:00
|
|
|
ast: Node<Program>
|
2024-07-15 19:20:32 +10:00
|
|
|
code: string
|
|
|
|
}) => {
|
2024-12-02 21:43:59 +01:00
|
|
|
// check if there is anything valid for the edge treatment in the scene
|
2024-07-15 19:20:32 +10:00
|
|
|
let extrudeExists = false
|
|
|
|
traverse(ast, {
|
|
|
|
enter(node) {
|
2024-12-02 21:43:59 +01:00
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
(node.callee.name === 'extrude' || node.callee.name === 'revolve')
|
|
|
|
) {
|
2024-07-15 19:20:32 +10:00
|
|
|
extrudeExists = true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if (!extrudeExists) return false
|
|
|
|
|
2024-10-11 23:25:51 +02:00
|
|
|
// check if nothing is selected
|
2024-11-21 15:04:30 +11:00
|
|
|
if (selectionRanges.graphSelections.length === 0) {
|
2024-10-11 23:25:51 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if selection is last string in code
|
2024-11-21 15:04:30 +11:00
|
|
|
if (selectionRanges.graphSelections[0]?.codeRef?.range[0] === code.length) {
|
2024-10-11 23:25:51 +02:00
|
|
|
return true
|
2024-07-15 19:20:32 +10:00
|
|
|
}
|
|
|
|
|
2024-10-11 23:25:51 +02:00
|
|
|
// selection exists:
|
2024-11-21 15:04:30 +11:00
|
|
|
for (const selection of selectionRanges.graphSelections) {
|
2024-10-11 23:25:51 +02:00
|
|
|
// check if all selections are in sketchLineHelperMap
|
2024-10-30 16:52:17 -04:00
|
|
|
const segmentNode = getNodeFromPath<Node<CallExpression>>(
|
2024-10-11 23:25:51 +02:00
|
|
|
ast,
|
2024-11-21 15:04:30 +11:00
|
|
|
selection.codeRef.pathToNode,
|
2024-10-11 23:25:51 +02:00
|
|
|
'CallExpression'
|
2024-07-15 19:20:32 +10:00
|
|
|
)
|
2024-10-11 23:25:51 +02:00
|
|
|
if (err(segmentNode)) return false
|
|
|
|
if (segmentNode.node.type !== 'CallExpression') {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (!(segmentNode.node.callee.name in sketchLineHelperMap)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if selection is extruded
|
|
|
|
// TODO: option 1 : extrude is in the sketch pipe
|
|
|
|
|
|
|
|
// option 2: extrude is outside the sketch pipe
|
|
|
|
const extrudeExists = hasSketchPipeBeenExtruded(selection, ast)
|
|
|
|
if (err(extrudeExists)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (!extrudeExists) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if tag exists for the selection
|
|
|
|
let tagExists = false
|
|
|
|
let tag = ''
|
|
|
|
traverse(segmentNode.node, {
|
|
|
|
enter(node) {
|
|
|
|
if (node.type === 'TagDeclarator') {
|
|
|
|
tagExists = true
|
|
|
|
tag = node.value
|
2024-07-15 19:20:32 +10:00
|
|
|
}
|
2024-10-11 23:25:51 +02:00
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// check if tag is used in edge treatment
|
2024-11-21 15:04:30 +11:00
|
|
|
if (tagExists && selection.artifact) {
|
2024-10-11 23:25:51 +02:00
|
|
|
// create tag call
|
2024-11-21 15:04:30 +11:00
|
|
|
let tagCall: Expr = getEdgeTagCall(tag, selection.artifact)
|
2024-10-11 23:25:51 +02:00
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
// check if tag is used in edge treatment
|
|
|
|
let inEdgeTreatment = false
|
|
|
|
let tagUsedInEdgeTreatment = false
|
|
|
|
|
2024-10-11 23:25:51 +02:00
|
|
|
traverse(ast, {
|
|
|
|
enter(node) {
|
2024-12-02 21:43:59 +01:00
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
isEdgeTreatmentType(node.callee.name)
|
|
|
|
) {
|
|
|
|
inEdgeTreatment = true
|
2024-10-11 23:25:51 +02:00
|
|
|
}
|
2024-12-02 21:43:59 +01:00
|
|
|
if (inEdgeTreatment && node.type === 'ObjectExpression') {
|
2024-10-11 23:25:51 +02:00
|
|
|
if (hasTag(node, tagCall)) {
|
2024-12-02 21:43:59 +01:00
|
|
|
tagUsedInEdgeTreatment = true
|
2024-10-11 23:25:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
leave(node) {
|
2024-12-02 21:43:59 +01:00
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
isEdgeTreatmentType(node.callee.name)
|
|
|
|
) {
|
|
|
|
inEdgeTreatment = false
|
2024-10-11 23:25:51 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2024-12-02 21:43:59 +01:00
|
|
|
if (tagUsedInEdgeTreatment) {
|
2024-10-11 23:25:51 +02:00
|
|
|
return false
|
2024-07-15 19:20:32 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-10-11 23:25:51 +02:00
|
|
|
return true
|
2024-07-15 19:20:32 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
type EdgeTypes =
|
|
|
|
| 'baseEdge'
|
|
|
|
| 'getNextAdjacentEdge'
|
|
|
|
| 'getPreviousAdjacentEdge'
|
|
|
|
| 'getOppositeEdge'
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
export const isTagUsedInEdgeTreatment = ({
|
2024-07-15 19:20:32 +10:00
|
|
|
ast,
|
|
|
|
callExp,
|
|
|
|
}: {
|
2024-10-30 16:52:17 -04:00
|
|
|
ast: Node<Program>
|
2024-07-15 19:20:32 +10:00
|
|
|
callExp: CallExpression
|
|
|
|
}): Array<EdgeTypes> => {
|
|
|
|
const tag = getTagFromCallExpression(callExp)
|
|
|
|
if (err(tag)) return []
|
|
|
|
|
2024-12-02 21:43:59 +01:00
|
|
|
let inEdgeTreatment = false
|
2024-07-15 19:20:32 +10:00
|
|
|
let inObj = false
|
|
|
|
let inTagHelper: EdgeTypes | '' = ''
|
|
|
|
const edges: Array<EdgeTypes> = []
|
2024-12-02 21:43:59 +01:00
|
|
|
|
2024-07-15 19:20:32 +10:00
|
|
|
traverse(ast, {
|
|
|
|
enter: (node) => {
|
2024-12-02 21:43:59 +01:00
|
|
|
// Check if we are entering an edge treatment call
|
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
isEdgeTreatmentType(node.callee.name)
|
|
|
|
) {
|
|
|
|
inEdgeTreatment = true
|
2024-07-15 19:20:32 +10:00
|
|
|
}
|
2024-12-02 21:43:59 +01:00
|
|
|
if (inEdgeTreatment && node.type === 'ObjectExpression') {
|
2024-07-15 19:20:32 +10:00
|
|
|
node.properties.forEach((prop) => {
|
|
|
|
if (
|
|
|
|
prop.key.name === 'tags' &&
|
|
|
|
prop.value.type === 'ArrayExpression'
|
|
|
|
) {
|
|
|
|
inObj = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
inObj &&
|
2024-12-02 21:43:59 +01:00
|
|
|
inEdgeTreatment &&
|
2024-07-15 19:20:32 +10:00
|
|
|
node.type === 'CallExpression' &&
|
2024-12-02 21:43:59 +01:00
|
|
|
isEdgeType(node.callee.name)
|
2024-07-15 19:20:32 +10:00
|
|
|
) {
|
|
|
|
inTagHelper = node.callee.name
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
inObj &&
|
2024-12-02 21:43:59 +01:00
|
|
|
inEdgeTreatment &&
|
2024-07-15 19:20:32 +10:00
|
|
|
!inTagHelper &&
|
|
|
|
node.type === 'Identifier' &&
|
|
|
|
node.name === tag
|
|
|
|
) {
|
|
|
|
edges.push('baseEdge')
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
inObj &&
|
2024-12-02 21:43:59 +01:00
|
|
|
inEdgeTreatment &&
|
2024-07-15 19:20:32 +10:00
|
|
|
inTagHelper &&
|
|
|
|
node.type === 'Identifier' &&
|
|
|
|
node.name === tag
|
|
|
|
) {
|
|
|
|
edges.push(inTagHelper)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
leave: (node) => {
|
2024-12-02 21:43:59 +01:00
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
isEdgeTreatmentType(node.callee.name)
|
|
|
|
) {
|
|
|
|
inEdgeTreatment = false
|
2024-07-15 19:20:32 +10:00
|
|
|
}
|
2024-12-02 21:43:59 +01:00
|
|
|
if (inEdgeTreatment && node.type === 'ObjectExpression') {
|
2024-07-15 19:20:32 +10:00
|
|
|
node.properties.forEach((prop) => {
|
|
|
|
if (
|
|
|
|
prop.key.name === 'tags' &&
|
|
|
|
prop.value.type === 'ArrayExpression'
|
|
|
|
) {
|
|
|
|
inObj = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
inObj &&
|
2024-12-02 21:43:59 +01:00
|
|
|
inEdgeTreatment &&
|
2024-07-15 19:20:32 +10:00
|
|
|
node.type === 'CallExpression' &&
|
2024-12-02 21:43:59 +01:00
|
|
|
isEdgeType(node.callee.name)
|
2024-07-15 19:20:32 +10:00
|
|
|
) {
|
|
|
|
inTagHelper = ''
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
return edges
|
|
|
|
}
|