Skip to content

Fix the Bevel node so it applies a consistent bevel size regardless of angle #2293

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

Merged
merged 38 commits into from
Jul 13, 2025

Conversation

MohdMohsin97
Copy link
Contributor

@MohdMohsin97 MohdMohsin97 commented Feb 15, 2025

Previously algorithm only spilt the line from both side with the give length.

411837396-7bfb998d-604c-45d5-90e4-c607c3144855

Update the bevel algorithm for straight line only to give same bevel length.

Screenshot 2025-02-15 222335

Closes #2281

@Keavon
Copy link
Member

Keavon commented Feb 16, 2025

Thanks!

Update the bevel algorithm for straight line only to give same bevel length.

Based on this sentence, do I presume correctly that this means curved segments are left for future work?

@Keavon Keavon changed the title feat: update the bevel algorithm for same bevel length Fix the Bevel node so it applies a consistent bevel size regardless of angle Feb 16, 2025
@MohdMohsin97
Copy link
Contributor Author

MohdMohsin97 commented Feb 17, 2025

Based on this sentence, do I presume correctly that this means curved segments are left for future work?

Yes, currently it only work for straight line only, I'll try to make it work for curve line also.

@Keavon
Copy link
Member

Keavon commented Feb 20, 2025

!build

Copy link

📦 Build Complete for 2900271
https://93383e91.graphite.pages.dev

@Keavon
Copy link
Member

Keavon commented Feb 20, 2025

This doesn't seem to have the expected behavior after passing a certain size, see video:

capture_33_.mp4

Could that be fixed, please?

@Keavon Keavon marked this pull request as draft February 20, 2025 11:27
@MohdMohsin97
Copy link
Contributor Author

I attempted to resolve the algorithm. Is this the expected behavior?

Screen.Recording.2025-02-20.212534.mp4

@Keavon
Copy link
Member

Keavon commented Feb 20, 2025

!build

Copy link

📦 Build Complete for 8c2de3b
https://77b9d277.graphite.pages.dev

@Keavon
Copy link
Member

Keavon commented Feb 20, 2025

capture_34_.mp4

@Keavon
Copy link
Member

Keavon commented Mar 11, 2025

!build

Copy link

📦 Build Complete for 8001050
https://764db1b7.graphite.pages.dev

@MohdMohsin97 MohdMohsin97 marked this pull request as ready for review March 15, 2025 08:31
@MohdMohsin97
Copy link
Contributor Author

Screen.Recording.2025-03-15.144906.mp4

Fixes the curve part

@Keavon
Copy link
Member

Keavon commented Mar 15, 2025

!build

Copy link

📦 Build Complete for f716b8b
https://9f507811.graphite.pages.dev

Copy link
Member

@0HyperCube 0HyperCube left a comment

Choose a reason for hiding this comment

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

Thanks for this contribution; however it does not work if the layer has a transform applied to it.

@0HyperCube 0HyperCube marked this pull request as draft March 26, 2025 18:33
@Keavon Keavon force-pushed the master branch 3 times, most recently from c7488cf to 1875779 Compare June 27, 2025 01:46
@Keavon
Copy link
Member

Keavon commented Jul 9, 2025

!build

Copy link

github-actions bot commented Jul 9, 2025

📦 Build Complete for 83a1e1f
https://e3360650.graphite.pages.dev

@Keavon
Copy link
Member

Keavon commented Jul 9, 2025

Adding bevel to the following layer crashes the node (paste this into a document then add a Bevel node):

graphite/layer: [{"nodes":[[3,{"document_node":{"inputs":[{"Value":{"tagged_value":{"VectorData":{"instance":[],"transform":[],"alpha_blending":[],"source_node_id":[]}},"exposed":true}},{"Value":{"tagged_value":{"VectorModification":{"points":{"add":[7101966268902852897,12959623941848655864,9422361377909595935,3098174827925137377],"remove":[],"delta":[[12959623941848655864,[0.0,0.0]],[9422361377909595935,[-100.0,0.0]],[7101966268902852897,[0.0,0.0]],[3098174827925137377,[100.0,0.0]]]},"segments":{"add":[16003530597502309584,10585305760792964956,6527407443855195459],"remove":[],"start_point":[[16003530597502309584,7101966268902852897],[10585305760792964956,7101966268902852897],[6527407443855195459,12959623941848655864]],"end_point":[[6527407443855195459,3098174827925137377],[16003530597502309584,12959623941848655864],[10585305760792964956,9422361377909595935]],"handle_primary":[[6527407443855195459,[10.0,0.0]],[16003530597502309584,[0.0,0.0]],[10585305760792964956,[0.0,0.0]]],"handle_end":[[6527407443855195459,[-90.0,100.0]],[16003530597502309584,[0.0,0.0]],[10585305760792964956,[0.0,0.0]]],"stroke":[[10585305760792964956,0],[16003530597502309584,0],[6527407443855195459,0]]},"regions":{"add":[],"remove":[],"segment_range":[],"fill":[]},"add_g1_continuous":[],"remove_g1_continuous":[[{"ty":"End","segment":16003530597502309584},{"ty":"Primary","segment":6527407443855195459}]]}},"exposed":false}}],"manual_composition":null,"implementation":{"Network":{"exports":[{"Node":{"node_id":1,"output_index":0,"lambda":false}}],"nodes":[[0,{"inputs":[{"Network":{"import_type":{"Concrete":{"name":"graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>","alias":null}},"import_index":0}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::memo::MonitorNode"}},"visible":true,"skip_deduplication":true}],[1,{"inputs":[{"Node":{"node_id":0,"output_index":0,"lambda":false}},{"Network":{"import_type":{"Concrete":{"name":"graphene_core::vector::vector_data::modification::VectorModification","alias":null}},"import_index":1}},{"Reflection":"DocumentNodePath"}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::vector::vector_data::modification::PathModifyNode"}},"visible":true,"skip_deduplication":false}]],"scope_injections":[]}},"visible":true,"skip_deduplication":false},"persistent_node_metadata":{"reference":"Path","display_name":"Path","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Vector Data","input_description":"TODO"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Modification","input_description":"TODO"}}],"output_names":["Vector Data"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":"Chain"}},"network_metadata":{"persistent_metadata":{"node_metadata":[[1,{"persistent_metadata":{"reference":null,"display_name":"Path Modify","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[7,0]}}},"network_metadata":null}}],[0,{"persistent_metadata":{"reference":null,"display_name":"Monitor","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[0,0]}}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,0.0,0.0],"node_graph_top_right":[0.0,0.0]},"selection_undo_history":[],"selection_redo_history":[]}}}}],[1,{"document_node":{"inputs":[{"Node":{"node_id":2,"output_index":0,"lambda":false}},{"Value":{"tagged_value":{"OptionalColor":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}},"exposed":false}},{"Value":{"tagged_value":{"F64":2.0},"exposed":false}},{"Value":{"tagged_value":{"StrokeAlign":"Center"},"exposed":false}},{"Value":{"tagged_value":{"StrokeCap":"Butt"},"exposed":false}},{"Value":{"tagged_value":{"StrokeJoin":"Miter"},"exposed":false}},{"Value":{"tagged_value":{"F64":4.0},"exposed":false}},{"Value":{"tagged_value":{"PaintOrder":"StrokeAbove"},"exposed":false}},{"Value":{"tagged_value":{"VecF64":[]},"exposed":false}},{"Value":{"tagged_value":{"F64":0.0},"exposed":false}}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::vector::StrokeNode"}},"visible":true,"skip_deduplication":false},"persistent_node_metadata":{"reference":"Stroke","display_name":"Stroke","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Vector Data","input_description":"The vector elements, or group of vector elements, to apply the stroke to.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Color","input_description":"The stroke color.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Weight","input_description":"The stroke weight.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Align","input_description":"The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Cap","input_description":"The shape of the stroke at open endpoints.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Join","input_description":"The curvature of the bent stroke at sharp corners.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Miter Limit","input_description":"The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Paint Order","input_description":"The order to paint the stroke on top of the fill, or the fill on top of the stroke.\n<https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Dash Lengths","input_description":"The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Dash Offset","input_description":"The phase offset distance from the starting point of the dash pattern.\n"}}],"output_names":["Instances<VectorData>"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":"Chain"}},"network_metadata":null}}],[2,{"document_node":{"inputs":[{"Node":{"node_id":3,"output_index":0,"lambda":false}},{"Value":{"tagged_value":{"Fill":{"Solid":{"red":0.99999994,"green":0.99999994,"blue":0.99999994,"alpha":1.0}}},"exposed":false}},{"Value":{"tagged_value":{"OptionalColor":{"red":0.99999994,"green":0.99999994,"blue":0.99999994,"alpha":1.0}},"exposed":false}},{"Value":{"tagged_value":{"Gradient":{"stops":[[0.0,{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}],[1.0,{"red":1.0,"green":1.0,"blue":1.0,"alpha":1.0}]],"gradient_type":"Linear","start":[0.0,0.5],"end":[1.0,0.5],"transform":[1.0,0.0,0.0,1.0,0.0,0.0]}},"exposed":false}}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::vector::FillNode"}},"visible":true,"skip_deduplication":false},"persistent_node_metadata":{"reference":"Fill","display_name":"Fill","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Vector Data","input_description":"The vector elements, or group of vector elements, to apply the fill to.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Fill","input_description":"The fill to paint the path with.\n"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Backup Color","input_description":""}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Backup Gradient","input_description":""}}],"output_names":["Instances<VectorData>"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":"Chain"}},"network_metadata":null}}],[0,{"document_node":{"inputs":[{"Value":{"tagged_value":{"GraphicGroup":{"instance":[],"transform":[],"alpha_blending":[],"source_node_id":[]}},"exposed":true}},{"Node":{"node_id":1,"output_index":0,"lambda":false}}],"manual_composition":null,"implementation":{"Network":{"exports":[{"Node":{"node_id":3,"output_index":0,"lambda":false}}],"nodes":[[0,{"inputs":[{"Network":{"import_type":{"Generic":"T"},"import_index":1}}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::ToElementNode"}},"visible":true,"skip_deduplication":false}],[3,{"inputs":[{"Node":{"node_id":1,"output_index":0,"lambda":false}},{"Node":{"node_id":2,"output_index":0,"lambda":false}},{"Reflection":"DocumentNodePath"}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::LayerNode"}},"visible":true,"skip_deduplication":false}],[2,{"inputs":[{"Node":{"node_id":0,"output_index":0,"lambda":false}}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::memo::MonitorNode"}},"visible":true,"skip_deduplication":true}],[1,{"inputs":[{"Network":{"import_type":{"Generic":"T"},"import_index":0}}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::ToGroupNode"}},"visible":true,"skip_deduplication":false}]],"scope_injections":[]}},"visible":true,"skip_deduplication":false},"persistent_node_metadata":{"reference":"Merge","display_name":"","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Graphical Data","input_description":"TODO"}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"Over","input_description":"TODO"}}],"output_names":["Out"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Layer":{"position":{"Absolute":[-6,5]}}},"network_metadata":{"persistent_metadata":{"node_metadata":[[3,{"persistent_metadata":{"reference":null,"display_name":"Layer","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}},{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[0,-3]}}},"network_metadata":null}}],[2,{"persistent_metadata":{"reference":null,"display_name":"Monitor","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-7,-1]}}},"network_metadata":null}}],[0,{"persistent_metadata":{"reference":null,"display_name":"To Element","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-14,-1]}}},"network_metadata":null}}],[1,{"persistent_metadata":{"reference":null,"display_name":"To Group","input_metadata":[{"persistent_metadata":{"input_data":{},"widget_override":null,"input_name":"","input_description":""}}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-14,-3]}}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,0.0,0.0],"node_graph_top_right":[0.0,0.0]},"selection_undo_history":[],"selection_redo_history":[]}}}}]],"selected":true,"visible":true,"locked":false,"collapsed":false}]

This is a path manually constructed to represent these points from the bevel_repeated_point test, which is one of the four failing tests:

  • vector::vector_nodes::test::bevel_open_curve
  • vector::vector_nodes::test::bevel_rect
  • vector::vector_nodes::test::bevel_repeated_point
  • vector::vector_nodes::test::bevel_with_transform

Please fix the edge case covered by the crash (I presume it's the case where there's a zero-length segment connected between two other segments).

And then please also fix the four test cases which are failing, either due to flaws in the algorithm (which should be fixed) or outdated expected cases in the test output calculations (which should be updated within the tests). Please take care to analyze which case is which while solving these.

Thanks! The behavior is looking really nice to use now, so I look forward to getting this merged.

@Keavon Keavon marked this pull request as draft July 9, 2025 04:50
@Keavon Keavon force-pushed the master branch 4 times, most recently from ec51271 to e025103 Compare July 10, 2025 05:48
@MohdMohsin97 MohdMohsin97 marked this pull request as ready for review July 13, 2025 10:22
@Keavon Keavon enabled auto-merge (squash) July 13, 2025 21:42
@Keavon Keavon merged commit c5800aa into GraphiteEditor:master Jul 13, 2025
4 checks passed
urisinger pushed a commit to urisinger/Graphite that referenced this pull request Jul 15, 2025
…f angle (GraphiteEditor#2293)

* feat: update the bevel algorithm for same bevel length

* Fix: Resolves issue in bevel algorithm

* fix bevel algorithm

* Feat : update the bevel algorithm for bezier curve

* Feat: Add support for rounded and inward-rounded Bevel options

* fix bevel algo for curves

* Nits

* refactor area node

* refactor close path node

* small refactor of 'check_point_inside_shape' method

* copy is_linear function from bezier-rs lib

* refactor bevel node implementation

* cleanup

* refactor bevel implementation

* fix transformation

* cleanup

* update the bevel algorithm

* Code review

* Clean up setup logic for failing test

* update tests to reflect new algorithm

* Decimal integer nits

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: indierusty <indierusty@gmail.com>
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.

Improve Bevel node algorithm
4 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy