Compare commits

...

6 Commits

Author SHA1 Message Date
c64c4705ef Write prepareToEdit callback for Fillet 2025-03-11 16:20:42 -04:00
a122128e0b Add codeAtRange utility 2025-03-11 16:20:27 -04:00
0fe79ea7cb Allow nodeToEdit in Fillet command 2025-03-11 16:20:06 -04:00
021e4b9565 Remove Selection from headerArguments in edit flow tests 2025-03-11 15:21:38 -04:00
e57783483a Hide selection type args if nodeToEdit is present
in workflows that have an edit flow set up
2025-03-11 15:21:14 -04:00
e952148cd2 Allow hidden config to be a callback 2025-03-11 15:19:35 -04:00
7 changed files with 132 additions and 10 deletions

View File

@ -274,7 +274,6 @@ test.describe('Feature Tree pane', () => {
currentArgKey: 'distance',
currentArgValue: initialInput,
headerArguments: {
Selection: '1 face',
Distance: initialInput,
},
highlightedHeaderArg: 'distance',
@ -291,7 +290,6 @@ test.describe('Feature Tree pane', () => {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 face',
// The calculated value is shown in the argument summary
Distance: initialInput,
},

View File

@ -17,7 +17,12 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
if (!selectedCommand?.args) return undefined
const s = { ...selectedCommand.args }
for (const [name, arg] of Object.entries(s)) {
if (arg.hidden) delete s[name]
if (
typeof arg.hidden === 'function'
? arg.hidden(commandBarState.context)
: arg.hidden
)
delete s[name]
}
return s
}, [selectedCommand])

View File

@ -7,7 +7,7 @@ import toast from 'react-hot-toast'
import { editorManager } from 'lib/singletons'
import { Annotation, Transaction } from '@codemirror/state'
import { EditorView, KeyBinding } from '@codemirror/view'
import { recast, Program, parse } from 'lang/wasm'
import { recast, Program, parse, SourceRange } from 'lang/wasm'
import { err, reportRejection } from 'lib/trap'
import { Compartment } from '@codemirror/state'
import { history } from '@codemirror/commands'
@ -57,6 +57,10 @@ export default class CodeManager {
return this._code
}
getCodeAtRange(range: SourceRange) {
return this._code.slice(range[0], range[1])
}
localStoragePersistCode(): string {
return safeLSGetItem(PERSIST_CODE_KEY) || ''
}

View File

@ -69,6 +69,8 @@ export type ModelingCommandSchema = {
edge: Selections
}
Fillet: {
// Enables editing workflow
nodeToEdit?: PathToNode
selection: Selections
radius: KclCommandValue
}
@ -319,6 +321,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: false, // TODO: multiple selection
required: true,
skip: true,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
// result: {
// inputType: 'options',
@ -407,6 +410,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: false, // TODO: multiple selection
required: true,
skip: true,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
axisOrEdge: {
inputType: 'options',
@ -416,6 +420,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
{ name: 'Axis', isCurrent: true, value: 'Axis' },
{ name: 'Edge', isCurrent: false, value: 'Edge' },
],
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
axis: {
required: (commandContext) =>
@ -437,6 +442,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: false,
validation: revolveAxisValidator,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
angle: {
inputType: 'kcl',
@ -534,12 +540,21 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
status: 'development',
needsReview: true,
args: {
nodeToEdit: {
description:
'Path to the node in the AST to edit. Never shown to the user.',
skip: true,
inputType: 'text',
required: false,
hidden: true,
},
selection: {
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: true,
required: true,
skip: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
warningMessage:
'Fillets cannot touch other fillets yet. This is under development.',
},

View File

@ -120,7 +120,12 @@ export type CommandArgumentConfig<
) => boolean)
warningMessage?: string
/** If `true`, arg is used as passed-through data, never for user input */
hidden?: boolean
hidden?:
| boolean
| ((
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
machineContext?: C
) => boolean)
skip?: boolean
/** For showing a summary display of the current value, such as in
* the command bar's header
@ -236,7 +241,12 @@ export type CommandArgument<
machineContext?: ContextFrom<T>
) => boolean)
/** If `true`, arg is used as passed-through data, never for user input */
hidden?: boolean
hidden?:
| boolean
| ((
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
machineContext?: ContextFrom<T>
) => boolean)
skip?: boolean
machineActor?: Actor<T>
warningMessage?: string

View File

@ -1,5 +1,10 @@
import { CustomIconName } from 'components/CustomIcon'
import { Artifact, getArtifactOfTypes } from 'lang/std/artifactGraph'
import {
Artifact,
getArtifactByPredicate,
getArtifactOfTypes,
getArtifactsOfTypes,
} from 'lang/std/artifactGraph'
import { Operation } from '@rust/kcl-lib/bindings/Operation'
import { codeManager, engineCommandManager, kclManager } from './singletons'
import { err } from './trap'
@ -293,6 +298,84 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
}
}
const prepareToEditFillet: PrepareToEditCallback = async ({
artifact,
operation,
}) => {
const baseCommand = {
name: 'Fillet',
groupId: 'modeling',
}
if (
operation.type !== 'StdLibCall' ||
!operation.labeledArgs ||
operation.name !== 'fillet' ||
!('tags' in operation.labeledArgs) ||
!('radius' in operation.labeledArgs) ||
!operation.labeledArgs.radius ||
!artifact ||
artifact.type !== 'edgeCut' ||
artifact.subType !== 'fillet'
) {
return baseCommand
}
if (
!operation.labeledArgs.tags ||
operation.labeledArgs.tags.value.type !== 'Array'
) {
return baseCommand
}
const edgeIds = operation.labeledArgs.tags.value.value.map((tag) =>
tag.type === 'Uuid' ? tag.value : ''
)
if (!edgeIds.every((id) => id)) {
return baseCommand
}
const edges = getArtifactsOfTypes(
{
keys: edgeIds,
types: ['sweepEdge', 'segment'],
},
engineCommandManager.artifactGraph
)
const selection: ModelingCommandSchema['Fillet']['selection'] = {
graphSelections: edges
.values()
.map((edge, index) => ({
artifact: edge,
codeRef: {
range: operation.labeledArgs.tags?.sourceRange ?? [0, 0, 0],
pathToNode: getNodePathFromSourceRange(
kclManager.ast,
operation.labeledArgs.tags?.sourceRange ?? [0, 0, 0]
),
},
}))
.toArray(),
otherSelections: [],
}
const maybeRadius = await stringToKclExpression(
codeManager.getCodeAtRange(operation.labeledArgs.radius.sourceRange)
)
if (err(maybeRadius) || 'errors' in maybeRadius) return baseCommand
const argDefaultValues: ModelingCommandSchema['Fillet'] = {
nodeToEdit: getNodePathFromSourceRange(
kclManager.ast,
sourceRangeFromRust(operation.sourceRange)
),
radius: maybeRadius,
selection,
}
return {
...baseCommand,
argDefaultValues,
}
}
/**
* A map of standard library calls to their corresponding information
* for use in the feature tree UI.
@ -312,6 +395,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
fillet: {
label: 'Fillet',
icon: 'fillet3d',
prepareToEdit: prepareToEditFillet,
},
helix: {
label: 'Helix',

View File

@ -134,8 +134,10 @@ export const commandBarMachine = setup({
// that is, the first argument that is not already in the argumentsToSubmit
// or hidden, or that is not undefined, or that is not marked as "skippable".
// TODO validate the type of the existing arguments
const nonHiddenArgs = Object.entries(selectedCommand.args).filter(
(a) => !a[1].hidden
const nonHiddenArgs = Object.entries(selectedCommand.args).filter((a) =>
a[1].hidden && typeof a[1].hidden === 'function'
? !a[1].hidden(context)
: !a[1].hidden
)
let argIndex = 0
@ -260,7 +262,11 @@ export const commandBarMachine = setup({
},
'All arguments are skippable': ({ context }) => {
return Object.values(context.selectedCommand!.args!).every(
(argConfig) => argConfig.skip || argConfig.hidden
(argConfig) =>
argConfig.skip ||
(typeof argConfig.hidden === 'function'
? argConfig.hidden(context)
: argConfig.hidden)
)
},
'Has selected command': ({ context }) => !!context.selectedCommand,