This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/animation/anim_channels_edit.c

3595 lines
107 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2009 Blender Foundation, Joshua Leung
* All rights reserved.
*/
/** \file
* \ingroup edanimation
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "DNA_anim_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_key_types.h"
#include "DNA_mask_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "BKE_action.h"
#include "BKE_anim_data.h"
#include "BKE_context.h"
#include "BKE_fcurve.h"
#include "BKE_global.h"
#include "BKE_gpencil.h"
#include "BKE_lib_id.h"
#include "BKE_mask.h"
#include "BKE_nla.h"
#include "BKE_scene.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "UI_interface.h"
#include "UI_view2d.h"
#include "ED_anim_api.h"
#include "ED_armature.h"
#include "ED_keyframes_edit.h" /* XXX move the select modes out of there! */
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "WM_api.h"
#include "WM_types.h"
/* ************************************************************************** */
/* CHANNELS API - Exposed API */
/* -------------------------- Selection ------------------------------------- */
/* Set the given animation-channel as the active one for the active context */
/* TODO: extend for animdata types... */
void ANIM_set_active_channel(bAnimContext *ac,
void *data,
eAnimCont_Types datatype,
eAnimFilter_Flags filter,
void *channel_data,
eAnim_ChannelType channel_type)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
/* try to build list of filtered items */
ANIM_animdata_filter(ac, &anim_data, filter, data, datatype);
if (BLI_listbase_is_empty(&anim_data)) {
return;
}
/* only clear the 'active' flag for the channels of the same type */
for (ale = anim_data.first; ale; ale = ale->next) {
/* skip if types don't match */
if (channel_type != ale->type) {
continue;
}
/* flag to set depends on type */
switch (ale->type) {
case ANIMTYPE_GROUP: {
bActionGroup *agrp = (bActionGroup *)ale->data;
ACHANNEL_SET_FLAG(agrp, ACHANNEL_SETFLAG_CLEAR, AGRP_ACTIVE);
break;
}
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE: {
FCurve *fcu = (FCurve *)ale->data;
ACHANNEL_SET_FLAG(fcu, ACHANNEL_SETFLAG_CLEAR, FCURVE_ACTIVE);
break;
}
case ANIMTYPE_NLATRACK: {
NlaTrack *nlt = (NlaTrack *)ale->data;
ACHANNEL_SET_FLAG(nlt, ACHANNEL_SETFLAG_CLEAR, NLATRACK_ACTIVE);
break;
}
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
case ANIMTYPE_DSCACHEFILE:
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
case ANIMTYPE_DSTEX:
case ANIMTYPE_DSLAT:
case ANIMTYPE_DSLINESTYLE:
case ANIMTYPE_DSSPK:
case ANIMTYPE_DSGPENCIL:
case ANIMTYPE_DSMCLIP:
case ANIMTYPE_DSHAIR:
case ANIMTYPE_DSPOINTCLOUD:
case ANIMTYPE_DSVOLUME:
case ANIMTYPE_DSSIMULATION: {
/* need to verify that this data is valid for now */
if (ale->adt) {
ACHANNEL_SET_FLAG(ale->adt, ACHANNEL_SETFLAG_CLEAR, ADT_UI_ACTIVE);
}
break;
}
case ANIMTYPE_GPLAYER: {
bGPDlayer *gpl = (bGPDlayer *)ale->data;
ACHANNEL_SET_FLAG(gpl, ACHANNEL_SETFLAG_CLEAR, GP_LAYER_ACTIVE);
break;
}
}
}
/* set active flag */
if (channel_data) {
switch (channel_type) {
case ANIMTYPE_GROUP: {
bActionGroup *agrp = (bActionGroup *)channel_data;
agrp->flag |= AGRP_ACTIVE;
break;
}
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE: {
FCurve *fcu = (FCurve *)channel_data;
fcu->flag |= FCURVE_ACTIVE;
break;
}
case ANIMTYPE_NLATRACK: {
NlaTrack *nlt = (NlaTrack *)channel_data;
nlt->flag |= NLATRACK_ACTIVE;
break;
}
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
case ANIMTYPE_DSCACHEFILE:
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
case ANIMTYPE_DSLAT:
case ANIMTYPE_DSLINESTYLE:
case ANIMTYPE_DSSPK:
case ANIMTYPE_DSNTREE:
case ANIMTYPE_DSTEX:
case ANIMTYPE_DSGPENCIL:
case ANIMTYPE_DSMCLIP:
case ANIMTYPE_DSHAIR:
case ANIMTYPE_DSPOINTCLOUD:
case ANIMTYPE_DSVOLUME:
case ANIMTYPE_DSSIMULATION: {
/* need to verify that this data is valid for now */
if (ale && ale->adt) {
ale->adt->flag |= ADT_UI_ACTIVE;
}
break;
}
case ANIMTYPE_GPLAYER: {
bGPDlayer *gpl = (bGPDlayer *)channel_data;
gpl->flag |= GP_LAYER_ACTIVE;
break;
}
/* unhandled currently, but may be interesting */
case ANIMTYPE_MASKLAYER:
case ANIMTYPE_SHAPEKEY:
case ANIMTYPE_NLAACTION:
break;
/* other types */
default:
break;
}
}
/* clean up */
ANIM_animdata_freelist(&anim_data);
}
static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp, bAnimListElem *ale)
{
/* Armatures-Specific Feature:
* See mouse_anim_channels() -> ANIMTYPE_GROUP case for more details (T38737)
*/
if ((ac->ads->filterflag & ADS_FILTER_ONLYSEL) == 0) {
if ((ale->id) && (GS(ale->id->name) == ID_OB)) {
Object *ob = (Object *)ale->id;
if (ob->type == OB_ARMATURE) {
/* Assume for now that any group with corresponding name is what we want
* (i.e. for an armature whose location is animated, things would break
* if the user were to add a bone named "Location").
*
* TODO: check the first F-Curve or so to be sure...
*/
bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, agrp->name);
if (agrp->flag & AGRP_SELECTED) {
ED_pose_bone_select(ob, pchan, true);
}
else {
ED_pose_bone_select(ob, pchan, false);
}
}
}
}
}
static ListBase /* bAnimListElem */ anim_channels_for_selection(bAnimContext *ac)
{
ListBase anim_data = {NULL, NULL};
/* filter data */
/* NOTE: no list visible, otherwise, we get dangling */
const int filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_CHANNELS;
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
return anim_data;
}
static eAnimChannels_SetFlag anim_channels_selection_flag_for_toggle(const ListBase anim_data)
{
/* See if we should be selecting or deselecting. */
for (bAnimListElem *ale = anim_data.first; ale; ale = ale->next) {
switch (ale->type) {
case ANIMTYPE_SCENE:
if (ale->flag & SCE_DS_SELECTED) {
return ACHANNEL_SETFLAG_CLEAR;
}
break;
case ANIMTYPE_OBJECT:
#if 0 /* for now, do not take object selection into account, since it gets too annoying */
if (ale->flag & SELECT) {
return ACHANNEL_SETFLAG_CLEAR;
}
#endif
break;
case ANIMTYPE_GROUP:
if (ale->flag & AGRP_SELECTED) {
return ACHANNEL_SETFLAG_CLEAR;
}
break;
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE:
if (ale->flag & FCURVE_SELECTED) {
return ACHANNEL_SETFLAG_CLEAR;
}
break;
case ANIMTYPE_SHAPEKEY:
if (ale->flag & KEYBLOCK_SEL) {
return ACHANNEL_SETFLAG_CLEAR;
}
break;
case ANIMTYPE_NLATRACK:
if (ale->flag & NLATRACK_SELECTED) {
return ACHANNEL_SETFLAG_CLEAR;
}
break;
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
case ANIMTYPE_DSCACHEFILE:
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
case ANIMTYPE_DSNTREE:
case ANIMTYPE_DSTEX:
case ANIMTYPE_DSLAT:
case ANIMTYPE_DSLINESTYLE:
case ANIMTYPE_DSSPK:
case ANIMTYPE_DSGPENCIL:
case ANIMTYPE_DSMCLIP:
case ANIMTYPE_DSHAIR:
case ANIMTYPE_DSPOINTCLOUD:
case ANIMTYPE_DSVOLUME:
case ANIMTYPE_DSSIMULATION: {
if ((ale->adt) && (ale->adt->flag & ADT_UI_SELECTED)) {
return ACHANNEL_SETFLAG_CLEAR;
}
break;
}
case ANIMTYPE_GPLAYER:
if (ale->flag & GP_LAYER_SELECT) {
return ACHANNEL_SETFLAG_CLEAR;
}
break;
case ANIMTYPE_MASKLAYER:
if (ale->flag & MASK_LAYERFLAG_SELECT) {
return ACHANNEL_SETFLAG_CLEAR;
}
break;
}
}
return ACHANNEL_SETFLAG_ADD;
}
static void anim_channels_select_set(bAnimContext *ac,
const ListBase anim_data,
eAnimChannels_SetFlag sel)
{
bAnimListElem *ale;
for (ale = anim_data.first; ale; ale = ale->next) {
switch (ale->type) {
case ANIMTYPE_SCENE: {
Scene *scene = (Scene *)ale->data;
ACHANNEL_SET_FLAG(scene, sel, SCE_DS_SELECTED);
if (scene->adt) {
ACHANNEL_SET_FLAG(scene, sel, ADT_UI_SELECTED);
}
break;
}
case ANIMTYPE_OBJECT: {
#if 0 /* for now, do not take object selection into account, since it gets too annoying */
Base *base = (Base *)ale->data;
Object *ob = base->object;
ACHANNEL_SET_FLAG(base, sel, SELECT);
ACHANNEL_SET_FLAG(ob, sel, SELECT);
if (ob->adt) {
ACHANNEL_SET_FLAG(ob, sel, ADT_UI_SELECTED);
}
#endif
break;
}
case ANIMTYPE_GROUP: {
bActionGroup *agrp = (bActionGroup *)ale->data;
ACHANNEL_SET_FLAG(agrp, sel, AGRP_SELECTED);
select_pchan_for_action_group(ac, agrp, ale);
agrp->flag &= ~AGRP_ACTIVE;
break;
}
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE: {
FCurve *fcu = (FCurve *)ale->data;
ACHANNEL_SET_FLAG(fcu, sel, FCURVE_SELECTED);
if ((fcu->flag & FCURVE_SELECTED) == 0) {
/* Only erase the ACTIVE flag when deselecting. This ensures that "select all curves"
* retains the currently active curve. */
fcu->flag &= ~FCURVE_ACTIVE;
}
break;
}
case ANIMTYPE_SHAPEKEY: {
KeyBlock *kb = (KeyBlock *)ale->data;
ACHANNEL_SET_FLAG(kb, sel, KEYBLOCK_SEL);
break;
}
case ANIMTYPE_NLATRACK: {
NlaTrack *nlt = (NlaTrack *)ale->data;
ACHANNEL_SET_FLAG(nlt, sel, NLATRACK_SELECTED);
nlt->flag &= ~NLATRACK_ACTIVE;
break;
}
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
case ANIMTYPE_DSCACHEFILE:
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
case ANIMTYPE_DSNTREE:
case ANIMTYPE_DSTEX:
case ANIMTYPE_DSLAT:
case ANIMTYPE_DSLINESTYLE:
case ANIMTYPE_DSSPK:
case ANIMTYPE_DSGPENCIL:
case ANIMTYPE_DSMCLIP:
case ANIMTYPE_DSHAIR:
case ANIMTYPE_DSPOINTCLOUD:
case ANIMTYPE_DSVOLUME:
case ANIMTYPE_DSSIMULATION: {
/* need to verify that this data is valid for now */
if (ale->adt) {
ACHANNEL_SET_FLAG(ale->adt, sel, ADT_UI_SELECTED);
ale->adt->flag &= ~ADT_UI_ACTIVE;
}
break;
}
case ANIMTYPE_GPLAYER: {
bGPDlayer *gpl = (bGPDlayer *)ale->data;
ACHANNEL_SET_FLAG(gpl, sel, GP_LAYER_SELECT);
break;
}
case ANIMTYPE_MASKLAYER: {
MaskLayer *masklay = (MaskLayer *)ale->data;
ACHANNEL_SET_FLAG(masklay, sel, MASK_LAYERFLAG_SELECT);
break;
}
}
}
}
/* Set selection state of all animation channels in the context. */
void ANIM_anim_channels_select_set(bAnimContext *ac, eAnimChannels_SetFlag sel)
{
ListBase anim_data = anim_channels_for_selection(ac);
anim_channels_select_set(ac, anim_data, sel);
ANIM_animdata_freelist(&anim_data);
}
/* Toggle selection state of all animation channels in the context. */
void ANIM_anim_channels_select_toggle(bAnimContext *ac)
{
ListBase anim_data = anim_channels_for_selection(ac);
const eAnimChannels_SetFlag sel = anim_channels_selection_flag_for_toggle(anim_data);
anim_channels_select_set(ac, anim_data, sel);
ANIM_animdata_freelist(&anim_data);
}
/* ---------------------------- Graph Editor ------------------------------------- */
/* Copy a certain channel setting to parents of the modified channel. */
static void anim_flush_channel_setting_up(bAnimContext *ac,
const eAnimChannel_Settings setting,
const eAnimChannels_SetFlag mode,
bAnimListElem *const match,
const int matchLevel)
{
/* flush up?
*
* For Visibility:
* - only flush up if the current state is now enabled (positive 'on' state is default)
* (otherwise, it's too much work to force the parents to be inactive too)
*
* For everything else:
* - only flush up if the current state is now disabled (negative 'off' state is default)
* (otherwise, it's too much work to force the parents to be active too)
*/
if (setting == ACHANNEL_SETTING_VISIBLE) {
if (mode == ACHANNEL_SETFLAG_CLEAR) {
return;
}
}
else {
if (mode != ACHANNEL_SETFLAG_CLEAR) {
return;
}
}
/* Go backwards in the list, until the highest-ranking element
* (by indention has been covered). */
int prevLevel = matchLevel;
for (bAnimListElem *ale = match->prev; ale; ale = ale->prev) {
const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
/* if no channel info was found, skip, since this type might not have any useful info */
if (acf == NULL) {
continue;
}
/* Get the level of the current channel traversed
* - we define the level as simply being the offset for the start of the channel
*/
const int level = (acf->get_offset) ? acf->get_offset(ac, ale) : 0;
if (level == prevLevel) {
/* Don't influence siblings. */
continue;
}
if (level > prevLevel) {
/* If previous level was a base-level (i.e. 0 offset / root of one hierarchy), stop here. */
if (prevLevel == 0) {
return;
}
/* Otherwise, this level weaves into another sibling hierarchy to the previous one just
* finished, so skip until we get to the parent of this level. */
continue;
}
/* The level is 'less than' (i.e. more important) the level we're matching but also 'less
* than' the level just tried (i.e. only the 1st group above grouped F-Curves, when toggling
* visibility of F-Curves, gets flushed, which should happen if we don't let prevLevel get
* updated below once the first 1st group is found). */
ANIM_channel_setting_set(ac, ale, setting, mode);
/* store this level as the 'old' level now */
prevLevel = level;
}
}
/* Copy a certain channel setting to children of the modified channel. */
static void anim_flush_channel_setting_down(bAnimContext *ac,
const eAnimChannel_Settings setting,
const eAnimChannels_SetFlag mode,
bAnimListElem *const match,
const int matchLevel)
{
/* go forwards in the list, until the lowest-ranking element (by indention has been covered) */
for (bAnimListElem *ale = match->next; ale; ale = ale->next) {
const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale);
/* if no channel info was found, skip, since this type might not have any useful info */
if (acf == NULL) {
continue;
}
/* get the level of the current channel traversed
* - we define the level as simply being the offset for the start of the channel
*/
const int level = (acf->get_offset) ? acf->get_offset(ac, ale) : 0;
/* if the level is 'greater than' (i.e. less important) the channel that was changed,
* flush the new status...
*/
if (level > matchLevel) {
ANIM_channel_setting_set(ac, ale, setting, mode);
/* however, if the level is 'less than or equal to' the channel that was changed,
* (i.e. the current channel is as important if not more important than the changed
* channel) then we should stop, since we've found the last one of the children we should
* flush
*/
}
else {
break;
}
}
}
/* Flush visibility (for Graph Editor) changes up/down hierarchy for changes in the given setting
* - anim_data: list of the all the anim channels that can be chosen
* -> filtered using ANIMFILTER_CHANNELS only, since if we took VISIBLE too,
* then the channels under closed expanders get ignored...
* - ale_setting: the anim channel (not in the anim_data list directly, though occurring there)
* with the new state of the setting that we want flushed up/down the hierarchy
* - setting: type of setting to set
* - on: whether the visibility setting has been enabled or disabled
*/
void ANIM_flush_setting_anim_channels(bAnimContext *ac,
ListBase *anim_data,
bAnimListElem *ale_setting,
eAnimChannel_Settings setting,
eAnimChannels_SetFlag mode)
{
bAnimListElem *ale, *match = NULL;
int matchLevel = 0;
/* sanity check */
if (ELEM(NULL, anim_data, anim_data->first)) {
return;
}
if (setting == ACHANNEL_SETTING_ALWAYS_VISIBLE) {
return;
}
/* find the channel that got changed */
for (ale = anim_data->first; ale; ale = ale->next) {
/* compare data, and type as main way of identifying the channel */
if ((ale->data == ale_setting->data) && (ale->type == ale_setting->type)) {
/* We also have to check the ID, this is assigned to,
* since a block may have multiple users. */
/* TODO: is the owner-data more revealing? */
if (ale->id == ale_setting->id) {
match = ale;
break;
}
}
}
if (match == NULL) {
printf("ERROR: no channel matching the one changed was found\n");
return;
}
{
const bAnimChannelType *acf = ANIM_channel_get_typeinfo(ale_setting);
if (acf == NULL) {
printf("ERROR: no channel info for the changed channel\n");
return;
}
/* get the level of the channel that was affected
* - we define the level as simply being the offset for the start of the channel
*/
matchLevel = (acf->get_offset) ? acf->get_offset(ac, ale_setting) : 0;
}
anim_flush_channel_setting_up(ac, setting, mode, match, matchLevel);
anim_flush_channel_setting_down(ac, setting, mode, match, matchLevel);
}
/* -------------------------- F-Curves ------------------------------------- */
/* Delete the given F-Curve from its AnimData block */
void ANIM_fcurve_delete_from_animdata(bAnimContext *ac, AnimData *adt, FCurve *fcu)
{
/* - if no AnimData, we've got nowhere to remove the F-Curve from
* (this doesn't guarantee that the F-Curve is in there, but at least we tried
* - if no F-Curve, there is nothing to remove
*/
if (ELEM(NULL, adt, fcu)) {
return;
}
/* remove from whatever list it came from
* - Action Group
* - Action
* - Drivers
* - TODO... some others?
*/
if ((ac) && (ac->datatype == ANIMCONT_DRIVERS)) {
/* driver F-Curve */
BLI_remlink(&adt->drivers, fcu);
}
else if (adt->action) {
bAction *act = adt->action;
/* remove from group or action, whichever one "owns" the F-Curve */
if (fcu->grp) {
bActionGroup *agrp = fcu->grp;
/* remove F-Curve from group+action */
action_groups_remove_channel(act, fcu);
/* if group has no more channels, remove it too,
* otherwise can have many dangling groups T33541.
*/
if (BLI_listbase_is_empty(&agrp->channels)) {
BLI_freelinkN(&act->groups, agrp);
}
}
else {
BLI_remlink(&act->curves, fcu);
}
/* if action has no more F-Curves as a result of this, unlink it from
* AnimData if it did not come from a NLA Strip being tweaked.
*
* This is done so that we don't have dangling Object+Action entries in
* channel list that are empty, and linger around long after the data they
* are for has disappeared (and probably won't come back).
*/
ANIM_remove_empty_action_from_animdata(adt);
}
/* free the F-Curve itself */
BKE_fcurve_free(fcu);
}
/* If the action has no F-Curves, unlink it from AnimData if it did not
* come from a NLA Strip being tweaked. */
bool ANIM_remove_empty_action_from_animdata(struct AnimData *adt)
{
if (adt->action != NULL) {
bAction *act = adt->action;
if (BLI_listbase_is_empty(&act->curves) && (adt->flag & ADT_NLA_EDIT_ON) == 0) {
id_us_min(&act->id);
adt->action = NULL;
return true;
}
}
return false;
}
/* ************************************************************************** */
/* OPERATORS */
/* ****************** Operator Utilities ********************************** */
/* poll callback for being in an Animation Editor channels list region */
static bool animedit_poll_channels_active(bContext *C)
{
ScrArea *area = CTX_wm_area(C);
/* channels region test */
/* TODO: could enhance with actually testing if channels region? */
if (ELEM(NULL, area, CTX_wm_region(C))) {
return 0;
}
/* animation editor test */
if (ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH, SPACE_NLA) == 0) {
return 0;
}
return 1;
}
/* poll callback for Animation Editor channels list region + not in NLA-tweakmode for NLA */
static bool animedit_poll_channels_nla_tweakmode_off(bContext *C)
{
ScrArea *area = CTX_wm_area(C);
Scene *scene = CTX_data_scene(C);
/* channels region test */
/* TODO: could enhance with actually testing if channels region? */
if (ELEM(NULL, area, CTX_wm_region(C))) {
return 0;
}
/* animation editor test */
if (ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH, SPACE_NLA) == 0) {
return 0;
}
/* NLA TweakMode test */
if (area->spacetype == SPACE_NLA) {
if ((scene == NULL) || (scene->flag & SCE_NLA_EDIT_ON)) {
return 0;
}
}
return 1;
}
/* ****************** Rearrange Channels Operator ******************* */
/* constants for channel rearranging */
/* WARNING: don't change existing ones without modifying rearrange func accordingly */
typedef enum eRearrangeAnimChan_Mode {
REARRANGE_ANIMCHAN_TOP = -2,
REARRANGE_ANIMCHAN_UP = -1,
REARRANGE_ANIMCHAN_DOWN = 1,
REARRANGE_ANIMCHAN_BOTTOM = 2,
} eRearrangeAnimChan_Mode;
/* defines for rearranging channels */
static const EnumPropertyItem prop_animchannel_rearrange_types[] = {
{REARRANGE_ANIMCHAN_TOP, "TOP", 0, "To Top", ""},
{REARRANGE_ANIMCHAN_UP, "UP", 0, "Up", ""},
{REARRANGE_ANIMCHAN_DOWN, "DOWN", 0, "Down", ""},
{REARRANGE_ANIMCHAN_BOTTOM, "BOTTOM", 0, "To Bottom", ""},
{0, NULL, 0, NULL, NULL},
};
/* Reordering "Islands" Defines ----------------------------------- */
/* Island definition - just a listbase container */
typedef struct tReorderChannelIsland {
struct tReorderChannelIsland *next, *prev;
ListBase channels; /* channels within this region with the same state */
int flag; /* eReorderIslandFlag */
} tReorderChannelIsland;
/* flags for channel reordering islands */
typedef enum eReorderIslandFlag {
REORDER_ISLAND_SELECTED = (1 << 0), /* island is selected */
REORDER_ISLAND_UNTOUCHABLE = (1 << 1), /* island should be ignored */
REORDER_ISLAND_MOVED = (1 << 2), /* island has already been moved */
REORDER_ISLAND_HIDDEN = (1 << 3), /* island is not visible */
} eReorderIslandFlag;
/* Rearrange Methods --------------------------------------------- */
static bool rearrange_island_ok(tReorderChannelIsland *island)
{
/* island must not be untouchable */
if (island->flag & REORDER_ISLAND_UNTOUCHABLE) {
return 0;
}
/* island should be selected to be moved */
return (island->flag & REORDER_ISLAND_SELECTED) && !(island->flag & REORDER_ISLAND_MOVED);
}
/* ............................. */
static bool rearrange_island_top(ListBase *list, tReorderChannelIsland *island)
{
if (rearrange_island_ok(island)) {
/* remove from current position */
BLI_remlink(list, island);
/* make it first element */
BLI_insertlinkbefore(list, list->first, island);
return 1;
}
return 0;
}
static bool rearrange_island_up(ListBase *list, tReorderChannelIsland *island)
{
if (rearrange_island_ok(island)) {
/* moving up = moving before the previous island, otherwise we're in the same place */
tReorderChannelIsland *prev = island->prev;
/* Skip hidden islands! */
while (prev && prev->flag & REORDER_ISLAND_HIDDEN) {
prev = prev->prev;
}
if (prev) {
/* remove from current position */
BLI_remlink(list, island);
/* push it up */
BLI_insertlinkbefore(list, prev, island);
return 1;
}
}
return 0;
}
static bool rearrange_island_down(ListBase *list, tReorderChannelIsland *island)
{
if (rearrange_island_ok(island)) {
/* moving down = moving after the next island, otherwise we're in the same place */
tReorderChannelIsland *next = island->next;
/* Skip hidden islands! */
while (next && next->flag & REORDER_ISLAND_HIDDEN) {
next = next->next;
}
if (next) {
/* can only move past if next is not untouchable (i.e. nothing can go after it) */
if ((next->flag & REORDER_ISLAND_UNTOUCHABLE) == 0) {
/* remove from current position */
BLI_remlink(list, island);
/* push it down */
BLI_insertlinkafter(list, next, island);
return true;
}
}
/* else: no next channel, so we're at the bottom already, so can't move */
}
return false;
}
static bool rearrange_island_bottom(ListBase *list, tReorderChannelIsland *island)
{
if (rearrange_island_ok(island)) {
tReorderChannelIsland *last = list->last;
/* remove island from current position */
BLI_remlink(list, island);
/* add before or after the last channel? */
if ((last->flag & REORDER_ISLAND_UNTOUCHABLE) == 0) {
/* can add after it */
BLI_addtail(list, island);
}
else {
/* can at most go just before it, since last cannot be moved */
BLI_insertlinkbefore(list, last, island);
}
return true;
}
return false;
}
/* ............................. */
/**
* typedef for channel rearranging function
*
* \param list: List of tReorderChannelIsland's that channels belong to
* \param island: Island to be moved
* \return Whether operation was a success
*/
typedef bool (*AnimChanRearrangeFp)(ListBase *list, tReorderChannelIsland *island);
/* get rearranging function, given 'rearrange' mode */
static AnimChanRearrangeFp rearrange_get_mode_func(eRearrangeAnimChan_Mode mode)
{
switch (mode) {
case REARRANGE_ANIMCHAN_TOP:
return rearrange_island_top;
case REARRANGE_ANIMCHAN_UP:
return rearrange_island_up;
case REARRANGE_ANIMCHAN_DOWN:
return rearrange_island_down;
case REARRANGE_ANIMCHAN_BOTTOM:
return rearrange_island_bottom;
default:
return NULL;
}
}
/* get rearranging function, given 'rearrange' mode (grease pencil is inverted) */
static AnimChanRearrangeFp rearrange_gpencil_get_mode_func(eRearrangeAnimChan_Mode mode)
{
switch (mode) {
case REARRANGE_ANIMCHAN_TOP:
return rearrange_island_bottom;
case REARRANGE_ANIMCHAN_UP:
return rearrange_island_down;
case REARRANGE_ANIMCHAN_DOWN:
return rearrange_island_up;
case REARRANGE_ANIMCHAN_BOTTOM:
return rearrange_island_top;
default:
return NULL;
}
}
/* Rearrange Islands Generics ------------------------------------- */
/* add channel into list of islands */
static void rearrange_animchannel_add_to_islands(ListBase *islands,
ListBase *srcList,
Link *channel,
eAnim_ChannelType type,
const bool is_hidden)
{
/* always try to add to last island if possible */
tReorderChannelIsland *island = islands->last;
bool is_sel = false, is_untouchable = false;
/* get flags - selected and untouchable from the channel */
switch (type) {
case ANIMTYPE_GROUP: {
bActionGroup *agrp = (bActionGroup *)channel;
is_sel = SEL_AGRP(agrp);
is_untouchable = (agrp->flag & AGRP_TEMP) != 0;
break;
}
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE: {
FCurve *fcu = (FCurve *)channel;
is_sel = SEL_FCU(fcu);
break;
}
case ANIMTYPE_NLATRACK: {
NlaTrack *nlt = (NlaTrack *)channel;
is_sel = SEL_NLT(nlt);
break;
}
case ANIMTYPE_GPLAYER: {
bGPDlayer *gpl = (bGPDlayer *)channel;
is_sel = SEL_GPL(gpl);
break;
}
default:
printf(
"rearrange_animchannel_add_to_islands(): don't know how to handle channels of type %u\n",
type);
return;
}
/* do we need to add to a new island? */
if (/* 1) no islands yet */
(island == NULL) ||
/* 2) unselected islands have single channels only - to allow up/down movement */
((island->flag & REORDER_ISLAND_SELECTED) == 0) ||
/* 3) if channel is unselected, stop existing island
* (it was either wrong sel status, or full already) */
(is_sel == 0) ||
/* 4) hidden status changes */
((island->flag & REORDER_ISLAND_HIDDEN) != is_hidden)) {
/* create a new island now */
island = MEM_callocN(sizeof(tReorderChannelIsland), "tReorderChannelIsland");
BLI_addtail(islands, island);
if (is_sel) {
island->flag |= REORDER_ISLAND_SELECTED;
}
if (is_untouchable) {
island->flag |= REORDER_ISLAND_UNTOUCHABLE;
}
if (is_hidden) {
island->flag |= REORDER_ISLAND_HIDDEN;
}
}
/* add channel to island - need to remove it from its existing list first though */
BLI_remlink(srcList, channel);
BLI_addtail(&island->channels, channel);
}
/* flatten islands out into a single list again */
static void rearrange_animchannel_flatten_islands(ListBase *islands, ListBase *srcList)
{
tReorderChannelIsland *island, *isn = NULL;
/* make sure srcList is empty now */
BLI_assert(BLI_listbase_is_empty(srcList));
/* go through merging islands */
for (island = islands->first; island; island = isn) {
isn = island->next;
/* merge island channels back to main list, then delete the island */
BLI_movelisttolist(srcList, &island->channels);
BLI_freelinkN(islands, island);
}
}
/* ............................. */
/* get a list of all bAnimListElem's of a certain type which are currently visible */
static void rearrange_animchannels_filter_visible(ListBase *anim_data_visible,
bAnimContext *ac,
eAnim_ChannelType type)
{
ListBase anim_data = {NULL, NULL};
eAnimFilter_Flags filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE |
ANIMFILTER_LIST_CHANNELS);
/* get all visible channels */
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* now, only keep the ones that are of the types we are interested in */
LISTBASE_FOREACH_MUTABLE (bAnimListElem *, ale, &anim_data) {
if (ale->type != type) {
BLI_freelinkN(&anim_data, ale);
continue;
}
if (type == ANIMTYPE_NLATRACK) {
NlaTrack *nlt = (NlaTrack *)ale->data;
if (BKE_nlatrack_is_nonlocal_in_liboverride(ale->id, nlt)) {
/* No re-arrangement of non-local tracks of override data. */
BLI_freelinkN(&anim_data, ale);
continue;
}
}
}
/* return cleaned up list */
*anim_data_visible = anim_data;
}
/* performing rearranging of channels using islands */
static bool rearrange_animchannel_islands(ListBase *list,
AnimChanRearrangeFp rearrange_func,
eRearrangeAnimChan_Mode mode,
eAnim_ChannelType type,
ListBase *anim_data_visible)
{
ListBase islands = {NULL, NULL};
Link *channel, *chanNext = NULL;
bool done = false;
/* don't waste effort on an empty list */
if (BLI_listbase_is_empty(list)) {
return 0;
}
/* group channels into islands */
for (channel = list->first; channel; channel = chanNext) {
/* find out whether this channel is present in anim_data_visible or not! */
const bool is_hidden =
(BLI_findptr(anim_data_visible, channel, offsetof(bAnimListElem, data)) == NULL);
chanNext = channel->next;
rearrange_animchannel_add_to_islands(&islands, list, channel, type, is_hidden);
}
/* Perform moving of selected islands now, but only if there is more than one of them
* so that something will happen:
*
* - Scanning of the list is performed in the opposite direction
* to the direction we're moving things,
* so that we shouldn't need to encounter items we've moved already.
*/
if (islands.first != islands.last) {
tReorderChannelIsland *first = (mode > 0) ? islands.last : islands.first;
tReorderChannelIsland *island, *isn = NULL;
for (island = first; island; island = isn) {
isn = (mode > 0) ? island->prev : island->next;
/* perform rearranging */
if (rearrange_func(&islands, island)) {
island->flag |= REORDER_ISLAND_MOVED;
done = true;
}
}
}
/* ungroup islands */
rearrange_animchannel_flatten_islands(&islands, list);
/* did we do anything? */
return done;
}
/* NLA Specific Stuff ----------------------------------------------------- */
/* Change the order NLA Tracks within NLA Stack
* ! NLA tracks are displayed in opposite order, so directions need care
* mode: REARRANGE_ANIMCHAN_*
*/
static void rearrange_nla_channels(bAnimContext *ac, AnimData *adt, eRearrangeAnimChan_Mode mode)
{
AnimChanRearrangeFp rearrange_func;
ListBase anim_data_visible = {NULL, NULL};
const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(ac->obact);
/* hack: invert mode so that functions will work in right order */
mode *= -1;
/* get rearranging function */
rearrange_func = rearrange_get_mode_func(mode);
if (rearrange_func == NULL) {
return;
}
/* In liboverride case, we need to extract non-local NLA tracks from current anim data before we
* can perform the move, and add then back afterwards. It's the only way to prevent them from
* being affected by the reordering.
*
* Note that both override apply code for NLA tracks collection, and NLA editing code, are
* responsible to ensure that non-local tracks always remain first in the list. */
ListBase extracted_nonlocal_nla_tracks = {NULL, NULL};
if (is_liboverride) {
NlaTrack *nla_track;
for (nla_track = adt->nla_tracks.first; nla_track != NULL; nla_track = nla_track->next) {
if (!BKE_nlatrack_is_nonlocal_in_liboverride(&ac->obact->id, nla_track)) {
break;
}
}
if (nla_track != NULL && nla_track->prev != NULL) {
extracted_nonlocal_nla_tracks.first = adt->nla_tracks.first;
extracted_nonlocal_nla_tracks.last = nla_track->prev;
adt->nla_tracks.first = nla_track;
nla_track->prev->next = NULL;
nla_track->prev = NULL;
}
}
/* Filter visible data. */
rearrange_animchannels_filter_visible(&anim_data_visible, ac, ANIMTYPE_NLATRACK);
/* perform rearranging on tracks list */
rearrange_animchannel_islands(
&adt->nla_tracks, rearrange_func, mode, ANIMTYPE_NLATRACK, &anim_data_visible);
/* Add back non-local NLA tracks at the beginning of the animation data's list. */
if (!BLI_listbase_is_empty(&extracted_nonlocal_nla_tracks)) {
BLI_assert(is_liboverride);
((NlaTrack *)extracted_nonlocal_nla_tracks.last)->next = adt->nla_tracks.first;
((NlaTrack *)adt->nla_tracks.first)->prev = extracted_nonlocal_nla_tracks.last;
adt->nla_tracks.first = extracted_nonlocal_nla_tracks.first;
}
/* free temp data */
BLI_freelistN(&anim_data_visible);
}
/* Drivers Specific Stuff ------------------------------------------------- */
/* Change the order drivers within AnimData block
* mode: REARRANGE_ANIMCHAN_*
*/
static void rearrange_driver_channels(bAnimContext *ac,
AnimData *adt,
eRearrangeAnimChan_Mode mode)
{
/* get rearranging function */
AnimChanRearrangeFp rearrange_func = rearrange_get_mode_func(mode);
ListBase anim_data_visible = {NULL, NULL};
if (rearrange_func == NULL) {
return;
}
/* only consider drivers if they're accessible */
if (EXPANDED_DRVD(adt) == 0) {
return;
}
/* Filter visible data. */
rearrange_animchannels_filter_visible(&anim_data_visible, ac, ANIMTYPE_FCURVE);
/* perform rearranging on drivers list (drivers are really just F-Curves) */
rearrange_animchannel_islands(
&adt->drivers, rearrange_func, mode, ANIMTYPE_FCURVE, &anim_data_visible);
/* free temp data */
BLI_freelistN(&anim_data_visible);
}
/* Action Specific Stuff ------------------------------------------------- */
/* make sure all action-channels belong to a group (and clear action's list) */
static void split_groups_action_temp(bAction *act, bActionGroup *tgrp)
{
FCurve *fcu;
if (act == NULL) {
return;
}
/* Separate F-Curves into lists per group */
LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) {
FCurve *const group_fcurves_first = agrp->channels.first;
FCurve *const group_fcurves_last = agrp->channels.last;
if (group_fcurves_first == NULL) {
/* Empty group. */
continue;
}
if (group_fcurves_first == act->curves.first) {
/* First of the action curves, update the start of the action curves. */
BLI_assert(group_fcurves_first->prev == NULL);
act->curves.first = group_fcurves_last->next;
}
else {
group_fcurves_first->prev->next = group_fcurves_last->next;
}
if (group_fcurves_last == act->curves.last) {
/* Last of the action curves, update the end of the action curves. */
BLI_assert(group_fcurves_last->next == NULL);
act->curves.last = group_fcurves_first->prev;
}
else {
group_fcurves_last->next->prev = group_fcurves_first->prev;
}
}
/* Initialize memory for temp-group */
memset(tgrp, 0, sizeof(bActionGroup));
tgrp->flag |= (AGRP_EXPANDED | AGRP_TEMP);
BLI_strncpy(tgrp->name, "#TempGroup", sizeof(tgrp->name));
/* Move any action-channels not already moved, to the temp group */
if (act->curves.first) {
/* start of list */
fcu = act->curves.first;
fcu->prev = NULL;
tgrp->channels.first = fcu;
act->curves.first = NULL;
/* end of list */
fcu = act->curves.last;
fcu->next = NULL;
tgrp->channels.last = fcu;
act->curves.last = NULL;
/* ensure that all of these get their group set to this temp group
* (so that visibility filtering works)
*/
for (fcu = tgrp->channels.first; fcu; fcu = fcu->next) {
fcu->grp = tgrp;
}
}
/* Add temp-group to list */
BLI_addtail(&act->groups, tgrp);
}
/* link lists of channels that groups have */
static void join_groups_action_temp(bAction *act)
{
bActionGroup *agrp;
for (agrp = act->groups.first; agrp; agrp = agrp->next) {
/* add list of channels to action's channels */
const ListBase group_channels = agrp->channels;
BLI_movelisttolist(&act->curves, &agrp->channels);
agrp->channels = group_channels;
/* clear moved flag */
agrp->flag &= ~AGRP_MOVED;
/* if group was temporary one:
* - unassign all FCurves which were temporarily added to it
* - remove from list (but don't free as it's on the stack!)
*/
if (agrp->flag & AGRP_TEMP) {
LISTBASE_FOREACH (FCurve *, fcu, &agrp->channels) {
fcu->grp = NULL;
if (fcu == agrp->channels.last) {
break;
}
}
BLI_remlink(&act->groups, agrp);
break;
}
}
/* BLI_movelisttolist() doesn't touch first->prev and last->next pointers in its "dst" list.
* Ensure that after the reshuffling the list is properly terminated. */
FCurve *act_fcurves_first = act->curves.first;
act_fcurves_first->prev = NULL;
FCurve *act_fcurves_last = act->curves.last;
act_fcurves_last->next = NULL;
}
/* Change the order of anim-channels within action
* mode: REARRANGE_ANIMCHAN_*
*/
static void rearrange_action_channels(bAnimContext *ac, bAction *act, eRearrangeAnimChan_Mode mode)
{
bActionGroup tgrp;
ListBase anim_data_visible = {NULL, NULL};
bool do_channels;
/* get rearranging function */
AnimChanRearrangeFp rearrange_func = rearrange_get_mode_func(mode);
if (rearrange_func == NULL) {
return;
}
/* make sure we're only operating with groups (vs a mixture of groups+curves) */
split_groups_action_temp(act, &tgrp);
/* Filter visible data. */
rearrange_animchannels_filter_visible(&anim_data_visible, ac, ANIMTYPE_GROUP);
/* Rearrange groups first:
* - The group's channels will only get considered
* if nothing happened when rearranging the groups
* i.e. the rearrange function returned 0.
*/
do_channels = (rearrange_animchannel_islands(
&act->groups, rearrange_func, mode, ANIMTYPE_GROUP, &anim_data_visible) == 0);
/* free temp data */
BLI_freelistN(&anim_data_visible);
if (do_channels) {
bActionGroup *agrp;
/* Filter visible data. */
rearrange_animchannels_filter_visible(&anim_data_visible, ac, ANIMTYPE_FCURVE);
for (agrp = act->groups.first; agrp; agrp = agrp->next) {
/* only consider F-Curves if they're visible (group expanded) */
if (EXPANDED_AGRP(ac, agrp)) {
rearrange_animchannel_islands(
&agrp->channels, rearrange_func, mode, ANIMTYPE_FCURVE, &anim_data_visible);
}
}
/* free temp data */
BLI_freelistN(&anim_data_visible);
}
/* assemble lists into one list (and clear moved tags) */
join_groups_action_temp(act);
}
/* ------------------- */
static void rearrange_nla_control_channels(bAnimContext *ac,
AnimData *adt,
eRearrangeAnimChan_Mode mode)
{
ListBase anim_data_visible = {NULL, NULL};
NlaTrack *nlt;
NlaStrip *strip;
/* get rearranging function */
AnimChanRearrangeFp rearrange_func = rearrange_get_mode_func(mode);
if (rearrange_func == NULL) {
return;
}
/* skip if these curves aren't being shown */
if (adt->flag & ADT_NLA_SKEYS_COLLAPSED) {
return;
}
/* Filter visible data. */
rearrange_animchannels_filter_visible(&anim_data_visible, ac, ANIMTYPE_NLACURVE);
/* we cannot rearrange between strips, but within each strip, we can rearrange those curves */
for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) {
for (strip = nlt->strips.first; strip; strip = strip->next) {
rearrange_animchannel_islands(
&strip->fcurves, rearrange_func, mode, ANIMTYPE_NLACURVE, &anim_data_visible);
}
}
/* free temp data */
BLI_freelistN(&anim_data_visible);
}
/* ------------------- */
static void rearrange_gpencil_channels(bAnimContext *ac, eRearrangeAnimChan_Mode mode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* get rearranging function */
AnimChanRearrangeFp rearrange_func = rearrange_gpencil_get_mode_func(mode);
if (rearrange_func == NULL) {
return;
}
/* get Grease Pencil datablocks */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
for (ale = anim_data.first; ale; ale = ale->next) {
ListBase anim_data_visible = {NULL, NULL};
bGPdata *gpd = ale->data;
/* only consider layers if this datablock is open */
BLI_assert(ale->type == ANIMTYPE_GPDATABLOCK);
if ((gpd->flag & GP_DATA_EXPAND) == 0) {
continue;
}
/* Filter visible data. */
rearrange_animchannels_filter_visible(&anim_data_visible, ac, ANIMTYPE_GPLAYER);
/* rearrange datablock's layers */
rearrange_animchannel_islands(
&gpd->layers, rearrange_func, mode, ANIMTYPE_GPLAYER, &anim_data_visible);
/* free visible layers data */
BLI_freelistN(&anim_data_visible);
/* Tag to recalc geometry */
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
}
/* free GPD channel data */
ANIM_animdata_freelist(&anim_data);
WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
}
/* ------------------- */
static int animchannels_rearrange_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
eRearrangeAnimChan_Mode mode;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* get mode */
mode = RNA_enum_get(op->ptr, "direction");
/* method to move channels depends on the editor */
if (ac.datatype == ANIMCONT_GPENCIL) {
/* Grease Pencil channels */
rearrange_gpencil_channels(&ac, mode);
}
else if (ac.datatype == ANIMCONT_MASK) {
/* Grease Pencil channels */
printf("Mask does not supported for moving yet\n");
}
else if (ac.datatype == ANIMCONT_ACTION) {
/* Directly rearrange action's channels */
rearrange_action_channels(&ac, ac.data, mode);
}
else {
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* get animdata blocks */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
for (ale = anim_data.first; ale; ale = ale->next) {
AnimData *adt = ale->data;
switch (ac.datatype) {
case ANIMCONT_NLA: /* NLA-tracks only */
rearrange_nla_channels(&ac, adt, mode);
DEG_id_tag_update(ale->id, ID_RECALC_ANIMATION);
break;
case ANIMCONT_DRIVERS: /* Drivers list only */
rearrange_driver_channels(&ac, adt, mode);
break;
case ANIMCONT_ACTION: /* Single Action only... */
case ANIMCONT_SHAPEKEY: /* DOUBLE CHECK ME... */
{
if (adt->action) {
rearrange_action_channels(&ac, adt->action, mode);
}
else if (G.debug & G_DEBUG) {
printf("Animdata has no action\n");
}
break;
}
default: /* DopeSheet/Graph Editor - Some Actions + NLA Control Curves */
{
/* NLA Control Curves */
if (adt->nla_tracks.first) {
rearrange_nla_control_channels(&ac, adt, mode);
}
/* Action */
if (adt->action) {
rearrange_action_channels(&ac, adt->action, mode);
}
else if (G.debug & G_DEBUG) {
printf("Animdata has no action\n");
}
break;
}
}
}
/* free temp data */
ANIM_animdata_freelist(&anim_data);
}
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ORDER, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_move(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Move Channels";
ot->idname = "ANIM_OT_channels_move";
ot->description = "Rearrange selected animation channels";
/* api callbacks */
ot->exec = animchannels_rearrange_exec;
ot->poll = animedit_poll_channels_nla_tweakmode_off;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
ot->prop = RNA_def_enum(ot->srna,
"direction",
prop_animchannel_rearrange_types,
REARRANGE_ANIMCHAN_DOWN,
"Direction",
"");
}
/* ******************** Group Channel Operator ************************ */
static bool animchannels_grouping_poll(bContext *C)
{
ScrArea *area = CTX_wm_area(C);
SpaceLink *sl;
/* channels region test */
/* TODO: could enhance with actually testing if channels region? */
if (ELEM(NULL, area, CTX_wm_region(C))) {
return 0;
}
/* animation editor test - must be suitable modes only */
sl = CTX_wm_space_data(C);
switch (area->spacetype) {
/* supported... */
case SPACE_ACTION: {
SpaceAction *saction = (SpaceAction *)sl;
/* dopesheet and action only - all others are for other datatypes or have no groups */
if (ELEM(saction->mode, SACTCONT_ACTION, SACTCONT_DOPESHEET) == 0) {
return 0;
}
break;
}
case SPACE_GRAPH: {
SpaceGraph *sipo = (SpaceGraph *)sl;
/* drivers can't have groups... */
if (sipo->mode != SIPO_MODE_ANIMATION) {
return 0;
}
break;
}
/* unsupported... */
default:
return 0;
}
return 1;
}
/* ----------------------------------------------------------- */
static void animchannels_group_channels(bAnimContext *ac,
bAnimListElem *adt_ref,
const char name[])
{
AnimData *adt = adt_ref->adt;
bAction *act = adt->action;
if (act) {
ListBase anim_data = {NULL, NULL};
int filter;
/* find selected F-Curves to re-group */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL);
ANIM_animdata_filter(ac, &anim_data, filter, adt_ref, ANIMCONT_CHANNEL);
if (anim_data.first) {
bActionGroup *agrp;
bAnimListElem *ale;
/* create new group, which should now be part of the action */
agrp = action_groups_add_new(act, name);
BLI_assert(agrp != NULL);
/* transfer selected F-Curves across to new group */
for (ale = anim_data.first; ale; ale = ale->next) {
FCurve *fcu = (FCurve *)ale->data;
bActionGroup *grp = fcu->grp;
/* remove F-Curve from group, then group too if it is now empty */
action_groups_remove_channel(act, fcu);
if ((grp) && BLI_listbase_is_empty(&grp->channels)) {
BLI_freelinkN(&act->groups, grp);
}
/* add F-Curve to group */
action_groups_add_channel(act, agrp, fcu);
}
}
/* cleanup */
ANIM_animdata_freelist(&anim_data);
}
}
static int animchannels_group_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
char name[MAX_NAME];
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* get name for new group */
RNA_string_get(op->ptr, "name", name);
/* XXX: name for group should never be empty... */
if (name[0]) {
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* handle each animdata block separately, so that the regrouping doesn't flow into blocks */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA |
ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
for (ale = anim_data.first; ale; ale = ale->next) {
animchannels_group_channels(&ac, ale, name);
}
/* free temp data */
ANIM_animdata_freelist(&anim_data);
/* Updates. */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
}
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_group(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Group Channels";
ot->idname = "ANIM_OT_channels_group";
ot->description = "Add selected F-Curves to a new group";
/* callbacks */
ot->invoke = WM_operator_props_popup;
ot->exec = animchannels_group_exec;
ot->poll = animchannels_grouping_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
ot->prop = RNA_def_string(ot->srna,
"name",
"New Group",
sizeof(((bActionGroup *)NULL)->name),
"Name",
"Name of newly created group");
/* XXX: still not too sure about this - keeping same text is confusing... */
// RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
}
/* ----------------------------------------------------------- */
static int animchannels_ungroup_exec(bContext *C, wmOperator *UNUSED(op))
{
bAnimContext ac;
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* just selected F-Curves... */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL |
ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
for (ale = anim_data.first; ale; ale = ale->next) {
/* find action for this F-Curve... */
if (ale->adt && ale->adt->action) {
FCurve *fcu = (FCurve *)ale->data;
bAction *act = ale->adt->action;
/* only proceed to remove if F-Curve is in a group... */
if (fcu->grp) {
bActionGroup *agrp = fcu->grp;
/* remove F-Curve from group and add at tail (ungrouped) */
action_groups_remove_channel(act, fcu);
BLI_addtail(&act->curves, fcu);
/* delete group if it is now empty */
if (BLI_listbase_is_empty(&agrp->channels)) {
BLI_freelinkN(&act->groups, agrp);
}
}
}
}
/* cleanup */
ANIM_animdata_freelist(&anim_data);
/* updates */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_ungroup(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Ungroup Channels";
ot->idname = "ANIM_OT_channels_ungroup";
ot->description = "Remove selected F-Curves from their current groups";
/* callbacks */
ot->exec = animchannels_ungroup_exec;
ot->poll = animchannels_grouping_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ******************** Delete Channel Operator *********************** */
static void tag_update_animation_element(bAnimListElem *ale)
{
ID *id = ale->id;
AnimData *adt = BKE_animdata_from_id(id);
/* TODO(sergey): Technically, if the animation element is being deleted
* from a driver we don't have to tag action. This is something we can check
* for in the future. For now just do most reliable tag which was always happening. */
if (adt != NULL) {
DEG_id_tag_update(id, ID_RECALC_ANIMATION);
if (adt->action != NULL) {
DEG_id_tag_update(&adt->action->id, ID_RECALC_ANIMATION);
}
}
/* Deals with NLA and drivers.
* Doesn't cause overhead for action updates, since object will receive
* animation update after dependency graph flushes update from action to
* all its users. */
DEG_id_tag_update(id, ID_RECALC_ANIMATION);
}
static int animchannels_delete_exec(bContext *C, wmOperator *UNUSED(op))
{
bAnimContext ac;
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* cannot delete in shapekey */
if (ac.datatype == ANIMCONT_SHAPEKEY) {
return OPERATOR_CANCELLED;
}
/* do groups only first (unless in Drivers mode, where there are none) */
if (ac.datatype != ANIMCONT_DRIVERS) {
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL |
ANIMFILTER_LIST_CHANNELS | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
/* delete selected groups and their associated channels */
for (ale = anim_data.first; ale; ale = ale->next) {
/* only groups - don't check other types yet, since they may no-longer exist */
if (ale->type == ANIMTYPE_GROUP) {
bActionGroup *agrp = (bActionGroup *)ale->data;
AnimData *adt = ale->adt;
FCurve *fcu, *fcn;
/* skip this group if no AnimData available, as we can't safely remove the F-Curves */
if (adt == NULL) {
continue;
}
/* delete all of the Group's F-Curves, but no others */
for (fcu = agrp->channels.first; fcu && fcu->grp == agrp; fcu = fcn) {
fcn = fcu->next;
/* remove from group and action, then free */
action_groups_remove_channel(adt->action, fcu);
BKE_fcurve_free(fcu);
}
/* free the group itself */
if (adt->action) {
BLI_freelinkN(&adt->action->groups, agrp);
DEG_id_tag_update_ex(CTX_data_main(C), &adt->action->id, ID_RECALC_ANIMATION);
}
else {
MEM_freeN(agrp);
}
}
}
/* cleanup */
ANIM_animdata_freelist(&anim_data);
}
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL |
ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
/* delete selected data channels */
for (ale = anim_data.first; ale; ale = ale->next) {
switch (ale->type) {
case ANIMTYPE_FCURVE: {
/* F-Curves if we can identify its parent */
AnimData *adt = ale->adt;
FCurve *fcu = (FCurve *)ale->data;
/* try to free F-Curve */
ANIM_fcurve_delete_from_animdata(&ac, adt, fcu);
tag_update_animation_element(ale);
break;
}
case ANIMTYPE_NLACURVE: {
/* NLA Control Curve - Deleting it should disable the corresponding setting... */
NlaStrip *strip = (NlaStrip *)ale->owner;
FCurve *fcu = (FCurve *)ale->data;
if (STREQ(fcu->rna_path, "strip_time")) {
strip->flag &= ~NLASTRIP_FLAG_USR_TIME;
}
else if (STREQ(fcu->rna_path, "influence")) {
strip->flag &= ~NLASTRIP_FLAG_USR_INFLUENCE;
}
else {
printf("ERROR: Trying to delete NLA Control Curve for unknown property '%s'\n",
fcu->rna_path);
}
/* unlink and free the F-Curve */
BLI_remlink(&strip->fcurves, fcu);
BKE_fcurve_free(fcu);
tag_update_animation_element(ale);
break;
}
case ANIMTYPE_GPLAYER: {
/* Grease Pencil layer */
bGPdata *gpd = (bGPdata *)ale->id;
bGPDlayer *gpl = (bGPDlayer *)ale->data;
/* try to delete the layer's data and the layer itself */
BKE_gpencil_layer_delete(gpd, gpl);
ale->update = ANIM_UPDATE_DEPS;
break;
}
case ANIMTYPE_MASKLAYER: {
/* Mask layer */
Mask *mask = (Mask *)ale->id;
MaskLayer *masklay = (MaskLayer *)ale->data;
/* try to delete the layer's data and the layer itself */
BKE_mask_layer_remove(mask, masklay);
break;
}
}
}
ANIM_animdata_update(&ac, &anim_data);
ANIM_animdata_freelist(&anim_data);
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_REMOVED, NULL);
DEG_relations_tag_update(CTX_data_main(C));
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_delete(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Channels";
ot->idname = "ANIM_OT_channels_delete";
ot->description = "Delete all selected animation channels";
/* api callbacks */
ot->exec = animchannels_delete_exec;
ot->poll = animedit_poll_channels_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ********************** Set Flags Operator *********************** */
/* defines for setting animation-channel flags */
static const EnumPropertyItem prop_animchannel_setflag_types[] = {
{ACHANNEL_SETFLAG_TOGGLE, "TOGGLE", 0, "Toggle", ""},
{ACHANNEL_SETFLAG_CLEAR, "DISABLE", 0, "Disable", ""},
{ACHANNEL_SETFLAG_ADD, "ENABLE", 0, "Enable", ""},
{ACHANNEL_SETFLAG_INVERT, "INVERT", 0, "Invert", ""},
{0, NULL, 0, NULL, NULL},
};
/* defines for set animation-channel settings */
/* TODO: could add some more types, but those are really quite dependent on the mode... */
static const EnumPropertyItem prop_animchannel_settings_types[] = {
{ACHANNEL_SETTING_PROTECT, "PROTECT", 0, "Protect", ""},
{ACHANNEL_SETTING_MUTE, "MUTE", 0, "Mute", ""},
{0, NULL, 0, NULL, NULL},
};
/* ------------------- */
/**
* Set/clear a particular flag (setting) for all selected + visible channels
* \param setting: the setting to modify.
* \param mode: eAnimChannels_SetFlag.
* \param onlysel: only selected channels get the flag set.
*
* TODO: enable a setting which turns flushing on/off?.
*/
static void setflag_anim_channels(bAnimContext *ac,
eAnimChannel_Settings setting,
eAnimChannels_SetFlag mode,
bool onlysel,
bool flush)
{
ListBase anim_data = {NULL, NULL};
ListBase all_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* filter data that we need if flush is on */
if (flush) {
/* get list of all channels that selection may need to be flushed to
* - hierarchy visibility needs to be ignored so that settings can get flushed
* "down" inside closed containers
*/
filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_CHANNELS;
ANIM_animdata_filter(ac, &all_data, filter, ac->data, ac->datatype);
}
/* filter data that we're working on
* - hierarchy matters if we're doing this from the channels region
* since we only want to apply this to channels we can "see",
* and have these affect their relatives
* - but for Graph Editor, this gets used also from main region
* where hierarchy doesn't apply T21276.
*/
if ((ac->spacetype == SPACE_GRAPH) && (ac->regiontype != RGN_TYPE_CHANNELS)) {
/* graph editor (case 2) */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_CHANNELS | ANIMFILTER_CURVE_VISIBLE |
ANIMFILTER_NODUPLIS);
}
else {
/* standard case */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS |
ANIMFILTER_NODUPLIS);
}
if (onlysel) {
filter |= ANIMFILTER_SEL;
}
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* if toggling, check if disable or enable */
if (mode == ACHANNEL_SETFLAG_TOGGLE) {
/* default to turn all on, unless we encounter one that's on... */
mode = ACHANNEL_SETFLAG_ADD;
/* see if we should turn off instead... */
for (ale = anim_data.first; ale; ale = ale->next) {
/* set the setting in the appropriate way (if available) */
if (ANIM_channel_setting_get(ac, ale, setting) > 0) {
mode = ACHANNEL_SETFLAG_CLEAR;
break;
}
}
}
/* apply the setting */
for (ale = anim_data.first; ale; ale = ale->next) {
/* skip channel if setting is not available */
if (ANIM_channel_setting_get(ac, ale, setting) == -1) {
continue;
}
/* set the setting in the appropriate way */
ANIM_channel_setting_set(ac, ale, setting, mode);
tag_update_animation_element(ale);
/* if flush status... */
if (flush) {
ANIM_flush_setting_anim_channels(ac, &all_data, ale, setting, mode);
}
}
ANIM_animdata_freelist(&anim_data);
BLI_freelistN(&all_data);
}
/* ------------------- */
static int animchannels_setflag_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
eAnimChannel_Settings setting;
eAnimChannels_SetFlag mode;
bool flush = true;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* mode (eAnimChannels_SetFlag), setting (eAnimChannel_Settings) */
mode = RNA_enum_get(op->ptr, "mode");
setting = RNA_enum_get(op->ptr, "type");
/* check if setting is flushable */
if (setting == ACHANNEL_SETTING_EXPAND) {
flush = false;
}
/* modify setting
* - only selected channels are affected
*/
setflag_anim_channels(&ac, setting, mode, true, flush);
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
/* duplicate of 'ANIM_OT_channels_setting_toggle' for menu title only, weak! */
static void ANIM_OT_channels_setting_enable(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Enable Channel Setting";
ot->idname = "ANIM_OT_channels_setting_enable";
ot->description = "Enable specified setting on all selected animation channels";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = animchannels_setflag_exec;
ot->poll = animedit_poll_channels_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
/* flag-setting mode */
prop = RNA_def_enum(
ot->srna, "mode", prop_animchannel_setflag_types, ACHANNEL_SETFLAG_ADD, "Mode", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
/* setting to set */
ot->prop = RNA_def_enum(ot->srna, "type", prop_animchannel_settings_types, 0, "Type", "");
}
/* duplicate of 'ANIM_OT_channels_setting_toggle' for menu title only, weak! */
static void ANIM_OT_channels_setting_disable(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Disable Channel Setting";
ot->idname = "ANIM_OT_channels_setting_disable";
ot->description = "Disable specified setting on all selected animation channels";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = animchannels_setflag_exec;
ot->poll = animedit_poll_channels_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
/* flag-setting mode */
prop = RNA_def_enum(
ot->srna, "mode", prop_animchannel_setflag_types, ACHANNEL_SETFLAG_CLEAR, "Mode", "");
RNA_def_property_flag(prop, PROP_HIDDEN); /* internal hack - don't expose */
/* setting to set */
ot->prop = RNA_def_enum(ot->srna, "type", prop_animchannel_settings_types, 0, "Type", "");
}
static void ANIM_OT_channels_setting_toggle(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Toggle Channel Setting";
ot->idname = "ANIM_OT_channels_setting_toggle";
ot->description = "Toggle specified setting on all selected animation channels";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = animchannels_setflag_exec;
ot->poll = animedit_poll_channels_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
/* flag-setting mode */
prop = RNA_def_enum(
ot->srna, "mode", prop_animchannel_setflag_types, ACHANNEL_SETFLAG_TOGGLE, "Mode", "");
RNA_def_property_flag(prop, PROP_HIDDEN); /* internal hack - don't expose */
/* setting to set */
ot->prop = RNA_def_enum(ot->srna, "type", prop_animchannel_settings_types, 0, "Type", "");
}
static void ANIM_OT_channels_editable_toggle(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Toggle Channel Editability";
ot->idname = "ANIM_OT_channels_editable_toggle";
ot->description = "Toggle editability of selected channels";
/* api callbacks */
ot->exec = animchannels_setflag_exec;
ot->poll = animedit_poll_channels_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
/* flag-setting mode */
RNA_def_enum(
ot->srna, "mode", prop_animchannel_setflag_types, ACHANNEL_SETFLAG_TOGGLE, "Mode", "");
/* setting to set */
prop = RNA_def_enum(
ot->srna, "type", prop_animchannel_settings_types, ACHANNEL_SETTING_PROTECT, "Type", "");
RNA_def_property_flag(prop, PROP_HIDDEN); /* internal hack - don't expose */
}
/* ********************** Expand Channels Operator *********************** */
static int animchannels_expand_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
bool onlysel = true;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* only affect selected channels? */
if (RNA_boolean_get(op->ptr, "all")) {
onlysel = false;
}
/* modify setting */
setflag_anim_channels(&ac, ACHANNEL_SETTING_EXPAND, ACHANNEL_SETFLAG_ADD, onlysel, false);
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_expand(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Expand Channels";
ot->idname = "ANIM_OT_channels_expand";
ot->description = "Expand (i.e. open) all selected expandable animation channels";
/* api callbacks */
ot->exec = animchannels_expand_exec;
ot->poll = animedit_poll_channels_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
ot->prop = RNA_def_boolean(
ot->srna, "all", 1, "All", "Expand all channels (not just selected ones)");
}
/* ********************** Collapse Channels Operator *********************** */
static int animchannels_collapse_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
bool onlysel = true;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* only affect selected channels? */
if (RNA_boolean_get(op->ptr, "all")) {
onlysel = false;
}
/* modify setting */
setflag_anim_channels(&ac, ACHANNEL_SETTING_EXPAND, ACHANNEL_SETFLAG_CLEAR, onlysel, false);
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_collapse(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Collapse Channels";
ot->idname = "ANIM_OT_channels_collapse";
ot->description = "Collapse (i.e. close) all selected expandable animation channels";
/* api callbacks */
ot->exec = animchannels_collapse_exec;
ot->poll = animedit_poll_channels_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
ot->prop = RNA_def_boolean(
ot->srna, "all", true, "All", "Collapse all channels (not just selected ones)");
}
/* ************ Remove All "Empty" AnimData Blocks Operator ********* */
/* We define "empty" AnimData blocks here as those which have all 3 of criteria:
* 1) No active action OR that active actions are empty
* Assuming that all legitimate entries will have an action,
* and that empty actions
* 2) No NLA Tracks + NLA Strips
* Assuming that users haven't set up any of these as "placeholders"
* for convenience sake, and that most that exist were either unintentional
* or are no longer wanted
* 3) No drivers
*/
static int animchannels_clean_empty_exec(bContext *C, wmOperator *UNUSED(op))
{
bAnimContext ac;
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* get animdata blocks */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA |
ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
for (ale = anim_data.first; ale; ale = ale->next) {
ID *id = ale->id;
AnimData *adt = ale->data;
bool action_empty = false;
bool nla_empty = false;
bool drivers_empty = false;
/* sanity checks */
BLI_assert((id != NULL) && (adt != NULL));
/* check if this is "empty" and can be deleted */
/* (For now, there are only these 3 criteria) */
/* 1) Active Action is missing or empty */
if (ELEM(NULL, adt->action, adt->action->curves.first)) {
action_empty = true;
}
else {
/* TODO: check for keyframe + fmodifier data on these too */
}
/* 2) No NLA Tracks and/or NLA Strips */
if (adt->nla_tracks.first == NULL) {
nla_empty = true;
}
else {
NlaTrack *nlt;
/* empty tracks? */
for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) {
if (nlt->strips.first) {
/* stop searching, as we found one that actually had stuff we don't want lost
* NOTE: nla_empty gets reset to false, as a previous track may have been empty
*/
nla_empty = false;
break;
}
if (nlt->strips.first == NULL) {
/* this track is empty, but another one may still have stuff in it, so can't break yet */
nla_empty = true;
}
}
}
/* 3) Drivers */
drivers_empty = (adt->drivers.first == NULL);
/* remove AnimData? */
if (action_empty && nla_empty && drivers_empty) {
BKE_animdata_free(id, true);
}
}
/* free temp data */
ANIM_animdata_freelist(&anim_data);
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_clean_empty(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Empty Animation Data";
ot->idname = "ANIM_OT_channels_clean_empty";
ot->description = "Delete all empty animation data containers from visible data-blocks";
/* api callbacks */
ot->exec = animchannels_clean_empty_exec;
ot->poll = animedit_poll_channels_nla_tweakmode_off;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ******************* Reenable Disabled Operator ******************* */
static bool animchannels_enable_poll(bContext *C)
{
ScrArea *area = CTX_wm_area(C);
/* channels region test */
/* TODO: could enhance with actually testing if channels region? */
if (ELEM(NULL, area, CTX_wm_region(C))) {
return 0;
}
/* animation editor test - Action/Dopesheet/etc. and Graph only */
if (ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH) == 0) {
return 0;
}
return 1;
}
static int animchannels_enable_exec(bContext *C, wmOperator *UNUSED(op))
{
bAnimContext ac;
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS);
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
/* loop through filtered data and clean curves */
for (ale = anim_data.first; ale; ale = ale->next) {
FCurve *fcu = (FCurve *)ale->data;
/* remove disabled flags from F-Curves */
fcu->flag &= ~FCURVE_DISABLED;
/* for drivers, let's do the same too */
if (fcu->driver) {
fcu->driver->flag &= ~DRIVER_FLAG_INVALID;
}
/* tag everything for updates - in particular, this is needed to get drivers working again */
ale->update |= ANIM_UPDATE_DEPS;
}
ANIM_animdata_update(&ac, &anim_data);
ANIM_animdata_freelist(&anim_data);
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_fcurves_enable(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Revive Disabled F-Curves";
ot->idname = "ANIM_OT_channels_fcurves_enable";
ot->description = "Clears 'disabled' tag from all F-Curves to get broken F-Curves working again";
/* api callbacks */
ot->exec = animchannels_enable_exec;
ot->poll = animchannels_enable_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ****************** Find / Set Filter Operator ******************** */
/* XXX: make this generic? */
static bool animchannels_find_poll(bContext *C)
{
ScrArea *area = CTX_wm_area(C);
if (area == NULL) {
return 0;
}
/* animation editor with dopesheet */
return ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH, SPACE_NLA);
}
/* find_invoke() - Get initial channels */
static int animchannels_find_invoke(bContext *C, wmOperator *op, const wmEvent *evt)
{
bAnimContext ac;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* set initial filter text, and enable filter */
RNA_string_set(op->ptr, "query", ac.ads->searchstr);
/* defer to popup */
return WM_operator_props_popup(C, op, evt);
}
/* find_exec() - Called to set the value */
static int animchannels_find_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* update filter text */
RNA_string_get(op->ptr, "query", ac.ads->searchstr);
/* redraw */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_find(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Find Channels";
ot->idname = "ANIM_OT_channels_find";
ot->description = "Filter the set of channels shown to only include those with matching names";
/* callbacks */
ot->invoke = animchannels_find_invoke;
ot->exec = animchannels_find_exec;
ot->poll = animchannels_find_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_string(ot->srna,
"query",
"Query",
sizeof(((bDopeSheet *)NULL)->searchstr),
"",
"Text to search for in channel names");
}
/* ********************** Select All Operator *********************** */
static int animchannels_selectall_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* 'standard' behavior - check if selected, then apply relevant selection */
const int action = RNA_enum_get(op->ptr, "action");
switch (action) {
case SEL_TOGGLE:
ANIM_anim_channels_select_toggle(&ac);
break;
case SEL_SELECT:
ANIM_anim_channels_select_set(&ac, ACHANNEL_SETFLAG_ADD);
break;
case SEL_DESELECT:
ANIM_anim_channels_select_set(&ac, ACHANNEL_SETFLAG_CLEAR);
break;
case SEL_INVERT:
ANIM_anim_channels_select_set(&ac, ACHANNEL_SETFLAG_INVERT);
break;
default:
BLI_assert(0);
break;
}
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_select_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select All";
ot->idname = "ANIM_OT_channels_select_all";
ot->description = "Toggle selection of all animation channels";
/* api callbacks */
ot->exec = animchannels_selectall_exec;
ot->poll = animedit_poll_channels_nla_tweakmode_off;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
WM_operator_properties_select_all(ot);
}
/* ******************** Box Select Operator *********************** */
static void box_select_anim_channels(bAnimContext *ac, rcti *rect, short selectmode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
SpaceNla *snla = (SpaceNla *)ac->sl;
View2D *v2d = &ac->region->v2d;
rctf rectf;
/* convert border-region to view coordinates */
UI_view2d_region_to_view(v2d, rect->xmin, rect->ymin + 2, &rectf.xmin, &rectf.ymin);
UI_view2d_region_to_view(v2d, rect->xmax, rect->ymax - 2, &rectf.xmax, &rectf.ymax);
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
float ymax;
if (ac->datatype == ANIMCONT_NLA) {
ymax = NLACHANNEL_FIRST_TOP(ac);
}
else {
ymax = ACHANNEL_FIRST_TOP(ac);
}
/* loop over data, doing box select */
for (ale = anim_data.first; ale; ale = ale->next) {
float ymin;
if (ac->datatype == ANIMCONT_NLA) {
ymin = ymax - NLACHANNEL_STEP(snla);
}
else {
ymin = ymax - ACHANNEL_STEP(ac);
}
/* if channel is within border-select region, alter it */
if (!((ymax < rectf.ymin) || (ymin > rectf.ymax))) {
/* set selection flags only */
ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, selectmode);
/* type specific actions */
switch (ale->type) {
case ANIMTYPE_GROUP: {
bActionGroup *agrp = (bActionGroup *)ale->data;
select_pchan_for_action_group(ac, agrp, ale);
/* always clear active flag after doing this */
agrp->flag &= ~AGRP_ACTIVE;
break;
}
case ANIMTYPE_NLATRACK: {
NlaTrack *nlt = (NlaTrack *)ale->data;
/* for now, it's easier just to do this here manually, as defining a new type
* currently adds complications when doing other stuff
*/
ACHANNEL_SET_FLAG(nlt, selectmode, NLATRACK_SELECTED);
break;
}
}
}
/* set minimum extent to be the maximum of the next channel */
ymax = ymin;
}
/* cleanup */
ANIM_animdata_freelist(&anim_data);
}
/* ------------------- */
static int animchannels_box_select_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
rcti rect;
short selectmode = 0;
const bool select = !RNA_boolean_get(op->ptr, "deselect");
const bool extend = RNA_boolean_get(op->ptr, "extend");
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* get settings from operator */
WM_operator_properties_border_to_rcti(op, &rect);
if (!extend) {
ANIM_anim_channels_select_set(&ac, ACHANNEL_SETFLAG_CLEAR);
}
if (select) {
selectmode = ACHANNEL_SETFLAG_ADD;
}
else {
selectmode = ACHANNEL_SETFLAG_CLEAR;
}
/* apply box_select animation channels */
box_select_anim_channels(&ac, &rect, selectmode);
/* send notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_select_box(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Box Select";
ot->idname = "ANIM_OT_channels_select_box";
ot->description = "Select all animation channels within the specified region";
/* api callbacks */
ot->invoke = WM_gesture_box_invoke;
ot->exec = animchannels_box_select_exec;
ot->modal = WM_gesture_box_modal;
ot->cancel = WM_gesture_box_cancel;
ot->poll = animedit_poll_channels_nla_tweakmode_off;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* rna */
WM_operator_properties_gesture_box_select(ot);
}
/* ******************* Rename Operator ***************************** */
/* Allow renaming some channels by clicking on them */
static bool rename_anim_channels(bAnimContext *ac, int channel_index)
{
ListBase anim_data = {NULL, NULL};
const bAnimChannelType *acf;
bAnimListElem *ale;
int filter;
bool success = false;
/* get the channel that was clicked on */
/* filter channels */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* get channel from index */
ale = BLI_findlink(&anim_data, channel_index);
if (ale == NULL) {
/* channel not found */
if (G.debug & G_DEBUG) {
printf("Error: animation channel (index = %d) not found in rename_anim_channels()\n",
channel_index);
}
ANIM_animdata_freelist(&anim_data);
return false;
}
/* don't allow renaming linked channels */
if ((ale->fcurve_owner_id != NULL && ID_IS_LINKED(ale->fcurve_owner_id)) ||
(ale->id != NULL && ID_IS_LINKED(ale->id))) {
ANIM_animdata_freelist(&anim_data);
return false;
}
/* check that channel can be renamed */
acf = ANIM_channel_get_typeinfo(ale);
if (acf && acf->name_prop) {
PointerRNA ptr;
PropertyRNA *prop;
/* ok if we can get name property to edit from this channel */
if (acf->name_prop(ale, &ptr, &prop)) {
/* Actually showing the rename text-field is done on redraw,
* so here we just store the index of this channel in the
* dope-sheet data, which will get utilized when drawing the channel.
*
* +1 factor is for backwards compatibility issues. */
if (ac->ads) {
ac->ads->renameIndex = channel_index + 1;
success = true;
}
}
}
/* free temp data and tag for refresh */
ANIM_animdata_freelist(&anim_data);
ED_region_tag_redraw(ac->region);
return success;
}
static int animchannels_channel_get(bAnimContext *ac, const int mval[2])
{
ARegion *region;
View2D *v2d;
int channel_index;
float x, y;
/* get useful pointers from animation context data */
region = ac->region;
v2d = &region->v2d;
/* Figure out which channel user clicked in. */
UI_view2d_region_to_view(v2d, mval[0], mval[1], &x, &y);
if (ac->datatype == ANIMCONT_NLA) {
SpaceNla *snla = (SpaceNla *)ac->sl;
UI_view2d_listview_view_to_cell(NLACHANNEL_NAMEWIDTH,
NLACHANNEL_STEP(snla),
0,
NLACHANNEL_FIRST_TOP(ac),
x,
y,
NULL,
&channel_index);
}
else {
UI_view2d_listview_view_to_cell(ACHANNEL_NAMEWIDTH,
ACHANNEL_STEP(ac),
0,
ACHANNEL_FIRST_TOP(ac),
x,
y,
NULL,
&channel_index);
}
return channel_index;
}
static int animchannels_rename_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
bAnimContext ac;
int channel_index;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
channel_index = animchannels_channel_get(&ac, event->mval);
/* handle click */
if (rename_anim_channels(&ac, channel_index)) {
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_RENAME, NULL);
return OPERATOR_FINISHED;
}
/* allow event to be handled by selectall operator */
return OPERATOR_PASS_THROUGH;
}
static void ANIM_OT_channels_rename(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Rename Channels";
ot->idname = "ANIM_OT_channels_rename";
ot->description = "Rename animation channel under mouse";
/* api callbacks */
ot->invoke = animchannels_rename_invoke;
ot->poll = animedit_poll_channels_active;
}
/* ******************** Mouse-Click Operator *********************** */
/* Handle selection changes due to clicking on channels. Settings will get caught by UI code... */
static int click_select_channel_scene(bAnimListElem *ale,
const short /* eEditKeyframes_Select or -1 */ selectmode)
{
Scene *sce = (Scene *)ale->data;
AnimData *adt = sce->adt;
/* set selection status */
if (selectmode == SELECT_INVERT) {
/* swap select */
sce->flag ^= SCE_DS_SELECTED;
if (adt) {
adt->flag ^= ADT_UI_SELECTED;
}
}
else {
sce->flag |= SCE_DS_SELECTED;
if (adt) {
adt->flag |= ADT_UI_SELECTED;
}
}
return (ND_ANIMCHAN | NA_SELECTED);
}
static int click_select_channel_object(bContext *C,
bAnimContext *ac,
bAnimListElem *ale,
const short /* eEditKeyframes_Select or -1 */ selectmode)
{
ViewLayer *view_layer = ac->view_layer;
Base *base = (Base *)ale->data;
Object *ob = base->object;
AnimData *adt = ob->adt;
if ((base->flag & BASE_SELECTABLE) == 0) {
return 0;
}
if (selectmode == SELECT_INVERT) {
/* swap select */
ED_object_base_select(base, BA_INVERT);
if (adt) {
adt->flag ^= ADT_UI_SELECTED;
}
}
else {
Base *b;
/* deselect all */
/* TODO: should this deselect all other types of channels too? */
for (b = view_layer->object_bases.first; b; b = b->next) {
ED_object_base_select(b, BA_DESELECT);
if (b->object->adt) {
b->object->adt->flag &= ~(ADT_UI_SELECTED | ADT_UI_ACTIVE);
}
}
/* select object now */
ED_object_base_select(base, BA_SELECT);
if (adt) {
adt->flag |= ADT_UI_SELECTED;
}
}
/* Change active object - regardless of whether it is now selected, see: T37883.
*
* Ensure we exit edit-mode on whatever object was active before
* to avoid getting stuck there, see: T48747. */
ED_object_base_activate_with_mode_exit_if_needed(C, base); /* adds notifier */
if ((adt) && (adt->flag & ADT_UI_SELECTED)) {
adt->flag |= ADT_UI_ACTIVE;
}
return (ND_ANIMCHAN | NA_SELECTED);
}
static int click_select_channel_dummy(bAnimContext *ac,
bAnimListElem *ale,
const short /* eEditKeyframes_Select or -1 */ selectmode)
{
if (ale->adt == NULL) {
return 0;
}
/* select/deselect */
if (selectmode == SELECT_INVERT) {
/* inverse selection status of this AnimData block only */
ale->adt->flag ^= ADT_UI_SELECTED;
}
else {
/* select AnimData block by itself */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
ale->adt->flag |= ADT_UI_SELECTED;
}
/* set active? */
if (ale->adt->flag & ADT_UI_SELECTED) {
ale->adt->flag |= ADT_UI_ACTIVE;
}
return (ND_ANIMCHAN | NA_SELECTED);
}
static int click_select_channel_group(bAnimContext *ac,
bAnimListElem *ale,
const short /* eEditKeyframes_Select or -1 */ selectmode,
const int filter)
{
bActionGroup *agrp = (bActionGroup *)ale->data;
Object *ob = NULL;
bPoseChannel *pchan = NULL;
/* Armatures-Specific Feature:
* Since groups are used to collect F-Curves of the same Bone by default
* (via Keying Sets) so that they can be managed better, we try to make
* things here easier for animators by mapping group selection to bone
* selection.
*
* Only do this if "Only Selected" dopesheet filter is not active, or else it
* becomes too unpredictable/tricky to manage
*/
if ((ac->ads->filterflag & ADS_FILTER_ONLYSEL) == 0) {
if ((ale->id) && (GS(ale->id->name) == ID_OB)) {
ob = (Object *)ale->id;
if (ob->type == OB_ARMATURE) {
/* Assume for now that any group with corresponding name is what we want
* (i.e. for an armature whose location is animated, things would break
* if the user were to add a bone named "Location").
*
* TODO: check the first F-Curve or so to be sure...
*/
pchan = BKE_pose_channel_find_name(ob->pose, agrp->name);
}
}
}
/* select/deselect group */
if (selectmode == SELECT_INVERT) {
/* inverse selection status of this group only */
agrp->flag ^= AGRP_SELECTED;
}
else if (selectmode == -1) {
/* select all in group (and deselect everything else) */
FCurve *fcu;
/* deselect all other channels */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
if (pchan) {
ED_pose_deselect_all(ob, SEL_DESELECT, false);
}
/* only select channels in group and group itself */
for (fcu = agrp->channels.first; fcu && fcu->grp == agrp; fcu = fcu->next) {
fcu->flag |= FCURVE_SELECTED;
}
agrp->flag |= AGRP_SELECTED;
}
else {
/* select group by itself */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
if (pchan) {
ED_pose_deselect_all(ob, SEL_DESELECT, false);
}
agrp->flag |= AGRP_SELECTED;
}
/* if group is selected now, make group the 'active' one in the visible list */
if (agrp->flag & AGRP_SELECTED) {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, agrp, ANIMTYPE_GROUP);
if (pchan) {
ED_pose_bone_select(ob, pchan, true);
}
}
else {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, NULL, ANIMTYPE_GROUP);
if (pchan) {
ED_pose_bone_select(ob, pchan, false);
}
}
return (ND_ANIMCHAN | NA_SELECTED);
}
static int click_select_channel_fcurve(bAnimContext *ac,
bAnimListElem *ale,
const short /* eEditKeyframes_Select or -1 */ selectmode,
const int filter)
{
FCurve *fcu = (FCurve *)ale->data;
/* select/deselect */
if (selectmode == SELECT_INVERT) {
/* inverse selection status of this F-Curve only */
fcu->flag ^= FCURVE_SELECTED;
}
else {
/* select F-Curve by itself */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
fcu->flag |= FCURVE_SELECTED;
}
/* if F-Curve is selected now, make F-Curve the 'active' one in the visible list */
if (fcu->flag & FCURVE_SELECTED) {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, fcu, ale->type);
}
return (ND_ANIMCHAN | NA_SELECTED);
}
static int click_select_channel_shapekey(bAnimContext *ac,
bAnimListElem *ale,
const short /* eEditKeyframes_Select or -1 */ selectmode)
{
KeyBlock *kb = (KeyBlock *)ale->data;
/* select/deselect */
if (selectmode == SELECT_INVERT) {
/* inverse selection status of this ShapeKey only */
kb->flag ^= KEYBLOCK_SEL;
}
else {
/* select ShapeKey by itself */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
kb->flag |= KEYBLOCK_SEL;
}
return (ND_ANIMCHAN | NA_SELECTED);
}
static int click_select_channel_nlacontrols(bAnimListElem *ale)
{
AnimData *adt = (AnimData *)ale->data;
/* Toggle expand:
* - Although the triangle widget already allows this,
* since there's nothing else that can be done here now,
* let's just use it for easier expand/collapse for now.
*/
adt->flag ^= ADT_NLA_SKEYS_COLLAPSED;
return (ND_ANIMCHAN | NA_EDITED);
}
static int click_select_channel_gpdatablock(bAnimListElem *ale)
{
bGPdata *gpd = (bGPdata *)ale->data;
/* Toggle expand:
* - Although the triangle widget already allows this,
* the whole channel can also be used for this purpose.
*/
gpd->flag ^= GP_DATA_EXPAND;
return (ND_ANIMCHAN | NA_EDITED);
}
static int click_select_channel_gplayer(bContext *C,
bAnimContext *ac,
bAnimListElem *ale,
const short /* eEditKeyframes_Select or -1 */ selectmode,
const int filter)
{
bGPdata *gpd = (bGPdata *)ale->id;
bGPDlayer *gpl = (bGPDlayer *)ale->data;
/* select/deselect */
if (selectmode == SELECT_INVERT) {
/* invert selection status of this layer only */
gpl->flag ^= GP_LAYER_SELECT;
}
else {
/* select layer by itself */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
gpl->flag |= GP_LAYER_SELECT;
}
/* change active layer, if this is selected (since we must always have an active layer) */
if (gpl->flag & GP_LAYER_SELECT) {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, gpl, ANIMTYPE_GPLAYER);
/* update other layer status */
BKE_gpencil_layer_active_set(gpd, gpl);
BKE_gpencil_layer_autolock_set(gpd, false);
DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
}
/* Grease Pencil updates */
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED | ND_SPACE_PROPERTIES, NULL);
return (ND_ANIMCHAN | NA_EDITED); /* Animation Editors updates */
}
static int click_select_channel_maskdatablock(bAnimListElem *ale)
{
Mask *mask = (Mask *)ale->data;
/* Toggle expand
* - Although the triangle widget already allows this,
* the whole channel can also be used for this purpose.
*/
mask->flag ^= MASK_ANIMF_EXPAND;
return (ND_ANIMCHAN | NA_EDITED);
}
static int click_select_channel_masklayer(bAnimContext *ac,
bAnimListElem *ale,
const short /* eEditKeyframes_Select or -1 */ selectmode)
{
MaskLayer *masklay = (MaskLayer *)ale->data;
/* select/deselect */
if (selectmode == SELECT_INVERT) {
/* invert selection status of this layer only */
masklay->flag ^= MASK_LAYERFLAG_SELECT;
}
else {
/* select layer by itself */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
masklay->flag |= MASK_LAYERFLAG_SELECT;
}
return (ND_ANIMCHAN | NA_EDITED);
}
static int mouse_anim_channels(bContext *C,
bAnimContext *ac,
const int channel_index,
const short /* eEditKeyframes_Select or -1 */ selectmode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
int notifierFlags = 0;
/* get the channel that was clicked on */
/* filter channels */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* get channel from index */
ale = BLI_findlink(&anim_data, channel_index);
if (ale == NULL) {
/* channel not found */
if (G.debug & G_DEBUG) {
printf("Error: animation channel (index = %d) not found in mouse_anim_channels()\n",
channel_index);
}
ANIM_animdata_freelist(&anim_data);
return 0;
}
/* selectmode -1 is a special case for ActionGroups only,
* which selects all of the channels underneath it only. */
/* TODO: should this feature be extended to work with other channel types too? */
if ((selectmode == -1) && (ale->type != ANIMTYPE_GROUP)) {
/* normal channels should not behave normally in this case */
ANIM_animdata_freelist(&anim_data);
return 0;
}
/* action to take depends on what channel we've got */
/* WARNING: must keep this in sync with the equivalent function in nla_channels.c */
switch (ale->type) {
case ANIMTYPE_SCENE:
notifierFlags |= click_select_channel_scene(ale, selectmode);
break;
case ANIMTYPE_OBJECT:
notifierFlags |= click_select_channel_object(C, ac, ale, selectmode);
break;
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
case ANIMTYPE_DSCACHEFILE:
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
case ANIMTYPE_DSNTREE:
case ANIMTYPE_DSTEX:
case ANIMTYPE_DSLAT:
case ANIMTYPE_DSLINESTYLE:
case ANIMTYPE_DSSPK:
case ANIMTYPE_DSGPENCIL:
case ANIMTYPE_DSMCLIP:
case ANIMTYPE_DSHAIR:
case ANIMTYPE_DSPOINTCLOUD:
case ANIMTYPE_DSVOLUME:
case ANIMTYPE_DSSIMULATION:
notifierFlags |= click_select_channel_dummy(ac, ale, selectmode);
break;
case ANIMTYPE_GROUP:
notifierFlags |= click_select_channel_group(ac, ale, selectmode, filter);
break;
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE:
notifierFlags |= click_select_channel_fcurve(ac, ale, selectmode, filter);
break;
case ANIMTYPE_SHAPEKEY:
notifierFlags |= click_select_channel_shapekey(ac, ale, selectmode);
break;
case ANIMTYPE_NLACONTROLS:
notifierFlags |= click_select_channel_nlacontrols(ale);
break;
case ANIMTYPE_GPDATABLOCK:
notifierFlags |= click_select_channel_gpdatablock(ale);
break;
case ANIMTYPE_GPLAYER:
notifierFlags |= click_select_channel_gplayer(C, ac, ale, selectmode, filter);
break;
case ANIMTYPE_MASKDATABLOCK:
notifierFlags |= click_select_channel_maskdatablock(ale);
break;
case ANIMTYPE_MASKLAYER:
notifierFlags |= click_select_channel_masklayer(ac, ale, selectmode);
break;
default:
if (G.debug & G_DEBUG) {
printf("Error: Invalid channel type in mouse_anim_channels()\n");
}
break;
}
/* free channels */
ANIM_animdata_freelist(&anim_data);
/* return notifier flags */
return notifierFlags;
}
/* ------------------- */
/* handle clicking */
static int animchannels_mouseclick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
bAnimContext ac;
ARegion *region;
View2D *v2d;
int channel_index;
int notifierFlags = 0;
short selectmode;
float x, y;
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* get useful pointers from animation context data */
region = ac.region;
v2d = &region->v2d;
/* select mode is either replace (deselect all, then add) or add/extend */
if (RNA_boolean_get(op->ptr, "extend")) {
selectmode = SELECT_INVERT;
}
else if (RNA_boolean_get(op->ptr, "children_only")) {
/* this is a bit of a special case for ActionGroups only...
* should it be removed or extended to all instead? */
selectmode = -1;
}
else {
selectmode = SELECT_REPLACE;
}
/* figure out which channel user clicked in */
UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &x, &y);
UI_view2d_listview_view_to_cell(ACHANNEL_NAMEWIDTH,
ACHANNEL_STEP(&ac),
0,
ACHANNEL_FIRST_TOP(&ac),
x,
y,
NULL,
&channel_index);
/* handle mouse-click in the relevant channel then */
notifierFlags = mouse_anim_channels(C, &ac, channel_index, selectmode);
/* set notifier that things have changed */
WM_event_add_notifier(C, NC_ANIMATION | notifierFlags, NULL);
return OPERATOR_FINISHED;
}
static void ANIM_OT_channels_click(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Mouse Click on Channels";
ot->idname = "ANIM_OT_channels_click";
ot->description = "Handle mouse clicks over animation channels";
/* api callbacks */
ot->invoke = animchannels_mouseclick_invoke;
ot->poll = animedit_poll_channels_active;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
/* NOTE: don't save settings, otherwise, can end up with some weird behavior (sticky extend) */
prop = RNA_def_boolean(ot->srna, "extend", false, "Extend Select", ""); /* SHIFTKEY */
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "children_only", false, "Select Children Only", ""); /* CTRLKEY|SHIFTKEY */
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
static bool select_anim_channel_keys(bAnimContext *ac, int channel_index, bool extend)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
int filter;
bool success = false;
FCurve *fcu;
int i;
/* get the channel that was clicked on */
/* filter channels */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* get channel from index */
ale = BLI_findlink(&anim_data, channel_index);
if (ale == NULL) {
/* channel not found */
if (G.debug & G_DEBUG) {
printf("Error: animation channel (index = %d) not found in rename_anim_channels()\n",
channel_index);
}
ANIM_animdata_freelist(&anim_data);
return false;
}
fcu = (FCurve *)ale->key_data;
success = (fcu != NULL);
ANIM_animdata_freelist(&anim_data);
/* F-Curve may not have any keyframes */
if (fcu && fcu->bezt) {
BezTriple *bezt;
if (!extend) {
filter = (ANIMFILTER_DATA_VISIBLE);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
for (ale = anim_data.first; ale; ale = ale->next) {
FCurve *fcu_inner = (FCurve *)ale->key_data;
if (fcu_inner != NULL && fcu_inner->bezt != NULL) {
for (i = 0, bezt = fcu_inner->bezt; i < fcu_inner->totvert; i++, bezt++) {
bezt->f2 = bezt->f1 = bezt->f3 = 0;
}
}
}
ANIM_animdata_freelist(&anim_data);
}
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
bezt->f2 = bezt->f1 = bezt->f3 = SELECT;
}
}
/* free temp data and tag for refresh */
ED_region_tag_redraw(ac->region);
return success;
}
static int animchannels_channel_select_keys_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
bAnimContext ac;
int channel_index;
bool extend = RNA_boolean_get(op->ptr, "extend");
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
channel_index = animchannels_channel_get(&ac, event->mval);
/* handle click */
if (select_anim_channel_keys(&ac, channel_index, extend)) {
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
return OPERATOR_FINISHED;
}
/* allow event to be handled by selectall operator */
return OPERATOR_PASS_THROUGH;
}
static void ANIM_OT_channel_select_keys(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Select Channel Keyframes";
ot->idname = "ANIM_OT_channel_select_keys";
ot->description = "Select all keyframes of channel under mouse";
/* api callbacks */
ot->invoke = animchannels_channel_select_keys_invoke;
ot->poll = animedit_poll_channels_active;
prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/* ************************************************************************** */
/* Operator Registration */
void ED_operatortypes_animchannels(void)
{
WM_operatortype_append(ANIM_OT_channels_select_all);
WM_operatortype_append(ANIM_OT_channels_select_box);
WM_operatortype_append(ANIM_OT_channels_click);
WM_operatortype_append(ANIM_OT_channel_select_keys);
WM_operatortype_append(ANIM_OT_channels_rename);
WM_operatortype_append(ANIM_OT_channels_find);
WM_operatortype_append(ANIM_OT_channels_setting_enable);
WM_operatortype_append(ANIM_OT_channels_setting_disable);
WM_operatortype_append(ANIM_OT_channels_setting_toggle);
WM_operatortype_append(ANIM_OT_channels_delete);
/* XXX does this need to be a separate operator? */
WM_operatortype_append(ANIM_OT_channels_editable_toggle);
WM_operatortype_append(ANIM_OT_channels_move);
WM_operatortype_append(ANIM_OT_channels_expand);
WM_operatortype_append(ANIM_OT_channels_collapse);
WM_operatortype_append(ANIM_OT_channels_fcurves_enable);
WM_operatortype_append(ANIM_OT_channels_clean_empty);
WM_operatortype_append(ANIM_OT_channels_group);
WM_operatortype_append(ANIM_OT_channels_ungroup);
}
/* TODO: check on a poll callback for this, to get hotkeys into menus */
void ED_keymap_animchannels(wmKeyConfig *keyconf)
{
WM_keymap_ensure(keyconf, "Animation Channels", 0, 0);
}
/* ************************************************************************** */