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 toolbar.sweepButton.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
currentArgKey: 'profile',
|
currentArgKey: 'target',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Path: '',
|
Target: '',
|
||||||
Profile: '',
|
Trajectory: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'profile',
|
highlightedHeaderArg: 'target',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await clickOnSketch1()
|
await clickOnSketch1()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
currentArgKey: 'path',
|
currentArgKey: 'trajectory',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Path: '',
|
Target: '1 face',
|
||||||
Profile: '1 face',
|
Trajectory: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'trajectory',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await clickOnSketch2()
|
await clickOnSketch2()
|
||||||
await cmdBar.expectState({
|
await page.waitForTimeout(500)
|
||||||
commandName: 'Sweep',
|
|
||||||
headerArguments: {
|
|
||||||
Path: '1 face',
|
|
||||||
Profile: '1 face',
|
|
||||||
},
|
|
||||||
stage: 'review',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
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 ({
|
test(`Fillet point-and-click`, async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
loftValidator,
|
loftValidator,
|
||||||
revolveAxisValidator,
|
revolveAxisValidator,
|
||||||
shellValidator,
|
shellValidator,
|
||||||
|
sweepValidator,
|
||||||
} from './validators'
|
} from './validators'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
@ -42,8 +43,8 @@ export type ModelingCommandSchema = {
|
|||||||
distance: KclCommandValue
|
distance: KclCommandValue
|
||||||
}
|
}
|
||||||
Sweep: {
|
Sweep: {
|
||||||
path: Selections
|
target: Selections
|
||||||
profile: Selections
|
trajectory: Selections
|
||||||
}
|
}
|
||||||
Loft: {
|
Loft: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
@ -308,25 +309,24 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
||||||
icon: 'sweep',
|
icon: 'sweep',
|
||||||
status: 'development',
|
status: 'development',
|
||||||
needsReview: true,
|
needsReview: false,
|
||||||
args: {
|
args: {
|
||||||
profile: {
|
target: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['solid2d'],
|
selectionTypes: ['solid2d'],
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
// TODO: add dry-run validation
|
|
||||||
warningMessage:
|
warningMessage:
|
||||||
'The sweep workflow is new and under tested. Please break it and report issues.',
|
'The sweep workflow is new and under tested. Please break it and report issues.',
|
||||||
},
|
},
|
||||||
path: {
|
trajectory: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['segment', 'path'],
|
selectionTypes: ['segment', 'path'],
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: false,
|
||||||
multiple: 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'
|
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')
|
if (!input) return new Error('No input provided')
|
||||||
// Extract inputs
|
// Extract inputs
|
||||||
const ast = kclManager.ast
|
const ast = kclManager.ast
|
||||||
const { profile, path } = input
|
const { target, trajectory } = input
|
||||||
|
|
||||||
// Find the profile declaration
|
// Find the profile declaration
|
||||||
const profileNodePath = getNodePathFromSourceRange(
|
const targetNodePath = getNodePathFromSourceRange(
|
||||||
ast,
|
ast,
|
||||||
profile.graphSelections[0].codeRef.range
|
target.graphSelections[0].codeRef.range
|
||||||
)
|
)
|
||||||
const profileNode = getNodeFromPath<VariableDeclarator>(
|
const targetNode = getNodeFromPath<VariableDeclarator>(
|
||||||
ast,
|
ast,
|
||||||
profileNodePath,
|
targetNodePath,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
if (err(profileNode)) {
|
if (err(targetNode)) {
|
||||||
return new Error("Couldn't parse profile selection")
|
return new Error("Couldn't parse profile selection")
|
||||||
}
|
}
|
||||||
const profileDeclarator = profileNode.node
|
const targetDeclarator = targetNode.node
|
||||||
|
|
||||||
// Find the path declaration
|
// Find the path declaration
|
||||||
const pathNodePath = getNodePathFromSourceRange(
|
const trajectoryNodePath = getNodePathFromSourceRange(
|
||||||
ast,
|
ast,
|
||||||
path.graphSelections[0].codeRef.range
|
trajectory.graphSelections[0].codeRef.range
|
||||||
)
|
)
|
||||||
const pathNode = getNodeFromPath<VariableDeclarator>(
|
const trajectoryNode = getNodeFromPath<VariableDeclarator>(
|
||||||
ast,
|
ast,
|
||||||
pathNodePath,
|
trajectoryNodePath,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
if (err(pathNode)) {
|
if (err(trajectoryNode)) {
|
||||||
return new Error("Couldn't parse path selection")
|
return new Error("Couldn't parse path selection")
|
||||||
}
|
}
|
||||||
const pathDeclarator = pathNode.node
|
const trajectoryDeclarator = trajectoryNode.node
|
||||||
|
|
||||||
// Perform the sweep
|
// Perform the sweep
|
||||||
const sweepRes = addSweep(ast, profileDeclarator, pathDeclarator)
|
const sweepRes = addSweep(ast, targetDeclarator, trajectoryDeclarator)
|
||||||
const updateAstResult = await kclManager.updateAst(
|
const updateAstResult = await kclManager.updateAst(
|
||||||
sweepRes.modifiedAst,
|
sweepRes.modifiedAst,
|
||||||
true,
|
true,
|
||||||
|
Reference in New Issue
Block a user