Add edit flows for extrude and offset plane operations (#5045)
* Start implementing a "prepareToEdit" callback for extrude
* Start of generic edit flow for operations
* Actually invoking command bar send generically on double-click
* Refactor: break out non-React hook helper to calculate Kcl expression value
* Add unit tests, fmt
* Integrate helper to get calculated KclExpression
* Clean up unused imports, simplify use of `programMemoryFromVariables`
* Implement basic extrude editing
* Refactor: move DefaultPlanesStr to its own lib file
* Add support for editing offset planes
* Add Edit right-click menu option
* Turn off edit flow for sketch for now
* Add e2e tests for sketch and offset plane editing, fix bug found with offset plane editing
* Add failing e2e extrude edit test
* Remove action version of extrude AST mod
* Fix behavior when adding a constant while editing operation, fixing e2e test
* Patch in changes from 61b02b5703
* Remove shell's prepareToEdit
* Add other Surface types to `artifactIsPlaneWithPaths`
* refactor: rename `item` to `operation`
* Allow `prepareToEdit` to fail with a toast, signal sketch-on-offset is unimplemented
* Rework sketch e2e test to test several working and failing cases
* Fix tsc errors related to making `codeRef` optional
* Make basic error messages more friendly
* fmt
* Reset modifyAst.ts to main
* Fix broken artifactGraph unit test
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* Remove unused import
* Look at this (photo)Graph *in the voice of Nickelback*
* Make the offset plane insert at the end, not one before
* Fix bug caught by e2e test failure with "Command needs review" logic
* Update src/machines/modelingMachine.ts
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
* Remove console logs per @pierremtb
* Update src/components/CommandBar/CommandBarHeader.tsx
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
* Use better programMemory init thanks @jtran
* Fix tsc post merge of #5068
* Fix logic for `artifactIsPlaneWithPaths` post-merge
* Need to disable the sketch-on-face case now that artifactGraph is in Rust. Will active in a future PR (cc @jtran)
* Re-run CI after snapshots
* Update FeatureTreePane to not use `useCommandsContext`, missed during merge
* Fix merge issue, import location change on edited file
* fix click test step, which I believe is waiting for context scripts to load
* Convert toolbarFixture.exeIndicator to getter
We need to convert all these selectors on fixtures to getters, because
they can go stale if called on the fixture constructor.
* Missed a dumb little thing in toolbarFixture.ts
* Fix goof with merge
* fmt
* Another dumb missed thing during merge
I gotta get used to the LazyGit merge tool I'm not good at it yet
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* Conver sceneFixture's exeIndicator to a getter
Locators on fixtures will be frozen from the time of the fixture's
initialization, I'm increasingly convinced
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* Post-kwargs E2E test cleanup
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
This commit is contained in:
@ -1,19 +1,212 @@
|
||||
import { CustomIconName } from 'components/CustomIcon'
|
||||
import { Artifact, getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||
import { codeManager, engineCommandManager, kclManager } from './singletons'
|
||||
import { err } from './trap'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { sourceRangeFromRust } from 'lang/wasm'
|
||||
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
|
||||
import { stringToKclExpression } from './kclHelpers'
|
||||
import { ModelingCommandSchema } from './commandBarConfigs/modelingCommandConfig'
|
||||
import { isDefaultPlaneStr } from './planes'
|
||||
import { Selections } from './selections'
|
||||
|
||||
type ExecuteCommandEvent = CommandBarMachineEvent & {
|
||||
type: 'Find and select command'
|
||||
}
|
||||
type ExecuteCommandEventPayload = ExecuteCommandEvent['data']
|
||||
type PrepareToEditFailurePayload = { reason: string }
|
||||
type PrepareToEditCallback = (
|
||||
props: Omit<EnterEditFlowProps, 'commandBarSend'>
|
||||
) =>
|
||||
| ExecuteCommandEventPayload
|
||||
| Promise<ExecuteCommandEventPayload | PrepareToEditFailurePayload>
|
||||
|
||||
interface StdLibCallInfo {
|
||||
label: string
|
||||
icon: CustomIconName
|
||||
/**
|
||||
* There are operations which are honored by the feature tree
|
||||
* that do not yet have a corresponding modeling command.
|
||||
*/
|
||||
prepareToEdit?:
|
||||
| ExecuteCommandEventPayload
|
||||
| PrepareToEditCallback
|
||||
| PrepareToEditFailurePayload
|
||||
}
|
||||
|
||||
const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
/**
|
||||
* Gather up the argument values for the Extrude command
|
||||
* to be used in the command bar edit flow.
|
||||
*/
|
||||
const prepareToEditExtrude: PrepareToEditCallback =
|
||||
async function prepareToEditExtrude({ operation, artifact }) {
|
||||
const baseCommand = {
|
||||
name: 'Extrude',
|
||||
groupId: 'modeling',
|
||||
}
|
||||
if (
|
||||
!artifact ||
|
||||
!('pathId' in artifact) ||
|
||||
operation.type !== 'StdLibCall'
|
||||
) {
|
||||
return baseCommand
|
||||
}
|
||||
|
||||
// We have to go a little roundabout to get from the original artifact
|
||||
// to the solid2DId that we need to pass to the Extrude command.
|
||||
const pathArtifact = getArtifactOfTypes(
|
||||
{
|
||||
key: artifact.pathId,
|
||||
types: ['path'],
|
||||
},
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (
|
||||
err(pathArtifact) ||
|
||||
pathArtifact.type !== 'path' ||
|
||||
!pathArtifact.solid2dId
|
||||
)
|
||||
return baseCommand
|
||||
const solid2DArtifact = getArtifactOfTypes(
|
||||
{
|
||||
key: pathArtifact.solid2dId,
|
||||
types: ['solid2d'],
|
||||
},
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(solid2DArtifact) || solid2DArtifact.type !== 'solid2d') {
|
||||
return baseCommand
|
||||
}
|
||||
|
||||
// Convert the length argument from a string to a KCL expression
|
||||
const distanceResult = await stringToKclExpression({
|
||||
value: codeManager.code.slice(
|
||||
operation.labeledArgs?.['length']?.sourceRange[0],
|
||||
operation.labeledArgs?.['length']?.sourceRange[1]
|
||||
),
|
||||
programMemory: kclManager.programMemory.clone(),
|
||||
})
|
||||
if (err(distanceResult) || 'errors' in distanceResult) {
|
||||
return baseCommand
|
||||
}
|
||||
|
||||
// Assemble the default argument values for the Extrude command,
|
||||
// with `nodeToEdit` set, which will let the Extrude actor know
|
||||
// to edit the node that corresponds to the StdLibCall.
|
||||
const argDefaultValues: ModelingCommandSchema['Extrude'] = {
|
||||
selection: {
|
||||
graphSelections: [
|
||||
{
|
||||
artifact: solid2DArtifact,
|
||||
codeRef: pathArtifact.codeRef,
|
||||
},
|
||||
],
|
||||
otherSelections: [],
|
||||
},
|
||||
distance: distanceResult,
|
||||
nodeToEdit: getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRangeFromRust(operation.sourceRange)
|
||||
),
|
||||
}
|
||||
return {
|
||||
...baseCommand,
|
||||
argDefaultValues,
|
||||
}
|
||||
}
|
||||
|
||||
const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
|
||||
operation,
|
||||
}) => {
|
||||
const baseCommand = {
|
||||
name: 'Offset plane',
|
||||
groupId: 'modeling',
|
||||
}
|
||||
if (
|
||||
operation.type !== 'StdLibCall' ||
|
||||
!operation.labeledArgs ||
|
||||
!('std_plane' in operation.labeledArgs) ||
|
||||
!operation.labeledArgs.std_plane ||
|
||||
!('offset' in operation.labeledArgs) ||
|
||||
!operation.labeledArgs.offset
|
||||
) {
|
||||
return baseCommand
|
||||
}
|
||||
// TODO: Implement conversion to arbitrary plane selection
|
||||
// once the Offset Plane command supports it.
|
||||
const planeName = codeManager.code
|
||||
.slice(
|
||||
operation.labeledArgs.std_plane.sourceRange[0],
|
||||
operation.labeledArgs.std_plane.sourceRange[1]
|
||||
)
|
||||
.replaceAll(`'`, ``)
|
||||
|
||||
if (!isDefaultPlaneStr(planeName)) {
|
||||
// TODO: error handling
|
||||
return baseCommand
|
||||
}
|
||||
const planeId = engineCommandManager.getDefaultPlaneId(planeName)
|
||||
if (err(planeId)) {
|
||||
// TODO: error handling
|
||||
return baseCommand
|
||||
}
|
||||
|
||||
const plane: Selections = {
|
||||
graphSelections: [],
|
||||
otherSelections: [
|
||||
{
|
||||
name: planeName,
|
||||
id: planeId,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Convert the distance argument from a string to a KCL expression
|
||||
const distanceResult = await stringToKclExpression({
|
||||
value: codeManager.code.slice(
|
||||
operation.labeledArgs.offset.sourceRange[0],
|
||||
operation.labeledArgs.offset.sourceRange[1]
|
||||
),
|
||||
programMemory: kclManager.programMemory.clone(),
|
||||
})
|
||||
|
||||
if (err(distanceResult) || 'errors' in distanceResult) {
|
||||
return baseCommand
|
||||
}
|
||||
|
||||
// Assemble the default argument values for the Offset Plane command,
|
||||
// with `nodeToEdit` set, which will let the Offset Plane actor know
|
||||
// to edit the node that corresponds to the StdLibCall.
|
||||
const argDefaultValues: ModelingCommandSchema['Offset plane'] = {
|
||||
distance: distanceResult,
|
||||
plane,
|
||||
nodeToEdit: getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRangeFromRust(operation.sourceRange)
|
||||
),
|
||||
}
|
||||
|
||||
return {
|
||||
...baseCommand,
|
||||
argDefaultValues,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of standard library calls to their corresponding information
|
||||
* for use in the feature tree UI.
|
||||
*/
|
||||
export const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
chamfer: {
|
||||
label: 'Chamfer',
|
||||
icon: 'chamfer3d',
|
||||
// modelingEvent: 'Chamfer',
|
||||
},
|
||||
extrude: {
|
||||
label: 'Extrude',
|
||||
icon: 'extrude',
|
||||
prepareToEdit: prepareToEditExtrude,
|
||||
},
|
||||
fillet: {
|
||||
label: 'Fillet',
|
||||
@ -42,6 +235,7 @@ const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
offsetPlane: {
|
||||
label: 'Offset Plane',
|
||||
icon: 'plane',
|
||||
prepareToEdit: prepareToEditOffsetPlane,
|
||||
},
|
||||
patternCircular2d: {
|
||||
label: 'Circular Pattern',
|
||||
@ -70,6 +264,21 @@ const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
startSketchOn: {
|
||||
label: 'Sketch',
|
||||
icon: 'sketch',
|
||||
// TODO: fix matching sketches-on-faces and offset planes back to their
|
||||
// original plane artifacts in order to edit them.
|
||||
async prepareToEdit({ artifact }) {
|
||||
if (artifact) {
|
||||
return {
|
||||
name: 'Enter sketch',
|
||||
groupId: 'modeling',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
reason:
|
||||
'Editing sketches on faces or offset planes through the feature tree is not yet supported. Please double-click the path in the scene for now.',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
sweep: {
|
||||
label: 'Sweep',
|
||||
@ -182,3 +391,47 @@ function isNotUserFunctionWithNoOperations(
|
||||
function isNotUserFunctionReturn(ops: Operation[]): Operation[] {
|
||||
return ops.filter((op) => op.type !== 'UserDefinedFunctionReturn')
|
||||
}
|
||||
|
||||
export interface EnterEditFlowProps {
|
||||
operation: Operation
|
||||
artifact?: Artifact
|
||||
}
|
||||
|
||||
export async function enterEditFlow({
|
||||
operation,
|
||||
artifact,
|
||||
}: EnterEditFlowProps): Promise<Error | CommandBarMachineEvent> {
|
||||
if (operation.type !== 'StdLibCall') {
|
||||
return new Error(
|
||||
'Feature tree editing not yet supported for user-defined functions. Please edit in the code editor.'
|
||||
)
|
||||
}
|
||||
const stdLibInfo = stdLibMap[operation.name]
|
||||
|
||||
if (stdLibInfo && stdLibInfo.prepareToEdit) {
|
||||
if (typeof stdLibInfo.prepareToEdit === 'function') {
|
||||
const eventPayload = await stdLibInfo.prepareToEdit({
|
||||
operation,
|
||||
artifact,
|
||||
})
|
||||
if ('reason' in eventPayload) {
|
||||
return new Error(eventPayload.reason)
|
||||
}
|
||||
return {
|
||||
type: 'Find and select command',
|
||||
data: eventPayload,
|
||||
}
|
||||
} else {
|
||||
return 'reason' in stdLibInfo.prepareToEdit
|
||||
? new Error(stdLibInfo.prepareToEdit.reason)
|
||||
: {
|
||||
type: 'Find and select command',
|
||||
data: stdLibInfo.prepareToEdit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Error(
|
||||
'Feature tree editing not yet supported for this operation. Please edit in the code editor.'
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user