Refactor: Weight Paint Select Linked Faces #104577
|
@ -212,106 +212,142 @@ void paintface_reveal(bContext *C, Object *ob, const bool select)
|
|||
paintface_flush_flags(C, ob, true, true);
|
||||
}
|
||||
|
||||
/* Set object-mode face selection seams based on edge data, uses hash table to find seam edges. */
|
||||
|
||||
static void select_linked_tfaces_with_seams(Mesh *me, const uint index, const bool select)
|
||||
/**
|
||||
* Join all edges of each poly in the AtomicDisjointSet. This can be used to find out which polys
|
||||
ChrisLend marked this conversation as resolved
|
||||
* are connected to each other.
|
||||
* \param islands Is expected to be of length mesh->totedge.
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
`@param islands` -> `\param islands:`
https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Comments
|
||||
* \param skip_seams Polys separated by a seam will be treated as not connected.
|
||||
*/
|
||||
static void build_poly_connections(blender::AtomicDisjointSet &islands,
|
||||
Mesh &mesh,
|
||||
const bool skip_seams = true)
|
||||
Hans Goudey
commented
Is there a reason to have the Is there a reason to have the `skip_seams` argument right now? If not, it can always be added easily later.
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
Hans Goudey
commented
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.
|
||||
{
|
||||
using namespace blender;
|
||||
bool do_it = true;
|
||||
bool mark = false;
|
||||
const Span<MPoly> polys = mesh.polys();
|
||||
const Span<MEdge> edges = mesh.edges();
|
||||
const Span<MLoop> loops = mesh.loops();
|
||||
|
||||
BLI_bitmap *edge_tag = BLI_BITMAP_NEW(me->totedge, __func__);
|
||||
BLI_bitmap *poly_tag = BLI_BITMAP_NEW(me->totpoly, __func__);
|
||||
|
||||
const Span<MEdge> edges = me->edges();
|
||||
const Span<MPoly> polys = me->polys();
|
||||
const Span<MLoop> loops = me->loops();
|
||||
bke::MutableAttributeAccessor attributes = me->attributes_for_write();
|
||||
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
||||
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
|
||||
".hide_poly", ATTR_DOMAIN_FACE, false);
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
Maybe I'm forgetting something, but why is it that we can ignore Maybe I'm forgetting something, but why is it that we can ignore `hide_poly` now?
|
||||
|
||||
/* Polys are connected if they share edges. By connecting all edges of a loop (as long as they
|
||||
* are not a seam) we can find connected faces. */
|
||||
threading::parallel_for(polys.index_range(), 1024, [&](const IndexRange range) {
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
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.
|
||||
|
||||
for (const int poly_index : range) {
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
`for (const int loop : IndexRange(poly.loopstart, poly.totloop)) {`
|
||||
if (hide_poly[poly_index]) {
|
||||
continue;
|
||||
}
|
||||
const MPoly &poly = polys[poly_index];
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
I suggest using a separate 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.
|
||||
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
|
||||
Hans Goudey
commented
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.
sure thing, missed that sure thing, missed that
out of curiosity: what is the reason for it?
Hans Goudey
commented
There are quite a few benefits-- they indicate non-null, they allow using 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
|
||||
|
||||
for (const int poly_loop_index : poly_loops.index_range()) {
|
||||
const MLoop &outer_mloop = poly_loops[poly_loop_index];
|
||||
if (skip_seams && (edges[outer_mloop.e].flag & ME_SEAM) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const MLoop &inner_mloop :
|
||||
poly_loops.slice(poly_loop_index, poly_loops.size() - poly_loop_index)) {
|
||||
if (&outer_mloop == &inner_mloop) {
|
||||
continue;
|
||||
}
|
||||
if (skip_seams && (edges[inner_mloop.e].flag & ME_SEAM) != 0) {
|
||||
continue;
|
||||
}
|
||||
islands.join(inner_mloop.e, outer_mloop.e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/* Select faces connected to the given face_indices. Seams are treated as separation. */
|
||||
static void paintface_select_linked_faces(Mesh &mesh,
|
||||
const blender::Span<int> face_indices,
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
`MPoly poly` -> `const MPoly &poly`
|
||||
const bool select)
|
||||
{
|
||||
Hans Goudey
commented
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:
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
I changed out all for loops except I found it a bit confusing to use 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
Hans Goudey
commented
Yeah, since the constructor uses Yeah, since the constructor uses `start, size` rather than `start, end`, that makes sense.
|
||||
using namespace blender;
|
||||
|
||||
AtomicDisjointSet islands(mesh.totedge);
|
||||
build_poly_connections(islands, mesh);
|
||||
|
||||
const Span<MPoly> polys = mesh.polys();
|
||||
const Span<MEdge> edges = mesh.edges();
|
||||
const Span<MLoop> loops = mesh.loops();
|
||||
|
||||
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);
|
||||
|
||||
if (index != uint(-1)) {
|
||||
/* only put face under cursor in array */
|
||||
const MPoly &poly = polys[index];
|
||||
BKE_mesh_poly_edgebitmap_insert(edge_tag, &poly, &loops[poly.loopstart]);
|
||||
BLI_BITMAP_ENABLE(poly_tag, index);
|
||||
}
|
||||
else {
|
||||
/* fill array by selection */
|
||||
for (int i = 0; i < me->totpoly; i++) {
|
||||
if (hide_poly[i]) {
|
||||
/* pass */
|
||||
}
|
||||
else if (select_poly.span[i]) {
|
||||
const MPoly &poly = polys[i];
|
||||
BKE_mesh_poly_edgebitmap_insert(edge_tag, &poly, &loops[poly.loopstart]);
|
||||
BLI_BITMAP_ENABLE(poly_tag, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (do_it) {
|
||||
do_it = false;
|
||||
|
||||
/* expand selection */
|
||||
for (int i = 0; i < me->totpoly; i++) {
|
||||
if (hide_poly[i]) {
|
||||
Set<int> selected_roots;
|
||||
for (const int i : face_indices) {
|
||||
const MPoly &poly = polys[i];
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
Add Add `using namespace blender` here so it's not necessary below.
|
||||
for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {
|
||||
if ((edges[loop.e].flag & ME_SEAM) != 0) {
|
||||
continue;
|
||||
}
|
||||
const int root = islands.find_root(loop.e);
|
||||
selected_roots.add(root);
|
||||
}
|
||||
}
|
||||
|
||||
if (!BLI_BITMAP_TEST(poly_tag, i)) {
|
||||
mark = false;
|
||||
|
||||
const MPoly &poly = polys[i];
|
||||
const MLoop *ml = &loops[poly.loopstart];
|
||||
for (int b = 0; b < poly.totloop; b++, ml++) {
|
||||
if ((edges[ml->e].flag & ME_SEAM) == 0) {
|
||||
if (BLI_BITMAP_TEST(edge_tag, ml->e)) {
|
||||
mark = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mark) {
|
||||
BLI_BITMAP_ENABLE(poly_tag, i);
|
||||
BKE_mesh_poly_edgebitmap_insert(edge_tag, &poly, &loops[poly.loopstart]);
|
||||
do_it = true;
|
||||
threading::parallel_for(select_poly.span.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int poly_index : range) {
|
||||
const MPoly &poly = polys[poly_index];
|
||||
for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {
|
||||
const int root = islands.find_root(loop.e);
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
Looks like this early return will skip Looks like this early return will skip `select_poly.finish();`. That will probably print a warning to the terminal.
|
||||
if (selected_roots.contains(root)) {
|
||||
select_poly.span[poly_index] = select;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MEM_freeN(edge_tag);
|
||||
|
||||
for (int i = 0; i < me->totpoly; i++) {
|
||||
if (BLI_BITMAP_TEST(poly_tag, i)) {
|
||||
select_poly.span[i] = select;
|
||||
}
|
||||
}
|
||||
|
||||
MEM_freeN(poly_tag);
|
||||
select_poly.finish();
|
||||
}
|
||||
|
||||
void paintface_select_linked(bContext *C, Object *ob, const int mval[2], const bool select)
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
Declare this closer to where it's first used, right above Declare this closer to where it's first used, right above `if (mval) {`
|
||||
{
|
||||
uint index = uint(-1);
|
||||
|
||||
using namespace blender;
|
||||
Mesh *me = BKE_mesh_from_object(ob);
|
||||
if (me == nullptr || me->totpoly == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = me->attributes_for_write();
|
||||
bke::SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".select_poly", ATTR_DOMAIN_FACE);
|
||||
|
||||
Vector<int> indices;
|
||||
if (mval) {
|
||||
uint index = uint(-1);
|
||||
if (!ED_mesh_pick_face(C, ob, mval, ED_MESH_PICK_DEFAULT_FACE_DIST, &index)) {
|
||||
select_poly.finish();
|
||||
return;
|
||||
}
|
||||
/* Since paintface_select_linked_faces might not select the face under the cursor, select it
|
||||
* here. */
|
||||
select_poly.span[index] = true;
|
||||
indices.append(index);
|
||||
}
|
||||
|
||||
else {
|
||||
for (const int i : select_poly.span.index_range()) {
|
||||
if (!select_poly.span[i]) {
|
||||
continue;
|
||||
}
|
||||
indices.append(i);
|
||||
}
|
||||
}
|
||||
|
||||
select_linked_tfaces_with_seams(me, index, select);
|
||||
select_poly.finish();
|
||||
|
||||
paintface_select_linked_faces(*me, indices, select);
|
||||
paintface_flush_flags(C, ob, true, false);
|
||||
}
|
||||
|
||||
|
|
Mesh *mesh
->Mesh &mesh
Using a reference indicates that the pointer won't be null