Blender V4.3
pose_select.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include <cstring>
10
11#include "DNA_anim_types.h"
12#include "DNA_armature_types.h"
15#include "DNA_object_types.h"
16#include "DNA_scene_types.h"
17
18#include "MEM_guardedalloc.h"
19
20#include "BLI_blenlib.h"
21
22#include "BKE_action.hh"
23#include "BKE_armature.hh"
24#include "BKE_constraint.h"
25#include "BKE_context.hh"
26#include "BKE_layer.hh"
27#include "BKE_modifier.hh"
28#include "BKE_object.hh"
29#include "BKE_report.hh"
30
31#include "DEG_depsgraph.hh"
32
33#include "RNA_access.hh"
34#include "RNA_define.hh"
35
36#include "WM_api.hh"
37#include "WM_types.hh"
38
39#include "ED_armature.hh"
40#include "ED_keyframing.hh"
41#include "ED_mesh.hh"
42#include "ED_object.hh"
43#include "ED_object_vgroup.hh"
44#include "ED_outliner.hh"
45#include "ED_screen.hh"
46#include "ED_select_utils.hh"
47#include "ED_view3d.hh"
48
50#include "ANIM_bonecolor.hh"
51#include "ANIM_keyingsets.hh"
52
53#include "armature_intern.hh"
54
55using blender::Span;
56using blender::Vector;
57
58/* utility macros for storing a temp int in the bone (selection flag) */
59#define PBONE_PREV_FLAG_GET(pchan) ((void)0, POINTER_AS_INT((pchan)->temp))
60#define PBONE_PREV_FLAG_SET(pchan, val) ((pchan)->temp = POINTER_FROM_INT(val))
61
62/* ***************** Pose Select Utilities ********************* */
63
64/* NOTE: SEL_TOGGLE is assumed to have already been handled! */
65static void pose_do_bone_select(bPoseChannel *pchan, const int select_mode)
66{
67 /* select pchan only if selectable, but deselect works always */
68 switch (select_mode) {
69 case SEL_SELECT:
70 if (!(pchan->bone->flag & BONE_UNSELECTABLE)) {
71 pchan->bone->flag |= BONE_SELECTED;
72 }
73 break;
74 case SEL_DESELECT:
76 break;
77 case SEL_INVERT:
78 if (pchan->bone->flag & BONE_SELECTED) {
80 }
81 else if (!(pchan->bone->flag & BONE_UNSELECTABLE)) {
82 pchan->bone->flag |= BONE_SELECTED;
83 }
84 break;
85 }
86}
87
89{
91 bArmature *arm = static_cast<bArmature *>(ob->data);
94
95 if (arm->flag & ARM_HAS_VIZ_DEPS) {
96 /* mask modifier ('armature' mode), etc. */
98 }
99
101}
102
103void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select, bool change_active)
104{
105 bArmature *arm;
106
107 /* sanity checks */
108 /* XXX: actually, we can probably still get away with no object - at most we have no updates */
109 if (ELEM(nullptr, ob, ob->pose, pchan, pchan->bone)) {
110 return;
111 }
112
113 arm = static_cast<bArmature *>(ob->data);
114
115 /* can only change selection state if bone can be modified */
116 if (PBONE_SELECTABLE(arm, pchan->bone)) {
117 /* change selection state - activate too if selected */
118 if (select) {
119 pchan->bone->flag |= BONE_SELECTED;
120 if (change_active) {
121 arm->act_bone = pchan->bone;
122 }
123 }
124 else {
125 pchan->bone->flag &= ~BONE_SELECTED;
126 if (change_active) {
127 arm->act_bone = nullptr;
128 }
129 }
130
131 /* TODO: select and activate corresponding vgroup? */
133 }
134}
135
137 ViewLayer *view_layer,
138 View3D *v3d,
139 Object *ob,
140 Bone *bone,
142{
143 bool found = false;
144 bool changed = false;
145
146 if (ob->pose) {
147 if (bone && ((bone->flag & BONE_UNSELECTABLE) == 0)) {
148 found = true;
149 }
150 }
151
152 if (params->sel_op == SEL_OP_SET) {
153 if ((found && params->select_passthrough) && (bone->flag & BONE_SELECTED)) {
154 found = false;
155 }
156 else if (found || params->deselect_all) {
157 /* Deselect everything. */
158 /* Don't use 'BKE_object_pose_base_array_get_unique'
159 * because we may be selecting from object mode. */
160 FOREACH_VISIBLE_BASE_BEGIN (scene, view_layer, v3d, base_iter) {
161 Object *ob_iter = base_iter->object;
162 if ((ob_iter->type == OB_ARMATURE) && (ob_iter->mode & OB_MODE_POSE)) {
163 if (ED_pose_deselect_all(ob_iter, SEL_DESELECT, true)) {
165 }
166 }
167 }
169 changed = true;
170 }
171 }
172
173 if (found) {
174 BKE_view_layer_synced_ensure(scene, view_layer);
175 Object *ob_act = BKE_view_layer_active_object_get(view_layer);
176 BLI_assert(BKE_view_layer_edit_object_get(view_layer) == nullptr);
177
178 /* If the bone cannot be affected, don't do anything. */
179 bArmature *arm = static_cast<bArmature *>(ob->data);
180
181 /* Since we do unified select, we don't shift+select a bone if the
182 * armature object was not active yet.
183 * NOTE(@ideasman42): special exception for armature mode so we can do multi-select
184 * we could check for multi-select explicitly but think its fine to
185 * always give predictable behavior in weight paint mode. */
186 if ((ob_act == nullptr) || ((ob_act != ob) && (ob_act->mode & OB_MODE_ALL_WEIGHT_PAINT) == 0))
187 {
188 /* When we are entering into posemode via toggle-select,
189 * from another active object - always select the bone. */
190 if (params->sel_op == SEL_OP_SET) {
191 /* Re-select the bone again later in this function. */
192 bone->flag &= ~BONE_SELECTED;
193 }
194 }
195
196 switch (params->sel_op) {
197 case SEL_OP_ADD: {
199 arm->act_bone = bone;
200 break;
201 }
202 case SEL_OP_SUB: {
204 break;
205 }
206 case SEL_OP_XOR: {
207 if (bone->flag & BONE_SELECTED) {
208 /* If not active, we make it active. */
209 if (bone != arm->act_bone) {
210 arm->act_bone = bone;
211 }
212 else {
214 }
215 }
216 else {
218 arm->act_bone = bone;
219 }
220 break;
221 }
222 case SEL_OP_SET: {
224 arm->act_bone = bone;
225 break;
226 }
227 case SEL_OP_AND: {
228 BLI_assert_unreachable(); /* Doesn't make sense for picking. */
229 break;
230 }
231 }
232
233 if (ob_act) {
234 /* In weight-paint we select the associated vertex group too. */
235 if (ob_act->mode & OB_MODE_ALL_WEIGHT_PAINT) {
236 if (bone == arm->act_bone) {
239 }
240 }
241 /* If there are some dependencies for visualizing armature state
242 * (e.g. Mask Modifier in 'Armature' mode), force update.
243 */
244 else if (arm->flag & ARM_HAS_VIZ_DEPS) {
245 /* NOTE: ob not ob_act here is intentional - it's the source of the
246 * bones being selected [#37247].
247 */
249 }
250
251 /* Tag armature for copy-on-evaluation update (since act_bone is in armature not object). */
253 }
254
255 changed = true;
256 }
257
258 return changed || found;
259}
260
262 ViewLayer *view_layer,
263 View3D *v3d,
264 Base *base,
265 const GPUSelectResult *hit_results,
266 const int hits,
268 bool do_nearest)
269{
270 Object *ob = base->object;
271 Bone *nearBone;
272
273 if (!ob || !ob->pose) {
274 return false;
275 }
276
277 /* Callers happen to already get the active base */
278 Base *base_dummy = nullptr;
280 {base}, hit_results, hits, true, do_nearest, &base_dummy);
281
282 return ED_armature_pose_select_pick_bone(scene, view_layer, v3d, ob, nearBone, params);
283}
284
286 ViewLayer *view_layer,
287 Base *base_select)
288{
289 BLI_assert(base_select && (base_select->object->type == OB_ARMATURE));
290 BKE_view_layer_synced_ensure(scene, view_layer);
291 Object *ob_active = BKE_view_layer_active_object_get(view_layer);
292 BLI_assert(ob_active && (ob_active->mode & OB_MODE_ALL_WEIGHT_PAINT));
293
294 VirtualModifierData virtual_modifier_data;
295 ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob_active, &virtual_modifier_data);
296 for (; md; md = md->next) {
297 if (md->type == eModifierType_Armature) {
299 Object *ob_arm = amd->object;
300 if (ob_arm != nullptr) {
301 Base *base_arm = BKE_view_layer_base_find(view_layer, ob_arm);
302 if ((base_arm != nullptr) && (base_arm != base_select) && (base_arm->flag & BASE_SELECTED))
303 {
305 }
306 }
307 }
308 }
309 if ((base_select->flag & BASE_SELECTED) == 0) {
311 }
312}
313
314bool ED_pose_deselect_all(Object *ob, int select_mode, const bool ignore_visibility)
315{
316 bArmature *arm = static_cast<bArmature *>(ob->data);
317
318 /* we call this from outliner too */
319 if (ob->pose == nullptr) {
320 return false;
321 }
322
323 /* Determine if we're selecting or deselecting */
324 if (select_mode == SEL_TOGGLE) {
325 select_mode = SEL_SELECT;
326 LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
327 if (ignore_visibility || PBONE_VISIBLE(arm, pchan->bone)) {
328 if (pchan->bone->flag & BONE_SELECTED) {
329 select_mode = SEL_DESELECT;
330 break;
331 }
332 }
333 }
334 }
335
336 /* Set the flags accordingly */
337 bool changed = false;
338 LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
339 /* ignore the pchan if it isn't visible or if its selection cannot be changed */
340 if (ignore_visibility || PBONE_VISIBLE(arm, pchan->bone)) {
341 int flag_prev = pchan->bone->flag;
342 pose_do_bone_select(pchan, select_mode);
343 changed = (changed || flag_prev != pchan->bone->flag);
344 }
345 }
346 return changed;
347}
348
349static bool ed_pose_is_any_selected(Object *ob, bool ignore_visibility)
350{
351 bArmature *arm = static_cast<bArmature *>(ob->data);
352 LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
353 if (ignore_visibility || PBONE_VISIBLE(arm, pchan->bone)) {
354 if (pchan->bone->flag & BONE_SELECTED) {
355 return true;
356 }
357 }
358 }
359 return false;
360}
361
362static bool ed_pose_is_any_selected_multi(const Span<Base *> bases, bool ignore_visibility)
363{
364 for (Base *base : bases) {
365 Object *ob_iter = base->object;
366 if (ed_pose_is_any_selected(ob_iter, ignore_visibility)) {
367 return true;
368 }
369 }
370 return false;
371}
372
374 int select_mode,
375 const bool ignore_visibility)
376{
377 if (select_mode == SEL_TOGGLE) {
378 select_mode = ed_pose_is_any_selected_multi(bases, ignore_visibility) ? SEL_DESELECT :
380 }
381
382 bool changed_multi = false;
383 for (Base *base : bases) {
384 Object *ob_iter = base->object;
385 if (ED_pose_deselect_all(ob_iter, select_mode, ignore_visibility)) {
387 changed_multi = true;
388 }
389 }
390 return changed_multi;
391}
392
393bool ED_pose_deselect_all_multi(bContext *C, int select_mode, const bool ignore_visibility)
394{
397
399 return ED_pose_deselect_all_multi_ex(bases, select_mode, ignore_visibility);
400}
401
402/* ***************** Selections ********************** */
403
404static void selectconnected_posebonechildren(Object *ob, Bone *bone, int extend)
405{
406 /* stop when unconnected child is encountered, or when unselectable bone is encountered */
407 if (!(bone->flag & BONE_CONNECTED) || (bone->flag & BONE_UNSELECTABLE)) {
408 return;
409 }
410
411 if (extend) {
412 bone->flag &= ~BONE_SELECTED;
413 }
414 else {
415 bone->flag |= BONE_SELECTED;
416 }
417
418 LISTBASE_FOREACH (Bone *, curBone, &bone->childbase) {
419 selectconnected_posebonechildren(ob, curBone, extend);
420 }
421}
422
423/* within active object context */
424/* previously known as "selectconnected_posearmature" */
426{
427 Bone *bone, *curBone, *next = nullptr;
428 const bool extend = RNA_boolean_get(op->ptr, "extend");
429
431
432 Base *base = nullptr;
433 bone = ED_armature_pick_bone(C, event->mval, !extend, &base);
434
435 if (!bone) {
436 return OPERATOR_CANCELLED;
437 }
438
439 /* Select parents */
440 for (curBone = bone; curBone; curBone = next) {
441 /* ignore bone if cannot be selected */
442 if ((curBone->flag & BONE_UNSELECTABLE) == 0) {
443 if (extend) {
444 curBone->flag &= ~BONE_SELECTED;
445 }
446 else {
447 curBone->flag |= BONE_SELECTED;
448 }
449
450 if (curBone->flag & BONE_CONNECTED) {
451 next = curBone->parent;
452 }
453 else {
454 next = nullptr;
455 }
456 }
457 else {
458 next = nullptr;
459 }
460 }
461
462 /* Select children */
463 LISTBASE_FOREACH (Bone *, curBone, &bone->childbase) {
464 selectconnected_posebonechildren(base->object, curBone, extend);
465 }
466
468
470
471 return OPERATOR_FINISHED;
472}
473
478
480{
481 PropertyRNA *prop;
482
483 /* identifiers */
484 ot->name = "Select Connected";
485 ot->idname = "POSE_OT_select_linked_pick";
486 ot->description = "Select bones linked by parent/child connections under the mouse cursor";
487
488 /* callbacks */
489 /* leave 'exec' unset */
492
493 /* flags */
495
496 /* props */
497 prop = RNA_def_boolean(ot->srna,
498 "extend",
499 false,
500 "Extend",
501 "Extend selection instead of deselecting everything first");
503}
504
506{
507 Bone *curBone, *next = nullptr;
508
509 CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
510 if ((pchan->bone->flag & BONE_SELECTED) == 0) {
511 continue;
512 }
513
514 bArmature *arm = static_cast<bArmature *>(ob->data);
515
516 /* Select parents */
517 for (curBone = pchan->bone; curBone; curBone = next) {
518 if (PBONE_SELECTABLE(arm, curBone)) {
519 curBone->flag |= BONE_SELECTED;
520
521 if (curBone->flag & BONE_CONNECTED) {
522 next = curBone->parent;
523 }
524 else {
525 next = nullptr;
526 }
527 }
528 else {
529 next = nullptr;
530 }
531 }
532
533 /* Select children */
534 LISTBASE_FOREACH (Bone *, curBone, &pchan->bone->childbase) {
535 selectconnected_posebonechildren(ob, curBone, false);
536 }
538 }
540
542
543 return OPERATOR_FINISHED;
544}
545
547{
548 /* identifiers */
549 ot->name = "Select Connected";
550 ot->idname = "POSE_OT_select_linked";
551 ot->description = "Select all bones linked by parent/child connections to the current selection";
552
553 /* callbacks */
556
557 /* flags */
559}
560
561/* -------------------------------------- */
562
564{
565 int action = RNA_enum_get(op->ptr, "action");
566
567 Scene *scene = CTX_data_scene(C);
568 int multipaint = scene->toolsettings->multipaint;
569
570 if (action == SEL_TOGGLE) {
571 action = CTX_DATA_COUNT(C, selected_pose_bones) ? SEL_DESELECT : SEL_SELECT;
572 }
573
574 Object *ob_prev = nullptr;
575
576 /* Set the flags. */
577 CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
578 bArmature *arm = static_cast<bArmature *>(ob->data);
579 pose_do_bone_select(pchan, action);
580
581 if (ob_prev != ob) {
582 /* Weight-paint or mask modifiers need depsgraph updates. */
583 if (multipaint || (arm->flag & ARM_HAS_VIZ_DEPS)) {
585 }
586 /* need to tag armature for cow updates, or else selection doesn't update */
588 ob_prev = ob;
589 }
590 }
592
594
596
597 return OPERATOR_FINISHED;
598}
599
601{
602 /* identifiers */
603 ot->name = "(De)select All";
604 ot->idname = "POSE_OT_select_all";
605 ot->description = "Toggle selection status of all bones";
606
607 /* api callbacks */
610
611 /* flags */
613
615}
616
617/* -------------------------------------- */
618
620{
622 bArmature *arm = (bArmature *)ob->data;
623 bPoseChannel *pchan, *parent;
624
625 /* Determine if there is an active bone */
626 pchan = CTX_data_active_pose_bone(C);
627 if (pchan) {
628 parent = pchan->parent;
629 if ((parent) && !(parent->bone->flag & (BONE_HIDDEN_P | BONE_UNSELECTABLE))) {
630 parent->bone->flag |= BONE_SELECTED;
631 arm->act_bone = parent->bone;
632 }
633 else {
634 return OPERATOR_CANCELLED;
635 }
636 }
637 else {
638 return OPERATOR_CANCELLED;
639 }
640
642
644 return OPERATOR_FINISHED;
645}
646
648{
649 /* identifiers */
650 ot->name = "Select Parent Bone";
651 ot->idname = "POSE_OT_select_parent";
652 ot->description = "Select bones that are parents of the currently selected bones";
653
654 /* api callbacks */
657
658 /* flags */
660}
661
662/* -------------------------------------- */
663
665{
666 int found = 0;
667
668 CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones) {
669 if (pchan->bone->flag & BONE_SELECTED) {
670 LISTBASE_FOREACH (bConstraint *, con, &pchan->constraints) {
671 ListBase targets = {nullptr, nullptr};
672 if (BKE_constraint_targets_get(con, &targets)) {
673 LISTBASE_FOREACH (bConstraintTarget *, ct, &targets) {
674 Object *ob = ct->tar;
675
676 /* Any armature that is also in pose mode should be selected. */
677 if ((ct->subtarget[0] != '\0') && (ob != nullptr) && (ob->type == OB_ARMATURE) &&
678 (ob->mode == OB_MODE_POSE))
679 {
680 bPoseChannel *pchanc = BKE_pose_channel_find_name(ob->pose, ct->subtarget);
681 if ((pchanc) && !(pchanc->bone->flag & BONE_UNSELECTABLE)) {
684 found = 1;
685 }
686 }
687 }
688
689 BKE_constraint_targets_flush(con, &targets, true);
690 }
691 }
692 }
693 }
695
696 if (!found) {
697 return OPERATOR_CANCELLED;
698 }
699
701
702 return OPERATOR_FINISHED;
703}
704
706{
707 /* identifiers */
708 ot->name = "Select Constraint Target";
709 ot->idname = "POSE_OT_select_constraint_target";
710 ot->description = "Select bones used as targets for the currently selected bones";
711
712 /* api callbacks */
715
716 /* flags */
718}
719
720/* -------------------------------------- */
721
722/* No need to convert to multi-objects. Just like we keep the non-active bones
723 * selected we then keep the non-active objects untouched (selected/unselected). */
725{
727 bArmature *arm = static_cast<bArmature *>(ob->data);
728 bPoseChannel *pchan_act;
729 int direction = RNA_enum_get(op->ptr, "direction");
730 const bool add_to_sel = RNA_boolean_get(op->ptr, "extend");
731 bool changed = false;
732
734 if (pchan_act == nullptr) {
735 return OPERATOR_CANCELLED;
736 }
737
738 if (direction == BONE_SELECT_PARENT) {
739 if (pchan_act->parent) {
740 Bone *bone_parent;
741 bone_parent = pchan_act->parent->bone;
742
743 if (PBONE_SELECTABLE(arm, bone_parent)) {
744 if (!add_to_sel) {
745 pchan_act->bone->flag &= ~BONE_SELECTED;
746 }
747 bone_parent->flag |= BONE_SELECTED;
748 arm->act_bone = bone_parent;
749
750 changed = true;
751 }
752 }
753 }
754 else { /* direction == BONE_SELECT_CHILD */
755 Bone *bone_child = nullptr;
756 int pass;
757
758 /* first pass, only connected bones (the logical direct child) */
759 for (pass = 0; pass < 2 && (bone_child == nullptr); pass++) {
760 LISTBASE_FOREACH (bPoseChannel *, pchan_iter, &ob->pose->chanbase) {
761 /* possible we have multiple children, some invisible */
762 if (PBONE_SELECTABLE(arm, pchan_iter->bone)) {
763 if (pchan_iter->parent == pchan_act) {
764 if ((pass == 1) || (pchan_iter->bone->flag & BONE_CONNECTED)) {
765 bone_child = pchan_iter->bone;
766 break;
767 }
768 }
769 }
770 }
771 }
772
773 if (bone_child) {
774 arm->act_bone = bone_child;
775
776 if (!add_to_sel) {
777 pchan_act->bone->flag &= ~BONE_SELECTED;
778 }
779 bone_child->flag |= BONE_SELECTED;
780
781 changed = true;
782 }
783 }
784
785 if (changed == false) {
786 return OPERATOR_CANCELLED;
787 }
788
790
792
793 return OPERATOR_FINISHED;
794}
795
797{
798 static const EnumPropertyItem direction_items[] = {
799 {BONE_SELECT_PARENT, "PARENT", 0, "Select Parent", ""},
800 {BONE_SELECT_CHILD, "CHILD", 0, "Select Child", ""},
801 {0, nullptr, 0, nullptr, nullptr},
802 };
803
804 /* identifiers */
805 ot->name = "Select Hierarchy";
806 ot->idname = "POSE_OT_select_hierarchy";
807 ot->description = "Select immediate parent/children of selected bones";
808
809 /* api callbacks */
812
813 /* flags */
815
816 /* props */
818 ot->srna, "direction", direction_items, BONE_SELECT_PARENT, "Direction", "");
819 RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
820}
821
822/* -------------------------------------- */
823
824/* modes for select same */
830
831static bool pose_select_same_color(bContext *C, const bool extend)
832{
833 /* Get a set of all the colors of the selected bones. */
835 blender::Set<Object *> updated_objects;
836 bool changed_any_selection = false;
837
838 /* Old approach that we may want to reinstate behind some option at some point. This will match
839 * against the colors of all selected bones, instead of just the active one. It also explains why
840 * there is a set of colors to begin with.
841 *
842 * CTX_DATA_BEGIN (C, bPoseChannel *, pchan, selected_pose_bones) {
843 * auto color = blender::animrig::ANIM_bonecolor_posebone_get(pchan);
844 * used_colors.add(color);
845 * }
846 * CTX_DATA_END;
847 */
848 if (!extend) {
849 CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, selected_pose_bones, Object *, ob) {
850 pchan->bone->flag &= ~BONE_SELECTED;
851 updated_objects.add(ob);
852 changed_any_selection = true;
853 }
855 }
856
857 /* Use the color of the active pose bone. */
858 bPoseChannel *active_pose_bone = CTX_data_active_pose_bone(C);
859 auto color = blender::animrig::ANIM_bonecolor_posebone_get(active_pose_bone);
860 used_colors.add(color);
861
862 /* Select all visible bones that have the same color. */
863 CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
864 Bone *bone = pchan->bone;
865 if (bone->flag & (BONE_UNSELECTABLE | BONE_SELECTED)) {
866 /* Skip bones that are unselectable or already selected. */
867 continue;
868 }
869
871 if (!used_colors.contains(color)) {
872 continue;
873 }
874
875 bone->flag |= BONE_SELECTED;
876 changed_any_selection = true;
877 updated_objects.add(ob);
878 }
880
881 if (!changed_any_selection) {
882 return false;
883 }
884
885 for (Object *ob : updated_objects) {
887 }
888 return true;
889}
890
891static bool pose_select_same_collection(bContext *C, const bool extend)
892{
893 bool changed_any_selection = false;
894 blender::Set<Object *> updated_objects;
895
896 /* Refuse to do anything if there is no active pose bone. */
897 bPoseChannel *active_pchan = CTX_data_active_pose_bone(C);
898 if (!active_pchan) {
899 return false;
900 }
901
902 if (!extend) {
903 /* Deselect all the bones. */
904 CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, selected_pose_bones, Object *, ob) {
905 pchan->bone->flag &= ~BONE_SELECTED;
906 updated_objects.add(ob);
907 changed_any_selection = true;
908 }
910 }
911
912 /* Build a set of bone collection names, to allow cross-Armature selection. */
913 blender::Set<std::string> collection_names;
914 LISTBASE_FOREACH (BoneCollectionReference *, bcoll_ref, &active_pchan->bone->runtime.collections)
915 {
916 collection_names.add(bcoll_ref->bcoll->name);
917 }
918
919 /* Select all bones that match any of the collection names. */
920 CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
921 Bone *bone = pchan->bone;
922 if (bone->flag & (BONE_UNSELECTABLE | BONE_SELECTED)) {
923 continue;
924 }
925
927 if (!collection_names.contains(bcoll_ref->bcoll->name)) {
928 continue;
929 }
930
931 bone->flag |= BONE_SELECTED;
932 changed_any_selection = true;
933 updated_objects.add(ob);
934 }
935 }
937
938 for (Object *ob : updated_objects) {
940 }
941
942 return changed_any_selection;
943}
944
945static bool pose_select_same_keyingset(bContext *C, ReportList *reports, bool extend)
946{
947 Scene *scene = CTX_data_scene(C);
948 ViewLayer *view_layer = CTX_data_view_layer(C);
949 bool changed_multi = false;
951
952 /* sanity checks: validate Keying Set and object */
953 if (ks == nullptr) {
954 BKE_report(reports, RPT_ERROR, "No active Keying Set to use");
955 return false;
956 }
958 if (ks->paths.first == nullptr) {
959 if ((ks->flag & KEYINGSET_ABSOLUTE) == 0) {
960 BKE_report(reports,
961 RPT_ERROR,
962 "Use another Keying Set, as the active one depends on the currently "
963 "selected items or cannot find any targets due to unsuitable context");
964 }
965 else {
966 BKE_report(reports, RPT_ERROR, "Keying Set does not contain any paths");
967 }
968 }
969 return false;
970 }
971
972 /* if not extending selection, deselect all selected first */
973 if (extend == false) {
974 CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones) {
975 if ((pchan->bone->flag & BONE_UNSELECTABLE) == 0) {
976 pchan->bone->flag &= ~BONE_SELECTED;
977 }
978 }
980 }
981
982 Vector<Object *> objects = BKE_object_pose_array_get_unique(scene, view_layer, CTX_wm_view3d(C));
983
984 for (const int ob_index : objects.index_range()) {
985 Object *ob = BKE_object_pose_armature_get(objects[ob_index]);
986 bArmature *arm = static_cast<bArmature *>((ob) ? ob->data : nullptr);
987 bPose *pose = (ob) ? ob->pose : nullptr;
988 bool changed = false;
989
990 /* Sanity checks. */
991 if (ELEM(nullptr, ob, pose, arm)) {
992 continue;
993 }
994
995 /* iterate over elements in the Keying Set, setting selection depending on whether
996 * that bone is visible or not...
997 */
998 LISTBASE_FOREACH (KS_Path *, ksp, &ks->paths) {
999 /* only items related to this object will be relevant */
1000 if ((ksp->id == &ob->id) && (ksp->rna_path != nullptr)) {
1001 bPoseChannel *pchan = nullptr;
1002 char boneName[sizeof(pchan->name)];
1003 if (!BLI_str_quoted_substr(ksp->rna_path, "bones[", boneName, sizeof(boneName))) {
1004 continue;
1005 }
1006 pchan = BKE_pose_channel_find_name(pose, boneName);
1007
1008 if (pchan) {
1009 /* select if bone is visible and can be affected */
1010 if (PBONE_SELECTABLE(arm, pchan->bone)) {
1011 pchan->bone->flag |= BONE_SELECTED;
1012 changed = true;
1013 }
1014 }
1015 }
1016 }
1017
1018 if (changed || !extend) {
1020 changed_multi = true;
1021 }
1022 }
1023
1024 return changed_multi;
1025}
1026
1028{
1031 const bool extend = RNA_boolean_get(op->ptr, "extend");
1032 bool changed = false;
1033
1034 /* sanity check */
1035 if (ob->pose == nullptr) {
1036 return OPERATOR_CANCELLED;
1037 }
1038
1039 /* selection types */
1040 switch (type) {
1042 changed = pose_select_same_collection(C, extend);
1043 break;
1044
1046 changed = pose_select_same_color(C, extend);
1047 break;
1048
1049 case POSE_SEL_SAME_KEYINGSET: /* Keying Set */
1050 changed = pose_select_same_keyingset(C, op->reports, extend);
1051 break;
1052
1053 default:
1054 printf("pose_select_grouped() - Unknown selection type %d\n", type);
1055 break;
1056 }
1057
1058 /* report done status */
1059 if (changed) {
1061
1062 return OPERATOR_FINISHED;
1063 }
1064 return OPERATOR_CANCELLED;
1065}
1066
1068{
1069 static const EnumPropertyItem prop_select_grouped_types[] = {
1071 "COLLECTION",
1072 0,
1073 "Collection",
1074 "Same collections as the active bone"},
1075 {POSE_SEL_SAME_COLOR, "COLOR", 0, "Color", "Same color as the active bone"},
1077 "KEYINGSET",
1078 0,
1079 "Keying Set",
1080 "All bones affected by active Keying Set"},
1081 {0, nullptr, 0, nullptr, nullptr},
1082 };
1083
1084 /* identifiers */
1085 ot->name = "Select Grouped";
1086 ot->description = "Select all visible bones grouped by similar properties";
1087 ot->idname = "POSE_OT_select_grouped";
1088
1089 /* api callbacks */
1092 ot->poll = ED_operator_posemode; /* TODO: expand to support edit mode as well. */
1093
1094 /* flags */
1096
1097 /* properties */
1099 "extend",
1100 false,
1101 "Extend",
1102 "Extend selection instead of deselecting everything first");
1103 ot->prop = RNA_def_enum(ot->srna, "type", prop_select_grouped_types, 0, "Type", "");
1104}
1105
1106/* -------------------------------------- */
1107
1112{
1113 const Scene *scene = CTX_data_scene(C);
1114 ViewLayer *view_layer = CTX_data_view_layer(C);
1115 Object *ob_active = CTX_data_active_object(C);
1116
1117 const bool is_weight_paint = (ob_active->mode & OB_MODE_WEIGHT_PAINT) != 0;
1118 const bool active_only = RNA_boolean_get(op->ptr, "only_active");
1119 const bool extend = RNA_boolean_get(op->ptr, "extend");
1120
1121 Vector<Object *> objects = BKE_object_pose_array_get_unique(scene, view_layer, CTX_wm_view3d(C));
1122 for (Object *ob : objects) {
1123 bArmature *arm = static_cast<bArmature *>(ob->data);
1124 bPoseChannel *pchan_mirror_act = nullptr;
1125
1126 LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
1127 const int flag = (pchan->bone->flag & BONE_SELECTED);
1128 PBONE_PREV_FLAG_SET(pchan, flag);
1129 }
1130
1131 LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
1132 if (PBONE_SELECTABLE(arm, pchan->bone)) {
1133 bPoseChannel *pchan_mirror;
1134 int flag_new = extend ? PBONE_PREV_FLAG_GET(pchan) : 0;
1135
1136 if ((pchan_mirror = BKE_pose_channel_get_mirrored(ob->pose, pchan->name)) &&
1137 PBONE_VISIBLE(arm, pchan_mirror->bone))
1138 {
1139 const int flag_mirror = PBONE_PREV_FLAG_GET(pchan_mirror);
1140 flag_new |= flag_mirror;
1141
1142 if (pchan->bone == arm->act_bone) {
1143 pchan_mirror_act = pchan_mirror;
1144 }
1145
1146 /* Skip all but the active or its mirror. */
1147 if (active_only && !ELEM(arm->act_bone, pchan->bone, pchan_mirror->bone)) {
1148 continue;
1149 }
1150 }
1151
1152 pchan->bone->flag = (pchan->bone->flag & ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL)) |
1153 flag_new;
1154 }
1155 }
1156
1157 if (pchan_mirror_act) {
1158 arm->act_bone = pchan_mirror_act->bone;
1159
1160 /* In weight-paint we select the associated vertex group too. */
1161 if (is_weight_paint) {
1162 blender::ed::object::vgroup_select_by_name(ob_active, pchan_mirror_act->name);
1164 }
1165 }
1166
1168
1169 /* Need to tag armature for cow updates, or else selection doesn't update. */
1171 }
1172
1174
1175 return OPERATOR_FINISHED;
1176}
1177
1179{
1180 /* identifiers */
1181 ot->name = "Select Mirror";
1182 ot->idname = "POSE_OT_select_mirror";
1183 ot->description = "Mirror the bone selection";
1184
1185 /* api callbacks */
1188
1189 /* flags */
1191
1192 /* properties */
1194 ot->srna, "only_active", false, "Active Only", "Only operate on the active bone");
1195 RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
1196}
C++ functions to deal with Armature collections (i.e. the successor of bone layers).
C++ part of the BoneColor DNA struct.
Functionality to interact with keying sets.
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
bPoseChannel * BKE_pose_channel_get_mirrored(const bPose *pose, const char *name) ATTR_WARN_UNUSED_RESULT
bPoseChannel * BKE_pose_channel_active_if_bonecoll_visible(Object *ob) ATTR_WARN_UNUSED_RESULT
#define PBONE_VISIBLE(arm, bone)
#define PBONE_SELECTABLE(arm, bone)
void BKE_constraint_targets_flush(struct bConstraint *con, struct ListBase *targets, bool no_copy)
int BKE_constraint_targets_get(struct bConstraint *con, struct ListBase *r_targets)
#define CTX_DATA_BEGIN_WITH_ID(C, Type, instance, member, Type_id, instance_id)
bPoseChannel * CTX_data_active_pose_bone(const bContext *C)
#define CTX_DATA_BEGIN(C, Type, instance, member)
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
#define CTX_DATA_COUNT(C, member)
Scene * CTX_data_scene(const bContext *C)
#define CTX_DATA_END
View3D * CTX_wm_view3d(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
#define FOREACH_VISIBLE_BASE_END
Definition BKE_layer.hh:417
void BKE_view_layer_synced_ensure(const Scene *scene, ViewLayer *view_layer)
Object * BKE_view_layer_active_object_get(const ViewLayer *view_layer)
#define FOREACH_VISIBLE_BASE_BEGIN(_scene, _view_layer, _v3d, _instance)
Definition BKE_layer.hh:404
Base * BKE_view_layer_base_find(ViewLayer *view_layer, Object *ob)
Object * BKE_view_layer_edit_object_get(const ViewLayer *view_layer)
ModifierData * BKE_modifiers_get_virtual_modifierlist(const Object *ob, VirtualModifierData *data)
General operations, lookup, etc. for blender objects.
blender::Vector< Base * > BKE_object_pose_base_array_get_unique(const Scene *scene, ViewLayer *view_layer, View3D *v3d)
Object * BKE_object_pose_armature_get(Object *ob)
blender::Vector< Object * > BKE_object_pose_array_get_unique(const Scene *scene, ViewLayer *view_layer, View3D *v3d)
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:125
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define LISTBASE_FOREACH(type, var, list)
bool bool BLI_str_quoted_substr(const char *__restrict str, const char *__restrict prefix, char *result, size_t result_maxncpy)
Definition string.c:517
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_SELECT
Definition DNA_ID.h:1068
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1085
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1041
@ KEYINGSET_ABSOLUTE
@ BONE_ROOTSEL
@ BONE_SELECTED
@ BONE_UNSELECTABLE
@ BONE_HIDDEN_P
@ BONE_TIPSEL
@ BONE_CONNECTED
@ ARM_HAS_VIZ_DEPS
@ eModifierType_Armature
#define OB_MODE_ALL_WEIGHT_PAINT
@ OB_MODE_WEIGHT_PAINT
@ OB_MODE_POSE
Object is a sort of wrapper for general info.
@ OB_ARMATURE
#define BASE_SELECTED(v3d, base)
#define BONE_SELECT_CHILD
#define BONE_SELECT_PARENT
void ED_outliner_select_sync_from_pose_bone_tag(bContext *C)
bool ED_operator_view3d_active(bContext *C)
bool ED_operator_posemode(bContext *C)
@ SEL_SELECT
@ SEL_INVERT
@ SEL_DESELECT
@ SEL_TOGGLE
@ SEL_OP_ADD
@ SEL_OP_SUB
@ SEL_OP_SET
@ SEL_OP_AND
@ SEL_OP_XOR
ViewContext ED_view3d_viewcontext_init(bContext *C, Depsgraph *depsgraph)
void view3d_operator_needs_opengl(const bContext *C)
Read Guarded memory(de)allocation.
@ PROP_SKIP_SAVE
Definition RNA_types.hh:245
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
#define NC_GEOM
Definition WM_types.hh:360
#define ND_DATA
Definition WM_types.hh:475
#define ND_BONE_SELECT
Definition WM_types.hh:427
#define NC_OBJECT
Definition WM_types.hh:346
Bone * ED_armature_pick_bone_from_selectbuffer(blender::Span< Base * > bases, const GPUSelectResult *hit_results, int hits, bool findunsel, bool do_nearest, Base **r_base)
Bone * ED_armature_pick_bone(bContext *C, const int xy[2], bool findunsel, Base **r_base)
bool contains(const Key &key) const
Definition BLI_set.hh:291
bool add(const Key &key)
Definition BLI_set.hh:248
#define printf
const Depsgraph * depsgraph
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
KeyingSet * ANIM_scene_get_active_keyingset(const Scene *scene)
blender::animrig::ModifyKeyReturn ANIM_validate_keyingset(bContext *C, blender::Vector< PointerRNA > *sources, KeyingSet *keyingset)
ccl_device_inline float4 select(const int4 mask, const float4 a, const float4 b)
static ulong * next
const BoneColor & ANIM_bonecolor_posebone_get(const bPoseChannel *pose_bone)
Definition bonecolor.cc:88
void base_select(Base *base, eObjectSelect_Mode mode)
void vgroup_select_by_name(Object *ob, const char *name)
static bool pose_select_same_collection(bContext *C, const bool extend)
void POSE_OT_select_parent(wmOperatorType *ot)
void ED_armature_pose_select_in_wpaint_mode(const Scene *scene, ViewLayer *view_layer, Base *base_select)
#define PBONE_PREV_FLAG_SET(pchan, val)
static int pose_select_hierarchy_exec(bContext *C, wmOperator *op)
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)
bool ED_armature_pose_select_pick_bone(const Scene *scene, ViewLayer *view_layer, View3D *v3d, Object *ob, Bone *bone, const SelectPick_Params *params)
static bool ed_pose_is_any_selected_multi(const Span< Base * > bases, bool ignore_visibility)
void POSE_OT_select_hierarchy(wmOperatorType *ot)
void POSE_OT_select_grouped(wmOperatorType *ot)
void POSE_OT_select_linked_pick(wmOperatorType *ot)
static void pose_do_bone_select(bPoseChannel *pchan, const int select_mode)
static int pose_select_linked_exec(bContext *C, wmOperator *)
void ED_pose_bone_select_tag_update(Object *ob)
bool ED_pose_deselect_all(Object *ob, int select_mode, const bool ignore_visibility)
static int pose_select_connected_invoke(bContext *C, wmOperator *op, const wmEvent *event)
void POSE_OT_select_mirror(wmOperatorType *ot)
bool ED_pose_deselect_all_multi_ex(const Span< Base * > bases, int select_mode, const bool ignore_visibility)
static bool ed_pose_is_any_selected(Object *ob, bool ignore_visibility)
bool ED_pose_deselect_all_multi(bContext *C, int select_mode, const bool ignore_visibility)
static int pose_select_mirror_exec(bContext *C, wmOperator *op)
void POSE_OT_select_all(wmOperatorType *ot)
void POSE_OT_select_constraint_target(wmOperatorType *ot)
static bool pose_select_linked_pick_poll(bContext *C)
ePose_SelectSame_Mode
@ POSE_SEL_SAME_COLOR
@ POSE_SEL_SAME_COLLECTION
@ POSE_SEL_SAME_KEYINGSET
static int pose_de_select_all_exec(bContext *C, wmOperator *op)
static int pose_select_parent_exec(bContext *C, wmOperator *)
static bool pose_select_same_keyingset(bContext *C, ReportList *reports, bool extend)
void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select, bool change_active)
static int pose_select_constraint_target_exec(bContext *C, wmOperator *)
void POSE_OT_select_linked(wmOperatorType *ot)
#define PBONE_PREV_FLAG_GET(pchan)
static bool pose_select_same_color(bContext *C, const bool extend)
static void selectconnected_posebonechildren(Object *ob, Bone *bone, int extend)
static int pose_select_grouped_exec(bContext *C, wmOperator *op)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
short flag
struct Object * object
Bone_Runtime runtime
struct Bone * parent
char name[64]
ListBase childbase
ListBase paths
void * first
struct ModifierData * next
struct bPose * pose
Scene * scene
Definition ED_view3d.hh:69
ViewLayer * view_layer
Definition ED_view3d.hh:70
View3D * v3d
Definition ED_view3d.hh:74
struct Bone * bone
struct bPoseChannel * parent
ListBase chanbase
int mval[2]
Definition WM_types.hh:728
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
PropertyRNA * prop
Definition WM_types.hh:1092
StructRNA * srna
Definition WM_types.hh:1080
struct ReportList * reports
struct PointerRNA * ptr
void WM_main_add_notifier(uint type, void *reference)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
wmOperatorType * ot
Definition wm_files.cc:4125
void WM_operator_properties_select_all(wmOperatorType *ot)
int WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)
uint8_t flag
Definition wm_window.cc:138