Enable optional arguments in point-and-click Sweep (#7580)

* Enable optional arguments in point-and-click Sweep
Fixes #7578

* Fix bug and add e2e test step

* Fix review not triggering bug and e2e test
This commit is contained in:
Pierre Jacquier
2025-06-23 16:17:17 -04:00
committed by GitHub
parent 1e1bdbd6e7
commit 0cd6031aae
6 changed files with 93 additions and 23 deletions

View File

@ -1954,6 +1954,7 @@ profile002 = startProfile(sketch002, at = [0, 0])
sketch001 = startSketchOn(XZ)
profile001 = ${circleCode}`
const sweepDeclaration = 'sweep001 = sweep(profile001, path = helix001)'
const editedSweepDeclaration = `sweep001 = sweep(profile001, path = helix001, relativeTo = 'sketchPlane')`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
@ -2015,11 +2016,43 @@ profile001 = ${circleCode}`
await editor.expectEditor.toContain(sweepDeclaration)
})
await test.step('Go through the edit flow via feature tree', async () => {
await toolbar.openPane('feature-tree')
const op = await toolbar.getFeatureTreeOperation('Sweep', 0)
await op.dblclick()
await cmdBar.expectState({
stage: 'review',
headerArguments: {},
commandName: 'Sweep',
})
await cmdBar.clickOptionalArgument('relativeTo')
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'relativeTo',
currentArgValue: '',
headerArguments: {
RelativeTo: '',
},
highlightedHeaderArg: 'relativeTo',
commandName: 'Sweep',
})
await cmdBar.selectOption({ name: 'sketchPlane' }).click()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
RelativeTo: 'sketchPlane',
},
commandName: 'Sweep',
})
await cmdBar.submit()
await editor.expectEditor.toContain(editedSweepDeclaration)
})
await test.step('Delete sweep via feature tree selection', async () => {
const sweep = await toolbar.getFeatureTreeOperation('Sweep', 0)
await sweep.click()
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain(sweepDeclaration)
await editor.expectEditor.not.toContain(editedSweepDeclaration)
})
})

View File

@ -148,12 +148,14 @@ export function addSweep({
sketches,
path,
sectional,
relativeTo,
nodeToEdit,
}: {
ast: Node<Program>
sketches: Selections
path: Selections
sectional?: boolean
relativeTo?: string
nodeToEdit?: PathToNode
}):
| {
@ -190,11 +192,15 @@ export function addSweep({
const sectionalExpr = sectional
? [createLabeledArg('sectional', createLiteral(sectional))]
: []
const relativeToExpr = relativeTo
? [createLabeledArg('relativeTo', createLiteral(relativeTo))]
: []
const sketchesExpr = createSketchExpression(sketchesExprList)
const call = createCallExpressionStdLibKw('sweep', sketchesExpr, [
createLabeledArg('path', pathExpr),
...sectionalExpr,
...relativeToExpr,
])
// 3. If edit, we assign the new function call declaration to the existing node,

View File

@ -86,6 +86,7 @@ export type ModelingCommandSchema = {
sketches: Selections
path: Selections
sectional?: boolean
relativeTo?: string
}
Loft: {
sketches: Selections
@ -455,13 +456,19 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
sectional: {
inputType: 'options',
skip: true,
required: false,
options: [
{ name: 'False', value: false },
{ name: 'True', value: true },
],
// No validation possible here until we have rollback
},
relativeTo: {
inputType: 'options',
required: false,
options: [
{ name: 'sketchPlane', value: 'sketchPlane' },
{ name: 'trajectoryCurve', value: 'trajectoryCurve' },
],
},
},
},

View File

@ -518,32 +518,45 @@ const prepareToEditSweep: PrepareToEditCallback = async ({ operation }) => {
// 2. Prepare labeled arguments
// Same roundabout but twice for 'path' aka trajectory: sketch -> path -> segment
if (operation.labeledArgs.path?.value.type !== 'Sketch') {
return { reason: "Couldn't retrieve trajectory argument" }
if (
operation.labeledArgs.path?.value.type !== 'Sketch' &&
operation.labeledArgs.path?.value.type !== 'Helix'
) {
return { reason: "Couldn't retrieve path argument" }
}
const trajectoryPathArtifact = getArtifactOfTypes(
{
key: operation.labeledArgs.path.value.value.artifactId,
types: ['path'],
types: ['path', 'helix'],
},
kclManager.artifactGraph
)
if (err(trajectoryPathArtifact) || trajectoryPathArtifact.type !== 'path') {
if (
err(trajectoryPathArtifact) ||
(trajectoryPathArtifact.type !== 'path' &&
trajectoryPathArtifact.type !== 'helix')
) {
return { reason: "Couldn't retrieve trajectory path artifact" }
}
const trajectoryArtifact = getArtifactOfTypes(
const trajectoryArtifact =
trajectoryPathArtifact.type === 'path'
? getArtifactOfTypes(
{
key: trajectoryPathArtifact.segIds[0],
types: ['segment'],
},
kclManager.artifactGraph
)
: trajectoryPathArtifact
if (err(trajectoryArtifact) || trajectoryArtifact.type !== 'segment') {
console.log(trajectoryArtifact)
if (
err(trajectoryArtifact) ||
(trajectoryArtifact.type !== 'segment' &&
trajectoryArtifact.type !== 'helix')
) {
return { reason: "Couldn't retrieve trajectory artifact" }
}
@ -557,7 +570,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({ operation }) => {
otherSelections: [],
}
// sectional argument from a string to a KCL expression
// optional arguments
let sectional: boolean | undefined
if ('sectional' in operation.labeledArgs && operation.labeledArgs.sectional) {
sectional =
@ -567,6 +580,17 @@ const prepareToEditSweep: PrepareToEditCallback = async ({ operation }) => {
) === 'true'
}
let relativeTo: string | undefined
if (
'relativeTo' in operation.labeledArgs &&
operation.labeledArgs.relativeTo
) {
relativeTo = codeManager.code.slice(
operation.labeledArgs.relativeTo.sourceRange[0],
operation.labeledArgs.relativeTo.sourceRange[1]
)
}
// 3. Assemble the default argument values for the command,
// with `nodeToEdit` set, which will let the actor know
// to edit the node that corresponds to the StdLibCall.
@ -574,6 +598,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({ operation }) => {
sketches,
path,
sectional,
relativeTo,
nodeToEdit: pathToNodeFromRustNodePath(operation.nodePath),
}
return {

View File

@ -288,7 +288,10 @@ export const commandBarMachine = setup({
argConfig.skip ||
(typeof argConfig.hidden === 'function'
? argConfig.hidden(context)
: argConfig.hidden)
: argConfig.hidden) ||
(typeof argConfig.required === 'function'
? !argConfig.required(context)
: !argConfig.required)
)
},
'Has selected command': ({ context }) => !!context.selectedCommand,

View File

@ -2482,14 +2482,10 @@ export const modelingMachine = setup({
return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
}
const { nodeToEdit, sketches, path, sectional } = input
const { ast } = kclManager
const astResult = addSweep({
...input,
ast,
sketches,
path,
sectional,
nodeToEdit,
})
if (err(astResult)) {
return Promise.reject(astResult)