Outliner RMB Menu - AnimData mangement
* When clicking on "Animation" items in the Outliner, there's now a menu containing from which you can change the action used, and refresh/delete all drivers. * Moved action-setting logic for AnimData actions to a single utility function in anim_sys, since this was starting to be done in too many places already. * Fixed Outliner refresh bug after changing the active action
This commit is contained in:
@@ -41,6 +41,7 @@ struct KeyingSet;
|
||||
struct KS_Path;
|
||||
|
||||
struct PointerRNA;
|
||||
struct ReportList;
|
||||
struct bAction;
|
||||
struct bActionGroup;
|
||||
struct AnimMapper;
|
||||
@@ -57,6 +58,9 @@ struct AnimData *BKE_animdata_from_id(struct ID *id);
|
||||
/* Add AnimData to the given ID-block */
|
||||
struct AnimData *BKE_id_add_animdata(struct ID *id);
|
||||
|
||||
/* Set active action used by AnimData from the given ID-block */
|
||||
short BKE_animdata_set_action(struct ReportList *reports, struct ID *id, struct bAction *act);
|
||||
|
||||
/* Free AnimData */
|
||||
void BKE_free_animdata(struct ID *id);
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
#include "BKE_global.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_library.h"
|
||||
#include "BKE_report.h"
|
||||
#include "BKE_utildefines.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
@@ -144,6 +145,59 @@ AnimData *BKE_id_add_animdata (ID *id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Action Setter --------------------------------------- */
|
||||
|
||||
/* Called when user tries to change the active action of an AnimData block (via RNA, Outliner, etc.) */
|
||||
short BKE_animdata_set_action (ReportList *reports, ID *id, bAction *act)
|
||||
{
|
||||
AnimData *adt = BKE_animdata_from_id(id);
|
||||
short ok = 0;
|
||||
|
||||
/* animdata validity check */
|
||||
if (adt == NULL) {
|
||||
BKE_report(reports, RPT_WARNING, "No AnimData to set action on");
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* active action is only editable when it is not a tweaking strip
|
||||
* see rna_AnimData_action_editable() in rna_animation.c
|
||||
*/
|
||||
if ((adt->flag & ADT_NLA_EDIT_ON) || (adt->actstrip) || (adt->tmpact)) {
|
||||
/* cannot remove, otherwise things turn to custard */
|
||||
BKE_report(reports, RPT_ERROR, "Cannot change action, as it is still being edited in NLA");
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* manage usercount for current action */
|
||||
if (adt->action)
|
||||
id_us_min((ID*)adt->action);
|
||||
|
||||
/* assume that AnimData's action can in fact be edited... */
|
||||
if (act) {
|
||||
/* action must have same type as owner */
|
||||
if (ELEM(act->idroot, 0, GS(id->name))) {
|
||||
/* can set */
|
||||
adt->action = act;
|
||||
id_us_plus((ID*)adt->action);
|
||||
ok = 1;
|
||||
}
|
||||
else {
|
||||
/* cannot set */
|
||||
BKE_reportf(reports, RPT_ERROR,
|
||||
"Couldn't set Action '%s' onto ID '%s', as it doesn't have suitably rooted paths for this purpose",
|
||||
act->id.name+2, id->name);
|
||||
//ok = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* just clearing the action... */
|
||||
adt->action = NULL;
|
||||
ok = 1;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* Freeing -------------------------------------------- */
|
||||
|
||||
/* Free AnimData used by the nominated ID-block, and clear ID-block's AnimData pointer */
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
|
||||
#include "RNA_access.h"
|
||||
#include "RNA_define.h"
|
||||
#include "RNA_enum_types.h"
|
||||
|
||||
#include "ED_keyframing.h"
|
||||
|
||||
@@ -3170,29 +3171,10 @@ static void set_operation_types(SpaceOops *soops, ListBase *lb,
|
||||
}
|
||||
}
|
||||
|
||||
static void unlink_action_cb(bContext *C, Scene *UNUSED(scene), TreeElement *te, TreeStoreElem *tsep, TreeStoreElem *UNUSED(tselem))
|
||||
static void unlink_action_cb(bContext *C, Scene *UNUSED(scene), TreeElement *UNUSED(te), TreeStoreElem *tsep, TreeStoreElem *UNUSED(tselem))
|
||||
{
|
||||
IdAdtTemplate *iat = (IdAdtTemplate *)tsep->id;
|
||||
AnimData *adt = iat->adt;
|
||||
|
||||
//printf("iat = '%s' | act = '%s'\n", iat->id.name, tselem->id->name);
|
||||
|
||||
/* active action is only editable when it is not a tweaking strip
|
||||
* see rna_AnimData_action_editable() in rna_animation.c
|
||||
*/
|
||||
if ((adt->flag & ADT_NLA_EDIT_ON) || (adt->actstrip) || (adt->tmpact)) {
|
||||
/* cannot remove, otherwise things turn to custard */
|
||||
ReportList *reports = CTX_wm_reports(C);
|
||||
|
||||
// FIXME: this only gets shown in info-window, since this is global not operator report
|
||||
BKE_report(reports, RPT_ERROR, "Cannot unlink action, as it is still being edited in NLA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* remove action... */
|
||||
id_us_min((ID*)adt->action);
|
||||
adt->action = NULL;
|
||||
/* just set action to NULL */
|
||||
BKE_animdata_set_action(CTX_wm_reports(C), tsep->id, NULL);
|
||||
}
|
||||
|
||||
static void unlink_material_cb(bContext *UNUSED(C), Scene *UNUSED(scene), TreeElement *te, TreeStoreElem *tsep, TreeStoreElem *UNUSED(tselem))
|
||||
@@ -3414,6 +3396,36 @@ static void outliner_do_object_operation(bContext *C, Scene *scene_act, SpaceOop
|
||||
|
||||
/* ******************************************** */
|
||||
|
||||
static void unlinkact_animdata_cb(int UNUSED(event), TreeElement *UNUSED(te), TreeStoreElem *tselem)
|
||||
{
|
||||
/* just set action to NULL */
|
||||
BKE_animdata_set_action(NULL, tselem->id, NULL);
|
||||
}
|
||||
|
||||
static void cleardrivers_animdata_cb(int UNUSED(event), TreeElement *UNUSED(te), TreeStoreElem *tselem)
|
||||
{
|
||||
IdAdtTemplate *iat = (IdAdtTemplate *)tselem->id;
|
||||
|
||||
/* just free drivers - stored as a list of F-Curves */
|
||||
free_fcurves(&iat->adt->drivers);
|
||||
}
|
||||
|
||||
static void refreshdrivers_animdata_cb(int UNUSED(event), TreeElement *UNUSED(te), TreeStoreElem *tselem)
|
||||
{
|
||||
IdAdtTemplate *iat = (IdAdtTemplate *)tselem->id;
|
||||
FCurve *fcu;
|
||||
|
||||
/* loop over drivers, performing refresh (i.e. check graph_buttons.c and rna_fcurve.c for details) */
|
||||
for (fcu = iat->adt->drivers.first; fcu; fcu= fcu->next) {
|
||||
fcu->flag &= ~FCURVE_DISABLED;
|
||||
|
||||
if (fcu->driver)
|
||||
fcu->driver->flag &= ~DRIVER_FLAG_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------- */
|
||||
|
||||
static void pchan_cb(int event, TreeElement *te, TreeStoreElem *UNUSED(tselem))
|
||||
{
|
||||
bPoseChannel *pchan= (bPoseChannel *)te->directdata;
|
||||
@@ -3775,6 +3787,224 @@ void OUTLINER_OT_id_operation(wmOperatorType *ot)
|
||||
|
||||
/* **************************************** */
|
||||
|
||||
static void outliner_do_id_set_operation(SpaceOops *soops, int type, ListBase *lb, ID *newid,
|
||||
void (*operation_cb)(TreeElement *, TreeStoreElem *, TreeStoreElem *, ID *))
|
||||
{
|
||||
TreeElement *te;
|
||||
TreeStoreElem *tselem;
|
||||
|
||||
for (te=lb->first; te; te= te->next) {
|
||||
tselem= TREESTORE(te);
|
||||
if (tselem->flag & TSE_SELECTED) {
|
||||
if(tselem->type==type) {
|
||||
TreeStoreElem *tsep = TREESTORE(te->parent);
|
||||
operation_cb(te, tselem, tsep, newid);
|
||||
}
|
||||
}
|
||||
if ((tselem->flag & TSE_CLOSED)==0) {
|
||||
outliner_do_id_set_operation(soops, type, &te->subtree, newid, operation_cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------ */
|
||||
|
||||
static void actionset_id_cb(TreeElement *te, TreeStoreElem *tselem, TreeStoreElem *tsep, ID *actId)
|
||||
{
|
||||
bAction *act = (bAction *)actId;
|
||||
|
||||
if (tselem->type == TSE_ANIM_DATA) {
|
||||
/* "animation" entries - action is child of this */
|
||||
BKE_animdata_set_action(NULL, tselem->id, act);
|
||||
}
|
||||
/* TODO: if any other "expander" channels which own actions need to support this menu,
|
||||
* add: tselem->type = ...
|
||||
*/
|
||||
else if (tsep && (tsep->type == TSE_ANIM_DATA)) {
|
||||
/* "animation" entries case again */
|
||||
BKE_animdata_set_action(NULL, tsep->id, act);
|
||||
}
|
||||
// TODO: other cases not supported yet
|
||||
}
|
||||
|
||||
static int outliner_action_set_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
SpaceOops *soops= CTX_wm_space_outliner(C);
|
||||
int scenelevel=0, objectlevel=0, idlevel=0, datalevel=0;
|
||||
|
||||
bAction *act;
|
||||
|
||||
/* check for invalid states */
|
||||
if (soops == NULL)
|
||||
return OPERATOR_CANCELLED;
|
||||
set_operation_types(soops, &soops->tree, &scenelevel, &objectlevel, &idlevel, &datalevel);
|
||||
|
||||
/* get action to use */
|
||||
act= BLI_findlink(&CTX_data_main(C)->action, RNA_enum_get(op->ptr, "action"));
|
||||
|
||||
if (act == NULL) {
|
||||
BKE_report(op->reports, RPT_ERROR, "No valid Action to add.");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
else if (act->idroot == 0) {
|
||||
/* hopefully in this case (i.e. library of userless actions), the user knows what they're doing... */
|
||||
BKE_reportf(op->reports, RPT_WARNING,
|
||||
"Action '%s' does not specify what datablocks it can be used on. Try setting the 'ID Root Type' setting from the Datablocks Editor for this Action to avoid future problems",
|
||||
act->id.name+2);
|
||||
}
|
||||
|
||||
/* perform action if valid channel */
|
||||
if (datalevel == TSE_ANIM_DATA)
|
||||
outliner_do_id_set_operation(soops, datalevel, &soops->tree, (ID*)act, actionset_id_cb);
|
||||
else if (idlevel == ID_AC)
|
||||
outliner_do_id_set_operation(soops, idlevel, &soops->tree, (ID*)act, actionset_id_cb);
|
||||
else
|
||||
return OPERATOR_CANCELLED;
|
||||
|
||||
/* set notifier that things have changed */
|
||||
WM_event_add_notifier(C, NC_ANIMATION|ND_NLA_ACTCHANGE, NULL);
|
||||
ED_undo_push(C, "Set action");
|
||||
|
||||
/* done */
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void OUTLINER_OT_action_set(wmOperatorType *ot)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
/* identifiers */
|
||||
ot->name= "Outliner Set Action";
|
||||
ot->idname= "OUTLINER_OT_action_set";
|
||||
ot->description= "Change the active action used";
|
||||
|
||||
/* api callbacks */
|
||||
ot->invoke= WM_enum_search_invoke;
|
||||
ot->exec= outliner_action_set_exec;
|
||||
ot->poll= ED_operator_outliner_active;
|
||||
|
||||
/* flags */
|
||||
ot->flag= 0;
|
||||
|
||||
/* props */
|
||||
// TODO: this would be nicer as an ID-pointer...
|
||||
prop= RNA_def_enum(ot->srna, "action", DummyRNA_NULL_items, 0, "Action", "");
|
||||
RNA_def_enum_funcs(prop, RNA_action_itemf);
|
||||
ot->prop= prop;
|
||||
}
|
||||
|
||||
/* **************************************** */
|
||||
|
||||
typedef enum eOutliner_AnimDataOps {
|
||||
OUTLINER_ANIMOP_INVALID = 0,
|
||||
|
||||
OUTLINER_ANIMOP_SET_ACT,
|
||||
OUTLINER_ANIMOP_CLEAR_ACT,
|
||||
|
||||
OUTLINER_ANIMOP_REFRESH_DRV,
|
||||
OUTLINER_ANIMOP_CLEAR_DRV
|
||||
|
||||
//OUTLINER_ANIMOP_COPY_DRIVERS,
|
||||
//OUTLINER_ANIMOP_PASTE_DRIVERS
|
||||
} eOutliner_AnimDataOps;
|
||||
|
||||
static EnumPropertyItem prop_animdata_op_types[] = {
|
||||
{OUTLINER_ANIMOP_SET_ACT, "SET_ACT", 0, "Set Action", ""},
|
||||
{OUTLINER_ANIMOP_CLEAR_ACT, "CLEAR_ACT", 0, "Unlink Action", ""},
|
||||
{OUTLINER_ANIMOP_REFRESH_DRV, "REFRESH_DRIVERS", 0, "Refresh Drivers", ""},
|
||||
//{OUTLINER_ANIMOP_COPY_DRIVERS, "COPY_DRIVERS", 0, "Copy Drivers", ""},
|
||||
//{OUTLINER_ANIMOP_PASTE_DRIVERS, "PASTE_DRIVERS", 0, "Paste Drivers", ""},
|
||||
{OUTLINER_ANIMOP_CLEAR_DRV, "CLEAR_DRIVERS", 0, "Clear Drivers", ""},
|
||||
{0, NULL, 0, NULL, NULL}
|
||||
};
|
||||
|
||||
static int outliner_animdata_operation_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
SpaceOops *soops= CTX_wm_space_outliner(C);
|
||||
int scenelevel=0, objectlevel=0, idlevel=0, datalevel=0;
|
||||
eOutliner_AnimDataOps event;
|
||||
short updateDeps = 0;
|
||||
|
||||
/* check for invalid states */
|
||||
if (soops == NULL)
|
||||
return OPERATOR_CANCELLED;
|
||||
|
||||
event= RNA_enum_get(op->ptr, "type");
|
||||
set_operation_types(soops, &soops->tree, &scenelevel, &objectlevel, &idlevel, &datalevel);
|
||||
|
||||
if (datalevel != TSE_ANIM_DATA)
|
||||
return OPERATOR_CANCELLED;
|
||||
|
||||
/* perform the core operation */
|
||||
switch (event) {
|
||||
case OUTLINER_ANIMOP_SET_ACT:
|
||||
/* delegate once again... */
|
||||
WM_operator_name_call(C, "OUTLINER_OT_action_set", WM_OP_INVOKE_REGION_WIN, NULL);
|
||||
break;
|
||||
|
||||
case OUTLINER_ANIMOP_CLEAR_ACT:
|
||||
/* clear active action - using standard rules */
|
||||
outliner_do_data_operation(soops, datalevel, event, &soops->tree, unlinkact_animdata_cb);
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION|ND_NLA_ACTCHANGE, NULL);
|
||||
ED_undo_push(C, "Unlink action");
|
||||
break;
|
||||
|
||||
case OUTLINER_ANIMOP_REFRESH_DRV:
|
||||
outliner_do_data_operation(soops, datalevel, event, &soops->tree, refreshdrivers_animdata_cb);
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION|ND_ANIMCHAN, NULL);
|
||||
//ED_undo_push(C, "Refresh Drivers"); /* no undo needed - shouldn't have any impact? */
|
||||
updateDeps = 1;
|
||||
break;
|
||||
|
||||
case OUTLINER_ANIMOP_CLEAR_DRV:
|
||||
outliner_do_data_operation(soops, datalevel, event, &soops->tree, cleardrivers_animdata_cb);
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION|ND_ANIMCHAN, NULL);
|
||||
ED_undo_push(C, "Clear Drivers");
|
||||
updateDeps = 1;
|
||||
break;
|
||||
|
||||
default: // invalid
|
||||
break;
|
||||
}
|
||||
|
||||
/* update dependencies */
|
||||
if (updateDeps) {
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
|
||||
/* rebuild depsgraph for the new deps */
|
||||
DAG_scene_sort(bmain, scene);
|
||||
|
||||
/* force an update of depsgraph */
|
||||
DAG_ids_flush_update(bmain, 0);
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
|
||||
void OUTLINER_OT_animdata_operation(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name= "Outliner Animation Data Operation";
|
||||
ot->idname= "OUTLINER_OT_animdata_operation";
|
||||
ot->description= "";
|
||||
|
||||
/* callbacks */
|
||||
ot->invoke= WM_menu_invoke;
|
||||
ot->exec= outliner_animdata_operation_exec;
|
||||
ot->poll= ED_operator_outliner_active;
|
||||
|
||||
ot->flag= 0;
|
||||
|
||||
ot->prop= RNA_def_enum(ot->srna, "type", prop_animdata_op_types, 0, "Animation Operation", "");
|
||||
}
|
||||
|
||||
/* **************************************** */
|
||||
|
||||
static EnumPropertyItem prop_data_op_types[] = {
|
||||
{1, "SELECT", 0, "Select", ""},
|
||||
{2, "DESELECT", 0, "Deselect", ""},
|
||||
@@ -3888,7 +4118,12 @@ static int do_outliner_operation_event(bContext *C, Scene *scene, ARegion *ar, S
|
||||
else if(datalevel) {
|
||||
if(datalevel==-1) error("Mixed selection");
|
||||
else {
|
||||
WM_operator_name_call(C, "OUTLINER_OT_data_operation", WM_OP_INVOKE_REGION_WIN, NULL);
|
||||
if (datalevel == TSE_ANIM_DATA)
|
||||
WM_operator_name_call(C, "OUTLINER_OT_animdata_operation", WM_OP_INVOKE_REGION_WIN, NULL);
|
||||
else if (datalevel == TSE_DRIVER_BASE)
|
||||
/* do nothing... no special ops needed yet */;
|
||||
else
|
||||
WM_operator_name_call(C, "OUTLINER_OT_data_operation", WM_OP_INVOKE_REGION_WIN, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,8 @@ void OUTLINER_OT_object_operation(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_group_operation(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_id_operation(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_data_operation(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_animdata_operation(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_action_set(struct wmOperatorType *ot);
|
||||
|
||||
void OUTLINER_OT_show_one_level(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_show_active(struct wmOperatorType *ot);
|
||||
|
||||
@@ -57,6 +57,8 @@ void outliner_operatortypes(void)
|
||||
WM_operatortype_append(OUTLINER_OT_group_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_id_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_data_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_animdata_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_action_set);
|
||||
|
||||
WM_operatortype_append(OUTLINER_OT_show_one_level);
|
||||
WM_operatortype_append(OUTLINER_OT_show_active);
|
||||
|
||||
@@ -179,6 +179,13 @@ static void outliner_main_area_listener(ARegion *ar, wmNotifier *wmn)
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case NC_ANIMATION:
|
||||
switch(wmn->data) {
|
||||
case ND_NLA_ACTCHANGE:
|
||||
ED_region_tag_redraw(ar);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,42 +74,7 @@ static int rna_AnimData_action_editable(PointerRNA *ptr)
|
||||
static void rna_AnimData_action_set(PointerRNA *ptr, PointerRNA value)
|
||||
{
|
||||
ID *ownerId = (ID *)ptr->id.data;
|
||||
AnimData *adt = (AnimData *)ptr->data;
|
||||
|
||||
/* manage usercount for current action */
|
||||
if (adt->action)
|
||||
id_us_min((ID*)adt->action);
|
||||
|
||||
/* assume that AnimData's action can in fact be edited... */
|
||||
if ((value.data) && (ownerId)) {
|
||||
bAction *act = (bAction *)value.data;
|
||||
|
||||
/* action must have same type as owner */
|
||||
if (ownerId) {
|
||||
if (ELEM(act->idroot, 0, GS(ownerId->name))) {
|
||||
/* can set */
|
||||
adt->action = act;
|
||||
id_us_plus((ID*)adt->action);
|
||||
}
|
||||
else {
|
||||
/* cannot set */
|
||||
printf("ERROR: Couldn't set Action '%s' onto ID '%s', as it doesn't have suitably rooted paths for this purpose\n",
|
||||
act->id.name+2, ownerId->name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* cannot tell if we can set, so let's just be generous... */
|
||||
printf("Warning: Set Action '%s' onto AnimData block with an unknown ID-owner. May have attached invalid data\n",
|
||||
act->id.name+2);
|
||||
|
||||
adt->action = act;
|
||||
id_us_plus((ID*)adt->action);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* just clearing the action... */
|
||||
adt->action = NULL;
|
||||
}
|
||||
BKE_animdata_set_action(NULL, ownerId, value.data);
|
||||
}
|
||||
|
||||
/* ****************************** */
|
||||
|
||||
@@ -424,6 +424,7 @@ static void rna_def_nlastrip(BlenderRNA *brna)
|
||||
RNA_def_property_update(prop, NC_ANIMATION|ND_NLA, NULL); /* this will do? */
|
||||
|
||||
/* Action */
|
||||
// TODO: this should only be editable if it is not being edited atm...
|
||||
prop= RNA_def_property(srna, "action", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_pointer_sdna(prop, NULL, "act");
|
||||
RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_Action_id_poll");
|
||||
|
||||
Reference in New Issue
Block a user