Files
modeling-app/src/lang/modifyAst/deleteFromSelection.ts
Andrew Varga 479179dd9b #6734 Clean up unused code (#6736)
* remove unused code in modelingMachine

* remove unused actions in featureTreeMachine

* video.pause is not async

* remove unused param in Toolbar

* remove unused rectangleOrigin from getRectangleCallExpressions

* fmt

* parseProjectRoute is not async anymore

* prefix unused params with underscore

* insertNewStartProfileAt/sketchEntryNodePath param is not used

* remove unused constraintType parameter from getRemoveConstraintsTransform

* underscore unused params

* remove unused scale param in segment.ts

* remove unused for in sceneInfra

* remove unused sketchEntryNodePath from sceneEntitiesManager methods

* remove unused shouldTearDown param

* remove unused planeNodePath param from setup draft methods

* remove unused ast param
2025-05-08 06:58:30 -04:00

434 lines
15 KiB
TypeScript

import type { Models } from '@kittycad/lib'
import type { ImportStatement } from '@rust/kcl-lib/bindings/ImportStatement'
import type { Node } from '@rust/kcl-lib/bindings/Node'
import {
createCallExpressionStdLibKw,
createLiteral,
createObjectExpression,
} from '@src/lang/create'
import { deleteEdgeTreatment } from '@src/lang/modifyAst/addEdgeTreatment'
import {
getNodeFromPath,
traverse,
findPipesWithImportAlias,
} from '@src/lang/queryAst'
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
import {
expandCap,
expandPlane,
expandWall,
getArtifactOfTypes,
getArtifactsOfTypes,
getFaceCodeRef,
} from '@src/lang/std/artifactGraph'
import type {
ArtifactGraph,
CallExpressionKw,
KclValue,
PathToNode,
PipeExpression,
Program,
VariableDeclarator,
VariableMap,
} from '@src/lang/wasm'
import type { Selection } from '@src/lib/selections'
import { err, reportRejection } from '@src/lib/trap'
import { isArray, roundOff } from '@src/lib/utils'
export async function deleteFromSelection(
ast: Node<Program>,
selection: Selection,
variables: VariableMap,
artifactGraph: ArtifactGraph,
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
({}) as any
): Promise<Node<Program> | Error> {
const astClone = structuredClone(ast)
let deletionArtifact = selection.artifact
// Coerce sketch artifacts to their plane first
if (selection.artifact?.type === 'startSketchOnPlane') {
const planeArtifact = getArtifactOfTypes(
{ key: selection.artifact.planeId, types: ['plane'] },
artifactGraph
)
if (!err(planeArtifact)) {
deletionArtifact = planeArtifact
}
} else if (selection.artifact?.type === 'startSketchOnFace') {
const planeArtifact = getArtifactOfTypes(
{ key: selection.artifact.faceId, types: ['plane'] },
artifactGraph
)
if (!err(planeArtifact)) {
deletionArtifact = planeArtifact
}
}
if (
(deletionArtifact?.type === 'plane' ||
deletionArtifact?.type === 'cap' ||
deletionArtifact?.type === 'wall') &&
deletionArtifact?.pathIds?.length
) {
const plane =
deletionArtifact.type === 'plane'
? expandPlane(deletionArtifact, artifactGraph)
: deletionArtifact.type === 'wall'
? expandWall(deletionArtifact, artifactGraph)
: expandCap(deletionArtifact, artifactGraph)
for (const path of plane.paths.sort(
(a, b) => b.codeRef.range?.[0] - a.codeRef.range?.[0]
)) {
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
path.codeRef.pathToNode,
'VariableDeclarator'
)
if (err(varDec)) return varDec
const bodyIndex = Number(varDec.shallowPath[1][0])
astClone.body.splice(bodyIndex, 1)
}
// If it's a cap, we're not going to continue and try to
// delete the extrusion
if (deletionArtifact.type === 'cap' || deletionArtifact.type === 'wall') {
// Delete the sketch node, which would not work if
// we continued down the traditional code path below.
// faceCodeRef's pathToNode is empty for some reason
// so using source range instead
const codeRef = getFaceCodeRef(deletionArtifact)
if (!codeRef) return new Error('Could not find face code ref')
const sketchVarDec = getNodePathFromSourceRange(astClone, codeRef.range)
const sketchBodyIndex = Number(sketchVarDec[1][0])
astClone.body.splice(sketchBodyIndex, 1)
return astClone
}
// If we coerced the artifact from a sketch to a plane,
// this is where we hop off after we delete the sketch variable declaration
if (
selection.artifact?.type === 'startSketchOnPlane' ||
selection.artifact?.type === 'startSketchOnFace'
) {
const sketchVarDec = getNodePathFromSourceRange(
astClone,
selection.artifact.codeRef.range
)
const sketchBodyIndex = Number(sketchVarDec[1][0])
astClone.body.splice(sketchBodyIndex, 1)
return astClone
}
}
// Module import and expression case, need to find and delete both
const statement = getNodeFromPath<ImportStatement>(
astClone,
selection.codeRef.pathToNode,
'ImportStatement'
)
if (
!err(statement) &&
statement.node.type === 'ImportStatement' &&
selection.codeRef.pathToNode[1] &&
typeof selection.codeRef.pathToNode[1][0] === 'number'
) {
const pipes = findPipesWithImportAlias(ast, selection.codeRef.pathToNode)
for (const { pathToNode: pathToPipeNode } of pipes.reverse()) {
if (typeof pathToPipeNode[1][0] === 'number') {
const pipeWithImportAliasIndex = pathToPipeNode[1][0]
astClone.body.splice(pipeWithImportAliasIndex, 1)
}
}
const importIndex = selection.codeRef.pathToNode[1][0]
astClone.body.splice(importIndex, 1)
return astClone
}
// Below is all AST-based deletion logic
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
selection?.codeRef?.pathToNode,
'VariableDeclarator'
)
if (err(varDec)) return varDec
if (
((selection?.artifact?.type === 'wall' ||
selection?.artifact?.type === 'cap') &&
varDec.node.init.type === 'PipeExpression') ||
selection.artifact?.type === 'sweep' ||
selection.artifact?.type === 'plane' ||
selection.artifact?.type === 'compositeSolid' ||
selection.artifact?.type === 'helix' ||
!selection.artifact // aka expected to be a shell at this point
) {
let extrudeNameToDelete = ''
let pathToNode: PathToNode | null = null
if (
selection.artifact &&
selection.artifact.type !== 'sweep' &&
selection.artifact.type !== 'plane' &&
selection.artifact.type !== 'compositeSolid' &&
selection.artifact.type !== 'helix'
) {
const varDecName = varDec.node.id.name
traverse(astClone, {
enter: (node, path) => {
if (node.type === 'VariableDeclaration') {
const dec = node.declaration
if (
dec.init.type === 'CallExpressionKw' &&
(dec.init.callee.name.name === 'extrude' ||
dec.init.callee.name.name === 'revolve') &&
dec.init.unlabeled?.type === 'Name' &&
dec.init.unlabeled?.name.name === varDecName
) {
pathToNode = path
extrudeNameToDelete = dec.id.name
}
if (
dec.init.type === 'CallExpressionKw' &&
dec.init.callee.name.name === 'loft' &&
dec.init.unlabeled !== null &&
dec.init.unlabeled.type === 'ArrayExpression' &&
dec.init.unlabeled.elements.some(
(a) => a.type === 'Name' && a.name.name === varDecName
)
) {
pathToNode = path
extrudeNameToDelete = dec.id.name
}
}
},
})
if (!pathToNode) return new Error('Could not find extrude variable')
} else {
pathToNode = selection.codeRef.pathToNode
if (varDec.node.type === 'VariableDeclarator') {
extrudeNameToDelete = varDec.node.id.name
} else if (varDec.node.type === 'CallExpressionKw') {
const callExp = getNodeFromPath<CallExpressionKw>(
astClone,
pathToNode,
['CallExpressionKw']
)
if (err(callExp)) return callExp
extrudeNameToDelete = callExp.node.callee.name.name
} else {
return new Error('Could not find extrude variable or call')
}
}
const expressionIndex = pathToNode[1][0] as number
astClone.body.splice(expressionIndex, 1)
if (extrudeNameToDelete) {
await new Promise((resolve) => {
;(async () => {
const pathsDependingOnExtrude: {
[id: string]: {
path: PathToNode
variable: KclValue
}
} = {}
const roundLiteral = (x: number) => createLiteral(roundOff(x))
const modificationDetails: {
parentPipe: PipeExpression['body']
parentInit: VariableDeclarator
faceDetails: Models['FaceIsPlanar_type']
lastKey: number | string
}[] = []
const wallArtifact =
selection.artifact?.type === 'wall'
? selection.artifact
: selection.artifact?.type === 'segment' &&
selection.artifact.surfaceId
? getArtifactOfTypes(
{ key: selection.artifact.surfaceId, types: ['wall'] },
artifactGraph
)
: null
if (err(wallArtifact)) return
if (wallArtifact) {
const sweep = getArtifactOfTypes(
{ key: wallArtifact.sweepId, types: ['sweep'] },
artifactGraph
)
if (err(sweep)) return
const wallsWithDependencies = Array.from(
getArtifactsOfTypes(
{ keys: sweep.surfaceIds, types: ['wall', 'cap'] },
artifactGraph
).values()
).filter((wall) => wall?.pathIds?.length)
const wallIds = wallsWithDependencies.map((wall) => wall.id)
Object.entries(variables).forEach(([_key, _var]) => {
if (
_var?.type === 'Face' &&
wallIds.includes(_var.value.artifactId)
) {
const artifact = getArtifactOfTypes(
{
key: _var.value.artifactId,
types: ['wall', 'cap', 'plane'],
},
artifactGraph
)
if (err(artifact)) return
const sourceRange = getFaceCodeRef(artifact)?.range
if (!sourceRange) return
const pathToStartSketchOn = getNodePathFromSourceRange(
astClone,
sourceRange
)
pathsDependingOnExtrude[_var.value.id] = {
path: pathToStartSketchOn,
variable: _var,
}
}
})
}
for (const { path, variable } of Object.values(
pathsDependingOnExtrude
)) {
// `parentPipe` and `parentInit` are the exact same node, but because it could either be an array or on object node
// putting them in two different variables was the only way to get TypeScript to stop complaining
// the reason why we're grabbing the parent and the last key is because we want to mutate the ast
// so `parent[lastKey]` does the trick, if there's a better way of doing this I'm all years
const parentPipe = getNodeFromPath<PipeExpression['body']>(
astClone,
path.slice(0, -1)
)
const parentInit = getNodeFromPath<VariableDeclarator>(
astClone,
path.slice(0, -1)
)
if (err(parentPipe) || err(parentInit)) {
return
}
if (!variable) return new Error('Could not find sketch')
const artifactId =
variable.type === 'Sketch'
? variable.value.artifactId
: variable.type === 'Face'
? variable.value.artifactId
: ''
if (!artifactId) return new Error('Sketch not on anything')
const onId =
variable.type === 'Sketch'
? variable.value.on.id
: variable.type === 'Face'
? variable.value.id
: ''
if (!onId) return new Error('Sketch not on anything')
// Can't kick off multiple requests at once as getFaceDetails
// is three engine calls in one and they conflict
const faceDetails = await getFaceDetails(onId)
if (
!(
faceDetails.origin &&
faceDetails.x_axis &&
faceDetails.y_axis &&
faceDetails.z_axis
)
) {
return
}
const lastKey = path.slice(-1)[0][0]
modificationDetails.push({
parentPipe: parentPipe.node,
parentInit: parentInit.node,
faceDetails,
lastKey,
})
}
for (const {
parentInit,
parentPipe,
faceDetails,
lastKey,
} of modificationDetails) {
if (
!(
faceDetails.origin &&
faceDetails.x_axis &&
faceDetails.y_axis &&
faceDetails.z_axis
)
) {
continue
}
const expression = createCallExpressionStdLibKw(
'startSketchOn',
createObjectExpression({
origin: createObjectExpression({
x: roundLiteral(faceDetails.origin.x),
y: roundLiteral(faceDetails.origin.y),
z: roundLiteral(faceDetails.origin.z),
}),
xAxis: createObjectExpression({
x: roundLiteral(faceDetails.x_axis.x),
y: roundLiteral(faceDetails.x_axis.y),
z: roundLiteral(faceDetails.x_axis.z),
}),
yAxis: createObjectExpression({
x: roundLiteral(faceDetails.y_axis.x),
y: roundLiteral(faceDetails.y_axis.y),
z: roundLiteral(faceDetails.y_axis.z),
}),
zAxis: createObjectExpression({
x: roundLiteral(faceDetails.z_axis.x),
y: roundLiteral(faceDetails.z_axis.y),
z: roundLiteral(faceDetails.z_axis.z),
}),
}),
[]
)
if (
parentInit.type === 'VariableDeclarator' &&
lastKey === 'init'
) {
parentInit[lastKey] = expression
} else if (isArray(parentPipe) && typeof lastKey === 'number') {
parentPipe[lastKey] = expression
}
}
resolve(true)
})().catch(reportRejection)
})
}
// await prom
return astClone
} else if (selection.artifact?.type === 'edgeCut') {
return deleteEdgeTreatment(astClone, selection)
} else if (varDec.node.init.type === 'PipeExpression') {
const pipeBody = varDec.node.init.body
const doNotDeleteProfileIfItHasBeenExtruded = !(
selection?.artifact?.type === 'segment' && selection?.artifact?.surfaceId
)
if (
pipeBody[0].type === 'CallExpressionKw' &&
doNotDeleteProfileIfItHasBeenExtruded &&
(pipeBody[0].callee.name.name === 'startSketchOn' ||
pipeBody[0].callee.name.name === 'startProfile')
) {
// remove varDec
const varDecIndex = varDec.shallowPath[1][0] as number
astClone.body.splice(varDecIndex, 1)
return astClone
}
} else if (
// single expression profiles
varDec.node.init.type === 'CallExpressionKw' &&
['circleThreePoint', 'circle'].includes(varDec.node.init.callee.name.name)
) {
const varDecIndex = varDec.shallowPath[1][0] as number
astClone.body.splice(varDecIndex, 1)
return astClone
}
return new Error('Selection not recognised, could not delete')
}