Add dry-run validation for Sweep (#5097)

* Add dry-run validation for Sweep
Fixes #5095

* Add sweep test failing validation

* Make naming more consistent with engine

* Fix tests after big rename

* Fix tsc after main merge
This commit is contained in:
Pierre Jacquier
2025-01-22 15:59:47 +01:00
committed by GitHub
parent 10789d9c3c
commit 10da986649
4 changed files with 162 additions and 38 deletions

View File

@ -963,37 +963,31 @@ sketch002 = startSketchOn('XZ')
await toolbar.sweepButton.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'profile',
currentArgKey: 'target',
currentArgValue: '',
headerArguments: {
Path: '',
Profile: '',
Target: '',
Trajectory: '',
},
highlightedHeaderArg: 'profile',
highlightedHeaderArg: 'target',
stage: 'arguments',
})
await clickOnSketch1()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'path',
currentArgKey: 'trajectory',
currentArgValue: '',
headerArguments: {
Path: '',
Profile: '1 face',
Target: '1 face',
Trajectory: '',
},
highlightedHeaderArg: 'path',
highlightedHeaderArg: 'trajectory',
stage: 'arguments',
})
await clickOnSketch2()
await cmdBar.expectState({
commandName: 'Sweep',
headerArguments: {
Path: '1 face',
Profile: '1 face',
},
stage: 'review',
})
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1020,6 +1014,75 @@ sketch002 = startSketchOn('XZ')
})
})
test(`Sweep point-and-click failing validation`, async ({
context,
page,
homePage,
scene,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('YZ')
|> circle({
center = [0, 0],
radius = 500
}, %)
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(-500, %)
|> lineTo([-2000, 500], %)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 700, y: 250 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
await test.step(`Look for sketch001`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
await test.step(`Go through the command bar flow and fail validation with a toast`, async () => {
await toolbar.sweepButton.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'target',
currentArgValue: '',
headerArguments: {
Target: '',
Trajectory: '',
},
highlightedHeaderArg: 'target',
stage: 'arguments',
})
await clickOnSketch1()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'trajectory',
currentArgValue: '',
headerArguments: {
Target: '1 face',
Trajectory: '',
},
highlightedHeaderArg: 'trajectory',
stage: 'arguments',
})
await clickOnSketch2()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await expect(
page.getByText('Unable to sweep with the provided selection')
).toBeVisible()
})
})
test(`Fillet point-and-click`, async ({
context,
page,

View File

@ -13,6 +13,7 @@ import {
loftValidator,
revolveAxisValidator,
shellValidator,
sweepValidator,
} from './validators'
type OutputFormat = Models['OutputFormat_type']
@ -42,8 +43,8 @@ export type ModelingCommandSchema = {
distance: KclCommandValue
}
Sweep: {
path: Selections
profile: Selections
target: Selections
trajectory: Selections
}
Loft: {
selection: Selections
@ -308,25 +309,24 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
'Create a 3D body by moving a sketch region along an arbitrary path.',
icon: 'sweep',
status: 'development',
needsReview: true,
needsReview: false,
args: {
profile: {
target: {
inputType: 'selection',
selectionTypes: ['solid2d'],
required: true,
skip: true,
multiple: false,
// TODO: add dry-run validation
warningMessage:
'The sweep workflow is new and under tested. Please break it and report issues.',
},
path: {
trajectory: {
inputType: 'selection',
selectionTypes: ['segment', 'path'],
required: true,
skip: true,
skip: false,
multiple: false,
// TODO: add dry-run validation
validation: sweepValidator,
},
},
},

View File

@ -207,3 +207,64 @@ export const shellValidator = async ({
return 'Unable to shell with the provided selection'
}
export const sweepValidator = async ({
context,
data,
}: {
context: CommandBarContext
data: { trajectory: Selections }
}): Promise<boolean | string> => {
if (!isSelections(data.trajectory)) {
console.log('Unable to sweep, selections are missing')
return 'Unable to sweep, selections are missing'
}
// Retrieve the parent path from the segment selection directly
const trajectoryArtifact = data.trajectory.graphSelections[0].artifact
if (!trajectoryArtifact) {
return "Unable to sweep, couldn't find the trajectory artifact"
}
if (trajectoryArtifact.type !== 'segment') {
return "Unable to sweep, couldn't find the target from a non-segment selection"
}
const trajectory = trajectoryArtifact.pathId
// Get the former arg in the command bar flow, and retrieve the path from the solid2d directly
const targetArg = context.argumentsToSubmit['target'] as Selections
const targetArtifact = targetArg.graphSelections[0].artifact
if (!targetArtifact) {
return "Unable to sweep, couldn't find the profile artifact"
}
if (targetArtifact.type !== 'solid2d') {
return "Unable to sweep, couldn't find the target from a non-solid2d selection"
}
const target = targetArtifact.pathId
const sweepCommand = async () => {
// TODO: second look on defaults here
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
const DEFAULT_SECTIONAL = false
const cmdArgs = {
target,
trajectory,
sectional: DEFAULT_SECTIONAL,
tolerance: DEFAULT_TOLERANCE,
}
return await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sweep',
...cmdArgs,
},
})
}
const attemptSweep = await dryRunWrapper(sweepCommand)
if (attemptSweep?.success) {
return true
}
return 'Unable to sweep with the provided selection'
}

View File

@ -1561,40 +1561,40 @@ export const modelingMachine = setup({
if (!input) return new Error('No input provided')
// Extract inputs
const ast = kclManager.ast
const { profile, path } = input
const { target, trajectory } = input
// Find the profile declaration
const profileNodePath = getNodePathFromSourceRange(
const targetNodePath = getNodePathFromSourceRange(
ast,
profile.graphSelections[0].codeRef.range
target.graphSelections[0].codeRef.range
)
const profileNode = getNodeFromPath<VariableDeclarator>(
const targetNode = getNodeFromPath<VariableDeclarator>(
ast,
profileNodePath,
targetNodePath,
'VariableDeclarator'
)
if (err(profileNode)) {
if (err(targetNode)) {
return new Error("Couldn't parse profile selection")
}
const profileDeclarator = profileNode.node
const targetDeclarator = targetNode.node
// Find the path declaration
const pathNodePath = getNodePathFromSourceRange(
const trajectoryNodePath = getNodePathFromSourceRange(
ast,
path.graphSelections[0].codeRef.range
trajectory.graphSelections[0].codeRef.range
)
const pathNode = getNodeFromPath<VariableDeclarator>(
const trajectoryNode = getNodeFromPath<VariableDeclarator>(
ast,
pathNodePath,
trajectoryNodePath,
'VariableDeclarator'
)
if (err(pathNode)) {
if (err(trajectoryNode)) {
return new Error("Couldn't parse path selection")
}
const pathDeclarator = pathNode.node
const trajectoryDeclarator = trajectoryNode.node
// Perform the sweep
const sweepRes = addSweep(ast, profileDeclarator, pathDeclarator)
const sweepRes = addSweep(ast, targetDeclarator, trajectoryDeclarator)
const updateAstResult = await kclManager.updateAst(
sweepRes.modifiedAst,
true,