Blender V4.3
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
10#include <cstring>
11
13
14#include "DNA_ID.h"
15#include "DNA_object_types.h"
16
17#include "BKE_action.hh"
18#include "BKE_context.hh"
19#include "BKE_lib_override.hh"
20#include "BKE_report.hh"
21
22#include "BLT_translation.hh"
23
24#include "DEG_depsgraph.hh"
25
26#include "RNA_access.hh"
27#include "RNA_define.hh"
28
29#include "WM_api.hh"
30#include "WM_types.hh"
31
32#include "ED_armature.hh"
33#include "ED_object.hh"
34#include "ED_outliner.hh"
35
36#include "UI_interface.hh"
37#include "UI_resources.hh"
38
39#include "armature_intern.hh"
40
41struct wmOperator;
42
43/* ********************************************** */
44/* Bone collections */
45
47{
48 bArmature *armature = ED_armature_context(C);
49 if (armature == nullptr) {
50 return false;
51 }
52
53 if (!ID_IS_EDITABLE(&armature->id)) {
55 "Cannot add bone collections to a linked Armature without an "
56 "override on the Armature Data");
57 return false;
58 }
59
60 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
62 "Cannot add bone collections to a linked Armature with a system "
63 "override; explicitly create an override on the Armature Data");
64 return false;
65 }
66
67 return true;
68}
69
72{
73 bArmature *armature = ED_armature_context(C);
74 if (armature == nullptr) {
75 return false;
76 }
77
78 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
80 "Cannot update a linked Armature with a system override; "
81 "explicitly create an override on the Armature Data");
82 return false;
83 }
84
85 BoneCollection *bcoll = armature->runtime.active_collection;
86 if (bcoll == nullptr) {
87 CTX_wm_operator_poll_msg_set(C, "Armature has no active bone collection, select one first");
88 return false;
89 }
90
91 if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
93 C, "Cannot edit bone collections that are linked from another blend file");
94 return false;
95 }
96 return true;
97}
98
100{
101 using namespace blender::animrig;
102
103 bArmature *armature = ED_armature_context(C);
104
105 /* If there is an active bone collection, create the new one as a sibling. */
106 const int parent_index = armature_bonecoll_find_parent_index(
107 armature, armature->runtime.active_collection_index);
108
109 BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, nullptr, parent_index);
110
111 if (armature->runtime.active_collection) {
112 const int active_child_index = armature_bonecoll_child_number_find(
113 armature, armature->runtime.active_collection);
114 armature_bonecoll_child_number_set(armature, bcoll, active_child_index + 1);
115 }
116
117 ANIM_armature_bonecoll_active_set(armature, bcoll);
118 /* TODO: ensure the ancestors of the new bone collection are all expanded. */
119
121 return OPERATOR_FINISHED;
122}
123
125{
126 /* identifiers */
127 ot->name = "Add Bone Collection";
128 ot->idname = "ARMATURE_OT_collection_add";
129 ot->description = "Add a new bone collection";
130
131 /* api callbacks */
134
135 /* flags */
137}
138
140{
141 /* The poll function ensures armature->active_collection is not NULL. */
142 bArmature *armature = ED_armature_context(C);
144
145 /* notifiers for updates */
148
149 return OPERATOR_FINISHED;
150}
151
153{
154 /* identifiers */
155 ot->name = "Remove Bone Collection";
156 ot->idname = "ARMATURE_OT_collection_remove";
157 ot->description = "Remove the active bone collection";
158
159 /* api callbacks */
162
163 /* flags */
165}
166
168{
169 const int direction = RNA_enum_get(op->ptr, "direction");
170
171 /* Poll function makes sure this is valid. */
172 bArmature *armature = ED_armature_context(C);
173
174 const bool ok = ANIM_armature_bonecoll_move(
175 armature, armature->runtime.active_collection, direction);
176 if (!ok) {
177 return OPERATOR_CANCELLED;
178 }
179
181
183 return OPERATOR_FINISHED;
184}
185
187{
188 static const EnumPropertyItem bcoll_slot_move[] = {
189 {-1, "UP", 0, "Up", ""},
190 {1, "DOWN", 0, "Down", ""},
191 {0, nullptr, 0, nullptr, nullptr},
192 };
193
194 /* identifiers */
195 ot->name = "Move Bone Collection";
196 ot->idname = "ARMATURE_OT_collection_move";
197 ot->description = "Change position of active Bone Collection in list of Bone collections";
198
199 /* api callbacks */
202
203 /* flags */
205
207 "direction",
208 bcoll_slot_move,
209 0,
210 "Direction",
211 "Direction to move the active Bone Collection towards");
212}
213
215{
216 bArmature *armature = static_cast<bArmature *>(ob->data);
217
218 char bcoll_name[MAX_NAME];
219 RNA_string_get(op->ptr, "name", bcoll_name);
220
221 if (bcoll_name[0] == '\0') {
222 return armature->runtime.active_collection;
223 }
224
225 BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(armature, bcoll_name);
226 if (!bcoll) {
227 WM_reportf(RPT_ERROR, "No bone collection named '%s'", bcoll_name);
228 return nullptr;
229 }
230
231 return bcoll;
232}
233
234using assign_bone_func = bool (*)(BoneCollection *bcoll, Bone *bone);
235using assign_ebone_func = bool (*)(BoneCollection *bcoll, EditBone *ebone);
236
237/* The following 3 functions either assign or unassign, depending on the
238 * 'assign_bone_func'/'assign_ebone_func' they get passed. */
239
241 Object *ob,
242 BoneCollection *bcoll,
243 assign_bone_func assign_func,
244 bool *made_any_changes,
245 bool *had_bones_to_assign)
246{
247 /* TODO: support multi-object pose mode. */
249 *made_any_changes |= assign_func(bcoll, pchan->bone);
250 *had_bones_to_assign = true;
251 }
253
255
256 bArmature *arm = static_cast<bArmature *>(ob->data);
257 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
258}
259
261 Object *ob,
262 BoneCollection *bcoll,
263 assign_ebone_func assign_func,
264 bool *made_any_changes,
265 bool *had_bones_to_assign)
266{
267 bArmature *arm = static_cast<bArmature *>(ob->data);
269
270 LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
271 if (!EBONE_EDITABLE(ebone)) {
272 continue;
273 }
274 *made_any_changes |= assign_func(bcoll, ebone);
275 *had_bones_to_assign = true;
276 }
277
281}
282
289 Object *ob,
290 BoneCollection *bcoll,
293 bool *made_any_changes,
294 bool *had_bones_to_assign)
295{
296 switch (CTX_data_mode_enum(C)) {
297 case CTX_MODE_POSE: {
299 C, ob, bcoll, assign_bone_func, made_any_changes, had_bones_to_assign);
300 return true;
301 }
302
305 C, ob, bcoll, assign_ebone_func, made_any_changes, had_bones_to_assign);
306
308 return true;
309 }
310
311 default:
312 return false;
313 }
314}
315
322 Object *ob,
323 BoneCollection *bcoll,
324 const char *bone_name,
327 bool *made_any_changes,
328 bool *had_bones_to_assign)
329{
330 bArmature *arm = static_cast<bArmature *>(ob->data);
331
332 switch (CTX_data_mode_enum(C)) {
333 case CTX_MODE_POSE: {
334 bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
335 if (!pchan) {
336 return true;
337 }
338
339 *had_bones_to_assign = true;
340 *made_any_changes |= assign_bone_func(bcoll, pchan->bone);
341
344 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
345 return true;
346 }
347
349 EditBone *ebone = ED_armature_ebone_find_name(arm->edbo, bone_name);
350 if (!ebone) {
351 return true;
352 }
353
354 *had_bones_to_assign = true;
355 *made_any_changes |= assign_ebone_func(bcoll, ebone);
356
360 return true;
361 }
362
363 default:
364 return false;
365 }
366}
367
369{
371 if (ob == nullptr) {
372 return false;
373 }
374
375 if (ob->type != OB_ARMATURE) {
376 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
377 return false;
378 }
379
380 bArmature *armature = static_cast<bArmature *>(ob->data);
381 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
383 C, "Cannot edit bone collections on linked Armatures without override");
384 return false;
385 }
386 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
388 "Cannot edit bone collections on a linked Armature with a system "
389 "override; explicitly create an override on the Armature Data");
390 return false;
391 }
392
393 CTX_wm_operator_poll_msg_set(C, "Linked bone collections are not editable");
394
395 /* The target bone collection can be specified by name in an operator property, but that's not
396 * available here. So just allow in the poll function, and do the final check in the execute. */
397 return true;
398}
399
400/* Assign selected pchans to the bone collection that the user selects */
402{
404 if (ob == nullptr) {
405 return OPERATOR_CANCELLED;
406 }
407
409 if (bcoll == nullptr) {
410 return OPERATOR_CANCELLED;
411 }
412
413 bArmature *armature = static_cast<bArmature *>(ob->data);
414 if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
415 WM_reportf(RPT_ERROR, "Cannot assign to linked bone collection %s", bcoll->name);
416 return OPERATOR_CANCELLED;
417 }
418
419 bool made_any_changes = false;
420 bool had_bones_to_assign = false;
421 const bool mode_is_supported = bone_collection_assign_mode_specific(
422 C,
423 ob,
424 bcoll,
427 &made_any_changes,
428 &had_bones_to_assign);
429
430 if (!mode_is_supported) {
431 WM_report(RPT_ERROR, "This operator only works in pose mode and armature edit mode");
432 return OPERATOR_CANCELLED;
433 }
434 if (!had_bones_to_assign) {
435 WM_report(RPT_WARNING, "No bones selected, nothing to assign to bone collection");
436 return OPERATOR_CANCELLED;
437 }
438 if (!made_any_changes) {
439 WM_report(RPT_WARNING, "All selected bones were already part of this collection");
440 return OPERATOR_CANCELLED;
441 }
442
444 return OPERATOR_FINISHED;
445}
446
448{
449 /* identifiers */
450 ot->name = "Add Selected Bones to Collection";
451 ot->idname = "ARMATURE_OT_collection_assign";
452 ot->description = "Add selected bones to the chosen bone collection";
453
454 /* api callbacks */
457
458 /* flags */
460
461 /* properties */
463 "name",
464 nullptr,
465 MAX_NAME,
466 "Bone Collection",
467 "Name of the bone collection to assign this bone to; empty to assign to the "
468 "active bone collection");
469}
470
472{
474 if (ob == nullptr) {
475 return false;
476 }
477
478 if (ob->type != OB_ARMATURE) {
479 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
480 return false;
481 }
482
483 bArmature *armature = static_cast<bArmature *>(ob->data);
484 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
486 C, "Cannot edit bone collections on linked Armatures without override");
487 return false;
488 }
489 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
491 "Cannot edit bone collections on a linked Armature with a system "
492 "override; explicitly create an override on the Armature Data");
493 return false;
494 }
495
496 return true;
497}
498
499/* Assign selected pchans to the bone collection that the user selects */
501{
503 if (ob == nullptr) {
504 return OPERATOR_CANCELLED;
505 }
506
507 bArmature *armature = static_cast<bArmature *>(ob->data);
508
509 char bcoll_name[MAX_NAME];
510 RNA_string_get(op->ptr, "name", bcoll_name);
511
512 /* Note that this bone collection can be removed later on, if the assignment part of this
513 * operation failed. */
514 BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, bcoll_name);
515
516 bool made_any_changes = false;
517 bool had_bones_to_assign = false;
518 const bool mode_is_supported = bone_collection_assign_mode_specific(
519 C,
520 ob,
521 bcoll,
524 &made_any_changes,
525 &had_bones_to_assign);
526
527 if (!mode_is_supported) {
528 WM_report(RPT_ERROR, "This operator only works in pose mode and armature edit mode");
529 ANIM_armature_bonecoll_remove(armature, bcoll);
530 return OPERATOR_CANCELLED;
531 }
532 if (!had_bones_to_assign) {
533 WM_report(RPT_WARNING, "No bones selected, nothing to assign to bone collection");
534 return OPERATOR_FINISHED;
535 }
536 /* Not checking for `made_any_changes`, as if there were any bones to assign, they never could
537 * have already been assigned to this brand new bone collection. */
538
539 ANIM_armature_bonecoll_active_set(armature, bcoll);
541 return OPERATOR_FINISHED;
542}
543
545{
546 /* identifiers */
547 ot->name = "Add Selected Bones to New Collection";
548 ot->idname = "ARMATURE_OT_collection_create_and_assign";
549 ot->description = "Create a new bone collection and assign all selected bones";
550
551 /* api callbacks */
554
555 /* flags */
557
558 /* properties */
560 "name",
561 nullptr,
562 MAX_NAME,
563 "Bone Collection",
564 "Name of the bone collection to create");
565}
566
568{
570 if (ob == nullptr) {
571 return OPERATOR_CANCELLED;
572 }
573
575 if (bcoll == nullptr) {
576 return OPERATOR_CANCELLED;
577 }
578
579 bool made_any_changes = false;
580 bool had_bones_to_unassign = false;
581 const bool mode_is_supported = bone_collection_assign_mode_specific(
582 C,
583 ob,
584 bcoll,
587 &made_any_changes,
588 &had_bones_to_unassign);
589
590 if (!mode_is_supported) {
591 WM_report(RPT_ERROR, "This operator only works in pose mode and armature edit mode");
592 return OPERATOR_CANCELLED;
593 }
594 if (!had_bones_to_unassign) {
595 WM_report(RPT_WARNING, "No bones selected, nothing to unassign from bone collection");
596 return OPERATOR_CANCELLED;
597 }
598 if (!made_any_changes) {
599 WM_report(RPT_WARNING, "None of the selected bones were assigned to this collection");
600 return OPERATOR_CANCELLED;
601 }
602 return OPERATOR_FINISHED;
603}
604
606{
607 /* identifiers */
608 ot->name = "Remove Selected from Bone collections";
609 ot->idname = "ARMATURE_OT_collection_unassign";
610 ot->description = "Remove selected bones from the active bone collection";
611
612 /* api callbacks */
615
616 /* flags */
618
620 "name",
621 nullptr,
622 MAX_NAME,
623 "Bone Collection",
624 "Name of the bone collection to unassign this bone from; empty to unassign from "
625 "the active bone collection");
626}
627
629{
631 if (ob == nullptr) {
632 return OPERATOR_CANCELLED;
633 }
634
636 if (bcoll == nullptr) {
637 return OPERATOR_CANCELLED;
638 }
639
640 char bone_name[MAX_NAME];
641 RNA_string_get(op->ptr, "bone_name", bone_name);
642 if (!bone_name[0]) {
643 WM_report(RPT_ERROR, "Missing bone name");
644 return OPERATOR_CANCELLED;
645 }
646
647 bool made_any_changes = false;
648 bool had_bones_to_unassign = false;
649 const bool mode_is_supported = bone_collection_assign_named_mode_specific(
650 C,
651 ob,
652 bcoll,
653 bone_name,
656 &made_any_changes,
657 &had_bones_to_unassign);
658
659 if (!mode_is_supported) {
660 WM_report(RPT_ERROR, "This operator only works in pose mode and armature edit mode");
661 return OPERATOR_CANCELLED;
662 }
663 if (!had_bones_to_unassign) {
664 WM_reportf(RPT_WARNING, "Could not find bone '%s'", bone_name);
665 return OPERATOR_CANCELLED;
666 }
667 if (!made_any_changes) {
669 RPT_WARNING, "Bone '%s' was not assigned to collection '%s'", bone_name, bcoll->name);
670 return OPERATOR_CANCELLED;
671 }
672 return OPERATOR_FINISHED;
673}
674
676{
677 /* identifiers */
678 ot->name = "Remove Bone from Bone Collection";
679 ot->idname = "ARMATURE_OT_collection_unassign_named";
680 ot->description = "Unassign the named bone from this bone collection";
681
682 /* api callbacks */
685
686 /* flags */
688
690 "name",
691 nullptr,
692 MAX_NAME,
693 "Bone Collection",
694 "Name of the bone collection to unassign this bone from; empty to unassign from "
695 "the active bone collection");
697 "bone_name",
698 nullptr,
699 MAX_NAME,
700 "Bone Name",
701 "Name of the bone to unassign from the collection; empty to use the active bone");
702}
703
704static bool editbone_is_member(const EditBone *ebone, const BoneCollection *bcoll)
705{
707 if (ref->bcoll == bcoll) {
708 return true;
709 }
710 }
711 return false;
712}
713
715{
717 if (ob == nullptr || ob->type != OB_ARMATURE) {
718 return false;
719 }
720
721 /* For bone selection, at least the pose should be editable to actually store
722 * the selection state. */
723 if (!ID_IS_EDITABLE(ob) && !ID_IS_OVERRIDE_LIBRARY(ob)) {
725 C, "Cannot (de)select bones on linked object, that would need an override");
726 return false;
727 }
728
729 const bArmature *armature = reinterpret_cast<bArmature *>(ob->data);
730 if (armature->runtime.active_collection == nullptr) {
731 CTX_wm_operator_poll_msg_set(C, "No active bone collection");
732 return false;
733 }
734 return true;
735}
736
738 Object *ob,
739 BoneCollection *bcoll,
740 const bool select)
741{
742 bArmature *armature = static_cast<bArmature *>(ob->data);
743 const bool is_editmode = armature->edbo != nullptr;
744
745 if (is_editmode) {
746 LISTBASE_FOREACH (EditBone *, ebone, armature->edbo) {
747 if (!EBONE_SELECTABLE(armature, ebone)) {
748 continue;
749 }
750 if (!editbone_is_member(ebone, bcoll)) {
751 continue;
752 }
754 }
755 }
756 else {
757 LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
758 Bone *bone = member->bone;
759 if (!ANIM_bone_is_visible(armature, bone)) {
760 continue;
761 }
762 if (bone->flag & BONE_UNSELECTABLE) {
763 continue;
764 }
765
766 if (select) {
767 bone->flag |= BONE_SELECTED;
768 }
769 else {
770 bone->flag &= ~BONE_SELECTED;
771 }
772 }
773 }
774
777
778 if (is_editmode) {
780 }
781 else {
783 }
784}
785
787{
789 if (ob == nullptr) {
790 return OPERATOR_CANCELLED;
791 }
792
793 bArmature *armature = reinterpret_cast<bArmature *>(ob->data);
794 BoneCollection *bcoll = armature->runtime.active_collection;
795 if (bcoll == nullptr) {
796 return OPERATOR_CANCELLED;
797 }
798
799 bone_collection_select(C, ob, bcoll, true);
800 return OPERATOR_FINISHED;
801}
802
804{
805 /* identifiers */
806 ot->name = "Select Bones of Bone Collection";
807 ot->idname = "ARMATURE_OT_collection_select";
808 ot->description = "Select bones in active Bone Collection";
809
810 /* api callbacks */
813
814 /* flags */
816}
817
819{
821 if (ob == nullptr) {
822 return OPERATOR_CANCELLED;
823 }
824
825 bArmature *armature = reinterpret_cast<bArmature *>(ob->data);
826 BoneCollection *bcoll = armature->runtime.active_collection;
827 if (bcoll == nullptr) {
828 return OPERATOR_CANCELLED;
829 }
830
831 bone_collection_select(C, ob, bcoll, false);
832 return OPERATOR_FINISHED;
833}
834
836{
837 /* identifiers */
838 ot->name = "Deselect Bone Collection";
839 ot->idname = "ARMATURE_OT_collection_deselect";
840 ot->description = "Deselect bones of active Bone Collection";
841
842 /* api callbacks */
845
846 /* flags */
848}
849
850/* -------------------------- */
851
853{
854 const int collection_index = RNA_int_get(op->ptr, "collection_index");
855 BoneCollection *target_bcoll;
856
857 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "new_collection_name");
858 if (RNA_property_is_set(op->ptr, prop) ||
859 /* Neither properties can be used, the operator may have been called with defaults.
860 * In this case add a root collection, the default name will be used. */
861 (collection_index < 0))
862 {
863 /* TODO: check this with linked, non-overridden armatures. */
864 char new_collection_name[MAX_NAME];
865 RNA_string_get(op->ptr, "new_collection_name", new_collection_name);
866 target_bcoll = ANIM_armature_bonecoll_new(arm, new_collection_name, collection_index);
867 BLI_assert_msg(target_bcoll,
868 "It should always be possible to create a new bone collection on an armature");
869 ANIM_armature_bonecoll_active_set(arm, target_bcoll);
870 }
871 else {
872 if (collection_index >= arm->collection_array_num) {
874 RPT_ERROR,
875 "Bone collection with index %d not found on Armature %s",
876 collection_index,
877 arm->id.name + 2);
878 return nullptr;
879 }
880 target_bcoll = arm->collection_array[collection_index];
881 }
882
883 if (!ANIM_armature_bonecoll_is_editable(arm, target_bcoll)) {
885 RPT_ERROR,
886 "Bone collection %s is not editable, maybe add an override on the armature Data?",
887 target_bcoll->name);
888 return nullptr;
889 }
890
891 return target_bcoll;
892}
893
895 wmOperator *op,
896 const assign_bone_func assign_func_bone,
897 const assign_ebone_func assign_func_ebone)
898{
900 if (ob->mode == OB_MODE_POSE) {
902 }
903 if (!ob) {
904 BKE_reportf(op->reports, RPT_ERROR, "No object found to operate on");
905 return OPERATOR_CANCELLED;
906 }
907
908 bArmature *arm = static_cast<bArmature *>(ob->data);
909 BoneCollection *target_bcoll = add_or_move_to_collection_bcoll(op, arm);
910 if (!target_bcoll) {
911 /* add_or_move_to_collection_bcoll() already reported the reason. */
912 return OPERATOR_CANCELLED;
913 }
914
915 bool made_any_changes = false;
916 bool had_bones_to_assign = false;
917 const bool mode_is_supported = bone_collection_assign_mode_specific(C,
918 ob,
919 target_bcoll,
920 assign_func_bone,
921 assign_func_ebone,
922 &made_any_changes,
923 &had_bones_to_assign);
924
925 if (!mode_is_supported) {
926 WM_report(RPT_ERROR, "This operator only works in pose mode and armature edit mode");
927 return OPERATOR_CANCELLED;
928 }
929 if (!had_bones_to_assign) {
930 WM_report(RPT_WARNING, "No bones selected, nothing to assign to bone collection");
931 return OPERATOR_CANCELLED;
932 }
933 if (!made_any_changes) {
934 WM_report(RPT_WARNING, "All selected bones were already part of this collection");
935 return OPERATOR_CANCELLED;
936 }
937
938 DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
939
942 return OPERATOR_FINISHED;
943}
944
952
958
960{
962 if (ob == nullptr) {
963 return false;
964 }
965
966 if (ob->type != OB_ARMATURE) {
967 CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
968 return false;
969 }
970
971 const bArmature *armature = static_cast<bArmature *>(ob->data);
972 if (!ID_IS_EDITABLE(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
973 CTX_wm_operator_poll_msg_set(C, "This needs a local Armature or an override");
974 return false;
975 }
976
977 if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
979 "Cannot update a linked Armature with a system override; "
980 "explicitly create an override on the Armature Data");
981 return false;
982 }
983
984 CTX_wm_operator_poll_msg_set(C, "Linked bone collections are not editable");
985
986 /* Ideally this would also check the target bone collection to move/assign to.
987 * However, that requires access to the operator properties, and those are not
988 * available in the poll function. */
989 return true;
990}
991
1001static void *menu_custom_data_encode(const int bcoll_index, const bool is_move_operation)
1002{
1003 /* Add 1 to the index, so that it's never negative (it can be -1 to indicate 'all roots'). */
1004 const uintptr_t index_and_move_bit = ((bcoll_index + 1) << 1) | (is_move_operation << 0);
1005 return reinterpret_cast<void *>(index_and_move_bit);
1006}
1007
1013static std::pair<int, bool> menu_custom_data_decode(void *menu_custom_data)
1014{
1015 const uintptr_t index_and_move_bit = reinterpret_cast<intptr_t>(menu_custom_data);
1016 const bool is_move_operation = (index_and_move_bit & 1) == 1;
1017 const int bcoll_index = int(index_and_move_bit >> 1) - 1;
1018 return std::make_pair(bcoll_index, is_move_operation);
1019}
1020
1021static int icon_for_bone_collection(const bool collection_contains_active_bone)
1022{
1023 return collection_contains_active_bone ? ICON_REMOVE : ICON_ADD;
1024}
1025
1027 const bArmature *arm,
1028 const BoneCollection *bcoll,
1029 const int bcoll_index,
1030 const bool is_move_operation)
1031{
1032 if (is_move_operation) {
1034 bcoll->name,
1035 ICON_NONE,
1036 "ARMATURE_OT_move_to_collection",
1037 "collection_index",
1038 bcoll_index);
1039 return;
1040 }
1041
1042 const bool contains_active_bone = ANIM_armature_bonecoll_contains_active_bone(arm, bcoll);
1043 const int icon = icon_for_bone_collection(contains_active_bone);
1044
1045 if (contains_active_bone) {
1047 layout, bcoll->name, icon, "ARMATURE_OT_collection_unassign", "name", bcoll->name);
1048 }
1049 else {
1050 uiItemStringO(layout, bcoll->name, icon, "ARMATURE_OT_collection_assign", "name", bcoll->name);
1051 }
1052}
1053
1064static void move_to_collection_menu_create(bContext *C, uiLayout *layout, void *menu_custom_data)
1065{
1066 int parent_bcoll_index;
1067 bool is_move_operation;
1068 std::tie(parent_bcoll_index, is_move_operation) = menu_custom_data_decode(menu_custom_data);
1069
1071 const bArmature *arm = static_cast<bArmature *>(ob->data);
1072
1073 /* The "Create a new collection" mode of this operator has its own menu, and should thus be
1074 * invoked. */
1077 "New Bone Collection",
1078 ICON_ADD,
1079 is_move_operation ? "ARMATURE_OT_move_to_collection" :
1080 "ARMATURE_OT_assign_to_collection",
1081 "collection_index",
1082 parent_bcoll_index);
1083
1084 uiItemS(layout);
1085
1086 /* The remaining operators in this menu should be executed on click. Invoking
1087 * them would show this same menu again. */
1089
1090 int child_index, child_count;
1091 if (parent_bcoll_index == -1) {
1092 child_index = 0;
1093 child_count = arm->collection_root_count;
1094 }
1095 else {
1096 /* Add a menu item to assign to the parent first, before listing the children.
1097 * The parent is assumed to be editable, because otherwise the menu would
1098 * have been disabled already one recursion level higher. */
1099 const BoneCollection *parent = arm->collection_array[parent_bcoll_index];
1101 layout, arm, parent, parent_bcoll_index, is_move_operation);
1102 uiItemS(layout);
1103
1104 child_index = parent->child_index;
1105 child_count = parent->child_count;
1106 }
1107
1108 /* Loop over the children. There should be at least one, otherwise this parent
1109 * bone collection wouldn't have been drawn as a menu. */
1110 for (int index = child_index; index < child_index + child_count; index++) {
1111 const BoneCollection *bcoll = arm->collection_array[index];
1112
1113 /* Avoid assigning/moving to a linked bone collection. */
1114 if (!ANIM_armature_bonecoll_is_editable(arm, bcoll)) {
1115 uiLayout *sub = uiLayoutRow(layout, false);
1116 uiLayoutSetEnabled(sub, false);
1117
1118 menu_add_item_for_move_assign_unassign(sub, arm, bcoll, index, is_move_operation);
1119 continue;
1120 }
1121
1124 bcoll->name,
1125 ICON_NONE,
1127 menu_custom_data_encode(index, is_move_operation));
1128 }
1129 else {
1130 menu_add_item_for_move_assign_unassign(layout, arm, bcoll, index, is_move_operation);
1131 }
1132 }
1133}
1134
1136{
1137 const char *title = CTX_IFACE_(op->type->translation_context, op->type->name);
1138 uiPopupMenu *pup = UI_popup_menu_begin(C, title, ICON_NONE);
1140
1141 const bool is_move_operation = STREQ(op->type->idname, "ARMATURE_OT_move_to_collection");
1143
1144 UI_popup_menu_end(C, pup);
1145
1146 return OPERATOR_INTERFACE;
1147}
1148
1150{
1151 RNA_string_set(op->ptr, "new_collection_name", IFACE_("Bones"));
1153 C, op, 200, IFACE_("Move to New Bone Collection"), IFACE_("Create"));
1154}
1155
1156static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
1157{
1158 /* Invoking with `collection_index` set has a special meaning: show the menu to create a new bone
1159 * collection as the child of this one. */
1160 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index");
1161 if (RNA_property_is_set(op->ptr, prop)) {
1162 return move_to_new_collection_invoke(C, op);
1163 }
1164
1166}
1167
1169{
1170 PropertyRNA *prop;
1171
1172 /* identifiers */
1173 ot->name = "Move to Collection";
1174 ot->description = "Move bones to a collection";
1175 ot->idname = "ARMATURE_OT_move_to_collection";
1176
1177 /* api callbacks */
1181
1182 /* Flags don't include OPTYPE_REGISTER, as the redo panel doesn't make much sense for this
1183 * operator. The visibility of the RNA properties is determined by the needs of the 'New Catalog'
1184 * popup, so that a name can be entered. This means that the redo panel would also only show the
1185 * 'Name' property, without any choice for another collection. */
1186 ot->flag = OPTYPE_UNDO;
1187
1188 prop = RNA_def_int(
1189 ot->srna,
1190 "collection_index",
1191 -1,
1192 -1,
1193 INT_MAX,
1194 "Collection Index",
1195 "Index of the collection to move selected bones to. When the operator should create a new "
1196 "bone collection, do not include this parameter and pass new_collection_name",
1197 -1,
1198 INT_MAX);
1200
1201 prop = RNA_def_string(
1202 ot->srna,
1203 "new_collection_name",
1204 nullptr,
1205 MAX_NAME,
1206 "Name",
1207 "Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
1208 "collection and move the selected bones to it. To move to an existing collection, do not "
1209 "include this parameter and use collection_index");
1211 ot->prop = prop;
1212}
1213
1215{
1216 PropertyRNA *prop;
1217
1218 /* identifiers */
1219 ot->name = "Assign to Collection";
1220 ot->description =
1221 "Assign all selected bones to a collection, or unassign them, depending on whether the "
1222 "active bone is already assigned or not";
1223 ot->idname = "ARMATURE_OT_assign_to_collection";
1224
1225 /* api callbacks */
1229
1230 /* Flags don't include OPTYPE_REGISTER, as the redo panel doesn't make much sense for this
1231 * operator. The visibility of the RNA properties is determined by the needs of the 'New Catalog'
1232 * popup, so that a name can be entered. This means that the redo panel would also only show the
1233 * 'Name' property, without any choice for another collection. */
1234 ot->flag = OPTYPE_UNDO;
1235
1236 prop = RNA_def_int(
1237 ot->srna,
1238 "collection_index",
1239 -1,
1240 -1,
1241 INT_MAX,
1242 "Collection Index",
1243 "Index of the collection to assign selected bones to. When the operator should create a new "
1244 "bone collection, use new_collection_name to define the collection name, and set this "
1245 "parameter to the parent index of the new bone collection",
1246 -1,
1247 INT_MAX);
1249
1250 prop = RNA_def_string(
1251 ot->srna,
1252 "new_collection_name",
1253 nullptr,
1254 MAX_NAME,
1255 "Name",
1256 "Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
1257 "collection and assign the selected bones to it. To assign to an existing collection, do "
1258 "not include this parameter and use collection_index");
1260 ot->prop = prop;
1261}
1262
1263/* ********************************************** */
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_bone_is_visible(const bArmature *armature, const Bone *bone)
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
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#define LISTBASE_FOREACH(type, var, list)
#define STREQ(a, b)
#define CTX_IFACE_(context, msgid)
#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:1068
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1085
#define ID_IS_EDITABLE(_id)
Definition DNA_ID.h:658
#define ID_IS_OVERRIDE_LIBRARY(_id)
Definition DNA_ID.h:683
@ BONE_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
#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:245
@ PROP_HIDDEN
Definition RNA_types.hh:239
void uiLayoutSetEnabled(uiLayout *layout, bool enabled)
void uiItemIntO(uiLayout *layout, const char *name, int icon, const char *opname, const char *propname, int value)
uiLayout * uiLayoutRow(uiLayout *layout, bool align)
void UI_popup_menu_end(bContext *C, uiPopupMenu *pup)
void uiItemS(uiLayout *layout)
uiPopupMenu * UI_popup_menu_begin(bContext *C, const char *title, int icon) ATTR_NONNULL()
uiLayout * UI_popup_menu_layout(uiPopupMenu *pup)
void uiItemStringO(uiLayout *layout, const char *name, int icon, const char *opname, const char *propname, const char *value)
void uiItemMenuF(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc func, void *arg)
void uiLayoutSetOperatorContext(uiLayout *layout, wmOperatorCallContext opcontext)
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
#define ND_DATA
Definition WM_types.hh:475
#define ND_POSE
Definition WM_types.hh:425
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:218
@ WM_OP_EXEC_DEFAULT
Definition WM_types.hh:225
#define ND_BONE_COLLECTION
Definition WM_types.hh:441
#define NC_OBJECT
Definition WM_types.hh:346
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 int bone_collection_select_exec(bContext *C, wmOperator *)
static void bone_collection_select(bContext *C, Object *ob, BoneCollection *bcoll, const bool select)
static bool active_bone_collection_poll(bContext *C)
void ARMATURE_OT_assign_to_collection(wmOperatorType *ot)
void ARMATURE_OT_collection_create_and_assign(wmOperatorType *ot)
static bool bone_collection_create_and_assign_poll(bContext *C)
static int bone_collection_remove_exec(bContext *C, wmOperator *)
static int bone_collection_create_and_assign_exec(bContext *C, wmOperator *op)
static int move_to_collection_regular_invoke(bContext *C, wmOperator *op)
static int bone_collection_deselect_exec(bContext *C, wmOperator *)
static int bone_collection_move_exec(bContext *C, wmOperator *op)
bool(*)(BoneCollection *bcoll, Bone *bone) assign_bone_func
static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent *)
void ARMATURE_OT_collection_deselect(wmOperatorType *ot)
void ARMATURE_OT_collection_remove(wmOperatorType *ot)
void ARMATURE_OT_collection_unassign_named(wmOperatorType *ot)
static int 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 int bone_collection_unassign_named_exec(bContext *C, wmOperator *op)
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 int bone_collection_add_exec(bContext *C, wmOperator *)
static bool bone_collection_assign_poll(bContext *C)
static BoneCollection * add_or_move_to_collection_bcoll(wmOperator *op, bArmature *arm)
static int move_to_collection_exec(bContext *C, wmOperator *op)
static void move_to_collection_menu_create(bContext *C, uiLayout *layout, void *menu_custom_data)
void ARMATURE_OT_collection_assign(wmOperatorType *ot)
void ARMATURE_OT_collection_add(wmOperatorType *ot)
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 std::pair< int, bool > menu_custom_data_decode(void *menu_custom_data)
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 int move_to_new_collection_invoke(bContext *C, wmOperator *op)
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 int bone_collection_unassign_exec(bContext *C, wmOperator *op)
static bool bone_collection_add_poll(bContext *C)
static int assign_to_collection_exec(bContext *C, wmOperator *op)
bool(*)(BoneCollection *bcoll, EditBone *ebone) assign_ebone_func
static bool move_to_collection_poll(bContext *C)
static int bone_collection_assign_exec(bContext *C, wmOperator *op)
void ARMATURE_OT_collection_unassign(wmOperatorType *ot)
void ARMATURE_OT_collection_select(wmOperatorType *ot)
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
ccl_device_inline float4 select(const int4 mask, const float4 a, const float4 b)
bool bonecoll_has_children(const BoneCollection *bcoll)
Object * context_object(const bContext *C)
Object * ED_pose_object_from_context(bContext *C)
Definition pose_edit.cc:59
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_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)
_W64 unsigned int uintptr_t
Definition stdint.h:119
_W64 int intptr_t
Definition stdint.h:118
ListBase bone_collections
char name[66]
Definition DNA_ID.h:425
struct bPose * pose
struct BoneCollection * active_collection
struct BoneCollection ** collection_array
ListBase * edbo
struct bArmature_Runtime runtime
struct Bone * bone
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 * translation_context
Definition WM_types.hh:994
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 uiLayout * layout
struct wmOperatorType * type
struct PointerRNA * ptr
void WM_report(eReportType type, const char *message)
void WM_main_add_notifier(uint type, void *reference)
void WM_reportf(eReportType type, const char *format,...)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
wmOperatorType * ot
Definition wm_files.cc:4125
int 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)