Skip to content

Commit 2d9b292

Browse files
authored
Point-and-click transforms & appearance refactor with opt args (#7635)
* WIP: `global` optional arg for all point-and-click transforms and add Scale Closes #7615 and #7634 * WIP transforms like sweep * WIP: Allow all sweeps to work on variable-less profiles Fixes #7657 * WIP * WIP * Fix test * Fix circ dep * Add unit tests for getVariableExprsFromSelection * Codespell * Add unit tests for createVariableExpressionsArray * Add unit test createPathToNodeForLastVariable * WIP new util function * Add other test case * We going * Lint & complete addExtrude tests * Add addSweep test * Add basic addLoft and addRevolve tests, will have to see how to distribute coverage * Progress integrating new stuff from sweeps * Add support for imports * Update rotate and scale to match * WIP edit flows * Fixing up edit flows for transforms, and WIP for simpler feature tree action * Clean pu * Clean up * Big time clean up and bringing clone on for the ride * Quick variable break out for clarity * Add getSketchSelectionsFromOperation test * retrieveSelectionsFromOpArg generic fn and tests * Lint * Extend getVariableExprsFromSelection to include child look up * Add test to catch potentially wrong double pipe substitutions * Fix circular dep and test * Fix bad imports * Fix test, still need to fix circ dep * Fix circular dep * Make transforms components optional args * Move some translate tests from pw to unit * Unit tests for rotate (moved most pw tests) and scale * Bring back variable name for clone and clean up tests * Remove playwright tests and beefing up vitest integration tests * Back to a px click for loft selection :sad:, other fixes * WIP p&c tests * WIP * Fix clone test * Delete irrelevant test * Change lotta things * Wire up appearance as point-and-click operation just like other transforms * Wire up opt args for Appearance * WIP tests * Set composite: false to work around tsc limitation * New unit test, still a bug to fix with appearance edit flow in e2e test * Fix e2e test
1 parent 7ca05a8 commit 2d9b292

20 files changed

+1959
-2026
lines changed

e2e/playwright/point-click-assemblies.spec.ts

Lines changed: 140 additions & 398 deletions
Large diffs are not rendered by default.

e2e/playwright/point-click.spec.ts

Lines changed: 62 additions & 619 deletions
Large diffs are not rendered by default.

src/components/ModelingSidebar/ModelingPanes/FeatureTreePane.tsx

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
stdLibMap,
2626
} from '@src/lib/operations'
2727
import {
28+
commandBarActor,
2829
editorManager,
2930
kclManager,
3031
rustContext,
@@ -73,6 +74,36 @@ export const FeatureTreePane = () => {
7374
scrollToError: () => {
7475
editorManager.scrollToFirstErrorDiagnosticIfExists()
7576
},
77+
sendTranslateCommand: () => {
78+
commandBarActor.send({
79+
type: 'Find and select command',
80+
data: { name: 'Translate', groupId: 'modeling' },
81+
})
82+
},
83+
sendRotateCommand: () => {
84+
commandBarActor.send({
85+
type: 'Find and select command',
86+
data: { name: 'Rotate', groupId: 'modeling' },
87+
})
88+
},
89+
sendScaleCommand: () => {
90+
commandBarActor.send({
91+
type: 'Find and select command',
92+
data: { name: 'Scale', groupId: 'modeling' },
93+
})
94+
},
95+
sendCloneCommand: () => {
96+
commandBarActor.send({
97+
type: 'Find and select command',
98+
data: { name: 'Clone', groupId: 'modeling' },
99+
})
100+
},
101+
sendAppearanceCommand: () => {
102+
commandBarActor.send({
103+
type: 'Find and select command',
104+
data: { name: 'Appearance', groupId: 'modeling' },
105+
})
106+
},
76107
sendSelectionEvent: ({ context }) => {
77108
if (!context.targetSourceRange) {
78109
return
@@ -379,10 +410,6 @@ const OperationItem = (props: {
379410
}
380411
}
381412

382-
/**
383-
* For now we can only enter the "edit" flow for the startSketchOn operation.
384-
* TODO: https://github.com/KittyCAD/modeling-app/issues/4442
385-
*/
386413
function enterEditFlow() {
387414
if (
388415
props.item.type === 'StdLibCall' ||
@@ -434,6 +461,18 @@ const OperationItem = (props: {
434461
}
435462
}
436463

464+
function enterScaleFlow() {
465+
if (props.item.type === 'StdLibCall' || props.item.type === 'GroupBegin') {
466+
props.send({
467+
type: 'enterScaleFlow',
468+
data: {
469+
targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
470+
currentOperation: props.item,
471+
},
472+
})
473+
}
474+
}
475+
437476
function enterCloneFlow() {
438477
if (props.item.type === 'StdLibCall' || props.item.type === 'GroupBegin') {
439478
props.send({
@@ -565,7 +604,7 @@ const OperationItem = (props: {
565604
!stdLibMap[props.item.name]?.supportsTransform
566605
}
567606
>
568-
Set translate
607+
Translate
569608
</ContextMenuItem>,
570609
<ContextMenuItem
571610
onClick={enterRotateFlow}
@@ -575,7 +614,17 @@ const OperationItem = (props: {
575614
!stdLibMap[props.item.name]?.supportsTransform
576615
}
577616
>
578-
Set rotate
617+
Rotate
618+
</ContextMenuItem>,
619+
<ContextMenuItem
620+
onClick={enterScaleFlow}
621+
data-testid="context-menu-set-scale"
622+
disabled={
623+
props.item.type !== 'GroupBegin' &&
624+
!stdLibMap[props.item.name]?.supportsTransform
625+
}
626+
>
627+
Scale
579628
</ContextMenuItem>,
580629
<ContextMenuItem
581630
onClick={enterCloneFlow}

src/lang/modifyAst.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,7 @@ profile001 = circle(sketch001, center = [0, 0], radius = 1)
10581058
const call = createCallExpressionStdLibKw('extrude', exprs, [
10591059
createLabeledArg('length', createLiteral(5)),
10601060
])
1061-
const pathToNode = setCallInAst(ast, call)
1061+
const pathToNode = setCallInAst({ ast, call, variableIfNewDecl: 'extrude' })
10621062
if (err(pathToNode)) {
10631063
throw pathToNode
10641064
}
@@ -1092,7 +1092,11 @@ profile001 = circle(sketch001, center = [0, 0], radius = 1)
10921092
const call = createCallExpressionStdLibKw('extrude', exprs, [
10931093
createLabeledArg('length', createLiteral(5)),
10941094
])
1095-
const pathToNode = setCallInAst(ast, call, undefined, vars.pathIfPipe)
1095+
const pathToNode = setCallInAst({
1096+
ast,
1097+
call,
1098+
pathIfNewPipe: vars.pathIfPipe,
1099+
})
10961100
if (err(pathToNode)) {
10971101
throw pathToNode
10981102
}
@@ -1126,7 +1130,12 @@ profile001 = circle(sketch001, center = [0, 0], radius = 1)
11261130
const call = createCallExpressionStdLibKw('extrude', exprs, [
11271131
createLabeledArg('length', createLiteral(5)),
11281132
])
1129-
const pathToNode = setCallInAst(ast, call, undefined, vars.pathIfPipe)
1133+
const pathToNode = setCallInAst({
1134+
ast,
1135+
call,
1136+
pathIfNewPipe: vars.pathIfPipe,
1137+
variableIfNewDecl: 'extrude',
1138+
})
11301139
if (err(pathToNode)) {
11311140
throw pathToNode
11321141
}

src/lang/modifyAst.ts

Lines changed: 55 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { NonCodeMeta } from '@rust/kcl-lib/bindings/NonCodeMeta'
66
import {
77
createArrayExpression,
88
createCallExpressionStdLibKw,
9+
createExpressionStatement,
910
createIdentifier,
1011
createImportAsSelector,
1112
createImportStatement,
@@ -596,39 +597,6 @@ export function addHelix({
596597
}
597598
}
598599

599-
/**
600-
* Add clone statement
601-
*/
602-
export function addClone({
603-
ast,
604-
geometryName,
605-
variableName,
606-
}: {
607-
ast: Node<Program>
608-
geometryName: string
609-
variableName: string
610-
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } {
611-
const modifiedAst = structuredClone(ast)
612-
const variable = createVariableDeclaration(
613-
variableName,
614-
createCallExpressionStdLibKw('clone', createLocalName(geometryName), [])
615-
)
616-
617-
modifiedAst.body.push(variable)
618-
const insertAt = modifiedAst.body.length - 1
619-
const pathToNode: PathToNode = [
620-
['body', ''],
621-
[insertAt, 'index'],
622-
['declaration', 'VariableDeclaration'],
623-
['init', 'VariableDeclarator'],
624-
]
625-
626-
return {
627-
modifiedAst,
628-
pathToNode,
629-
}
630-
}
631-
632600
/**
633601
* Return a modified clone of an AST with a named constant inserted into the body
634602
*/
@@ -1189,65 +1157,76 @@ export function createPathToNodeForLastVariable(
11891157
return pathToCall
11901158
}
11911159

1192-
export function setCallInAst(
1193-
ast: Node<Program>,
1194-
call: Node<CallExpressionKw>,
1195-
nodeToEdit?: PathToNode,
1196-
pathIfPipe?: PathToNode
1197-
): Error | PathToNode {
1160+
export function setCallInAst({
1161+
ast,
1162+
call,
1163+
pathToEdit,
1164+
pathIfNewPipe,
1165+
variableIfNewDecl,
1166+
}: {
1167+
ast: Node<Program>
1168+
call: Node<CallExpressionKw>
1169+
pathToEdit?: PathToNode
1170+
pathIfNewPipe?: PathToNode
1171+
variableIfNewDecl?: string
1172+
}): Error | PathToNode {
11981173
let pathToNode: PathToNode | undefined
1199-
if (nodeToEdit) {
1174+
if (pathToEdit) {
12001175
const result = getNodeFromPath<CallExpressionKw>(
12011176
ast,
1202-
nodeToEdit,
1177+
pathToEdit,
12031178
'CallExpressionKw'
12041179
)
12051180
if (err(result)) {
12061181
return result
12071182
}
12081183

12091184
Object.assign(result.node, call)
1210-
pathToNode = nodeToEdit
1211-
} else {
1212-
if (!call.unlabeled && pathIfPipe) {
1213-
const pipe = getNodeFromPath<PipeExpression>(
1185+
pathToNode = pathToEdit
1186+
} else if (pathIfNewPipe) {
1187+
const pipe = getNodeFromPath<PipeExpression>(
1188+
ast,
1189+
pathIfNewPipe,
1190+
'PipeExpression'
1191+
)
1192+
if (err(pipe)) {
1193+
return pipe
1194+
}
1195+
if (pipe.node.type === 'PipeExpression') {
1196+
pipe.node.body.push(call)
1197+
} else if (pipe.node.type === 'CallExpressionKw') {
1198+
const expression = getNodeFromPath<ExpressionStatement>(
12141199
ast,
1215-
pathIfPipe,
1216-
'PipeExpression'
1200+
pathIfNewPipe,
1201+
'ExpressionStatement'
12171202
)
1218-
if (err(pipe)) {
1219-
return pipe
1203+
if (err(expression) || expression.node.type !== 'ExpressionStatement') {
1204+
return new Error('Could not retrieve ExpressionStatement')
12201205
}
12211206

1222-
if (pipe.node.type === 'PipeExpression') {
1223-
pipe.node.body.push(call)
1224-
} else if (pipe.node.type === 'CallExpressionKw') {
1225-
const expression = getNodeFromPath<ExpressionStatement>(
1226-
ast,
1227-
pathIfPipe,
1228-
'ExpressionStatement'
1229-
)
1230-
if (err(expression) || expression.node.type !== 'ExpressionStatement') {
1231-
return new Error('Could not retrieve ExpressionStatement')
1232-
}
1233-
1234-
expression.node.expression = createPipeExpression([
1235-
expression.node.expression,
1236-
call,
1237-
])
1238-
} else {
1239-
return new Error(
1240-
'Expected pipeIfPipe to be a PipeExpression or CallExpressionKw'
1241-
)
1242-
}
1243-
pathToNode = pathIfPipe
1207+
expression.node.expression = createPipeExpression([
1208+
expression.node.expression,
1209+
call,
1210+
])
12441211
} else {
1245-
const name = findUniqueName(ast, call.callee.name.name)
1246-
const declaration = createVariableDeclaration(name, call)
1247-
ast.body.push(declaration)
1248-
const toFirstKwarg = call.arguments.length > 0
1249-
pathToNode = createPathToNodeForLastVariable(ast, toFirstKwarg)
1212+
return new Error(
1213+
'Expected pipeIfPipe to be a PipeExpression or CallExpressionKw'
1214+
)
12501215
}
1216+
pathToNode = pathIfNewPipe
1217+
} else if (variableIfNewDecl) {
1218+
const name = findUniqueName(ast, variableIfNewDecl)
1219+
const declaration = createVariableDeclaration(name, call)
1220+
ast.body.push(declaration)
1221+
const toFirstKwarg = call.arguments.length > 0
1222+
pathToNode = createPathToNodeForLastVariable(ast, toFirstKwarg)
1223+
} else {
1224+
ast.body.push(createExpressionStatement(call))
1225+
pathToNode = [
1226+
['body', ''],
1227+
[ast.body.length - 1, 'index'],
1228+
['expression', 'ExpressionStatement'],
1229+
]
12511230
}
12521231

12531232
return pathToNode

src/lang/modifyAst/faces.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
getSweepArtifactFromSelection,
3333
} from '@src/lang/std/artifactGraph'
3434
import type { OpArg, OpKclValue } from '@rust/kcl-lib/bindings/Operation'
35+
import { KCL_DEFAULT_CONSTANT_PREFIXES } from '@src/lib/constants'
3536

3637
export function addShell({
3738
ast,
@@ -103,12 +104,13 @@ export function addShell({
103104

104105
// 3. If edit, we assign the new function call declaration to the existing node,
105106
// otherwise just push to the end
106-
const pathToNode = setCallInAst(
107-
modifiedAst,
107+
const pathToNode = setCallInAst({
108+
ast: modifiedAst,
108109
call,
109-
nodeToEdit,
110-
vars.pathIfPipe
111-
)
110+
pathToEdit: nodeToEdit,
111+
pathIfNewPipe: vars.pathIfPipe,
112+
variableIfNewDecl: KCL_DEFAULT_CONSTANT_PREFIXES.SHELL,
113+
})
112114
if (err(pathToNode)) {
113115
return pathToNode
114116
}

0 commit comments

Comments
 (0)