Fix #114981 : Add box, circle and lasso selection tools to weight paint + bone select mode #115807

Open
Daiki Hashimoto wants to merge 11 commits from daisea3e1203/blender:add-select-tools-to-bone-select-mode into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
4 changed files with 73 additions and 21 deletions

View File

@ -1660,10 +1660,9 @@ class _defs_weight_paint:
ob = context.active_object
if (ob and ob.type == 'MESH' and
(ob.data.use_paint_mask or
ob.data.use_paint_mask_vertex)):
ob.data.use_paint_mask_vertex or
context.pose_object)):
return VIEW3D_PT_tools_active._tools_select
elif context.pose_object:
return (_defs_view3d_select.select,)
return ()
@staticmethod

View File

@ -199,13 +199,18 @@ void BKE_paint_palette_set(Paint *p, Palette *palette);
void BKE_paint_curve_clamp_endpoint_add_index(PaintCurve *pc, int add_index);
/**
* Return true when in vertex/weight/texture paint + face-select mode?
* Return true when in vertex/weight/texture paint + face-select mode.
*/
bool BKE_paint_select_face_test(const Object *ob);
/**
* Return true when in vertex/weight paint + vertex-select mode?
* Return true when in vertex/weight paint + vertex-select mode.
*/
bool BKE_paint_select_vert_test(const Object *ob);
/**
* Return true when in weight paint + bone-select mode.
*/
daisea3e1203 marked this conversation as resolved Outdated

The blender comment style is without the double slash (//) at the start

Also I feel like the comment should not be a question. I know this is the same as the functions above,
but it's confusing to read.
I assume its just a statement. As in it returns true when in weight paint + bone-select mode.

The blender comment style is without the double slash (//) at the start Also I feel like the comment should not be a question. I know this is the same as the functions above, but it's confusing to read. I assume its just a statement. As in it returns true when in weight paint + bone-select mode.

I applied the same change in 09a3f4b9b1 to the two functions above (change the question mark to a period) since I encountered them many times when coding for this PR, and were used as a statement to check for their respective modes.

I applied the same change in 09a3f4b9b1 to the two functions above (change the question mark to a period) since I encountered them many times when coding for this PR, and were used as a statement to check for their respective modes.
bool BKE_paint_select_bone_test(const Object *ob);
/**
* used to check if selection is possible
* (when we don't care if its face or vert)

View File

@ -1023,16 +1023,36 @@ bool BKE_palette_from_hash(Main *bmain, GHash *color_table, const char *name, co
bool BKE_paint_select_face_test(const Object *ob)
{
return ((ob != nullptr) && (ob->type == OB_MESH) && (ob->data != nullptr) &&
(((Mesh *)ob->data)->editflag & ME_EDIT_PAINT_FACE_SEL) &&
(ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)));
if (ob == nullptr || ob->type != OB_MESH ||
(ob->mode & (OB_MODE_WEIGHT_PAINT | OB_MODE_VERTEX_PAINT | OB_MODE_TEXTURE_PAINT)) == 0)
{
return false;
}
Mesh *mesh = static_cast<Mesh *>(ob->data);
return mesh->editflag & ME_EDIT_PAINT_FACE_SEL;
}
bool BKE_paint_select_vert_test(const Object *ob)
{
return ((ob != nullptr) && (ob->type == OB_MESH) && (ob->data != nullptr) &&
(((Mesh *)ob->data)->editflag & ME_EDIT_PAINT_VERT_SEL) &&
(ob->mode & OB_MODE_WEIGHT_PAINT || ob->mode & OB_MODE_VERTEX_PAINT));
if (ob == nullptr || ob->type != OB_MESH ||
(ob->mode & (OB_MODE_WEIGHT_PAINT | OB_MODE_VERTEX_PAINT)) == 0)
{
return false;
}
Mesh *mesh = static_cast<Mesh *>(ob->data);
return mesh->editflag & ME_EDIT_PAINT_VERT_SEL;
}
bool BKE_paint_select_bone_test(const Object *ob)
{

This will be clearer when written as separate expressions. I know the current code is consistent with the existing code, but I find the existing code really hard to read too, and I don't think we should duplicate that.

if (ob == nullptr || ob->type != OB_MESH || (ob->mode & OB_MODE_WEIGHT_PAINT) == 0) {
  return false;
}

Mesh *mesh = static_cast<Mesh *>(ob->data);
return mesh->editflag & ~(ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL);

This separates the precondition checks from the actual query.

I also think the query is incorrect, as it checks whether any flag is set, ignoring ME_EDIT_PAINT_FACE_SEL and ME_EDIT_PAINT_VERT_SEL. IMO it should check that these flags are not set. I suspect this is the right check, but please do verify

return (mesh->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) == 0;

Note that I didn't include the check for ob->data != nullptr, as this is implied by ob->type == OB_MESH. If a Mesh object doesn't have any data, there is something very wrong and other areas in the code will have stopped working already. This function is not the place to try and solve this problem.

This will be clearer when written as separate expressions. I know the current code is consistent with the existing code, but I find the existing code really hard to read too, and I don't think we should duplicate that. ```cpp if (ob == nullptr || ob->type != OB_MESH || (ob->mode & OB_MODE_WEIGHT_PAINT) == 0) { return false; } Mesh *mesh = static_cast<Mesh *>(ob->data); return mesh->editflag & ~(ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL); ``` This separates the precondition checks from the actual query. I also think the query is incorrect, as it checks whether any flag is set, ignoring `ME_EDIT_PAINT_FACE_SEL` and `ME_EDIT_PAINT_VERT_SEL`. IMO it should check that these flags are *not* set. I suspect this is the right check, but please do verify ```cpp return (mesh->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) == 0; ``` Note that I didn't include the check for `ob->data != nullptr`, as this is implied by `ob->type == OB_MESH`. If a Mesh object doesn't have any data, there is something very wrong and other areas in the code will have stopped working already. This function is not the place to try and solve this problem.
Review

Thank you very much for the review and the insights about mesh data 🙏

The query is incorrect as you have pointed out, and have probably lead to the tools not functioning properly in some situations as @nathanvegdahl has demonstrated below. This has been fixed in e11fce8298 .

I have also refactored the two functions above in a similar fashion in 93a58c7601, but if you think this change should occur in a different PR or occasion, please let me know.

Thank you very much for the review and the insights about mesh data 🙏 The query is incorrect as you have pointed out, and have probably lead to the tools not functioning properly in some situations as @nathanvegdahl has demonstrated below. This has been fixed in e11fce8298 . I have also refactored the two functions above in a similar fashion in 93a58c7601, but if you think this change should occur in a different PR or occasion, please let me know.

This has been fixed in e11fce8298 .
👍

if you think this change should occur in a different PR or occasion, please let me know.
Yup, that's for a different PR.

Ideally, such cleanups would happen before this PR (first cleanup, then change functionality). In this case I don't think that would be worth the effort, though.

> This has been fixed in e11fce8298 . :+1: > if you think this change should occur in a different PR or occasion, please let me know. Yup, that's for a different PR. Ideally, such cleanups would happen *before* this PR (first cleanup, then change functionality). In this case I don't think that would be worth the effort, though.
Review

Thank you very much for the feedback!

Ideally, such cleanups would happen before this PR (first cleanup, then change functionality). In this case I don't think that would be worth the effort, though.

When I attempt to make a large change to the codebase in the future, I will make sure to create a separate clean up PR beforehand.

Thank you very much for the feedback! > Ideally, such cleanups would happen before this PR (first cleanup, then change functionality). In this case I don't think that would be worth the effort, though. When I attempt to make a large change to the codebase in the future, I will make sure to create a separate clean up PR beforehand.

Sorry for the back-and-forth, but looking back at this now, I do think that it's better to have this change as a small separate PR that we can land before this one. That way this little fix can be described by itself, and in the hypothetical case that it causes any issues, rolled back by itself, without having to deal with this entire PR.

Sorry for the back-and-forth, but looking back at this now, I do think that it's better to have this change as a small separate PR that we can land before this one. That way this little fix can be described by itself, and in the hypothetical case that it causes any issues, rolled back by itself, without having to deal with this entire PR.
if (ob == nullptr || ob->type != OB_MESH || (ob->mode & OB_MODE_WEIGHT_PAINT) == 0) {
return false;
}
Mesh *mesh = static_cast<Mesh *>(ob->data);
return (mesh->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) == 0;
}
bool BKE_paint_select_elem_test(const Object *ob)

View File

@ -468,9 +468,14 @@ static bool view3d_selectable_data(bContext *C)
}
}
else {
if ((ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)) &&
!BKE_paint_select_elem_test(ob))
{
if ((ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_TEXTURE_PAINT)) &&
!BKE_paint_select_elem_test(ob)) {
return false;
}
Object *ob_armature = BKE_object_pose_armature_get(ob);
if (ob->mode & OB_MODE_WEIGHT_PAINT && !BKE_paint_select_elem_test(ob) && !ob_armature) {
return false;
}
}
@ -1378,6 +1383,12 @@ static bool view3d_lasso_select(bContext *C,
else if (BKE_paint_select_vert_test(ob)) {
changed_multi |= do_lasso_select_paintvert(vc, wm_userdata, mcoords, mcoords_len, sel_op);
}
else if (ob && (ob->mode & OB_MODE_POSE || BKE_paint_select_bone_test(ob))) {
changed_multi |= do_lasso_select_pose(vc, mcoords, mcoords_len, sel_op);
if (changed_multi) {
ED_outliner_select_sync_from_pose_bone_tag(C);
}
}
else if (ob &&
(ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT))) {
/* pass */
@ -1385,12 +1396,6 @@ static bool view3d_lasso_select(bContext *C,
else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT)) {
changed_multi |= PE_lasso_select(C, mcoords, mcoords_len, sel_op) != OPERATOR_CANCELLED;
}
else if (ob && (ob->mode & OB_MODE_POSE)) {
changed_multi |= do_lasso_select_pose(vc, mcoords, mcoords_len, sel_op);
if (changed_multi) {
ED_outliner_select_sync_from_pose_bone_tag(C);
}
}
else {
changed_multi |= do_lasso_select_objects(vc, mcoords, mcoords_len, sel_op);
if (changed_multi) {
@ -2911,6 +2916,14 @@ static bool ed_object_select_pick(bContext *C,
if ((found && select_passthrough) && (basact->flag & BASE_SELECTED)) {
found = false;
}
else if (BKE_paint_select_bone_test(vc.obact)) {
/* pass */
/* During weight paint mode, when there are multiple armatures deforming a single mesh,
daisea3e1203 marked this conversation as resolved

this comment is great. I love it

this comment is great. I love it
* object_deselect_all_except might switch the active armature away from pose mode, making
* selection of bones with the select tools not possible afterwards.
* Passing here makes sure armatures stay in pose mode. */
}
else if (found || params->deselect_all) {
/* Deselect everything. */
/* `basact` may be nullptr. */
@ -4353,7 +4366,7 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op)
else if (vc.obact && vc.obact->mode & OB_MODE_PARTICLE_EDIT) {
changed_multi = PE_box_select(C, &rect, sel_op);
}
else if (vc.obact && vc.obact->mode & OB_MODE_POSE) {
else if (vc.obact && (vc.obact->mode & OB_MODE_POSE || BKE_paint_select_bone_test(vc.obact))) {
changed_multi = do_pose_box_select(C, &vc, &rect, sel_op);
if (changed_multi) {
ED_outliner_select_sync_from_pose_bone_tag(C);
@ -5307,6 +5320,21 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op)
}
FOREACH_OBJECT_IN_MODE_END;
}
/* Select bones from pose mode armature in weight paint + bone-select mode. */
daisea3e1203 marked this conversation as resolved Outdated

comments end with a dot

comments end with a dot

Addressed in 05bb4f72e0
Thank you for spotting these mistakes! Will be careful with comments in future PRs 🙏

Addressed in 05bb4f72e0 Thank you for spotting these mistakes! Will be careful with comments in future PRs 🙏
else if (BKE_paint_select_bone_test(obact)) {
view3d_operator_needs_opengl(C);
BKE_object_update_select_id(CTX_data_main(C));
ViewContext vc_tmp;
FOREACH_OBJECT_IN_MODE_BEGIN (
vc.scene, vc.view_layer, vc.v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) {
vc_tmp = vc;
vc_tmp.obact = ob_iter;
pose_circle_select(&vc_tmp, sel_op, mval, float(radius));
ED_outliner_select_sync_from_pose_bone_tag(C);
}
FOREACH_OBJECT_IN_MODE_END;
}
else if (obact && (obact->mode & OB_MODE_PARTICLE_EDIT)) {
if (PE_circle_select(C, wm_userdata, sel_op, mval, float(radius))) {
return OPERATOR_FINISHED;