Refactor: Weight Paint Select Linked Faces #104577

Merged
Christoph Lendenfeld merged 15 commits from ChrisLend/blender:weight_paint_refactor_face_select into main 2023-02-23 08:26:44 +01:00

When implementing the operator to select linked vertices in weight paint mode, the new AtomicDisjointSet was used.
In order to keep the code consistent, and also prepare it to add things like Extending/Shrinking selection, the select linked faces logic was also updated.

It now also makes use of the AtomicDisjointSet by connecting all edges of each poly. In order to find connecting Faces you then have to check if edges of that poly share a connection.


I tried to connect poly indices directly but there didn't seem to be a good way to find out which faces are connected without going through the edges first. So I stuck with the edges.

When implementing the operator to select linked vertices in weight paint mode, the new `AtomicDisjointSet` was used. In order to keep the code consistent, and also prepare it to add things like Extending/Shrinking selection, the select linked faces logic was also updated. It now also makes use of the `AtomicDisjointSet` by connecting all edges of each poly. In order to find connecting Faces you then have to check if edges of that poly share a connection. ---- I tried to connect poly indices directly but there didn't seem to be a good way to find out which faces are connected without going through the edges first. So I stuck with the edges.
Christoph Lendenfeld added the
Module
Animation & Rigging
label 2023-02-10 16:18:39 +01:00
Christoph Lendenfeld added 2 commits 2023-02-10 16:18:41 +01:00
Christoph Lendenfeld requested review from Hans Goudey 2023-02-10 16:18:54 +01:00
Christoph Lendenfeld added 1 commit 2023-02-10 16:35:37 +01:00
Hans Goudey requested changes 2023-02-10 16:54:42 +01:00
Hans Goudey left a comment
Member

Very nice! Using edge indices in the disjoint set is a nice way to avoid topology mappings. It doesn't seem obvious though, compared to the recently added logic from FaceSetFromBoundariesInput, for example. I'd think that it wouldn't let two faces separated by a seam be in different islands/groups. What do you think about that? I'll test it after the next update, for now I just looked at the code.

Very nice! Using edge indices in the disjoint set is a nice way to avoid topology mappings. It doesn't seem obvious though, compared to the recently added logic from `FaceSetFromBoundariesInput`, for example. I'd think that it wouldn't let two faces separated by a seam be in different islands/groups. What do you think about that? I'll test it after the next update, for now I just looked at the code.
@ -228,4 +264,0 @@
const Span<MLoop> loops = me->loops();
bke::MutableAttributeAccessor attributes = me->attributes_for_write();
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
".hide_poly", ATTR_DOMAIN_FACE, false);
Member

Maybe I'm forgetting something, but why is it that we can ignore hide_poly now?

Maybe I'm forgetting something, but why is it that we can ignore `hide_poly` now?
Author
Member

thanks I missed that. Of course we can't ignore that :)

thanks I missed that. Of course we can't ignore that :)
ChrisLend marked this conversation as resolved
@ -214,2 +214,3 @@
/* Set object-mode face selection seams based on edge data, uses hash table to find seam edges. */
/**
* Join all edges of each poly in the AtomicDisjointSet. This can be used to find out which polys
Member

Mesh *mesh -> Mesh &mesh

Using a reference indicates that the pointer won't be null

`Mesh *mesh` -> `Mesh &mesh` Using a reference indicates that the pointer won't be null
ChrisLend marked this conversation as resolved
@ -216,0 +233,4 @@
for (const MPoly &poly : polys.slice(range)) {
/* All edges of the face need to be joined in the DisjointSet. Unless the are marked as seam.
*/
for (int outer_loop_index = 0; outer_loop_index < poly.totloop; outer_loop_index++) {
Member

Grammar: "Unless the are"

Also, the previous comment said the same thing, this is redundant.

Grammar: "Unless the are" Also, the previous comment said the same thing, this is redundant.
ChrisLend marked this conversation as resolved
@ -216,0 +235,4 @@
*/
for (int outer_loop_index = 0; outer_loop_index < poly.totloop; outer_loop_index++) {
const MLoop *outer_mloop = &loops[poly.loopstart + outer_loop_index];
if (skip_seams && (edges[outer_mloop->e].flag & ME_SEAM) != 0) {
Member

for (const int loop : IndexRange(poly.loopstart, poly.totloop)) {

`for (const int loop : IndexRange(poly.loopstart, poly.totloop)) {`
ChrisLend marked this conversation as resolved
@ -232,2 +267,4 @@
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
".select_poly", ATTR_DOMAIN_FACE);
Member

MPoly poly -> const MPoly &poly

`MPoly poly` -> `const MPoly &poly`
ChrisLend marked this conversation as resolved
@ -248,3 +272,1 @@
const MPoly &poly = polys[i];
BKE_mesh_poly_edgebitmap_insert(edge_tag, &poly, &loops[poly.loopstart]);
BLI_BITMAP_ENABLE(poly_tag, i);
Set<int> selected_roots;
Member

Try to structure C++ code so that you don't resort to using raw pointers. That will usually lead to more readable code. The C-style for loop syntax is another good thing to avoid.

In this case:

for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {

This applies to other areas in this function too

Try to structure C++ code so that you don't resort to using raw pointers. That will usually lead to more readable code. The C-style for loop syntax is another good thing to avoid. In this case: `for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {` This applies to other areas in this function too
Author
Member

I changed out all for loops except for (int inner_loop_index = outer_loop_index + 1; inner_loop_index < poly.totloop; inner_loop_index++)

I found it a bit confusing to use IndexRange for that use case since I'd have to subtract from the length each time. Might just be me but I find the classic syntax reads clearer in that case

I changed out all for loops except `for (int inner_loop_index = outer_loop_index + 1; inner_loop_index < poly.totloop; inner_loop_index++)` I found it a bit confusing to use `IndexRange` for that use case since I'd have to subtract from the length each time. Might just be me but I find the classic syntax reads clearer in that case
Member

Yeah, since the constructor uses start, size rather than start, end, that makes sense.

Yeah, since the constructor uses `start, size` rather than `start, end`, that makes sense.
Author
Member

using const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop); as you suggested also solved that issue :)

using `const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);` as you suggested also solved that issue :)
@ -282,0 +285,4 @@
threading::parallel_for(select_poly.span.index_range(), 1024, [&](const IndexRange range) {
for (const int poly_index : range) {
MPoly poly = polys[poly_index];
const MLoop *loop = &loops[poly.loopstart];
Member

Add using namespace blender here so it's not necessary below.

Add `using namespace blender` here so it's not necessary below.
ChrisLend marked this conversation as resolved
@ -296,3 +299,4 @@
select_poly.finish();
}
void paintface_select_linked(bContext *C, Object *ob, const int mval[2], const bool select)
Member

Looks like this early return will skip select_poly.finish();. That will probably print a warning to the terminal.

Looks like this early return will skip `select_poly.finish();`. That will probably print a warning to the terminal.
ChrisLend marked this conversation as resolved
Christoph Lendenfeld added 2 commits 2023-02-10 18:05:58 +01:00
Christoph Lendenfeld added 1 commit 2023-02-10 18:07:02 +01:00
Christoph Lendenfeld added 1 commit 2023-02-10 18:14:02 +01:00
Author
Member

@HooglyBoogly
I had a look at FaceSetFromBoundariesInput honestly I didn't even think of using a map. It could work though if there is a way to ignore hidden faces and seams.

@HooglyBoogly I had a look at `FaceSetFromBoundariesInput` honestly I didn't even think of using a map. It could work though if there is a way to ignore hidden faces and seams.
Christoph Lendenfeld requested review from Hans Goudey 2023-02-10 18:23:04 +01:00
Hans Goudey requested changes 2023-02-10 18:36:04 +01:00
@ -218,0 +215,4 @@
/**
* Join all edges of each poly in the AtomicDisjointSet. This can be used to find out which polys
* are connected to each other.
* @param islands Is expected to be of length mesh->totedge.
Member
`@param islands` -> `\param islands:` https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Comments
ChrisLend marked this conversation as resolved
@ -218,0 +220,4 @@
*/
static void build_poly_connections(blender::AtomicDisjointSet &islands,
Mesh &mesh,
const bool skip_seams = true)
Member

Is there a reason to have the skip_seams argument right now? If not, it can always be added easily later.

Is there a reason to have the `skip_seams` argument right now? If not, it can always be added easily later.
Author
Member

My idea was that it makes it explicit what the function is doing. I thought it's not immediately obvious that it would stop at seams.

Let me know what you think. I can remove it until needed otherwise

My idea was that it makes it explicit what the function is doing. I thought it's not immediately obvious that it would stop at seams. Let me know what you think. I can remove it until needed otherwise
Member

Not sure, I feel like there must be a better way to indicate the purpose besides adding an unused argument-- the name or the docstring? But it's also not a big deal at all.

Not sure, I feel like there must be a better way to indicate the purpose besides adding an unused argument-- the name or the docstring? But it's also not a big deal at all.
@ -249,2 +241,2 @@
BKE_mesh_poly_edgebitmap_insert(edge_tag, &poly, &loops[poly.loopstart]);
BLI_BITMAP_ENABLE(poly_tag, i);
const MPoly &poly = polys[poly_index];
for (const int outer_loop_index : IndexRange(0, poly.totloop)) {
Member

I suggest using a separate const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop); variable. Then the next loop can use for (const int outer_loop_index : poly_loops.index_range()) { and you don't have to add poly.totloop every time a loop in the poly is accessed.

I suggest using a separate `const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);` variable. Then the next loop can use `for (const int outer_loop_index : poly_loops.index_range()) {` and you don't have to add `poly.totloop` every time a loop in the poly is accessed.
ChrisLend marked this conversation as resolved
@ -250,1 +241,3 @@
BLI_BITMAP_ENABLE(poly_tag, i);
const MPoly &poly = polys[poly_index];
for (const int outer_loop_index : IndexRange(0, poly.totloop)) {
const MLoop *outer_mloop = &loops[poly.loopstart + outer_loop_index];
Member

Use references instead of pointers, that should be your default in C++ code.

Use references instead of pointers, that should be your default in C++ code.
Author
Member

sure thing, missed that
out of curiosity: what is the reason for it?

sure thing, missed that out of curiosity: what is the reason for it?
Member

There are quite a few benefits-- they indicate non-null, they allow using . instead of ->, they can't be indexed (so they can't be mistaken for pointers to arrays). There's more info here:

https://stackoverflow.com/questions/57483/what-are-the-differences-between-a-pointer-variable-and-a-reference-variable

There are quite a few benefits-- they indicate non-null, they allow using `.` instead of `->`, they can't be indexed (so they can't be mistaken for pointers to arrays). There's more info here: https://stackoverflow.com/questions/57483/what-are-the-differences-between-a-pointer-variable-and-a-reference-variable
@ -304,3 +311,4 @@
return;
}
Vector<int> indices;
Member

Declare this closer to where it's first used, right above if (mval) {

Declare this closer to where it's first used, right above `if (mval) {`
ChrisLend marked this conversation as resolved
Brecht Van Lommel added this to the Animation & Rigging project 2023-02-13 09:11:32 +01:00
Christoph Lendenfeld added 3 commits 2023-02-16 11:12:05 +01:00
Christoph Lendenfeld added 1 commit 2023-02-16 11:27:40 +01:00
Christoph Lendenfeld added 1 commit 2023-02-16 11:33:11 +01:00
Christoph Lendenfeld added 2 commits 2023-02-16 11:36:00 +01:00
Christoph Lendenfeld added 1 commit 2023-02-16 11:37:09 +01:00
Christoph Lendenfeld requested review from Hans Goudey 2023-02-16 11:38:14 +01:00
Hans Goudey approved these changes 2023-02-16 13:58:22 +01:00

I've quickly browsed through the code, looks good! The newer C++ structures sure help a lot.

I've quickly browsed through the code, looks good! The newer C++ structures sure help a lot.
Christoph Lendenfeld merged commit 45731fd987 into main 2023-02-23 08:26:44 +01:00
Christoph Lendenfeld deleted branch weight_paint_refactor_face_select 2023-02-23 08:26:44 +01:00
Member

@ChrisLend next time don't forget to edit the commit message here for the style guide-- the line length in particular here. Thanks.

@ChrisLend next time don't forget to edit the commit message here for the [style guide](https://wiki.blender.org/wiki/Style_Guide/Commit_Messages)-- the line length in particular here. Thanks.
Sybren A. Stüvel removed this from the Animation & Rigging project 2023-03-02 17:01:15 +01:00
Sign in to join this conversation.
No reviewers
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
3 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#104577
No description provided.