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
|
testPoint.y + 80
|
||||||
)
|
)
|
||||||
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
|
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 test.step(`Look for the white of the sketch001 shape`, async () => {
|
||||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||||
@ -1681,6 +1683,39 @@ sketch002 = startSketchOn(plane001)
|
|||||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
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 test.step('Delete loft via feature tree selection', async () => {
|
||||||
await editor.closePane()
|
await editor.closePane()
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
|
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 = [
|
const sweepCases = [
|
||||||
{
|
{
|
||||||
targetType: 'circle',
|
targetType: 'circle',
|
||||||
|
@ -237,9 +237,13 @@ export function addSweep({
|
|||||||
export function addLoft({
|
export function addLoft({
|
||||||
ast,
|
ast,
|
||||||
sketches,
|
sketches,
|
||||||
|
vDegree,
|
||||||
|
nodeToEdit,
|
||||||
}: {
|
}: {
|
||||||
ast: Node<Program>
|
ast: Node<Program>
|
||||||
sketches: Selections
|
sketches: Selections
|
||||||
|
vDegree?: KclCommandValue
|
||||||
|
nodeToEdit?: PathToNode
|
||||||
}):
|
}):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
@ -251,21 +255,52 @@ export function addLoft({
|
|||||||
|
|
||||||
// 2. Prepare unlabeled and labeled arguments
|
// 2. Prepare unlabeled and labeled arguments
|
||||||
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
|
// 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)) {
|
if (err(sketchesExprList)) {
|
||||||
return sketchesExprList
|
return sketchesExprList
|
||||||
}
|
}
|
||||||
|
|
||||||
const sketchesExpr = createSketchExpression(sketchesExprList)
|
// Extra labeled args expressions
|
||||||
const call = createCallExpressionStdLibKw('loft', sketchesExpr, [])
|
const vDegreeExpr = vDegree
|
||||||
|
? [createLabeledArg('vDegree', valueOrVariable(vDegree))]
|
||||||
|
: []
|
||||||
|
|
||||||
// 3. Just push the declaration to the end
|
const sketchesExpr = createSketchExpression(sketchesExprList)
|
||||||
// Note that Loft doesn't support edit flows yet since it's selection only atm
|
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 name = findUniqueName(modifiedAst, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
|
||||||
const declaration = createVariableDeclaration(name, call)
|
const declaration = createVariableDeclaration(name, call)
|
||||||
modifiedAst.body.push(declaration)
|
modifiedAst.body.push(declaration)
|
||||||
const toFirstKwarg = false
|
const toFirstKwarg = !!vDegree
|
||||||
const pathToNode = createPathToNode(modifiedAst, toFirstKwarg)
|
pathToNode = createPathToNode(modifiedAst, toFirstKwarg)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
|
@ -89,7 +89,11 @@ export type ModelingCommandSchema = {
|
|||||||
relativeTo?: string
|
relativeTo?: string
|
||||||
}
|
}
|
||||||
Loft: {
|
Loft: {
|
||||||
|
// Enables editing workflow
|
||||||
|
nodeToEdit?: PathToNode
|
||||||
|
// KCL stdlib arguments
|
||||||
sketches: Selections
|
sketches: Selections
|
||||||
|
vDegree?: KclCommandValue
|
||||||
}
|
}
|
||||||
Revolve: {
|
Revolve: {
|
||||||
// Enables editing workflow
|
// Enables editing workflow
|
||||||
@ -477,12 +481,20 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
icon: 'loft',
|
icon: 'loft',
|
||||||
needsReview: true,
|
needsReview: true,
|
||||||
args: {
|
args: {
|
||||||
|
nodeToEdit: {
|
||||||
|
...nodeToEditProps,
|
||||||
|
},
|
||||||
sketches: {
|
sketches: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
displayName: 'Profiles',
|
displayName: 'Profiles',
|
||||||
selectionTypes: ['solid2d'],
|
selectionTypes: ['solid2d'],
|
||||||
multiple: true,
|
multiple: true,
|
||||||
required: 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
|
* Gather up the argument values for the Chamfer or Fillet command
|
||||||
* to be used in the command bar edit flow.
|
* to be used in the command bar edit flow.
|
||||||
@ -1046,6 +1099,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
|
|||||||
loft: {
|
loft: {
|
||||||
label: 'Loft',
|
label: 'Loft',
|
||||||
icon: 'loft',
|
icon: 'loft',
|
||||||
|
prepareToEdit: prepareToEditLoft,
|
||||||
supportsAppearance: true,
|
supportsAppearance: true,
|
||||||
supportsTransform: true,
|
supportsTransform: true,
|
||||||
},
|
},
|
||||||
|
@ -2516,9 +2516,8 @@ export const modelingMachine = setup({
|
|||||||
return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
|
return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sketches } = input
|
|
||||||
const { ast } = kclManager
|
const { ast } = kclManager
|
||||||
const astResult = addLoft({ ast, sketches })
|
const astResult = addLoft({ ast, ...input })
|
||||||
if (err(astResult)) {
|
if (err(astResult)) {
|
||||||
return Promise.reject(astResult)
|
return Promise.reject(astResult)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user