Add edit flow for Shell thickness (#5525)

* WIP: Add edit flow for Shell thickness
Would fix #5406 but not like this :sad:

* Early win with working edit on shell

* Extend to logic to walls with tags

* Remove skip

* Add wall test

* Fixing inconsistencies in code. Tests not working on win

* Refactor addShell for consitency

* Clean up

* More clean up

* Add validation on both params

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Passing tests

* Prettier

* Change from test.skip to comment

* Clean up for review

* Add review suggestions and disable thickness validator

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Remove uneccessary changes

* Add edit flow for Shell thickness
Fixes #5406

* Enable edit step in test that works only if the start code is properly formatted

* Clean up for review

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Pierre Jacquier
2025-03-14 16:05:41 -04:00
committed by GitHub
parent e9806b83d7
commit 4741d9592b
7 changed files with 420 additions and 162 deletions

View File

@ -1,4 +1,5 @@
import {
Expr,
PathToNode,
VariableDeclaration,
VariableDeclarator,
@ -13,7 +14,7 @@ import {
Selection,
updateSelections,
} from 'lib/selections'
import { assign, fromPromise, fromCallback, setup } from 'xstate'
import { assign, fromPromise, setup } from 'xstate'
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
import { isNodeSafeToReplacePath } from 'lang/queryAst'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
@ -43,7 +44,10 @@ import { revolveSketch } from 'lang/modifyAst/addRevolve'
import {
addHelix,
addOffsetPlane,
addShell,
addSweep,
createIdentifier,
createLiteral,
extrudeSketch,
loftSketches,
} from 'lang/modifyAst'
@ -52,6 +56,8 @@ import {
ChamferParameters,
EdgeTreatmentType,
FilletParameters,
getPathToExtrudeForSegmentSelection,
mutateAstWithTagForSketchSegment,
} from 'lang/modifyAst/addEdgeTreatment'
import { getNodeFromPath } from '../lang/queryAst'
import {
@ -78,7 +84,6 @@ import { ToolbarModeName } from 'lib/toolbar'
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { Mesh, Vector3 } from 'three'
import { MachineManager } from 'components/MachineManagerProvider'
import { addShell } from 'lang/modifyAst/addShell'
import { KclCommandValue } from 'lib/commandTypes'
import { ModelingMachineContext } from 'components/ModelingMachineProvider'
import {
@ -1994,42 +1999,162 @@ export const modelingMachine = setup({
// Extract inputs
const ast = kclManager.ast
const { selection, thickness } = input
const { selection, thickness, nodeToEdit } = input
let variableName: string | undefined = undefined
let insertIndex: number | undefined = undefined
// Insert the thickness variable if it exists
if (
'variableName' in thickness &&
thickness.variableName &&
thickness.insertIndex !== undefined
) {
const newBody = [...ast.body]
newBody.splice(
thickness.insertIndex,
0,
thickness.variableDeclarationAst
// If this is an edit flow, first we're going to remove the old extrusion
if (nodeToEdit && typeof nodeToEdit[1][0] === 'number') {
// Extract the plane name from the node to edit
const variableNode = getNodeFromPath<VariableDeclaration>(
ast,
nodeToEdit,
'VariableDeclaration'
)
if (err(variableNode)) {
console.error('Error extracting name')
} else {
variableName = variableNode.node.declaration.id.name
}
// Removing the old statement
const newBody = [...ast.body]
newBody.splice(nodeToEdit[1][0], 1)
ast.body = newBody
insertIndex = nodeToEdit[1][0]
}
// Turn the selection into the faces list
const clonedAstForGetExtrude = structuredClone(ast)
const faces: Expr[] = []
let pathToExtrudeNode: PathToNode | undefined = undefined
for (const graphSelection of selection.graphSelections) {
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude,
graphSelection,
engineCommandManager.artifactGraph
)
if (err(extrudeLookupResult)) {
return new Error(
"Couldn't find extrude paths from getPathToExtrudeForSegmentSelection",
{ cause: extrudeLookupResult }
)
}
const extrudeNode = getNodeFromPath<VariableDeclaration>(
ast,
extrudeLookupResult.pathToExtrudeNode,
'VariableDeclaration'
)
if (err(extrudeNode)) {
return new Error("Couldn't find extrude node from selection", {
cause: extrudeNode,
})
}
const segmentNode = getNodeFromPath<VariableDeclaration>(
ast,
extrudeLookupResult.pathToSegmentNode,
'VariableDeclaration'
)
if (err(segmentNode)) {
return new Error("Couldn't find segment node from selection", {
cause: segmentNode,
})
}
if (
extrudeNode.node.declaration.init.type === 'CallExpression' ||
extrudeNode.node.declaration.init.type === 'CallExpressionKw'
) {
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
} else if (
segmentNode.node.declaration.init.type === 'PipeExpression'
) {
pathToExtrudeNode = extrudeLookupResult.pathToSegmentNode
} else {
return new Error(
"Couldn't find extrude node that was either a call expression or a pipe",
{ cause: segmentNode }
)
}
const selectedArtifact = graphSelection.artifact
if (!selectedArtifact) {
return new Error('Bad artifact from selection')
}
// Check on the selection, and handle the wall vs cap casees
let expr: Expr
if (selectedArtifact.type === 'cap') {
expr = createLiteral(selectedArtifact.subType)
} else if (selectedArtifact.type === 'wall') {
const tagResult = mutateAstWithTagForSketchSegment(
ast,
extrudeLookupResult.pathToSegmentNode
)
if (err(tagResult)) {
return tagResult
}
const { tag } = tagResult
expr = createIdentifier(tag)
} else {
return new Error('Artifact is neither a cap nor a wall')
}
faces.push(expr)
}
if (!pathToExtrudeNode) {
return new Error('No path to extrude node found')
}
const extrudeNode = getNodeFromPath<VariableDeclarator>(
ast,
pathToExtrudeNode,
'VariableDeclarator'
)
if (err(extrudeNode)) {
return new Error("Couldn't find extrude node", { cause: extrudeNode })
}
// Perform the shell op
const shellResult = addShell({
const sweepName = extrudeNode.node.id.name
const addResult = addShell({
node: ast,
selection,
artifactGraph: engineCommandManager.artifactGraph,
sweepName,
faces: faces,
thickness:
'variableName' in thickness
? thickness.variableIdentifierAst
: thickness.valueAst,
insertIndex,
variableName,
})
if (err(shellResult)) {
return err(shellResult)
// Insert the thickness variable if the user has provided a variable name
if (
'variableName' in thickness &&
thickness.variableName &&
typeof addResult.pathToNode[1][0] === 'number'
) {
const insertIndex = Math.min(
addResult.pathToNode[1][0],
thickness.insertIndex
)
const newBody = [...addResult.modifiedAst.body]
newBody.splice(insertIndex, 0, thickness.variableDeclarationAst)
addResult.modifiedAst.body = newBody
// Since we inserted a new variable, we need to update the path to the extrude argument
addResult.pathToNode[1][0]++
}
const updateAstResult = await kclManager.updateAst(
shellResult.modifiedAst,
addResult.modifiedAst,
true,
{
focusPath: [shellResult.pathToNode],
focusPath: [addResult.pathToNode],
}
)