Adding point and click revolve workflow for sketch and axis selection (#4687)

* selection stuff

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* trigger CI

* fix bugs

* some edge cut stuff

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* fix sketch mode issues

* fix more tests, selection in sketch related

* more test fixing

* 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)

* Trigger ci

* more sketch mode selection fixes

* fix unit tests

* rename function

* remove .only

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* lint

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* fix bad pathToNode issue

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* fix sketch on face

* migrate a more selections types

* migrate a more selections types

* fix code selection of fillets

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* fix bad path to node, looks like a race

* migrate a more selections types

* migrate a more selections types

* fix cmd bar selections

* fix cmd bar selections

* fix display issues

* feat: implementing axis selection for point and click revolve

* feat: enforcing selection of 2 options for axis rotation

* feat: added negative rotations for the revolve

* fix: fmt, tsc fixes

* migrate a more selections types

* Revert "migrate a more selections types"

This reverts commit 0d0e453bbb.

* migrate a more selections types

* clean up1

* clean up 2

* chore: improving the copy after discussing with Frank

* fix: merge main fixes

* chore: was able to add a seg to a line. Does not check if one exists already

* saving off some code

* chore: moving revolveSketch into own file for readability, improving variable names instead of node1

* chore: renaming more variables for readability

* chore: more renaming

* fix: allows creating a custom rotation on axis

* fix: added opposite edge logic and adj, need to error handle still

* fix: use other import

* feat: point and click on edges, crude implementation

* feat: implemented toast message and returned error message from validation

* fix: auto linter

* fix: addressing tsc errors

* fix: fighting typescript

* fix: cleaning up PR

* fix: trying to resolve more typescript issues

* fix: save off tsc fixes

* fix: adding comments

* fix: resolving tsc errors

* fix: tsc errors

* fix: auto linter fixes and tsc fixes

* fix:??

* fix: revolve ast works with declaration

* fix: retry logic to make sure the disable dry run actually runs

* fix: codespell typo

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Kevin Nadro
2024-12-10 12:11:01 -05:00
committed by GitHub
parent 3f1f40eeba
commit f2cfa4d5cf
12 changed files with 418 additions and 16 deletions

View File

@ -54,6 +54,7 @@ import {
Selections, Selections,
updateSelections, updateSelections,
canLoftSelection, canLoftSelection,
canRevolveSelection,
canShellSelection, canShellSelection,
} from 'lib/selections' } from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect' import { applyConstraintIntersect } from './Toolbar/Intersect'
@ -575,6 +576,26 @@ export const ModelingMachineProvider = ({
if (err(canSweep)) return false if (err(canSweep)) return false
return canSweep return canSweep
}, },
'has valid revolve selection': ({ context: { selectionRanges } }) => {
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
// they have no selection, we should enable the button
// so they can select the face through the cmdbar
// BUT only if there's extrudable geometry
return doesSceneHaveSweepableSketch(kclManager.ast)
}
if (!isSketchPipe(selectionRanges)) return false
const canSweep = canRevolveSelection(selectionRanges)
if (err(canSweep)) return false
return canSweep
},
'has valid loft selection': ({ context: { selectionRanges } }) => { 'has valid loft selection': ({ context: { selectionRanges } }) => {
const hasNoSelection = const hasNoSelection =
selectionRanges.graphSelections.length === 0 || selectionRanges.graphSelections.length === 0 ||

View File

@ -335,7 +335,7 @@ export function mutateAstWithTagForSketchSegment(
return { modifiedAst: astClone, tag } return { modifiedAst: astClone, tag }
} }
function getEdgeTagCall( export function getEdgeTagCall(
tag: string, tag: string,
artifact: Artifact artifact: Artifact
): Node<Identifier | CallExpression> { ): Node<Identifier | CallExpression> {

View File

@ -0,0 +1,154 @@
import { err } from 'lib/trap'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
import {
Program,
PathToNode,
Expr,
CallExpression,
PipeExpression,
VariableDeclarator,
} from 'lang/wasm'
import { Selections } from 'lib/selections'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import {
createLiteral,
createCallExpressionStdLib,
createObjectExpression,
createIdentifier,
createPipeExpression,
findUniqueName,
createVariableDeclaration,
} from 'lang/modifyAst'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import {
mutateAstWithTagForSketchSegment,
getEdgeTagCall,
} from 'lang/modifyAst/addEdgeTreatment'
export function revolveSketch(
ast: Node<Program>,
pathToSketchNode: PathToNode,
shouldPipe = false,
angle: Expr = createLiteral(4),
axis: Selections
):
| {
modifiedAst: Node<Program>
pathToSketchNode: PathToNode
pathToRevolveArg: PathToNode
}
| Error {
const clonedAst = structuredClone(ast)
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
if (err(sketchNode)) return sketchNode
// testing code
const pathToAxisSelection = getNodePathFromSourceRange(
clonedAst,
axis.graphSelections[0]?.codeRef.range
)
const lineNode = getNodeFromPath<CallExpression>(
clonedAst,
pathToAxisSelection,
'CallExpression'
)
if (err(lineNode)) return lineNode
// TODO Kevin: What if |> close(%)?
// TODO Kevin: What if opposite edge
// TODO Kevin: What if the edge isn't planar to the sketch?
// TODO Kevin: add a tag.
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
/* Original Code */
const { node: sketchExpression } = sketchNode
// determine if sketchExpression is in a pipeExpression or not
const sketchPipeExpressionNode = getNodeFromPath<PipeExpression>(
clonedAst,
pathToSketchNode,
'PipeExpression'
)
if (err(sketchPipeExpressionNode)) return sketchPipeExpressionNode
const { node: sketchPipeExpression } = sketchPipeExpressionNode
const isInPipeExpression = sketchPipeExpression.type === 'PipeExpression'
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
clonedAst,
pathToSketchNode,
'VariableDeclarator'
)
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
const {
node: sketchVariableDeclarator,
shallowPath: sketchPathToDecleration,
} = sketchVariableDeclaratorNode
const axisSelection = axis?.graphSelections[0]?.artifact
if (!axisSelection) return new Error('Axis selection is missing.')
const revolveCall = createCallExpressionStdLib('revolve', [
createObjectExpression({
angle: angle,
axis: getEdgeTagCall(tag, axisSelection),
}),
createIdentifier(sketchVariableDeclarator.id.name),
])
if (shouldPipe) {
const pipeChain = createPipeExpression(
isInPipeExpression
? [...sketchPipeExpression.body, revolveCall]
: [sketchExpression as any, revolveCall]
)
sketchVariableDeclarator.init = pipeChain
const pathToRevolveArg: PathToNode = [
...sketchPathToDecleration,
['init', 'VariableDeclarator'],
['body', ''],
[pipeChain.body.length - 1, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: clonedAst,
pathToSketchNode,
pathToRevolveArg,
}
}
// We're not creating a pipe expression,
// but rather a separate constant for the extrusion
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
const sketchIndexInPathToNode =
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
if (typeof sketchIndexInBody !== 'number')
return new Error('expected sketchIndexInBody to be a number')
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
const pathToRevolveArg: PathToNode = [
['body', ''],
[sketchIndexInBody + 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: clonedAst,
pathToSketchNode: [...pathToSketchNode.slice(0, -1), [-1, 'index']],
pathToRevolveArg,
}
}

View File

@ -871,3 +871,15 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
pathToNode: getNodePathFromSourceRange(ast, range), pathToNode: getNodePathFromSourceRange(ast, range),
} }
} }
export function isSolid2D(artifact: Artifact): artifact is solid2D {
return (artifact as solid2D).pathId !== undefined
}
export function isSegment(artifact: Artifact): artifact is SegmentArtifact {
return (artifact as SegmentArtifact).pathId !== undefined
}
export function isSweep(artifact: Artifact): artifact is SweepArtifact {
return (artifact as SweepArtifact).pathId !== undefined
}

View File

@ -9,6 +9,7 @@ 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 { revolveAxisValidator } from './validators'
type OutputFormat = Models['OutputFormat_type'] type OutputFormat = Models['OutputFormat_type']
type OutputTypeKey = OutputFormat['type'] type OutputTypeKey = OutputFormat['type']
@ -46,6 +47,7 @@ export type ModelingCommandSchema = {
Revolve: { Revolve: {
selection: Selections selection: Selections
angle: KclCommandValue angle: KclCommandValue
axis: Selections
} }
Fillet: { Fillet: {
// todo // todo
@ -330,6 +332,13 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
required: true, required: true,
skip: true, skip: true,
}, },
axis: {
required: true,
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: false,
validation: revolveAxisValidator,
},
angle: { angle: {
inputType: 'kcl', inputType: 'kcl',
defaultValue: KCL_DEFAULT_DEGREE, defaultValue: KCL_DEFAULT_DEGREE,

View File

@ -0,0 +1,107 @@
import { Models } from '@kittycad/lib'
import { engineCommandManager } from 'lib/singletons'
import { uuidv4 } from 'lib/utils'
import { CommandBarContext } from 'machines/commandBarMachine'
import { Selections } from 'lib/selections'
import { isSolid2D, isSegment, isSweep } from 'lang/std/artifactGraph'
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
for (let tries = 0; tries < numberOfRetries; tries++) {
try {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'disable_dry_run' },
})
// Exit out since the command was successful
return
} catch (e) {
console.error(e)
console.error('disable_dry_run failed. This is bad!')
}
}
}
// Takes a callback function and wraps it around enable_dry_run and disable_dry_run
export const dryRunWrapper = async (callback: () => Promise<any>) => {
// Gotcha: What about race conditions?
try {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'enable_dry_run' },
})
const result = await callback()
return result
} catch (e) {
console.error(e)
} finally {
await disableDryRunWithRetry(5)
}
}
function isSelections(selections: unknown): selections is Selections {
return (
(selections as Selections).graphSelections !== undefined &&
(selections as Selections).otherSelections !== undefined
)
}
export const revolveAxisValidator = async ({
data,
context,
}: {
data: { [key: string]: Selections }
context: CommandBarContext
}): Promise<boolean | string> => {
if (!isSelections(context.argumentsToSubmit.selection)) {
return 'Unable to revolve, selections are missing'
}
const artifact =
context.argumentsToSubmit.selection.graphSelections[0].artifact
if (!artifact) {
return 'Unable to revolve, sketch not found'
}
if (!(isSolid2D(artifact) || isSegment(artifact) || isSweep(artifact))) {
return 'Unable to revolve, sketch has no path'
}
const sketchSelection = artifact.pathId
let edgeSelection = data.axis.graphSelections[0].artifact?.id
if (!sketchSelection) {
return 'Unable to revolve, sketch is missing'
}
if (!edgeSelection) {
return 'Unable to revolve, edge is missing'
}
const angleInDegrees: Models['Angle_type'] = {
unit: 'degrees',
value: 360,
}
const revolveAboutEdgeCommand = async () => {
return await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'revolve_about_edge',
angle: angleInDegrees,
edge_id: edgeSelection,
target: sketchSelection,
tolerance: 0.0001,
},
})
}
const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
if (attemptRevolve?.success) {
return true
} else {
// return error message for the toast
return 'Unable to revolve with selected axis'
}
}

View File

@ -7,7 +7,7 @@ import { ReactNode } from 'react'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Artifact } from 'lang/std/artifactGraph' import { Artifact } from 'lang/std/artifactGraph'
import { CommandBarContext } from 'machines/commandBarMachine'
type Icon = CustomIconName type Icon = CustomIconName
const PLATFORMS = ['both', 'web', 'desktop'] as const const PLATFORMS = ['both', 'web', 'desktop'] as const
const INPUT_TYPES = [ const INPUT_TYPES = [
@ -147,6 +147,13 @@ export type CommandArgumentConfig<
inputType: 'selection' inputType: 'selection'
selectionTypes: Artifact['type'][] selectionTypes: Artifact['type'][]
multiple: boolean multiple: boolean
validation?: ({
data,
context,
}: {
data: any
context: CommandBarContext
}) => Promise<boolean | string>
} }
| { | {
inputType: 'kcl' inputType: 'kcl'
@ -236,6 +243,13 @@ export type CommandArgument<
inputType: 'selection' inputType: 'selection'
selectionTypes: Artifact['type'][] selectionTypes: Artifact['type'][]
multiple: boolean multiple: boolean
validation?: ({
data,
context,
}: {
data: any
context: CommandBarContext
}) => Promise<boolean | string>
} }
| { | {
inputType: 'kcl' inputType: 'kcl'

View File

@ -111,3 +111,10 @@ export const KCL_SAMPLES_MANIFEST_URLS = {
/** Toast id for the app auto-updater toast */ /** Toast id for the app auto-updater toast */
export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast' export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast'
/** Local sketch axis values in KCL for operations, it could either be 'X' or 'Y' */
export const KCL_AXIS_X = 'X'
export const KCL_AXIS_Y = 'Y'
export const KCL_AXIS_NEG_X = '-X'
export const KCL_AXIS_NEG_Y = '-Y'
export const KCL_DEFAULT_AXIS = 'X'

View File

@ -155,6 +155,8 @@ export function buildCommandArgument<
context: ContextFrom<T>, context: ContextFrom<T>,
machineActor: Actor<T> machineActor: Actor<T>
): CommandArgument<O, T> & { inputType: typeof arg.inputType } { ): CommandArgument<O, T> & { inputType: typeof arg.inputType } {
// GOTCHA: modelingCommandConfig is not a 1:1 mapping to this baseCommandArgument
// You need to manually add key/value pairs here.
const baseCommandArgument = { const baseCommandArgument = {
description: arg.description, description: arg.description,
required: arg.required, required: arg.required,
@ -181,6 +183,7 @@ export function buildCommandArgument<
...baseCommandArgument, ...baseCommandArgument,
multiple: arg.multiple, multiple: arg.multiple,
selectionTypes: arg.selectionTypes, selectionTypes: arg.selectionTypes,
validation: arg.validation,
} satisfies CommandArgument<O, T> & { inputType: 'selection' } } satisfies CommandArgument<O, T> & { inputType: 'selection' }
} else if (arg.inputType === 'kcl') { } else if (arg.inputType === 'kcl') {
return { return {

View File

@ -569,6 +569,17 @@ export function canSweepSelection(selection: Selections) {
) )
} }
export function canRevolveSelection(selection: Selections) {
const commonNodes = selection.graphSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i)
)
return (
!!isSketchPipe(selection) &&
(commonNodes.every((n) => nodeHasClose(n)) ||
commonNodes.every((n) => nodeHasCircle(n)))
)
}
export function canLoftSelection(selection: Selections) { export function canLoftSelection(selection: Selections) {
const commonNodes = selection.graphSelections.map((_, i) => const commonNodes = selection.graphSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i) buildCommonNodeFromSelection(selection, i)

View File

@ -8,6 +8,7 @@ import {
import { Selections__old } from 'lib/selections' import { Selections__old } from 'lib/selections'
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils' import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import toast from 'react-hot-toast'
export type CommandBarContext = { export type CommandBarContext = {
commands: Command[] commands: Command[]
@ -247,14 +248,69 @@ export const commandBarMachine = setup({
'All arguments are skippable': () => false, 'All arguments are skippable': () => false,
}, },
actors: { actors: {
'Validate argument': fromPromise(({ input }) => { 'Validate argument': fromPromise(
return new Promise((resolve, reject) => { ({
// TODO: figure out if we should validate argument data here or in the form itself, input,
// and if we should support people configuring a argument's validation function }: {
input: {
context: CommandBarContext | undefined
event: CommandBarMachineEvent | undefined
}
}) => {
return new Promise((resolve, reject) => {
if (!input || input?.event?.type !== 'Submit argument') {
toast.error(`Unable to validate, wrong event type.`)
return reject(`Unable to validate, wrong event type`)
}
resolve(input) const context = input?.context
})
}), if (!context) {
toast.error(`Unable to validate, wrong argument.`)
return reject(`Unable to validate, wrong argument`)
}
const data = input.event.data
const argName = context.currentArgument?.name
const args = context?.selectedCommand?.args
const argConfig = args && argName ? args[argName] : undefined
// Only do a validation check if the argument, selectedCommand, and the validation function are defined
if (
context.currentArgument &&
context.selectedCommand &&
argConfig?.inputType === 'selection' &&
argConfig?.validation
) {
argConfig
.validation({ context, data })
.then((result) => {
if (typeof result === 'boolean' && result === true) {
return resolve(data)
} else {
// validation failed
if (typeof result === 'string') {
// The result of the validation is the error message
toast.error(result)
return reject(
`unable to validate ${argName}, Message: ${result}`
)
} else {
// Default message if there is not a custom one sent
toast.error(`Unable to validate ${argName}`)
return reject(`unable to validate ${argName}}`)
}
}
})
.catch(() => {
return reject(`unable to validate ${argName}}`)
})
} else {
// Missing several requirements for validate argument, just bypass
return resolve(data)
}
})
}
),
'Validate all arguments': fromPromise( 'Validate all arguments': fromPromise(
({ input }: { input: CommandBarContext }) => { ({ input }: { input: CommandBarContext }) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -449,9 +505,10 @@ export const commandBarMachine = setup({
invoke: { invoke: {
src: 'Validate argument', src: 'Validate argument',
id: 'validateSingleArgument', id: 'validateSingleArgument',
input: ({ event }) => { input: ({ event, context }) => {
if (event.type !== 'Submit argument') return {} if (event.type !== 'Submit argument')
return event.data return { event: undefined, context: undefined }
return { event, context }
}, },
onDone: { onDone: {
target: '#Command Bar.Checking Arguments', target: '#Command Bar.Checking Arguments',

View File

@ -42,12 +42,12 @@ import {
applyConstraintEqualLength, applyConstraintEqualLength,
setEqualLengthInfo, setEqualLengthInfo,
} from 'components/Toolbar/EqualLength' } from 'components/Toolbar/EqualLength'
import { revolveSketch } from 'lang/modifyAst/addRevolve'
import { import {
addOffsetPlane, addOffsetPlane,
deleteFromSelection, deleteFromSelection,
extrudeSketch, extrudeSketch,
loftSketches, loftSketches,
revolveSketch,
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { import {
applyEdgeTreatmentToSelection, applyEdgeTreatmentToSelection,
@ -394,6 +394,7 @@ export const modelingMachine = setup({
guards: { guards: {
'Selection is on face': () => false, 'Selection is on face': () => false,
'has valid sweep selection': () => false, 'has valid sweep selection': () => false,
'has valid revolve selection': () => false,
'has valid loft selection': () => false, 'has valid loft selection': () => false,
'has valid shell selection': () => false, 'has valid shell selection': () => false,
'has valid edge treatment selection': () => false, 'has valid edge treatment selection': () => false,
@ -682,7 +683,7 @@ export const modelingMachine = setup({
if (event.type !== 'Revolve') return if (event.type !== 'Revolve') return
;(async () => { ;(async () => {
if (!event.data) return if (!event.data) return
const { selection, angle } = event.data const { selection, angle, axis } = event.data
let ast = kclManager.ast let ast = kclManager.ast
if ( if (
'variableName' in angle && 'variableName' in angle &&
@ -693,15 +694,21 @@ export const modelingMachine = setup({
newBody.splice(angle.insertIndex, 0, angle.variableDeclarationAst) newBody.splice(angle.insertIndex, 0, angle.variableDeclarationAst)
ast.body = newBody ast.body = newBody
} }
// This is the selection of the sketch that will be revolved
const pathToNode = getNodePathFromSourceRange( const pathToNode = getNodePathFromSourceRange(
ast, ast,
selection.graphSelections[0]?.codeRef.range selection.graphSelections[0]?.codeRef.range
) )
const revolveSketchRes = revolveSketch( const revolveSketchRes = revolveSketch(
ast, ast,
pathToNode, pathToNode,
false, false,
'variableName' in angle ? angle.variableIdentifierAst : angle.valueAst 'variableName' in angle
? angle.variableIdentifierAst
: angle.valueAst,
axis
) )
if (trap(revolveSketchRes)) return if (trap(revolveSketchRes)) return
const { modifiedAst, pathToRevolveArg } = revolveSketchRes const { modifiedAst, pathToRevolveArg } = revolveSketchRes
@ -1687,7 +1694,7 @@ export const modelingMachine = setup({
Revolve: { Revolve: {
target: 'idle', target: 'idle',
guard: 'has valid sweep selection', guard: 'has valid revolve selection',
actions: ['AST revolve'], actions: ['AST revolve'],
reenter: false, reenter: false,
}, },