Blender V5.0
editors/armature/bone_collections.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9
10#include <cstring>
11
12#include "ANIM_armature.hh"
14
15#include "DNA_ID.h"
16#include "DNA_object_types.h"
17
18#include "BLI_listbase.h"
19
20#include "BKE_action.hh"
21#include "BKE_context.hh"
22#include "BKE_lib_override.hh"
23#include "BKE_library.hh"
24#include "BKE_report.hh"
25
26#include "BLT_translation.hh"
27
28#include "DEG_depsgraph.hh"
29
30#include "RNA_access.hh"
31#include "RNA_define.hh"
32
33#include "WM_api.hh"
34#include "WM_types.hh"
35
36#include "ED_armature.hh"
37#include "ED_object.hh"
38#include "ED_outliner.hh"
39
40#include "UI_interface.hh"
42#include "UI_resources.hh"
43
44#include "armature_intern.hh"
45
46struct wmOperator;
47
48/* ********************************************** */
49/* Bone collections */
50
52{
53 bArmature *armature = ED_armature_context(C);
54 if (armature == nullptr) {
55 return false;
56 }
57
58 if (!ID_IS_EDITABLE(&armature->id)) {
60 "Cannot add bone collections to a linked Armature without an "
61 "override on the Armature Data");
62 return false;
63 }
64
65 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
67 "Cannot add bone collections to a linked Armature with a system "
68 "override; explicitly create an override on the Armature Data");
69 return false;
70 }
71
72 return true;
73}
74
77{
78 bArmature *armature = ED_armature_context(C);
79 if (armature == nullptr) {
80 return false;
81 }
82
83 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
85 "Cannot update a linked Armature with a system override; "
86 "explicitly create an override on the Armature Data");
87 return false;
88 }
89
90 BoneCollection *bcoll = armature->runtime.active_collection;
91 if (bcoll == nullptr) {
92 CTX_wm_operator_poll_msg_set(C, "Armature has no active bone collection, select one first");
93 return false;
94 }
95
96 if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
98 C, "Cannot edit bone collections that are linked from another blend file");
99 return false;
100 }
101 return true;
102}
103
105{
106 using namespace blender::animrig;
107
108 bArmature *armature = ED_armature_context(C);
109
110 /* If there is an active bone collection, create the new one as a sibling. */
111 const int parent_index = armature_bonecoll_find_parent_index(
112 armature, armature->runtime.active_collection_index);
113
114 BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, nullptr, parent_index);
115
116 if (armature->runtime.active_collection) {
117 const int active_child_index = armature_bonecoll_child_number_find(
118 armature, armature->runtime.active_collection);
119 armature_bonecoll_child_number_set(armature, bcoll, active_child_index + 1);
120 }
121
122 ANIM_armature_bonecoll_active_set(armature, bcoll);
123 /* TODO: ensure the ancestors of the new bone collection are all expanded. */
124
126 return OPERATOR_FINISHED;
127}
128
130{
131 /* identifiers */
132 ot->name = "Add Bone Collection";
133 ot->idname = "ARMATURE_OT_collection_add";
134 ot->description = "Add a new bone collection";
135
136 /* API callbacks. */
139
140 /* flags */
142}
143
145{
146 /* The poll function ensures armature->active_collection is not NULL. */
147 bArmature *armature = ED_armature_context(C);
149
150 /* notifiers for updates */
153
154 return OPERATOR_FINISHED;
155}
156
158{
159 /* identifiers */
160 ot->name = "Remove Bone Collection";
161 ot->idname = "ARMATURE_OT_collection_remove";
162 ot->description = "Remove the active bone collection";
163
164 /* API callbacks. */
167
168 /* flags */
170}
171
173{
174 const int direction = RNA_enum_get(op->ptr, "direction");
175
176 /* Poll function makes sure this is valid. */
177 bArmature *armature = ED_armature_context(C);
178
179 const bool ok = ANIM_armature_bonecoll_move(
180 armature, armature->runtime.active_collection, direction);
181 if (!ok) {
182 return OPERATOR_CANCELLED;
183 }
184
186
188 return OPERATOR_FINISHED;
189}
190
192{
193 static const EnumPropertyItem bcoll_slot_move[] = {
194 {-1, "UP", 0, "Up", ""},
195 {1, "DOWN", 0, "Down", ""},
196 {0, nullptr, 0, nullptr, nullptr},
197 };
198
199 /* identifiers */
200 ot->name = "Move Bone Collection";
201 ot->idname = "ARMATURE_OT_collection_move";
202 ot->description = "Change position of active Bone Collection in list of Bone collections";
203
204 /* API callbacks. */
207
208 /* flags */
210
211 RNA_def_enum(ot->srna,
212 "direction",
213 bcoll_slot_move,
214 0,
215 "Direction",
216 "Direction to move the active Bone Collection towards");
217}
218
220{
221 bArmature *armature = static_cast<bArmature *>(ob->data);
222
223 char bcoll_name[MAX_NAME];
224 RNA_string_get(op->ptr, "name", bcoll_name);
225
226 if (bcoll_name[0] == '\0') {
227 return armature->runtime.active_collection;
228 }
229
230 BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(armature, bcoll_name);
231 if (!bcoll) {
232 BKE_reportf(op->reports, RPT_ERROR, "No bone collection named '%s'", bcoll_name);
233 return nullptr;
234 }
235
236 return bcoll;
237}
238
239using assign_bone_func = bool (*)(BoneCollection *bcoll, Bone *bone);
240using assign_ebone_func = bool (*)(BoneCollection *bcoll, EditBone *ebone);
241
242/* The following 3 functions either assign or unassign, depending on the
243 * 'assign_bone_func'/'assign_ebone_func' they get passed. */
244
246 Object *ob,
247 BoneCollection *bcoll,
248 assign_bone_func assign_func,
249 bool *made_any_changes,
250 bool *had_bones_to_assign)
251{
252 /* TODO: support multi-object pose mode. */
254 *made_any_changes |= assign_func(bcoll, pchan->bone);
255 *had_bones_to_assign = true;
256 }
258
260
261 bArmature *arm = static_cast<bArmature *>(ob->data);
262 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
263}
264
266 Object *ob,
267 BoneCollection *bcoll,
268 assign_ebone_func assign_func,
269 bool *made_any_changes,
270 bool *had_bones_to_assign)
271{
272 bArmature *arm = static_cast<bArmature *>(ob->data);
274
275 LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
276 if (!EBONE_EDITABLE(ebone) || !blender::animrig::bone_is_visible(arm, ebone)) {
277 continue;
278 }
279 *made_any_changes |= assign_func(bcoll, ebone);
280 *had_bones_to_assign = true;
281 }
282
286}
287
294 Object *ob,
295 BoneCollection *bcoll,
298 bool *made_any_changes,
299 bool *had_bones_to_assign)
300{
301 switch (CTX_data_mode_enum(C)) {
302 case CTX_MODE_POSE: {
304 C, ob, bcoll, assign_bone_func, made_any_changes, had_bones_to_assign);
305 return true;
306 }
307
310 C, ob, bcoll, assign_ebone_func, made_any_changes, had_bones_to_assign);
311
313 return true;
314 }
315
316 default:
317 return false;
318 }
319}
320
327 Object *ob,
328 BoneCollection *bcoll,
329 const char *bone_name,
332 bool *made_any_changes,
333 bool *had_bones_to_assign)
334{
335 bArmature *arm = static_cast<bArmature *>(ob->data);
336
337 switch (CTX_data_mode_enum(C)) {
338 case CTX_MODE_POSE: {
339 bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
340 if (!pchan) {
341 return true;
342 }
343
344 *had_bones_to_assign = true;
345 *made_any_changes |= assign_bone_func(bcoll, pchan->bone);
346
349 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
350 return true;
351 }
352
354 EditBone *ebone = ED_armature_ebone_find_name(arm->edbo, bone_name);
355 if (!ebone) {
356 return true;
357 }
358
359 *had_bones_to_assign = true;
360 *made_any_changes |= assign_ebone_func(bcoll, ebone);
361
365 return true;
366 }
367
368 default:
369 return false;
370 }
371}
372
374{
376 if (ob == nullptr) {
377 return false;
378 }
379
380 if (ob->type != OB_ARMATURE) {
381 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
382 return false;
383 }
384
385 bArmature *armature = static_cast<bArmature *>(ob->data);
386 if (armature != ED_armature_context(C)) {
387 CTX_wm_operator_poll_msg_set(C, "Pinned armature is not active in the 3D viewport");
388 return false;
389 }
390
391 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
393 C, "Cannot edit bone collections on linked Armatures without override");
394 return false;
395 }
396 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
398 "Cannot edit bone collections on a linked Armature with a system "
399 "override; explicitly create an override on the Armature Data");
400 return false;
401 }
402
403 CTX_wm_operator_poll_msg_set(C, "Linked bone collections are not editable");
404
405 /* The target bone collection can be specified by name in an operator property, but that's not
406 * available here. So just allow in the poll function, and do the final check in the execute. */
407 return true;
408}
409
410/* Assign selected pchans to the bone collection that the user selects */
412{
414 if (ob == nullptr) {
415 return OPERATOR_CANCELLED;
416 }
417
419 if (bcoll == nullptr) {
420 return OPERATOR_CANCELLED;
421 }
422
423 bArmature *armature = static_cast<bArmature *>(ob->data);
424 if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
425 BKE_reportf(op->reports, RPT_ERROR, "Cannot assign to linked bone collection %s", bcoll->name);
426 return OPERATOR_CANCELLED;
427 }
428
429 bool made_any_changes = false;
430 bool had_bones_to_assign = false;
431 const bool mode_is_supported = bone_collection_assign_mode_specific(
432 C,
433 ob,
434 bcoll,
437 &made_any_changes,
438 &had_bones_to_assign);
439
440 if (!mode_is_supported) {
442 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
443 return OPERATOR_CANCELLED;
444 }
445 if (!had_bones_to_assign) {
447 op->reports, RPT_WARNING, "No bones selected, nothing to assign to bone collection");
448 return OPERATOR_CANCELLED;
449 }
450 if (!made_any_changes) {
452 op->reports, RPT_WARNING, "All selected bones were already part of this collection");
453 return OPERATOR_CANCELLED;
454 }
455
457 return OPERATOR_FINISHED;
458}
459
461{
462 /* identifiers */
463 ot->name = "Add Selected Bones to Collection";
464 ot->idname = "ARMATURE_OT_collection_assign";
465 ot->description = "Add selected bones to the chosen bone collection";
466
467 /* API callbacks. */
470
471 /* flags */
472 ot->flag = OPTYPE_UNDO;
473
474 /* properties */
475 RNA_def_string(ot->srna,
476 "name",
477 nullptr,
478 MAX_NAME,
479 "Bone Collection",
480 "Name of the bone collection to assign this bone to; empty to assign to the "
481 "active bone collection");
482}
483
485{
487 if (ob == nullptr) {
488 return false;
489 }
490
491 if (ob->type != OB_ARMATURE) {
492 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
493 return false;
494 }
495
496 bArmature *armature = static_cast<bArmature *>(ob->data);
497 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
499 C, "Cannot edit bone collections on linked Armatures without override");
500 return false;
501 }
502 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
504 "Cannot edit bone collections on a linked Armature with a system "
505 "override; explicitly create an override on the Armature Data");
506 return false;
507 }
508
509 return true;
510}
511
512/* Assign selected pchans to the bone collection that the user selects */
514{
516 if (ob == nullptr) {
517 return OPERATOR_CANCELLED;
518 }
519
520 bArmature *armature = static_cast<bArmature *>(ob->data);
521
522 char bcoll_name[MAX_NAME];
523 RNA_string_get(op->ptr, "name", bcoll_name);
524
525 /* Note that this bone collection can be removed later on, if the assignment part of this
526 * operation failed. */
527 BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, bcoll_name);
528
529 bool made_any_changes = false;
530 bool had_bones_to_assign = false;
531 const bool mode_is_supported = bone_collection_assign_mode_specific(
532 C,
533 ob,
534 bcoll,
537 &made_any_changes,
538 &had_bones_to_assign);
539
540 if (!mode_is_supported) {
542 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
543 ANIM_armature_bonecoll_remove(armature, bcoll);
544 return OPERATOR_CANCELLED;
545 }
546 if (!had_bones_to_assign) {
548 op->reports, RPT_WARNING, "No bones selected, nothing to assign to bone collection");
549 return OPERATOR_FINISHED;
550 }
551 /* Not checking for `made_any_changes`, as if there were any bones to assign, they never could
552 * have already been assigned to this brand new bone collection. */
553
554 ANIM_armature_bonecoll_active_set(armature, bcoll);
556 return OPERATOR_FINISHED;
557}
558
560{
561 /* identifiers */
562 ot->name = "Add Selected Bones to New Collection";
563 ot->idname = "ARMATURE_OT_collection_create_and_assign";
564 ot->description = "Create a new bone collection and assign all selected bones";
565
566 /* API callbacks. */
569
570 /* flags */
572
573 /* properties */
574 RNA_def_string(ot->srna,
575 "name",
576 nullptr,
577 MAX_NAME,
578 "Bone Collection",
579 "Name of the bone collection to create");
580}
581
583{
585 if (ob == nullptr) {
586 return OPERATOR_CANCELLED;
587 }
588
590 if (bcoll == nullptr) {
591 return OPERATOR_CANCELLED;
592 }
593
594 bool made_any_changes = false;
595 bool had_bones_to_unassign = false;
596 const bool mode_is_supported = bone_collection_assign_mode_specific(
597 C,
598 ob,
599 bcoll,
602 &made_any_changes,
603 &had_bones_to_unassign);
604
605 if (!mode_is_supported) {
607 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
608 return OPERATOR_CANCELLED;
609 }
610 if (!had_bones_to_unassign) {
612 op->reports, RPT_WARNING, "No bones selected, nothing to unassign from bone collection");
613 return OPERATOR_CANCELLED;
614 }
615 if (!made_any_changes) {
617 op->reports, RPT_WARNING, "None of the selected bones were assigned to this collection");
618 return OPERATOR_CANCELLED;
619 }
620 return OPERATOR_FINISHED;
621}
622
624{
625 /* identifiers */
626 ot->name = "Remove Selected from Bone collections";
627 ot->idname = "ARMATURE_OT_collection_unassign";
628 ot->description = "Remove selected bones from the active bone collection";
629
630 /* API callbacks. */
633
634 /* flags */
635 ot->flag = OPTYPE_UNDO;
636
637 RNA_def_string(ot->srna,
638 "name",
639 nullptr,
640 MAX_NAME,
641 "Bone Collection",
642 "Name of the bone collection to unassign this bone from; empty to unassign from "
643 "the active bone collection");
644}
645
647{
649 if (ob == nullptr) {
650 return OPERATOR_CANCELLED;
651 }
652
654 if (bcoll == nullptr) {
655 return OPERATOR_CANCELLED;
656 }
657
658 char bone_name[MAX_NAME];
659 RNA_string_get(op->ptr, "bone_name", bone_name);
660 if (!bone_name[0]) {
661 BKE_reportf(op->reports, RPT_ERROR, "Missing bone name");
662 return OPERATOR_CANCELLED;
663 }
664
665 bool made_any_changes = false;
666 bool had_bones_to_unassign = false;
667 const bool mode_is_supported = bone_collection_assign_named_mode_specific(
668 C,
669 ob,
670 bcoll,
671 bone_name,
674 &made_any_changes,
675 &had_bones_to_unassign);
676
677 if (!mode_is_supported) {
679 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
680 return OPERATOR_CANCELLED;
681 }
682 if (!had_bones_to_unassign) {
683 BKE_reportf(op->reports, RPT_WARNING, "Could not find bone '%s'", bone_name);
684 return OPERATOR_CANCELLED;
685 }
686 if (!made_any_changes) {
689 "Bone '%s' was not assigned to collection '%s'",
690 bone_name,
691 bcoll->name);
692 return OPERATOR_CANCELLED;
693 }
694 return OPERATOR_FINISHED;
695}
696
698{
699 /* identifiers */
700 ot->name = "Remove Bone from Bone Collection";
701 ot->idname = "ARMATURE_OT_collection_unassign_named";
702 ot->description = "Unassign the named bone from this bone collection";
703
704 /* API callbacks. */
707
708 /* flags */
709 ot->flag = OPTYPE_UNDO;
710
711 RNA_def_string(ot->srna,
712 "name",
713 nullptr,
714 MAX_NAME,
715 "Bone Collection",
716 "Name of the bone collection to unassign this bone from; empty to unassign from "
717 "the active bone collection");
718 RNA_def_string(ot->srna,
719 "bone_name",
720 nullptr,
721 MAX_NAME,
722 "Bone Name",
723 "Name of the bone to unassign from the collection; empty to use the active bone");
724}
725
726static bool editbone_is_member(const EditBone *ebone, const BoneCollection *bcoll)
727{
729 if (ref->bcoll == bcoll) {
730 return true;
731 }
732 }
733 return false;
734}
735
737{
739 if (ob && ob->type == OB_ARMATURE) {
740
741 /* For bone selection, at least the pose should be editable to actually store
742 * the selection state. */
743 if (!ID_IS_EDITABLE(ob) && !ID_IS_OVERRIDE_LIBRARY(ob)) {
745 C, "Cannot (de)select bones on linked object, that would need an override");
746 return false;
747 }
748 }
749
750 const bArmature *armature = ED_armature_context(C);
751 if (armature == nullptr) {
752 return false;
753 }
754
755 const bool is_editmode = armature->edbo != nullptr;
756 if (!is_editmode) {
758 if (!active_object || active_object->type != OB_ARMATURE || active_object->data != armature) {
759 /* There has to be an active object in order to hide a pose bone that points to the correct
760 * armature. With pinning, the active object may not be an armature. */
761 CTX_wm_operator_poll_msg_set(C, "The active object does not match the armature");
762 return false;
763 }
764 }
765
766 if (armature->runtime.active_collection == nullptr) {
767 CTX_wm_operator_poll_msg_set(C, "No active bone collection");
768 return false;
769 }
770 return true;
771}
772
774 bArmature *armature,
775 BoneCollection *bcoll,
776 const bool select)
777{
778 const bool is_editmode = armature->edbo != nullptr;
779
780 if (is_editmode) {
781 LISTBASE_FOREACH (EditBone *, ebone, armature->edbo) {
782 if (!EBONE_SELECTABLE(armature, ebone)) {
783 continue;
784 }
785 if (!editbone_is_member(ebone, bcoll)) {
786 continue;
787 }
789 }
790 }
791 else {
793 if (!active_object || active_object->type != OB_ARMATURE || active_object->data != armature) {
794 /* This is covered by the poll function. */
796 return;
797 }
798 LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
799 Bone *bone = member->bone;
800 bPoseChannel *pose_bone = BKE_pose_channel_find_name(active_object->pose, bone->name);
801 BLI_assert_msg(pose_bone != nullptr, "The pose bones and armature bones are out of sync");
802 if (!blender::animrig::bone_is_visible(armature, pose_bone)) {
803 continue;
804 }
805 if (bone->flag & BONE_UNSELECTABLE) {
806 continue;
807 }
808
809 if (select) {
810 pose_bone->flag |= POSE_SELECTED;
811 }
812 else {
813 pose_bone->flag &= ~POSE_SELECTED;
814 }
815 }
816 DEG_id_tag_update(&active_object->id, ID_RECALC_SELECT);
817 }
818
821
822 if (is_editmode) {
824 }
825 else {
827 }
828}
829
831{
832 bArmature *armature = ED_armature_context(C);
833 if (armature == nullptr) {
834 return OPERATOR_CANCELLED;
835 }
836
837 BoneCollection *bcoll = armature->runtime.active_collection;
838 if (bcoll == nullptr) {
839 return OPERATOR_CANCELLED;
840 }
841
842 bone_collection_select(C, armature, bcoll, true);
843 return OPERATOR_FINISHED;
844}
845
847{
848 /* identifiers */
849 ot->name = "Select Bones of Bone Collection";
850 ot->idname = "ARMATURE_OT_collection_select";
851 ot->description = "Select bones in active Bone Collection";
852
853 /* API callbacks. */
856
857 /* flags */
859}
860
862{
863 bArmature *armature = ED_armature_context(C);
864 if (armature == nullptr) {
865 return OPERATOR_CANCELLED;
866 }
867
868 BoneCollection *bcoll = armature->runtime.active_collection;
869 if (bcoll == nullptr) {
870 return OPERATOR_CANCELLED;
871 }
872
873 bone_collection_select(C, armature, bcoll, false);
874 return OPERATOR_FINISHED;
875}
876
878{
879 /* identifiers */
880 ot->name = "Deselect Bone Collection";
881 ot->idname = "ARMATURE_OT_collection_deselect";
882 ot->description = "Deselect bones of active Bone Collection";
883
884 /* API callbacks. */
887
888 /* flags */
890}
891
892/* -------------------------- */
893
895{
896 const int collection_index = RNA_int_get(op->ptr, "collection_index");
897 BoneCollection *target_bcoll;
898
899 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "new_collection_name");
900 if (RNA_property_is_set(op->ptr, prop) ||
901 /* Neither properties can be used, the operator may have been called with defaults.
902 * In this case add a root collection, the default name will be used. */
903 (collection_index < 0))
904 {
905 /* TODO: check this with linked, non-overridden armatures. */
906 char new_collection_name[MAX_NAME];
907 RNA_string_get(op->ptr, "new_collection_name", new_collection_name);
908 target_bcoll = ANIM_armature_bonecoll_new(arm, new_collection_name, collection_index);
909 BLI_assert_msg(target_bcoll,
910 "It should always be possible to create a new bone collection on an armature");
911 ANIM_armature_bonecoll_active_set(arm, target_bcoll);
912 }
913 else {
914 if (collection_index >= arm->collection_array_num) {
916 RPT_ERROR,
917 "Bone collection with index %d not found on Armature %s",
918 collection_index,
919 arm->id.name + 2);
920 return nullptr;
921 }
922 target_bcoll = arm->collection_array[collection_index];
923 }
924
925 if (!ANIM_armature_bonecoll_is_editable(arm, target_bcoll)) {
927 RPT_ERROR,
928 "Bone collection %s is not editable, maybe add an override on the armature Data?",
929 target_bcoll->name);
930 return nullptr;
931 }
932
933 return target_bcoll;
934}
935
937 wmOperator *op,
938 const assign_bone_func assign_func_bone,
939 const assign_ebone_func assign_func_ebone)
940{
942 if (ob->mode == OB_MODE_POSE) {
944 }
945 if (!ob) {
946 BKE_reportf(op->reports, RPT_ERROR, "No object found to operate on");
947 return OPERATOR_CANCELLED;
948 }
949
950 bArmature *arm = static_cast<bArmature *>(ob->data);
951 BoneCollection *target_bcoll = add_or_move_to_collection_bcoll(op, arm);
952 if (!target_bcoll) {
953 /* add_or_move_to_collection_bcoll() already reported the reason. */
954 return OPERATOR_CANCELLED;
955 }
956
957 bool made_any_changes = false;
958 bool had_bones_to_assign = false;
959 const bool mode_is_supported = bone_collection_assign_mode_specific(C,
960 ob,
961 target_bcoll,
962 assign_func_bone,
963 assign_func_ebone,
964 &made_any_changes,
965 &had_bones_to_assign);
966
967 if (!mode_is_supported) {
969 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
970 return OPERATOR_CANCELLED;
971 }
972 if (!had_bones_to_assign) {
974 op->reports, RPT_WARNING, "No bones selected, nothing to assign to bone collection");
975 return OPERATOR_CANCELLED;
976 }
977 if (!made_any_changes) {
979 op->reports, RPT_WARNING, "All selected bones were already part of this collection");
980 return OPERATOR_CANCELLED;
981 }
982
983 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
984
987 return OPERATOR_FINISHED;
988}
989
997
1003
1005{
1007 if (ob == nullptr) {
1008 return false;
1009 }
1010
1011 if (ob->type != OB_ARMATURE) {
1012 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
1013 return false;
1014 }
1015
1016 const bArmature *armature = static_cast<bArmature *>(ob->data);
1017 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
1018 CTX_wm_operator_poll_msg_set(C, "This needs a local Armature or an override");
1019 return false;
1020 }
1021
1022 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
1024 "Cannot update a linked Armature with a system override; "
1025 "explicitly create an override on the Armature Data");
1026 return false;
1027 }
1028
1029 CTX_wm_operator_poll_msg_set(C, "Linked bone collections are not editable");
1030
1031 /* Ideally this would also check the target bone collection to move/assign to.
1032 * However, that requires access to the operator properties, and those are not
1033 * available in the poll function. */
1034 return true;
1035}
1036
1046static void *menu_custom_data_encode(const int bcoll_index, const bool is_move_operation)
1047{
1048 /* Add 1 to the index, so that it's never negative (it can be -1 to indicate 'all roots'). */
1049 const uintptr_t index_and_move_bit = ((bcoll_index + 1) << 1) | (is_move_operation << 0);
1050 return reinterpret_cast<void *>(index_and_move_bit);
1051}
1052
1058static std::pair<int, bool> menu_custom_data_decode(void *menu_custom_data)
1059{
1060 const uintptr_t index_and_move_bit = reinterpret_cast<intptr_t>(menu_custom_data);
1061 const bool is_move_operation = (index_and_move_bit & 1) == 1;
1062 const int bcoll_index = int(index_and_move_bit >> 1) - 1;
1063 return std::make_pair(bcoll_index, is_move_operation);
1064}
1065
1066static int icon_for_bone_collection(const bool collection_contains_active_bone)
1067{
1068 return collection_contains_active_bone ? ICON_REMOVE : ICON_ADD;
1069}
1070
1072 const bArmature *arm,
1073 const BoneCollection *bcoll,
1074 const int bcoll_index,
1075 const bool is_move_operation)
1076{
1077 if (is_move_operation) {
1078 PointerRNA op_ptr = layout->op("ARMATURE_OT_move_to_collection", bcoll->name, ICON_NONE);
1079 RNA_int_set(&op_ptr, "collection_index", bcoll_index);
1080 return;
1081 }
1082
1083 const bool contains_active_bone = ANIM_armature_bonecoll_contains_active_bone(arm, bcoll);
1084 const int icon = icon_for_bone_collection(contains_active_bone);
1085
1086 if (contains_active_bone) {
1087 PointerRNA op_ptr = layout->op("ARMATURE_OT_collection_unassign", bcoll->name, icon);
1088 RNA_string_set(&op_ptr, "name", bcoll->name);
1089 }
1090 else {
1091 PointerRNA op_ptr = layout->op("ARMATURE_OT_collection_assign", bcoll->name, icon);
1092 RNA_string_set(&op_ptr, "name", bcoll->name);
1093 }
1094}
1095
1107static void move_to_collection_menu_create(bContext *C, uiLayout *layout, void *menu_custom_data)
1108{
1109 int parent_bcoll_index;
1110 bool is_move_operation;
1111 std::tie(parent_bcoll_index, is_move_operation) = menu_custom_data_decode(menu_custom_data);
1112
1114 const bArmature *arm = static_cast<bArmature *>(ob->data);
1115
1116 /* The "Create a new collection" mode of this operator has its own menu, and should thus be
1117 * invoked. */
1119 PointerRNA op_ptr = layout->op(
1120 is_move_operation ? "ARMATURE_OT_move_to_collection" : "ARMATURE_OT_assign_to_collection",
1121 CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "New Bone Collection"),
1122 ICON_ADD);
1123 RNA_int_set(&op_ptr, "collection_index", parent_bcoll_index);
1124
1125 layout->separator();
1126
1127 /* The remaining operators in this menu should be executed on click. Invoking
1128 * them would show this same menu again. */
1130
1131 int child_index, child_count;
1132 if (parent_bcoll_index == -1) {
1133 child_index = 0;
1134 child_count = arm->collection_root_count;
1135 }
1136 else {
1137 /* Add a menu item to assign to the parent first, before listing the children.
1138 * The parent is assumed to be editable, because otherwise the menu would
1139 * have been disabled already one recursion level higher. */
1140 const BoneCollection *parent = arm->collection_array[parent_bcoll_index];
1142 layout, arm, parent, parent_bcoll_index, is_move_operation);
1143 layout->separator();
1144
1145 child_index = parent->child_index;
1146 child_count = parent->child_count;
1147 }
1148
1149 /* Loop over the children. There should be at least one, otherwise this parent
1150 * bone collection wouldn't have been drawn as a menu. */
1151 for (int index = child_index; index < child_index + child_count; index++) {
1152 const BoneCollection *bcoll = arm->collection_array[index];
1153
1154 /* Avoid assigning/moving to a linked bone collection. */
1155 if (!ANIM_armature_bonecoll_is_editable(arm, bcoll)) {
1156 uiLayout *sub = &layout->row(false);
1157 sub->enabled_set(false);
1158
1159 menu_add_item_for_move_assign_unassign(sub, arm, bcoll, index, is_move_operation);
1160 continue;
1161 }
1162
1164 layout->menu_fn(bcoll->name,
1165 ICON_NONE,
1167 menu_custom_data_encode(index, is_move_operation));
1168 }
1169 else {
1170 menu_add_item_for_move_assign_unassign(layout, arm, bcoll, index, is_move_operation);
1171 }
1172 }
1173}
1174
1176{
1177 const char *title = CTX_IFACE_(op->type->translation_context, op->type->name);
1178 uiPopupMenu *pup = UI_popup_menu_begin(C, title, ICON_NONE);
1180
1181 const bool is_move_operation = STREQ(op->type->idname, "ARMATURE_OT_move_to_collection");
1183
1184 UI_popup_menu_end(C, pup);
1185
1186 return OPERATOR_INTERFACE;
1187}
1188
1190{
1191 RNA_string_set(op->ptr, "new_collection_name", IFACE_("Bones"));
1193 C, op, 200, IFACE_("Move to New Bone Collection"), IFACE_("Create"));
1194}
1195
1197 wmOperator *op,
1198 const wmEvent * /*event*/)
1199{
1200 /* Invoking with `collection_index` set has a special meaning: show the menu to create a new bone
1201 * collection as the child of this one. */
1202 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index");
1203 if (RNA_property_is_set(op->ptr, prop)) {
1204 return move_to_new_collection_invoke(C, op);
1205 }
1206
1208}
1209
1211{
1212 PropertyRNA *prop;
1213
1214 /* identifiers */
1215 ot->name = "Move to Collection";
1216 ot->description = "Move bones to a collection";
1217 ot->idname = "ARMATURE_OT_move_to_collection";
1218
1219 /* API callbacks. */
1221 ot->invoke = move_to_collection_invoke;
1223
1224 /* Flags don't include OPTYPE_REGISTER, as the redo panel doesn't make much sense for this
1225 * operator. The visibility of the RNA properties is determined by the needs of the 'New Catalog'
1226 * popup, so that a name can be entered. This means that the redo panel would also only show the
1227 * 'Name' property, without any choice for another collection. */
1228 ot->flag = OPTYPE_UNDO;
1229
1230 prop = RNA_def_int(
1231 ot->srna,
1232 "collection_index",
1233 -1,
1234 -1,
1235 INT_MAX,
1236 "Collection Index",
1237 "Index of the collection to move selected bones to. When the operator should create a new "
1238 "bone collection, do not include this parameter and pass new_collection_name",
1239 -1,
1240 INT_MAX);
1242
1243 prop = RNA_def_string(
1244 ot->srna,
1245 "new_collection_name",
1246 nullptr,
1247 MAX_NAME,
1248 "Name",
1249 "Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
1250 "collection and move the selected bones to it. To move to an existing collection, do not "
1251 "include this parameter and use collection_index");
1253 ot->prop = prop;
1254}
1255
1257{
1258 PropertyRNA *prop;
1259
1260 /* identifiers */
1261 ot->name = "Assign to Collection";
1262 ot->description =
1263 "Assign all selected bones to a collection, or unassign them, depending on whether the "
1264 "active bone is already assigned or not";
1265 ot->idname = "ARMATURE_OT_assign_to_collection";
1266
1267 /* API callbacks. */
1269 ot->invoke = move_to_collection_invoke;
1271
1272 /* Flags don't include OPTYPE_REGISTER, as the redo panel doesn't make much sense for this
1273 * operator. The visibility of the RNA properties is determined by the needs of the 'New Catalog'
1274 * popup, so that a name can be entered. This means that the redo panel would also only show the
1275 * 'Name' property, without any choice for another collection. */
1276 ot->flag = OPTYPE_UNDO;
1277
1278 prop = RNA_def_int(
1279 ot->srna,
1280 "collection_index",
1281 -1,
1282 -1,
1283 INT_MAX,
1284 "Collection Index",
1285 "Index of the collection to assign selected bones to. When the operator should create a new "
1286 "bone collection, use new_collection_name to define the collection name, and set this "
1287 "parameter to the parent index of the new bone collection",
1288 -1,
1289 INT_MAX);
1291
1292 prop = RNA_def_string(
1293 ot->srna,
1294 "new_collection_name",
1295 nullptr,
1296 MAX_NAME,
1297 "Name",
1298 "Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
1299 "collection and assign the selected bones to it. To assign to an existing collection, do "
1300 "not include this parameter and use collection_index");
1302 ot->prop = prop;
1303}
1304
1305/* ********************************************** */
Functions to deal with Armatures.
C++ functions to deal with Armature collections (i.e. the successor of bone layers).
bool ANIM_armature_bonecoll_assign_and_move(BoneCollection *bcoll, Bone *bone)
void ANIM_armature_bonecoll_active_set(bArmature *armature, BoneCollection *bcoll)
bool ANIM_armature_bonecoll_is_editable(const bArmature *armature, const BoneCollection *bcoll)
bool ANIM_armature_bonecoll_assign_and_move_editbone(BoneCollection *bcoll, EditBone *ebone)
bool ANIM_armature_bonecoll_unassign_editbone(BoneCollection *bcoll, EditBone *ebone)
bool ANIM_armature_bonecoll_assign(BoneCollection *bcoll, Bone *bone)
void ANIM_armature_bonecoll_remove(bArmature *armature, BoneCollection *bcoll)
bool ANIM_armature_bonecoll_unassign(BoneCollection *bcoll, Bone *bone)
void ANIM_armature_bonecoll_active_runtime_refresh(bArmature *armature)
bool ANIM_armature_bonecoll_move(bArmature *armature, BoneCollection *bcoll, int step)
BoneCollection * ANIM_armature_bonecoll_new(bArmature *armature, const char *name, int parent_index=-1)
bool ANIM_armature_bonecoll_contains_active_bone(const bArmature *armature, const BoneCollection *bcoll)
BoneCollection * ANIM_armature_bonecoll_get_by_name(bArmature *armature, const char *name) ATTR_WARN_UNUSED_RESULT
bool ANIM_armature_bonecoll_assign_editbone(BoneCollection *bcoll, EditBone *ebone)
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
#define FOREACH_PCHAN_SELECTED_IN_OBJECT_END
#define FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN(_ob, _pchan)
@ CTX_MODE_EDIT_ARMATURE
@ CTX_MODE_POSE
void CTX_wm_operator_poll_msg_set(bContext *C, const char *msg)
enum eContextObjectMode CTX_data_mode_enum(const bContext *C)
bool BKE_lib_override_library_is_system_defined(const Main *bmain, const ID *id)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_ERROR
Definition BKE_report.hh:39
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define LISTBASE_FOREACH(type, var, list)
#define STREQ(a, b)
#define CTX_IFACE_(context, msgid)
#define BLT_I18NCONTEXT_OPERATOR_DEFAULT
#define IFACE_(msgid)
void DEG_id_tag_update(ID *id, unsigned int flags)
ID and Library types, which are fundamental for SDNA.
@ ID_RECALC_SELECT
Definition DNA_ID.h:1101
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1118
#define ID_IS_EDITABLE(_id)
Definition DNA_ID.h:705
#define ID_IS_OVERRIDE_LIBRARY(_id)
Definition DNA_ID.h:730
@ POSE_SELECTED
@ BONE_UNSELECTABLE
#define MAX_NAME
Definition DNA_defs.h:50
@ OB_MODE_POSE
Object is a sort of wrapper for general info.
@ OB_ARMATURE
@ OPERATOR_CANCELLED
@ OPERATOR_INTERFACE
@ OPERATOR_FINISHED
#define EBONE_SELECTABLE(arm, ebone)
#define EBONE_EDITABLE(ebone)
void ED_outliner_select_sync_from_edit_bone_tag(bContext *C)
void ED_outliner_select_sync_from_pose_bone_tag(bContext *C)
@ PROP_SKIP_SAVE
Definition RNA_types.hh:344
@ PROP_HIDDEN
Definition RNA_types.hh:338
#define C
Definition RandGen.cpp:29
void UI_popup_menu_end(bContext *C, uiPopupMenu *pup)
uiPopupMenu * UI_popup_menu_begin(bContext *C, const char *title, int icon) ATTR_NONNULL()
uiLayout * UI_popup_menu_layout(uiPopupMenu *pup)
#define ND_DATA
Definition WM_types.hh:509
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define ND_POSE
Definition WM_types.hh:458
#define ND_BONE_COLLECTION
Definition WM_types.hh:474
#define NC_OBJECT
Definition WM_types.hh:379
bArmature * ED_armature_context(const bContext *C)
EditBone * ED_armature_ebone_find_name(const ListBase *edbo, const char *name)
void ED_armature_edit_sync_selection(ListBase *edbo)
void ED_armature_ebone_select_set(EditBone *ebone, bool select)
static bool active_bone_collection_poll(bContext *C)
static wmOperatorStatus move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent *)
void ARMATURE_OT_assign_to_collection(wmOperatorType *ot)
void ARMATURE_OT_collection_create_and_assign(wmOperatorType *ot)
static wmOperatorStatus bone_collection_deselect_exec(bContext *C, wmOperator *)
static bool bone_collection_create_and_assign_poll(bContext *C)
static wmOperatorStatus bone_collection_unassign_named_exec(bContext *C, wmOperator *op)
static wmOperatorStatus bone_collection_create_and_assign_exec(bContext *C, wmOperator *op)
static wmOperatorStatus bone_collection_select_exec(bContext *C, wmOperator *)
static void bone_collection_select(bContext *C, bArmature *armature, BoneCollection *bcoll, const bool select)
static wmOperatorStatus move_to_new_collection_invoke(bContext *C, wmOperator *op)
bool(*)(BoneCollection *bcoll, Bone *bone) assign_bone_func
void ARMATURE_OT_collection_deselect(wmOperatorType *ot)
void ARMATURE_OT_collection_remove(wmOperatorType *ot)
void ARMATURE_OT_collection_unassign_named(wmOperatorType *ot)
static void bone_collection_assign_pchans(bContext *C, Object *ob, BoneCollection *bcoll, assign_bone_func assign_func, bool *made_any_changes, bool *had_bones_to_assign)
static bool bone_collection_assign_named_mode_specific(bContext *C, Object *ob, BoneCollection *bcoll, const char *bone_name, assign_bone_func assign_bone_func, assign_ebone_func assign_ebone_func, bool *made_any_changes, bool *had_bones_to_assign)
static void * menu_custom_data_encode(const int bcoll_index, const bool is_move_operation)
static wmOperatorStatus bone_collection_move_exec(bContext *C, wmOperator *op)
static bool bone_collection_assign_poll(bContext *C)
static BoneCollection * add_or_move_to_collection_bcoll(wmOperator *op, bArmature *arm)
static wmOperatorStatus move_to_collection_exec(bContext *C, wmOperator *op)
static void move_to_collection_menu_create(bContext *C, uiLayout *layout, void *menu_custom_data)
static wmOperatorStatus bone_collection_remove_exec(bContext *C, wmOperator *)
void ARMATURE_OT_collection_assign(wmOperatorType *ot)
void ARMATURE_OT_collection_add(wmOperatorType *ot)
static wmOperatorStatus bone_collection_unassign_exec(bContext *C, wmOperator *op)
void ARMATURE_OT_collection_move(wmOperatorType *ot)
static bool armature_bone_select_poll(bContext *C)
static void menu_add_item_for_move_assign_unassign(uiLayout *layout, const bArmature *arm, const BoneCollection *bcoll, const int bcoll_index, const bool is_move_operation)
static BoneCollection * get_bonecoll_named_or_active(bContext *, wmOperator *op, Object *ob)
static wmOperatorStatus add_or_move_to_collection_exec(bContext *C, wmOperator *op, const assign_bone_func assign_func_bone, const assign_ebone_func assign_func_ebone)
static std::pair< int, bool > menu_custom_data_decode(void *menu_custom_data)
static wmOperatorStatus assign_to_collection_exec(bContext *C, wmOperator *op)
void ARMATURE_OT_move_to_collection(wmOperatorType *ot)
static void bone_collection_assign_editbones(bContext *C, Object *ob, BoneCollection *bcoll, assign_ebone_func assign_func, bool *made_any_changes, bool *had_bones_to_assign)
static int icon_for_bone_collection(const bool collection_contains_active_bone)
static bool editbone_is_member(const EditBone *ebone, const BoneCollection *bcoll)
static bool bone_collection_assign_mode_specific(bContext *C, Object *ob, BoneCollection *bcoll, assign_bone_func assign_bone_func, assign_ebone_func assign_ebone_func, bool *made_any_changes, bool *had_bones_to_assign)
static bool bone_collection_add_poll(bContext *C)
static wmOperatorStatus move_to_collection_regular_invoke(bContext *C, wmOperator *op)
bool(*)(BoneCollection *bcoll, EditBone *ebone) assign_ebone_func
static wmOperatorStatus bone_collection_add_exec(bContext *C, wmOperator *)
static bool move_to_collection_poll(bContext *C)
void ARMATURE_OT_collection_unassign(wmOperatorType *ot)
static wmOperatorStatus bone_collection_assign_exec(bContext *C, wmOperator *op)
void ARMATURE_OT_collection_select(wmOperatorType *ot)
#define select(A, B, C)
bool bone_is_visible(const bArmature *armature, const Bone *bone)
int armature_bonecoll_child_number_find(const bArmature *armature, const ::BoneCollection *bcoll)
bool bonecoll_has_children(const BoneCollection *bcoll)
int armature_bonecoll_find_parent_index(const bArmature *armature, int bcoll_index)
int armature_bonecoll_child_number_set(bArmature *armature, ::BoneCollection *bcoll, int new_child_number)
Object * context_object(const bContext *C)
Object * context_active_object(const bContext *C)
Object * ED_pose_object_from_context(bContext *C)
Definition pose_edit.cc:58
void RNA_string_set(PointerRNA *ptr, const char *name, const char *value)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
bool RNA_property_is_set(PointerRNA *ptr, PropertyRNA *prop)
void RNA_int_set(PointerRNA *ptr, const char *name, int value)
int RNA_int_get(PointerRNA *ptr, const char *name)
std::string RNA_string_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_string(StructOrFunctionRNA *cont_, const char *identifier, const char *default_value, const int maxlen, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
PropertyRNA * RNA_def_int(StructOrFunctionRNA *cont_, const char *identifier, const int default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
char name[64]
ListBase bone_collections
char name[258]
Definition DNA_ID.h:432
struct bPose * pose
struct BoneCollection * active_collection
struct BoneCollection ** collection_array
ListBase * edbo
struct bArmature_Runtime runtime
struct Bone * bone
void operator_context_set(blender::wm::OpCallContext opcontext)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
void enabled_set(bool enabled)
void menu_fn(blender::StringRefNull name, int icon, uiMenuCreateFunc func, void *arg)
uiLayout & row(bool align)
PointerRNA op(wmOperatorType *ot, std::optional< blender::StringRef > name, int icon, blender::wm::OpCallContext context, eUI_Item_Flag flag)
const char * name
Definition WM_types.hh:1033
const char * idname
Definition WM_types.hh:1035
const char * translation_context
Definition WM_types.hh:1037
struct ReportList * reports
struct uiLayout * layout
struct wmOperatorType * type
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:4237
wmOperatorStatus WM_operator_props_dialog_popup(bContext *C, wmOperator *op, int width, std::optional< std::string > title, std::optional< std::string > confirm_text, const bool cancel_default, std::optional< std::string > message)