Files
test2/source/blender/editors/armature/pose_select.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1396 lines
42 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
*/
#include <cstring>
#include "DNA_action_types.h"
#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_constraint_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_string.h"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "BKE_constraint.h"
#include "BKE_context.hh"
2024-01-23 15:18:09 -05:00
#include "BKE_layer.hh"
2023-11-14 09:30:40 +01:00
#include "BKE_modifier.hh"
#include "BKE_object.hh"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_armature.hh"
#include "ED_keyframing.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_object_vgroup.hh"
#include "ED_outliner.hh"
#include "ED_screen.hh"
#include "ED_select_utils.hh"
#include "ED_view3d.hh"
#include "ANIM_armature.hh"
#include "ANIM_bonecolor.hh"
#include "ANIM_keyingsets.hh"
Anim: replace Bone Groups & Armature Layers with Bone Collections Armature layers (the 32 little dots) and bone groups are replaced with Bone Collections: - Bone collections are stored on the armature, and have a name that is unique within that armature. - An armature can have an arbitrary number of bone collections (instead of the fixed 32 layers). - Bones can be assigned to zero or more bone collections. - Bone collections have a visibility setting, just like objects in scene collections. - When a bone is in at least one collection, and all its collections in are hidden, the bone is hidden. In other cases (in any visible collection, or in no collection at all), the bone visibility is determined by its own 'hidden' flag. - For now, bone collections cannot be nested; they are a flat list just like bone groups were. Nestability of bone collections is intended to be implemented in a later 4.x release. - Since bone collections are defined on the armature, they can be used from both pose mode and edit mode. Versioning converts bone groups and armature layers to new bone collections. Layers that do not contain any bones are skipped. The old data structures remain in DNA and are unaltered, for limited forward compatibility. That way at least a save with Blender 4.0 will not immediately erase the bone group and armature layers and their bone assignments. Shortcuts: - M/Shift+M in pose/edit mode: move to collection (M) and add to collection (shift+M). This works similar to the M/Shift+M menus for objects & scene collections. - Ctrl+G in pose mode shows a port of the old 'bone groups' menu. This is likely to be removed in the near future, as the functionality overlaps with the M/Shift+M menus. This is the first commit of a series; the bone collections feature will be improved before the Blender 4.0 release. See #108941 for more info. Pull request: https://projects.blender.org/blender/blender/pulls/109976
2023-08-22 12:15:54 +02:00
2024-01-24 11:32:26 -05:00
#include "armature_intern.hh"
using blender::Span;
using blender::Vector;
/* ***************** Pose Select Utilities ********************* */
/* NOTE: SEL_TOGGLE is assumed to have already been handled! */
2014-08-01 02:03:09 +10:00
static void pose_do_bone_select(bPoseChannel *pchan, const int select_mode)
{
/* select pchan only if selectable, but deselect works always */
switch (select_mode) {
case SEL_SELECT:
2019-04-22 09:19:45 +10:00
if (!(pchan->bone->flag & BONE_UNSELECTABLE)) {
pchan->bone->flag |= BONE_SELECTED;
2019-04-22 09:19:45 +10:00
}
break;
case SEL_DESELECT:
pchan->bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
break;
case SEL_INVERT:
if (pchan->bone->flag & BONE_SELECTED) {
pchan->bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
}
else if (!(pchan->bone->flag & BONE_UNSELECTABLE)) {
pchan->bone->flag |= BONE_SELECTED;
}
break;
}
}
void ED_pose_bone_select_tag_update(Object *ob)
{
BLI_assert(ob->type == OB_ARMATURE);
bArmature *arm = static_cast<bArmature *>(ob->data);
WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, ob);
WM_main_add_notifier(NC_GEOM | ND_DATA, ob);
if (arm->flag & ARM_HAS_VIZ_DEPS) {
/* mask modifier ('armature' mode), etc. */
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
}
void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select, bool change_active)
{
bArmature *arm;
/* sanity checks */
/* XXX: actually, we can probably still get away with no object - at most we have no updates */
if (ELEM(nullptr, ob, ob->pose, pchan, pchan->bone)) {
return;
2019-04-22 09:19:45 +10:00
}
arm = static_cast<bArmature *>(ob->data);
/* can only change selection state if bone can be modified */
if (PBONE_SELECTABLE(arm, pchan->bone)) {
/* change selection state - activate too if selected */
if (select) {
pchan->bone->flag |= BONE_SELECTED;
if (change_active) {
arm->act_bone = pchan->bone;
}
}
else {
pchan->bone->flag &= ~BONE_SELECTED;
if (change_active) {
arm->act_bone = nullptr;
}
}
/* TODO: select and activate corresponding vgroup? */
ED_pose_bone_select_tag_update(ob);
}
}
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
bool ED_armature_pose_select_pick_bone(const Scene *scene,
ViewLayer *view_layer,
View3D *v3d,
Object *ob,
Bone *bone,
const SelectPick_Params &params)
{
bool found = false;
bool changed = false;
if (ob->pose) {
if (bone && ((bone->flag & BONE_UNSELECTABLE) == 0)) {
found = true;
}
}
if (params.sel_op == SEL_OP_SET) {
if ((found && params.select_passthrough) && (bone->flag & BONE_SELECTED)) {
found = false;
}
else if (found || params.deselect_all) {
/* Deselect everything. */
/* Don't use #BKE_object_pose_base_array_get_unique
* because we may be selecting from object mode. */
FOREACH_VISIBLE_BASE_BEGIN (scene, view_layer, v3d, base_iter) {
Object *ob_iter = base_iter->object;
if ((ob_iter->type == OB_ARMATURE) && (ob_iter->mode & OB_MODE_POSE)) {
if (ED_pose_deselect_all(ob_iter, SEL_DESELECT, true)) {
ED_pose_bone_select_tag_update(ob_iter);
}
}
}
FOREACH_VISIBLE_BASE_END;
changed = true;
}
}
if (found) {
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob_act = BKE_view_layer_active_object_get(view_layer);
BLI_assert(BKE_view_layer_edit_object_get(view_layer) == nullptr);
/* If the bone cannot be affected, don't do anything. */
bArmature *arm = static_cast<bArmature *>(ob->data);
/* Since we do unified select, we don't shift+select a bone if the
* armature object was not active yet.
* NOTE(@ideasman42): special exception for armature mode so we can do multi-select
* we could check for multi-select explicitly but think its fine to
* always give predictable behavior in weight paint mode. */
if ((ob_act == nullptr) || ((ob_act != ob) && (ob_act->mode & OB_MODE_ALL_WEIGHT_PAINT) == 0))
{
/* When we are entering into posemode via toggle-select,
* from another active object - always select the bone. */
if (params.sel_op == SEL_OP_SET) {
/* Re-select the bone again later in this function. */
bone->flag &= ~BONE_SELECTED;
}
}
switch (params.sel_op) {
case SEL_OP_ADD: {
bone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
arm->act_bone = bone;
break;
}
case SEL_OP_SUB: {
bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
break;
}
case SEL_OP_XOR: {
if (bone->flag & BONE_SELECTED) {
/* If not active, we make it active. */
if (bone != arm->act_bone) {
arm->act_bone = bone;
}
else {
bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
}
}
else {
bone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
arm->act_bone = bone;
}
break;
}
case SEL_OP_SET: {
bone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
arm->act_bone = bone;
break;
}
case SEL_OP_AND: {
BLI_assert_unreachable(); /* Doesn't make sense for picking. */
break;
}
}
if (ob_act) {
/* In weight-paint we select the associated vertex group too. */
if (ob_act->mode & OB_MODE_ALL_WEIGHT_PAINT) {
if (bone == arm->act_bone) {
blender::ed::object::vgroup_select_by_name(ob_act, bone->name);
DEG_id_tag_update(&ob_act->id, ID_RECALC_GEOMETRY);
}
}
/* If there are some dependencies for visualizing armature state
* (e.g. Mask Modifier in 'Armature' mode), force update.
*/
else if (arm->flag & ARM_HAS_VIZ_DEPS) {
/* NOTE: ob not ob_act here is intentional - it's the source of the
* bones being selected [#37247].
*/
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
/* Tag armature for copy-on-evaluation update (since act_bone is in armature not object). */
DEG_id_tag_update(&arm->id, ID_RECALC_SYNC_TO_EVAL);
}
changed = true;
}
return changed || found;
}
bool ED_armature_pose_select_pick_with_buffer(const Scene *scene,
ViewLayer *view_layer,
View3D *v3d,
Base *base,
const GPUSelectResult *hit_results,
const int hits,
const SelectPick_Params &params,
bool do_nearest)
{
Object *ob = base->object;
Bone *nearBone;
2019-04-22 09:19:45 +10:00
if (!ob || !ob->pose) {
return false;
2019-04-22 09:19:45 +10:00
}
/* Callers happen to already get the active base */
Base *base_dummy = nullptr;
nearBone = ED_armature_pick_bone_from_selectbuffer(
{base}, hit_results, hits, true, do_nearest, &base_dummy);
return ED_armature_pose_select_pick_bone(scene, view_layer, v3d, ob, nearBone, params);
}
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
void ED_armature_pose_select_in_wpaint_mode(const Scene *scene,
ViewLayer *view_layer,
Base *base_select)
{
BLI_assert(base_select && (base_select->object->type == OB_ARMATURE));
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob_active = BKE_view_layer_active_object_get(view_layer);
BLI_assert(ob_active && (ob_active->mode & OB_MODE_ALL_WEIGHT_PAINT));
VirtualModifierData virtual_modifier_data;
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob_active, &virtual_modifier_data);
for (; md; md = md->next) {
if (md->type == eModifierType_Armature) {
ArmatureModifierData *amd = reinterpret_cast<ArmatureModifierData *>(md);
Object *ob_arm = amd->object;
if (ob_arm != nullptr) {
Base *base_arm = BKE_view_layer_base_find(view_layer, ob_arm);
if ((base_arm != nullptr) && (base_arm != base_select) && (base_arm->flag & BASE_SELECTED))
{
blender::ed::object::base_select(base_arm, blender::ed::object::BA_DESELECT);
}
}
}
}
if ((base_select->flag & BASE_SELECTED) == 0) {
blender::ed::object::base_select(base_select, blender::ed::object::BA_SELECT);
}
}
bool ED_pose_deselect_all(Object *ob, int select_mode, const bool ignore_visibility)
{
bArmature *arm = static_cast<bArmature *>(ob->data);
/* we call this from outliner too */
if (ob->pose == nullptr) {
return false;
}
/* Determine if we're selecting or deselecting */
if (select_mode == SEL_TOGGLE) {
select_mode = SEL_SELECT;
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
if (ignore_visibility || blender::animrig::bone_is_visible_pchan(arm, pchan)) {
if (pchan->bone->flag & BONE_SELECTED) {
select_mode = SEL_DESELECT;
break;
}
}
}
}
/* Set the flags accordingly */
bool changed = false;
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
/* ignore the pchan if it isn't visible or if its selection cannot be changed */
if (ignore_visibility || blender::animrig::bone_is_visible_pchan(arm, pchan)) {
int flag_prev = pchan->bone->flag;
pose_do_bone_select(pchan, select_mode);
changed = (changed || flag_prev != pchan->bone->flag);
}
}
return changed;
}
static bool ed_pose_is_any_selected(Object *ob, bool ignore_visibility)
{
bArmature *arm = static_cast<bArmature *>(ob->data);
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
if (ignore_visibility || blender::animrig::bone_is_visible_pchan(arm, pchan)) {
if (pchan->bone->flag & BONE_SELECTED) {
return true;
}
}
}
return false;
}
static bool ed_pose_is_any_selected_multi(const Span<Base *> bases, bool ignore_visibility)
{
for (Base *base : bases) {
Object *ob_iter = base->object;
if (ed_pose_is_any_selected(ob_iter, ignore_visibility)) {
return true;
}
}
return false;
}
bool ED_pose_deselect_all_multi_ex(const Span<Base *> bases,
int select_mode,
const bool ignore_visibility)
{
if (select_mode == SEL_TOGGLE) {
select_mode = ed_pose_is_any_selected_multi(bases, ignore_visibility) ? SEL_DESELECT :
SEL_SELECT;
}
bool changed_multi = false;
for (Base *base : bases) {
Object *ob_iter = base->object;
if (ED_pose_deselect_all(ob_iter, select_mode, ignore_visibility)) {
ED_pose_bone_select_tag_update(ob_iter);
changed_multi = true;
}
}
return changed_multi;
}
bool ED_pose_deselect_all_multi(bContext *C, int select_mode, const bool ignore_visibility)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
Vector<Base *> bases = BKE_object_pose_base_array_get_unique(vc.scene, vc.view_layer, vc.v3d);
return ED_pose_deselect_all_multi_ex(bases, select_mode, ignore_visibility);
}
/* ***************** Selections ********************** */
static void selectconnected_posebonechildren(Object *ob, Bone *bone, int extend)
{
2014-07-05 01:05:50 +12:00
/* stop when unconnected child is encountered, or when unselectable bone is encountered */
2019-04-22 09:19:45 +10:00
if (!(bone->flag & BONE_CONNECTED) || (bone->flag & BONE_UNSELECTABLE)) {
return;
2019-04-22 09:19:45 +10:00
}
2019-04-22 09:19:45 +10:00
if (extend) {
bone->flag &= ~BONE_SELECTED;
2019-04-22 09:19:45 +10:00
}
else {
bone->flag |= BONE_SELECTED;
2019-04-22 09:19:45 +10:00
}
LISTBASE_FOREACH (Bone *, curBone, &bone->childbase) {
selectconnected_posebonechildren(ob, curBone, extend);
2019-04-22 09:19:45 +10:00
}
}
/* within active object context */
/* previously known as "selectconnected_posearmature" */
static wmOperatorStatus pose_select_connected_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
Bone *bone, *curBone, *next = nullptr;
const bool extend = RNA_boolean_get(op->ptr, "extend");
view3d_operator_needs_gpu(C);
Base *base = nullptr;
bone = ED_armature_pick_bone(C, event->mval, !extend, &base);
2019-04-22 09:19:45 +10:00
if (!bone) {
return OPERATOR_CANCELLED;
2019-04-22 09:19:45 +10:00
}
/* Select parents */
for (curBone = bone; curBone; curBone = next) {
/* ignore bone if cannot be selected */
if ((curBone->flag & BONE_UNSELECTABLE) == 0) {
2019-04-22 09:19:45 +10:00
if (extend) {
curBone->flag &= ~BONE_SELECTED;
2019-04-22 09:19:45 +10:00
}
else {
curBone->flag |= BONE_SELECTED;
2019-04-22 09:19:45 +10:00
}
2019-04-22 09:19:45 +10:00
if (curBone->flag & BONE_CONNECTED) {
next = curBone->parent;
2019-04-22 09:19:45 +10:00
}
else {
next = nullptr;
2019-04-22 09:19:45 +10:00
}
}
2019-04-22 09:19:45 +10:00
else {
next = nullptr;
2019-04-22 09:19:45 +10:00
}
}
/* Select children */
LISTBASE_FOREACH (Bone *, curBone, &bone->childbase) {
selectconnected_posebonechildren(base->object, curBone, extend);
2019-04-22 09:19:45 +10:00
}
ED_outliner_select_sync_from_pose_bone_tag(C);
ED_pose_bone_select_tag_update(base->object);
return OPERATOR_FINISHED;
}
static bool pose_select_linked_pick_poll(bContext *C)
{
return (ED_operator_view3d_active(C) && ED_operator_posemode(C));
}
void POSE_OT_select_linked_pick(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Select Connected";
ot->idname = "POSE_OT_select_linked_pick";
ot->description = "Select bones linked by parent/child connections under the mouse cursor";
2018-04-23 14:49:44 +02:00
/* callbacks */
/* leave 'exec' unset */
ot->invoke = pose_select_connected_invoke;
ot->poll = pose_select_linked_pick_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
prop = RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection instead of deselecting everything first");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
static wmOperatorStatus pose_select_linked_exec(bContext *C, wmOperator * /*op*/)
{
Bone *curBone, *next = nullptr;
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
if ((pchan->bone->flag & BONE_SELECTED) == 0) {
continue;
}
bArmature *arm = static_cast<bArmature *>(ob->data);
/* Select parents */
for (curBone = pchan->bone; curBone; curBone = next) {
if (PBONE_SELECTABLE(arm, curBone)) {
curBone->flag |= BONE_SELECTED;
if (curBone->flag & BONE_CONNECTED) {
next = curBone->parent;
}
else {
next = nullptr;
}
}
else {
next = nullptr;
}
}
/* Select children */
LISTBASE_FOREACH (Bone *, curBone, &pchan->bone->childbase) {
selectconnected_posebonechildren(ob, curBone, false);
}
ED_pose_bone_select_tag_update(ob);
}
CTX_DATA_END;
ED_outliner_select_sync_from_pose_bone_tag(C);
return OPERATOR_FINISHED;
}
void POSE_OT_select_linked(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Connected";
ot->idname = "POSE_OT_select_linked";
ot->description = "Select all bones linked by parent/child connections to the current selection";
/* callbacks */
ot->exec = pose_select_linked_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* -------------------------------------- */
static wmOperatorStatus pose_de_select_all_exec(bContext *C, wmOperator *op)
{
int action = RNA_enum_get(op->ptr, "action");
Scene *scene = CTX_data_scene(C);
int multipaint = scene->toolsettings->multipaint;
if (action == SEL_TOGGLE) {
action = CTX_DATA_COUNT(C, selected_pose_bones) ? SEL_DESELECT : SEL_SELECT;
}
Object *ob_prev = nullptr;
2020-03-29 16:33:51 +11:00
/* Set the flags. */
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
bArmature *arm = static_cast<bArmature *>(ob->data);
pose_do_bone_select(pchan, action);
if (ob_prev != ob) {
/* Weight-paint or mask modifiers need depsgraph updates. */
if (multipaint || (arm->flag & ARM_HAS_VIZ_DEPS)) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
/* need to tag armature for cow updates, or else selection doesn't update */
DEG_id_tag_update(&arm->id, ID_RECALC_SYNC_TO_EVAL);
ob_prev = ob;
}
}
CTX_DATA_END;
ED_outliner_select_sync_from_pose_bone_tag(C);
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, nullptr);
return OPERATOR_FINISHED;
}
void POSE_OT_select_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "(De)select All";
ot->idname = "POSE_OT_select_all";
ot->description = "Toggle selection status of all bones";
/* API callbacks. */
ot->exec = pose_de_select_all_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
WM_operator_properties_select_all(ot);
}
/* -------------------------------------- */
static wmOperatorStatus pose_select_parent_exec(bContext *C, wmOperator * /*op*/)
{
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
bArmature *arm = static_cast<bArmature *>(ob->data);
bPoseChannel *pchan, *parent;
/* Determine if there is an active bone */
pchan = CTX_data_active_pose_bone(C);
if (pchan) {
parent = pchan->parent;
if ((parent) && !(parent->bone->flag & (BONE_HIDDEN_P | BONE_UNSELECTABLE))) {
parent->bone->flag |= BONE_SELECTED;
arm->act_bone = parent->bone;
}
else {
return OPERATOR_CANCELLED;
}
}
else {
return OPERATOR_CANCELLED;
}
ED_outliner_select_sync_from_pose_bone_tag(C);
ED_pose_bone_select_tag_update(ob);
return OPERATOR_FINISHED;
}
void POSE_OT_select_parent(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Parent Bone";
ot->idname = "POSE_OT_select_parent";
ot->description = "Select bones that are parents of the currently selected bones";
/* API callbacks. */
ot->exec = pose_select_parent_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* -------------------------------------- */
static wmOperatorStatus pose_select_constraint_target_exec(bContext *C, wmOperator * /*op*/)
{
bool found = false;
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones) {
if (pchan->bone->flag & BONE_SELECTED) {
LISTBASE_FOREACH (bConstraint *, con, &pchan->constraints) {
ListBase targets = {nullptr, nullptr};
if (BKE_constraint_targets_get(con, &targets)) {
LISTBASE_FOREACH (bConstraintTarget *, ct, &targets) {
Object *ob = ct->tar;
/* Any armature that is also in pose mode should be selected. */
if ((ct->subtarget[0] != '\0') && (ob != nullptr) && (ob->type == OB_ARMATURE) &&
(ob->mode == OB_MODE_POSE))
{
bPoseChannel *pchanc = BKE_pose_channel_find_name(ob->pose, ct->subtarget);
if ((pchanc) && !(pchanc->bone->flag & BONE_UNSELECTABLE)) {
pchanc->bone->flag |= BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL;
ED_pose_bone_select_tag_update(ob);
found = true;
}
}
}
BKE_constraint_targets_flush(con, &targets, true);
}
}
}
}
CTX_DATA_END;
2019-04-22 09:19:45 +10:00
if (!found) {
return OPERATOR_CANCELLED;
2019-04-22 09:19:45 +10:00
}
ED_outliner_select_sync_from_pose_bone_tag(C);
return OPERATOR_FINISHED;
}
void POSE_OT_select_constraint_target(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Constraint Target";
ot->idname = "POSE_OT_select_constraint_target";
ot->description = "Select bones used as targets for the currently selected bones";
/* API callbacks. */
ot->exec = pose_select_constraint_target_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* -------------------------------------- */
/* No need to convert to multi-objects. Just like we keep the non-active bones
* selected we then keep the non-active objects untouched (selected/unselected). */
static wmOperatorStatus pose_select_hierarchy_exec(bContext *C, wmOperator *op)
{
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
bArmature *arm = static_cast<bArmature *>(ob->data);
bPoseChannel *pchan_act;
int direction = RNA_enum_get(op->ptr, "direction");
2014-02-03 18:55:59 +11:00
const bool add_to_sel = RNA_boolean_get(op->ptr, "extend");
bool changed = false;
pchan_act = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (pchan_act == nullptr) {
return OPERATOR_CANCELLED;
}
if (direction == BONE_SELECT_PARENT) {
if (pchan_act->parent) {
Bone *bone_parent;
bone_parent = pchan_act->parent->bone;
if (PBONE_SELECTABLE(arm, bone_parent)) {
if (!add_to_sel) {
pchan_act->bone->flag &= ~BONE_SELECTED;
}
bone_parent->flag |= BONE_SELECTED;
arm->act_bone = bone_parent;
changed = true;
}
}
}
else { /* direction == BONE_SELECT_CHILD */
Bone *bone_child = nullptr;
int pass;
/* first pass, only connected bones (the logical direct child) */
for (pass = 0; pass < 2 && (bone_child == nullptr); pass++) {
LISTBASE_FOREACH (bPoseChannel *, pchan_iter, &ob->pose->chanbase) {
/* possible we have multiple children, some invisible */
if (PBONE_SELECTABLE(arm, pchan_iter->bone)) {
if (pchan_iter->parent == pchan_act) {
if ((pass == 1) || (pchan_iter->bone->flag & BONE_CONNECTED)) {
bone_child = pchan_iter->bone;
break;
}
}
}
}
}
if (bone_child) {
arm->act_bone = bone_child;
if (!add_to_sel) {
pchan_act->bone->flag &= ~BONE_SELECTED;
}
bone_child->flag |= BONE_SELECTED;
changed = true;
}
}
if (changed == false) {
return OPERATOR_CANCELLED;
}
ED_outliner_select_sync_from_pose_bone_tag(C);
ED_pose_bone_select_tag_update(ob);
return OPERATOR_FINISHED;
}
void POSE_OT_select_hierarchy(wmOperatorType *ot)
{
static const EnumPropertyItem direction_items[] = {
{BONE_SELECT_PARENT, "PARENT", 0, "Select Parent", ""},
{BONE_SELECT_CHILD, "CHILD", 0, "Select Child", ""},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Select Hierarchy";
ot->idname = "POSE_OT_select_hierarchy";
ot->description = "Select immediate parent/children of selected bones";
/* API callbacks. */
ot->exec = pose_select_hierarchy_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
ot->prop = RNA_def_enum(
ot->srna, "direction", direction_items, BONE_SELECT_PARENT, "Direction", "");
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
}
/* -------------------------------------- */
/* Modes for the `select_grouped` operator. */
enum class SelectRelatedMode {
SAME_COLLECTION = 0,
SAME_COLOR,
SAME_KEYINGSET,
CHILDREN,
IMMEDIATE_CHILDREN,
PARENT,
SIBLINGS,
};
static bool pose_select_same_color(bContext *C, const bool extend)
{
/* Get a set of all the colors of the selected bones. */
blender::Set<blender::animrig::BoneColor> used_colors;
blender::Set<Object *> updated_objects;
bool changed_any_selection = false;
/* Old approach that we may want to reinstate behind some option at some point. This will match
* against the colors of all selected bones, instead of just the active one. It also explains why
* there is a set of colors to begin with.
*
* CTX_DATA_BEGIN (C, bPoseChannel *, pchan, selected_pose_bones) {
* auto color = blender::animrig::ANIM_bonecolor_posebone_get(pchan);
* used_colors.add(color);
* }
* CTX_DATA_END;
*/
if (!extend) {
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, selected_pose_bones, Object *, ob) {
pchan->bone->flag &= ~BONE_SELECTED;
updated_objects.add(ob);
changed_any_selection = true;
}
CTX_DATA_END;
}
/* Use the color of the active pose bone. */
bPoseChannel *active_pose_bone = CTX_data_active_pose_bone(C);
auto color = blender::animrig::ANIM_bonecolor_posebone_get(active_pose_bone);
used_colors.add(color);
/* Select all visible bones that have the same color. */
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
Bone *bone = pchan->bone;
if (bone->flag & (BONE_UNSELECTABLE | BONE_SELECTED)) {
/* Skip bones that are unselectable or already selected. */
continue;
}
auto color = blender::animrig::ANIM_bonecolor_posebone_get(pchan);
if (!used_colors.contains(color)) {
continue;
}
bone->flag |= BONE_SELECTED;
changed_any_selection = true;
updated_objects.add(ob);
}
CTX_DATA_END;
if (!changed_any_selection) {
return false;
}
for (Object *ob : updated_objects) {
ED_pose_bone_select_tag_update(ob);
}
return true;
}
static bool pose_select_same_collection(bContext *C, const bool extend)
{
bool changed_any_selection = false;
blender::Set<Object *> updated_objects;
/* Refuse to do anything if there is no active pose bone. */
bPoseChannel *active_pchan = CTX_data_active_pose_bone(C);
if (!active_pchan) {
return false;
}
if (!extend) {
/* Deselect all the bones. */
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, selected_pose_bones, Object *, ob) {
pchan->bone->flag &= ~BONE_SELECTED;
updated_objects.add(ob);
changed_any_selection = true;
2019-04-22 09:19:45 +10:00
}
CTX_DATA_END;
}
/* Build a set of bone collection names, to allow cross-Armature selection. */
blender::Set<std::string> collection_names;
LISTBASE_FOREACH (BoneCollectionReference *, bcoll_ref, &active_pchan->bone->runtime.collections)
{
collection_names.add(bcoll_ref->bcoll->name);
}
/* Select all bones that match any of the collection names. */
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
Bone *bone = pchan->bone;
if (bone->flag & (BONE_UNSELECTABLE | BONE_SELECTED)) {
continue;
}
LISTBASE_FOREACH (BoneCollectionReference *, bcoll_ref, &bone->runtime.collections) {
if (!collection_names.contains(bcoll_ref->bcoll->name)) {
continue;
}
bone->flag |= BONE_SELECTED;
changed_any_selection = true;
updated_objects.add(ob);
}
}
CTX_DATA_END;
for (Object *ob : updated_objects) {
ED_pose_bone_select_tag_update(ob);
}
return changed_any_selection;
}
/* Useful to get the selection before modifying it. */
static blender::Set<bPoseChannel *> get_selected_pose_bones(Object *pose_object)
{
blender::Set<bPoseChannel *> selected_pose_bones;
bArmature *arm = static_cast<bArmature *>((pose_object) ? pose_object->data : nullptr);
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose_object->pose->chanbase) {
if (PBONE_SELECTED(arm, pchan->bone)) {
selected_pose_bones.add(pchan);
}
}
return selected_pose_bones;
}
static bool pose_bone_is_below_one_of(bPoseChannel &bone,
const blender::Set<bPoseChannel *> &potential_parents)
{
bPoseChannel *bone_iter = &bone;
while (bone_iter) {
if (potential_parents.contains(bone_iter)) {
return true;
}
bone_iter = bone_iter->parent;
}
return false;
}
static void deselect_pose_bones(const blender::Set<bPoseChannel *> &pose_bones)
{
for (bPoseChannel *pose_bone : pose_bones) {
if (!pose_bone) {
/* There may be a nullptr in the set if selecting siblings of root bones. */
continue;
}
pose_bone->bone->flag &= ~BONE_SELECTED;
}
}
/* Selects children of currently selected bones in all objects in pose mode. If `all` is true, a
* bone will be selected if any bone in it's parent hierarchy is selected. If false, only bones
* whose direct parent is selected are changed. */
static bool pose_select_children(bContext *C, const bool all, const bool extend)
{
Vector<Object *> objects = BKE_object_pose_array_get_unique(
CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C));
bool changed_any_selection = false;
for (Object *pose_object : objects) {
bArmature *arm = static_cast<bArmature *>(pose_object->data);
BLI_assert(arm);
blender::Set<bPoseChannel *> selected_pose_bones = get_selected_pose_bones(pose_object);
if (!extend) {
deselect_pose_bones(selected_pose_bones);
}
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose_object->pose->chanbase) {
if (!PBONE_SELECTABLE(arm, pchan->bone)) {
continue;
}
if (all) {
if (pose_bone_is_below_one_of(*pchan, selected_pose_bones)) {
pose_do_bone_select(pchan, SEL_SELECT);
changed_any_selection = true;
}
}
else {
if (selected_pose_bones.contains(pchan->parent)) {
pose_do_bone_select(pchan, SEL_SELECT);
changed_any_selection = true;
}
}
}
ED_pose_bone_select_tag_update(pose_object);
}
return changed_any_selection;
}
static bool pose_select_parents(bContext *C, const bool extend)
{
Vector<Object *> objects = BKE_object_pose_array_get_unique(
CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C));
bool changed_any_selection = false;
for (Object *pose_object : objects) {
bArmature *arm = static_cast<bArmature *>(pose_object->data);
BLI_assert(arm);
blender::Set<bPoseChannel *> selected_pose_bones = get_selected_pose_bones(pose_object);
if (!extend) {
deselect_pose_bones(selected_pose_bones);
}
for (bPoseChannel *pchan : selected_pose_bones) {
if (!pchan->parent) {
continue;
}
if (!PBONE_SELECTABLE(arm, pchan->parent->bone)) {
continue;
}
pose_do_bone_select(pchan->parent, SEL_SELECT);
changed_any_selection = true;
}
ED_pose_bone_select_tag_update(pose_object);
}
return changed_any_selection;
}
static bool pose_select_siblings(bContext *C, const bool extend)
{
Vector<Object *> objects = BKE_object_pose_array_get_unique(
CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C));
bool changed_any_selection = false;
for (Object *pose_object : objects) {
bArmature *arm = static_cast<bArmature *>(pose_object->data);
BLI_assert(arm);
blender::Set<bPoseChannel *> parents_of_selected;
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose_object->pose->chanbase) {
if (PBONE_SELECTED(arm, pchan->bone)) {
parents_of_selected.add(pchan->parent);
}
}
if (!extend) {
deselect_pose_bones(parents_of_selected);
}
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose_object->pose->chanbase) {
if (!PBONE_SELECTABLE(arm, pchan->bone)) {
continue;
}
/* Checking if the bone is already selected so `changed_any_selection` stays true to its
* word. */
if (parents_of_selected.contains(pchan->parent) && !PBONE_SELECTED(arm, pchan->bone)) {
pose_do_bone_select(pchan, SEL_SELECT);
changed_any_selection = true;
}
}
ED_pose_bone_select_tag_update(pose_object);
}
return changed_any_selection;
}
static bool pose_select_same_keyingset(bContext *C, ReportList *reports, bool extend)
{
using namespace blender::animrig;
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
bool changed_multi = false;
KeyingSet *ks = scene_get_active_keyingset(CTX_data_scene(C));
/* sanity checks: validate Keying Set and object */
if (ks == nullptr) {
BKE_report(reports, RPT_ERROR, "No active Keying Set to use");
return false;
}
if (validate_keyingset(C, nullptr, ks) != ModifyKeyReturn::SUCCESS) {
if (ks->paths.first == nullptr) {
if ((ks->flag & KEYINGSET_ABSOLUTE) == 0) {
BKE_report(reports,
RPT_ERROR,
"Use another Keying Set, as the active one depends on the currently "
"selected items or cannot find any targets due to unsuitable context");
}
else {
BKE_report(reports, RPT_ERROR, "Keying Set does not contain any paths");
}
}
return false;
}
/* if not extending selection, deselect all selected first */
if (extend == false) {
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones) {
2019-04-22 09:19:45 +10:00
if ((pchan->bone->flag & BONE_UNSELECTABLE) == 0) {
pchan->bone->flag &= ~BONE_SELECTED;
2019-04-22 09:19:45 +10:00
}
}
CTX_DATA_END;
}
Vector<Object *> objects = BKE_object_pose_array_get_unique(scene, view_layer, CTX_wm_view3d(C));
for (const int ob_index : objects.index_range()) {
Object *ob = BKE_object_pose_armature_get(objects[ob_index]);
bArmature *arm = static_cast<bArmature *>((ob) ? ob->data : nullptr);
bPose *pose = (ob) ? ob->pose : nullptr;
bool changed = false;
/* Sanity checks. */
if (ELEM(nullptr, ob, pose, arm)) {
continue;
}
/* iterate over elements in the Keying Set, setting selection depending on whether
* that bone is visible or not...
*/
LISTBASE_FOREACH (KS_Path *, ksp, &ks->paths) {
/* only items related to this object will be relevant */
if ((ksp->id == &ob->id) && (ksp->rna_path != nullptr)) {
bPoseChannel *pchan = nullptr;
char boneName[sizeof(pchan->name)];
if (!BLI_str_quoted_substr(ksp->rna_path, "bones[", boneName, sizeof(boneName))) {
continue;
}
pchan = BKE_pose_channel_find_name(pose, boneName);
if (pchan) {
/* select if bone is visible and can be affected */
if (PBONE_SELECTABLE(arm, pchan->bone)) {
pchan->bone->flag |= BONE_SELECTED;
changed = true;
}
}
}
}
if (changed || !extend) {
ED_pose_bone_select_tag_update(ob);
changed_multi = true;
}
}
return changed_multi;
}
static wmOperatorStatus pose_select_grouped_exec(bContext *C, wmOperator *op)
{
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
const SelectRelatedMode mode = SelectRelatedMode(RNA_enum_get(op->ptr, "type"));
const bool extend = RNA_boolean_get(op->ptr, "extend");
bool changed = false;
/* sanity check */
if (ob->pose == nullptr) {
return OPERATOR_CANCELLED;
2019-04-22 09:19:45 +10:00
}
/* selection types */
switch (mode) {
case SelectRelatedMode::SAME_COLLECTION:
changed = pose_select_same_collection(C, extend);
break;
case SelectRelatedMode::SAME_COLOR:
changed = pose_select_same_color(C, extend);
break;
case SelectRelatedMode::SAME_KEYINGSET:
changed = pose_select_same_keyingset(C, op->reports, extend);
break;
case SelectRelatedMode::CHILDREN:
changed = pose_select_children(C, true, extend);
break;
case SelectRelatedMode::IMMEDIATE_CHILDREN:
changed = pose_select_children(C, false, extend);
break;
case SelectRelatedMode::PARENT:
changed = pose_select_parents(C, extend);
break;
case SelectRelatedMode::SIBLINGS:
changed = pose_select_siblings(C, extend);
break;
default:
printf("pose_select_grouped() - Unknown selection type %d\n", int(mode));
break;
}
/* report done status */
2019-04-22 09:19:45 +10:00
if (changed) {
ED_outliner_select_sync_from_pose_bone_tag(C);
return OPERATOR_FINISHED;
2019-04-22 09:19:45 +10:00
}
return OPERATOR_CANCELLED;
}
void POSE_OT_select_grouped(wmOperatorType *ot)
{
static const EnumPropertyItem prop_select_grouped_types[] = {
{int(SelectRelatedMode::SAME_COLLECTION),
"COLLECTION",
0,
"Collection",
"Same collections as the active bone"},
{int(SelectRelatedMode::SAME_COLOR), "COLOR", 0, "Color", "Same color as the active bone"},
{int(SelectRelatedMode::SAME_KEYINGSET),
"KEYINGSET",
0,
"Keying Set",
"All bones affected by active Keying Set"},
{int(SelectRelatedMode::CHILDREN),
"CHILDREN",
0,
"Children",
"Select all children of currently selected bones"},
{int(SelectRelatedMode::IMMEDIATE_CHILDREN),
"CHILDREN_IMMEDIATE",
0,
"Immediate Children",
"Select direct children of currently selected bones"},
{int(SelectRelatedMode::PARENT),
"PARENT",
0,
"Parents",
"Select the parents of currently selected bones"},
{int(SelectRelatedMode::SIBLINGS),
"SIBILINGS",
0,
"Siblings",
"Select all bones that have the same parent as currently selected bones"},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Select Grouped";
ot->description = "Select all visible bones grouped by similar properties";
ot->idname = "POSE_OT_select_grouped";
/* API callbacks. */
ot->invoke = WM_menu_invoke;
ot->exec = pose_select_grouped_exec;
ot->poll = ED_operator_posemode; /* TODO: expand to support edit mode as well. */
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection instead of deselecting everything first");
ot->prop = RNA_def_enum(ot->srna, "type", prop_select_grouped_types, 0, "Type", "");
}
/* -------------------------------------- */
/* Add the given selection flags to the bone flags. */
static void bone_selection_flags_add(bPoseChannel *pchan, const eBone_Flag new_selection_flags)
{
pchan->bone->flag |= (new_selection_flags & BONE_SELECTED);
}
/* Set the bone flags to the given selection flags. */
static void bone_selection_flags_set(bPoseChannel *pchan, const eBone_Flag new_selection_flags)
{
pchan->bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
pchan->bone->flag |= (new_selection_flags & BONE_SELECTED);
}
/**
* \note clone of #armature_select_mirror_exec keep in sync
*/
static wmOperatorStatus pose_select_mirror_exec(bContext *C, wmOperator *op)
{
const Scene *scene = CTX_data_scene(C);
2018-04-25 17:52:02 +02:00
ViewLayer *view_layer = CTX_data_view_layer(C);
Object *ob_active = CTX_data_active_object(C);
const bool is_weight_paint = (ob_active->mode & OB_MODE_WEIGHT_PAINT) != 0;
const bool active_only = RNA_boolean_get(op->ptr, "only_active");
const bool extend = RNA_boolean_get(op->ptr, "extend");
const auto set_bone_selection_flags = extend ? bone_selection_flags_add :
bone_selection_flags_set;
Vector<Object *> objects = BKE_object_pose_array_get_unique(scene, view_layer, CTX_wm_view3d(C));
for (Object *ob : objects) {
bArmature *arm = static_cast<bArmature *>(ob->data);
bPoseChannel *pchan_mirror_act = nullptr;
/* Remember the pre-mirroring selection flags of the bones. */
blender::Map<bPoseChannel *, eBone_Flag> old_selection_flags;
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
/* Treat invisible bones as deselected. */
const int flags = blender::animrig::bone_is_visible_pchan(arm, pchan) ? pchan->bone->flag :
0;
old_selection_flags.add_new(pchan, eBone_Flag(flags));
2018-04-25 17:52:02 +02:00
}
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
if (!PBONE_SELECTABLE(arm, pchan->bone)) {
continue;
}
bPoseChannel *pchan_mirror = BKE_pose_channel_get_mirrored(ob->pose, pchan->name);
if (!pchan_mirror) {
/* If a bone cannot be mirrored, keep its flags as-is. This makes it possible to select
* the spine and an arm, and still flip the selection to the other arm (without losing
* the selection on the spine). */
continue;
}
if (pchan->bone == arm->act_bone) {
pchan_mirror_act = pchan_mirror;
}
/* If active-only, don't touch unrelated bones. */
if (active_only && !ELEM(arm->act_bone, pchan->bone, pchan_mirror->bone)) {
continue;
}
const eBone_Flag flags_mirror = old_selection_flags.lookup(pchan_mirror);
set_bone_selection_flags(pchan, flags_mirror);
}
2018-04-25 17:52:02 +02:00
if (pchan_mirror_act) {
arm->act_bone = pchan_mirror_act->bone;
/* In weight-paint we select the associated vertex group too. */
if (is_weight_paint) {
blender::ed::object::vgroup_select_by_name(ob_active, pchan_mirror_act->name);
DEG_id_tag_update(&ob_active->id, ID_RECALC_GEOMETRY);
2018-04-25 17:52:02 +02:00
}
}
2018-04-25 17:52:02 +02:00
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
/* Need to tag armature for cow updates, or else selection doesn't update. */
DEG_id_tag_update(&arm->id, ID_RECALC_SYNC_TO_EVAL);
2018-04-25 17:52:02 +02:00
}
ED_outliner_select_sync_from_pose_bone_tag(C);
return OPERATOR_FINISHED;
}
void POSE_OT_select_mirror(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Mirror";
ot->idname = "POSE_OT_select_mirror";
ot->description = "Mirror the bone selection";
/* API callbacks. */
ot->exec = pose_select_mirror_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(
ot->srna, "only_active", false, "Active Only", "Only operate on the active bone");
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
}