-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fillet UI #2718
Fillet UI #2718
Conversation
QA Wolf here! As you write new code it's important that your test coverage is keeping up. |
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
src/lang/modifyAst/addFillet.ts
Outdated
pathToSegmentNode: PathToNode, | ||
pathToExtrudeNode: PathToNode, | ||
filletRadius: number = 5 | ||
): { modifiedAst: Program; pathToSegmentNode: PathToNode } { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're decided on a new convention of not throwing, because they can get a little out of hand, and @lf94 has gone to a massive effort to refactor existing throws in #2654, plenty of examples in that PR but the main thing to change is the return type should be { modifiedAst: Program; pathToSegmentNode: PathToNode } | Error
and replace throws with return Promise.reject(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if I would just add an Error as a possible return type:
{ modifiedAst: Program; pathToSegmentNode: PathToNode } | Error
then I would have to return an Error instead of promise:
return new Error(...)
if we want to return the Promise, like this:
return Promise.reject(...)
then I would need to wrap the whole function in the promise:
Promise<{ modifiedAst: Program; pathToSegmentNode: PathToNode } | Error> {
return new Promise((resolve, reject) => {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may return an Error
directly without the promise if that's what makes sense. Promise.reject(new Error(...)) is just for functions which are async. So in other words yes just do T | Error
:)
src/lang/modifyAst/addFillet.ts
Outdated
let tag = null | ||
if (lineSegment.callee.name === 'line') { | ||
if (lineSegment.arguments.length === 2) { | ||
tag = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SEGMENT, 2) | ||
lineSegment.arguments.push(createLiteral(tag)) | ||
} else if (lineSegment.arguments.length === 3) { | ||
if (isLiteral(lineSegment.arguments[2])) { | ||
tag = lineSegment.arguments[2].value as string | ||
} else { | ||
throw new Error('Expected a Literal node') | ||
} | ||
} else { | ||
throw new Error('Line segment not found.') | ||
} | ||
} else { | ||
throw new Error('Line segment not found.') | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes a couple of assumtions, on that it's a line
stdlib function call, and that the tag is in the 3rd positon (which ofc makes sense for line) but really we want this to be flexible for all of the stdlib segment functions. The good news is that all of the sketchLineHelpers
in src/lang/std/sketch.ts
have a util function addTag
that knows how to add a tag for each stdlib segment function. (It's always the 3rd argument, with the exception of close
which it's the second, but there could be more in future where it's different.)
All of these helper function can be combine by using the sketchLineHelpersMap
, you should be able to use addTagForSketchOnFace
or at least take inspiration from it.
There was a typing tweak I wanted to make debf270
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, good point, I've swapped the whole chunk with a:
const { tag } = addTagForSketchOnFace(
{
pathToNode: pathToSegmentNode,
node: node,
},
sketchSegment.callee.name
)
and it works just fine :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay sweet, we should probably rename that function then haha 😝
src/lang/modifyAst/addFillet.ts
Outdated
const filletCall = createCallExpressionStdLib('fillet', [ | ||
createObjectExpression({ | ||
radius: createLiteral(filletRadius), | ||
tags: createArrayExpression([createLiteral(tag)]), | ||
}), | ||
createPipeSubstitution(), | ||
]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌
I was eager to see the fillet button working for the simplest case of one sketch and one extrude in the code editor. You can select the edge, set the radius and that's it. Until now it was crafted for the extrude function, so the text and number of steps are just hard-coded. Modified files: Next steps |
Dope, you're doing really well. Let us know if you get stuck with the cmdbar stuff, feel free to reach out to Frank too, he's done most of the cmdbar implementation. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #2718 +/- ##
=======================================
Coverage 86.75% 86.75%
=======================================
Files 62 62
Lines 24565 24565
=======================================
Hits 21311 21311
Misses 3254 3254
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Big UpdateI've finally touched all the files in the chain and uploaded the working fillet button. Here are the updates: ButtonIcon
@franknoirot, is there an existing naming convention for icons, states, and functions? We might end up with many functions having the same name but different logic for 2D and 3D applications. States
The button is active if:
The button is inactive if:
The checker currently works only with extrudes. Ideally, we should add a body map similar to sketchLineHelperMap. Functions need refactoring to support chamfers and other features. Command BarSelection Text
Selection Types
Selecting the body
Currently, I assume one extrusion per Tests
Current tests cover single selection cases:
Further tests to consider:
Further Steps
Example of a separate pipe: const extrude001 = extrude(5, sketch001)
const myBody = fillet({
radius: 1,
tags: [seg01 ]
}, extrude001) Standbywaiting for the opposite and adjacent edges in the
|
Sorry I might punt the I think the priority should be to get this merged, I know it's not ready yet, but we're just going to be constantly fixing conflicts unless we do. We should take a note out of Jess's note book and do what's she's done for copilot and only have it enable for DEV, that we we can merge it before it's 100% done, and do the rest in follow up PRs. You should be able to use The tests are great, since they're all unit test, we'll definitely want a couple of e2e/playwright tests before the feature is finished (doesn't need to be done right now though) |
@Irev-Dev Could you please leave a couple links related to it? |
So the idea with only having it enable in dev mode, is all the code can be present and merged so long as it's not exposed to the user, in this case I think that mostly means the button and the hotkey so like
|
4c83444
to
fbb86c5
Compare
5134bb2
to
59b2f68
Compare
I went ahead and merged this for Jess and Josh, I'll still do a normal review and leave more comments on what I did etc. |
src/lang/modifyAst/addFillet.test.ts
Outdated
|
||
/** | ||
* 5. Case of existing fillet on selected segment | ||
*/ | ||
|
||
it('should modify existing fillet', async () => { | ||
const code = `const sketch001 = startSketchOn('XZ') | ||
|> startProfileAt([2.16, 49.67], %) | ||
|> line([101.49, 139.93], %) | ||
|> line([60.04, -55.72], %) | ||
|> line([1.29, -115.74], %) | ||
|> line([-87.24, -47.08], %, $seg03) | ||
|> tangentialArcTo([56.15, -94.58], %) | ||
|> tangentialArcTo([14.68, -104.52], %) | ||
|> lineTo([profileStartX(%), profileStartY(%)], %) | ||
|> close(%) | ||
const extrude001 = extrude(50, sketch001) | ||
|> fillet({ radius: 10, tags: [seg03] }, %)` | ||
const segmentSnippet = `line([-87.24, -47.08], %, $seg03)` | ||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` | ||
const radius = createLiteral(5) as Value | ||
const expectedCode = `const sketch001 = startSketchOn('XZ') | ||
|> startProfileAt([2.16, 49.67], %) | ||
|> line([101.49, 139.93], %) | ||
|> line([60.04, -55.72], %) | ||
|> line([1.29, -115.74], %) | ||
|> line([-87.24, -47.08], %, $seg03) | ||
|> tangentialArcTo([56.15, -94.58], %) | ||
|> tangentialArcTo([14.68, -104.52], %) | ||
|> lineTo([profileStartX(%), profileStartY(%)], %) | ||
|> close(%) | ||
const extrude001 = extrude(50, sketch001) | ||
|> fillet({ radius: 5, tags: [seg03] }, %)` | ||
|
||
await runFilletTest( | ||
code, | ||
segmentSnippet, | ||
extrudeSnippet, | ||
radius, | ||
expectedCode | ||
) | ||
}) | ||
|
||
/** | ||
* 6. Case of existing fillet on selected segment | ||
*/ | ||
|
||
it('should modify existing fillet, surrounded with other fillets', async () => { | ||
const code = `const sketch001 = startSketchOn('XZ') | ||
|> startProfileAt([2.16, 49.67], %) | ||
|> line([101.49, 139.93], %) | ||
|> line([60.04, -55.72], %, $seg05) | ||
|> line([1.29, -115.74], %) | ||
|> line([-87.24, -47.08], %, $seg03) | ||
|> tangentialArcTo([56.15, -94.58], %) | ||
|> tangentialArcTo([14.68, -104.52], %, $seg07) | ||
|> lineTo([profileStartX(%), profileStartY(%)], %) | ||
|> close(%) | ||
const extrude001 = extrude(50, sketch001) | ||
|> fillet({ radius: 10, tags: [seg03] }, %) | ||
|> fillet({ radius: 15, tags: [seg05] }, %) | ||
|> fillet({ radius: 7, tags: [seg07] }, %)` | ||
const segmentSnippet = `line([-87.24, -47.08], %, $seg03)` | ||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)` | ||
const radius = createLiteral(5) as Value | ||
const expectedCode = `const sketch001 = startSketchOn('XZ') | ||
|> startProfileAt([2.16, 49.67], %) | ||
|> line([101.49, 139.93], %) | ||
|> line([60.04, -55.72], %, $seg05) | ||
|> line([1.29, -115.74], %) | ||
|> line([-87.24, -47.08], %, $seg03) | ||
|> tangentialArcTo([56.15, -94.58], %) | ||
|> tangentialArcTo([14.68, -104.52], %, $seg07) | ||
|> lineTo([profileStartX(%), profileStartY(%)], %) | ||
|> close(%) | ||
const extrude001 = extrude(50, sketch001) | ||
|> fillet({ radius: 5, tags: [seg03] }, %) | ||
|> fillet({ radius: 15, tags: [seg05] }, %) | ||
|> fillet({ radius: 7, tags: [seg07] }, %)` | ||
|
||
await runFilletTest( | ||
code, | ||
segmentSnippet, | ||
extrudeSnippet, | ||
radius, | ||
expectedCode | ||
) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed these tests because I didn't think they made much sense as the only way to "select" the right source range is for the user to manually put their cursor in the right place, the original edge itself was not selectable since the edge got filleted, so editing the existing fillet I don't think made much sense.
I would however like the fillet itself to be selectable and editable, but that will require some engine updates too.
src/lang/modifyAst/addFillet.ts
Outdated
const pathToSketch = [pathToSegmentNode[0], pathToSegmentNode[1]] | ||
const sketchNode = getNodeFromPath(_node, pathToSketch) as { | ||
node: VariableDeclaration | ||
} | ||
const sketchName = sketchNode.node.declarations[0].id.name | ||
|
||
// 2. Find the extrue expression with the same name | ||
// TODO: Has to work with Pipe Expressions, Lofts, etc... | ||
|
||
let extrudeRange: [number, number] | undefined = undefined | ||
_node.body.forEach((node) => { | ||
traverse(node, { | ||
enter(node) { | ||
if (node.type === 'CallExpression') { | ||
if (node.callee.name === 'extrude') { | ||
if ( | ||
node.arguments[1].type === 'Identifier' && | ||
node.arguments[1].name === sketchName | ||
) { | ||
extrudeRange = [node.start, node.end] | ||
} | ||
} | ||
} | ||
}, | ||
}) | ||
}) | ||
|
||
if (extrudeRange === undefined) { | ||
return new Error('No valid body found') | ||
} | ||
|
||
const preselectedPathToExtrudeTag = getNodePathFromSourceRange( | ||
_node, | ||
extrudeRange | ||
) | ||
const preselectedPathToExtrudeNode = [ | ||
preselectedPathToExtrudeTag[0], | ||
preselectedPathToExtrudeTag[1], | ||
] | ||
if (pathToExtrudeNode.length < 2) { | ||
pathToExtrudeNode = preselectedPathToExtrudeNode | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was no point in having the pathToExtrudeNode
as an input if we were not going to use it, instead I move the logic for finding this path to node into the AST fillet
action. And the way I got the node was with the same method we use for find the extrude for sketch on face, to keep it consistent.
src/lang/modifyAst/addFillet.ts
Outdated
const getPathToNodeOfFilletLiteral = ( | ||
pathToExtrudeNode: PathToNode, | ||
extrudeDeclarator: VariableDeclarator, | ||
tag: string | ||
): PathToNode => { | ||
let pathToFilletObj: any | ||
let inFillet = false | ||
traverse(extrudeDeclarator.init, { | ||
enter(node, path) { | ||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') { | ||
inFillet = true | ||
} | ||
if (inFillet && node.type === 'ObjectExpression') { | ||
const hasTag = node.properties.some((prop) => { | ||
const isTagProp = prop.key.name === 'tags' | ||
if (isTagProp && prop.value.type === 'ArrayExpression') { | ||
return prop.value.elements.some((element) => { | ||
// console.log() | ||
return element.type === 'Identifier' && element.name === tag | ||
}) | ||
} | ||
return false | ||
}) | ||
if (!hasTag) return false | ||
pathToFilletObj = path | ||
node.properties.forEach((prop, index) => { | ||
if (prop.key.name === 'radius') { | ||
pathToFilletObj.push( | ||
['properties', 'ObjectExpression'], | ||
[index, 'index'], | ||
['value', 'Property'] | ||
) | ||
} | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While a little more verbose, this not hardcoding values for the path to node should make it more robust. I made a task on #2606 to write a test for this util
src/lang/modifyAst/addFillet.ts
Outdated
@@ -218,58 +207,20 @@ export function addFillet( | |||
return new Error('Expected an ObjectExpression node') | |||
} | |||
|
|||
if (filletTag === tag) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up removing the edit case for the same reason as ad54158#r1678768111
Related to #2606