Extend point-and-click edit flow to non-pipe Chamfer and Fillet (#6767)
* enable non-piped fillets and chamfers * reorder chamferAstMod * tsc * editEdgeTreatment + refactor + hookup * remove unused stuff * test * typos Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> * else else else * Apply suggestions from code review pierre edits Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com> * const * parameterName * fmt * efficiency ! * graphite being helpful Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --------- Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
This commit is contained in:
@ -2321,11 +2321,12 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
})
|
||||
})
|
||||
|
||||
test(`Fillet point-and-click edit rejected when not in pipe`, async ({
|
||||
test(`Fillet point-and-click edit standalone expression`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
@ -2339,23 +2340,44 @@ profile001 = circle(
|
||||
extrude001 = extrude(profile001, length = 100)
|
||||
fillet001 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
|
||||
`
|
||||
await test.step(`Initial test setup`, async () => {
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
await test.step('Double-click in feature tree and expect error toast', async () => {
|
||||
})
|
||||
await test.step('Edit fillet', async () => {
|
||||
await toolbar.openPane('feature-tree')
|
||||
await toolbar.closePane('code')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Fillet', 0)
|
||||
await operationButton.dblclick({ button: 'left' })
|
||||
await expect(
|
||||
page.getByText(
|
||||
'Only chamfer and fillet in pipe expressions are supported for edits'
|
||||
)
|
||||
).toBeVisible()
|
||||
await page.waitForTimeout(1000)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Fillet',
|
||||
currentArgKey: 'radius',
|
||||
currentArgValue: '5',
|
||||
headerArguments: {
|
||||
Radius: '5',
|
||||
},
|
||||
highlightedHeaderArg: 'radius',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await page.keyboard.insertText('20')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Radius: '20',
|
||||
},
|
||||
commandName: 'Fillet',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
await test.step('Confirm changes', async () => {
|
||||
await toolbar.openPane('code')
|
||||
await toolbar.closePane('feature-tree')
|
||||
await editor.expectEditor.toContain('radius = 20')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -371,7 +371,7 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
||||
}
|
||||
|
||||
// apply edge treatment to selection
|
||||
const result = modifyAstWithEdgeTreatmentAndTag(
|
||||
const result = await modifyAstWithEdgeTreatmentAndTag(
|
||||
ast,
|
||||
selection,
|
||||
parameters,
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
createLocalName,
|
||||
createPipeExpression,
|
||||
} from '@src/lang/create'
|
||||
import { updateModelingState } from '@src/lang/modelingWorkflows'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
hasSketchPipeBeenExtruded,
|
||||
@ -39,7 +38,6 @@ import type {
|
||||
VariableDeclarator,
|
||||
} from '@src/lang/wasm'
|
||||
import type { KclCommandValue } from '@src/lib/commandTypes'
|
||||
import { EXECUTION_TYPE_REAL } from '@src/lib/constants'
|
||||
import type { Selection, Selections } from '@src/lib/selections'
|
||||
import { err } from '@src/lib/trap'
|
||||
import { isArray } from '@src/lib/utils'
|
||||
@ -65,43 +63,7 @@ export interface FilletParameters {
|
||||
export type EdgeTreatmentParameters = ChamferParameters | FilletParameters
|
||||
|
||||
// Apply Edge Treatment (Fillet or Chamfer) To Selection
|
||||
export async function applyEdgeTreatmentToSelection(
|
||||
ast: Node<Program>,
|
||||
selection: Selections,
|
||||
parameters: EdgeTreatmentParameters,
|
||||
dependencies: {
|
||||
kclManager: KclManager
|
||||
engineCommandManager: EngineCommandManager
|
||||
editorManager: EditorManager
|
||||
codeManager: CodeManager
|
||||
}
|
||||
): Promise<void | Error> {
|
||||
// 1. clone and modify with edge treatment and tag
|
||||
const result = modifyAstWithEdgeTreatmentAndTag(
|
||||
ast,
|
||||
selection,
|
||||
parameters,
|
||||
dependencies
|
||||
)
|
||||
if (err(result)) return result
|
||||
const { modifiedAst, pathToEdgeTreatmentNode } = result
|
||||
|
||||
// 2. update ast
|
||||
await updateModelingState(
|
||||
modifiedAst,
|
||||
EXECUTION_TYPE_REAL,
|
||||
{
|
||||
kclManager: dependencies.kclManager,
|
||||
editorManager: dependencies.editorManager,
|
||||
codeManager: dependencies.codeManager,
|
||||
},
|
||||
{
|
||||
focusPath: pathToEdgeTreatmentNode,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function modifyAstWithEdgeTreatmentAndTag(
|
||||
export async function modifyAstWithEdgeTreatmentAndTag(
|
||||
ast: Node<Program>,
|
||||
selections: Selections,
|
||||
parameters: EdgeTreatmentParameters,
|
||||
@ -111,9 +73,9 @@ export function modifyAstWithEdgeTreatmentAndTag(
|
||||
editorManager: EditorManager
|
||||
codeManager: CodeManager
|
||||
}
|
||||
):
|
||||
| { modifiedAst: Node<Program>; pathToEdgeTreatmentNode: Array<PathToNode> }
|
||||
| Error {
|
||||
): Promise<
|
||||
{ modifiedAst: Node<Program>; pathToEdgeTreatmentNode: PathToNode[] } | Error
|
||||
> {
|
||||
let clonedAst = structuredClone(ast)
|
||||
const clonedAstForGetExtrude = structuredClone(ast)
|
||||
|
||||
@ -784,3 +746,47 @@ export async function deleteEdgeTreatment(
|
||||
|
||||
return Error('Delete fillets not implemented')
|
||||
}
|
||||
|
||||
// Edit Edge Treatment
|
||||
export async function editEdgeTreatment(
|
||||
ast: Node<Program>,
|
||||
selection: Selection,
|
||||
parameters: EdgeTreatmentParameters
|
||||
): Promise<
|
||||
{ modifiedAst: Node<Program>; pathToEdgeTreatmentNode: PathToNode } | Error
|
||||
> {
|
||||
// 1. clone and modify with new value
|
||||
const modifiedAst = structuredClone(ast)
|
||||
|
||||
// find the edge treatment call
|
||||
const edgeTreatmentCall = getNodeFromPath<CallExpressionKw>(
|
||||
modifiedAst,
|
||||
selection?.codeRef?.pathToNode,
|
||||
'CallExpressionKw'
|
||||
)
|
||||
if (err(edgeTreatmentCall)) return edgeTreatmentCall
|
||||
|
||||
// edge treatment parameter
|
||||
const parameterResult = getParameterNameAndValue(parameters)
|
||||
if (err(parameterResult)) return parameterResult
|
||||
const { parameterName, parameterValue } = parameterResult
|
||||
|
||||
// find the index of an argument to update
|
||||
const index = edgeTreatmentCall.node.arguments.findIndex(
|
||||
(arg) => arg.label.name === parameterName
|
||||
)
|
||||
|
||||
// create a new argument with the updated value
|
||||
const newArg = createLabeledArg(parameterName, parameterValue)
|
||||
|
||||
// if the parameter doesn't exist, add it; otherwise replace it
|
||||
if (index === -1) {
|
||||
edgeTreatmentCall.node.arguments.push(newArg)
|
||||
} else {
|
||||
edgeTreatmentCall.node.arguments[index] = newArg
|
||||
}
|
||||
|
||||
let pathToEdgeTreatmentNode = selection?.codeRef?.pathToNode
|
||||
|
||||
return { modifiedAst, pathToEdgeTreatmentNode }
|
||||
}
|
||||
|
@ -166,15 +166,6 @@ const prepareToEditEdgeTreatment: PrepareToEditCallback = async ({
|
||||
kclManager.ast,
|
||||
sourceRangeFromRust(operation.sourceRange)
|
||||
)
|
||||
const isPipeExpression = nodeToEdit.some(
|
||||
([_, type]) => type === 'PipeExpression'
|
||||
)
|
||||
if (!isPipeExpression) {
|
||||
return {
|
||||
reason:
|
||||
'Only chamfer and fillet in pipe expressions are supported for edits',
|
||||
}
|
||||
}
|
||||
|
||||
let argDefaultValues:
|
||||
| ModelingCommandSchema['Chamfer']
|
||||
|
@ -58,7 +58,8 @@ import type {
|
||||
} from '@src/lang/modifyAst/addEdgeTreatment'
|
||||
import {
|
||||
EdgeTreatmentType,
|
||||
applyEdgeTreatmentToSelection,
|
||||
modifyAstWithEdgeTreatmentAndTag,
|
||||
editEdgeTreatment,
|
||||
getPathToExtrudeForSegmentSelection,
|
||||
mutateAstWithTagForSketchSegment,
|
||||
} from '@src/lang/modifyAst/addEdgeTreatment'
|
||||
@ -133,7 +134,6 @@ import {
|
||||
import type { ToolbarModeName } from '@src/lib/toolbar'
|
||||
import { err, reportRejection, trap } from '@src/lib/trap'
|
||||
import { uuidv4 } from '@src/lib/utils'
|
||||
import { deleteNodeInExtrudePipe } from '@src/lang/modifyAst/deleteNodeInExtrudePipe'
|
||||
import type { ImportStatement } from '@rust/kcl-lib/bindings/ImportStatement'
|
||||
|
||||
export type SetSelections =
|
||||
@ -2311,18 +2311,107 @@ export const modelingMachine = setup({
|
||||
|
||||
// Extract inputs
|
||||
const ast = kclManager.ast
|
||||
let modifiedAst = structuredClone(ast)
|
||||
let focusPath: PathToNode[] = []
|
||||
const { nodeToEdit, selection, radius } = input
|
||||
|
||||
// If this is an edit flow, first we're going to remove the old node
|
||||
if (nodeToEdit) {
|
||||
const oldNodeDeletion = deleteNodeInExtrudePipe(nodeToEdit, ast)
|
||||
if (err(oldNodeDeletion)) return oldNodeDeletion
|
||||
}
|
||||
|
||||
const parameters: FilletParameters = {
|
||||
type: EdgeTreatmentType.Fillet,
|
||||
radius,
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
kclManager,
|
||||
engineCommandManager,
|
||||
editorManager,
|
||||
codeManager,
|
||||
}
|
||||
|
||||
// Apply or edit fillet
|
||||
if (nodeToEdit) {
|
||||
// Edit existing fillet
|
||||
// selection is not the edge treatment itself,
|
||||
// but just the first edge in the fillet expression >
|
||||
// we need to find the edgeCut artifact
|
||||
// and build a new selection from it
|
||||
// TODO: this is a bit of a hack, we should be able
|
||||
// to get the edgeCut artifact from the selection
|
||||
const firstSelection = selection.graphSelections[0]
|
||||
const edgeCutArtifact = Array.from(
|
||||
kclManager.artifactGraph.values()
|
||||
).find(
|
||||
(artifact) =>
|
||||
artifact.type === 'edgeCut' &&
|
||||
artifact.consumedEdgeId === firstSelection.artifact?.id
|
||||
)
|
||||
if (!edgeCutArtifact || edgeCutArtifact.type !== 'edgeCut') {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'Failed to retrieve edgeCut artifact from sweepEdge selection'
|
||||
)
|
||||
)
|
||||
}
|
||||
const edgeTreatmentSelection = {
|
||||
artifact: edgeCutArtifact,
|
||||
codeRef: edgeCutArtifact.codeRef,
|
||||
}
|
||||
|
||||
const editResult = await editEdgeTreatment(
|
||||
ast,
|
||||
edgeTreatmentSelection,
|
||||
parameters
|
||||
)
|
||||
if (err(editResult)) return Promise.reject(editResult)
|
||||
|
||||
modifiedAst = editResult.modifiedAst
|
||||
focusPath = [editResult.pathToEdgeTreatmentNode]
|
||||
} else {
|
||||
// Apply fillet to selection
|
||||
const filletResult = await modifyAstWithEdgeTreatmentAndTag(
|
||||
ast,
|
||||
selection,
|
||||
parameters,
|
||||
dependencies
|
||||
)
|
||||
if (err(filletResult)) return Promise.reject(filletResult)
|
||||
modifiedAst = filletResult.modifiedAst
|
||||
focusPath = filletResult.pathToEdgeTreatmentNode
|
||||
}
|
||||
|
||||
await updateModelingState(
|
||||
modifiedAst,
|
||||
EXECUTION_TYPE_REAL,
|
||||
{
|
||||
kclManager,
|
||||
editorManager,
|
||||
codeManager,
|
||||
},
|
||||
{
|
||||
focusPath: focusPath,
|
||||
}
|
||||
)
|
||||
}
|
||||
),
|
||||
chamferAstMod: fromPromise(
|
||||
async ({
|
||||
input,
|
||||
}: {
|
||||
input: ModelingCommandSchema['Chamfer'] | undefined
|
||||
}) => {
|
||||
if (!input) {
|
||||
return Promise.reject(new Error('No input provided'))
|
||||
}
|
||||
|
||||
// Extract inputs
|
||||
const ast = kclManager.ast
|
||||
let modifiedAst = structuredClone(ast)
|
||||
let focusPath: PathToNode[] = []
|
||||
const { nodeToEdit, selection, length } = input
|
||||
|
||||
const parameters: ChamferParameters = {
|
||||
type: EdgeTreatmentType.Chamfer,
|
||||
length,
|
||||
}
|
||||
const dependencies = {
|
||||
kclManager,
|
||||
engineCommandManager,
|
||||
@ -2330,14 +2419,69 @@ export const modelingMachine = setup({
|
||||
codeManager,
|
||||
}
|
||||
|
||||
// Apply fillet to selection
|
||||
const filletResult = await applyEdgeTreatmentToSelection(
|
||||
// Apply or edit chamfer
|
||||
if (nodeToEdit) {
|
||||
// Edit existing chamfer
|
||||
// selection is not the edge treatment itself,
|
||||
// but just the first edge in the chamfer expression >
|
||||
// we need to find the edgeCut artifact
|
||||
// and build a new selection from it
|
||||
// TODO: this is a bit of a hack, we should be able
|
||||
// to get the edgeCut artifact from the selection
|
||||
const firstSelection = selection.graphSelections[0]
|
||||
const edgeCutArtifact = Array.from(
|
||||
kclManager.artifactGraph.values()
|
||||
).find(
|
||||
(artifact) =>
|
||||
artifact.type === 'edgeCut' &&
|
||||
artifact.consumedEdgeId === firstSelection.artifact?.id
|
||||
)
|
||||
if (!edgeCutArtifact || edgeCutArtifact.type !== 'edgeCut') {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'Failed to retrieve edgeCut artifact from sweepEdge selection'
|
||||
)
|
||||
)
|
||||
}
|
||||
const edgeTreatmentSelection = {
|
||||
artifact: edgeCutArtifact,
|
||||
codeRef: edgeCutArtifact.codeRef,
|
||||
}
|
||||
|
||||
const editResult = await editEdgeTreatment(
|
||||
ast,
|
||||
edgeTreatmentSelection,
|
||||
parameters
|
||||
)
|
||||
if (err(editResult)) return Promise.reject(editResult)
|
||||
|
||||
modifiedAst = editResult.modifiedAst
|
||||
focusPath = [editResult.pathToEdgeTreatmentNode]
|
||||
} else {
|
||||
// Apply chamfer to selection
|
||||
const chamferResult = await modifyAstWithEdgeTreatmentAndTag(
|
||||
ast,
|
||||
selection,
|
||||
parameters,
|
||||
dependencies
|
||||
)
|
||||
if (err(filletResult)) return filletResult
|
||||
if (err(chamferResult)) return Promise.reject(chamferResult)
|
||||
modifiedAst = chamferResult.modifiedAst
|
||||
focusPath = chamferResult.pathToEdgeTreatmentNode
|
||||
}
|
||||
|
||||
await updateModelingState(
|
||||
modifiedAst,
|
||||
EXECUTION_TYPE_REAL,
|
||||
{
|
||||
kclManager,
|
||||
editorManager,
|
||||
codeManager,
|
||||
},
|
||||
{
|
||||
focusPath: focusPath,
|
||||
}
|
||||
)
|
||||
}
|
||||
),
|
||||
'actor.parameter.create': fromPromise(
|
||||
@ -2461,47 +2605,6 @@ export const modelingMachine = setup({
|
||||
return {} as SketchDetailsUpdate
|
||||
}
|
||||
),
|
||||
chamferAstMod: fromPromise(
|
||||
async ({
|
||||
input,
|
||||
}: {
|
||||
input: ModelingCommandSchema['Chamfer'] | undefined
|
||||
}) => {
|
||||
if (!input) {
|
||||
return new Error('No input provided')
|
||||
}
|
||||
|
||||
// Extract inputs
|
||||
const ast = kclManager.ast
|
||||
const { nodeToEdit, selection, length } = input
|
||||
|
||||
// If this is an edit flow, first we're going to remove the old node
|
||||
if (nodeToEdit) {
|
||||
const oldNodeDeletion = deleteNodeInExtrudePipe(nodeToEdit, ast)
|
||||
if (err(oldNodeDeletion)) return oldNodeDeletion
|
||||
}
|
||||
|
||||
const parameters: ChamferParameters = {
|
||||
type: EdgeTreatmentType.Chamfer,
|
||||
length,
|
||||
}
|
||||
const dependencies = {
|
||||
kclManager,
|
||||
engineCommandManager,
|
||||
editorManager,
|
||||
codeManager,
|
||||
}
|
||||
|
||||
// Apply chamfer to selection
|
||||
const chamferResult = await applyEdgeTreatmentToSelection(
|
||||
ast,
|
||||
selection,
|
||||
parameters,
|
||||
dependencies
|
||||
)
|
||||
if (err(chamferResult)) return chamferResult
|
||||
}
|
||||
),
|
||||
'submit-prompt-edit': fromPromise(
|
||||
async ({
|
||||
input,
|
||||
|
Reference in New Issue
Block a user