Skip to content
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

Merged
merged 9 commits into from
Jul 15, 2024
Merged

Fillet UI #2718

merged 9 commits into from
Jul 15, 2024

Conversation

Irev-Dev
Copy link
Collaborator

@Irev-Dev Irev-Dev commented Jun 20, 2024

Related to #2606

Copy link

qa-wolf bot commented Jun 20, 2024

QA Wolf here! As you write new code it's important that your test coverage is keeping up.
Click here to request test coverage for this PR!

@Irev-Dev Irev-Dev linked an issue Jun 20, 2024 that may be closed by this pull request
Copy link

vercel bot commented Jun 20, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
modeling-app ✅ Ready (Inspect) Visit Preview Jul 15, 2024 7:24am

pathToSegmentNode: PathToNode,
pathToExtrudeNode: PathToNode,
filletRadius: number = 5
): { modifiedAst: Program; pathToSegmentNode: PathToNode } {
Copy link
Collaborator Author

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(...)

Copy link
Collaborator

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) => {

is this the way ?
w69KswqfwebHt4TiZ4HmCd

Copy link
Contributor

@lf94 lf94 Jun 23, 2024

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 :)

Comment on lines 47 to 53
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.')
}
Copy link
Collaborator Author

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

Copy link
Collaborator

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 :)

Copy link
Collaborator Author

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 😝

Comment on lines 70 to 84
const filletCall = createCallExpressionStdLib('fillet', [
createObjectExpression({
radius: createLiteral(filletRadius),
tags: createArrayExpression([createLiteral(tag)]),
}),
createPipeSubstitution(),
])
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

@max-mrgrsk
Copy link
Collaborator

I was eager to see the fillet button working for the simplest case of one sketch and one extrude in the code editor.
And it is working!

You can select the edge, set the radius and that's it.
Selection of extrude node to fillet is just hard-coded for now.
The next step would be to take care of the UI menu in CommandBarSelectionInput.tsx and add the extra step to select the extrude object.

Until now it was crafted for the extrude function, so the text and number of steps are just hard-coded.
I'll have to modify it into a more flexible system that can take any input.

Screenshot 2024-06-23 at 18 21 03

Modified files:
src/Toolbar.tsx - Fillet button and hotkey F
src/machines/modelingMachine.ts - Fillet condition and action
src/lib/selections.ts - canFilletSelection placeholder
src/lib/commandBarConfigs/modelingCommandConfig.ts - Fillet selection types
src/components/ModelingMachineProvider.tsx - ‘fillet selection’ guard
src/components/CommandBarSelectionInput.tsx - Currently works only for the extrude case
src/components/CustomIcon.tsx - Fillet icon placeholder @franknoirot could you please make a nice icon ? ^^
src/lang/modifyAst/addFillet.ts
src/lang/modifyAst/addFillet.test.ts

Next steps
Currently works only with a simplest case of one sketch and one extrude in the kcl

@Irev-Dev
Copy link
Collaborator Author

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.

@Irev-Dev Irev-Dev changed the title 2607 ast mod for fillet Fillet UI Jun 24, 2024
Copy link

codecov bot commented Jun 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 86.75%. Comparing base (bb9d24f) to head (8c87188).
Report is 4 commits behind head on main.

Current head 8c87188 differs from pull request most recent head ddaf36d

Please upload reports for the commit ddaf36d to get more accurate results.

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           
Flag Coverage Δ
wasm-lib 86.75% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@max-mrgrsk
Copy link
Collaborator

max-mrgrsk commented Jul 7, 2024

Big Update

I've finally touched all the files in the chain and uploaded the working fillet button. Here are the updates:

Button

Icon

src/components/CustomIcon.tsx

  • Added a fillet icon

@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

ModelingMachineProvider.tsx

The button is active if:

  • Nothing is selected and there are bodies to fillet in the scene.
  • A sketch segment is selected, and there is a related body.

The button is inactive if:

  • Nothing is selected, and there are no bodies to fillet.
  • A sketch segment is selected, but there is no related body.
  • A non-sketch segment is selected.

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 Bar

Selection Text

src/components/CommandBar/CommandBarSelectionInput.tsx

  • Thanks to @franknoirot, the text now adapts to selection (point/edge/face).

Selection Types

src/lib/commandBarConfigs/modelingCommandConfig.ts

  • The selectionTypes currently only accept extrude-wall. @Irev-Dev

Selecting the body

src/lang/modifyAst/addFillet.ts
src/machines/modelingMachine.ts

Currently, I assume one extrusion per sketch in the scene, using the sketch name as a tag. This works for now, but future lofts, sweeps, and multiple extrudes from one sketch will require precise body selection. We'll need a "body selection tab" in the command bar UI. @Irev-Dev I need some tips for having different selectionRanges for edges and bodies.

Tests

src/lang/modifyAst/addFillet.test.ts

Current tests cover single selection cases:

  1. Single fillet after extrusion
  2. Existing tag in another segment
  3. Tag exists in the selected line
  4. Fillet exists on another segment (same sketch)
  5. Fillet exists on the selected segment (updates radius)
  6. Fillet exists on the selected segment and others (updates radius)

Further tests to consider:

  • Selecting multiple edges
  • Selecting a single edge when it exists in a fillet call with multiple tags (the selected fillet should become a separate call)

Further Steps

  • Select multiple edges
  • Fix Selection Types
  • Add separate fillet pipe
  • Add body selection tab
  • Add a bodyHelperMap
  • Refactor button states
    deny fillet if chamfer exists ?

Example of a separate pipe:

const extrude001 = extrude(5, sketch001)
const myBody = fillet({
  radius: 1,
  tags: [seg01 ]
}, extrude001)

Standby

waiting for the opposite and adjacent edges in the selectionRanges:

getNextAdjacentEdge("tag", myExtrude)
getOppositeEdge("tag2", myExtrude)

@Irev-Dev
Copy link
Collaborator Author

Irev-Dev commented Jul 8, 2024

Sorry I might punt the Selecting the body stuff to @franknoirot as well, he knows better than me.

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 DEV from src/env.ts

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)

@max-mrgrsk
Copy link
Collaborator

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

@Irev-Dev Could you please leave a couple links related to it?

@Irev-Dev
Copy link
Collaborator Author

Irev-Dev commented Jul 9, 2024

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

import { DEV } from 'env'

// ...

{state.matches('idle') && DEV && (
    <li className="contents">

@Irev-Dev Irev-Dev merged commit a1df3d0 into main Jul 15, 2024
15 checks passed
@Irev-Dev Irev-Dev deleted the 2607-ast-mod-for-fillet branch July 15, 2024 09:20
@Irev-Dev
Copy link
Collaborator Author

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.

@Irev-Dev Irev-Dev mentioned this pull request Jul 16, 2024
Comment on lines 232 to 319

/**
* 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
)
})
Copy link
Collaborator Author

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.

Comment on lines 75 to 116
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
}
Copy link
Collaborator Author

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.

Comment on lines 111 to 145
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']
)
}
})
}
Copy link
Collaborator Author

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

@@ -218,58 +207,20 @@ export function addFillet(
return new Error('Expected an ObjectExpression node')
}

if (filletTag === tag) {
Copy link
Collaborator Author

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

@max-mrgrsk max-mrgrsk mentioned this pull request Jul 16, 2024
37 tasks
@Irev-Dev Irev-Dev mentioned this pull request Jul 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fillet UI write AST-mod for adding fillet, piped to extrude variable expression
3 participants