Enable optional arguments in point-and-click Loft (#7587)
* 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 * WIP: Enable optional arguments in point-and-click Loft * Add edit flow for loft * WIP: e2e test and fix * Got it * Got it v2 🤦
This commit is contained in:
@ -1610,6 +1610,8 @@ sketch002 = startSketchOn(plane001)
|
||||
testPoint.y + 80
|
||||
)
|
||||
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
|
||||
const editedLoftDeclaration =
|
||||
'loft001 = loft([sketch001, sketch002], vDegree = 3)'
|
||||
|
||||
await test.step(`Look for the white of the sketch001 shape`, async () => {
|
||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||
@ -1681,6 +1683,39 @@ sketch002 = startSketchOn(plane001)
|
||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step('Go through the edit flow via feature tree', async () => {
|
||||
await toolbar.openPane('feature-tree')
|
||||
const op = await toolbar.getFeatureTreeOperation('Loft', 0)
|
||||
await op.dblclick()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {},
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.clickOptionalArgument('vDegree')
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'vDegree',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
VDegree: '',
|
||||
},
|
||||
highlightedHeaderArg: 'vDegree',
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await page.keyboard.insertText('3')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
VDegree: '3',
|
||||
},
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.submit()
|
||||
await editor.expectEditor.toContain(editedLoftDeclaration)
|
||||
})
|
||||
|
||||
await test.step('Delete loft via feature tree selection', async () => {
|
||||
await editor.closePane()
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
|
||||
@ -1691,72 +1726,6 @@ sketch002 = startSketchOn(plane001)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: merge with above test. Right now we're not able to delete a loft
|
||||
// right after creation via selection for some reason, so we go with a new instance
|
||||
test('Loft and offset plane deletion via selection', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn(XZ)
|
||||
|> circle(center = [0, 0], radius = 30)
|
||||
plane001 = offsetPlane(XZ, offset = 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle(center = [0, 0], radius = 20)
|
||||
loft001 = loft([sketch001, sketch002])
|
||||
`
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const [clickOnSketch2] = scene.makeMouseHelpers(
|
||||
testPoint.x,
|
||||
testPoint.y + 80
|
||||
)
|
||||
|
||||
await test.step(`Delete loft`, async () => {
|
||||
// Check for loft
|
||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
||||
await clickOnSketch1()
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||
|> circle(center = [0, 0], radius = 30)
|
||||
`)
|
||||
await page.keyboard.press('Delete')
|
||||
// Check for sketch 1
|
||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step('Delete sketch002', async () => {
|
||||
await page.waitForTimeout(1000)
|
||||
await clickOnSketch2()
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||
|> circle(center = [0, 0], radius = 20)
|
||||
`)
|
||||
await page.keyboard.press('Delete')
|
||||
// Check for plane001
|
||||
await scene.expectPixelColor([228, 228, 228], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step('Delete plane001', async () => {
|
||||
await page.waitForTimeout(1000)
|
||||
await clickOnSketch2()
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||
plane001 = offsetPlane(XZ, offset = 50)
|
||||
`)
|
||||
await page.keyboard.press('Delete')
|
||||
// Check for sketch 1
|
||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||
})
|
||||
})
|
||||
|
||||
const sweepCases = [
|
||||
{
|
||||
targetType: 'circle',
|
||||
|
@ -237,9 +237,13 @@ export function addSweep({
|
||||
export function addLoft({
|
||||
ast,
|
||||
sketches,
|
||||
vDegree,
|
||||
nodeToEdit,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
sketches: Selections
|
||||
vDegree?: KclCommandValue
|
||||
nodeToEdit?: PathToNode
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
@ -251,21 +255,52 @@ export function addLoft({
|
||||
|
||||
// 2. Prepare unlabeled and labeled arguments
|
||||
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
||||
const sketchesExprList = getSketchExprsFromSelection(sketches, modifiedAst)
|
||||
const sketchesExprList = getSketchExprsFromSelection(
|
||||
sketches,
|
||||
modifiedAst,
|
||||
nodeToEdit
|
||||
)
|
||||
if (err(sketchesExprList)) {
|
||||
return sketchesExprList
|
||||
}
|
||||
|
||||
const sketchesExpr = createSketchExpression(sketchesExprList)
|
||||
const call = createCallExpressionStdLibKw('loft', sketchesExpr, [])
|
||||
// Extra labeled args expressions
|
||||
const vDegreeExpr = vDegree
|
||||
? [createLabeledArg('vDegree', valueOrVariable(vDegree))]
|
||||
: []
|
||||
|
||||
// 3. Just push the declaration to the end
|
||||
// Note that Loft doesn't support edit flows yet since it's selection only atm
|
||||
const name = findUniqueName(modifiedAst, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
|
||||
const declaration = createVariableDeclaration(name, call)
|
||||
modifiedAst.body.push(declaration)
|
||||
const toFirstKwarg = false
|
||||
const pathToNode = createPathToNode(modifiedAst, toFirstKwarg)
|
||||
const sketchesExpr = createSketchExpression(sketchesExprList)
|
||||
const call = createCallExpressionStdLibKw('loft', sketchesExpr, [
|
||||
...vDegreeExpr,
|
||||
])
|
||||
|
||||
// Insert variables for labeled arguments if provided
|
||||
if (vDegree && 'variableName' in vDegree && vDegree.variableName) {
|
||||
insertVariableAndOffsetPathToNode(vDegree, modifiedAst, nodeToEdit)
|
||||
}
|
||||
|
||||
// 3. If edit, we assign the new function call declaration to the existing node,
|
||||
// otherwise just push to the end
|
||||
let pathToNode: PathToNode | undefined
|
||||
if (nodeToEdit) {
|
||||
const result = getNodeFromPath<CallExpressionKw>(
|
||||
modifiedAst,
|
||||
nodeToEdit,
|
||||
'CallExpressionKw'
|
||||
)
|
||||
if (err(result)) {
|
||||
return result
|
||||
}
|
||||
|
||||
Object.assign(result.node, call)
|
||||
pathToNode = nodeToEdit
|
||||
} else {
|
||||
const name = findUniqueName(modifiedAst, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
|
||||
const declaration = createVariableDeclaration(name, call)
|
||||
modifiedAst.body.push(declaration)
|
||||
const toFirstKwarg = !!vDegree
|
||||
pathToNode = createPathToNode(modifiedAst, toFirstKwarg)
|
||||
}
|
||||
|
||||
return {
|
||||
modifiedAst,
|
||||
|
@ -89,7 +89,11 @@ export type ModelingCommandSchema = {
|
||||
relativeTo?: string
|
||||
}
|
||||
Loft: {
|
||||
// Enables editing workflow
|
||||
nodeToEdit?: PathToNode
|
||||
// KCL stdlib arguments
|
||||
sketches: Selections
|
||||
vDegree?: KclCommandValue
|
||||
}
|
||||
Revolve: {
|
||||
// Enables editing workflow
|
||||
@ -477,12 +481,20 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
icon: 'loft',
|
||||
needsReview: true,
|
||||
args: {
|
||||
nodeToEdit: {
|
||||
...nodeToEditProps,
|
||||
},
|
||||
sketches: {
|
||||
inputType: 'selection',
|
||||
displayName: 'Profiles',
|
||||
selectionTypes: ['solid2d'],
|
||||
multiple: true,
|
||||
required: true,
|
||||
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
|
||||
},
|
||||
vDegree: {
|
||||
inputType: 'kcl',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -199,6 +199,59 @@ const prepareToEditExtrude: PrepareToEditCallback = async ({ operation }) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather up the argument values for the Loft command
|
||||
* to be used in the command bar edit flow.
|
||||
*/
|
||||
const prepareToEditLoft: PrepareToEditCallback = async ({ operation }) => {
|
||||
const baseCommand = {
|
||||
name: 'Loft',
|
||||
groupId: 'modeling',
|
||||
}
|
||||
if (operation.type !== 'StdLibCall') {
|
||||
return { reason: 'Wrong operation type' }
|
||||
}
|
||||
|
||||
// 1. Map the unlabeled arguments to solid2d selections
|
||||
const sketches = getSketchSelectionsFromOperation(
|
||||
operation,
|
||||
kclManager.artifactGraph
|
||||
)
|
||||
if (err(sketches)) {
|
||||
return { reason: "Couldn't retrieve sketches" }
|
||||
}
|
||||
|
||||
// 2.
|
||||
// vDegree argument from a string to a KCL expression
|
||||
let vDegree: KclCommandValue | undefined
|
||||
if ('vDegree' in operation.labeledArgs && operation.labeledArgs.vDegree) {
|
||||
const result = await stringToKclExpression(
|
||||
codeManager.code.slice(
|
||||
operation.labeledArgs.vDegree.sourceRange[0],
|
||||
operation.labeledArgs.vDegree.sourceRange[1]
|
||||
)
|
||||
)
|
||||
if (err(result) || 'errors' in result) {
|
||||
return { reason: "Couldn't retrieve vDegree argument" }
|
||||
}
|
||||
|
||||
vDegree = result
|
||||
}
|
||||
|
||||
// 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.
|
||||
const argDefaultValues: ModelingCommandSchema['Loft'] = {
|
||||
sketches,
|
||||
vDegree,
|
||||
nodeToEdit: pathToNodeFromRustNodePath(operation.nodePath),
|
||||
}
|
||||
return {
|
||||
...baseCommand,
|
||||
argDefaultValues,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather up the argument values for the Chamfer or Fillet command
|
||||
* to be used in the command bar edit flow.
|
||||
@ -1046,6 +1099,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
loft: {
|
||||
label: 'Loft',
|
||||
icon: 'loft',
|
||||
prepareToEdit: prepareToEditLoft,
|
||||
supportsAppearance: true,
|
||||
supportsTransform: true,
|
||||
},
|
||||
|
@ -2516,9 +2516,8 @@ export const modelingMachine = setup({
|
||||
return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
|
||||
}
|
||||
|
||||
const { sketches } = input
|
||||
const { ast } = kclManager
|
||||
const astResult = addLoft({ ast, sketches })
|
||||
const astResult = addLoft({ ast, ...input })
|
||||
if (err(astResult)) {
|
||||
return Promise.reject(astResult)
|
||||
}
|
||||
|
Reference in New Issue
Block a user