Add support for sweepEdge and tests

This commit is contained in:
Pierre Jacquier
2025-03-19 17:12:49 -04:00
parent 50a648881f
commit 343e2d591d
4 changed files with 223 additions and 18 deletions

View File

@ -1068,7 +1068,7 @@ openSketch = startSketchOn('XY')
})
})
test('Helix point-and-click', async ({
test('Helix point-and-click on default axis', async ({
context,
page,
homePage,
@ -1084,9 +1084,6 @@ openSketch = startSketchOn('XY')
await homePage.goToModelingScene()
// await test.step(`Look for the red of the default plane`, async () => {
// await scene.expectPixelColor([96, 52, 52], testPoint, 15)
// })
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState({
@ -1177,6 +1174,165 @@ openSketch = startSketchOn('XY')
})
})
const helixCases = [
{
selectionType: 'segment',
testPoint: { x: 513, y: 221 },
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, counterClockWise = true, radius = 1, axis = seg01, length = 100,)`,
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, counterClockWise = true, radius = 1, axis = seg01, length = 50,)`,
},
{
selectionType: 'sweepEdge',
testPoint: { x: 564, y: 364 },
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, counterClockWise = true, radius = 1, axis = getOppositeEdge(seg01), length = 100,)`,
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, counterClockWise = true, radius = 1, axis = getOppositeEdge(seg01), length = 50,)`,
},
]
helixCases.map(
({ selectionType, testPoint, expectedOutput, expectedEditedOutput }) => {
test(`Helix point-and-click around ${selectionType}`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
page.on('console', console.log)
const initialCode = `sketch001 = startSketchOn('XZ')
profile001 = startProfileAt([0, 0], sketch001)
|> yLine(length = 100)
|> line(endAbsolute = [100, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile001, length = 100)`
// One dumb hardcoded screen pixel value
const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await test.step(`Go through the command bar flow`, async () => {
await toolbar.closePane('code')
await toolbar.helixButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'axisOrEdge',
currentArgValue: '',
headerArguments: {
AngleStart: '',
AxisOrEdge: '',
CounterClockWise: '',
Length: '',
Radius: '',
Revolutions: '',
},
highlightedHeaderArg: 'axisOrEdge',
commandName: 'Helix',
})
await cmdBar.selectOption({ name: 'Edge' }).click()
await clickOnEdge()
await cmdBar.progressCmdBar()
await cmdBar.argumentInput.focus()
await page.keyboard.insertText('20')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('0')
await cmdBar.progressCmdBar()
await cmdBar.selectOption({ name: 'True' }).click()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('100')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
AxisOrEdge: 'Edge',
Edge: `1 ${selectionType}`,
CounterClockWise: '',
Length: '100',
Radius: '1',
Revolutions: '20',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedOutput)
await toolbar.closePane('code')
})
await test.step(`Edit helix through the feature tree`, async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
await operationButton.dblclick()
const initialInput = '100'
const newInput = '50'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'length',
currentArgValue: initialInput,
headerArguments: {
AngleStart: '0',
CounterClockWise: '',
Length: initialInput,
Radius: '1',
Revolutions: '20',
},
highlightedHeaderArg: 'length',
})
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput
.locator('.cm-content')
.fill(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
CounterClockWise: '',
Length: newInput,
Radius: '1',
Revolutions: '20',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedEditedOutput)
await toolbar.closePane('code')
})
await test.step('Delete helix via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain(expectedEditedOutput)
expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
})
}
)
const loftPointAndClickCases = [
{ shouldPreselect: true },
{ shouldPreselect: false },

View File

@ -529,9 +529,8 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
commandContext.argumentsToSubmit.axisOrEdge as string
),
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
selectionTypes: ['segment', 'sweepEdge'],
multiple: false,
skip: true,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
revolutions: {

View File

@ -3,6 +3,7 @@ import {
Artifact,
getArtifactOfTypes,
getCapCodeRef,
getSweepEdgeCodeRef,
} from 'lang/std/artifactGraph'
import { Operation } from '@rust/kcl-lib/bindings/Operation'
import { codeManager, engineCommandManager, kclManager } from './singletons'
@ -332,9 +333,11 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
let axis: string | undefined
let edge: Selections | undefined
if (axisValue.type === 'String') {
// default axis casee
axisOrEdge = 'Axis'
axis = axisValue.value
} else if (axisValue.type === 'TagIdentifier' && axisValue.artifact_id) {
// segment case
axisOrEdge = 'Edge'
const artifact = getArtifactOfTypes(
{
@ -356,6 +359,37 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
],
otherSelections: [],
}
} else if (axisValue.type === 'Uuid') {
// sweepEdge case
axisOrEdge = 'Edge'
const artifact = getArtifactOfTypes(
{
key: axisValue.value,
types: ['sweepEdge'],
},
engineCommandManager.artifactGraph
)
if (err(artifact)) {
return baseCommand
}
const codeRef = getSweepEdgeCodeRef(
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) {
return baseCommand
}
edge = {
graphSelections: [
{
artifact,
codeRef,
},
],
otherSelections: [],
}
} else {
return baseCommand
}
@ -364,8 +398,9 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
if (
!('revolutions' in operation.labeledArgs) ||
!operation.labeledArgs.revolutions
)
) {
return baseCommand
}
const revolutions = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.revolutions.sourceRange[0],
@ -378,22 +413,26 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
if (
!('angleStart' in operation.labeledArgs) ||
!operation.labeledArgs.angleStart
)
) {
return baseCommand
}
const angleStart = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.angleStart.sourceRange[0],
operation.labeledArgs.angleStart.sourceRange[1]
)
)
if (err(angleStart) || 'errors' in angleStart) return baseCommand
if (err(angleStart) || 'errors' in angleStart) {
return baseCommand
}
// counterClockWise options boolean arg
if (
!('counterClockWise' in operation.labeledArgs) ||
!operation.labeledArgs.counterClockWise
)
) {
return baseCommand
}
const counterClockWise =
codeManager.code.slice(
operation.labeledArgs.counterClockWise.sourceRange[0],
@ -401,26 +440,35 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
) === 'true'
// radius kcl arg
if (!('radius' in operation.labeledArgs) || !operation.labeledArgs.radius)
if (!('radius' in operation.labeledArgs) || !operation.labeledArgs.radius) {
console.log(
"!('radius' in operation.labeledArgs) || !operation.labeledArgs.radius"
)
return baseCommand
}
const radius = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.radius.sourceRange[0],
operation.labeledArgs.radius.sourceRange[1]
)
)
if (err(radius) || 'errors' in radius) return baseCommand
if (err(radius) || 'errors' in radius) {
return baseCommand
}
// length kcl arg
if (!('length' in operation.labeledArgs) || !operation.labeledArgs.length)
if (!('length' in operation.labeledArgs) || !operation.labeledArgs.length) {
return baseCommand
}
const length = await stringToKclExpression(
codeManager.code.slice(
operation.labeledArgs.length.sourceRange[0],
operation.labeledArgs.length.sourceRange[1]
)
)
if (err(length) || 'errors' in length) return baseCommand
if (err(length) || 'errors' in length) {
return baseCommand
}
// Assemble the default argument values for the Offset Plane command,
// with `nodeToEdit` set, which will let the Offset Plane actor know

View File

@ -1940,15 +1940,17 @@ export const modelingMachine = setup({
ast
)
if (err(getAxisResult)) return getAxisResult
const { generatedAxis, axisIndexIfAxis } = getAxisResult
const { generatedAxis } = getAxisResult
if (!generatedAxis) {
return new Error('Generated axis selection is missing.')
}
// TODO: figure out if we want to smart insert after the sketch as below
// *or* after the sweep that consumes the sketch, in which case the below code doesn't work
// If an axis was selected in KCL, find the max index to insert the revolve command
if (axisIndexIfAxis) {
opInsertIndex = axisIndexIfAxis + 1
}
// if (axisIndexIfAxis) {
// opInsertIndex = axisIndexIfAxis + 1
// }
for (const variable of [revolutions, angleStart, radius, length]) {
// Insert the variable if it exists