Compare commits

...

60 Commits

Author SHA1 Message Date
f94cc40fcc Add rectangle based sweep test case 2025-03-20 11:55:17 -04:00
f9b2750356 Fix base test 2025-03-20 11:08:01 -04:00
82ca178e77 Merge branch 'pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow' of https://github.com/KittyCAD/modeling-app into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-20 10:54:26 -04:00
6416d9e0d9 Improve filtering readibility 2025-03-20 10:54:24 -04:00
785a63ce90 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 13:32:50 +00:00
9e82921f08 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 13:19:14 +00:00
2dd98c8a96 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 13:04:10 +00:00
ab2075df9d A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 12:51:04 +00:00
45fd7134d3 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 12:33:45 +00:00
5865a08fc8 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 12:20:27 +00:00
0cabc461a9 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 12:06:46 +00:00
a0ea54e33f A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 11:53:17 +00:00
1268e62b97 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-20 07:39:59 -04:00
e8fcd805a1 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-19 20:11:09 -04:00
698e648de7 A snapshot a day keeps the bugs away! 📷🐛 2025-03-19 23:01:06 +00:00
5751ac1aed A snapshot a day keeps the bugs away! 📷🐛 2025-03-19 22:46:51 +00:00
dbb18b13a2 A snapshot a day keeps the bugs away! 📷🐛 2025-03-19 22:27:41 +00:00
e0e7db6cbd A snapshot a day keeps the bugs away! 📷🐛 2025-03-19 22:12:31 +00:00
289cfa86f1 A snapshot a day keeps the bugs away! 📷🐛 2025-03-19 21:57:17 +00:00
e869e2395d Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-19 17:43:03 -04:00
6976eb2041 A snapshot a day keeps the bugs away! 📷🐛 2025-03-19 18:01:52 +00:00
df53436ed0 A snapshot a day keeps the bugs away! 📷🐛 2025-03-19 17:47:51 +00:00
431de3d666 Use updateModelingState in codemod 2025-03-19 13:32:41 -04:00
d296a9eb4a Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-19 13:28:21 -04:00
5bd15932d1 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-19 09:14:03 -04:00
804a544196 A snapshot a day keeps the bugs away! 📷🐛 2025-03-18 19:16:00 +00:00
97cc1863e9 A snapshot a day keeps the bugs away! 📷🐛 2025-03-18 18:57:51 +00:00
38b934255a A snapshot a day keeps the bugs away! 📷🐛 2025-03-18 18:39:35 +00:00
b298e4cb24 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-18 14:25:42 -04:00
c67baa34a0 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-17 16:58:35 -04:00
9a6ca2dfce Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-17 12:34:09 -04:00
f47801a22d Expose the sectional argument in the Sweep command flow
Fixes #5301
2025-03-17 12:24:06 -04:00
52c0fe6144 Fixme back 2025-03-14 11:27:32 -04:00
5584180957 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-14 10:35:25 -04:00
ee414bb5dc Lint 2025-03-13 13:46:51 -04:00
dc48823e9c Add face filtering filter for opposite and next adjacent faces 2025-03-13 12:29:56 -04:00
9f8a7cd2d2 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-13 11:40:49 -04:00
cbf455c7c6 Clean up 2025-03-12 11:10:20 -04:00
b55ecfdea9 Fix edit issue in e2e 2025-03-12 11:09:19 -04:00
19dd060912 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-12 09:46:04 -04:00
3dc9bf282d Made selection args hidden 2025-03-11 16:57:50 -04:00
cc864681f4 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-03-11 16:30:02 -04:00
9744d13d4f Hack sectional 2025-03-10 18:09:21 -04:00
434f1045ef Update from main 2025-03-10 14:54:47 -04:00
575844ff45 Clean up for review 2025-02-28 13:56:29 -05:00
d08f671ab3 Comment out bad test 2025-02-28 13:32:48 -05:00
0512e7d404 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-28 16:32:38 +00:00
638a6d0761 Remove validation on non-selection arg 2025-02-28 11:25:41 -05:00
416299b47a Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-02-28 09:26:16 -05:00
9bd33ed256 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-27 20:31:50 +00:00
2818f5c897 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-27 20:25:11 +00:00
97355d1e86 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-02-27 15:18:20 -05:00
0dfb2fae39 Allow in place editing, more consistent code 2025-02-27 08:56:02 -05:00
4bf580192c Lint 2025-02-27 08:18:27 -05:00
a8d02ac197 Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow 2025-02-27 08:16:29 -05:00
f68310898b Working edit flow 2025-02-24 17:29:54 -05:00
d1563ead8c A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-24 19:52:23 +00:00
0199a30178 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-24 19:45:54 +00:00
ced2f5aa5d A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-24 19:39:37 +00:00
4ef1312c91 WIP: Expose the sectional argument in the Sweep command flow
Fixes #5301
2025-02-24 14:33:06 -05:00
13 changed files with 429 additions and 125 deletions

View File

@ -1333,98 +1333,166 @@ loft001 = loft([sketch001, sketch002])
}) })
}) })
test(`Sweep point-and-click`, async ({ const sweepCases = [
context, {
page, targetType: 'circle',
homePage, testPoint: { x: 700, y: 250 },
scene, initialCode: `sketch001 = startSketchOn('YZ')
editor, profile001 = circle(sketch001, center = [0, 0], radius = 500)
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('YZ')
|> circle(
center = [0, 0],
radius = 500
)
sketch002 = startSketchOn('XZ') sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> xLine(length = -500) |> xLine(length = -500)
|> tangentialArcTo([-2000, 500], %) |> tangentialArcTo([-2000, 500], %)`,
` },
await context.addInitScript((initialCode) => { {
localStorage.setItem('persistCode', initialCode) targetType: 'rectangle',
}, initialCode) testPoint: { x: 710, y: 255 },
await page.setBodyDimensions({ width: 1000, height: 500 }) initialCode: `sketch001 = startSketchOn('YZ')
await homePage.goToModelingScene() profile001 = startProfileAt([-400, -400], sketch001)
await scene.waitForExecutionDone() |> angledLine([0, 800], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) + 90,
800
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(length = -500)
|> tangentialArcTo([-2000, 500], %)`,
},
]
sweepCases.map(({ initialCode, targetType, testPoint }) => {
test(`Sweep point-and-click ${targetType}`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 700, y: 250 } const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y) const [clickOnSketch2] = scene.makeMouseHelpers(
const [clickOnSketch2] = scene.makeMouseHelpers( testPoint.x - 50,
testPoint.x - 50, testPoint.y
testPoint.y )
) const sweepDeclaration =
const sweepDeclaration = 'sweep001 = sweep(sketch001, path = sketch002)' 'sweep001 = sweep(profile001, path = sketch002, sectional = false)'
const editedSweepDeclaration =
'sweep001 = sweep(profile001, path = sketch002, sectional = true)'
await test.step(`Look for sketch001`, async () => { await test.step(`Look for sketch001`, async () => {
await toolbar.closePane('code') await toolbar.closePane('code')
await scene.expectPixelColor([53, 53, 53], testPoint, 15) await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
await test.step(`Go through the command bar flow`, async () => {
await toolbar.sweepButton.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'target',
currentArgValue: '',
headerArguments: {
Target: '',
Trajectory: '',
},
highlightedHeaderArg: 'target',
stage: 'arguments',
}) })
await clickOnSketch1()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'trajectory',
currentArgValue: '',
headerArguments: {
Target: '1 face',
Trajectory: '',
},
highlightedHeaderArg: 'trajectory',
stage: 'arguments',
})
await clickOnSketch2()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await toolbar.openPane('code')
await page.waitForTimeout(500)
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => { await test.step(`Go through the command bar flow`, async () => {
// await scene.expectPixelColor([135, 64, 73], testPoint, 15) // FIXME await toolbar.sweepButton.click()
await editor.expectEditor.toContain(sweepDeclaration) await cmdBar.expectState({
await editor.expectState({ commandName: 'Sweep',
diagnostics: [], currentArgKey: 'target',
activeLines: [sweepDeclaration], currentArgValue: '',
highlightedCode: '', headerArguments: {
Sectional: '',
Target: '',
Trajectory: '',
},
highlightedHeaderArg: 'target',
stage: 'arguments',
})
await clickOnSketch1()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'trajectory',
currentArgValue: '',
headerArguments: {
Sectional: '',
Target: '1 face',
Trajectory: '',
},
highlightedHeaderArg: 'trajectory',
stage: 'arguments',
})
await clickOnSketch2()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Sweep',
headerArguments: {
Target: '1 face',
Trajectory: '1 segment',
Sectional: '',
},
stage: 'review',
})
await cmdBar.progressCmdBar()
}) })
await toolbar.closePane('code')
})
await test.step('Delete sweep via feature tree selection', async () => { await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('feature-tree') await toolbar.openPane('code')
await page.waitForTimeout(500) await editor.expectEditor.toContain(sweepDeclaration)
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0) await scene.expectPixelColor([120, 120, 120], testPoint, 40)
await operationButton.click({ button: 'left' }) await toolbar.closePane('code')
await page.keyboard.press('Delete') })
await page.waitForTimeout(500)
await toolbar.closePane('feature-tree') await test.step('Edit sweep via feature tree selection works', async () => {
await scene.expectPixelColor([53, 53, 53], testPoint, 15) await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Sweep',
0
)
await operationButton.dblclick({ button: 'left' })
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'sectional',
currentArgValue: '',
headerArguments: {
Sectional: '',
},
highlightedHeaderArg: 'sectional',
stage: 'arguments',
})
await cmdBar.selectOption({ name: 'True' }).click()
await cmdBar.expectState({
commandName: 'Sweep',
headerArguments: {
Sectional: '',
},
stage: 'review',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.toContain(editedSweepDeclaration)
await toolbar.closePane('code')
})
await test.step('Delete sweep via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
await page.waitForTimeout(500)
const operationButton = await toolbar.getFeatureTreeOperation(
'Sweep',
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.waitForTimeout(500)
await toolbar.closePane('feature-tree')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
}) })
}) })
@ -1473,6 +1541,7 @@ sketch002 = startSketchOn('XZ')
currentArgKey: 'target', currentArgKey: 'target',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '',
Target: '', Target: '',
Trajectory: '', Trajectory: '',
}, },
@ -1485,6 +1554,7 @@ sketch002 = startSketchOn('XZ')
currentArgKey: 'trajectory', currentArgKey: 'trajectory',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '',
Target: '1 face', Target: '1 face',
Trajectory: '', Trajectory: '',
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -205,6 +205,21 @@ pub(crate) async fn do_post_extrude<'a>(
vec![] vec![]
}; };
// Face filtering attempt in order to resolve https://github.com/KittyCAD/modeling-app/issues/5328
// In case of a sectional sweep, empirically it looks that the first n faces that are yielded from the sweep
// are the ones that work with GetOppositeEdge and GetNextAdjacentEdge, aka the n sides in the sweep.
// So here we're figuring out that n number as yielded_sides_count here,
// making sure that circle() calls count but close() don't (no length)
let yielded_sides_count = sketch
.paths
.iter()
.filter(|p| {
let is_circle = matches!(p, Path::Circle { .. });
let has_length = p.get_base().from != p.get_base().to;
return is_circle || has_length;
})
.count();
for (curve_id, face_id) in face_infos for (curve_id, face_id) in face_infos
.iter() .iter()
.filter(|face_info| face_info.cap == ExtrusionFaceCapType::None) .filter(|face_info| face_info.cap == ExtrusionFaceCapType::None)
@ -215,6 +230,7 @@ pub(crate) async fn do_post_extrude<'a>(
None None
} }
}) })
.take(yielded_sides_count)
{ {
// Batch these commands, because the Rust code doesn't actually care about the outcome. // Batch these commands, because the Rust code doesn't actually care about the outcome.
// So, there's no need to await them. // So, there's no need to await them.

View File

@ -515,30 +515,54 @@ export function addShell({
} }
} }
export function addSweep( export function addSweep({
node: Node<Program>, node,
profileDeclarator: VariableDeclarator, targetDeclarator,
pathDeclarator: VariableDeclarator trajectoryDeclarator,
): { sectional,
variableName,
insertIndex,
}: {
node: Node<Program>
targetDeclarator: VariableDeclarator
trajectoryDeclarator: VariableDeclarator
sectional: boolean
variableName?: string
insertIndex?: number
}): {
modifiedAst: Node<Program> modifiedAst: Node<Program>
pathToNode: PathToNode pathToNode: PathToNode
} { } {
const modifiedAst = structuredClone(node) const modifiedAst = structuredClone(node)
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP) const name =
const sweep = createCallExpressionStdLibKw( variableName ?? findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP)
const call = createCallExpressionStdLibKw(
'sweep', 'sweep',
createIdentifier(profileDeclarator.id.name), createIdentifier(targetDeclarator.id.name),
[createLabeledArg('path', createIdentifier(pathDeclarator.id.name))] [
createLabeledArg('path', createIdentifier(trajectoryDeclarator.id.name)),
createLabeledArg('sectional', createLiteral(sectional)),
]
) )
const declaration = createVariableDeclaration(name, sweep) const variable = createVariableDeclaration(name, call)
modifiedAst.body.push(declaration) const insertAt =
insertIndex !== undefined
? insertIndex
: modifiedAst.body.length
? modifiedAst.body.length
: 0
modifiedAst.body.length
? modifiedAst.body.splice(insertAt, 0, variable)
: modifiedAst.body.push(variable)
const argIndex = 0
const pathToNode: PathToNode = [ const pathToNode: PathToNode = [
['body', ''], ['body', ''],
[modifiedAst.body.length - 1, 'index'], [insertAt, 'index'],
['declaration', 'VariableDeclaration'], ['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpressionKw'], ['arguments', 'CallExpressionKw'],
[0, ARG_INDEX_FIELD], [argIndex, ARG_INDEX_FIELD],
['arg', LABELED_ARG_FIELD], ['arg', LABELED_ARG_FIELD],
] ]

View File

@ -47,8 +47,12 @@ export type ModelingCommandSchema = {
distance: KclCommandValue distance: KclCommandValue
} }
Sweep: { Sweep: {
// Enables editing workflow
nodeToEdit?: PathToNode
// Arguments
target: Selections target: Selections
trajectory: Selections trajectory: Selections
sectional: boolean
} }
Loft: { Loft: {
selection: Selections selection: Selections
@ -345,22 +349,40 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
description: description:
'Create a 3D body by moving a sketch region along an arbitrary path.', 'Create a 3D body by moving a sketch region along an arbitrary path.',
icon: 'sweep', icon: 'sweep',
needsReview: false, needsReview: true,
args: { args: {
nodeToEdit: {
description:
'Path to the node in the AST to edit. Never shown to the user.',
skip: true,
inputType: 'text',
required: false,
},
target: { target: {
inputType: 'selection', inputType: 'selection',
selectionTypes: ['solid2d'], selectionTypes: ['solid2d'],
required: true, required: true,
skip: true, skip: true,
multiple: false, multiple: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
}, },
trajectory: { trajectory: {
inputType: 'selection', inputType: 'selection',
selectionTypes: ['segment', 'path'], selectionTypes: ['segment'],
required: true, required: true,
skip: false, skip: true,
multiple: false, multiple: false,
validation: sweepValidator, validation: sweepValidator,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
sectional: {
inputType: 'options',
required: true,
options: [
{ name: 'False', value: false },
{ name: 'True', value: true },
],
// No validation possible here until we have rollback
}, },
}, },
}, },

View File

@ -311,6 +311,143 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
} }
} }
const prepareToEditSweep: PrepareToEditCallback = async ({
artifact,
operation,
}) => {
const baseCommand = {
name: 'Sweep',
groupId: 'modeling',
}
if (
operation.type !== 'StdLibCall' ||
!operation.labeledArgs ||
!operation.unlabeledArg ||
!('sectional' in operation.labeledArgs) ||
!operation.labeledArgs.sectional
) {
return baseCommand
}
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 Sweep command, just like Extrude.
const pathArtifact = getArtifactOfTypes(
{
key: artifact.pathId,
types: ['path'],
},
engineCommandManager.artifactGraph
)
if (
err(pathArtifact) ||
pathArtifact.type !== 'path' ||
!pathArtifact.solid2dId
) {
return baseCommand
}
const targetArtifact = getArtifactOfTypes(
{
key: pathArtifact.solid2dId,
types: ['solid2d'],
},
engineCommandManager.artifactGraph
)
if (err(targetArtifact) || targetArtifact.type !== 'solid2d') {
return baseCommand
}
const target = {
graphSelections: [
{
artifact: targetArtifact,
codeRef: pathArtifact.codeRef,
},
],
otherSelections: [],
}
// Same roundabout but twice for 'path' aka trajectory: sketch -> path -> segment
if (!('path' in operation.labeledArgs) || !operation.labeledArgs.path) {
return baseCommand
}
if (operation.labeledArgs.path.value.type !== 'Sketch') {
return baseCommand
}
const trajectoryPathArtifact = getArtifactOfTypes(
{
key: operation.labeledArgs.path.value.value.artifactId,
types: ['path'],
},
engineCommandManager.artifactGraph
)
if (err(trajectoryPathArtifact) || trajectoryPathArtifact.type !== 'path') {
return baseCommand
}
const trajectoryArtifact = getArtifactOfTypes(
{
key: trajectoryPathArtifact.segIds[0],
types: ['segment'],
},
engineCommandManager.artifactGraph
)
if (err(trajectoryArtifact) || trajectoryArtifact.type !== 'segment') {
return baseCommand
}
const trajectory = {
graphSelections: [
{
artifact: trajectoryArtifact,
codeRef: trajectoryArtifact.codeRef,
},
],
otherSelections: [],
}
// sectional options boolean arg
if (
!('sectional' in operation.labeledArgs) ||
!operation.labeledArgs.sectional
) {
return baseCommand
}
const sectional =
codeManager.code.slice(
operation.labeledArgs.sectional.sourceRange[0],
operation.labeledArgs.sectional.sourceRange[1]
) === 'true'
// 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['Sweep'] = {
target: target,
trajectory,
sectional,
nodeToEdit: getNodePathFromSourceRange(
kclManager.ast,
sourceRangeFromRust(operation.sourceRange)
),
}
return {
...baseCommand,
argDefaultValues,
}
}
const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => { const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
const baseCommand = { const baseCommand = {
name: 'Helix', name: 'Helix',
@ -511,6 +648,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
sweep: { sweep: {
label: 'Sweep', label: 'Sweep',
icon: 'sweep', icon: 'sweep',
prepareToEdit: prepareToEditSweep,
supportsAppearance: true, supportsAppearance: true,
}, },
} }

View File

@ -78,7 +78,7 @@ import {
import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConfig' import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConfig'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { DefaultPlaneStr } from 'lib/planes' import { DefaultPlaneStr } from 'lib/planes'
import { uuidv4 } from 'lib/utils' import { isArray, uuidv4 } from 'lib/utils'
import { Coords2d } from 'lang/std/sketch' import { Coords2d } from 'lang/std/sketch'
import { deleteSegment } from 'clientSideScene/ClientSideSceneComp' import { deleteSegment } from 'clientSideScene/ClientSideSceneComp'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
@ -97,6 +97,7 @@ import { createProfileStartHandle } from 'clientSideScene/segments'
import { DRAFT_POINT } from 'clientSideScene/sceneInfra' import { DRAFT_POINT } from 'clientSideScene/sceneInfra'
import { setAppearance } from 'lang/modifyAst/setAppearance' import { setAppearance } from 'lang/modifyAst/setAppearance'
import { DRAFT_DASHED_LINE } from 'clientSideScene/sceneEntities' import { DRAFT_DASHED_LINE } from 'clientSideScene/sceneEntities'
import { updateModelingState } from 'lang/modelingWorkflows'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY' export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
@ -1988,55 +1989,88 @@ export const modelingMachine = setup({
if (!input) return new Error('No input provided') if (!input) return new Error('No input provided')
// Extract inputs // Extract inputs
const ast = kclManager.ast const ast = kclManager.ast
const { target, trajectory } = input const { target, trajectory, sectional, nodeToEdit } = input
let variableName: string | undefined = undefined
let insertIndex: number | undefined = undefined
// Find the profile declaration // If this is an edit flow, first we're going to remove the old one
if (nodeToEdit !== undefined && typeof nodeToEdit[1][0] === 'number') {
// Extract the plane name from the node to edit
const variableNode = getNodeFromPath<VariableDeclaration>(
ast,
nodeToEdit,
'VariableDeclaration'
)
if (err(variableNode)) {
console.error('Error extracting name')
} else {
variableName = variableNode.node.declaration.id.name
}
// Removing the old statement
const newBody = [...ast.body]
newBody.splice(nodeToEdit[1][0], 1)
ast.body = newBody
insertIndex = nodeToEdit[1][0]
}
// Find the target declaration
const targetNodePath = getNodePathFromSourceRange( const targetNodePath = getNodePathFromSourceRange(
ast, ast,
target.graphSelections[0].codeRef.range target.graphSelections[0].codeRef.range
) )
const targetNode = getNodeFromPath<VariableDeclarator>( // Gotchas, not sure why
ast, // - it seems like in some cases we get a list on edit, especially the state that e2e hits
targetNodePath, // - looking for a VariableDeclaration seems more robust than VariableDeclarator
'VariableDeclarator' const targetNode = getNodeFromPath<
) VariableDeclaration | VariableDeclaration[]
>(ast, targetNodePath, 'VariableDeclaration')
if (err(targetNode)) { if (err(targetNode)) {
return new Error("Couldn't parse profile selection") return new Error("Couldn't parse profile selection")
} }
const targetDeclarator = targetNode.node
// Find the path declaration const targetDeclarator = isArray(targetNode.node)
? targetNode.node[0].declaration
: targetNode.node.declaration
// Find the trajectory (or path) declaration
const trajectoryNodePath = getNodePathFromSourceRange( const trajectoryNodePath = getNodePathFromSourceRange(
ast, ast,
trajectory.graphSelections[0].codeRef.range trajectory.graphSelections[0].codeRef.range
) )
const trajectoryNode = getNodeFromPath<VariableDeclarator>( // Also looking for VariableDeclaration for consistency here
const trajectoryNode = getNodeFromPath<VariableDeclaration>(
ast, ast,
trajectoryNodePath, trajectoryNodePath,
'VariableDeclarator' 'VariableDeclaration'
) )
if (err(trajectoryNode)) { if (err(trajectoryNode)) {
return new Error("Couldn't parse path selection") return new Error("Couldn't parse path selection")
} }
const trajectoryDeclarator = trajectoryNode.node
const trajectoryDeclarator = trajectoryNode.node.declaration
// Perform the sweep // Perform the sweep
const sweepRes = addSweep(ast, targetDeclarator, trajectoryDeclarator) const { modifiedAst, pathToNode } = addSweep({
const updateAstResult = await kclManager.updateAst( node: ast,
sweepRes.modifiedAst, targetDeclarator,
true, trajectoryDeclarator,
sectional,
variableName,
insertIndex,
})
await updateModelingState(
modifiedAst,
{ {
focusPath: [sweepRes.pathToNode], kclManager,
editorManager,
codeManager,
},
{
focusPath: [pathToNode],
} }
) )
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
} }
), ),
loftAstMod: fromPromise( loftAstMod: fromPromise(

BIN
trace.zip Normal file

Binary file not shown.