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:
@ -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,
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user