Blender V4.3
anim_channels_edit.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2009 Blender Authors, Joshua Leung. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include <cstdio>
10#include <cstdlib>
11#include <cstring>
12
13#include "MEM_guardedalloc.h"
14
15#include "BLI_blenlib.h"
16#include "BLI_listbase.h"
17#include "BLI_span.hh"
18#include "BLI_utildefines.h"
19
20#include "DNA_anim_types.h"
22#include "DNA_key_types.h"
23#include "DNA_mask_types.h"
24#include "DNA_object_types.h"
25#include "DNA_scene_types.h"
26
27#include "RNA_access.hh"
28#include "RNA_define.hh"
29#include "RNA_path.hh"
30
31#include "BKE_action.hh"
32#include "BKE_anim_data.hh"
33#include "BKE_context.hh"
34#include "BKE_fcurve.hh"
35#include "BKE_global.hh"
36#include "BKE_gpencil_legacy.h"
37#include "BKE_grease_pencil.hh"
38#include "BKE_layer.hh"
39#include "BKE_lib_id.hh"
40#include "BKE_mask.h"
41#include "BKE_nla.hh"
42#include "BKE_scene.hh"
43#include "BKE_screen.hh"
44#include "BKE_workspace.hh"
45
46#include "ANIM_action.hh"
47#include "ANIM_action_legacy.hh"
48
49#include "DEG_depsgraph.hh"
51
52#include "UI_interface.hh"
53#include "UI_view2d.hh"
54
55#include "ED_armature.hh"
56#include "ED_keyframes_edit.hh" /* XXX move the select modes out of there! */
57#include "ED_markers.hh"
58#include "ED_object.hh"
59#include "ED_screen.hh"
60#include "ED_select_utils.hh"
61
62#include "ANIM_animdata.hh"
63#include "ANIM_fcurve.hh"
64
65#include "WM_api.hh"
66#include "WM_message.hh"
67#include "WM_types.hh"
68
69#include "BLT_translation.hh"
70
71/* -------------------------------------------------------------------- */
76 AnimData *anim_data,
77 SpaceLink *space_link,
78 Scene *scene,
79 ID *id,
80 const bool include_handles,
81 const float range[2],
82 rctf *r_bounds)
83{
84 const bool fcu_selection_only = false;
85 const bool found_bounds = BKE_fcurve_calc_bounds(
86 fcu, fcu_selection_only, include_handles, range, r_bounds);
87
88 if (!found_bounds) {
89 return false;
90 }
91
92 const short mapping_flag = ANIM_get_normalization_flags(space_link);
93
94 float offset;
95 const float unit_fac = ANIM_unit_mapping_get_factor(scene, id, fcu, mapping_flag, &offset);
96
97 r_bounds->ymin = (r_bounds->ymin + offset) * unit_fac;
98 r_bounds->ymax = (r_bounds->ymax + offset) * unit_fac;
99
100 const float min_height = 0.01f;
101 const float height = BLI_rctf_size_y(r_bounds);
102 if (height < min_height) {
103 r_bounds->ymin -= (min_height - height) / 2;
104 r_bounds->ymax += (min_height - height) / 2;
105 }
106 r_bounds->xmin = BKE_nla_tweakedit_remap(anim_data, r_bounds->xmin, NLATIME_CONVERT_MAP);
107 r_bounds->xmax = BKE_nla_tweakedit_remap(anim_data, r_bounds->xmax, NLATIME_CONVERT_MAP);
108
109 return true;
110}
111
112static bool get_gpencil_bounds(bGPDlayer *gpl, const float range[2], rctf *r_bounds)
113{
114 bool found_start = false;
115 int start_frame = 0;
116 int end_frame = 1;
117 LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
118 if (gpf->framenum < range[0]) {
119 continue;
120 }
121 if (gpf->framenum > range[1]) {
122 break;
123 }
124 if (!found_start) {
125 start_frame = gpf->framenum;
126 found_start = true;
127 }
128 end_frame = gpf->framenum;
129 }
130 r_bounds->xmin = start_frame;
131 r_bounds->xmax = end_frame;
132 r_bounds->ymin = 0;
133 r_bounds->ymax = 1;
134
135 return found_start;
136}
137
139 const float range[2],
140 rctf *r_bounds)
141{
142 using namespace blender::bke::greasepencil;
143 const Layer &layer = gplayer->wrap();
144
145 bool found_start = false;
146 int start_frame = 0;
147 int end_frame = 1;
148
149 for (const FramesMapKeyT key : layer.sorted_keys()) {
150 if (key < range[0]) {
151 continue;
152 }
153 if (key > range[1]) {
154 break;
155 }
156
157 if (!found_start) {
158 start_frame = key;
159 found_start = true;
160 }
161 end_frame = key;
162 }
163 r_bounds->xmin = start_frame;
164 r_bounds->xmax = end_frame;
165 r_bounds->ymin = 0;
166 r_bounds->ymax = 1;
167
168 return found_start;
169}
170
172 bAnimListElem *ale,
173 const float range[2],
174 const bool include_handles,
175 rctf *r_bounds)
176{
177 bool found_bounds = false;
178 switch (ale->datatype) {
179 case ALE_GPFRAME: {
180 bGPDlayer *gpl = (bGPDlayer *)ale->data;
181 found_bounds = get_gpencil_bounds(gpl, range, r_bounds);
182 break;
183 }
185 found_bounds = get_grease_pencil_layer_bounds(
186 static_cast<const GreasePencilLayer *>(ale->data), range, r_bounds);
187 break;
188
189 case ALE_FCURVE: {
190 FCurve *fcu = (FCurve *)ale->key_data;
191 AnimData *anim_data = ANIM_nla_mapping_get(ac, ale);
192 found_bounds = get_normalized_fcurve_bounds(
193 fcu, anim_data, ac->sl, ac->scene, ale->id, include_handles, range, r_bounds);
194 break;
195 }
196 case ALE_NONE:
197 case ALE_MASKLAY:
198 case ALE_NLASTRIP:
199 case ALE_ALL:
200 case ALE_SCE:
201 case ALE_OB:
202 case ALE_ACT:
203 case ALE_GROUP:
205 case ALE_ACTION_SLOT:
208 return false;
209 }
210 return found_bounds;
211}
212
213/* Pad the given rctf with regions that could block the view.
214 * For example Markers and Time Scrubbing. */
216{
217 BLI_rctf_scale(bounds, 1.1f);
218
219 const float pad_top = UI_TIME_SCRUB_MARGIN_Y;
220 const float pad_bottom = BLI_listbase_is_empty(ED_context_get_markers(C)) ?
223 BLI_rctf_pad_y(bounds, region->winy, pad_bottom, pad_top);
224}
225
228/* -------------------------------------------------------------------- */
233 void *data,
234 eAnimCont_Types datatype,
235 eAnimFilter_Flags filter,
236 void *channel_data,
237 eAnim_ChannelType channel_type)
238{
239 /* TODO: extend for animdata types. */
240
241 ListBase anim_data = {nullptr, nullptr};
242 bAnimListElem *ale;
243
244 /* try to build list of filtered items */
245 ANIM_animdata_filter(ac, &anim_data, filter, data, datatype);
246 if (BLI_listbase_is_empty(&anim_data)) {
247 return;
248 }
249
250 /* only clear the 'active' flag for the channels of the same type */
251 for (ale = static_cast<bAnimListElem *>(anim_data.first); ale; ale = ale->next) {
252 /* skip if types don't match */
253 if (channel_type != ale->type) {
254 continue;
255 }
256
257 /* flag to set depends on type */
258 switch (ale->type) {
259 case ANIMTYPE_GROUP: {
260 bActionGroup *agrp = (bActionGroup *)ale->data;
261
263 break;
264 }
265 case ANIMTYPE_FCURVE:
266 case ANIMTYPE_NLACURVE: {
267 FCurve *fcu = (FCurve *)ale->data;
268
270 break;
271 }
272 case ANIMTYPE_NLATRACK: {
273 NlaTrack *nlt = (NlaTrack *)ale->data;
274
276 break;
277 }
278 case ANIMTYPE_FILLACTD: /* Action Expander */
279 case ANIMTYPE_FILLACT_LAYERED: /* Animation Expander */
280 case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
281 case ANIMTYPE_DSLAM:
282 case ANIMTYPE_DSCAM:
284 case ANIMTYPE_DSCUR:
285 case ANIMTYPE_DSSKEY:
286 case ANIMTYPE_DSWOR:
287 case ANIMTYPE_DSPART:
288 case ANIMTYPE_DSMBALL:
289 case ANIMTYPE_DSARM:
290 case ANIMTYPE_DSMESH:
291 case ANIMTYPE_DSTEX:
292 case ANIMTYPE_DSLAT:
294 case ANIMTYPE_DSSPK:
296 case ANIMTYPE_DSMCLIP:
297 case ANIMTYPE_DSHAIR:
300 case ANIMTYPE_NLAACTION: {
301 /* need to verify that this data is valid for now */
302 if (ale->adt) {
304 }
305 break;
306 }
307 case ANIMTYPE_GPLAYER: {
308 bGPDlayer *gpl = (bGPDlayer *)ale->data;
309
311 break;
312 }
313 case ANIMTYPE_NONE:
316 case ANIMTYPE_SUMMARY:
317 case ANIMTYPE_SCENE:
318 case ANIMTYPE_OBJECT:
322 case ANIMTYPE_DSNTREE:
330 case ANIMTYPE_PALETTE:
332 break;
333 }
334 }
335
336 /* set active flag */
337 if (channel_data) {
338 switch (channel_type) {
339 case ANIMTYPE_GROUP: {
340 bActionGroup *agrp = (bActionGroup *)channel_data;
341 agrp->flag |= AGRP_ACTIVE;
342 break;
343 }
344 case ANIMTYPE_FCURVE:
345 case ANIMTYPE_NLACURVE: {
346 FCurve *fcu = (FCurve *)channel_data;
347 fcu->flag |= FCURVE_ACTIVE;
348 break;
349 }
350 case ANIMTYPE_NLATRACK: {
351 NlaTrack *nlt = (NlaTrack *)channel_data;
352 nlt->flag |= NLATRACK_ACTIVE;
353 break;
354 }
356 /* ANIMTYPE_ACTION_SLOT is not supported by this function (because the to-be-activated
357 * bAnimListElement is not passed here, only sub-fields of it), just call
358 * Action::slot_active_set() directly. */
359 break;
360 case ANIMTYPE_FILLACTD: /* Action Expander */
361 case ANIMTYPE_FILLACT_LAYERED: /* Animation Expander */
362 case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
363 case ANIMTYPE_DSLAM:
364 case ANIMTYPE_DSCAM:
366 case ANIMTYPE_DSCUR:
367 case ANIMTYPE_DSSKEY:
368 case ANIMTYPE_DSWOR:
369 case ANIMTYPE_DSPART:
370 case ANIMTYPE_DSMBALL:
371 case ANIMTYPE_DSARM:
372 case ANIMTYPE_DSMESH:
373 case ANIMTYPE_DSLAT:
375 case ANIMTYPE_DSSPK:
376 case ANIMTYPE_DSNTREE:
377 case ANIMTYPE_DSTEX:
379 case ANIMTYPE_DSMCLIP:
380 case ANIMTYPE_DSHAIR:
383 case ANIMTYPE_NLAACTION: {
384 /* need to verify that this data is valid for now */
385 if (ale && ale->adt) {
386 ale->adt->flag |= ADT_UI_ACTIVE;
387 }
388 break;
389 }
390
391 case ANIMTYPE_GPLAYER: {
392 bGPDlayer *gpl = (bGPDlayer *)channel_data;
393 gpl->flag |= GP_LAYER_ACTIVE;
394 break;
395 }
396 /* unhandled currently, but may be interesting */
399 break;
400
401 /* other types */
402 default:
403 break;
404 }
405 }
406
407 /* clean up */
408 ANIM_animdata_freelist(&anim_data);
409}
410
412{
413 using namespace blender;
414
415 switch (ale->type) {
416 case ANIMTYPE_FILLACTD: /* Action Expander */
417 case ANIMTYPE_FILLACT_LAYERED: /* Animation Expander */
418 case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
419 case ANIMTYPE_DSLAM:
420 case ANIMTYPE_DSCAM:
422 case ANIMTYPE_DSCUR:
423 case ANIMTYPE_DSSKEY:
424 case ANIMTYPE_DSWOR:
425 case ANIMTYPE_DSPART:
426 case ANIMTYPE_DSMBALL:
427 case ANIMTYPE_DSARM:
428 case ANIMTYPE_DSMESH:
429 case ANIMTYPE_DSNTREE:
430 case ANIMTYPE_DSTEX:
431 case ANIMTYPE_DSLAT:
433 case ANIMTYPE_DSSPK:
435 case ANIMTYPE_DSMCLIP:
436 case ANIMTYPE_DSHAIR:
439 case ANIMTYPE_NLAACTION: {
440 return ale->adt && (ale->adt->flag & ADT_UI_ACTIVE);
441 }
442 case ANIMTYPE_GROUP: {
443 bActionGroup *argp = (bActionGroup *)ale->data;
444 return argp->flag & AGRP_ACTIVE;
445 }
446 case ANIMTYPE_FCURVE:
447 case ANIMTYPE_NLACURVE: {
448 FCurve *fcu = (FCurve *)ale->data;
449 return fcu->flag & FCURVE_ACTIVE;
450 }
451 case ANIMTYPE_GPLAYER: {
452 bGPDlayer *gpl = (bGPDlayer *)ale->data;
453 return gpl->flag & GP_LAYER_ACTIVE;
454 }
456 GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(ale->id);
457 return grease_pencil->is_layer_active(
458 static_cast<blender::bke::greasepencil::Layer *>(ale->data));
459 }
461 animrig::Slot *slot = reinterpret_cast<animrig::Slot *>(ale->data);
462 return slot->is_active();
463 }
464 /* These channel types do not have active flags. */
465 case ANIMTYPE_NONE:
468 case ANIMTYPE_SUMMARY:
469 case ANIMTYPE_SCENE:
470 case ANIMTYPE_OBJECT:
480 case ANIMTYPE_PALETTE:
482 break;
483 }
484 return false;
485}
486
487/* change_active determines whether to change the active bone of the armature when selecting pose
488 * channels. It is false during range selection otherwise true. */
490 bActionGroup *agrp,
491 bAnimListElem *ale,
492 const bool change_active)
493{
494 /* Armatures-Specific Feature:
495 * See mouse_anim_channels() -> ANIMTYPE_GROUP case for more details (#38737)
496 */
497 if ((ac->ads->filterflag & ADS_FILTER_ONLYSEL) == 0) {
498 if ((ale->id) && (GS(ale->id->name) == ID_OB)) {
499 Object *ob = (Object *)ale->id;
500 if (ob->type == OB_ARMATURE) {
501 /* Assume for now that any group with corresponding name is what we want
502 * (i.e. for an armature whose location is animated, things would break
503 * if the user were to add a bone named "Location").
504 *
505 * TODO: check the first F-Curve or so to be sure...
506 */
508
509 if (agrp->flag & AGRP_SELECTED) {
510 ED_pose_bone_select(ob, pchan, true, change_active);
511 }
512 else {
513 ED_pose_bone_select(ob, pchan, false, change_active);
514 }
515 }
516 }
517 }
518}
519
521{
522 ListBase anim_data = {nullptr, nullptr};
523
524 /* filter data */
525 /* NOTE: no list visible, otherwise, we get dangling */
528 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
529
530 return anim_data;
531}
532
534{
535 /* See if we should be selecting or deselecting. */
536 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
537 switch (ale->type) {
538 case ANIMTYPE_SCENE:
539 if (ale->flag & SCE_DS_SELECTED) {
541 }
542 break;
543 case ANIMTYPE_OBJECT:
544#if 0 /* for now, do not take object selection into account, since it gets too annoying */
545 if (ale->flag & SELECT) {
547 }
548#endif
549 break;
550 case ANIMTYPE_GROUP:
551 if (ale->flag & AGRP_SELECTED) {
553 }
554 break;
555 case ANIMTYPE_FCURVE:
557 if (ale->flag & FCURVE_SELECTED) {
559 }
560 break;
562 if (ale->flag & KEYBLOCK_SEL) {
564 }
565 break;
567 if (ale->flag & NLATRACK_SELECTED) {
569 }
570 break;
572 using namespace blender::animrig;
573 if (static_cast<Slot *>(ale->data)->is_selected()) {
575 }
576 break;
577 }
578 case ANIMTYPE_FILLACTD: /* Action Expander */
579 case ANIMTYPE_FILLACT_LAYERED: /* Animation Expander */
580 case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
581 case ANIMTYPE_DSLAM:
582 case ANIMTYPE_DSCAM:
584 case ANIMTYPE_DSCUR:
585 case ANIMTYPE_DSSKEY:
586 case ANIMTYPE_DSWOR:
587 case ANIMTYPE_DSPART:
588 case ANIMTYPE_DSMBALL:
589 case ANIMTYPE_DSARM:
590 case ANIMTYPE_DSMESH:
591 case ANIMTYPE_DSNTREE:
592 case ANIMTYPE_DSTEX:
593 case ANIMTYPE_DSLAT:
595 case ANIMTYPE_DSSPK:
597 case ANIMTYPE_DSMCLIP:
598 case ANIMTYPE_DSHAIR:
601 case ANIMTYPE_NLAACTION: {
602 if ((ale->adt) && (ale->adt->flag & ADT_UI_SELECTED)) {
604 }
605 break;
606 }
607 case ANIMTYPE_GPLAYER:
608 if (ale->flag & GP_LAYER_SELECT) {
610 }
611 break;
613 if (ale->flag & MASK_LAYERFLAG_SELECT) {
615 }
616 break;
617 case ANIMTYPE_NONE:
620 case ANIMTYPE_SUMMARY:
628 case ANIMTYPE_PALETTE:
630 break;
631 }
632 }
633
635}
636
647template<typename T>
648static void templated_selection_state_update(T &selectable_thing,
649 const eAnimChannels_SetFlag selectmode)
650{
651 switch (selectmode) {
653 selectable_thing.set_selected(!selectable_thing.is_selected());
654 break;
656 selectable_thing.set_selected(true);
657 break;
658 /* You would probably expect "extend range" to select rather than deselect,
659 * and "toggle" to behave the same as "invert", because that's what a sane
660 * system would do. However, this function is used in the same places as the
661 * `ACHANNEL_SET_FLAG` macro, and therefore reproduces its logic. Note that
662 * in the "extend range" case this is actually functionally important,
663 * because `anim_channels_select_set()` below uses that case to *deselect
664 * everything* before `animchannel_select_range()` later does the actual
665 * selection of the channels in the range. */
669 selectable_thing.set_selected(false);
670 break;
671 }
672}
673
675 const ListBase anim_data,
677{
678 using namespace blender;
679
680 /* Boolean to keep active channel status during range selection. */
681 const bool change_active = (sel != ACHANNEL_SETFLAG_EXTEND_RANGE);
682
683 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
684 switch (ale->type) {
685 case ANIMTYPE_SCENE: {
686 if (change_active) {
687 break;
688 }
689 Scene *scene = (Scene *)ale->data;
690
692
693 if (scene->adt) {
695 }
696 break;
697 }
698 case ANIMTYPE_OBJECT: {
699#if 0 /* for now, do not take object selection into account, since it gets too annoying */
700 Base *base = (Base *)ale->data;
701 Object *ob = base->object;
702
703 ACHANNEL_SET_FLAG(base, sel, SELECT);
704 ACHANNEL_SET_FLAG(ob, sel, SELECT);
705
706 if (ob->adt) {
708 }
709#endif
710 break;
711 }
712 case ANIMTYPE_GROUP: {
713 bActionGroup *agrp = (bActionGroup *)ale->data;
715 select_pchan_for_action_group(ac, agrp, ale, change_active);
716 if (change_active) {
717 agrp->flag &= ~AGRP_ACTIVE;
718 }
719 break;
720 }
721 case ANIMTYPE_FCURVE:
722 case ANIMTYPE_NLACURVE: {
723 FCurve *fcu = (FCurve *)ale->data;
724
726 if (!(fcu->flag & FCURVE_SELECTED) && change_active) {
727 /* Only erase the ACTIVE flag when deselecting. This ensures that "select all curves"
728 * retains the currently active curve. */
729 fcu->flag &= ~FCURVE_ACTIVE;
730 }
731 break;
732 }
733 case ANIMTYPE_SHAPEKEY: {
734 KeyBlock *kb = (KeyBlock *)ale->data;
735
737 break;
738 }
739 case ANIMTYPE_NLATRACK: {
740 NlaTrack *nlt = (NlaTrack *)ale->data;
741
743 nlt->flag &= ~NLATRACK_ACTIVE;
744 break;
745 }
747 animrig::Slot *slot = static_cast<animrig::Slot *>(ale->data);
749 break;
750 }
751 case ANIMTYPE_FILLACTD: /* Action Expander */
752 case ANIMTYPE_FILLACT_LAYERED: /* Animation Expander */
753 case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
754 case ANIMTYPE_DSLAM:
755 case ANIMTYPE_DSCAM:
757 case ANIMTYPE_DSCUR:
758 case ANIMTYPE_DSSKEY:
759 case ANIMTYPE_DSWOR:
760 case ANIMTYPE_DSPART:
761 case ANIMTYPE_DSMBALL:
762 case ANIMTYPE_DSARM:
763 case ANIMTYPE_DSMESH:
764 case ANIMTYPE_DSNTREE:
765 case ANIMTYPE_DSTEX:
766 case ANIMTYPE_DSLAT:
768 case ANIMTYPE_DSSPK:
770 case ANIMTYPE_DSMCLIP:
771 case ANIMTYPE_DSHAIR:
774 case ANIMTYPE_NLAACTION: {
775 /* need to verify that this data is valid for now */
776 if (ale->adt) {
777 ACHANNEL_SET_FLAG(ale->adt, sel, ADT_UI_SELECTED);
778 if (change_active) {
779 ale->adt->flag &= ~ADT_UI_ACTIVE;
780 }
781 }
782 break;
783 }
785 using namespace blender::bke::greasepencil;
786 Layer *layer = static_cast<Layer *>(ale->data);
787 ACHANNEL_SET_FLAG(&(layer->base), sel, GP_LAYER_TREE_NODE_SELECT);
788 break;
789 }
790 case ANIMTYPE_GPLAYER: {
791 bGPDlayer *gpl = (bGPDlayer *)ale->data;
792
794 break;
795 }
796 case ANIMTYPE_MASKLAYER: {
797 MaskLayer *masklay = (MaskLayer *)ale->data;
798
800 break;
801 }
802 case ANIMTYPE_NONE:
805 case ANIMTYPE_SUMMARY:
812 case ANIMTYPE_PALETTE:
814 break;
815 }
816 }
817}
818
825
833
836/* -------------------------------------------------------------------- */
840/* Copy a certain channel setting to parents of the modified channel. */
842 const eAnimChannel_Settings setting,
843 const eAnimChannels_SetFlag mode,
844 bAnimListElem *const match,
845 const int matchLevel)
846{
847 /* flush up?
848 *
849 * For Visibility:
850 * - only flush up if the current state is now enabled (positive 'on' state is default)
851 * (otherwise, it's too much work to force the parents to be inactive too)
852 *
853 * For everything else:
854 * - only flush up if the current state is now disabled (negative 'off' state is default)
855 * (otherwise, it's too much work to force the parents to be active too)
856 */
857 if (setting == ACHANNEL_SETTING_VISIBLE) {
858 if (mode == ACHANNEL_SETFLAG_CLEAR) {
859 return;
860 }
861 }
862 else {
863 if (mode != ACHANNEL_SETFLAG_CLEAR) {
864 return;
865 }
866 }
867
868 /* Go backwards in the list, until the highest-ranking element
869 * (by indentation has been covered). */
870 int prevLevel = matchLevel;
871 for (bAnimListElem *ale = match->prev; ale; ale = ale->prev) {
873
874 /* if no channel info was found, skip, since this type might not have any useful info */
875 if (acf == nullptr) {
876 continue;
877 }
878
879 /* Get the level of the current channel traversed
880 * - we define the level as simply being the offset for the start of the channel
881 */
882 const int level = (acf->get_offset) ? acf->get_offset(ac, ale) : 0;
883
884 if (level == prevLevel) {
885 /* Don't influence siblings. */
886 continue;
887 }
888
889 if (level > prevLevel) {
890 /* If previous level was a base-level (i.e. 0 offset / root of one hierarchy), stop here. */
891 if (prevLevel == 0) {
892 return;
893 }
894
895 /* Otherwise, this level weaves into another sibling hierarchy to the previous one just
896 * finished, so skip until we get to the parent of this level. */
897 continue;
898 }
899
900 /* The level is 'less than' (i.e. more important) the level we're matching but also 'less
901 * than' the level just tried (i.e. only the 1st group above grouped F-Curves, when toggling
902 * visibility of F-Curves, gets flushed, which should happen if we don't let prevLevel get
903 * updated below once the first 1st group is found). */
904 ANIM_channel_setting_set(ac, ale, setting, mode);
905
906 /* store this level as the 'old' level now */
907 prevLevel = level;
908 }
909}
910
911/* Copy a certain channel setting to children of the modified channel. */
913 const eAnimChannel_Settings setting,
914 const eAnimChannels_SetFlag mode,
915 bAnimListElem *const match,
916 const int matchLevel)
917{
918 /* go forwards in the list, until the lowest-ranking element (by indentation has been covered) */
919 for (bAnimListElem *ale = match->next; ale; ale = ale->next) {
921
922 /* if no channel info was found, skip, since this type might not have any useful info */
923 if (acf == nullptr) {
924 continue;
925 }
926
927 /* get the level of the current channel traversed
928 * - we define the level as simply being the offset for the start of the channel
929 */
930 const int level = (acf->get_offset) ? acf->get_offset(ac, ale) : 0;
931
932 /* if the level is 'greater than' (i.e. less important) the channel that was changed,
933 * flush the new status...
934 */
935 if (level > matchLevel) {
936 ANIM_channel_setting_set(ac, ale, setting, mode);
937 /* however, if the level is 'less than or equal to' the channel that was changed,
938 * (i.e. the current channel is as important if not more important than the changed
939 * channel) then we should stop, since we've found the last one of the children we should
940 * flush
941 */
942 }
943 else {
944 break;
945 }
946 }
947}
948
950 ListBase *anim_data,
951 bAnimListElem *ale_setting,
952 eAnimChannel_Settings setting,
954{
955 bAnimListElem *match = nullptr;
956 int matchLevel = 0;
957
958 /* sanity check */
959 if (ELEM(nullptr, anim_data, anim_data->first)) {
960 return;
961 }
962
963 if (setting == ACHANNEL_SETTING_ALWAYS_VISIBLE) {
964 return;
965 }
966
967 /* find the channel that got changed */
968 LISTBASE_FOREACH (bAnimListElem *, ale, anim_data) {
969 /* compare data, and type as main way of identifying the channel */
970 if ((ale->data == ale_setting->data) && (ale->type == ale_setting->type)) {
971 /* We also have to check the ID, this is assigned to,
972 * since a block may have multiple users. */
973 /* TODO: is the owner-data more revealing? */
974 if (ale->id == ale_setting->id) {
975 match = ale;
976 break;
977 }
978 }
979 }
980 if (match == nullptr) {
981 printf("ERROR: no channel matching the one changed was found\n");
982 return;
983 }
984
985 {
986 const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale_setting);
987 if (acf == nullptr) {
988 printf("ERROR: no channel info for the changed channel\n");
989 return;
990 }
991
992 /* get the level of the channel that was affected
993 * - we define the level as simply being the offset for the start of the channel
994 */
995 matchLevel = (acf->get_offset) ? acf->get_offset(ac, ale_setting) : 0;
996 }
997
998 anim_flush_channel_setting_up(ac, setting, mode, match, matchLevel);
999 anim_flush_channel_setting_down(ac, setting, mode, match, matchLevel);
1000}
1001
1003{
1004
1006
1007 if (!window_region) {
1008 return;
1009 }
1010
1011 ListBase anim_data = {nullptr, nullptr};
1015 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
1016
1017 rctf bounds{};
1018 bounds.xmin = FLT_MAX;
1019 bounds.xmax = -FLT_MAX;
1020 bounds.ymin = FLT_MAX;
1021 bounds.ymax = -FLT_MAX;
1022 const bool include_handles = false;
1023 float frame_range[2] = {window_region->v2d.cur.xmin, window_region->v2d.cur.xmax};
1024 if (ac->scene->r.flag & SCER_PRV_RANGE) {
1025 frame_range[0] = ac->scene->r.psfra;
1026 frame_range[1] = ac->scene->r.pefra;
1027 }
1028
1029 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
1030 rctf channel_bounds;
1031 const bool found_bounds = get_channel_bounds(
1032 ac, ale, frame_range, include_handles, &channel_bounds);
1033 if (found_bounds) {
1034 BLI_rctf_union(&bounds, &channel_bounds);
1035 }
1036 }
1037
1038 if (!BLI_rctf_is_valid(&bounds)) {
1039 ANIM_animdata_freelist(&anim_data);
1040 return;
1041 }
1042
1043 add_region_padding(C, window_region, &bounds);
1044
1045 window_region->v2d.cur.ymin = bounds.ymin;
1046 window_region->v2d.cur.ymax = bounds.ymax;
1047
1048 ANIM_animdata_freelist(&anim_data);
1049}
1050
1053/* -------------------------------------------------------------------- */
1057/* poll callback for being in an Animation Editor channels list region */
1059{
1060 ScrArea *area = CTX_wm_area(C);
1061
1062 /* channels region test */
1063 /* TODO: could enhance with actually testing if channels region? */
1064 if (ELEM(nullptr, area, CTX_wm_region(C))) {
1065 return false;
1066 }
1067 /* animation editor test */
1068 if (ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH, SPACE_NLA) == 0) {
1069 return false;
1070 }
1071
1072 return true;
1073}
1074
1075/* Poll callback for Animation Editor channels list region + not in NLA-tweak-mode for NLA. */
1077{
1078 ScrArea *area = CTX_wm_area(C);
1079 Scene *scene = CTX_data_scene(C);
1080
1081 /* channels region test */
1082 /* TODO: could enhance with actually testing if channels region? */
1083 if (ELEM(nullptr, area, CTX_wm_region(C))) {
1084 return false;
1085 }
1086 /* animation editor test */
1087 if (ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH, SPACE_NLA) == 0) {
1088 return false;
1089 }
1090
1091 /* NLA tweak-mode test. */
1092 if (area->spacetype == SPACE_NLA) {
1093 if ((scene == nullptr) || (scene->flag & SCE_NLA_EDIT_ON)) {
1094 return false;
1095 }
1096 }
1097
1098 return true;
1099}
1100
1103/* -------------------------------------------------------------------- */
1107/* constants for channel rearranging */
1108/* WARNING: don't change existing ones without modifying rearrange func accordingly */
1115
1116/* defines for rearranging channels */
1118 {REARRANGE_ANIMCHAN_TOP, "TOP", 0, "To Top", ""},
1119 {REARRANGE_ANIMCHAN_UP, "UP", 0, "Up", ""},
1120 {REARRANGE_ANIMCHAN_DOWN, "DOWN", 0, "Down", ""},
1121 {REARRANGE_ANIMCHAN_BOTTOM, "BOTTOM", 0, "To Bottom", ""},
1122 {0, nullptr, 0, nullptr, nullptr},
1123};
1124
1125/* Reordering "Islands" Defines ----------------------------------- */
1126
1127/* Island definition - just a listbase container */
1130
1131 ListBase channels; /* channels within this region with the same state */
1132 int flag; /* eReorderIslandFlag */
1133};
1134
1135/* flags for channel reordering islands */
1137 REORDER_ISLAND_SELECTED = (1 << 0), /* island is selected */
1138 REORDER_ISLAND_UNTOUCHABLE = (1 << 1), /* island should be ignored */
1139 REORDER_ISLAND_MOVED = (1 << 2), /* island has already been moved */
1140 REORDER_ISLAND_HIDDEN = (1 << 3), /* island is not visible */
1141};
1142
1143/* Rearrange Methods --------------------------------------------- */
1144
1146{
1147 /* island must not be untouchable */
1148 if (island->flag & REORDER_ISLAND_UNTOUCHABLE) {
1149 return false;
1150 }
1151
1152 /* island should be selected to be moved */
1153 return (island->flag & REORDER_ISLAND_SELECTED) && !(island->flag & REORDER_ISLAND_MOVED);
1154}
1155
1156/* ............................. */
1157
1159{
1160 if (rearrange_island_ok(island)) {
1161 /* remove from current position */
1162 BLI_remlink(list, island);
1163
1164 /* make it first element */
1165 BLI_insertlinkbefore(list, list->first, island);
1166
1167 return true;
1168 }
1169
1170 return false;
1171}
1172
1174{
1175 if (rearrange_island_ok(island)) {
1176 /* moving up = moving before the previous island, otherwise we're in the same place */
1177 tReorderChannelIsland *prev = island->prev;
1178
1179 /* Skip hidden islands! */
1180 while (prev && prev->flag & REORDER_ISLAND_HIDDEN) {
1181 prev = prev->prev;
1182 }
1183
1184 if (prev) {
1185 /* remove from current position */
1186 BLI_remlink(list, island);
1187
1188 /* push it up */
1189 BLI_insertlinkbefore(list, prev, island);
1190
1191 return true;
1192 }
1193 }
1194
1195 return false;
1196}
1197
1199{
1200 if (rearrange_island_ok(island)) {
1201 /* moving down = moving after the next island, otherwise we're in the same place */
1202 tReorderChannelIsland *next = island->next;
1203
1204 /* Skip hidden islands! */
1205 while (next && next->flag & REORDER_ISLAND_HIDDEN) {
1206 next = next->next;
1207 }
1208
1209 if (next) {
1210 /* can only move past if next is not untouchable (i.e. nothing can go after it) */
1211 if ((next->flag & REORDER_ISLAND_UNTOUCHABLE) == 0) {
1212 /* remove from current position */
1213 BLI_remlink(list, island);
1214
1215 /* push it down */
1216 BLI_insertlinkafter(list, next, island);
1217
1218 return true;
1219 }
1220 }
1221 /* else: no next channel, so we're at the bottom already, so can't move */
1222 }
1223
1224 return false;
1225}
1226
1228{
1229 if (rearrange_island_ok(island)) {
1230 tReorderChannelIsland *last = static_cast<tReorderChannelIsland *>(list->last);
1231
1232 /* remove island from current position */
1233 BLI_remlink(list, island);
1234
1235 /* add before or after the last channel? */
1236 if ((last->flag & REORDER_ISLAND_UNTOUCHABLE) == 0) {
1237 /* can add after it */
1238 BLI_addtail(list, island);
1239 }
1240 else {
1241 /* can at most go just before it, since last cannot be moved */
1242 BLI_insertlinkbefore(list, last, island);
1243 }
1244
1245 return true;
1246 }
1247
1248 return false;
1249}
1250
1251/* ............................. */
1252
1260using AnimChanRearrangeFp = bool (*)(ListBase *list, tReorderChannelIsland *island);
1261
1262/* get rearranging function, given 'rearrange' mode */
1264{
1265 switch (mode) {
1267 return rearrange_island_top;
1269 return rearrange_island_up;
1271 return rearrange_island_down;
1274 default:
1275 return nullptr;
1276 }
1277}
1278
1279/* get rearranging function, given 'rearrange' mode (grease pencil is inverted) */
1281{
1282 switch (mode) {
1286 return rearrange_island_down;
1288 return rearrange_island_up;
1290 return rearrange_island_top;
1291 default:
1292 return nullptr;
1293 }
1294}
1295
1296/* Rearrange Islands Generics ------------------------------------- */
1297
1298/* add channel into list of islands */
1300 ListBase *srcList,
1301 Link *channel,
1302 eAnim_ChannelType type,
1303 const bool is_hidden)
1304{
1305 /* always try to add to last island if possible */
1306 tReorderChannelIsland *island = static_cast<tReorderChannelIsland *>(islands->last);
1307 bool is_sel = false, is_untouchable = false;
1308
1309 /* get flags - selected and untouchable from the channel */
1310 switch (type) {
1311 case ANIMTYPE_GROUP: {
1312 bActionGroup *agrp = (bActionGroup *)channel;
1313
1314 is_sel = SEL_AGRP(agrp);
1315 is_untouchable = (agrp->flag & AGRP_TEMP) != 0;
1316 break;
1317 }
1318 case ANIMTYPE_FCURVE:
1319 case ANIMTYPE_NLACURVE: {
1320 FCurve *fcu = (FCurve *)channel;
1321
1322 is_sel = SEL_FCU(fcu);
1323 break;
1324 }
1325 case ANIMTYPE_NLATRACK: {
1326 NlaTrack *nlt = (NlaTrack *)channel;
1327
1328 is_sel = SEL_NLT(nlt);
1329 break;
1330 }
1331 case ANIMTYPE_GPLAYER: {
1332 bGPDlayer *gpl = (bGPDlayer *)channel;
1333
1334 is_sel = SEL_GPL(gpl);
1335 break;
1336 }
1337 default:
1338 printf(
1339 "rearrange_animchannel_add_to_islands(): don't know how to handle channels of type %d\n",
1340 type);
1341 return;
1342 }
1343
1344 /* do we need to add to a new island? */
1345 if (/* 1) no islands yet */
1346 (island == nullptr) ||
1347 /* 2) unselected islands have single channels only - to allow up/down movement */
1348 ((island->flag & REORDER_ISLAND_SELECTED) == 0) ||
1349 /* 3) if channel is unselected, stop existing island
1350 * (it was either wrong sel status, or full already) */
1351 (is_sel == 0) ||
1352 /* 4) hidden status changes */
1353 (bool(island->flag & REORDER_ISLAND_HIDDEN) != is_hidden))
1354 {
1355 /* create a new island now */
1356 island = static_cast<tReorderChannelIsland *>(
1357 MEM_callocN(sizeof(tReorderChannelIsland), "tReorderChannelIsland"));
1358 BLI_addtail(islands, island);
1359
1360 if (is_sel) {
1361 island->flag |= REORDER_ISLAND_SELECTED;
1362 }
1363 if (is_untouchable) {
1365 }
1366 if (is_hidden) {
1367 island->flag |= REORDER_ISLAND_HIDDEN;
1368 }
1369 }
1370
1371 /* add channel to island - need to remove it from its existing list first though */
1372 BLI_remlink(srcList, channel);
1373 BLI_addtail(&island->channels, channel);
1374}
1375
1376/* flatten islands out into a single list again */
1378{
1379 tReorderChannelIsland *island, *isn = nullptr;
1380
1381 /* make sure srcList is empty now */
1383
1384 /* go through merging islands */
1385 for (island = static_cast<tReorderChannelIsland *>(islands->first); island; island = isn) {
1386 isn = island->next;
1387
1388 /* merge island channels back to main list, then delete the island */
1389 BLI_movelisttolist(srcList, &island->channels);
1390 BLI_freelinkN(islands, island);
1391 }
1392}
1393
1394/* ............................. */
1395
1396/* get a list of all bAnimListElem's of a certain type which are currently visible */
1398 bAnimContext *ac,
1399 eAnim_ChannelType type)
1400{
1401 ListBase anim_data = {nullptr, nullptr};
1404
1405 /* get all visible channels */
1406 ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
1407
1408 /* now, only keep the ones that are of the types we are interested in */
1409 LISTBASE_FOREACH_MUTABLE (bAnimListElem *, ale, &anim_data) {
1410 if (ale->type != type) {
1411 BLI_freelinkN(&anim_data, ale);
1412 continue;
1413 }
1414
1415 if (type == ANIMTYPE_NLATRACK) {
1416 NlaTrack *nlt = (NlaTrack *)ale->data;
1417
1418 if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) {
1419 /* No re-arrangement of non-local tracks of override data. */
1420 BLI_freelinkN(&anim_data, ale);
1421 continue;
1422 }
1423 }
1424 }
1425
1426 /* return cleaned up list */
1427 *anim_data_visible = anim_data;
1428}
1429
1430/* performing rearranging of channels using islands */
1432 AnimChanRearrangeFp rearrange_func,
1434 eAnim_ChannelType type,
1435 ListBase *anim_data_visible)
1436{
1437 ListBase islands = {nullptr, nullptr};
1438 Link *channel, *chanNext = nullptr;
1439 bool done = false;
1440
1441 /* don't waste effort on an empty list */
1442 if (BLI_listbase_is_empty(list)) {
1443 return false;
1444 }
1445
1446 /* group channels into islands */
1447 for (channel = static_cast<Link *>(list->first); channel; channel = chanNext) {
1448 /* find out whether this channel is present in anim_data_visible or not! */
1449 const bool is_hidden =
1450 (BLI_findptr(anim_data_visible, channel, offsetof(bAnimListElem, data)) == nullptr);
1451 chanNext = channel->next;
1452 rearrange_animchannel_add_to_islands(&islands, list, channel, type, is_hidden);
1453 }
1454
1455 /* Perform moving of selected islands now, but only if there is more than one of them
1456 * so that something will happen:
1457 *
1458 * - Scanning of the list is performed in the opposite direction
1459 * to the direction we're moving things,
1460 * so that we shouldn't need to encounter items we've moved already.
1461 */
1462 if (islands.first != islands.last) {
1463 tReorderChannelIsland *first = static_cast<tReorderChannelIsland *>(
1464 (mode > 0) ? islands.last : islands.first);
1465 tReorderChannelIsland *island, *isn = nullptr;
1466
1467 for (island = first; island; island = isn) {
1468 isn = (mode > 0) ? island->prev : island->next;
1469
1470 /* perform rearranging */
1471 if (rearrange_func(&islands, island)) {
1472 island->flag |= REORDER_ISLAND_MOVED;
1473 done = true;
1474 }
1475 }
1476 }
1477
1478 /* ungroup islands */
1480
1481 /* did we do anything? */
1482 return done;
1483}
1484
1485/* NLA Specific Stuff ----------------------------------------------------- */
1486
1487/* Change the order NLA Tracks within NLA Stack
1488 * ! NLA tracks are displayed in opposite order, so directions need care
1489 * mode: REARRANGE_ANIMCHAN_*
1490 */
1492{
1493 AnimChanRearrangeFp rearrange_func;
1494 ListBase anim_data_visible = {nullptr, nullptr};
1495 const bool is_liboverride = (ac->obact != nullptr) ? ID_IS_OVERRIDE_LIBRARY(ac->obact) : false;
1496
1497 /* hack: invert mode so that functions will work in right order */
1498 mode = eRearrangeAnimChan_Mode(int(mode) * -1);
1499
1500 /* get rearranging function */
1501 rearrange_func = rearrange_get_mode_func(mode);
1502 if (rearrange_func == nullptr) {
1503 return;
1504 }
1505
1506 /* In liboverride case, we need to extract non-local NLA tracks from current anim data before we
1507 * can perform the move, and add then back afterwards. It's the only way to prevent them from
1508 * being affected by the reordering.
1509 *
1510 * Note that both override apply code for NLA tracks collection, and NLA editing code, are
1511 * responsible to ensure that non-local tracks always remain first in the list. */
1512 ListBase extracted_nonlocal_nla_tracks = {nullptr, nullptr};
1513 if (is_liboverride) {
1514 NlaTrack *nla_track;
1515 for (nla_track = static_cast<NlaTrack *>(adt->nla_tracks.first); nla_track != nullptr;
1516 nla_track = nla_track->next)
1517 {
1518 if (!BKE_nlatrack_is_nonlocal_in_liboverride(&ac->obact->id, nla_track)) {
1519 break;
1520 }
1521 }
1522 if (nla_track != nullptr && nla_track->prev != nullptr) {
1523 extracted_nonlocal_nla_tracks.first = adt->nla_tracks.first;
1524 extracted_nonlocal_nla_tracks.last = nla_track->prev;
1525 adt->nla_tracks.first = nla_track;
1526 nla_track->prev->next = nullptr;
1527 nla_track->prev = nullptr;
1528 }
1529 }
1530
1531 /* Filter visible data. */
1533
1534 /* perform rearranging on tracks list */
1536 &adt->nla_tracks, rearrange_func, mode, ANIMTYPE_NLATRACK, &anim_data_visible);
1537
1538 /* Add back non-local NLA tracks at the beginning of the animation data's list. */
1539 if (!BLI_listbase_is_empty(&extracted_nonlocal_nla_tracks)) {
1540 BLI_assert(is_liboverride);
1541 ((NlaTrack *)extracted_nonlocal_nla_tracks.last)->next = static_cast<NlaTrack *>(
1542 adt->nla_tracks.first);
1543 ((NlaTrack *)adt->nla_tracks.first)->prev = static_cast<NlaTrack *>(
1544 extracted_nonlocal_nla_tracks.last);
1545 adt->nla_tracks.first = extracted_nonlocal_nla_tracks.first;
1546 }
1547
1548 /* free temp data */
1549 BLI_freelistN(&anim_data_visible);
1550}
1551
1552/* Drivers Specific Stuff ------------------------------------------------- */
1553
1554/* Change the order drivers within AnimData block
1555 * mode: REARRANGE_ANIMCHAN_*
1556 */
1558 AnimData *adt,
1560{
1561 /* get rearranging function */
1562 AnimChanRearrangeFp rearrange_func = rearrange_get_mode_func(mode);
1563 ListBase anim_data_visible = {nullptr, nullptr};
1564
1565 if (rearrange_func == nullptr) {
1566 return;
1567 }
1568
1569 /* only consider drivers if they're accessible */
1570 if (EXPANDED_DRVD(adt) == 0) {
1571 return;
1572 }
1573
1574 /* Filter visible data. */
1576
1577 /* perform rearranging on drivers list (drivers are really just F-Curves) */
1579 &adt->drivers, rearrange_func, mode, ANIMTYPE_FCURVE, &anim_data_visible);
1580
1581 /* free temp data */
1582 BLI_freelistN(&anim_data_visible);
1583}
1584
1585/* Action Specific Stuff ------------------------------------------------- */
1586
1587/* make sure all action-channels belong to a group (and clear action's list) */
1589{
1590 FCurve *fcu;
1591
1592 if (act == nullptr) {
1593 return;
1594 }
1595
1596 BLI_assert(act->wrap().is_action_legacy());
1597
1598 /* Separate F-Curves into lists per group */
1599 LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) {
1600 FCurve *const group_fcurves_first = static_cast<FCurve *>(agrp->channels.first);
1601 FCurve *const group_fcurves_last = static_cast<FCurve *>(agrp->channels.last);
1602 if (group_fcurves_first == nullptr) {
1603 /* Empty group. */
1604 continue;
1605 }
1606
1607 if (group_fcurves_first == act->curves.first) {
1608 /* First of the action curves, update the start of the action curves. */
1609 BLI_assert(group_fcurves_first->prev == nullptr);
1610 act->curves.first = group_fcurves_last->next;
1611 }
1612 else {
1613 group_fcurves_first->prev->next = group_fcurves_last->next;
1614 }
1615
1616 if (group_fcurves_last == act->curves.last) {
1617 /* Last of the action curves, update the end of the action curves. */
1618 BLI_assert(group_fcurves_last->next == nullptr);
1619 act->curves.last = group_fcurves_first->prev;
1620 }
1621 else {
1622 group_fcurves_last->next->prev = group_fcurves_first->prev;
1623 }
1624
1625 /* Clear links pointing outside the per-group list. */
1626 group_fcurves_first->prev = group_fcurves_last->next = nullptr;
1627 }
1628
1629 /* Initialize memory for temp-group */
1630 memset(tgrp, 0, sizeof(bActionGroup));
1632 STRNCPY(tgrp->name, "#TempGroup");
1633
1634 /* Move any action-channels not already moved, to the temp group */
1635 if (act->curves.first) {
1636 /* start of list */
1637 fcu = static_cast<FCurve *>(act->curves.first);
1638 fcu->prev = nullptr;
1639 tgrp->channels.first = fcu;
1640 act->curves.first = nullptr;
1641
1642 /* end of list */
1643 fcu = static_cast<FCurve *>(act->curves.last);
1644 fcu->next = nullptr;
1645 tgrp->channels.last = fcu;
1646 act->curves.last = nullptr;
1647
1648 /* ensure that all of these get their group set to this temp group
1649 * (so that visibility filtering works)
1650 */
1651 LISTBASE_FOREACH (FCurve *, fcu, &tgrp->channels) {
1652 fcu->grp = tgrp;
1653 }
1654 }
1655
1656 /* Add temp-group to list */
1657 BLI_addtail(&act->groups, tgrp);
1658}
1659
1660/* link lists of channels that groups have */
1662{
1663 LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) {
1664 /* add list of channels to action's channels */
1665 const ListBase group_channels = agrp->channels;
1666 BLI_movelisttolist(&act->curves, &agrp->channels);
1667 agrp->channels = group_channels;
1668
1669 /* clear moved flag */
1670 agrp->flag &= ~AGRP_MOVED;
1671
1672 /* if group was temporary one:
1673 * - unassign all FCurves which were temporarily added to it
1674 * - remove from list (but don't free as it's on the stack!)
1675 */
1676 if (agrp->flag & AGRP_TEMP) {
1677 LISTBASE_FOREACH (FCurve *, fcu, &agrp->channels) {
1678 fcu->grp = nullptr;
1679 }
1680
1681 BLI_remlink(&act->groups, agrp);
1682 break;
1683 }
1684 }
1685}
1686
1706 const eRearrangeAnimChan_Mode mode)
1707{
1708 ListBase anim_data_visible = {nullptr, nullptr};
1709
1710 /* We don't use `ANIMFILTER_SEL` here, and instead individually check on each
1711 * element whether it's selected or not in the code further below. This is
1712 * because it's what the legacy code does (see for example
1713 * `rearrange_animchannel_add_to_islands()`), and we're avoiding diverging
1714 * unnecessarily from that in case there was a reason for it. */
1716
1717 switch (mode) {
1718 case REARRANGE_ANIMCHAN_UP: {
1719 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data_visible) {
1720 BLI_assert(ale->type == ANIMTYPE_GROUP);
1721 bActionGroup *group = (bActionGroup *)ale->data;
1722 if (!SEL_AGRP(group)) {
1723 continue;
1724 }
1725 blender::animrig::ChannelBag &bag = group->channel_bag->wrap();
1726 const int group_index = bag.channel_groups().first_index_try(group);
1727 const int to_index = group_index - 1;
1728 BLI_assert(group_index >= 0);
1729
1730 /* We skip moving when the destination is also selected because that
1731 * would swap two selected groups rather than moving them all in the
1732 * same direction. This happens when multiple selected groups are
1733 * already packed together at the top. */
1734 if (to_index < 0 || SEL_AGRP(bag.channel_group(to_index))) {
1735 continue;
1736 }
1737
1738 bag.channel_group_move(*group, to_index);
1739 }
1740 break;
1741 }
1742
1744 LISTBASE_FOREACH_BACKWARD (bAnimListElem *, ale, &anim_data_visible) {
1745 BLI_assert(ale->type == ANIMTYPE_GROUP);
1746 bActionGroup *group = (bActionGroup *)ale->data;
1747 if (!SEL_AGRP(group)) {
1748 continue;
1749 }
1750 blender::animrig::ChannelBag &bag = group->channel_bag->wrap();
1751 bag.channel_group_move(*group, 0);
1752 }
1753 break;
1754 }
1755
1757 LISTBASE_FOREACH_BACKWARD (bAnimListElem *, ale, &anim_data_visible) {
1758 BLI_assert(ale->type == ANIMTYPE_GROUP);
1759 bActionGroup *group = (bActionGroup *)ale->data;
1760 if (!SEL_AGRP(group)) {
1761 continue;
1762 }
1763 blender::animrig::ChannelBag &bag = group->channel_bag->wrap();
1764 const int group_index = bag.channel_groups().first_index_try(group);
1765 const int to_index = group_index + 1;
1766 BLI_assert(group_index >= 0);
1767
1768 /* We skip moving when the destination is also selected because that
1769 * would swap two selected groups rather than moving them all in the
1770 * same direction. This happens when multiple selected groups are
1771 * already packed together at the bottom. */
1772 if (to_index >= bag.channel_groups().size() || SEL_AGRP(bag.channel_group(to_index))) {
1773 continue;
1774 }
1775
1776 bag.channel_group_move(*group, to_index);
1777 }
1778 break;
1779 }
1780
1782 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data_visible) {
1783 BLI_assert(ale->type == ANIMTYPE_GROUP);
1784 bActionGroup *group = (bActionGroup *)ale->data;
1785 if (!SEL_AGRP(group)) {
1786 continue;
1787 }
1788 blender::animrig::ChannelBag &bag = group->channel_bag->wrap();
1789 bag.channel_group_move(*group, bag.channel_groups().size() - 1);
1790 }
1791 break;
1792 }
1793 }
1794
1795 BLI_freelistN(&anim_data_visible);
1796}
1797
1817 const eRearrangeAnimChan_Mode mode)
1818{
1819 ListBase anim_data_visible = {nullptr, nullptr};
1820
1821 /* We don't use `ANIMFILTER_SEL` here, and instead individually check on each
1822 * element whether it's selected or not in the code further below. This is
1823 * because it's what the legacy code does (see for example
1824 * `rearrange_animchannel_add_to_islands()`), and we're avoiding diverging
1825 * unnecessarily from that in case there was a reason for it. */
1827
1828 /* Lambda to either fetch an fcurve's group if it has one, or otherwise
1829 * construct a fake one representing the ungrouped range at the end of the
1830 * fcurve array. This lets the code further below be much less of a special-case,
1831 * in exchange for a little data copying.
1832 *
1833 * NOTE: this returns a *copy* of the group, rather a pointer or reference, to
1834 * make it possible to return a fake group when needed. */
1835 auto get_group_or_make_fake = [&action](bAnimListElem *fcurve_ale) -> bActionGroup {
1836 FCurve *fcurve = (FCurve *)fcurve_ale->data;
1837 if (fcurve->grp) {
1838 return *fcurve->grp;
1839 }
1840
1841 blender::animrig::ChannelBag *bag = channelbag_for_action_slot(action,
1842 fcurve_ale->slot_handle);
1843 BLI_assert(bag != nullptr);
1844
1845 bActionGroup group = {};
1846 group.channel_bag = bag;
1847 group.fcurve_range_start = 0;
1848 if (!bag->channel_groups().is_empty()) {
1849 bActionGroup *last_group = bag->channel_groups().last();
1850 group.fcurve_range_start = last_group->fcurve_range_start + last_group->fcurve_range_length;
1851 }
1852 group.fcurve_range_length = bag->fcurves().size() - group.fcurve_range_start;
1853
1854 return group;
1855 };
1856
1857 /* Lambda to determine whether an fcurve should be skipped, given both the
1858 * fcurve and the group it belongs to. */
1859 auto should_skip = [](FCurve &fcurve, bActionGroup &group) {
1860 /* If the curve itself isn't selected, then it shouldn't be operated on. If
1861 * its group is selected then the group was moved so we don't move the
1862 * fcurve individually. */
1863 return !SEL_FCU(&fcurve) || SEL_AGRP(&group);
1864 };
1865
1866 switch (mode) {
1867 case REARRANGE_ANIMCHAN_UP: {
1868 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data_visible) {
1869 BLI_assert(ale->type == ANIMTYPE_FCURVE);
1870 FCurve *fcurve = (FCurve *)ale->data;
1871 bActionGroup group = get_group_or_make_fake(ale);
1872
1873 if (should_skip(*fcurve, group)) {
1874 continue;
1875 }
1876
1877 blender::animrig::ChannelBag &bag = group.channel_bag->wrap();
1878 const int fcurve_index = bag.fcurves().first_index_try(fcurve);
1879 const int to_index = fcurve_index - 1;
1880
1881 /* We skip moving when the destination is also selected because that
1882 * would swap two selected fcurves rather than moving them all in the
1883 * same direction. This happens when multiple selected fcurves are
1884 * already packed together at the top. */
1885 if (to_index < group.fcurve_range_start || SEL_FCU(bag.fcurve(to_index))) {
1886 continue;
1887 }
1888
1889 bag.fcurve_move(*fcurve, to_index);
1890 }
1891 return;
1892 }
1893
1895 LISTBASE_FOREACH_BACKWARD (bAnimListElem *, ale, &anim_data_visible) {
1896 BLI_assert(ale->type == ANIMTYPE_FCURVE);
1897 FCurve *fcurve = (FCurve *)ale->data;
1898 bActionGroup group = get_group_or_make_fake(ale);
1899
1900 if (should_skip(*fcurve, group)) {
1901 continue;
1902 }
1903
1904 blender::animrig::ChannelBag &bag = group.channel_bag->wrap();
1905 bag.fcurve_move(*fcurve, group.fcurve_range_start);
1906 }
1907 return;
1908 }
1909
1911 LISTBASE_FOREACH_BACKWARD (bAnimListElem *, ale, &anim_data_visible) {
1912 BLI_assert(ale->type == ANIMTYPE_FCURVE);
1913 FCurve *fcurve = (FCurve *)ale->data;
1914 bActionGroup group = get_group_or_make_fake(ale);
1915
1916 if (should_skip(*fcurve, group)) {
1917 continue;
1918 }
1919
1920 blender::animrig::ChannelBag &bag = group.channel_bag->wrap();
1921 const int fcurve_index = bag.fcurves().first_index_try(fcurve);
1922 const int to_index = fcurve_index + 1;
1923
1924 /* We skip moving when the destination is also selected because that
1925 * would swap two selected fcurves rather than moving them all in the
1926 * same direction. This happens when multiple selected fcurves are
1927 * already packed together at the bottom. */
1928 if (to_index >= group.fcurve_range_start + group.fcurve_range_length ||
1929 SEL_FCU(bag.fcurve(to_index)))
1930 {
1931 continue;
1932 }
1933
1934 bag.fcurve_move(*fcurve, to_index);
1935 }
1936 return;
1937 }
1938
1940 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data_visible) {
1941 BLI_assert(ale->type == ANIMTYPE_FCURVE);
1942 FCurve *fcurve = (FCurve *)ale->data;
1943 bActionGroup group = get_group_or_make_fake(ale);
1944
1945 if (should_skip(*fcurve, group)) {
1946 continue;
1947 }
1948
1949 blender::animrig::ChannelBag &bag = group.channel_bag->wrap();
1950 bag.fcurve_move(*fcurve, group.fcurve_range_start + group.fcurve_range_length - 1);
1951 }
1952 return;
1953 }
1954 }
1955}
1956
1957/* Change the order of anim-channels within action
1958 * mode: REARRANGE_ANIMCHAN_*
1959 */
1961{
1962 BLI_assert(act != nullptr);
1963
1964 /* Layered actions. */
1967 rearrange_layered_action_fcurves(ac, act->wrap(), mode);
1968 return;
1969 }
1970
1971 /* Legacy actions. */
1972 bActionGroup tgrp;
1973 ListBase anim_data_visible = {nullptr, nullptr};
1974 bool do_channels;
1975
1976 /* get rearranging function */
1977 AnimChanRearrangeFp rearrange_func = rearrange_get_mode_func(mode);
1978
1979 if (rearrange_func == nullptr) {
1980 return;
1981 }
1982
1983 /* make sure we're only operating with groups (vs a mixture of groups+curves) */
1984 split_groups_action_temp(act, &tgrp);
1985
1986 /* Filter visible data. */
1988
1989 /* Rearrange groups first:
1990 * - The group's channels will only get considered
1991 * if nothing happened when rearranging the groups
1992 * i.e. the rearrange function returned 0.
1993 */
1994 do_channels = (rearrange_animchannel_islands(
1995 &act->groups, rearrange_func, mode, ANIMTYPE_GROUP, &anim_data_visible) == 0);
1996
1997 /* free temp data */
1998 BLI_freelistN(&anim_data_visible);
1999
2000 if (do_channels) {
2001 /* Filter visible data. */
2003
2004 LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) {
2005 /* only consider F-Curves if they're visible (group expanded) */
2006 if (EXPANDED_AGRP(ac, agrp)) {
2008 &agrp->channels, rearrange_func, mode, ANIMTYPE_FCURVE, &anim_data_visible);
2009 }
2010 }
2011
2012 /* free temp data */
2013 BLI_freelistN(&anim_data_visible);
2014 }
2015
2016 /* assemble lists into one list (and clear moved tags) */
2018}
2019
2020/* ------------------- */
2021
2023 AnimData *adt,
2025{
2026 ListBase anim_data_visible = {nullptr, nullptr};
2027
2028 /* get rearranging function */
2029 AnimChanRearrangeFp rearrange_func = rearrange_get_mode_func(mode);
2030
2031 if (rearrange_func == nullptr) {
2032 return;
2033 }
2034
2035 /* skip if these curves aren't being shown */
2036 if (adt->flag & ADT_NLA_SKEYS_COLLAPSED) {
2037 return;
2038 }
2039
2040 /* Filter visible data. */
2042
2043 /* we cannot rearrange between strips, but within each strip, we can rearrange those curves */
2044 LISTBASE_FOREACH (NlaTrack *, nlt, &adt->nla_tracks) {
2045 LISTBASE_FOREACH (NlaStrip *, strip, &nlt->strips) {
2047 &strip->fcurves, rearrange_func, mode, ANIMTYPE_NLACURVE, &anim_data_visible);
2048 }
2049 }
2050
2051 /* free temp data */
2052 BLI_freelistN(&anim_data_visible);
2053}
2054
2055/* ------------------- */
2056
2058{
2059 using namespace blender::bke::greasepencil;
2060 ListBase anim_data = {nullptr, nullptr};
2061 blender::Vector<Layer *> layer_list;
2062 int filter = ANIMFILTER_DATA_VISIBLE;
2063
2065 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
2066
2067 if (mode == REARRANGE_ANIMCHAN_TOP) {
2068 LISTBASE_FOREACH_BACKWARD (bAnimListElem *, ale, &anim_data) {
2069 GreasePencil &grease_pencil = *reinterpret_cast<GreasePencil *>(ale->id);
2070 Layer *layer = static_cast<Layer *>(ale->data);
2071 if (layer->is_selected()) {
2072 grease_pencil.move_node_top(layer->as_node());
2073 }
2074 }
2075 }
2076 else {
2077 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2078 GreasePencil &grease_pencil = *reinterpret_cast<GreasePencil *>(ale->id);
2079 Layer *layer = static_cast<Layer *>(ale->data);
2080
2081 switch (mode) {
2082 case REARRANGE_ANIMCHAN_UP: {
2083 if (layer->is_selected()) {
2084 grease_pencil.move_node_up(layer->as_node());
2085 }
2086 break;
2087 }
2089 if (layer->is_selected()) {
2090 grease_pencil.move_node_down(layer->as_node());
2091 }
2092 break;
2093 }
2095 if (layer->is_selected()) {
2096 grease_pencil.move_node_bottom(layer->as_node());
2097 }
2098 break;
2099 }
2100 default:
2101 break;
2102 }
2103 }
2104 }
2105
2106 BLI_freelistN(&anim_data);
2107}
2108
2110{
2111 ListBase anim_data = {nullptr, nullptr};
2112 int filter;
2113
2114 /* get rearranging function */
2116
2117 if (rearrange_func == nullptr) {
2118 return;
2119 }
2120
2121 /* get Grease Pencil datablocks */
2125 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
2126
2127 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2128 /* only consider grease pencil container channels */
2129 if (!ELEM(ale->type, ANIMTYPE_GPDATABLOCK, ANIMTYPE_DSGPENCIL)) {
2130 continue;
2131 }
2132
2133 ListBase anim_data_visible = {nullptr, nullptr};
2134 bGPdata *gpd = static_cast<bGPdata *>(ale->data);
2135
2136 /* only consider layers if this datablock is open */
2137 if ((gpd->flag & GP_DATA_EXPAND) == 0) {
2138 continue;
2139 }
2140
2141 /* Filter visible data. */
2143
2144 /* Rearrange data-block's layers. */
2146 &gpd->layers, rearrange_func, mode, ANIMTYPE_GPLAYER, &anim_data_visible);
2147
2148 /* free visible layers data */
2149 BLI_freelistN(&anim_data_visible);
2150
2151 /* Tag to recalc geometry */
2153 }
2154
2155 /* free GPD channel data */
2156 ANIM_animdata_freelist(&anim_data);
2157
2159}
2160
2161/* ------------------- */
2162
2164{
2165 bAnimContext ac;
2167
2168 /* get editor data */
2169 if (ANIM_animdata_get_context(C, &ac) == 0) {
2170 return OPERATOR_CANCELLED;
2171 }
2172
2173 /* get mode */
2174 mode = eRearrangeAnimChan_Mode(RNA_enum_get(op->ptr, "direction"));
2175
2176 /* method to move channels depends on the editor */
2177 if (ac.datatype == ANIMCONT_GPENCIL) {
2178 /* Grease Pencil channels */
2180 }
2181 else if (ac.datatype == ANIMCONT_MASK) {
2182 /* Grease Pencil channels */
2183 printf("Mask does not supported for moving yet\n");
2184 }
2185 else if (ac.datatype == ANIMCONT_ACTION) {
2186 /* Directly rearrange action's channels */
2187 rearrange_action_channels(&ac, static_cast<bAction *>(ac.data), mode);
2188 }
2189 else {
2190 ListBase anim_data = {nullptr, nullptr};
2191 int filter;
2192
2194 rearrange_gpencil_channels(&ac, mode);
2195 }
2196
2197 /* get animdata blocks */
2201 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
2202
2203 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2204 AnimData *adt = static_cast<AnimData *>(ale->data);
2205
2206 switch (ac.datatype) {
2207 case ANIMCONT_NLA: /* NLA-tracks only */
2208 rearrange_nla_tracks(&ac, adt, mode);
2210 break;
2211
2212 case ANIMCONT_DRIVERS: /* Drivers list only */
2213 rearrange_driver_channels(&ac, adt, mode);
2214 break;
2215
2216 case ANIMCONT_ACTION: /* Single Action only... */
2217 case ANIMCONT_SHAPEKEY: /* DOUBLE CHECK ME... */
2218 {
2219 if (adt->action) {
2220 rearrange_action_channels(&ac, adt->action, mode);
2221 }
2222 else if (G.debug & G_DEBUG) {
2223 printf("Animdata has no action\n");
2224 }
2225 break;
2226 }
2227
2228 default: /* DopeSheet/Graph Editor - Some Actions + NLA Control Curves */
2229 {
2230 /* NLA Control Curves */
2231 if (adt->nla_tracks.first) {
2232 rearrange_nla_control_channels(&ac, adt, mode);
2233 }
2234
2235 /* Action */
2236 if (adt->action) {
2237 rearrange_action_channels(&ac, adt->action, mode);
2238 }
2239 else if (G.debug & G_DEBUG) {
2240 printf("Animdata has no action\n");
2241 }
2242 break;
2243 }
2244 }
2245 }
2246
2247 /* free temp data */
2248 ANIM_animdata_freelist(&anim_data);
2249 }
2250
2251 /* send notifier that things have changed */
2254
2255 return OPERATOR_FINISHED;
2256}
2257
2259{
2260 /* identifiers */
2261 ot->name = "Move Channels";
2262 ot->idname = "ANIM_OT_channels_move";
2263 ot->description = "Rearrange selected animation channels";
2264
2265 /* api callbacks */
2268
2269 /* flags */
2271
2272 /* props */
2274 "direction",
2277 "Direction",
2278 "");
2279}
2280
2283/* -------------------------------------------------------------------- */
2288{
2289 ScrArea *area = CTX_wm_area(C);
2290 SpaceLink *sl;
2291
2292 /* channels region test */
2293 /* TODO: could enhance with actually testing if channels region? */
2294 if (ELEM(nullptr, area, CTX_wm_region(C))) {
2295 return false;
2296 }
2297
2298 /* animation editor test - must be suitable modes only */
2299 sl = CTX_wm_space_data(C);
2300
2301 switch (area->spacetype) {
2302 /* supported... */
2303 case SPACE_ACTION: {
2304 SpaceAction *saction = (SpaceAction *)sl;
2305
2306 /* Dopesheet and action only - all others are for other data-types or have no groups. */
2307 if (ELEM(saction->mode, SACTCONT_ACTION, SACTCONT_DOPESHEET) == 0) {
2308 return false;
2309 }
2310
2311 break;
2312 }
2313 case SPACE_GRAPH: {
2314 SpaceGraph *sipo = (SpaceGraph *)sl;
2315
2316 /* drivers can't have groups... */
2317 if (sipo->mode != SIPO_MODE_ANIMATION) {
2318 return false;
2319 }
2320
2321 break;
2322 }
2323 /* unsupported... */
2324 default:
2325 return false;
2326 }
2327
2328 return true;
2329}
2330
2331/* ----------------------------------------------------------- */
2332
2334 bAnimListElem *adt_ref,
2335 const char name[])
2336{
2337 AnimData *adt = adt_ref->adt;
2338 bAction *act = adt->action;
2339
2340 if (act == nullptr) {
2341 return;
2342 }
2343
2344 /* Get list of selected F-Curves to re-group. */
2345 ListBase anim_data = {nullptr, nullptr};
2348 ANIM_animdata_filter(ac, &anim_data, filter, adt_ref, ANIMCONT_CHANNEL);
2349
2350 if (anim_data.first == nullptr) {
2351 return;
2352 }
2353
2354 /* Legacy actions. */
2356 bActionGroup *agrp;
2357
2358 /* create new group, which should now be part of the action */
2359 agrp = action_groups_add_new(act, name);
2360 BLI_assert(agrp != nullptr);
2361
2362 /* Transfer selected F-Curves across to new group. */
2363 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2364 FCurve *fcu = (FCurve *)ale->data;
2365 bActionGroup *grp = fcu->grp;
2366
2367 /* remove F-Curve from group, then group too if it is now empty */
2369
2370 if ((grp) && BLI_listbase_is_empty(&grp->channels)) {
2371 BLI_freelinkN(&act->groups, grp);
2372 }
2373
2374 /* add F-Curve to group */
2375 action_groups_add_channel(act, agrp, fcu);
2376 }
2377
2378 /* cleanup */
2379 ANIM_animdata_freelist(&anim_data);
2380
2381 return;
2382 }
2383
2384 /* Layered action.
2385 *
2386 * The anim-list doesn't explicitly group the channels by channel bag, so we
2387 * have to get a little clever here. We take advantage of the fact that the
2388 * fcurves are at least listed in order, and so all fcurves in the same
2389 * channel bag will be next to each other. So we keep track of the channel bag
2390 * from the last fcurve, and check it against the current fcurve to see if
2391 * we've progressed into a new channel bag, and then we create the new group
2392 * for that channel bag.
2393 *
2394 * It's a little messy, and also has quadratic performance due to handling
2395 * each fcurve individually (each of which is an O(N) operation), but it's
2396 * also the simplest thing we can do given the data we have. In the future we
2397 * can do something smarter, particularly if it becomes a performance issue. */
2398 blender::animrig::ChannelBag *last_channelbag = nullptr;
2399 bActionGroup *group = nullptr;
2400 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2401 FCurve *fcu = (FCurve *)ale->data;
2402 blender::animrig::ChannelBag *channelbag = channelbag_for_action_slot(act->wrap(),
2403 ale->slot_handle);
2404
2405 if (channelbag != last_channelbag) {
2406 last_channelbag = channelbag;
2407 group = &channelbag->channel_group_create(name);
2408 }
2409
2410 channelbag->fcurve_assign_to_channel_group(*fcu, *group);
2411 }
2412
2413 /* Cleanup. */
2414 ANIM_animdata_freelist(&anim_data);
2415}
2416
2418{
2419 bAnimContext ac;
2420 char name[MAX_NAME];
2421
2422 /* get editor data */
2423 if (ANIM_animdata_get_context(C, &ac) == 0) {
2424 return OPERATOR_CANCELLED;
2425 }
2426
2427 /* get name for new group */
2428 RNA_string_get(op->ptr, "name", name);
2429
2430 /* XXX: name for group should never be empty... */
2431 if (name[0]) {
2432 ListBase anim_data = {nullptr, nullptr};
2433 int filter;
2434
2435 /* Handle each animdata block separately, so that the regrouping doesn't flow into blocks. */
2439 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
2440
2441 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2442 animchannels_group_channels(&ac, ale, name);
2443 }
2444
2445 /* free temp data */
2446 ANIM_animdata_freelist(&anim_data);
2447
2448 /* Updates. */
2450 }
2451
2452 return OPERATOR_FINISHED;
2453}
2454
2456{
2457 /* identifiers */
2458 ot->name = "Group Channels";
2459 ot->idname = "ANIM_OT_channels_group";
2460 ot->description = "Add selected F-Curves to a new group";
2461
2462 /* callbacks */
2466
2467 /* flags */
2469
2470 /* props */
2472 "name",
2473 "New Group",
2474 sizeof(bActionGroup::name),
2475 "Name",
2476 "Name of newly created group");
2477 /* XXX: still not too sure about this - keeping same text is confusing... */
2478 // RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
2479}
2480
2483/* -------------------------------------------------------------------- */
2488{
2489 bAnimContext ac;
2490
2491 ListBase anim_data = {nullptr, nullptr};
2492 int filter;
2493
2494 /* get editor data */
2495 if (ANIM_animdata_get_context(C, &ac) == 0) {
2496 return OPERATOR_CANCELLED;
2497 }
2498
2499 /* just selected F-Curves... */
2503 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
2504
2505 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2506
2507 FCurve *fcu = (FCurve *)ale->data;
2508
2509 /* Already ungrouped, so skip. */
2510 if (fcu->grp == nullptr) {
2511 continue;
2512 }
2513
2514 /* find action for this F-Curve... */
2515 if (!ale->adt || !ale->adt->action) {
2516 continue;
2517 }
2518 bAction *act = ale->adt->action;
2519
2520 /* Legacy actions. */
2522 bActionGroup *agrp = fcu->grp;
2523
2524 /* remove F-Curve from group and add at tail (ungrouped) */
2526 BLI_addtail(&act->curves, fcu);
2527
2528 /* delete group if it is now empty */
2529 if (BLI_listbase_is_empty(&agrp->channels)) {
2530 BLI_freelinkN(&act->groups, agrp);
2531 }
2532 continue;
2533 }
2534
2535 /* Layered action. */
2536 fcu->grp->channel_bag->wrap().fcurve_ungroup(*fcu);
2537 }
2538
2539 /* cleanup */
2540 ANIM_animdata_freelist(&anim_data);
2541
2542 /* updates */
2544
2545 return OPERATOR_FINISHED;
2546}
2547
2549{
2550 /* identifiers */
2551 ot->name = "Ungroup Channels";
2552 ot->idname = "ANIM_OT_channels_ungroup";
2553 ot->description = "Remove selected F-Curves from their current groups";
2554
2555 /* callbacks */
2558
2559 /* flags */
2561}
2562
2565/* -------------------------------------------------------------------- */
2570{
2571 ID *id = ale->id;
2572 AnimData *adt = BKE_animdata_from_id(id);
2573 /* TODO(sergey): Technically, if the animation element is being deleted
2574 * from a driver we don't have to tag action. This is something we can check
2575 * for in the future. For now just do most reliable tag which was always happening. */
2576 if (adt != nullptr) {
2578 if (adt->action != nullptr) {
2580 }
2581 }
2582 /* Deals with NLA and drivers.
2583 * Doesn't cause overhead for action updates, since object will receive
2584 * animation update after dependency graph flushes update from action to
2585 * all its users. */
2587}
2588
2590{
2591 bAnimContext ac;
2592 ListBase anim_data = {nullptr, nullptr};
2593 int filter;
2594
2595 /* get editor data */
2596 if (ANIM_animdata_get_context(C, &ac) == 0) {
2597 return OPERATOR_CANCELLED;
2598 }
2599
2600 /* cannot delete in shapekey */
2601 if (ac.datatype == ANIMCONT_SHAPEKEY) {
2602 return OPERATOR_CANCELLED;
2603 }
2604
2605 /* Do groups and other "summary/expander" types first (unless in Drivers mode, where there are
2606 * none), because the following loop will not find those channels. Also deleting an entire group
2607 * or slot will delete the channels they contain as well, so better avoid looping over those in
2608 * the same loop. */
2609 if (ac.datatype != ANIMCONT_DRIVERS) {
2610 /* filter data */
2614 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
2615
2616 /* delete selected groups and their associated channels */
2617 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2618 switch (ale->type) {
2619 case ANIMTYPE_ACTION_SLOT: {
2620 BLI_assert(ale->fcurve_owner_id);
2621 BLI_assert(ale->data);
2622 BLI_assert_msg(GS(ale->fcurve_owner_id->name) == ID_AC,
2623 "fcurve_owner_id should be an Action");
2624
2625 blender::animrig::Action &action =
2626 reinterpret_cast<bAction *>(ale->fcurve_owner_id)->wrap();
2627 blender::animrig::Slot &slot_to_remove = static_cast<ActionSlot *>(ale->data)->wrap();
2628
2629 action.slot_remove(slot_to_remove);
2630
2632 break;
2633 }
2634 case ANIMTYPE_GROUP: {
2635 bActionGroup *agrp = (bActionGroup *)ale->data;
2636 AnimData *adt = ale->adt;
2637 FCurve *fcu, *fcn;
2638
2639 /* Groups should always be part of an action. */
2640 if (adt == nullptr || adt->action == nullptr) {
2642 continue;
2643 }
2644
2645 blender::animrig::Action &action = adt->action->wrap();
2646
2647 /* Legacy actions */
2648 if (!action.is_action_layered()) {
2649 /* delete all of the Group's F-Curves, but no others */
2650 for (fcu = static_cast<FCurve *>(agrp->channels.first); fcu && fcu->grp == agrp;
2651 fcu = fcn)
2652 {
2653 fcn = fcu->next;
2654
2655 /* remove from group and action, then free */
2657 BKE_fcurve_free(fcu);
2658 }
2659
2660 /* free the group itself */
2661 BLI_freelinkN(&adt->action->groups, agrp);
2663
2664 break;
2665 }
2666
2667 /* Layered actions.
2668 *
2669 * Note that the behavior here is different from deleting groups via
2670 * the Python API: in the Python API the fcurves that belonged to the
2671 * group remain, and just get ungrouped, whereas here they are deleted
2672 * along with the group. This difference in behavior is replicated
2673 * from legacy actions. */
2674
2675 blender::animrig::ChannelBag &channel_bag = agrp->channel_bag->wrap();
2676
2677 /* Remove all the fcurves in the group, which also automatically
2678 * deletes the group when the last fcurve is deleted. Since the group
2679 * is automatically deleted, we store the fcurve range ahead of time
2680 * so we don't have to worry about the memory disappearing out from
2681 * under us. */
2682 const int fcurve_range_start = agrp->fcurve_range_start;
2683 const int fcurve_range_length = agrp->fcurve_range_length;
2684 for (int i = 0; i < fcurve_range_length; i++) {
2685 channel_bag.fcurve_remove(*channel_bag.fcurve(fcurve_range_start));
2686 }
2687
2689
2690 break;
2691 }
2692
2693 case ANIMTYPE_NONE:
2694 case ANIMTYPE_ANIMDATA:
2696 case ANIMTYPE_SUMMARY:
2697 case ANIMTYPE_SCENE:
2698 case ANIMTYPE_OBJECT:
2699 case ANIMTYPE_FCURVE:
2701 case ANIMTYPE_NLACURVE:
2703 case ANIMTYPE_FILLACTD:
2705 case ANIMTYPE_DSMAT:
2706 case ANIMTYPE_DSLAM:
2707 case ANIMTYPE_DSCAM:
2709 case ANIMTYPE_DSCUR:
2710 case ANIMTYPE_DSSKEY:
2711 case ANIMTYPE_DSWOR:
2712 case ANIMTYPE_DSNTREE:
2713 case ANIMTYPE_DSPART:
2714 case ANIMTYPE_DSMBALL:
2715 case ANIMTYPE_DSARM:
2716 case ANIMTYPE_DSMESH:
2717 case ANIMTYPE_DSTEX:
2718 case ANIMTYPE_DSLAT:
2720 case ANIMTYPE_DSSPK:
2721 case ANIMTYPE_DSGPENCIL:
2722 case ANIMTYPE_DSMCLIP:
2723 case ANIMTYPE_DSHAIR:
2725 case ANIMTYPE_DSVOLUME:
2726 case ANIMTYPE_SHAPEKEY:
2728 case ANIMTYPE_GPLAYER:
2733 case ANIMTYPE_MASKLAYER:
2734 case ANIMTYPE_NLATRACK:
2735 case ANIMTYPE_NLAACTION:
2736 case ANIMTYPE_PALETTE:
2737 case ANIMTYPE_NUM_TYPES:
2738 break;
2739 }
2740 }
2741
2742 /* cleanup */
2743 ANIM_animdata_freelist(&anim_data);
2744 }
2745
2746 /* filter data */
2750 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
2751
2752 /* delete selected data channels */
2753 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2754 switch (ale->type) {
2755 case ANIMTYPE_FCURVE: {
2756 /* F-Curves if we can identify its parent */
2757 AnimData *adt = ale->adt;
2758 FCurve *fcu = (FCurve *)ale->data;
2759
2760 /* try to free F-Curve */
2763 break;
2764 }
2765 case ANIMTYPE_NLACURVE: {
2766 /* NLA Control Curve - Deleting it should disable the corresponding setting... */
2767 NlaStrip *strip = (NlaStrip *)ale->owner;
2768 FCurve *fcu = (FCurve *)ale->data;
2769
2770 if (STREQ(fcu->rna_path, "strip_time")) {
2771 strip->flag &= ~NLASTRIP_FLAG_USR_TIME;
2772 }
2773 else if (STREQ(fcu->rna_path, "influence")) {
2774 strip->flag &= ~NLASTRIP_FLAG_USR_INFLUENCE;
2775 }
2776 else {
2777 printf("ERROR: Trying to delete NLA Control Curve for unknown property '%s'\n",
2778 fcu->rna_path);
2779 }
2780
2781 /* unlink and free the F-Curve */
2782 BLI_remlink(&strip->fcurves, fcu);
2783 BKE_fcurve_free(fcu);
2785 break;
2786 }
2787 case ANIMTYPE_GPLAYER: {
2788 /* Grease Pencil layer */
2789 bGPdata *gpd = (bGPdata *)ale->id;
2790 bGPDlayer *gpl = (bGPDlayer *)ale->data;
2791
2792 /* try to delete the layer's data and the layer itself */
2793 BKE_gpencil_layer_delete(gpd, gpl);
2794 ale->update = ANIM_UPDATE_DEPS;
2795
2796 /* Free Grease Pencil data block when last annotation layer is removed, see: #112683. */
2797 if (gpd->flag & GP_DATA_ANNOTATIONS && gpd->layers.first == nullptr) {
2798 BKE_gpencil_free_data(gpd, true);
2799
2800 Scene *scene = CTX_data_scene(C);
2801 scene->gpd = nullptr;
2802
2803 Main *bmain = CTX_data_main(C);
2804 BKE_id_free_us(bmain, gpd);
2805 }
2806 break;
2807 }
2809 using namespace blender::bke::greasepencil;
2810 GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(ale->id);
2811 Layer *layer = static_cast<Layer *>(ale->data);
2812 grease_pencil->remove_layer(*layer);
2813 DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY);
2814 break;
2815 }
2816 case ANIMTYPE_MASKLAYER: {
2817 /* Mask layer */
2818 Mask *mask = (Mask *)ale->id;
2819 MaskLayer *masklay = (MaskLayer *)ale->data;
2820
2821 /* try to delete the layer's data and the layer itself */
2822 BKE_mask_layer_remove(mask, masklay);
2823 break;
2824 }
2825 case ANIMTYPE_NONE:
2826 case ANIMTYPE_ANIMDATA:
2828 case ANIMTYPE_SUMMARY:
2829 case ANIMTYPE_SCENE:
2830 case ANIMTYPE_OBJECT:
2831 case ANIMTYPE_GROUP:
2835 case ANIMTYPE_FILLACTD:
2837 case ANIMTYPE_DSMAT:
2838 case ANIMTYPE_DSLAM:
2839 case ANIMTYPE_DSCAM:
2841 case ANIMTYPE_DSCUR:
2842 case ANIMTYPE_DSSKEY:
2843 case ANIMTYPE_DSWOR:
2844 case ANIMTYPE_DSNTREE:
2845 case ANIMTYPE_DSPART:
2846 case ANIMTYPE_DSMBALL:
2847 case ANIMTYPE_DSARM:
2848 case ANIMTYPE_DSMESH:
2849 case ANIMTYPE_DSTEX:
2850 case ANIMTYPE_DSLAT:
2852 case ANIMTYPE_DSSPK:
2853 case ANIMTYPE_DSGPENCIL:
2854 case ANIMTYPE_DSMCLIP:
2855 case ANIMTYPE_DSHAIR:
2857 case ANIMTYPE_DSVOLUME:
2858 case ANIMTYPE_SHAPEKEY:
2863 case ANIMTYPE_NLATRACK:
2864 case ANIMTYPE_NLAACTION:
2865 case ANIMTYPE_PALETTE:
2866 case ANIMTYPE_NUM_TYPES:
2867 break;
2868 }
2869 }
2870
2871 ANIM_animdata_update(&ac, &anim_data);
2872 ANIM_animdata_freelist(&anim_data);
2873
2874 /* send notifier that things have changed */
2878
2879 return OPERATOR_FINISHED;
2880}
2881
2883{
2884 /* identifiers */
2885 ot->name = "Delete Channels";
2886 ot->idname = "ANIM_OT_channels_delete";
2887 ot->description = "Delete all selected animation channels";
2888
2889 /* api callbacks */
2892
2893 /* flags */
2895}
2896
2899/* -------------------------------------------------------------------- */
2903/* defines for setting animation-channel flags */
2905 {ACHANNEL_SETFLAG_TOGGLE, "TOGGLE", 0, "Toggle", ""},
2906 {ACHANNEL_SETFLAG_CLEAR, "DISABLE", 0, "Disable", ""},
2907 {ACHANNEL_SETFLAG_ADD, "ENABLE", 0, "Enable", ""},
2908 {ACHANNEL_SETFLAG_INVERT, "INVERT", 0, "Invert", ""},
2909 {0, nullptr, 0, nullptr, nullptr},
2910};
2911
2912/* defines for set animation-channel settings */
2913/* TODO: could add some more types, but those are really quite dependent on the mode... */
2915 {ACHANNEL_SETTING_PROTECT, "PROTECT", 0, "Protect", ""},
2916 {ACHANNEL_SETTING_MUTE, "MUTE", 0, "Mute", ""},
2917 {0, nullptr, 0, nullptr, nullptr},
2918};
2919
2920/* ------------------- */
2921
2931 eAnimChannel_Settings setting,
2933 bool onlysel,
2934 bool flush)
2935{
2936 ListBase anim_data = {nullptr, nullptr};
2937 ListBase all_data = {nullptr, nullptr};
2938 int filter;
2939
2940 /* filter data that we need if flush is on */
2941 if (flush) {
2942 /* get list of all channels that selection may need to be flushed to
2943 * - hierarchy visibility needs to be ignored so that settings can get flushed
2944 * "down" inside closed containers
2945 */
2948 ac, &all_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
2949 }
2950
2951 /* filter data that we're working on
2952 * - hierarchy matters if we're doing this from the channels region
2953 * since we only want to apply this to channels we can "see",
2954 * and have these affect their relatives
2955 * - but for Graph Editor, this gets used also from main region
2956 * where hierarchy doesn't apply #21276.
2957 */
2958 if ((ac->spacetype == SPACE_GRAPH) && (ac->regiontype != RGN_TYPE_CHANNELS)) {
2959 /* graph editor (case 2) */
2962 }
2963 else {
2964 /* standard case */
2967 }
2968 if (onlysel) {
2969 filter |= ANIMFILTER_SEL;
2970 }
2972 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
2973
2974 /* if toggling, check if disable or enable */
2975 if (mode == ACHANNEL_SETFLAG_TOGGLE) {
2976 /* default to turn all on, unless we encounter one that's on... */
2977 mode = ACHANNEL_SETFLAG_ADD;
2978
2979 /* see if we should turn off instead... */
2980 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2981 /* set the setting in the appropriate way (if available) */
2982 if (ANIM_channel_setting_get(ac, ale, setting) > 0) {
2984 break;
2985 }
2986 }
2987 }
2988
2989 /* apply the setting */
2990 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
2991 /* skip channel if setting is not available */
2992 if (ANIM_channel_setting_get(ac, ale, setting) == -1) {
2993 continue;
2994 }
2995
2996 /* set the setting in the appropriate way */
2997 ANIM_channel_setting_set(ac, ale, setting, mode);
2999
3000 /* if flush status... */
3001 if (flush) {
3002 ANIM_flush_setting_anim_channels(ac, &all_data, ale, setting, mode);
3003 }
3004 }
3005
3006 ANIM_animdata_freelist(&anim_data);
3007 BLI_freelistN(&all_data);
3008}
3009
3010/* ------------------- */
3011
3013{
3014 bAnimContext ac;
3015 eAnimChannel_Settings setting;
3017 bool flush = true;
3018
3019 /* get editor data */
3020 if (ANIM_animdata_get_context(C, &ac) == 0) {
3021 return OPERATOR_CANCELLED;
3022 }
3023
3024 /* mode (eAnimChannels_SetFlag), setting (eAnimChannel_Settings) */
3025 mode = eAnimChannels_SetFlag(RNA_enum_get(op->ptr, "mode"));
3026 setting = eAnimChannel_Settings(RNA_enum_get(op->ptr, "type"));
3027
3028 /* check if setting is flushable */
3029 if (setting == ACHANNEL_SETTING_EXPAND) {
3030 flush = false;
3031 }
3032
3033 /* modify setting
3034 * - only selected channels are affected
3035 */
3036 setflag_anim_channels(&ac, setting, mode, true, flush);
3037
3038 /* send notifier that things have changed */
3040
3041 return OPERATOR_FINISHED;
3042}
3043
3044/* duplicate of 'ANIM_OT_channels_setting_toggle' for menu title only, weak! */
3046{
3047 PropertyRNA *prop;
3048
3049 /* identifiers */
3050 ot->name = "Enable Channel Setting";
3051 ot->idname = "ANIM_OT_channels_setting_enable";
3052 ot->description = "Enable specified setting on all selected animation channels";
3053
3054 /* api callbacks */
3058
3059 /* flags */
3061
3062 /* props */
3063 /* flag-setting mode */
3064 prop = RNA_def_enum(
3067 /* setting to set */
3068 ot->prop = RNA_def_enum(ot->srna, "type", prop_animchannel_settings_types, 0, "Type", "");
3069}
3070/* duplicate of 'ANIM_OT_channels_setting_toggle' for menu title only, weak! */
3072{
3073 PropertyRNA *prop;
3074
3075 /* identifiers */
3076 ot->name = "Disable Channel Setting";
3077 ot->idname = "ANIM_OT_channels_setting_disable";
3078 ot->description = "Disable specified setting on all selected animation channels";
3079
3080 /* api callbacks */
3084
3085 /* flags */
3087
3088 /* props */
3089 /* flag-setting mode */
3090 prop = RNA_def_enum(
3092 RNA_def_property_flag(prop, PROP_HIDDEN); /* internal hack - don't expose */
3093 /* setting to set */
3094 ot->prop = RNA_def_enum(ot->srna, "type", prop_animchannel_settings_types, 0, "Type", "");
3095}
3096
3098{
3099 PropertyRNA *prop;
3100
3101 /* identifiers */
3102 ot->name = "Toggle Channel Setting";
3103 ot->idname = "ANIM_OT_channels_setting_toggle";
3104 ot->description = "Toggle specified setting on all selected animation channels";
3105
3106 /* api callbacks */
3110
3111 /* flags */
3113
3114 /* props */
3115 /* flag-setting mode */
3116 prop = RNA_def_enum(
3118 RNA_def_property_flag(prop, PROP_HIDDEN); /* internal hack - don't expose */
3119 /* setting to set */
3120 ot->prop = RNA_def_enum(ot->srna, "type", prop_animchannel_settings_types, 0, "Type", "");
3121}
3122
3124{
3125 PropertyRNA *prop;
3126
3127 /* identifiers */
3128 ot->name = "Toggle Channel Editability";
3129 ot->idname = "ANIM_OT_channels_editable_toggle";
3130 ot->description = "Toggle editability of selected channels";
3131
3132 /* api callbacks */
3135
3136 /* flags */
3138
3139 /* props */
3140 /* flag-setting mode */
3143 /* setting to set */
3144 prop = RNA_def_enum(
3146 RNA_def_property_flag(prop, PROP_HIDDEN); /* internal hack - don't expose */
3147}
3148
3151/* -------------------------------------------------------------------- */
3156{
3157 bAnimContext ac;
3158 bool onlysel = true;
3159
3160 /* get editor data */
3161 if (ANIM_animdata_get_context(C, &ac) == 0) {
3162 return OPERATOR_CANCELLED;
3163 }
3164
3165 /* only affect selected channels? */
3166 if (RNA_boolean_get(op->ptr, "all")) {
3167 onlysel = false;
3168 }
3169
3170 /* modify setting */
3172
3173 /* send notifier that things have changed */
3175
3176 return OPERATOR_FINISHED;
3177}
3178
3180{
3181 /* identifiers */
3182 ot->name = "Expand Channels";
3183 ot->idname = "ANIM_OT_channels_expand";
3184 ot->description = "Expand (open) all selected expandable animation channels";
3185
3186 /* api callbacks */
3189
3190 /* flags */
3192
3193 /* props */
3195 ot->srna, "all", true, "All", "Expand all channels (not just selected ones)");
3196}
3197
3200/* -------------------------------------------------------------------- */
3205{
3206 bAnimContext ac;
3207 bool onlysel = true;
3208
3209 /* get editor data */
3210 if (ANIM_animdata_get_context(C, &ac) == 0) {
3211 return OPERATOR_CANCELLED;
3212 }
3213
3214 /* only affect selected channels? */
3215 if (RNA_boolean_get(op->ptr, "all")) {
3216 onlysel = false;
3217 }
3218
3219 /* modify setting */
3221
3222 /* send notifier that things have changed */
3224
3225 return OPERATOR_FINISHED;
3226}
3227
3229{
3230 /* identifiers */
3231 ot->name = "Collapse Channels";
3232 ot->idname = "ANIM_OT_channels_collapse";
3233 ot->description = "Collapse (close) all selected expandable animation channels";
3234
3235 /* api callbacks */
3238
3239 /* flags */
3241
3242 /* props */
3244 ot->srna, "all", true, "All", "Collapse all channels (not just selected ones)");
3245}
3246
3249/* -------------------------------------------------------------------- */
3265{
3266 bAnimContext ac;
3267
3268 ListBase anim_data = {nullptr, nullptr};
3269 int filter;
3270
3271 /* get editor data */
3272 if (ANIM_animdata_get_context(C, &ac) == 0) {
3273 return OPERATOR_CANCELLED;
3274 }
3275
3276 /* get animdata blocks */
3280 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
3281
3282 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
3283 ID *id = ale->id;
3284 AnimData *adt = static_cast<AnimData *>(ale->data);
3285
3286 bool action_empty = false;
3287 bool nla_empty = false;
3288 bool drivers_empty = false;
3289
3290 /* sanity checks */
3291 BLI_assert((id != nullptr) && (adt != nullptr));
3292
3293 /* check if this is "empty" and can be deleted */
3294 /* (For now, there are only these 3 criteria) */
3295
3296 /* 1) Active Action is missing or empty */
3297 if (ELEM(nullptr, adt->action, adt->action->curves.first)) {
3298 action_empty = true;
3299 }
3300 else {
3301 /* TODO: check for keyframe + F-modifier data on these too. */
3302 }
3303
3304 /* 2) No NLA Tracks and/or NLA Strips */
3305 if (adt->nla_tracks.first == nullptr) {
3306 nla_empty = true;
3307 }
3308 else {
3309 /* empty tracks? */
3310 LISTBASE_FOREACH (NlaTrack *, nlt, &adt->nla_tracks) {
3311 if (nlt->strips.first) {
3312 /* stop searching, as we found one that actually had stuff we don't want lost
3313 * NOTE: nla_empty gets reset to false, as a previous track may have been empty
3314 */
3315 nla_empty = false;
3316 break;
3317 }
3318 if (nlt->strips.first == nullptr) {
3319 /* this track is empty, but another one may still have stuff in it, so can't break yet */
3320 nla_empty = true;
3321 }
3322 }
3323 }
3324
3325 /* 3) Drivers */
3326 drivers_empty = (adt->drivers.first == nullptr);
3327
3328 /* remove AnimData? */
3329 if (action_empty && nla_empty && drivers_empty) {
3330 BKE_animdata_free(id, true);
3331 }
3332 }
3333
3334 /* free temp data */
3335 ANIM_animdata_freelist(&anim_data);
3336
3337 /* send notifier that things have changed */
3340
3341 return OPERATOR_FINISHED;
3342}
3343
3345{
3346 /* identifiers */
3347 ot->name = "Remove Empty Animation Data";
3348 ot->idname = "ANIM_OT_channels_clean_empty";
3349 ot->description = "Delete all empty animation data containers from visible data-blocks";
3350
3351 /* api callbacks */
3354
3355 /* flags */
3357}
3358
3361/* -------------------------------------------------------------------- */
3366{
3367 ScrArea *area = CTX_wm_area(C);
3368
3369 /* channels region test */
3370 /* TODO: could enhance with actually testing if channels region? */
3371 if (ELEM(nullptr, area, CTX_wm_region(C))) {
3372 return false;
3373 }
3374
3375 /* animation editor test - Action/Dopesheet/etc. and Graph only */
3376 if (ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH) == 0) {
3377 return false;
3378 }
3379
3380 return true;
3381}
3382
3384{
3385 bAnimContext ac;
3386
3387 ListBase anim_data = {nullptr, nullptr};
3388 int filter;
3389
3390 /* get editor data */
3391 if (ANIM_animdata_get_context(C, &ac) == 0) {
3392 return OPERATOR_CANCELLED;
3393 }
3394
3395 /* filter data */
3398 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
3399
3400 /* loop through filtered data and clean curves */
3401 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
3402 FCurve *fcu = (FCurve *)ale->data;
3403
3404 /* remove disabled flags from F-Curves */
3405 fcu->flag &= ~FCURVE_DISABLED;
3406
3407 /* for drivers, let's do the same too */
3408 if (fcu->driver) {
3409 fcu->driver->flag &= ~DRIVER_FLAG_INVALID;
3410 }
3411
3412 /* tag everything for updates - in particular, this is needed to get drivers working again */
3413 ale->update |= ANIM_UPDATE_DEPS;
3414 }
3415
3416 ANIM_animdata_update(&ac, &anim_data);
3417 ANIM_animdata_freelist(&anim_data);
3418
3419 /* send notifier that things have changed */
3421
3422 return OPERATOR_FINISHED;
3423}
3424
3426{
3427 /* identifiers */
3428 ot->name = "Revive Disabled F-Curves";
3429 ot->idname = "ANIM_OT_channels_fcurves_enable";
3430 ot->description = "Clear 'disabled' tag from all F-Curves to get broken F-Curves working again";
3431
3432 /* api callbacks */
3435
3436 /* flags */
3438}
3439
3442/* -------------------------------------------------------------------- */
3446/* XXX: make this generic? */
3448{
3449 ScrArea *area = CTX_wm_area(C);
3450
3451 if (area == nullptr) {
3452 return false;
3453 }
3454
3455 /* animation editor with dopesheet */
3456 return ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH, SPACE_NLA);
3457}
3458
3460 wmOperator *op,
3461 const wmEvent * /*event*/)
3462{
3463 ScrArea *area = CTX_wm_area(C);
3464 ARegion *region_ctx = CTX_wm_region(C);
3465 ARegion *region_channels = BKE_area_find_region_type(area, RGN_TYPE_CHANNELS);
3466
3467 CTX_wm_region_set(C, region_channels);
3468
3469 /* Show the channel region if it's hidden. This means that direct activation of the input field
3470 * is impossible, as it may not exist yet. For that reason, the actual activation is deferred to
3471 * the modal callback function; by the time it runs, the screen has been redrawn and the UI
3472 * element is there to activate. */
3473 if (region_channels->flag & RGN_FLAG_HIDDEN) {
3474 ED_region_toggle_hidden(C, region_channels);
3475 ED_region_tag_redraw(region_channels);
3476 }
3477
3479
3480 CTX_wm_region_set(C, region_ctx);
3482}
3483
3485 wmOperator * /*op*/,
3486 const wmEvent * /*event*/)
3487{
3488 bAnimContext ac;
3489 if (ANIM_animdata_get_context(C, &ac) == 0) {
3490 return OPERATOR_CANCELLED;
3491 }
3492
3493 ARegion *region = CTX_wm_region(C);
3494 if (UI_textbutton_activate_rna(C, region, ac.ads, "filter_text")) {
3495 /* Redraw to make sure it shows the cursor after activating */
3497 }
3498
3499 return OPERATOR_FINISHED;
3500}
3501
3503{
3504 /* identifiers */
3505 ot->name = "Filter Channels";
3506 ot->idname = "ANIM_OT_channels_select_filter";
3507 ot->description =
3508 "Start entering text which filters the set of channels shown to only include those with "
3509 "matching names";
3510
3511 /* callbacks */
3515}
3516
3519/* -------------------------------------------------------------------- */
3524{
3525 bAnimContext ac;
3526
3527 /* get editor data */
3528 if (ANIM_animdata_get_context(C, &ac) == 0) {
3529 return OPERATOR_CANCELLED;
3530 }
3531
3532 /* 'standard' behavior - check if selected, then apply relevant selection */
3533 const int action = RNA_enum_get(op->ptr, "action");
3534 switch (action) {
3535 case SEL_TOGGLE:
3537 break;
3538 case SEL_SELECT:
3540 break;
3541 case SEL_DESELECT:
3543 break;
3544 case SEL_INVERT:
3546 break;
3547 default:
3548 BLI_assert(0);
3549 break;
3550 }
3551
3552 /* send notifier that things have changed */
3554
3555 return OPERATOR_FINISHED;
3556}
3557
3559{
3560 /* identifiers */
3561 ot->name = "Select All";
3562 ot->idname = "ANIM_OT_channels_select_all";
3563 ot->description = "Toggle selection of all animation channels";
3564
3565 /* api callbacks */
3568
3569 /* flags */
3571
3572 /* properties */
3574}
3575
3578/* -------------------------------------------------------------------- */
3582static void box_select_anim_channels(bAnimContext *ac, const rcti &rect, short selectmode)
3583{
3584 ListBase anim_data = {nullptr, nullptr};
3585 int filter;
3586
3587 SpaceNla *snla = (SpaceNla *)ac->sl;
3588 View2D *v2d = &ac->region->v2d;
3589 rctf rectf;
3590
3591 /* convert border-region to view coordinates */
3592 UI_view2d_region_to_view(v2d, rect.xmin, rect.ymin + 2, &rectf.xmin, &rectf.ymin);
3593 UI_view2d_region_to_view(v2d, rect.xmax, rect.ymax - 2, &rectf.xmax, &rectf.ymax);
3594
3595 /* filter data */
3597
3599 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
3600
3601 float ymax;
3602 if (ac->datatype == ANIMCONT_NLA) {
3603 ymax = NLATRACK_FIRST_TOP(ac);
3604 }
3605 else {
3607 }
3608
3609 /* loop over data, doing box select */
3610 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
3611 float ymin;
3612
3613 if (ale->type == ANIMTYPE_GPDATABLOCK) {
3614 ymax -= ANIM_UI_get_channel_step();
3615 continue;
3616 }
3617
3618 if (ac->datatype == ANIMCONT_NLA) {
3619 ymin = ymax - NLATRACK_STEP(snla);
3620 }
3621 else {
3622 ymin = ymax - ANIM_UI_get_channel_step();
3623 }
3624
3625 /* if channel is within border-select region, alter it */
3626 if (ymax >= rectf.ymin && ymin <= rectf.ymax) {
3627 /* set selection flags only */
3629 ac, ale, ACHANNEL_SETTING_SELECT, eAnimChannels_SetFlag(selectmode));
3630
3631 /* type specific actions */
3632 switch (ale->type) {
3633 case ANIMTYPE_GROUP: {
3634 bActionGroup *agrp = (bActionGroup *)ale->data;
3635 select_pchan_for_action_group(ac, agrp, ale, true);
3636 /* always clear active flag after doing this */
3637 agrp->flag &= ~AGRP_ACTIVE;
3638 break;
3639 }
3640 case ANIMTYPE_NLATRACK: {
3641 NlaTrack *nlt = (NlaTrack *)ale->data;
3642
3643 /* for now, it's easier just to do this here manually, as defining a new type
3644 * currently adds complications when doing other stuff
3645 */
3646 ACHANNEL_SET_FLAG(nlt, selectmode, NLATRACK_SELECTED);
3647 break;
3648 }
3649 case ANIMTYPE_ACTION_SLOT: {
3650 using namespace blender::animrig;
3651 Slot *slot = static_cast<Slot *>(ale->data);
3653 break;
3654 }
3655 case ANIMTYPE_NONE:
3656 case ANIMTYPE_ANIMDATA:
3658 case ANIMTYPE_SUMMARY:
3659 case ANIMTYPE_SCENE:
3660 case ANIMTYPE_OBJECT:
3661 case ANIMTYPE_FCURVE:
3663 case ANIMTYPE_NLACURVE:
3665 case ANIMTYPE_FILLACTD:
3667 case ANIMTYPE_DSMAT:
3668 case ANIMTYPE_DSLAM:
3669 case ANIMTYPE_DSCAM:
3671 case ANIMTYPE_DSCUR:
3672 case ANIMTYPE_DSSKEY:
3673 case ANIMTYPE_DSWOR:
3674 case ANIMTYPE_DSNTREE:
3675 case ANIMTYPE_DSPART:
3676 case ANIMTYPE_DSMBALL:
3677 case ANIMTYPE_DSARM:
3678 case ANIMTYPE_DSMESH:
3679 case ANIMTYPE_DSTEX:
3680 case ANIMTYPE_DSLAT:
3682 case ANIMTYPE_DSSPK:
3683 case ANIMTYPE_DSGPENCIL:
3684 case ANIMTYPE_DSMCLIP:
3685 case ANIMTYPE_DSHAIR:
3687 case ANIMTYPE_DSVOLUME:
3688 case ANIMTYPE_SHAPEKEY:
3690 case ANIMTYPE_GPLAYER:
3695 case ANIMTYPE_MASKLAYER:
3696 case ANIMTYPE_NLAACTION:
3697 case ANIMTYPE_PALETTE:
3698 case ANIMTYPE_NUM_TYPES:
3699 break;
3700 }
3701 }
3702
3703 /* set minimum extent to be the maximum of the next channel */
3704 ymax = ymin;
3705 }
3706
3707 /* cleanup */
3708 ANIM_animdata_freelist(&anim_data);
3709}
3710
3712{
3713 bAnimContext ac;
3714 rcti rect;
3715 short selectmode = 0;
3716 const bool select = !RNA_boolean_get(op->ptr, "deselect");
3717 const bool extend = RNA_boolean_get(op->ptr, "extend");
3718
3719 /* get editor data */
3720 if (ANIM_animdata_get_context(C, &ac) == 0) {
3721 return OPERATOR_CANCELLED;
3722 }
3723
3724 /* get settings from operator */
3726
3727 if (!extend) {
3729 }
3730
3731 if (select) {
3732 selectmode = ACHANNEL_SETFLAG_ADD;
3733 }
3734 else {
3735 selectmode = ACHANNEL_SETFLAG_CLEAR;
3736 }
3737
3738 /* apply box_select animation channels */
3739 box_select_anim_channels(&ac, rect, selectmode);
3740
3741 /* send notifier that things have changed */
3743
3744 return OPERATOR_FINISHED;
3745}
3746
3748{
3749 /* identifiers */
3750 ot->name = "Box Select";
3751 ot->idname = "ANIM_OT_channels_select_box";
3752 ot->description = "Select all animation channels within the specified region";
3753
3754 /* api callbacks */
3759
3761
3762 /* flags */
3764
3765 /* rna */
3767}
3768
3771/* -------------------------------------------------------------------- */
3777static bool rename_anim_channels(bAnimContext *ac, int channel_index)
3778{
3779 ListBase anim_data = {nullptr, nullptr};
3780 const bAnimChannelType *acf;
3781 bAnimListElem *ale;
3782 int filter;
3783 bool success = false;
3784
3785 /* Filter relevant channels (note that grease-pencil/annotations are not displayed in Graph
3786 * Editor). */
3789 filter |= ANIMFILTER_FCURVESONLY;
3790 }
3792 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
3793
3794 /* Get channel that was clicked on from index. */
3795 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, channel_index));
3796 if (ale == nullptr) {
3797 /* channel not found */
3798 if (G.debug & G_DEBUG) {
3799 printf("Error: animation channel (index = %d) not found in rename_anim_channels()\n",
3800 channel_index);
3801 }
3802
3803 ANIM_animdata_freelist(&anim_data);
3804 return false;
3805 }
3806
3807 /* Don't allow renaming linked/liboverride channels. */
3808 if (ale->fcurve_owner_id != nullptr &&
3810 {
3811 ANIM_animdata_freelist(&anim_data);
3812 return false;
3813 }
3814 if (ale->id != nullptr) {
3815 if (!ID_IS_EDITABLE(ale->id)) {
3816 ANIM_animdata_freelist(&anim_data);
3817 return false;
3818 }
3819 /* There is one exception to not allowing renaming on liboverride channels: locally-inserted
3820 * NLA tracks. */
3821 if (ID_IS_OVERRIDE_LIBRARY(ale->id)) {
3822 switch (ale->type) {
3823 case ANIMTYPE_NLATRACK: {
3824 NlaTrack *nlt = (NlaTrack *)ale->data;
3825 if ((nlt->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0) {
3826 ANIM_animdata_freelist(&anim_data);
3827 return false;
3828 }
3829 break;
3830 }
3831 default:
3832 ANIM_animdata_freelist(&anim_data);
3833 return false;
3834 }
3835 }
3836 }
3837
3838 /* check that channel can be renamed */
3839 acf = ANIM_channel_get_typeinfo(ale);
3840 if (acf && acf->name_prop) {
3842 PropertyRNA *prop;
3843
3844 /* ok if we can get name property to edit from this channel */
3845 if (acf->name_prop(ale, &ptr, &prop)) {
3846 /* Actually showing the rename text-field is done on redraw,
3847 * so here we just store the index of this channel in the
3848 * dope-sheet data, which will get utilized when drawing the channel.
3849 *
3850 * +1 factor is for backwards compatibility issues. */
3851 if (ac->ads) {
3852 ac->ads->renameIndex = channel_index + 1;
3853 success = true;
3854 }
3855 }
3856 }
3857
3858 /* free temp data and tag for refresh */
3859 ANIM_animdata_freelist(&anim_data);
3861 return success;
3862}
3863
3864static int animchannels_channel_get(bAnimContext *ac, const int mval[2])
3865{
3866 ARegion *region;
3867 View2D *v2d;
3868 int channel_index;
3869 float x, y;
3870
3871 /* get useful pointers from animation context data */
3872 region = ac->region;
3873 v2d = &region->v2d;
3874
3875 /* Figure out which channel user clicked in. */
3876 UI_view2d_region_to_view(v2d, mval[0], mval[1], &x, &y);
3877
3878 if (ac->datatype == ANIMCONT_NLA) {
3879 SpaceNla *snla = (SpaceNla *)ac->sl;
3881 NLATRACK_STEP(snla),
3882 0,
3884 x,
3885 y,
3886 nullptr,
3887 &channel_index);
3888 }
3889 else {
3892 0,
3894 x,
3895 y,
3896 nullptr,
3897 &channel_index);
3898 }
3899
3900 return channel_index;
3901}
3902
3903static int animchannels_rename_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
3904{
3905 bAnimContext ac;
3906 int channel_index;
3907
3908 /* get editor data */
3909 if (ANIM_animdata_get_context(C, &ac) == 0) {
3910 return OPERATOR_CANCELLED;
3911 }
3912
3913 channel_index = animchannels_channel_get(&ac, event->mval);
3914
3915 /* handle click */
3916 if (rename_anim_channels(&ac, channel_index)) {
3918 return OPERATOR_FINISHED;
3919 }
3920
3921 /* allow event to be handled by selectall operator */
3922 return OPERATOR_PASS_THROUGH;
3923}
3924
3926{
3927 /* identifiers */
3928 ot->name = "Rename Channel";
3929 ot->idname = "ANIM_OT_channels_rename";
3930 ot->description = "Rename animation channel under mouse";
3931
3932 /* api callbacks */
3935}
3936
3939/* -------------------------------------------------------------------- */
3943/* Handle selection changes due to clicking on channels. Settings will get caught by UI code... */
3944
3946 const short /* eEditKeyframes_Select or -1 */ selectmode)
3947{
3948 Scene *sce = (Scene *)ale->data;
3949 AnimData *adt = sce->adt;
3950
3951 /* set selection status */
3952 if (selectmode == SELECT_INVERT) {
3953 /* swap select */
3954 sce->flag ^= SCE_DS_SELECTED;
3955 if (adt) {
3956 adt->flag ^= ADT_UI_SELECTED;
3957 }
3958 }
3959 else {
3960 sce->flag |= SCE_DS_SELECTED;
3961 if (adt) {
3962 adt->flag |= ADT_UI_SELECTED;
3963 }
3964 }
3965 return (ND_ANIMCHAN | NA_SELECTED);
3966}
3967
3968/* Return whether active channel of given type is present. */
3970{
3971 ListBase anim_data = anim_channels_for_selection(ac);
3972 bool is_active_found = false;
3973
3974 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
3975 if (ale->type != type) {
3976 continue;
3977 }
3978 is_active_found = ANIM_is_active_channel(ale);
3979 if (is_active_found) {
3980 break;
3981 }
3982 }
3983
3984 ANIM_animdata_freelist(&anim_data);
3985 return is_active_found;
3986}
3987
3988/* Select channels that lies between active channel and cursor_elem. */
3990{
3991 ListBase anim_data = anim_channels_for_selection(ac);
3992 bool in_selection_range = false;
3993
3994 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
3995
3996 /* Allow selection when active channel and `cursor_elem` are of same type. */
3997 if (ale->type != cursor_elem->type) {
3998 continue;
3999 }
4000
4001 const bool is_cursor_elem = (ale->data == cursor_elem->data);
4002 const bool is_active_elem = ANIM_is_active_channel(ale);
4003
4004 /* Restrict selection when active element is not found and group-channels are excluded from the
4005 * selection. */
4006 if (is_active_elem || is_cursor_elem) {
4007 /* Select first and last element from the range. Reverse selection status on extremes. */
4009 in_selection_range = !in_selection_range;
4010 if (ale->type == ANIMTYPE_GROUP) {
4011 select_pchan_for_action_group(ac, (bActionGroup *)ale->data, ale, false);
4012 }
4013 }
4014 else if (in_selection_range) {
4015 /* Select elements between the range. */
4017 if (ale->type == ANIMTYPE_GROUP) {
4018 select_pchan_for_action_group(ac, (bActionGroup *)ale->data, ale, false);
4019 }
4020 }
4021
4022 if (is_active_elem && is_cursor_elem) {
4023 /* Selection range is only one element when active channel and clicked channel are same. So
4024 * exit out of the loop when this condition is hit. */
4025 break;
4026 }
4027 }
4028
4029 ANIM_animdata_freelist(&anim_data);
4030}
4031
4033 bAnimContext *ac,
4034 bAnimListElem *ale,
4035 const short /* eEditKeyframes_Select or -1 */ selectmode)
4036{
4037 using namespace blender::ed;
4038 Scene *scene = ac->scene;
4039 ViewLayer *view_layer = ac->view_layer;
4040 Base *base = (Base *)ale->data;
4041 Object *ob = base->object;
4042 AnimData *adt = ob->adt;
4043
4044 if ((base->flag & BASE_SELECTABLE) == 0) {
4045 return 0;
4046 }
4047
4048 if (selectmode == SELECT_INVERT) {
4049 /* swap select */
4050 object::base_select(base, object::BA_INVERT);
4051
4052 if (adt) {
4053 adt->flag ^= ADT_UI_SELECTED;
4054 }
4055 }
4056 else if (selectmode == SELECT_EXTEND_RANGE) {
4058 animchannel_select_range(ac, ale);
4059 }
4060 else {
4061 /* deselect all */
4063 BKE_view_layer_synced_ensure(scene, view_layer);
4064 /* TODO: should this deselect all other types of channels too? */
4066 object::base_select(b, object::BA_DESELECT);
4067 if (b->object->adt) {
4068 b->object->adt->flag &= ~(ADT_UI_SELECTED | ADT_UI_ACTIVE);
4069 }
4070 }
4071
4072 /* select object now */
4073 object::base_select(base, object::BA_SELECT);
4074 if (adt) {
4075 adt->flag |= ADT_UI_SELECTED;
4076 }
4077 }
4078
4079 /* Change active object - regardless of whether it is now selected, see: #37883.
4080 *
4081 * Ensure we exit edit-mode on whatever object was active before
4082 * to avoid getting stuck there, see: #48747. */
4083 object::base_activate_with_mode_exit_if_needed(C, base); /* adds notifier */
4084
4085 /* Similar to outliner, do not change active element when selecting elements in range. */
4086 if ((adt) && (adt->flag & ADT_UI_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) {
4087 adt->flag |= ADT_UI_ACTIVE;
4088 }
4089
4090 return (ND_ANIMCHAN | NA_SELECTED);
4091}
4092
4094 bAnimListElem *ale,
4095 const short /* eEditKeyframes_Select or -1 */ selectmode)
4096{
4097 if (ale->adt == nullptr) {
4098 return 0;
4099 }
4100
4101 /* select/deselect */
4102 if (selectmode == SELECT_INVERT) {
4103 /* inverse selection status of this AnimData block only */
4104 ale->adt->flag ^= ADT_UI_SELECTED;
4105 }
4106 else if (selectmode == SELECT_EXTEND_RANGE) {
4108 animchannel_select_range(ac, ale);
4109 }
4110 else {
4111 /* select AnimData block by itself */
4113 ale->adt->flag |= ADT_UI_SELECTED;
4114 }
4115
4116 /* Similar to outliner, do not change active element when selecting elements in range. */
4117 if ((ale->adt->flag & ADT_UI_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) {
4118 ale->adt->flag |= ADT_UI_ACTIVE;
4119 }
4120
4121 return (ND_ANIMCHAN | NA_SELECTED);
4122}
4123
4125 bAnimListElem *ale,
4126 const short /* eEditKeyframes_Select or -1 */ selectmode,
4127 const int filter)
4128{
4129 bActionGroup *agrp = (bActionGroup *)ale->data;
4130 Object *ob = nullptr;
4131 bPoseChannel *pchan = nullptr;
4132
4133 /* Armatures-Specific Feature:
4134 * Since groups are used to collect F-Curves of the same Bone by default
4135 * (via Keying Sets) so that they can be managed better, we try to make
4136 * things here easier for animators by mapping group selection to bone
4137 * selection.
4138 *
4139 * Only do this if "Only Selected" dopesheet filter is not active, or else it
4140 * becomes too unpredictable/tricky to manage
4141 */
4142 if ((ac->ads->filterflag & ADS_FILTER_ONLYSEL) == 0) {
4143 if ((ale->id) && (GS(ale->id->name) == ID_OB)) {
4144 ob = (Object *)ale->id;
4145
4146 if (ob->type == OB_ARMATURE) {
4147 /* Assume for now that any group with corresponding name is what we want
4148 * (i.e. for an armature whose location is animated, things would break
4149 * if the user were to add a bone named "Location").
4150 *
4151 * TODO: check the first F-Curve or so to be sure...
4152 */
4153 pchan = BKE_pose_channel_find_name(ob->pose, agrp->name);
4154 }
4155 }
4156 }
4157
4158 /* select/deselect group */
4159 if (selectmode == SELECT_INVERT) {
4160 /* inverse selection status of this group only */
4161 agrp->flag ^= AGRP_SELECTED;
4162 }
4163 else if (selectmode == SELECT_EXTEND_RANGE) {
4165 animchannel_select_range(ac, ale);
4166 }
4167 else if (selectmode == -1) {
4168 /* select all in group (and deselect everything else) */
4169 FCurve *fcu;
4170
4171 /* deselect all other channels */
4173 if (pchan) {
4175 }
4176
4177 /* only select channels in group and group itself */
4178 for (fcu = static_cast<FCurve *>(agrp->channels.first); fcu && fcu->grp == agrp;
4179 fcu = fcu->next)
4180 {
4181 fcu->flag |= FCURVE_SELECTED;
4182 }
4183 agrp->flag |= AGRP_SELECTED;
4184 }
4185 else {
4186 /* select group by itself */
4188 if (pchan) {
4190 }
4191
4192 agrp->flag |= AGRP_SELECTED;
4193 }
4194
4195 /* if group is selected now, make group the 'active' one in the visible list.
4196 * Similar to outliner, do not change active element when selecting elements in range. */
4197 if (agrp->flag & AGRP_SELECTED) {
4198 if (selectmode != SELECT_EXTEND_RANGE) {
4200 ac->data,
4202 eAnimFilter_Flags(filter),
4203 agrp,
4205 if (pchan) {
4206 ED_pose_bone_select(ob, pchan, true, true);
4207 }
4208 }
4209 }
4210 else {
4211 if (selectmode != SELECT_EXTEND_RANGE) {
4213 ac->data,
4215 eAnimFilter_Flags(filter),
4216 nullptr,
4218 if (pchan) {
4219 ED_pose_bone_select(ob, pchan, false, true);
4220 }
4221 }
4222 }
4223
4224 return (ND_ANIMCHAN | NA_SELECTED);
4225}
4226
4228 bAnimListElem *ale,
4229 const short /* eEditKeyframes_Select or -1 */ selectmode,
4230 const int filter)
4231{
4232 FCurve *fcu = (FCurve *)ale->data;
4233
4234 /* select/deselect */
4235 if (selectmode == SELECT_INVERT) {
4236 /* inverse selection status of this F-Curve only */
4237 fcu->flag ^= FCURVE_SELECTED;
4238 }
4239 else if (selectmode == SELECT_EXTEND_RANGE) {
4241 animchannel_select_range(ac, ale);
4242 }
4243 else {
4244 /* select F-Curve by itself */
4246 fcu->flag |= FCURVE_SELECTED;
4247 }
4248
4249 /* if F-Curve is selected now, make F-Curve the 'active' one in the visible list.
4250 * Similar to outliner, do not change active element when selecting elements in range. */
4251 if ((fcu->flag & FCURVE_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) {
4253 ac->data,
4255 eAnimFilter_Flags(filter),
4256 fcu,
4257 eAnim_ChannelType(ale->type));
4258 }
4259
4260 return (ND_ANIMCHAN | NA_SELECTED);
4261}
4263 bAnimListElem *ale,
4264 short /* eEditKeyframes_Select or -1 */ selectmode)
4265{
4266 using namespace blender;
4267
4269 "fcurve_owner_id of an Action Slot should be an Action");
4270 animrig::Action *action = reinterpret_cast<animrig::Action *>(ale->fcurve_owner_id);
4271 animrig::Slot *slot = static_cast<animrig::Slot *>(ale->data);
4272
4273 if (selectmode == SELECT_INVERT) {
4274 selectmode = slot->is_selected() ? SELECT_SUBTRACT : SELECT_ADD;
4275 }
4276
4277 switch (selectmode) {
4278 case SELECT_REPLACE:
4281 case SELECT_ADD:
4282 slot->set_selected(true);
4283 action->slot_active_set(slot->handle);
4284 break;
4285 case SELECT_SUBTRACT:
4286 slot->set_selected(false);
4287 break;
4290 animchannel_select_range(ac, ale);
4291 break;
4292 case SELECT_INVERT:
4294 break;
4295 }
4296
4297 return (ND_ANIMCHAN | NA_SELECTED);
4298}
4299
4301 bAnimListElem *ale,
4302 const short /* eEditKeyframes_Select or -1 */ selectmode)
4303{
4304 KeyBlock *kb = (KeyBlock *)ale->data;
4305
4306 /* select/deselect */
4307 if (selectmode == SELECT_INVERT) {
4308 /* inverse selection status of this ShapeKey only */
4309 kb->flag ^= KEYBLOCK_SEL;
4310 }
4311 else {
4312 /* select ShapeKey by itself */
4314 kb->flag |= KEYBLOCK_SEL;
4315 }
4316
4317 return (ND_ANIMCHAN | NA_SELECTED);
4318}
4319
4321{
4322 AnimData *adt = (AnimData *)ale->data;
4323
4324 /* Toggle expand:
4325 * - Although the triangle widget already allows this,
4326 * since there's nothing else that can be done here now,
4327 * let's just use it for easier expand/collapse for now.
4328 */
4330
4331 return (ND_ANIMCHAN | NA_EDITED);
4332}
4333
4335{
4336 bGPdata *gpd = (bGPdata *)ale->data;
4337
4338 /* Toggle expand:
4339 * - Although the triangle widget already allows this,
4340 * the whole channel can also be used for this purpose.
4341 */
4342 gpd->flag ^= GP_DATA_EXPAND;
4343
4344 return (ND_ANIMCHAN | NA_EDITED);
4345}
4346
4348 bAnimContext *ac,
4349 bAnimListElem *ale,
4350 const short /* eEditKeyframes_Select or -1 */ selectmode,
4351 const int filter)
4352{
4353 bGPdata *gpd = (bGPdata *)ale->id;
4354 bGPDlayer *gpl = (bGPDlayer *)ale->data;
4355
4356 /* select/deselect */
4357 if (selectmode == SELECT_INVERT) {
4358 /* invert selection status of this layer only */
4359 gpl->flag ^= GP_LAYER_SELECT;
4360 }
4361 else if (selectmode == SELECT_EXTEND_RANGE) {
4363 animchannel_select_range(ac, ale);
4364 }
4365 else {
4366 /* select layer by itself */
4368 gpl->flag |= GP_LAYER_SELECT;
4369 }
4370
4371 /* change active layer, if this is selected (since we must always have an active layer).
4372 * Similar to outliner, do not change active element when selecting elements in range. */
4373 if ((gpl->flag & GP_LAYER_SELECT) && (selectmode != SELECT_EXTEND_RANGE)) {
4375 ac->data,
4377 eAnimFilter_Flags(filter),
4378 gpl,
4380 /* update other layer status */
4384 }
4385
4386 /* Grease Pencil updates */
4388 return (ND_ANIMCHAN | NA_EDITED); /* Animation Editors updates */
4389}
4390
4392{
4393 GreasePencil *grease_pencil = static_cast<GreasePencil *>(ale->data);
4394
4395 /* Toggle expand:
4396 * - Although the triangle widget already allows this,
4397 * the whole channel can also be used for this purpose.
4398 */
4399 grease_pencil->flag ^= GREASE_PENCIL_ANIM_CHANNEL_EXPANDED;
4400
4401 return (ND_ANIMCHAN | NA_EDITED);
4402}
4403
4405{
4406 GreasePencilLayerTreeGroup *layer_group = static_cast<GreasePencilLayerTreeGroup *>(ale->data);
4407
4408 /* Toggle expand:
4409 * - Although the triangle widget already allows this,
4410 * the whole channel can also be used for this purpose.
4411 */
4412 layer_group->base.flag ^= GP_LAYER_TREE_NODE_EXPANDED;
4413
4414 return (ND_ANIMCHAN | NA_EDITED);
4415}
4416
4418 bAnimContext *ac,
4419 bAnimListElem *ale,
4420 const short selectmode,
4421 const int /*filter*/)
4422{
4423 using namespace blender::bke::greasepencil;
4424 Layer *layer = static_cast<Layer *>(ale->data);
4425 GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(ale->id);
4426
4427 if (selectmode == SELECT_INVERT) {
4428 layer->set_selected(!layer->is_selected());
4429 }
4430 else if (selectmode == SELECT_EXTEND_RANGE) {
4432 animchannel_select_range(ac, ale);
4433 }
4434 else {
4436 layer->set_selected(true);
4437 }
4438
4439 /* Active channel is not changed during range select. */
4440 if (layer->is_selected() && (selectmode != SELECT_EXTEND_RANGE)) {
4441 grease_pencil->set_active_layer(layer);
4443 CTX_wm_message_bus(C), &grease_pencil->id, &grease_pencil, GreasePencilv3Layers, active);
4444 DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY);
4445 }
4446
4448 return (ND_ANIMCHAN | NA_EDITED);
4449}
4450
4452{
4453 Mask *mask = (Mask *)ale->data;
4454
4455 /* Toggle expand
4456 * - Although the triangle widget already allows this,
4457 * the whole channel can also be used for this purpose.
4458 */
4459 mask->flag ^= MASK_ANIMF_EXPAND;
4460
4461 return (ND_ANIMCHAN | NA_EDITED);
4462}
4463
4465 bAnimListElem *ale,
4466 const short /* eEditKeyframes_Select or -1 */ selectmode)
4467{
4468 MaskLayer *masklay = (MaskLayer *)ale->data;
4469
4470 /* select/deselect */
4471 if (selectmode == SELECT_INVERT) {
4472 /* invert selection status of this layer only */
4473 masklay->flag ^= MASK_LAYERFLAG_SELECT;
4474 }
4475 else {
4476 /* select layer by itself */
4478 masklay->flag |= MASK_LAYERFLAG_SELECT;
4479 }
4480
4481 return (ND_ANIMCHAN | NA_EDITED);
4482}
4483
4485 bAnimContext *ac,
4486 const int channel_index,
4487 short /* eEditKeyframes_Select or -1 */ selectmode)
4488{
4489 ListBase anim_data = {nullptr, nullptr};
4490 bAnimListElem *ale;
4491 int filter;
4492 int notifierFlags = 0;
4493 ScrArea *area = CTX_wm_area(C);
4494
4495 /* get the channel that was clicked on */
4496 /* filter channels */
4498 if (ELEM(area->spacetype, SPACE_NLA, SPACE_GRAPH)) {
4499 filter |= ANIMFILTER_FCURVESONLY;
4500 }
4502 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
4503
4504 /* get channel from index */
4505 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, channel_index));
4506 if (ale == nullptr) {
4507 /* channel not found */
4508 if (G.debug & G_DEBUG) {
4509 printf("Error: animation channel (index = %d) not found in mouse_anim_channels()\n",
4510 channel_index);
4511 }
4512
4513 ANIM_animdata_freelist(&anim_data);
4514 return 0;
4515 }
4516
4517 /* selectmode -1 is a special case for ActionGroups only,
4518 * which selects all of the channels underneath it only. */
4519 /* TODO: should this feature be extended to work with other channel types too? */
4520 if ((selectmode == -1) && (ale->type != ANIMTYPE_GROUP)) {
4521 /* normal channels should not behave normally in this case */
4522 ANIM_animdata_freelist(&anim_data);
4523 return 0;
4524 }
4525
4526 /* Change selection mode to single when no active element is found. */
4527 if ((selectmode == SELECT_EXTEND_RANGE) &&
4529 {
4530 selectmode = SELECT_INVERT;
4531 }
4532
4533 /* action to take depends on what channel we've got */
4534 /* WARNING: must keep this in sync with the equivalent function in `nla_tracks.cc`. */
4535 switch (ale->type) {
4536 case ANIMTYPE_SCENE:
4537 notifierFlags |= click_select_channel_scene(ale, selectmode);
4538 break;
4539 case ANIMTYPE_OBJECT:
4540 notifierFlags |= click_select_channel_object(C, ac, ale, selectmode);
4541 break;
4542 case ANIMTYPE_FILLACTD: /* Action Expander */
4543 case ANIMTYPE_FILLACT_LAYERED: /* Animation Expander */
4544 case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
4545 case ANIMTYPE_DSLAM:
4546 case ANIMTYPE_DSCAM:
4548 case ANIMTYPE_DSCUR:
4549 case ANIMTYPE_DSSKEY:
4550 case ANIMTYPE_DSWOR:
4551 case ANIMTYPE_DSPART:
4552 case ANIMTYPE_DSMBALL:
4553 case ANIMTYPE_DSARM:
4554 case ANIMTYPE_DSMESH:
4555 case ANIMTYPE_DSNTREE:
4556 case ANIMTYPE_DSTEX:
4557 case ANIMTYPE_DSLAT:
4559 case ANIMTYPE_DSSPK:
4560 case ANIMTYPE_DSGPENCIL:
4561 case ANIMTYPE_DSMCLIP:
4562 case ANIMTYPE_DSHAIR:
4564 case ANIMTYPE_DSVOLUME:
4565 notifierFlags |= click_select_channel_dummy(ac, ale, selectmode);
4566 break;
4567 case ANIMTYPE_GROUP:
4568 notifierFlags |= click_select_channel_group(ac, ale, selectmode, filter);
4569 break;
4570 case ANIMTYPE_FCURVE:
4571 case ANIMTYPE_NLACURVE:
4572 notifierFlags |= click_select_channel_fcurve(ac, ale, selectmode, filter);
4573 break;
4575 notifierFlags |= click_select_channel_action_slot(ac, ale, selectmode);
4576 break;
4577 case ANIMTYPE_SHAPEKEY:
4578 notifierFlags |= click_select_channel_shapekey(ac, ale, selectmode);
4579 break;
4581 notifierFlags |= click_select_channel_nlacontrols(ale);
4582 break;
4584 notifierFlags |= click_select_channel_gpdatablock(ale);
4585 break;
4586 case ANIMTYPE_GPLAYER:
4587 notifierFlags |= click_select_channel_gplayer(C, ac, ale, selectmode, filter);
4588 break;
4591 break;
4594 break;
4596 notifierFlags |= click_select_channel_grease_pencil_layer(C, ac, ale, selectmode, filter);
4597 break;
4599 notifierFlags |= click_select_channel_maskdatablock(ale);
4600 break;
4601 case ANIMTYPE_MASKLAYER:
4602 notifierFlags |= click_select_channel_masklayer(ac, ale, selectmode);
4603 break;
4604 default:
4605 if (G.debug & G_DEBUG) {
4606 printf("Error: Invalid channel type in mouse_anim_channels()\n");
4607 }
4608 break;
4609 }
4610
4611 /* free channels */
4612 ANIM_animdata_freelist(&anim_data);
4613
4614 /* return notifier flags */
4615 return notifierFlags;
4616}
4617
4620/* -------------------------------------------------------------------- */
4626{
4627 bAnimContext ac;
4628 ARegion *region;
4629 View2D *v2d;
4630 int channel_index;
4631 int notifierFlags = 0;
4632 short selectmode;
4633 float x, y;
4634
4635 /* get editor data */
4636 if (ANIM_animdata_get_context(C, &ac) == 0) {
4637 return OPERATOR_CANCELLED;
4638 }
4639
4640 /* get useful pointers from animation context data */
4641 region = ac.region;
4642 v2d = &region->v2d;
4643
4644 /* select mode is either replace (deselect all, then add) or add/extend */
4645 if (RNA_boolean_get(op->ptr, "extend")) {
4646 selectmode = SELECT_INVERT;
4647 }
4648 else if (RNA_boolean_get(op->ptr, "extend_range")) {
4649 selectmode = SELECT_EXTEND_RANGE;
4650 }
4651 else if (RNA_boolean_get(op->ptr, "children_only")) {
4652 /* this is a bit of a special case for ActionGroups only...
4653 * should it be removed or extended to all instead? */
4654 selectmode = -1;
4655 }
4656 else {
4657 selectmode = SELECT_REPLACE;
4658 }
4659
4660 /* figure out which channel user clicked in */
4661 UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &x, &y);
4664 0,
4666 x,
4667 y,
4668 nullptr,
4669 &channel_index);
4670
4671 /* handle mouse-click in the relevant channel then */
4672 notifierFlags = mouse_anim_channels(C, &ac, channel_index, selectmode);
4673
4674 /* set notifier that things have changed */
4675 WM_event_add_notifier(C, NC_ANIMATION | notifierFlags, nullptr);
4676
4678 event);
4679}
4680
4682{
4683 PropertyRNA *prop;
4684
4685 /* identifiers */
4686 ot->name = "Mouse Click on Channels";
4687 ot->idname = "ANIM_OT_channels_click";
4688 ot->description = "Handle mouse clicks over animation channels";
4689
4690 /* api callbacks */
4693
4694 /* flags */
4695 ot->flag = OPTYPE_UNDO;
4696
4697 /* properties */
4698 /* NOTE: don't save settings, otherwise, can end up with some weird behavior (sticky extend)
4699 *
4700 * Key-map: Enable with `Shift`. */
4701 prop = RNA_def_boolean(ot->srna, "extend", false, "Extend Select", "");
4703
4704 prop = RNA_def_boolean(ot->srna,
4705 "extend_range",
4706 false,
4707 "Extend Range",
4708 "Selection of active channel to clicked channel");
4710
4711 /* Key-map: Enable with `Ctrl-Shift`. */
4712 prop = RNA_def_boolean(ot->srna, "children_only", false, "Select Children Only", "");
4714}
4715
4716static bool select_anim_channel_keys(bAnimContext *ac, int channel_index, bool extend)
4717{
4718 ListBase anim_data = {nullptr, nullptr};
4719 bAnimListElem *ale;
4720 int filter;
4721 bool success = false;
4722 FCurve *fcu;
4723 int i;
4724
4725 /* get the channel that was clicked on */
4726 /* filter channels */
4730 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
4731
4732 /* get channel from index */
4733 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, channel_index));
4734 if (ale == nullptr) {
4735 /* channel not found */
4736 if (G.debug & G_DEBUG) {
4737 printf("Error: animation channel (index = %d) not found in rename_anim_channels()\n",
4738 channel_index);
4739 }
4740
4741 ANIM_animdata_freelist(&anim_data);
4742 return false;
4743 }
4744
4745 /* Only FCuves can have their keys selected. */
4746 if (ale->datatype != ALE_FCURVE) {
4747 ANIM_animdata_freelist(&anim_data);
4748 return false;
4749 }
4750
4751 fcu = (FCurve *)ale->key_data;
4752 success = (fcu != nullptr);
4753
4754 ANIM_animdata_freelist(&anim_data);
4755
4756 /* F-Curve may not have any keyframes */
4757 if (fcu && fcu->bezt) {
4758 BezTriple *bezt;
4759
4760 if (!extend) {
4761 filter = (ANIMFILTER_DATA_VISIBLE);
4763 ac, &anim_data, eAnimFilter_Flags(filter), ac->data, eAnimCont_Types(ac->datatype));
4764 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
4765 FCurve *fcu_inner = (FCurve *)ale->key_data;
4766
4767 if (fcu_inner != nullptr && fcu_inner->bezt != nullptr) {
4768 for (i = 0, bezt = fcu_inner->bezt; i < fcu_inner->totvert; i++, bezt++) {
4769 bezt->f2 = bezt->f1 = bezt->f3 = 0;
4770 }
4771 }
4772 }
4773
4774 ANIM_animdata_freelist(&anim_data);
4775 }
4776
4777 for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
4778 bezt->f2 = bezt->f1 = bezt->f3 = SELECT;
4779 }
4780 }
4781
4782 /* free temp data and tag for refresh */
4784 return success;
4785}
4786
4788 wmOperator *op,
4789 const wmEvent *event)
4790{
4791 bAnimContext ac;
4792 int channel_index;
4793 bool extend = RNA_boolean_get(op->ptr, "extend");
4794
4795 /* get editor data */
4796 if (ANIM_animdata_get_context(C, &ac) == 0) {
4797 return OPERATOR_CANCELLED;
4798 }
4799
4800 channel_index = animchannels_channel_get(&ac, event->mval);
4801
4802 /* handle click */
4803 if (select_anim_channel_keys(&ac, channel_index, extend)) {
4805 return OPERATOR_FINISHED;
4806 }
4807
4808 /* allow event to be handled by selectall operator */
4809 return OPERATOR_PASS_THROUGH;
4810}
4811
4813{
4814 PropertyRNA *prop;
4815
4816 /* identifiers */
4817 ot->name = "Select Channel Keyframes";
4818 ot->idname = "ANIM_OT_channel_select_keys";
4819 ot->description = "Select all keyframes of channel under mouse";
4820
4821 /* api callbacks */
4824
4825 ot->flag = OPTYPE_UNDO;
4826
4827 prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection");
4829}
4830
4833/* -------------------------------------------------------------------- */
4837static void get_view_range(Scene *scene, const bool use_preview_range, float r_range[2])
4838{
4839 if (use_preview_range && scene->r.flag & SCER_PRV_RANGE) {
4840 r_range[0] = scene->r.psfra;
4841 r_range[1] = scene->r.pefra;
4842 }
4843 else {
4844 r_range[0] = scene->r.sfra;
4845 r_range[1] = scene->r.efra;
4846 }
4847}
4848
4850{
4851 bAnimContext ac;
4852
4853 /* Get editor data. */
4854 if (ANIM_animdata_get_context(C, &ac) == 0) {
4855 return OPERATOR_CANCELLED;
4856 }
4858
4859 if (!window_region) {
4860 return OPERATOR_CANCELLED;
4861 }
4862
4863 ListBase anim_data = {nullptr, nullptr};
4866 size_t anim_data_length = ANIM_animdata_filter(
4867 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
4868
4869 if (anim_data_length == 0) {
4870 WM_report(RPT_WARNING, "No channels to operate on");
4871 return OPERATOR_CANCELLED;
4872 }
4873
4874 float range[2];
4875 const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
4876 get_view_range(ac.scene, use_preview_range, range);
4877
4878 rctf bounds{};
4879 bounds.xmin = FLT_MAX;
4880 bounds.xmax = -FLT_MAX;
4881 bounds.ymin = FLT_MAX;
4882 bounds.ymax = -FLT_MAX;
4883
4884 const bool include_handles = RNA_boolean_get(op->ptr, "include_handles");
4885
4886 bool valid_bounds = false;
4887 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
4888 rctf channel_bounds;
4889 const bool found_bounds = get_channel_bounds(
4890 &ac, ale, range, include_handles, &channel_bounds);
4891 if (found_bounds) {
4892 BLI_rctf_union(&bounds, &channel_bounds);
4893 valid_bounds = true;
4894 }
4895 }
4896
4897 if (!valid_bounds) {
4898 ANIM_animdata_freelist(&anim_data);
4899 WM_report(RPT_WARNING, "No keyframes to focus on");
4900 return OPERATOR_CANCELLED;
4901 }
4902
4903 add_region_padding(C, window_region, &bounds);
4904
4905 if (ac.spacetype == SPACE_ACTION) {
4906 bounds.ymin = window_region->v2d.cur.ymin;
4907 bounds.ymax = window_region->v2d.cur.ymax;
4908 }
4909
4910 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
4911 UI_view2d_smooth_view(C, window_region, &bounds, smooth_viewtx);
4912
4913 ANIM_animdata_freelist(&anim_data);
4914
4915 return OPERATOR_FINISHED;
4916}
4917
4922
4924{
4925 /* Identifiers */
4926 ot->name = "Frame Selected Channels";
4927 ot->idname = "ANIM_OT_channels_view_selected";
4928 ot->description = "Reset viewable area to show the selected channels";
4929
4930 /* API callbacks */
4933
4934 ot->flag = 0;
4935
4937 "include_handles",
4938 true,
4939 "Include Handles",
4940 "Include handles of keyframes when calculating extents");
4941
4943 "use_preview_range",
4944 true,
4945 "Use Preview Range",
4946 "Ignore frames outside of the preview range");
4947}
4948
4950{
4951 bAnimContext ac;
4952
4953 if (ANIM_animdata_get_context(C, &ac) == 0) {
4954 return OPERATOR_CANCELLED;
4955 }
4956
4958
4959 if (!window_region) {
4960 return OPERATOR_CANCELLED;
4961 }
4962
4963 ListBase anim_data = {nullptr, nullptr};
4967 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
4968
4969 bAnimListElem *ale;
4970 const int channel_index = animchannels_channel_get(&ac, event->mval);
4971 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, channel_index));
4972 if (ale == nullptr) {
4973 ANIM_animdata_freelist(&anim_data);
4974 return OPERATOR_CANCELLED;
4975 }
4976
4977 float range[2];
4978 const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
4979
4980 get_view_range(ac.scene, use_preview_range, range);
4981
4982 rctf bounds;
4983 const bool include_handles = RNA_boolean_get(op->ptr, "include_handles");
4984 const bool found_bounds = get_channel_bounds(&ac, ale, range, include_handles, &bounds);
4985
4986 if (!found_bounds) {
4987 ANIM_animdata_freelist(&anim_data);
4988 WM_report(RPT_WARNING, "No keyframes to focus on");
4989 return OPERATOR_CANCELLED;
4990 }
4991
4992 add_region_padding(C, window_region, &bounds);
4993
4994 if (ac.spacetype == SPACE_ACTION) {
4995 bounds.ymin = window_region->v2d.cur.ymin;
4996 bounds.ymax = window_region->v2d.cur.ymax;
4997 }
4998
4999 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
5000 UI_view2d_smooth_view(C, window_region, &bounds, smooth_viewtx);
5001
5002 ANIM_animdata_freelist(&anim_data);
5003
5004 return OPERATOR_FINISHED;
5005}
5006
5008{
5009 /* Identifiers */
5010 ot->name = "Frame Channel Under Cursor";
5011 ot->idname = "ANIM_OT_channel_view_pick";
5012 ot->description = "Reset viewable area to show the channel under the cursor";
5013
5014 /* API callbacks */
5017
5018 ot->flag = 0;
5019
5021 "include_handles",
5022 true,
5023 "Include Handles",
5024 "Include handles of keyframes when calculating extents");
5025
5027 "use_preview_range",
5028 true,
5029 "Use Preview Range",
5030 "Ignore frames outside of the preview range");
5031}
5032
5034 {BEZT_IPO_BEZ, "BEZIER", 0, "Bézier", "New keys will be Bézier"},
5035 {BEZT_IPO_LIN, "LIN", 0, "Linear", "New keys will be linear"},
5036 {BEZT_IPO_CONST, "CONST", 0, "Constant", "New keys will be constant"},
5037 {0, nullptr, 0, nullptr, nullptr},
5038};
5039
5041{
5042 using namespace blender::animrig;
5043 bAnimContext ac;
5044
5045 /* Get editor data. */
5046 if (ANIM_animdata_get_context(C, &ac) == 0) {
5047 return OPERATOR_CANCELLED;
5048 }
5049
5050 ListBase anim_data = {nullptr, nullptr};
5053 size_t anim_data_length = ANIM_animdata_filter(
5054 &ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
5055
5056 if (anim_data_length == 0) {
5057 WM_report(RPT_WARNING, "No channels to operate on");
5058 return OPERATOR_CANCELLED;
5059 }
5060
5061 Scene *scene = CTX_data_scene(C);
5062
5063 /* The range will default to the scene or preview range, but only if it hasn't been set before.
5064 * If a range is set here, the redo panel wouldn't work properly because the range would
5065 * constantly be overridden. */
5066 blender::int2 frame_range;
5067 RNA_int_get_array(op->ptr, "range", frame_range);
5068 if (frame_range[1] < frame_range[0]) {
5069 frame_range[1] = frame_range[0];
5070 }
5071 const float step = RNA_float_get(op->ptr, "step");
5072 if (frame_range[0] == 0 && frame_range[1] == 0) {
5073 if (scene->r.flag & SCER_PRV_RANGE) {
5074 frame_range = {scene->r.psfra, scene->r.pefra};
5075 }
5076 else {
5077 frame_range = {scene->r.sfra, scene->r.efra};
5078 }
5079 RNA_int_set_array(op->ptr, "range", frame_range);
5080 }
5081
5082 const bool remove_outside_range = RNA_boolean_get(op->ptr, "remove_outside_range");
5083 const BakeCurveRemove remove_existing = remove_outside_range ? BakeCurveRemove::ALL :
5084 BakeCurveRemove::IN_RANGE;
5085 const int interpolation_type = RNA_enum_get(op->ptr, "interpolation_type");
5086 const bool bake_modifiers = RNA_boolean_get(op->ptr, "bake_modifiers");
5087
5088 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
5089 FCurve *fcu = static_cast<FCurve *>(ale->data);
5090 if (!fcu->bezt) {
5091 continue;
5092 }
5093 AnimData *adt = ANIM_nla_mapping_get(&ac, ale);
5094 blender::int2 nla_mapped_range;
5095 nla_mapped_range[0] = int(BKE_nla_tweakedit_remap(adt, frame_range[0], NLATIME_CONVERT_UNMAP));
5096 nla_mapped_range[1] = int(BKE_nla_tweakedit_remap(adt, frame_range[1], NLATIME_CONVERT_UNMAP));
5097 /* Save current state of modifier flags so they can be reapplied after baking. */
5098 blender::Vector<short> modifier_flags;
5099 if (!bake_modifiers) {
5100 LISTBASE_FOREACH (FModifier *, modifier, &fcu->modifiers) {
5101 modifier_flags.append(modifier->flag);
5102 modifier->flag |= FMODIFIER_FLAG_MUTED;
5103 }
5104 }
5105
5106 bool replace;
5107 const int last_index = BKE_fcurve_bezt_binarysearch_index(
5108 fcu->bezt, nla_mapped_range[1], fcu->totvert, &replace);
5109
5110 /* Since the interpolation of a key defines the curve following it, the last key in the baked
5111 * segment needs to keep the interpolation mode that existed previously so the curve isn't
5112 * changed. */
5113 const char segment_end_interpolation = fcu->bezt[min_ii(last_index, fcu->totvert - 1)].ipo;
5114
5115 bake_fcurve(fcu, nla_mapped_range, step, remove_existing);
5116
5117 if (bake_modifiers) {
5119 }
5120 else {
5121 int modifier_index = 0;
5122 LISTBASE_FOREACH (FModifier *, modifier, &fcu->modifiers) {
5123 modifier->flag = modifier_flags[modifier_index];
5124 modifier_index++;
5125 }
5126 }
5127
5128 for (int i = 0; i < fcu->totvert; i++) {
5129 BezTriple *key = &fcu->bezt[i];
5130 if (key->vec[1][0] < nla_mapped_range[0]) {
5131 continue;
5132 }
5133 if (key->vec[1][0] > nla_mapped_range[1]) {
5134 fcu->bezt[max_ii(i - 1, 0)].ipo = segment_end_interpolation;
5135 break;
5136 }
5137 key->ipo = interpolation_type;
5138 }
5139 }
5140
5141 ANIM_animdata_freelist(&anim_data);
5143
5144 return OPERATOR_FINISHED;
5145}
5146
5148{
5149 /* Identifiers */
5150 ot->name = "Bake Channels";
5151 ot->idname = "ANIM_OT_channels_bake";
5152 ot->description =
5153 "Create keyframes following the current shape of F-Curves of selected channels";
5154
5155 /* API callbacks */
5158
5161 "range",
5162 2,
5163 nullptr,
5164 INT_MIN,
5165 INT_MAX,
5166 "Frame Range",
5167 "The range in which to create new keys",
5168 0,
5169 INT_MAX);
5170
5172 "step",
5173 1.0f,
5174 0.01f,
5175 FLT_MAX,
5176 "Frame Step",
5177 "At which interval to add keys",
5178 1.0f,
5179 16.0f);
5180
5182 "remove_outside_range",
5183 false,
5184 "Remove Outside Range",
5185 "Removes keys outside the given range, leaving only the newly baked");
5186
5188 "interpolation_type",
5191 "Interpolation Type",
5192 "Choose the interpolation type with which new keys will be added");
5193
5195 "bake_modifiers",
5196 true,
5197 "Bake Modifiers",
5198 "Bake Modifiers into keyframes and delete them after");
5199}
5200
5201#ifdef WITH_ANIM_BAKLAVA
5202
5203static int slot_channels_move_to_new_action_exec(bContext *C, wmOperator * /* op */)
5204{
5205 using namespace blender::animrig;
5206 bAnimContext ac;
5207
5208 /* Get editor data. */
5209 if (ANIM_animdata_get_context(C, &ac) == 0) {
5210 return OPERATOR_CANCELLED;
5211 }
5212
5213 ListBase anim_data = {nullptr, nullptr};
5216
5217 size_t anim_data_length = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
5218
5219 if (anim_data_length == 0) {
5220 WM_report(RPT_WARNING, "No channels to operate on");
5221 return OPERATOR_CANCELLED;
5222 }
5223
5225 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
5226 if (ale->type != ANIMTYPE_ACTION_SLOT) {
5227 continue;
5228 }
5229 BLI_assert(GS(ale->fcurve_owner_id->name) == ID_AC);
5230 bAction *owning_action = reinterpret_cast<bAction *>(ale->fcurve_owner_id);
5231 slots.append({reinterpret_cast<Slot *>(ale->data), owning_action});
5232 }
5233 ANIM_animdata_freelist(&anim_data);
5234
5235 if (slots.size() == 0) {
5236 WM_report(RPT_WARNING, "None of the selected channels is an Action Slot");
5237 return OPERATOR_CANCELLED;
5238 }
5239
5240 /* If multiple slots are selected they are moved to the new action together. In that case it is
5241 * hard to determine a name, so a constant default is used. */
5242 Action *target_action;
5243 Main *bmain = CTX_data_main(C);
5244 if (slots.size() == 1) {
5245 char actname[MAX_ID_NAME - 2];
5246 SNPRINTF(actname, DATA_("%sAction"), slots[0].first->name + 2);
5247 target_action = &action_add(*bmain, actname);
5248 }
5249 else {
5250 target_action = &action_add(*bmain, DATA_("CombinedAction"));
5251 }
5252
5253 Layer &layer = target_action->layer_add(std::nullopt);
5254 layer.strip_add(*target_action, Strip::Type::Keyframe);
5255
5256 for (std::pair<Slot *, bAction *> &slot_data : slots) {
5257 Action &source_action = slot_data.second->wrap();
5258 move_slot(*bmain, *slot_data.first, source_action, *target_action);
5260 }
5261
5265
5266 return OPERATOR_FINISHED;
5267}
5268
5269static bool slot_channels_move_to_new_action_poll(bContext *C)
5270{
5271 SpaceAction *space_action = CTX_wm_space_action(C);
5272 if (!space_action) {
5273 return false;
5274 }
5275 if (!space_action->action) {
5276 CTX_wm_operator_poll_msg_set(C, "No active action to operate on");
5277 return false;
5278 }
5279 if (!space_action->action->wrap().is_action_layered()) {
5280 CTX_wm_operator_poll_msg_set(C, "Active action is not layered");
5281 return false;
5282 }
5283 return true;
5284}
5285
5286static void ANIM_OT_slot_channels_move_to_new_action(wmOperatorType *ot)
5287{
5288 ot->name = "Move Slots to new Action";
5289 ot->idname = "ANIM_OT_slot_channels_move_to_new_action";
5290 ot->description = "Move the selected slots into a newly created action";
5291
5292 ot->exec = slot_channels_move_to_new_action_exec;
5293 ot->poll = slot_channels_move_to_new_action_poll;
5294
5296}
5297
5298static int separate_slots_exec(bContext *C, wmOperator * /* op */)
5299{
5300 using namespace blender::animrig;
5301 Object *active_object = CTX_data_active_object(C);
5302 /* Checked by the poll function. */
5303 BLI_assert(active_object != nullptr);
5304
5305 Action *action = get_action(active_object->id);
5306 /* Also checked by the poll function. */
5307 BLI_assert(action != nullptr);
5308
5309 Main *bmain = CTX_data_main(C);
5310 while (action->slot_array_num) {
5311 Slot *slot = action->slot(action->slot_array_num - 1);
5312 char actname[MAX_ID_NAME - 2];
5313 SNPRINTF(actname, DATA_("%sAction"), slot->name + 2);
5314 Action &target_action = action_add(*bmain, actname);
5315 Layer &layer = target_action.layer_add(std::nullopt);
5316 layer.strip_add(target_action, Strip::Type::Keyframe);
5317 move_slot(*bmain, *slot, *action, target_action);
5319 }
5320
5324
5325 return OPERATOR_FINISHED;
5326}
5327
5328static bool separate_slots_poll(bContext *C)
5329{
5330 Object *active_object = CTX_data_active_object(C);
5331 if (!active_object) {
5332 CTX_wm_operator_poll_msg_set(C, "No active object");
5333 return false;
5334 }
5335
5337 if (!action) {
5338 CTX_wm_operator_poll_msg_set(C, "Active object isn't animated");
5339 return false;
5340 }
5341 if (!action->is_action_layered()) {
5342 return false;
5343 }
5344 return true;
5345}
5346
5347static void ANIM_OT_separate_slots(wmOperatorType *ot)
5348{
5349 ot->name = "Separate Slots";
5350 ot->idname = "ANIM_OT_separate_slots";
5351 ot->description =
5352 "Move all slots of the action on the active object into newly created, separate actions. "
5353 "All users of those slots will be reassigned to the new actions. The current action won't "
5354 "be deleted but will be empty and might end up having zero users";
5355
5356 ot->exec = separate_slots_exec;
5357 ot->poll = separate_slots_poll;
5358
5360}
5361
5362#endif /* WITH_ANIM_BAKLAVA */
5363
5368 wmWindow **r_win,
5369 ScrArea **r_area,
5370 ARegion **r_region)
5371{
5372 LISTBASE_FOREACH (wmWindow *, win, &CTX_wm_manager(C)->windows) {
5373 bScreen *screen = WM_window_get_active_screen(win);
5374
5375 LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
5376 if (area->spacetype != SPACE_GRAPH) {
5377 continue;
5378 }
5380 if (!region) {
5381 continue;
5382 }
5383
5384 *r_win = win;
5385 *r_area = area;
5386 *r_region = region;
5387 return true;
5388 }
5389 }
5390 return false;
5391}
5392
5393static void deselect_all_fcurves(bAnimContext *ac, const bool hide)
5394{
5395 ListBase anim_data = {nullptr, nullptr};
5398 ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
5399
5400 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
5401 FCurve *fcu = (FCurve *)ale->key_data;
5402 fcu->flag &= ~FCURVE_SELECTED;
5403 fcu->flag &= ~FCURVE_ACTIVE;
5404 if (hide) {
5405 fcu->flag &= ~FCURVE_VISIBLE;
5406 }
5407 }
5408
5409 ANIM_animdata_freelist(&anim_data);
5410}
5411
5413{
5414 ListBase anim_data = {nullptr, nullptr};
5415 if (ac->sl->spacetype != SPACE_GRAPH) {
5416 return 0;
5417 }
5418 SpaceGraph *sipo = (SpaceGraph *)ac->sl;
5419 const eAnimFilter_Flags filter = eAnimFilter_Flags(sipo->ads->filterflag);
5420 ANIM_animdata_filter(ac, &anim_data, filter, ac->data, eAnimCont_Types(ac->datatype));
5421
5422 /* Adding FCurves to a map for quicker lookup times. */
5423 blender::Map<FCurve *, bool> filtered_fcurves;
5424 LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
5425 FCurve *fcu = (FCurve *)ale->key_data;
5426 filtered_fcurves.add(fcu, true);
5427 }
5428
5429 int hidden_fcurve_count = fcurves.size();
5430 for (FCurve *fcu : fcurves) {
5431 if (filtered_fcurves.contains(fcu)) {
5432 hidden_fcurve_count--;
5433 }
5434 }
5435 ANIM_animdata_freelist(&anim_data);
5436 return hidden_fcurve_count;
5437}
5438
5440 ID *id, PointerRNA *ptr, PropertyRNA *prop, const bool whole_array, const int index)
5441{
5442 using namespace blender;
5443
5444 AnimData *anim_data = BKE_animdata_from_id(id);
5445 if (anim_data == nullptr) {
5446 return Vector<FCurve *>();
5447 }
5448
5449 const std::optional<std::string> path = RNA_path_from_ID_to_property(ptr, prop);
5450
5452 if (RNA_property_array_check(prop) && whole_array) {
5453 const int length = RNA_property_array_length(ptr, prop);
5454 for (int i = 0; i < length; i++) {
5456 anim_data, path->c_str(), i, nullptr, nullptr);
5457 if (fcurve != nullptr) {
5458 fcurves.append(fcurve);
5459 }
5460 }
5461 }
5462 else {
5464 anim_data, path->c_str(), index, nullptr, nullptr);
5465 if (fcurve != nullptr) {
5466 fcurves.append(fcurve);
5467 }
5468 }
5469 return fcurves;
5470}
5471
5473 Scene *scene,
5474 ID *id,
5475 const blender::Span<FCurve *> fcurves)
5476{
5477 rctf bounds;
5478 bounds.xmin = INFINITY;
5479 bounds.xmax = -INFINITY;
5480 bounds.ymin = INFINITY;
5481 bounds.ymax = -INFINITY;
5482
5483 if (space_link->spacetype != SPACE_GRAPH) {
5484 return bounds;
5485 }
5486
5487 AnimData *anim_data = BKE_animdata_from_id(id);
5488 if (anim_data == nullptr) {
5489 return bounds;
5490 }
5491
5492 float frame_range[2];
5493 get_view_range(scene, true, frame_range);
5494 float mapped_frame_range[2];
5495 mapped_frame_range[0] = BKE_nla_tweakedit_remap(
5496 anim_data, frame_range[0], NLATIME_CONVERT_UNMAP);
5497 mapped_frame_range[1] = BKE_nla_tweakedit_remap(
5498 anim_data, frame_range[1], NLATIME_CONVERT_UNMAP);
5499
5500 const bool include_handles = false;
5501
5502 for (FCurve *fcurve : fcurves) {
5503 fcurve->flag |= (FCURVE_SELECTED | FCURVE_VISIBLE);
5504 rctf fcu_bounds;
5506 anim_data,
5507 space_link,
5508 scene,
5509 id,
5510 include_handles,
5511 mapped_frame_range,
5512 &fcu_bounds);
5513
5514 if (BLI_rctf_is_valid(&fcu_bounds)) {
5515 BLI_rctf_union(&bounds, &fcu_bounds);
5516 }
5517 }
5518
5519 return bounds;
5520}
5521
5523 blender::Span<PointerRNA> selection,
5524 PropertyRNA *prop,
5525 const blender::StringRefNull id_to_prop_path,
5526 const int index,
5527 const bool whole_array,
5528 int *r_filtered_fcurve_count)
5529{
5530 rctf bounds;
5531 bounds.xmin = INFINITY;
5532 bounds.xmax = -INFINITY;
5533 bounds.ymin = INFINITY;
5534 bounds.ymax = -INFINITY;
5535
5536 for (const PointerRNA &selected : selection) {
5537 ID *selected_id = selected.owner_id;
5538 if (!BKE_animdata_id_is_animated(selected_id)) {
5539 continue;
5540 }
5541 PointerRNA resolved_ptr;
5542 PropertyRNA *resolved_prop;
5543 if (!id_to_prop_path.is_empty()) {
5544 const bool resolved = RNA_path_resolve_property(
5545 &selected, id_to_prop_path.c_str(), &resolved_ptr, &resolved_prop);
5546 if (!resolved) {
5547 continue;
5548 }
5549 }
5550 else {
5551 resolved_ptr = selected;
5552 resolved_prop = prop;
5553 }
5555 selected_id, &resolved_ptr, resolved_prop, whole_array, index);
5556 *r_filtered_fcurve_count += count_fcurves_hidden_by_filter(ac, fcurves);
5557 rctf fcu_bounds = calculate_fcurve_bounds_and_unhide(ac->sl, ac->scene, selected_id, fcurves);
5558 if (BLI_rctf_is_valid(&fcu_bounds)) {
5559 BLI_rctf_union(&bounds, &fcu_bounds);
5560 }
5561 }
5562
5563 return bounds;
5564}
5565
5567{
5568 PointerRNA button_ptr = {nullptr};
5569 PropertyRNA *button_prop = nullptr;
5570 uiBut *but;
5571 int index;
5572
5573 if (!(but = UI_context_active_but_prop_get(C, &button_ptr, &button_prop, &index))) {
5574 /* Pass event on if no active button found. */
5576 }
5577
5578 int retval = OPERATOR_FINISHED;
5579
5581
5582 struct {
5583 wmWindow *win;
5584 ScrArea *area;
5585 ARegion *region;
5586 } wm_context_prev = {nullptr}, wm_context_temp = {nullptr};
5587
5588 bool path_from_id;
5589 std::optional<std::string> id_to_prop_path;
5590 const bool selected_list_success = UI_context_copy_to_selected_list(
5591 C, &button_ptr, button_prop, &selection, &path_from_id, &id_to_prop_path);
5592
5594 C, &wm_context_temp.win, &wm_context_temp.area, &wm_context_temp.region))
5595 {
5596 WM_report(RPT_WARNING, "No open Graph Editor window found");
5597 retval = OPERATOR_CANCELLED;
5598 }
5599 else {
5600 wm_context_prev.win = CTX_wm_window(C);
5601 wm_context_prev.area = CTX_wm_area(C);
5602 wm_context_prev.region = CTX_wm_region(C);
5603
5604 CTX_wm_window_set(C, wm_context_temp.win);
5605 CTX_wm_area_set(C, wm_context_temp.area);
5606 CTX_wm_region_set(C, wm_context_temp.region);
5607
5608 bAnimContext ac;
5609 if (!ANIM_animdata_get_context(C, &ac)) {
5610 /* This might never be called since we are manually setting the Graph Editor just before. */
5611 WM_report(RPT_ERROR, "Cannot create the Animation Context");
5612 retval = OPERATOR_CANCELLED;
5613 }
5614 else {
5615 const bool isolate = RNA_boolean_get(op->ptr, "isolate");
5616 /* The index can be less than 0 e.g. on color properties. */
5617 const bool whole_array = RNA_boolean_get(op->ptr, "all") || index < 0;
5618
5619 deselect_all_fcurves(&ac, isolate);
5620
5621 rctf bounds;
5622 bounds.xmin = INFINITY;
5623 bounds.xmax = -INFINITY;
5624 bounds.ymin = INFINITY;
5625 bounds.ymax = -INFINITY;
5626 int filtered_fcurve_count = 0;
5627 if (selected_list_success && !selection.is_empty()) {
5628 rctf selection_bounds = calculate_selection_fcurve_bounds(&ac,
5629 selection,
5630 button_prop,
5631 id_to_prop_path.value_or(""),
5632 index,
5633 whole_array,
5634 &filtered_fcurve_count);
5635 if (BLI_rctf_is_valid(&selection_bounds)) {
5636 BLI_rctf_union(&bounds, &selection_bounds);
5637 }
5638 }
5639
5640 /* The object to which the button belongs might not be selected, or selectable. */
5642 button_ptr.owner_id, &button_ptr, button_prop, whole_array, index);
5643 filtered_fcurve_count += count_fcurves_hidden_by_filter(&ac, button_fcurves);
5645 ac.sl, ac.scene, button_ptr.owner_id, button_fcurves);
5646 if (BLI_rctf_is_valid(&button_bounds)) {
5647 BLI_rctf_union(&bounds, &button_bounds);
5648 }
5649
5650 if (filtered_fcurve_count > 0) {
5651 WM_report(RPT_WARNING, "One or more F-Curves are not visible due to filter settings");
5652 }
5653
5654 if (!BLI_rctf_is_valid(&bounds)) {
5655 WM_report(RPT_ERROR, "F-Curves have no valid size");
5656 retval = OPERATOR_CANCELLED;
5657 }
5658 else {
5659 ARegion *region = wm_context_temp.region;
5660 ScrArea *area = wm_context_temp.area;
5661 add_region_padding(C, region, &bounds);
5662
5663 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
5664 UI_view2d_smooth_view(C, region, &bounds, smooth_viewtx);
5665
5666 /* This ensures the channel list updates. */
5667 ED_area_tag_redraw(area);
5668 }
5669 }
5670
5671 CTX_wm_window_set(C, wm_context_prev.win);
5672 CTX_wm_area_set(C, wm_context_prev.area);
5673 CTX_wm_region_set(C, wm_context_prev.region);
5674 }
5675
5676 return retval;
5677}
5678
5680{
5681 /* Identifiers */
5682 ot->name = "View In Graph Editor";
5683 ot->idname = "ANIM_OT_view_curve_in_graph_editor";
5684 ot->description = "Frame the property under the cursor in the Graph Editor";
5685
5686 /* API callbacks */
5688
5690 "all",
5691 false,
5692 "Show All",
5693 "Frame the whole array property instead of only the index under the cursor");
5694
5696 "isolate",
5697 false,
5698 "Isolate",
5699 "Hides all F-Curves other than the ones being framed");
5700}
5701
5704/* -------------------------------------------------------------------- */
5709{
5712
5716
5718
5722
5726
5728
5729 /* XXX does this need to be a separate operator? */
5731
5733
5736
5738
5740
5743
5745
5746#ifdef WITH_ANIM_BAKLAVA
5747 WM_operatortype_append(ANIM_OT_slot_channels_move_to_new_action);
5748 WM_operatortype_append(ANIM_OT_separate_slots);
5749#endif
5750}
5751
5753{
5754 /* TODO: check on a poll callback for this, to get hotkeys into menus. */
5755
5756 WM_keymap_ensure(keyconf, "Animation Channels", SPACE_EMPTY, RGN_TYPE_WINDOW);
5757}
5758
Functions and classes to work with Actions.
Functions for backward compatibility with the legacy Action API.
Functions to work with AnimData.
Functions to modify FCurves.
Blender kernel action and pose functionality.
void action_groups_add_channel(bAction *act, bActionGroup *agrp, FCurve *fcurve)
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
void action_groups_remove_channel(bAction *act, FCurve *fcu)
bActionGroup * action_groups_add_new(bAction *act, const char name[])
bool BKE_animdata_id_is_animated(const ID *id)
Definition anim_data.cc:321
AnimData * BKE_animdata_from_id(const ID *id)
Definition anim_data.cc:89
void BKE_animdata_free(ID *id, bool do_id_user)
Definition anim_data.cc:263
SpaceAction * CTX_wm_space_action(const bContext *C)
void CTX_wm_operator_poll_msg_set(bContext *C, const char *msg)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
SpaceLink * CTX_wm_space_data(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
void CTX_wm_window_set(bContext *C, wmWindow *win)
Main * CTX_data_main(const bContext *C)
void CTX_wm_area_set(bContext *C, ScrArea *area)
void CTX_wm_region_set(bContext *C, ARegion *region)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
wmMsgBus * CTX_wm_message_bus(const bContext *C)
int BKE_fcurve_bezt_binarysearch_index(const BezTriple array[], float frame, int arraylen, bool *r_replace)
FCurve * BKE_animadata_fcurve_find_by_rna_path(AnimData *animdata, const char *rna_path, const int rna_index, bAction **r_action, bool *r_driven)
void free_fmodifiers(ListBase *modifiers)
void BKE_fcurve_free(FCurve *fcu)
bool BKE_fcurve_calc_bounds(const FCurve *fcu, bool selected_keys_only, bool include_handles, const float frame_range[2], rctf *r_bounds)
@ G_DEBUG
void BKE_gpencil_layer_active_set(struct bGPdata *gpd, struct bGPDlayer *active)
void BKE_gpencil_free_data(struct bGPdata *gpd, bool free_all)
void BKE_gpencil_layer_delete(struct bGPdata *gpd, struct bGPDlayer *gpl)
void BKE_gpencil_layer_autolock_set(struct bGPdata *gpd, bool unlock)
Low-level operations for grease pencil.
void BKE_view_layer_synced_ensure(const Scene *scene, ViewLayer *view_layer)
ListBase * BKE_view_layer_object_bases_get(ViewLayer *view_layer)
void BKE_id_free_us(Main *bmain, void *idv) ATTR_NONNULL()
void BKE_mask_layer_remove(struct Mask *mask, struct MaskLayer *masklay)
float BKE_nla_tweakedit_remap(AnimData *adt, float cframe, short mode)
@ NLATIME_CONVERT_MAP
Definition BKE_nla.hh:516
@ NLATIME_CONVERT_UNMAP
Definition BKE_nla.hh:513
bool BKE_nlatrack_is_nonlocal_in_liboverride(const ID *id, const NlaTrack *nlt)
ARegion * BKE_area_find_region_type(const ScrArea *area, int region_type)
Definition screen.cc:815
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#define ATTR_FALLTHROUGH
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
#define LISTBASE_FOREACH(type, var, list)
void BLI_freelinkN(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:269
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
#define LISTBASE_FOREACH_BACKWARD(type, var, list)
void BLI_insertlinkafter(struct ListBase *listbase, void *vprevlink, void *vnewlink) ATTR_NONNULL(1)
Definition listbase.cc:331
void * BLI_findlink(const struct ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
void void void BLI_movelisttolist(struct ListBase *dst, struct ListBase *src) ATTR_NONNULL(1
void void BLI_freelistN(struct ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:496
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:130
void * BLI_findptr(const struct ListBase *listbase, const void *ptr, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
void BLI_insertlinkbefore(struct ListBase *listbase, void *vnextlink, void *vnewlink) ATTR_NONNULL(1)
Definition listbase.cc:370
MINLINE int min_ii(int a, int b)
MINLINE int max_ii(int a, int b)
void BLI_rctf_union(struct rctf *rct_a, const struct rctf *rct_b)
bool BLI_rctf_is_valid(const struct rctf *rect)
void BLI_rctf_pad_y(struct rctf *rect, float boundary_size, float pad_min, float pad_max)
Definition rct.c:683
void BLI_rctf_scale(rctf *rect, float scale)
Definition rct.c:671
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:201
#define STRNCPY(dst, src)
Definition BLI_string.h:593
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:597
#define ELEM(...)
#define STREQ(a, b)
#define DATA_(msgid)
void DEG_id_tag_update(ID *id, unsigned int flags)
void DEG_id_tag_update_ex(Main *bmain, ID *id, unsigned int flags)
void DEG_relations_tag_update(Main *bmain)
@ ID_RECALC_TRANSFORM
Definition DNA_ID.h:1021
@ ID_RECALC_ANIMATION
Definition DNA_ID.h:1044
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1041
@ ID_RECALC_ANIMATION_NO_FLUSH
Definition DNA_ID.h:1143
#define MAX_ID_NAME
Definition DNA_ID.h:377
#define ID_IS_EDITABLE(_id)
Definition DNA_ID.h:658
#define ID_IS_OVERRIDE_LIBRARY(_id)
Definition DNA_ID.h:683
@ ID_AC
@ ID_OB
@ ADS_FILTER_ONLYSEL
@ AGRP_TEMP
@ AGRP_ACTIVE
@ AGRP_SELECTED
@ AGRP_EXPANDED_G
@ AGRP_EXPANDED
@ SACTCONT_ACTION
@ SACTCONT_DOPESHEET
@ ADT_NLA_SKEYS_COLLAPSED
@ ADT_UI_ACTIVE
@ ADT_UI_SELECTED
@ FMODIFIER_FLAG_MUTED
@ FCURVE_ACTIVE
@ FCURVE_SELECTED
@ FCURVE_VISIBLE
@ NLATRACK_ACTIVE
@ NLATRACK_SELECTED
@ NLATRACK_OVERRIDELIBRARY_LOCAL
@ BEZT_IPO_CONST
@ BEZT_IPO_BEZ
@ BEZT_IPO_LIN
#define MAX_NAME
Definition DNA_defs.h:50
@ GP_LAYER_TREE_NODE_EXPANDED
@ GP_LAYER_TREE_NODE_SELECT
@ GREASE_PENCIL_ANIM_CHANNEL_EXPANDED
@ KEYBLOCK_SEL
@ MASK_ANIMF_EXPAND
@ MASK_LAYERFLAG_SELECT
Object is a sort of wrapper for general info.
@ OB_ARMATURE
@ SCER_PRV_RANGE
@ SCE_DS_SELECTED
@ SCE_NLA_EDIT_ON
#define BASE_SELECTABLE(v3d, base)
@ RGN_TYPE_CHANNELS
@ RGN_TYPE_WINDOW
@ RGN_FLAG_HIDDEN
@ SPACE_ACTION
@ SPACE_NLA
@ SPACE_EMPTY
@ SPACE_GRAPH
@ SIPO_MODE_ANIMATION
@ OPERATOR_RUNNING_MODAL
@ OPERATOR_PASS_THROUGH
eAnimChannels_SetFlag
@ ACHANNEL_SETFLAG_TOGGLE
@ ACHANNEL_SETFLAG_EXTEND_RANGE
@ ACHANNEL_SETFLAG_ADD
@ ACHANNEL_SETFLAG_INVERT
@ ACHANNEL_SETFLAG_CLEAR
eAnim_ChannelType
@ ANIMTYPE_DSSPK
@ ANIMTYPE_DSTEX
@ ANIMTYPE_SUMMARY
@ ANIMTYPE_DSNTREE
@ ANIMTYPE_NLACURVE
@ ANIMTYPE_SHAPEKEY
@ ANIMTYPE_DSMBALL
@ ANIMTYPE_DSCAM
@ ANIMTYPE_DSPOINTCLOUD
@ ANIMTYPE_FILLDRIVERS
@ ANIMTYPE_NONE
@ ANIMTYPE_DSPART
@ ANIMTYPE_DSLINESTYLE
@ ANIMTYPE_GROUP
@ ANIMTYPE_ACTION_SLOT
@ ANIMTYPE_SPECIALDATA__UNUSED
@ ANIMTYPE_GREASE_PENCIL_DATABLOCK
@ ANIMTYPE_DSCUR
@ ANIMTYPE_SCENE
@ ANIMTYPE_DSARM
@ ANIMTYPE_NLACONTROLS
@ ANIMTYPE_GPLAYER
@ ANIMTYPE_MASKDATABLOCK
@ ANIMTYPE_ANIMDATA
@ ANIMTYPE_MASKLAYER
@ ANIMTYPE_DSGPENCIL
@ ANIMTYPE_DSLAT
@ ANIMTYPE_NLAACTION
@ ANIMTYPE_DSMCLIP
@ ANIMTYPE_DSMAT
@ ANIMTYPE_NUM_TYPES
@ ANIMTYPE_DSCACHEFILE
@ ANIMTYPE_DSVOLUME
@ ANIMTYPE_FCURVE
@ ANIMTYPE_DSLAM
@ ANIMTYPE_PALETTE
@ ANIMTYPE_GPDATABLOCK
@ ANIMTYPE_GREASE_PENCIL_LAYER
@ ANIMTYPE_FILLACT_LAYERED
@ ANIMTYPE_FILLACTD
@ ANIMTYPE_OBJECT
@ ANIMTYPE_DSMESH
@ ANIMTYPE_GREASE_PENCIL_LAYER_GROUP
@ ANIMTYPE_NLATRACK
@ ANIMTYPE_DSWOR
@ ANIMTYPE_DSSKEY
@ ANIMTYPE_DSHAIR
#define SEL_AGRP(agrp)
#define SEL_GPL(gpl)
#define NLATRACK_FIRST_TOP(ac)
#define EXPANDED_AGRP(ac, agrp)
@ ALE_GREASE_PENCIL_GROUP
@ ALE_SCE
@ ALE_GREASE_PENCIL_CEL
@ ALE_GREASE_PENCIL_DATA
@ ALE_NONE
@ ALE_GPFRAME
@ ALE_FCURVE
@ ALE_NLASTRIP
@ ALE_ALL
@ ALE_ACT
@ ALE_ACTION_LAYERED
@ ALE_OB
@ ALE_GROUP
@ ALE_ACTION_SLOT
@ ALE_MASKLAY
#define NLATRACK_STEP(snla)
@ ANIM_UPDATE_DEPS
eAnimCont_Types
@ ANIMCONT_DRIVERS
@ ANIMCONT_FCURVES
@ ANIMCONT_NLA
@ ANIMCONT_MASK
@ ANIMCONT_SHAPEKEY
@ ANIMCONT_TIMELINE
@ ANIMCONT_DOPESHEET
@ ANIMCONT_ACTION
@ ANIMCONT_GPENCIL
@ ANIMCONT_CHANNEL
#define SEL_FCU(fcu)
eAnimChannel_Settings
@ ACHANNEL_SETTING_ALWAYS_VISIBLE
@ ACHANNEL_SETTING_MUTE
@ ACHANNEL_SETTING_PROTECT
@ ACHANNEL_SETTING_VISIBLE
@ ACHANNEL_SETTING_EXPAND
@ ACHANNEL_SETTING_SELECT
#define ACHANNEL_SET_FLAG(channel, smode, sflag)
#define EXPANDED_DRVD(adt)
#define SEL_NLT(nlt)
#define NLATRACK_NAMEWIDTH
eAnimFilter_Flags
@ ANIMFILTER_FOREDIT
@ ANIMFILTER_ANIMDATA
@ ANIMFILTER_DATA_VISIBLE
@ ANIMFILTER_CURVE_VISIBLE
@ ANIMFILTER_LIST_VISIBLE
@ ANIMFILTER_LIST_CHANNELS
@ ANIMFILTER_NODUPLIS
@ ANIMFILTER_FCURVESONLY
@ ANIMFILTER_SEL
@ SELECT_INVERT
@ SELECT_EXTEND_RANGE
@ SELECT_SUBTRACT
@ SELECT_REPLACE
@ SELECT_ADD
void ED_area_tag_redraw(ScrArea *area)
Definition area.cc:708
bool ED_operator_graphedit_active(bContext *C)
bool ED_operator_action_active(bContext *C)
void ED_region_toggle_hidden(bContext *C, ARegion *region)
Definition area.cc:2276
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:634
@ SEL_SELECT
@ SEL_INVERT
@ SEL_DESELECT
@ SEL_TOGGLE
Read Guarded memory(de)allocation.
@ PROP_SKIP_SAVE
Definition RNA_types.hh:245
@ PROP_HIDDEN
Definition RNA_types.hh:239
bool UI_textbutton_activate_rna(const bContext *C, ARegion *region, const void *rna_poin_data, const char *rna_prop_id)
uiBut * UI_context_active_but_prop_get(const bContext *C, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index)
bool UI_context_copy_to_selected_list(bContext *C, PointerRNA *ptr, PropertyRNA *prop, blender::Vector< PointerRNA > *r_lb, bool *r_use_path_from_id, std::optional< std::string > *r_path)
void UI_view2d_smooth_view(const bContext *C, ARegion *region, const rctf *cur, int smooth_viewtx)
#define UI_MARKER_MARGIN_Y
Definition UI_view2d.hh:471
#define V2D_SCROLL_HANDLE_HEIGHT
Definition UI_view2d.hh:67
#define UI_TIME_SCRUB_MARGIN_Y
Definition UI_view2d.hh:472
void UI_view2d_listview_view_to_cell(float columnwidth, float rowheight, float startx, float starty, float viewx, float viewy, int *r_column, int *r_row)
Definition view2d.cc:1616
void UI_view2d_region_to_view(const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) ATTR_NONNULL()
Definition view2d.cc:1663
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
#define ND_NLA_ACTCHANGE
Definition WM_types.hh:465
#define ND_DATA
Definition WM_types.hh:475
#define NC_ANIMATION
Definition WM_types.hh:355
#define ND_SPACE_PROPERTIES
Definition WM_types.hh:495
#define NA_EDITED
Definition WM_types.hh:550
#define NA_REMOVED
Definition WM_types.hh:553
#define ND_NLA_ORDER
Definition WM_types.hh:467
#define NC_GPENCIL
Definition WM_types.hh:366
#define ND_NLA
Definition WM_types.hh:464
#define NA_RENAME
Definition WM_types.hh:554
#define ND_KEYFRAME
Definition WM_types.hh:461
#define ND_ANIMCHAN
Definition WM_types.hh:463
#define NA_SELECTED
Definition WM_types.hh:555
void ANIM_channel_setting_set(bAnimContext *ac, bAnimListElem *ale, eAnimChannel_Settings setting, eAnimChannels_SetFlag mode)
float ANIM_UI_get_channel_name_width()
const bAnimChannelType * ANIM_channel_get_typeinfo(bAnimListElem *ale)
float ANIM_UI_get_channel_step()
float ANIM_UI_get_first_channel_top(View2D *v2d)
short ANIM_channel_setting_get(bAnimContext *ac, bAnimListElem *ale, eAnimChannel_Settings setting)
static int animchannels_expand_exec(bContext *C, wmOperator *op)
static int animchannels_collapse_exec(bContext *C, wmOperator *op)
static bool get_gpencil_bounds(bGPDlayer *gpl, const float range[2], rctf *r_bounds)
static int click_select_channel_grease_pencil_datablock(bAnimListElem *ale)
static int click_select_channel_grease_pencil_layer_group(bAnimListElem *ale)
static void rearrange_gpencil_channels(bAnimContext *ac, eRearrangeAnimChan_Mode mode)
static int animchannels_rearrange_exec(bContext *C, wmOperator *op)
static void anim_flush_channel_setting_down(bAnimContext *ac, const eAnimChannel_Settings setting, const eAnimChannels_SetFlag mode, bAnimListElem *const match, const int matchLevel)
void ANIM_flush_setting_anim_channels(bAnimContext *ac, ListBase *anim_data, bAnimListElem *ale_setting, eAnimChannel_Settings setting, eAnimChannels_SetFlag mode)
static void box_select_anim_channels(bAnimContext *ac, const rcti &rect, short selectmode)
void ANIM_anim_channels_select_set(bAnimContext *ac, eAnimChannels_SetFlag sel)
static void add_region_padding(bContext *C, ARegion *region, rctf *bounds)
static int graphkeys_view_selected_channels_exec(bContext *C, wmOperator *op)
static bool animchannels_enable_poll(bContext *C)
static void join_groups_action_temp(bAction *act)
static void tag_update_animation_element(bAnimListElem *ale)
static void ANIM_OT_channel_select_keys(wmOperatorType *ot)
static int view_curve_in_graph_editor_exec(bContext *C, wmOperator *op)
@ REORDER_ISLAND_UNTOUCHABLE
@ REORDER_ISLAND_MOVED
@ REORDER_ISLAND_SELECTED
@ REORDER_ISLAND_HIDDEN
static bool animchannel_has_active_of_type(bAnimContext *ac, const eAnim_ChannelType type)
void ED_keymap_animchannels(wmKeyConfig *keyconf)
static void split_groups_action_temp(bAction *act, bActionGroup *tgrp)
static bool animchannels_select_filter_poll(bContext *C)
static const EnumPropertyItem prop_animchannel_rearrange_types[]
static int animchannels_select_filter_invoke(bContext *C, wmOperator *op, const wmEvent *)
static void setflag_anim_channels(bAnimContext *ac, eAnimChannel_Settings setting, eAnimChannels_SetFlag mode, bool onlysel, bool flush)
static void get_view_range(Scene *scene, const bool use_preview_range, float r_range[2])
static int graphkeys_channel_view_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void ANIM_OT_channels_clean_empty(wmOperatorType *ot)
static bool rearrange_island_bottom(ListBase *list, tReorderChannelIsland *island)
static void rearrange_animchannel_add_to_islands(ListBase *islands, ListBase *srcList, Link *channel, eAnim_ChannelType type, const bool is_hidden)
static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp, bAnimListElem *ale, const bool change_active)
static bool rearrange_island_up(ListBase *list, tReorderChannelIsland *island)
bool(*)(ListBase *list, tReorderChannelIsland *island) AnimChanRearrangeFp
static int animchannels_group_exec(bContext *C, wmOperator *op)
static void rearrange_nla_tracks(bAnimContext *ac, AnimData *adt, eRearrangeAnimChan_Mode mode)
static bool select_anim_channel_keys(bAnimContext *ac, int channel_index, bool extend)
static bool get_channel_bounds(bAnimContext *ac, bAnimListElem *ale, const float range[2], const bool include_handles, rctf *r_bounds)
static int click_select_channel_maskdatablock(bAnimListElem *ale)
static int click_select_channel_object(bContext *C, bAnimContext *ac, bAnimListElem *ale, const short selectmode)
static void anim_flush_channel_setting_up(bAnimContext *ac, const eAnimChannel_Settings setting, const eAnimChannels_SetFlag mode, bAnimListElem *const match, const int matchLevel)
static void ANIM_OT_channels_delete(wmOperatorType *ot)
static int animchannels_channel_get(bAnimContext *ac, const int mval[2])
static rctf calculate_fcurve_bounds_and_unhide(SpaceLink *space_link, Scene *scene, ID *id, const blender::Span< FCurve * > fcurves)
void ANIM_frame_channel_y_extents(bContext *C, bAnimContext *ac)
static int click_select_channel_action_slot(bAnimContext *ac, bAnimListElem *ale, short selectmode)
static int click_select_channel_group(bAnimContext *ac, bAnimListElem *ale, const short selectmode, const int filter)
static int animchannels_enable_exec(bContext *C, wmOperator *)
static void rearrange_driver_channels(bAnimContext *ac, AnimData *adt, eRearrangeAnimChan_Mode mode)
static void rearrange_animchannel_flatten_islands(ListBase *islands, ListBase *srcList)
static int animchannels_delete_exec(bContext *C, wmOperator *)
static const EnumPropertyItem prop_animchannel_settings_types[]
bool ANIM_is_active_channel(bAnimListElem *ale)
static bool animedit_poll_channels_active(bContext *C)
static ListBase anim_channels_for_selection(bAnimContext *ac)
static void ANIM_OT_channels_expand(wmOperatorType *ot)
static void ANIM_OT_channels_rename(wmOperatorType *ot)
static int animchannels_select_filter_modal(bContext *C, wmOperator *, const wmEvent *)
static void deselect_all_fcurves(bAnimContext *ac, const bool hide)
static int click_select_channel_shapekey(bAnimContext *ac, bAnimListElem *ale, const short selectmode)
static void ANIM_OT_view_curve_in_graph_editor(wmOperatorType *ot)
static bool animedit_poll_channels_nla_tweakmode_off(bContext *C)
void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datatype, eAnimFilter_Flags filter, void *channel_data, eAnim_ChannelType channel_type)
static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_elem)
static int click_select_channel_gpdatablock(bAnimListElem *ale)
static void animchannels_group_channels(bAnimContext *ac, bAnimListElem *adt_ref, const char name[])
static bool rename_anim_channels(bAnimContext *ac, int channel_index)
static int click_select_channel_nlacontrols(bAnimListElem *ale)
static bool rearrange_island_down(ListBase *list, tReorderChannelIsland *island)
static void ANIM_OT_channels_select_box(wmOperatorType *ot)
static void ANIM_OT_channels_editable_toggle(wmOperatorType *ot)
static blender::Vector< FCurve * > get_fcurves_of_property(ID *id, PointerRNA *ptr, PropertyRNA *prop, const bool whole_array, const int index)
static bool rearrange_island_top(ListBase *list, tReorderChannelIsland *island)
static int channels_bake_exec(bContext *C, wmOperator *op)
static int animchannels_selectall_exec(bContext *C, wmOperator *op)
static void ANIM_OT_channels_fcurves_enable(wmOperatorType *ot)
static void ANIM_OT_channels_ungroup(wmOperatorType *ot)
static void rearrange_action_channels(bAnimContext *ac, bAction *act, eRearrangeAnimChan_Mode mode)
static void ANIM_OT_channels_view_selected(wmOperatorType *ot)
static int click_select_channel_gplayer(bContext *C, bAnimContext *ac, bAnimListElem *ale, const short selectmode, const int filter)
static void rearrange_layered_action_channel_groups(bAnimContext *ac, const eRearrangeAnimChan_Mode mode)
static bool rearrange_animchannel_islands(ListBase *list, AnimChanRearrangeFp rearrange_func, eRearrangeAnimChan_Mode mode, eAnim_ChannelType type, ListBase *anim_data_visible)
static void ANIM_OT_channels_setting_enable(wmOperatorType *ot)
static int click_select_channel_grease_pencil_layer(bContext *C, bAnimContext *ac, bAnimListElem *ale, const short selectmode, const int)
static const EnumPropertyItem channel_bake_key_options[]
static int mouse_anim_channels(bContext *C, bAnimContext *ac, const int channel_index, short selectmode)
static void ANIM_OT_channels_setting_toggle(wmOperatorType *ot)
static bool context_find_graph_editor(bContext *C, wmWindow **r_win, ScrArea **r_area, ARegion **r_region)
static bool animchannels_grouping_poll(bContext *C)
static void ANIM_OT_channels_move(wmOperatorType *ot)
static int animchannels_channel_select_keys_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static AnimChanRearrangeFp rearrange_get_mode_func(eRearrangeAnimChan_Mode mode)
static bool channel_view_poll(bContext *C)
void ANIM_anim_channels_select_toggle(bAnimContext *ac)
static void rearrange_nla_control_channels(bAnimContext *ac, AnimData *adt, eRearrangeAnimChan_Mode mode)
static AnimChanRearrangeFp rearrange_gpencil_get_mode_func(eRearrangeAnimChan_Mode mode)
static rctf calculate_selection_fcurve_bounds(bAnimContext *ac, blender::Span< PointerRNA > selection, PropertyRNA *prop, const blender::StringRefNull id_to_prop_path, const int index, const bool whole_array, int *r_filtered_fcurve_count)
static bool rearrange_island_ok(tReorderChannelIsland *island)
static int animchannels_ungroup_exec(bContext *C, wmOperator *)
static void ANIM_OT_channels_bake(wmOperatorType *ot)
static void ANIM_OT_channels_group(wmOperatorType *ot)
static void rearrange_layered_action_fcurves(bAnimContext *ac, blender::animrig::Action &action, const eRearrangeAnimChan_Mode mode)
static void ANIM_OT_channels_select_filter(wmOperatorType *ot)
static int click_select_channel_dummy(bAnimContext *ac, bAnimListElem *ale, const short selectmode)
static bool get_grease_pencil_layer_bounds(const GreasePencilLayer *gplayer, const float range[2], rctf *r_bounds)
static void anim_channels_select_set(bAnimContext *ac, const ListBase anim_data, eAnimChannels_SetFlag sel)
static int count_fcurves_hidden_by_filter(bAnimContext *ac, const blender::Span< FCurve * > fcurves)
static void templated_selection_state_update(T &selectable_thing, const eAnimChannels_SetFlag selectmode)
static void ANIM_OT_channels_setting_disable(wmOperatorType *ot)
static void ANIM_OT_channels_click(wmOperatorType *ot)
static eAnimChannels_SetFlag anim_channels_selection_flag_for_toggle(const ListBase anim_data)
static int animchannels_mouseclick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static int animchannels_clean_empty_exec(bContext *C, wmOperator *)
static int animchannels_box_select_exec(bContext *C, wmOperator *op)
static int click_select_channel_scene(bAnimListElem *ale, const short selectmode)
static const EnumPropertyItem prop_animchannel_setflag_types[]
static bool get_normalized_fcurve_bounds(FCurve *fcu, AnimData *anim_data, SpaceLink *space_link, Scene *scene, ID *id, const bool include_handles, const float range[2], rctf *r_bounds)
void ED_operatortypes_animchannels()
static int animchannels_setflag_exec(bContext *C, wmOperator *op)
eRearrangeAnimChan_Mode
@ REARRANGE_ANIMCHAN_DOWN
@ REARRANGE_ANIMCHAN_UP
@ REARRANGE_ANIMCHAN_BOTTOM
@ REARRANGE_ANIMCHAN_TOP
static int animchannels_rename_invoke(bContext *C, wmOperator *, const wmEvent *event)
static int click_select_channel_masklayer(bAnimContext *ac, bAnimListElem *ale, const short selectmode)
static int click_select_channel_fcurve(bAnimContext *ac, bAnimListElem *ale, const short selectmode, const int filter)
static void ANIM_OT_channel_view_pick(wmOperatorType *ot)
static void rearrange_animchannels_filter_visible(ListBase *anim_data_visible, bAnimContext *ac, eAnim_ChannelType type)
static void rearrange_grease_pencil_channels(bAnimContext *ac, eRearrangeAnimChan_Mode mode)
static void ANIM_OT_channels_collapse(wmOperatorType *ot)
static void ANIM_OT_channels_select_all(wmOperatorType *ot)
void ANIM_animdata_freelist(ListBase *anim_data)
Definition anim_deps.cc:457
void ANIM_animdata_update(bAnimContext *ac, ListBase *anim_data)
Definition anim_deps.cc:350
short ANIM_get_normalization_flags(SpaceLink *space_link)
Definition anim_draw.cc:320
AnimData * ANIM_nla_mapping_get(bAnimContext *ac, bAnimListElem *ale)
Definition anim_draw.cc:210
float ANIM_unit_mapping_get_factor(Scene *scene, ID *id, FCurve *fcu, short flag, float *r_offset)
Definition anim_draw.cc:529
bool ANIM_animdata_get_context(const bContext *C, bAnimContext *ac)
size_t ANIM_animdata_filter(bAnimContext *ac, ListBase *anim_data, const eAnimFilter_Flags filter_mode, void *data, const eAnimCont_Types datatype)
ListBase * ED_context_get_markers(const bContext *C)
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:271
bool contains(const Key &key) const
Definition BLI_map.hh:329
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr bool is_empty() const
constexpr const char * c_str() const
int64_t size() const
void append(const T &value)
void slot_active_set(slot_handle_t slot_handle)
const Slot * slot(int64_t index) const
bool slot_remove(Slot &slot_to_remove)
Layer & layer_add(std::optional< StringRefNull > name)
void fcurve_move(FCurve &fcurve, int to_fcurve_index)
bActionGroup & channel_group_create(StringRefNull name)
const bActionGroup * channel_group(int64_t index) const
void channel_group_move(bActionGroup &group, int to_group_index)
const FCurve * fcurve(int64_t index) const
bool fcurve_assign_to_channel_group(FCurve &fcurve, bActionGroup &to_group)
blender::Span< const bActionGroup * > channel_groups() const
blender::Span< const FCurve * > fcurves() const
bool fcurve_remove(FCurve &fcurve_to_remove)
Strip & strip_add(Action &owning_action, Strip::Type strip_type)
local_group_size(16, 16) .push_constant(Type b
#define printf
#define SELECT
#define offsetof(t, d)
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
DO_INLINE void filter(lfVector *V, fmatrix3x3 *S)
#define GS(x)
Definition iris.cc:202
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
ccl_device_inline float4 select(const int4 mask, const float4 a, const float4 b)
static ulong * next
#define G(x, y, z)
bool action_treat_as_legacy(const bAction &action)
Action & action_add(Main &bmain, StringRefNull name)
Action * get_action(ID &animated_id)
void move_slot(Main &bmain, Slot &slot, Action &from_action, Action &to_action)
void animdata_fcurve_delete(bAnimContext *ac, AnimData *adt, FCurve *fcu)
Definition animdata.cc:253
float wrap(float value, float max, float min)
Definition node_math.h:71
GPU_SHADER_INTERFACE_INFO(overlay_edit_curve_handle_iface, "vert").flat(Type pos vertex_in(1, Type::UINT, "data") .vertex_out(overlay_edit_curve_handle_iface) .geometry_layout(PrimitiveIn Frequency::GEOMETRY storage_buf(1, Qualifier::READ, "uint", "data[]", Frequency::GEOMETRY) .push_constant(Type Frequency::GEOMETRY selection[]
bool ED_pose_deselect_all(Object *ob, int select_mode, const bool ignore_visibility)
void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select, bool change_active)
void RNA_int_set_array(PointerRNA *ptr, const char *name, const int *values)
bool RNA_property_array_check(PropertyRNA *prop)
void RNA_int_get_array(PointerRNA *ptr, const char *name, int *values)
void RNA_string_get(PointerRNA *ptr, const char *name, char *value)
float RNA_float_get(PointerRNA *ptr, const char *name)
int RNA_property_array_length(PointerRNA *ptr, PropertyRNA *prop)
bool RNA_boolean_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_int_array(StructOrFunctionRNA *cont_, const char *identifier, const int len, const int *default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
PropertyRNA * RNA_def_float(StructOrFunctionRNA *cont_, const char *identifier, const float default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
std::optional< std::string > RNA_path_from_ID_to_property(const PointerRNA *ptr, PropertyRNA *prop)
Definition rna_path.cc:1166
bool RNA_path_resolve_property(const PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop)
Definition rna_path.cc:553
#define FLT_MAX
Definition stdcycles.h:14
bAction * action
ListBase drivers
ListBase nla_tracks
short flag
struct Object * object
float vec[3][3]
struct FCurve * next
bActionGroup * grp
char * rna_path
ChannelDriver * driver
BezTriple * bezt
struct FCurve * prev
unsigned int totvert
ListBase modifiers
GreasePencilLayerTreeNode base
Definition DNA_ID.h:413
char name[66]
Definition DNA_ID.h:425
void * data
void * last
void * first
ListBase fcurves
ListBase strips
struct NlaTrack * next
struct NlaTrack * prev
struct bPose * pose
struct AnimData * adt
ID * owner_id
Definition RNA_types.hh:40
struct RenderData r
struct AnimData * adt
struct bDopeSheet * ads
struct ActionChannelBag * channel_bag
ListBase curves
ListBase groups
short(* get_offset)(bAnimContext *ac, bAnimListElem *ale)
bool(* name_prop)(bAnimListElem *ale, PointerRNA *r_ptr, PropertyRNA **r_prop)
SpaceLink * sl
eAnimCont_Types datatype
bDopeSheet * ads
eSpace_Type spacetype
ViewLayer * view_layer
ARegion * region
Object * obact
eRegion_Type regiontype
ScrArea * area
AnimData * adt
bAnimListElem * next
eAnim_ChannelType type
eAnim_KeyType datatype
bAnimListElem * prev
float xmax
float xmin
float ymax
float ymin
int ymin
int ymax
int xmin
int xmax
tReorderChannelIsland * prev
tReorderChannelIsland * next
int mval[2]
Definition WM_types.hh:728
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* modal)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1036
int(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
PropertyRNA * prop
Definition WM_types.hh:1092
StructRNA * srna
Definition WM_types.hh:1080
void(* cancel)(bContext *C, wmOperator *op)
Definition WM_types.hh:1028
struct PointerRNA * ptr
void WM_report(eReportType type, const char *message)
void WM_main_add_notifier(uint type, void *reference)
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
PointerRNA * ptr
Definition wm_files.cc:4126
wmOperatorType * ot
Definition wm_files.cc:4125
void WM_gesture_box_cancel(bContext *C, wmOperator *op)
int WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event)
int WM_gesture_box_modal(bContext *C, wmOperator *op, const wmEvent *event)
wmKeyMap * WM_keymap_ensure(wmKeyConfig *keyconf, const char *idname, int spaceid, int regionid)
Definition wm_keymap.cc:897
#define WM_msg_publish_rna_prop(mbus, id_, data_, type_, prop_)
void WM_operator_properties_border_to_rcti(wmOperator *op, rcti *r_rect)
void WM_operator_properties_gesture_box_select(wmOperatorType *ot)
void WM_operator_properties_select_all(wmOperatorType *ot)
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))
int WM_operator_flag_only_pass_through_on_press(int retval, const wmEvent *event)
int WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)
int WM_operator_smooth_viewtx_get(const wmOperator *op)
int WM_operator_props_popup(bContext *C, wmOperator *op, const wmEvent *)
bScreen * WM_window_get_active_screen(const wmWindow *win)