Add edge and segment selection in point-and-click Helix flow (#5866)

* WIP: Add edge and segment selection in point-and-click Helix flow
Fixes #5393

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Working edge based helix edit

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Add utility function for shared code between revolve and helix

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Use updateModelingState in codemod

* A snapshot a day keeps the bugs away! 📷🐛

* Add skip: true for edge helix to be consistent with axis as options

* A snapshot a day keeps the bugs away! 📷🐛

* Add support for sweepEdge and tests

* Lint

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Clean up snapshots

* Fix up tests after ccw change

* Use displayName: 'CounterClockWise' cause ccw not cutting it

* Fix tsc

* Update 2020 snapshots after helix change

* Update 2020 snapshots after helix change

* A snapshot a day keeps the bugs away! 📷🐛

* Another one :djkhaled:

* Clean up

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Pierre Jacquier
2025-03-24 16:08:19 -04:00
committed by GitHub
parent fdeb2b3f49
commit 89dd4fb039
7 changed files with 401 additions and 107 deletions

View File

@ -1071,7 +1071,7 @@ openSketch = startSketchOn(XY)
}) })
}) })
test('Helix point-and-click', async ({ test('Helix point-and-click on default axis', async ({
context, context,
page, page,
homePage, homePage,
@ -1087,24 +1087,21 @@ openSketch = startSketchOn(XY)
await homePage.goToModelingScene() 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 test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click() await toolbar.helixButton.click()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'revolutions', currentArgKey: 'axisOrEdge',
currentArgValue: '1', currentArgValue: '',
headerArguments: { headerArguments: {
AngleStart: '', AngleStart: '',
Axis: '', AxisOrEdge: '',
Ccw: '', CounterClockWise: '',
Length: '', Length: '',
Radius: '', Radius: '',
Revolutions: '', Revolutions: '',
}, },
highlightedHeaderArg: 'revolutions', highlightedHeaderArg: 'axisOrEdge',
commandName: 'Helix', commandName: 'Helix',
}) })
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
@ -1114,6 +1111,7 @@ openSketch = startSketchOn(XY)
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
}) })
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 () => {
@ -1141,7 +1139,7 @@ openSketch = startSketchOn(XY)
headerArguments: { headerArguments: {
AngleStart: '360', AngleStart: '360',
Axis: 'X', Axis: 'X',
Ccw: '', CounterClockWise: '',
Length: initialInput, Length: initialInput,
Radius: '5', Radius: '5',
Revolutions: '1', Revolutions: '1',
@ -1156,7 +1154,7 @@ openSketch = startSketchOn(XY)
headerArguments: { headerArguments: {
AngleStart: '360', AngleStart: '360',
Axis: 'X', Axis: 'X',
Ccw: '', CounterClockWise: '',
Length: newInput, Length: newInput,
Radius: '5', Radius: '5',
Revolutions: '1', Revolutions: '1',
@ -1179,6 +1177,165 @@ openSketch = startSketchOn(XY)
}) })
}) })
const helixCases = [
{
selectionType: 'segment',
testPoint: { x: 513, y: 221 },
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = seg01, length = 100,)`,
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = seg01, length = 50,)`,
},
{
selectionType: 'sweepEdge',
testPoint: { x: 564, y: 364 },
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = getOppositeEdge(seg01), length = 100,)`,
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = 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)
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
})
}
)
const loftPointAndClickCases = [ const loftPointAndClickCases = [
{ shouldPreselect: true }, { shouldPreselect: true },
{ shouldPreselect: false }, { shouldPreselect: false },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -822,7 +822,7 @@ export function addHelix({
angleStart: Expr angleStart: Expr
ccw: boolean ccw: boolean
radius: Expr radius: Expr
axis: string axis: Node<Literal> | Node<Name | CallExpression | CallExpressionKw>
length: Expr length: Expr
insertIndex?: number insertIndex?: number
variableName?: string variableName?: string
@ -840,7 +840,7 @@ export function addHelix({
createLabeledArg('angleStart', angleStart), createLabeledArg('angleStart', angleStart),
createLabeledArg('ccw', createLiteral(ccw)), createLabeledArg('ccw', createLiteral(ccw)),
createLabeledArg('radius', radius), createLabeledArg('radius', radius),
createLabeledArg('axis', createLiteral(axis)), createLabeledArg('axis', axis),
createLabeledArg('length', length), createLabeledArg('length', length),
] ]
) )

View File

@ -32,11 +32,66 @@ import {
import { Artifact, getPathsFromArtifact } from 'lang/std/artifactGraph' import { Artifact, getPathsFromArtifact } from 'lang/std/artifactGraph'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
export function getAxisExpressionAndIndex(
axisOrEdge: 'Axis' | 'Edge',
axis: string | undefined,
edge: Selections | undefined,
ast: Node<Program>
) {
let generatedAxis
let axisDeclaration: PathToNode | null = null
let axisIndexIfAxis: number | undefined = undefined
if (axisOrEdge === 'Edge' && edge) {
const pathToAxisSelection = getNodePathFromSourceRange(
ast,
edge.graphSelections[0]?.codeRef.range
)
const lineNode = getNodeFromPath<CallExpression | CallExpressionKw>(
ast,
pathToAxisSelection,
['CallExpression', 'CallExpressionKw']
)
if (err(lineNode)) return lineNode
const tagResult = mutateAstWithTagForSketchSegment(ast, pathToAxisSelection)
// Have the tag whether it is already created or a new one is generated
if (err(tagResult)) return tagResult
const { tag } = tagResult
const axisSelection = edge?.graphSelections[0]?.artifact
if (!axisSelection) return new Error('Generated axis selection is missing.')
generatedAxis = getEdgeTagCall(tag, axisSelection)
if (
axisSelection.type === 'segment' ||
axisSelection.type === 'path' ||
axisSelection.type === 'edgeCut'
) {
axisDeclaration = axisSelection.codeRef.pathToNode
if (!axisDeclaration)
return new Error('Expected to fine axis declaration')
const axisIndexInPathToNode =
axisDeclaration.findIndex((a) => a[0] === 'body') + 1
const value = axisDeclaration[axisIndexInPathToNode][0]
if (typeof value !== 'number')
return new Error('expected axis index value to be a number')
axisIndexIfAxis = value
}
} else if (axisOrEdge === 'Axis' && axis) {
generatedAxis = createLiteral(axis)
}
return {
generatedAxis,
axisIndexIfAxis,
}
}
export function revolveSketch( export function revolveSketch(
ast: Node<Program>, ast: Node<Program>,
pathToSketchNode: PathToNode, pathToSketchNode: PathToNode,
angle: Expr = createLiteral(4), angle: Expr = createLiteral(4),
axisOrEdge: string, axisOrEdge: 'Axis' | 'Edge',
axis: string, axis: string,
edge: Selections, edge: Selections,
artifactGraph: ArtifactGraph, artifactGraph: ArtifactGraph,
@ -58,44 +113,6 @@ export function revolveSketch(
const clonedAst = structuredClone(ast) const clonedAst = structuredClone(ast)
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode) const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
if (err(sketchNode)) return sketchNode if (err(sketchNode)) return sketchNode
let generatedAxis
let axisDeclaration: PathToNode | null = null
if (axisOrEdge === 'Edge') {
const pathToAxisSelection = getNodePathFromSourceRange(
clonedAst,
edge.graphSelections[0]?.codeRef.range
)
const lineNode = getNodeFromPath<CallExpression | CallExpressionKw>(
clonedAst,
pathToAxisSelection,
['CallExpression', 'CallExpressionKw']
)
if (err(lineNode)) return lineNode
const tagResult = mutateAstWithTagForSketchSegment(
clonedAst,
pathToAxisSelection
)
// Have the tag whether it is already created or a new one is generated
if (err(tagResult)) return tagResult
const { tag } = tagResult
const axisSelection = edge?.graphSelections[0]?.artifact
if (!axisSelection) return new Error('Generated axis selection is missing.')
generatedAxis = getEdgeTagCall(tag, axisSelection)
if (
axisSelection.type === 'segment' ||
axisSelection.type === 'path' ||
axisSelection.type === 'edgeCut'
) {
axisDeclaration = axisSelection.codeRef.pathToNode
}
} else {
generatedAxis = createLiteral(axis)
}
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>( const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
clonedAst, clonedAst,
pathToSketchNode, pathToSketchNode,
@ -104,6 +121,9 @@ export function revolveSketch(
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
const { node: sketchVariableDeclarator } = sketchVariableDeclaratorNode const { node: sketchVariableDeclarator } = sketchVariableDeclaratorNode
const getAxisResult = getAxisExpressionAndIndex(axisOrEdge, axis, edge, ast)
if (err(getAxisResult)) return getAxisResult
const { generatedAxis, axisIndexIfAxis } = getAxisResult
if (!generatedAxis) return new Error('Generated axis selection is missing.') if (!generatedAxis) return new Error('Generated axis selection is missing.')
const revolveCall = createCallExpressionStdLibKw( const revolveCall = createCallExpressionStdLibKw(
@ -124,15 +144,8 @@ export function revolveSketch(
} }
// If an axis was selected in KCL, find the max index to insert the revolve command // If an axis was selected in KCL, find the max index to insert the revolve command
if (axisDeclaration) { if (axisIndexIfAxis) {
const axisIndexInPathToNode = sketchIndexInBody = Math.max(sketchIndexInBody, axisIndexIfAxis)
axisDeclaration.findIndex((a) => a[0] === 'body') + 1
const axisIndex = axisDeclaration[axisIndexInPathToNode][0]
if (typeof axisIndex !== 'number')
return new Error('expected axisIndex to be a number')
sketchIndexInBody = Math.max(sketchIndexInBody, axisIndex)
} }
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration) clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)

View File

@ -96,12 +96,15 @@ export type ModelingCommandSchema = {
Helix: { Helix: {
// Enables editing workflow // Enables editing workflow
nodeToEdit?: PathToNode nodeToEdit?: PathToNode
// Flow arg
axisOrEdge: 'Axis' | 'Edge'
// KCL stdlib arguments // KCL stdlib arguments
axis: string | undefined
edge: Selections | undefined
revolutions: KclCommandValue revolutions: KclCommandValue
angleStart: KclCommandValue angleStart: KclCommandValue
ccw: boolean ccw: boolean
radius: KclCommandValue radius: KclCommandValue
axis: string
length: KclCommandValue length: KclCommandValue
} }
'event.parameter.create': { 'event.parameter.create': {
@ -532,6 +535,38 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
required: false, required: false,
hidden: true, hidden: true,
}, },
axisOrEdge: {
inputType: 'options',
required: true,
defaultValue: 'Axis',
options: [
{ name: 'Axis', isCurrent: true, value: 'Axis' },
{ name: 'Edge', isCurrent: false, value: 'Edge' },
],
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
axis: {
inputType: 'options',
required: (commandContext) =>
['Axis'].includes(
commandContext.argumentsToSubmit.axisOrEdge as string
),
options: [
{ name: 'X Axis', value: 'X' },
{ name: 'Y Axis', value: 'Y' },
{ name: 'Z Axis', value: 'Z' },
],
},
edge: {
required: (commandContext) =>
['Edge'].includes(
commandContext.argumentsToSubmit.axisOrEdge as string
),
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge'],
multiple: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
revolutions: { revolutions: {
inputType: 'kcl', inputType: 'kcl',
defaultValue: '1', defaultValue: '1',
@ -547,6 +582,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
ccw: { ccw: {
inputType: 'options', inputType: 'options',
required: true, required: true,
displayName: 'CounterClockWise',
defaultValue: false, defaultValue: false,
options: [ options: [
{ name: 'False', value: false }, { name: 'False', value: false },
@ -558,16 +594,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
defaultValue: KCL_DEFAULT_LENGTH, defaultValue: KCL_DEFAULT_LENGTH,
required: true, required: true,
}, },
axis: {
inputType: 'options',
required: true,
defaultValue: 'X',
options: [
{ name: 'X Axis', value: 'X' },
{ name: 'Y Axis', value: 'Y' },
{ name: 'Z Axis', value: 'Z' },
],
},
length: { length: {
inputType: 'kcl', inputType: 'kcl',
defaultValue: KCL_DEFAULT_LENGTH, defaultValue: KCL_DEFAULT_LENGTH,

View File

@ -3,6 +3,7 @@ import {
Artifact, Artifact,
getArtifactOfTypes, getArtifactOfTypes,
getCapCodeRef, getCapCodeRef,
getSweepEdgeCodeRef,
} from 'lang/std/artifactGraph' } from 'lang/std/artifactGraph'
import { Operation } from '@rust/kcl-lib/bindings/Operation' import { Operation } from '@rust/kcl-lib/bindings/Operation'
import { codeManager, engineCommandManager, kclManager } from './singletons' import { codeManager, engineCommandManager, kclManager } from './singletons'
@ -458,12 +459,85 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
} }
// TODO: find a way to loop over the arguments while keeping it safe // TODO: find a way to loop over the arguments while keeping it safe
// axis options string arg
if (!('axis' in operation.labeledArgs) || !operation.labeledArgs.axis) {
return baseCommand
}
const axisValue = operation.labeledArgs.axis.value
let axisOrEdge: 'Axis' | 'Edge' | undefined
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(
{
key: axisValue.artifact_id,
types: ['segment'],
},
engineCommandManager.artifactGraph
)
if (err(artifact)) {
return baseCommand
}
edge = {
graphSelections: [
{
artifact,
codeRef: artifact.codeRef,
},
],
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
}
// revolutions kcl arg // revolutions kcl arg
if ( if (
!('revolutions' in operation.labeledArgs) || !('revolutions' in operation.labeledArgs) ||
!operation.labeledArgs.revolutions !operation.labeledArgs.revolutions
) ) {
return baseCommand return baseCommand
}
const revolutions = await stringToKclExpression( const revolutions = await stringToKclExpression(
codeManager.code.slice( codeManager.code.slice(
operation.labeledArgs.revolutions.sourceRange[0], operation.labeledArgs.revolutions.sourceRange[0],
@ -476,19 +550,23 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
if ( if (
!('angleStart' in operation.labeledArgs) || !('angleStart' in operation.labeledArgs) ||
!operation.labeledArgs.angleStart !operation.labeledArgs.angleStart
) ) {
return baseCommand return baseCommand
}
const angleStart = await stringToKclExpression( const angleStart = await stringToKclExpression(
codeManager.code.slice( codeManager.code.slice(
operation.labeledArgs.angleStart.sourceRange[0], operation.labeledArgs.angleStart.sourceRange[0],
operation.labeledArgs.angleStart.sourceRange[1] 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 // counterClockWise options boolean arg
if (!('ccw' in operation.labeledArgs) || !operation.labeledArgs.ccw) if (!('ccw' in operation.labeledArgs) || !operation.labeledArgs.ccw) {
return baseCommand return baseCommand
}
const ccw = const ccw =
codeManager.code.slice( codeManager.code.slice(
operation.labeledArgs.ccw.sourceRange[0], operation.labeledArgs.ccw.sourceRange[0],
@ -496,46 +574,47 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
) === 'true' ) === 'true'
// radius kcl arg // 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 return baseCommand
}
const radius = await stringToKclExpression( const radius = await stringToKclExpression(
codeManager.code.slice( codeManager.code.slice(
operation.labeledArgs.radius.sourceRange[0], operation.labeledArgs.radius.sourceRange[0],
operation.labeledArgs.radius.sourceRange[1] operation.labeledArgs.radius.sourceRange[1]
) )
) )
if (err(radius) || 'errors' in radius) return baseCommand if (err(radius) || 'errors' in radius) {
// axis options string arg
if (!('axis' in operation.labeledArgs) || !operation.labeledArgs.axis)
return baseCommand return baseCommand
const axis = codeManager.code }
.slice(
operation.labeledArgs.axis.sourceRange[0],
operation.labeledArgs.axis.sourceRange[1]
)
.replaceAll("'", '') // TODO: fix this crap
// length kcl arg // length kcl arg
if (!('length' in operation.labeledArgs) || !operation.labeledArgs.length) if (!('length' in operation.labeledArgs) || !operation.labeledArgs.length) {
return baseCommand return baseCommand
}
const length = await stringToKclExpression( const length = await stringToKclExpression(
codeManager.code.slice( codeManager.code.slice(
operation.labeledArgs.length.sourceRange[0], operation.labeledArgs.length.sourceRange[0],
operation.labeledArgs.length.sourceRange[1] 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, // Assemble the default argument values for the Offset Plane command,
// with `nodeToEdit` set, which will let the Offset Plane actor know // with `nodeToEdit` set, which will let the Offset Plane actor know
// to edit the node that corresponds to the StdLibCall. // to edit the node that corresponds to the StdLibCall.
const argDefaultValues: ModelingCommandSchema['Helix'] = { const argDefaultValues: ModelingCommandSchema['Helix'] = {
axisOrEdge,
axis,
edge,
revolutions, revolutions,
angleStart, angleStart,
ccw, ccw,
radius, radius,
axis,
length, length,
nodeToEdit: getNodePathFromSourceRange( nodeToEdit: getNodePathFromSourceRange(
kclManager.ast, kclManager.ast,

View File

@ -41,7 +41,10 @@ import {
applyConstraintEqualLength, applyConstraintEqualLength,
setEqualLengthInfo, setEqualLengthInfo,
} from 'components/Toolbar/EqualLength' } from 'components/Toolbar/EqualLength'
import { revolveSketch } from 'lang/modifyAst/addRevolve' import {
getAxisExpressionAndIndex,
revolveSketch,
} from 'lang/modifyAst/addRevolve'
import { import {
addHelix, addHelix,
addOffsetPlane, addOffsetPlane,
@ -1904,11 +1907,13 @@ export const modelingMachine = setup({
// Extract inputs // Extract inputs
const ast = kclManager.ast const ast = kclManager.ast
const { const {
axisOrEdge,
axis,
edge,
revolutions, revolutions,
angleStart, angleStart,
ccw, ccw,
radius, radius,
axis,
length, length,
nodeToEdit, nodeToEdit,
} = input } = input
@ -1936,6 +1941,25 @@ export const modelingMachine = setup({
opInsertIndex = nodeToEdit[1][0] opInsertIndex = nodeToEdit[1][0]
} }
const getAxisResult = getAxisExpressionAndIndex(
axisOrEdge,
axis,
edge,
ast
)
if (err(getAxisResult)) return 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
// }
for (const variable of [revolutions, angleStart, radius, length]) { for (const variable of [revolutions, angleStart, radius, length]) {
// Insert the variable if it exists // Insert the variable if it exists
if ( if (
@ -1958,33 +1982,28 @@ export const modelingMachine = setup({
? variable.variableIdentifierAst ? variable.variableIdentifierAst
: variable.valueAst : variable.valueAst
const result = addHelix({ const { modifiedAst, pathToNode } = addHelix({
node: ast, node: ast,
revolutions: valueOrVariable(revolutions), revolutions: valueOrVariable(revolutions),
angleStart: valueOrVariable(angleStart), angleStart: valueOrVariable(angleStart),
ccw, ccw,
radius: valueOrVariable(radius), radius: valueOrVariable(radius),
axis, axis: generatedAxis,
length: valueOrVariable(length), length: valueOrVariable(length),
insertIndex: opInsertIndex, insertIndex: opInsertIndex,
variableName: opVariableName, variableName: opVariableName,
}) })
await updateModelingState(
const updateAstResult = await kclManager.updateAst( modifiedAst,
result.modifiedAst,
true,
{ {
focusPath: [result.pathToNode], kclManager,
editorManager,
codeManager,
},
{
focusPath: [pathToNode],
} }
) )
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
} }
), ),
sweepAstMod: fromPromise( sweepAstMod: fromPromise(