Selection dry-run validation for Shell (#4775)
* WIP: mess with shell selection validation Will eventually fix #4711 * Update from main * WIP: not working yet * Working loft dry run validator * Clean up shell (still not working) * Bump kittycad-modeling-cmds * Clean up * Add logging * Add proper object_id and face_id mapping, still not working for shell * Fix faceId * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) * Working validation after engine merge; Clean up * Fix codespell * Add pw test * More clean up * Back to basics * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * Clean up * Fix tests * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * Remove kcl-samples --------- Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -1503,6 +1503,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
await clickOnCap()
|
await clickOnCap()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
@ -1523,6 +1524,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||||
await toolbar.shellButton.click()
|
await toolbar.shellButton.click()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
@ -1604,6 +1606,7 @@ extrude001 = extrude(40, sketch001)
|
|||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
@ -1727,3 +1730,61 @@ shellSketchOnFacesCases.forEach((initialCode, index) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Shell dry-run validation rejects sweeps`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('YZ')
|
||||||
|
|> circle({
|
||||||
|
center = [0, 0],
|
||||||
|
radius = 500
|
||||||
|
}, %)
|
||||||
|
sketch002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> xLine(-2000, %)
|
||||||
|
sweep001 = sweep({ path = sketch002 }, sketch001)
|
||||||
|
`
|
||||||
|
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: 500, y: 250 }
|
||||||
|
const [clickOnSweep] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
|
||||||
|
await test.step(`Confirm sweep exists`, async () => {
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
await scene.expectPixelColor([231, 231, 231], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the Shell flow and fail validation with a toast`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Thickness: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await clickOnSweep()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await expect(
|
||||||
|
page.getByText('Unable to shell with the provided selection')
|
||||||
|
).toBeVisible()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -9,7 +9,11 @@ import { Selections } from 'lib/selections'
|
|||||||
import { kclManager } from 'lib/singletons'
|
import { kclManager } from 'lib/singletons'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||||
import { loftValidator, revolveAxisValidator } from './validators'
|
import {
|
||||||
|
loftValidator,
|
||||||
|
revolveAxisValidator,
|
||||||
|
shellValidator,
|
||||||
|
} from './validators'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
type OutputTypeKey = OutputFormat['type']
|
type OutputTypeKey = OutputFormat['type']
|
||||||
@ -351,12 +355,13 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
selectionTypes: ['cap', 'wall'],
|
selectionTypes: ['cap', 'wall'],
|
||||||
multiple: true,
|
multiple: true,
|
||||||
required: true,
|
required: true,
|
||||||
skip: false,
|
validation: shellValidator,
|
||||||
},
|
},
|
||||||
thickness: {
|
thickness: {
|
||||||
inputType: 'kcl',
|
inputType: 'kcl',
|
||||||
defaultValue: KCL_DEFAULT_LENGTH,
|
defaultValue: KCL_DEFAULT_LENGTH,
|
||||||
required: true,
|
required: true,
|
||||||
|
// TODO: add dry-run validation on thickness param
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -153,3 +153,57 @@ export const loftValidator = async ({
|
|||||||
return 'Unable to loft with selected sketches'
|
return 'Unable to loft with selected sketches'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const shellValidator = async ({
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
data: { selection: Selections }
|
||||||
|
}): Promise<boolean | string> => {
|
||||||
|
if (!isSelections(data.selection)) {
|
||||||
|
return 'Unable to shell, selections are missing'
|
||||||
|
}
|
||||||
|
|
||||||
|
// No validation on the faces, filtering is done upstream and we have the dry run validation just below
|
||||||
|
const face_ids = data.selection.graphSelections.flatMap((s) =>
|
||||||
|
s.artifact ? s.artifact.id : []
|
||||||
|
)
|
||||||
|
|
||||||
|
// We don't have the concept of solid3ds in TS yet.
|
||||||
|
// So we're listing out the sweeps as if they were solids and taking the first one, just like in Rust for Shell:
|
||||||
|
// https://github.com/KittyCAD/modeling-app/blob/e61fff115b9fa94aaace6307b1842cc15d41655e/src/wasm-lib/kcl/src/std/shell.rs#L237-L238
|
||||||
|
// TODO: This is one cheap way to make sketch-on-face supported now but will likely fail multiple solids
|
||||||
|
const object_id = engineCommandManager.artifactGraph
|
||||||
|
.values()
|
||||||
|
.find((v) => v.type === 'sweep')?.pathId
|
||||||
|
|
||||||
|
if (!object_id) {
|
||||||
|
return "Unable to shell, couldn't find the solid"
|
||||||
|
}
|
||||||
|
|
||||||
|
const shellCommand = async () => {
|
||||||
|
// TODO: figure out something better than an arbitrarily small value
|
||||||
|
const DEFAULT_THICKNESS: Models['LengthUnit_type'] = 1e-9
|
||||||
|
const DEFAULT_HOLLOW = false
|
||||||
|
const cmdArgs = {
|
||||||
|
face_ids,
|
||||||
|
object_id,
|
||||||
|
hollow: DEFAULT_HOLLOW,
|
||||||
|
shell_thickness: DEFAULT_THICKNESS,
|
||||||
|
}
|
||||||
|
return await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'solid3d_shell_face',
|
||||||
|
...cmdArgs,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const attemptShell = await dryRunWrapper(shellCommand)
|
||||||
|
if (attemptShell?.success) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Unable to shell with the provided selection'
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user