Blender V4.5
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"
41#include "UI_resources.hh"
42
43#include "armature_intern.hh"
44
45struct wmOperator;
46
47/* ********************************************** */
48/* Bone collections */
49
51{
52 bArmature *armature = ED_armature_context(C);
53 if (armature == nullptr) {
54 return false;
55 }
56
57 if (!ID_IS_EDITABLE(&armature->id)) {
59 "Cannot add bone collections to a linked Armature without an "
60 "override on the Armature Data");
61 return false;
62 }
63
64 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
66 "Cannot add bone collections to a linked Armature with a system "
67 "override; explicitly create an override on the Armature Data");
68 return false;
69 }
70
71 return true;
72}
73
76{
77 bArmature *armature = ED_armature_context(C);
78 if (armature == nullptr) {
79 return false;
80 }
81
82 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
84 "Cannot update a linked Armature with a system override; "
85 "explicitly create an override on the Armature Data");
86 return false;
87 }
88
89 BoneCollection *bcoll = armature->runtime.active_collection;
90 if (bcoll == nullptr) {
91 CTX_wm_operator_poll_msg_set(C, "Armature has no active bone collection, select one first");
92 return false;
93 }
94
95 if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
97 C, "Cannot edit bone collections that are linked from another blend file");
98 return false;
99 }
100 return true;
101}
102
104{
105 using namespace blender::animrig;
106
107 bArmature *armature = ED_armature_context(C);
108
109 /* If there is an active bone collection, create the new one as a sibling. */
110 const int parent_index = armature_bonecoll_find_parent_index(
111 armature, armature->runtime.active_collection_index);
112
113 BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, nullptr, parent_index);
114
115 if (armature->runtime.active_collection) {
116 const int active_child_index = armature_bonecoll_child_number_find(
117 armature, armature->runtime.active_collection);
118 armature_bonecoll_child_number_set(armature, bcoll, active_child_index + 1);
119 }
120
121 ANIM_armature_bonecoll_active_set(armature, bcoll);
122 /* TODO: ensure the ancestors of the new bone collection are all expanded. */
123
125 return OPERATOR_FINISHED;
126}
127
129{
130 /* identifiers */
131 ot->name = "Add Bone Collection";
132 ot->idname = "ARMATURE_OT_collection_add";
133 ot->description = "Add a new bone collection";
134
135 /* API callbacks. */
138
139 /* flags */
141}
142
144{
145 /* The poll function ensures armature->active_collection is not NULL. */
146 bArmature *armature = ED_armature_context(C);
148
149 /* notifiers for updates */
152
153 return OPERATOR_FINISHED;
154}
155
157{
158 /* identifiers */
159 ot->name = "Remove Bone Collection";
160 ot->idname = "ARMATURE_OT_collection_remove";
161 ot->description = "Remove the active bone collection";
162
163 /* API callbacks. */
166
167 /* flags */
169}
170
172{
173 const int direction = RNA_enum_get(op->ptr, "direction");
174
175 /* Poll function makes sure this is valid. */
176 bArmature *armature = ED_armature_context(C);
177
178 const bool ok = ANIM_armature_bonecoll_move(
179 armature, armature->runtime.active_collection, direction);
180 if (!ok) {
181 return OPERATOR_CANCELLED;
182 }
183
185
187 return OPERATOR_FINISHED;
188}
189
191{
192 static const EnumPropertyItem bcoll_slot_move[] = {
193 {-1, "UP", 0, "Up", ""},
194 {1, "DOWN", 0, "Down", ""},
195 {0, nullptr, 0, nullptr, nullptr},
196 };
197
198 /* identifiers */
199 ot->name = "Move Bone Collection";
200 ot->idname = "ARMATURE_OT_collection_move";
201 ot->description = "Change position of active Bone Collection in list of Bone collections";
202
203 /* API callbacks. */
206
207 /* flags */
209
210 RNA_def_enum(ot->srna,
211 "direction",
212 bcoll_slot_move,
213 0,
214 "Direction",
215 "Direction to move the active Bone Collection towards");
216}
217
219{
220 bArmature *armature = static_cast<bArmature *>(ob->data);
221
222 char bcoll_name[MAX_NAME];
223 RNA_string_get(op->ptr, "name", bcoll_name);
224
225 if (bcoll_name[0] == '\0') {
226 return armature->runtime.active_collection;
227 }
228
229 BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(armature, bcoll_name);
230 if (!bcoll) {
231 BKE_reportf(op->reports, RPT_ERROR, "No bone collection named '%s'", bcoll_name);
232 return nullptr;
233 }
234
235 return bcoll;
236}
237
238using assign_bone_func = bool (*)(BoneCollection *bcoll, Bone *bone);
239using assign_ebone_func = bool (*)(BoneCollection *bcoll, EditBone *ebone);
240
241/* The following 3 functions either assign or unassign, depending on the
242 * 'assign_bone_func'/'assign_ebone_func' they get passed. */
243
245 Object *ob,
246 BoneCollection *bcoll,
247 assign_bone_func assign_func,
248 bool *made_any_changes,
249 bool *had_bones_to_assign)
250{
251 /* TODO: support multi-object pose mode. */
253 *made_any_changes |= assign_func(bcoll, pchan->bone);
254 *had_bones_to_assign = true;
255 }
257
259
260 bArmature *arm = static_cast<bArmature *>(ob->data);
261 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
262}
263
265 Object *ob,
266 BoneCollection *bcoll,
267 assign_ebone_func assign_func,
268 bool *made_any_changes,
269 bool *had_bones_to_assign)
270{
271 bArmature *arm = static_cast<bArmature *>(ob->data);
273
274 LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
276 continue;
277 }
278 *made_any_changes |= assign_func(bcoll, ebone);
279 *had_bones_to_assign = true;
280 }
281
285}
286
293 Object *ob,
294 BoneCollection *bcoll,
297 bool *made_any_changes,
298 bool *had_bones_to_assign)
299{
300 switch (CTX_data_mode_enum(C)) {
301 case CTX_MODE_POSE: {
303 C, ob, bcoll, assign_bone_func, made_any_changes, had_bones_to_assign);
304 return true;
305 }
306
309 C, ob, bcoll, assign_ebone_func, made_any_changes, had_bones_to_assign);
310
312 return true;
313 }
314
315 default:
316 return false;
317 }
318}
319
326 Object *ob,
327 BoneCollection *bcoll,
328 const char *bone_name,
331 bool *made_any_changes,
332 bool *had_bones_to_assign)
333{
334 bArmature *arm = static_cast<bArmature *>(ob->data);
335
336 switch (CTX_data_mode_enum(C)) {
337 case CTX_MODE_POSE: {
338 bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
339 if (!pchan) {
340 return true;
341 }
342
343 *had_bones_to_assign = true;
344 *made_any_changes |= assign_bone_func(bcoll, pchan->bone);
345
348 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
349 return true;
350 }
351
353 EditBone *ebone = ED_armature_ebone_find_name(arm->edbo, bone_name);
354 if (!ebone) {
355 return true;
356 }
357
358 *had_bones_to_assign = true;
359 *made_any_changes |= assign_ebone_func(bcoll, ebone);
360
364 return true;
365 }
366
367 default:
368 return false;
369 }
370}
371
373{
375 if (ob == nullptr) {
376 return false;
377 }
378
379 if (ob->type != OB_ARMATURE) {
380 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
381 return false;
382 }
383
384 bArmature *armature = static_cast<bArmature *>(ob->data);
385 if (armature != ED_armature_context(C)) {
386 CTX_wm_operator_poll_msg_set(C, "Pinned armature is not active in the 3D viewport");
387 return false;
388 }
389
390 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
392 C, "Cannot edit bone collections on linked Armatures without override");
393 return false;
394 }
395 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
397 "Cannot edit bone collections on a linked Armature with a system "
398 "override; explicitly create an override on the Armature Data");
399 return false;
400 }
401
402 CTX_wm_operator_poll_msg_set(C, "Linked bone collections are not editable");
403
404 /* The target bone collection can be specified by name in an operator property, but that's not
405 * available here. So just allow in the poll function, and do the final check in the execute. */
406 return true;
407}
408
409/* Assign selected pchans to the bone collection that the user selects */
411{
413 if (ob == nullptr) {
414 return OPERATOR_CANCELLED;
415 }
416
418 if (bcoll == nullptr) {
419 return OPERATOR_CANCELLED;
420 }
421
422 bArmature *armature = static_cast<bArmature *>(ob->data);
423 if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
424 BKE_reportf(op->reports, RPT_ERROR, "Cannot assign to linked bone collection %s", bcoll->name);
425 return OPERATOR_CANCELLED;
426 }
427
428 bool made_any_changes = false;
429 bool had_bones_to_assign = false;
430 const bool mode_is_supported = bone_collection_assign_mode_specific(
431 C,
432 ob,
433 bcoll,
436 &made_any_changes,
437 &had_bones_to_assign);
438
439 if (!mode_is_supported) {
441 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
442 return OPERATOR_CANCELLED;
443 }
444 if (!had_bones_to_assign) {
446 op->reports, RPT_WARNING, "No bones selected, nothing to assign to bone collection");
447 return OPERATOR_CANCELLED;
448 }
449 if (!made_any_changes) {
451 op->reports, RPT_WARNING, "All selected bones were already part of this collection");
452 return OPERATOR_CANCELLED;
453 }
454
456 return OPERATOR_FINISHED;
457}
458
460{
461 /* identifiers */
462 ot->name = "Add Selected Bones to Collection";
463 ot->idname = "ARMATURE_OT_collection_assign";
464 ot->description = "Add selected bones to the chosen bone collection";
465
466 /* API callbacks. */
469
470 /* flags */
471 ot->flag = OPTYPE_UNDO;
472
473 /* properties */
474 RNA_def_string(ot->srna,
475 "name",
476 nullptr,
477 MAX_NAME,
478 "Bone Collection",
479 "Name of the bone collection to assign this bone to; empty to assign to the "
480 "active bone collection");
481}
482
484{
486 if (ob == nullptr) {
487 return false;
488 }
489
490 if (ob->type != OB_ARMATURE) {
491 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
492 return false;
493 }
494
495 bArmature *armature = static_cast<bArmature *>(ob->data);
496 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
498 C, "Cannot edit bone collections on linked Armatures without override");
499 return false;
500 }
501 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
503 "Cannot edit bone collections on a linked Armature with a system "
504 "override; explicitly create an override on the Armature Data");
505 return false;
506 }
507
508 return true;
509}
510
511/* Assign selected pchans to the bone collection that the user selects */
513{
515 if (ob == nullptr) {
516 return OPERATOR_CANCELLED;
517 }
518
519 bArmature *armature = static_cast<bArmature *>(ob->data);
520
521 char bcoll_name[MAX_NAME];
522 RNA_string_get(op->ptr, "name", bcoll_name);
523
524 /* Note that this bone collection can be removed later on, if the assignment part of this
525 * operation failed. */
526 BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, bcoll_name);
527
528 bool made_any_changes = false;
529 bool had_bones_to_assign = false;
530 const bool mode_is_supported = bone_collection_assign_mode_specific(
531 C,
532 ob,
533 bcoll,
536 &made_any_changes,
537 &had_bones_to_assign);
538
539 if (!mode_is_supported) {
541 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
542 ANIM_armature_bonecoll_remove(armature, bcoll);
543 return OPERATOR_CANCELLED;
544 }
545 if (!had_bones_to_assign) {
547 op->reports, RPT_WARNING, "No bones selected, nothing to assign to bone collection");
548 return OPERATOR_FINISHED;
549 }
550 /* Not checking for `made_any_changes`, as if there were any bones to assign, they never could
551 * have already been assigned to this brand new bone collection. */
552
553 ANIM_armature_bonecoll_active_set(armature, bcoll);
555 return OPERATOR_FINISHED;
556}
557
559{
560 /* identifiers */
561 ot->name = "Add Selected Bones to New Collection";
562 ot->idname = "ARMATURE_OT_collection_create_and_assign";
563 ot->description = "Create a new bone collection and assign all selected bones";
564
565 /* API callbacks. */
568
569 /* flags */
571
572 /* properties */
573 RNA_def_string(ot->srna,
574 "name",
575 nullptr,
576 MAX_NAME,
577 "Bone Collection",
578 "Name of the bone collection to create");
579}
580
582{
584 if (ob == nullptr) {
585 return OPERATOR_CANCELLED;
586 }
587
589 if (bcoll == nullptr) {
590 return OPERATOR_CANCELLED;
591 }
592
593 bool made_any_changes = false;
594 bool had_bones_to_unassign = false;
595 const bool mode_is_supported = bone_collection_assign_mode_specific(
596 C,
597 ob,
598 bcoll,
601 &made_any_changes,
602 &had_bones_to_unassign);
603
604 if (!mode_is_supported) {
606 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
607 return OPERATOR_CANCELLED;
608 }
609 if (!had_bones_to_unassign) {
611 op->reports, RPT_WARNING, "No bones selected, nothing to unassign from bone collection");
612 return OPERATOR_CANCELLED;
613 }
614 if (!made_any_changes) {
616 op->reports, RPT_WARNING, "None of the selected bones were assigned to this collection");
617 return OPERATOR_CANCELLED;
618 }
619 return OPERATOR_FINISHED;
620}
621
623{
624 /* identifiers */
625 ot->name = "Remove Selected from Bone collections";
626 ot->idname = "ARMATURE_OT_collection_unassign";
627 ot->description = "Remove selected bones from the active bone collection";
628
629 /* API callbacks. */
632
633 /* flags */
634 ot->flag = OPTYPE_UNDO;
635
636 RNA_def_string(ot->srna,
637 "name",
638 nullptr,
639 MAX_NAME,
640 "Bone Collection",
641 "Name of the bone collection to unassign this bone from; empty to unassign from "
642 "the active bone collection");
643}
644
646{
648 if (ob == nullptr) {
649 return OPERATOR_CANCELLED;
650 }
651
653 if (bcoll == nullptr) {
654 return OPERATOR_CANCELLED;
655 }
656
657 char bone_name[MAX_NAME];
658 RNA_string_get(op->ptr, "bone_name", bone_name);
659 if (!bone_name[0]) {
660 BKE_reportf(op->reports, RPT_ERROR, "Missing bone name");
661 return OPERATOR_CANCELLED;
662 }
663
664 bool made_any_changes = false;
665 bool had_bones_to_unassign = false;
666 const bool mode_is_supported = bone_collection_assign_named_mode_specific(
667 C,
668 ob,
669 bcoll,
670 bone_name,
673 &made_any_changes,
674 &had_bones_to_unassign);
675
676 if (!mode_is_supported) {
678 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
679 return OPERATOR_CANCELLED;
680 }
681 if (!had_bones_to_unassign) {
682 BKE_reportf(op->reports, RPT_WARNING, "Could not find bone '%s'", bone_name);
683 return OPERATOR_CANCELLED;
684 }
685 if (!made_any_changes) {
688 "Bone '%s' was not assigned to collection '%s'",
689 bone_name,
690 bcoll->name);
691 return OPERATOR_CANCELLED;
692 }
693 return OPERATOR_FINISHED;
694}
695
697{
698 /* identifiers */
699 ot->name = "Remove Bone from Bone Collection";
700 ot->idname = "ARMATURE_OT_collection_unassign_named";
701 ot->description = "Unassign the named bone from this bone collection";
702
703 /* API callbacks. */
706
707 /* flags */
708 ot->flag = OPTYPE_UNDO;
709
710 RNA_def_string(ot->srna,
711 "name",
712 nullptr,
713 MAX_NAME,
714 "Bone Collection",
715 "Name of the bone collection to unassign this bone from; empty to unassign from "
716 "the active bone collection");
717 RNA_def_string(ot->srna,
718 "bone_name",
719 nullptr,
720 MAX_NAME,
721 "Bone Name",
722 "Name of the bone to unassign from the collection; empty to use the active bone");
723}
724
725static bool editbone_is_member(const EditBone *ebone, const BoneCollection *bcoll)
726{
728 if (ref->bcoll == bcoll) {
729 return true;
730 }
731 }
732 return false;
733}
734
736{
738 if (ob && ob->type == OB_ARMATURE) {
739
740 /* For bone selection, at least the pose should be editable to actually store
741 * the selection state. */
742 if (!ID_IS_EDITABLE(ob) && !ID_IS_OVERRIDE_LIBRARY(ob)) {
744 C, "Cannot (de)select bones on linked object, that would need an override");
745 return false;
746 }
747 }
748
749 const bArmature *armature = ED_armature_context(C);
750 if (armature == nullptr) {
751 return false;
752 }
753
754 if (armature->runtime.active_collection == nullptr) {
755 CTX_wm_operator_poll_msg_set(C, "No active bone collection");
756 return false;
757 }
758 return true;
759}
760
762 bArmature *armature,
763 BoneCollection *bcoll,
764 const bool select)
765{
766 const bool is_editmode = armature->edbo != nullptr;
767
768 if (is_editmode) {
769 LISTBASE_FOREACH (EditBone *, ebone, armature->edbo) {
770 if (!EBONE_SELECTABLE(armature, ebone)) {
771 continue;
772 }
773 if (!editbone_is_member(ebone, bcoll)) {
774 continue;
775 }
777 }
778 }
779 else {
780 LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
781 Bone *bone = member->bone;
782 if (!blender::animrig::bone_is_visible(armature, bone)) {
783 continue;
784 }
785 if (bone->flag & BONE_UNSELECTABLE) {
786 continue;
787 }
788
789 if (select) {
790 bone->flag |= BONE_SELECTED;
791 }
792 else {
793 bone->flag &= ~BONE_SELECTED;
794 }
795 }
796 }
797
800
801 if (is_editmode) {
803 }
804 else {
806 }
807}
808
810{
811 bArmature *armature = ED_armature_context(C);
812 if (armature == nullptr) {
813 return OPERATOR_CANCELLED;
814 }
815
816 BoneCollection *bcoll = armature->runtime.active_collection;
817 if (bcoll == nullptr) {
818 return OPERATOR_CANCELLED;
819 }
820
821 bone_collection_select(C, armature, bcoll, true);
822 return OPERATOR_FINISHED;
823}
824
826{
827 /* identifiers */
828 ot->name = "Select Bones of Bone Collection";
829 ot->idname = "ARMATURE_OT_collection_select";
830 ot->description = "Select bones in active Bone Collection";
831
832 /* API callbacks. */
835
836 /* flags */
838}
839
841{
842 bArmature *armature = ED_armature_context(C);
843 if (armature == nullptr) {
844 return OPERATOR_CANCELLED;
845 }
846
847 BoneCollection *bcoll = armature->runtime.active_collection;
848 if (bcoll == nullptr) {
849 return OPERATOR_CANCELLED;
850 }
851
852 bone_collection_select(C, armature, bcoll, false);
853 return OPERATOR_FINISHED;
854}
855
857{
858 /* identifiers */
859 ot->name = "Deselect Bone Collection";
860 ot->idname = "ARMATURE_OT_collection_deselect";
861 ot->description = "Deselect bones of active Bone Collection";
862
863 /* API callbacks. */
866
867 /* flags */
869}
870
871/* -------------------------- */
872
874{
875 const int collection_index = RNA_int_get(op->ptr, "collection_index");
876 BoneCollection *target_bcoll;
877
878 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "new_collection_name");
879 if (RNA_property_is_set(op->ptr, prop) ||
880 /* Neither properties can be used, the operator may have been called with defaults.
881 * In this case add a root collection, the default name will be used. */
882 (collection_index < 0))
883 {
884 /* TODO: check this with linked, non-overridden armatures. */
885 char new_collection_name[MAX_NAME];
886 RNA_string_get(op->ptr, "new_collection_name", new_collection_name);
887 target_bcoll = ANIM_armature_bonecoll_new(arm, new_collection_name, collection_index);
888 BLI_assert_msg(target_bcoll,
889 "It should always be possible to create a new bone collection on an armature");
890 ANIM_armature_bonecoll_active_set(arm, target_bcoll);
891 }
892 else {
893 if (collection_index >= arm->collection_array_num) {
895 RPT_ERROR,
896 "Bone collection with index %d not found on Armature %s",
897 collection_index,
898 arm->id.name + 2);
899 return nullptr;
900 }
901 target_bcoll = arm->collection_array[collection_index];
902 }
903
904 if (!ANIM_armature_bonecoll_is_editable(arm, target_bcoll)) {
906 RPT_ERROR,
907 "Bone collection %s is not editable, maybe add an override on the armature Data?",
908 target_bcoll->name);
909 return nullptr;
910 }
911
912 return target_bcoll;
913}
914
916 wmOperator *op,
917 const assign_bone_func assign_func_bone,
918 const assign_ebone_func assign_func_ebone)
919{
921 if (ob->mode == OB_MODE_POSE) {
923 }
924 if (!ob) {
925 BKE_reportf(op->reports, RPT_ERROR, "No object found to operate on");
926 return OPERATOR_CANCELLED;
927 }
928
929 bArmature *arm = static_cast<bArmature *>(ob->data);
930 BoneCollection *target_bcoll = add_or_move_to_collection_bcoll(op, arm);
931 if (!target_bcoll) {
932 /* add_or_move_to_collection_bcoll() already reported the reason. */
933 return OPERATOR_CANCELLED;
934 }
935
936 bool made_any_changes = false;
937 bool had_bones_to_assign = false;
938 const bool mode_is_supported = bone_collection_assign_mode_specific(C,
939 ob,
940 target_bcoll,
941 assign_func_bone,
942 assign_func_ebone,
943 &made_any_changes,
944 &had_bones_to_assign);
945
946 if (!mode_is_supported) {
948 op->reports, RPT_ERROR, "This operator only works in pose mode and armature edit mode");
949 return OPERATOR_CANCELLED;
950 }
951 if (!had_bones_to_assign) {
953 op->reports, RPT_WARNING, "No bones selected, nothing to assign to bone collection");
954 return OPERATOR_CANCELLED;
955 }
956 if (!made_any_changes) {
958 op->reports, RPT_WARNING, "All selected bones were already part of this collection");
959 return OPERATOR_CANCELLED;
960 }
961
962 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
963
966 return OPERATOR_FINISHED;
967}
968
976
982
984{
986 if (ob == nullptr) {
987 return false;
988 }
989
990 if (ob->type != OB_ARMATURE) {
991 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
992 return false;
993 }
994
995 const bArmature *armature = static_cast<bArmature *>(ob->data);
996 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
997 CTX_wm_operator_poll_msg_set(C, "This needs a local Armature or an override");
998 return false;
999 }
1000
1001 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
1003 "Cannot update a linked Armature with a system override; "
1004 "explicitly create an override on the Armature Data");
1005 return false;
1006 }
1007
1008 CTX_wm_operator_poll_msg_set(C, "Linked bone collections are not editable");
1009
1010 /* Ideally this would also check the target bone collection to move/assign to.
1011 * However, that requires access to the operator properties, and those are not
1012 * available in the poll function. */
1013 return true;
1014}
1015
1025static void *menu_custom_data_encode(const int bcoll_index, const bool is_move_operation)
1026{
1027 /* Add 1 to the index, so that it's never negative (it can be -1 to indicate 'all roots'). */
1028 const uintptr_t index_and_move_bit = ((bcoll_index + 1) << 1) | (is_move_operation << 0);
1029 return reinterpret_cast<void *>(index_and_move_bit);
1030}
1031
1037static std::pair<int, bool> menu_custom_data_decode(void *menu_custom_data)
1038{
1039 const uintptr_t index_and_move_bit = reinterpret_cast<intptr_t>(menu_custom_data);
1040 const bool is_move_operation = (index_and_move_bit & 1) == 1;
1041 const int bcoll_index = int(index_and_move_bit >> 1) - 1;
1042 return std::make_pair(bcoll_index, is_move_operation);
1043}
1044
1045static int icon_for_bone_collection(const bool collection_contains_active_bone)
1046{
1047 return collection_contains_active_bone ? ICON_REMOVE : ICON_ADD;
1048}
1049
1051 const bArmature *arm,
1052 const BoneCollection *bcoll,
1053 const int bcoll_index,
1054 const bool is_move_operation)
1055{
1056 if (is_move_operation) {
1057 PointerRNA op_ptr = layout->op("ARMATURE_OT_move_to_collection", bcoll->name, ICON_NONE);
1058 RNA_int_set(&op_ptr, "collection_index", bcoll_index);
1059 return;
1060 }
1061
1062 const bool contains_active_bone = ANIM_armature_bonecoll_contains_active_bone(arm, bcoll);
1063 const int icon = icon_for_bone_collection(contains_active_bone);
1064
1065 if (contains_active_bone) {
1066 PointerRNA op_ptr = layout->op("ARMATURE_OT_collection_unassign", bcoll->name, icon);
1067 RNA_string_set(&op_ptr, "name", bcoll->name);
1068 }
1069 else {
1070 PointerRNA op_ptr = layout->op("ARMATURE_OT_collection_assign", bcoll->name, icon);
1071 RNA_string_set(&op_ptr, "name", bcoll->name);
1072 }
1073}
1074
1086static void move_to_collection_menu_create(bContext *C, uiLayout *layout, void *menu_custom_data)
1087{
1088 int parent_bcoll_index;
1089 bool is_move_operation;
1090 std::tie(parent_bcoll_index, is_move_operation) = menu_custom_data_decode(menu_custom_data);
1091
1093 const bArmature *arm = static_cast<bArmature *>(ob->data);
1094
1095 /* The "Create a new collection" mode of this operator has its own menu, and should thus be
1096 * invoked. */
1098 PointerRNA op_ptr = layout->op(
1099 is_move_operation ? "ARMATURE_OT_move_to_collection" : "ARMATURE_OT_assign_to_collection",
1100 CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "New Bone Collection"),
1101 ICON_ADD);
1102 RNA_int_set(&op_ptr, "collection_index", parent_bcoll_index);
1103
1104 layout->separator();
1105
1106 /* The remaining operators in this menu should be executed on click. Invoking
1107 * them would show this same menu again. */
1109
1110 int child_index, child_count;
1111 if (parent_bcoll_index == -1) {
1112 child_index = 0;
1113 child_count = arm->collection_root_count;
1114 }
1115 else {
1116 /* Add a menu item to assign to the parent first, before listing the children.
1117 * The parent is assumed to be editable, because otherwise the menu would
1118 * have been disabled already one recursion level higher. */
1119 const BoneCollection *parent = arm->collection_array[parent_bcoll_index];
1121 layout, arm, parent, parent_bcoll_index, is_move_operation);
1122 layout->separator();
1123
1124 child_index = parent->child_index;
1125 child_count = parent->child_count;
1126 }
1127
1128 /* Loop over the children. There should be at least one, otherwise this parent
1129 * bone collection wouldn't have been drawn as a menu. */
1130 for (int index = child_index; index < child_index + child_count; index++) {
1131 const BoneCollection *bcoll = arm->collection_array[index];
1132
1133 /* Avoid assigning/moving to a linked bone collection. */
1134 if (!ANIM_armature_bonecoll_is_editable(arm, bcoll)) {
1135 uiLayout *sub = &layout->row(false);
1136 uiLayoutSetEnabled(sub, false);
1137
1138 menu_add_item_for_move_assign_unassign(sub, arm, bcoll, index, is_move_operation);
1139 continue;
1140 }
1141
1143 layout->menu_fn(bcoll->name,
1144 ICON_NONE,
1146 menu_custom_data_encode(index, is_move_operation));
1147 }
1148 else {
1149 menu_add_item_for_move_assign_unassign(layout, arm, bcoll, index, is_move_operation);
1150 }
1151 }
1152}
1153
1155{
1156 const char *title = CTX_IFACE_(op->type->translation_context, op->type->name);
1157 uiPopupMenu *pup = UI_popup_menu_begin(C, title, ICON_NONE);
1159
1160 const bool is_move_operation = STREQ(op->type->idname, "ARMATURE_OT_move_to_collection");
1162
1163 UI_popup_menu_end(C, pup);
1164
1165 return OPERATOR_INTERFACE;
1166}
1167
1169{
1170 RNA_string_set(op->ptr, "new_collection_name", IFACE_("Bones"));
1172 C, op, 200, IFACE_("Move to New Bone Collection"), IFACE_("Create"));
1173}
1174
1176 wmOperator *op,
1177 const wmEvent * /*event*/)
1178{
1179 /* Invoking with `collection_index` set has a special meaning: show the menu to create a new bone
1180 * collection as the child of this one. */
1181 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index");
1182 if (RNA_property_is_set(op->ptr, prop)) {
1183 return move_to_new_collection_invoke(C, op);
1184 }
1185
1187}
1188
1190{
1191 PropertyRNA *prop;
1192
1193 /* identifiers */
1194 ot->name = "Move to Collection";
1195 ot->description = "Move bones to a collection";
1196 ot->idname = "ARMATURE_OT_move_to_collection";
1197
1198 /* API callbacks. */
1200 ot->invoke = move_to_collection_invoke;
1202
1203 /* Flags don't include OPTYPE_REGISTER, as the redo panel doesn't make much sense for this
1204 * operator. The visibility of the RNA properties is determined by the needs of the 'New Catalog'
1205 * popup, so that a name can be entered. This means that the redo panel would also only show the
1206 * 'Name' property, without any choice for another collection. */
1207 ot->flag = OPTYPE_UNDO;
1208
1209 prop = RNA_def_int(
1210 ot->srna,
1211 "collection_index",
1212 -1,
1213 -1,
1214 INT_MAX,
1215 "Collection Index",
1216 "Index of the collection to move selected bones to. When the operator should create a new "
1217 "bone collection, do not include this parameter and pass new_collection_name",
1218 -1,
1219 INT_MAX);
1221
1222 prop = RNA_def_string(
1223 ot->srna,
1224 "new_collection_name",
1225 nullptr,
1226 MAX_NAME,
1227 "Name",
1228 "Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
1229 "collection and move the selected bones to it. To move to an existing collection, do not "
1230 "include this parameter and use collection_index");
1232 ot->prop = prop;
1233}
1234
1236{
1237 PropertyRNA *prop;
1238
1239 /* identifiers */
1240 ot->name = "Assign to Collection";
1241 ot->description =
1242 "Assign all selected bones to a collection, or unassign them, depending on whether the "
1243 "active bone is already assigned or not";
1244 ot->idname = "ARMATURE_OT_assign_to_collection";
1245
1246 /* API callbacks. */
1248 ot->invoke = move_to_collection_invoke;
1250
1251 /* Flags don't include OPTYPE_REGISTER, as the redo panel doesn't make much sense for this
1252 * operator. The visibility of the RNA properties is determined by the needs of the 'New Catalog'
1253 * popup, so that a name can be entered. This means that the redo panel would also only show the
1254 * 'Name' property, without any choice for another collection. */
1255 ot->flag = OPTYPE_UNDO;
1256
1257 prop = RNA_def_int(
1258 ot->srna,
1259 "collection_index",
1260 -1,
1261 -1,
1262 INT_MAX,
1263 "Collection Index",
1264 "Index of the collection to assign selected bones to. When the operator should create a new "
1265 "bone collection, use new_collection_name to define the collection name, and set this "
1266 "parameter to the parent index of the new bone collection",
1267 -1,
1268 INT_MAX);
1270
1271 prop = RNA_def_string(
1272 ot->srna,
1273 "new_collection_name",
1274 nullptr,
1275 MAX_NAME,
1276 "Name",
1277 "Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
1278 "collection and assign the selected bones to it. To assign to an existing collection, do "
1279 "not include this parameter and use collection_index");
1281 ot->prop = prop;
1282}
1283
1284/* ********************************************** */
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
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
#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:1009
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1026
@ BONE_SELECTED
@ BONE_UNSELECTABLE
@ 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:330
@ PROP_HIDDEN
Definition RNA_types.hh:324
#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)
void uiLayoutSetEnabled(uiLayout *layout, bool enabled)
void uiLayoutSetOperatorContext(uiLayout *layout, wmOperatorCallContext opcontext)
#define ND_DATA
Definition WM_types.hh:506
#define ND_POSE
Definition WM_types.hh:455
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:238
@ WM_OP_EXEC_DEFAULT
Definition WM_types.hh:245
#define ND_BONE_COLLECTION
Definition WM_types.hh:471
#define NC_OBJECT
Definition WM_types.hh:376
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
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)
#define ID_IS_EDITABLE(_id)
#define MAX_NAME
#define ID_IS_OVERRIDE_LIBRARY(_id)
bool bone_is_visible_editbone(const bArmature *armature, const EditBone *ebone)
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)
void RNA_string_get(PointerRNA *ptr, const char *name, char *value)
int RNA_int_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)
ListBase bone_collections
char name[66]
Definition DNA_ID.h:415
struct bPose * pose
struct BoneCollection * active_collection
struct BoneCollection ** collection_array
ListBase * edbo
struct bArmature_Runtime runtime
struct Bone * bone
PointerRNA op(wmOperatorType *ot, std::optional< blender::StringRef > name, int icon, wmOperatorCallContext context, eUI_Item_Flag flag)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
void menu_fn(blender::StringRefNull name, int icon, uiMenuCreateFunc func, void *arg)
uiLayout & row(bool align)
const char * name
Definition WM_types.hh:1030
const char * idname
Definition WM_types.hh:1032
const char * translation_context
Definition WM_types.hh:1034
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:4226
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)