Action Editor: Go to Next/Previous Animation Layer
authorJoshua Leung <aligorith@gmail.com>
Fri, 3 Apr 2015 10:15:56 +0000 (23:15 +1300)
committerJoshua Leung <aligorith@gmail.com>
Fri, 3 Apr 2015 12:39:53 +0000 (01:39 +1300)
With this feature, it is now possible to quickly switch between different actions
stacked/stashed on top of each other in the NLA Stack without having to go to the
NLA Editor and doing a tab-select-tab dance, thus saving quite a few clicks. It
was specifically designed with Game Animation / Action Library workflows in mind,
but also helps layered animation workflows.

Usage:
Simply click on the up/down arrow buttons (between the action datablock selector
and the pushdown/stash buttons) to go to the action in the NLA Track above/below
the NLA Strip being whose action is being tweaked in the Action Editor.

Notes:
- These still work when you're not editing the action used by a NLA Strip.
If you're just animating a new action normally, it is possible to use the "down arrow"
to temporarily jump down to the previous action without losing the new action you're
working on, and then use the "up arrow" to get back to it once you're done checking
the other action(s).

- If there are multiple actions/strips on the same layer/track, then only the one
closest to the current frame will be used.

release/scripts/startup/bl_ui/space_dopesheet.py
source/blender/editors/space_action/action_data.c
source/blender/editors/space_action/action_intern.h
source/blender/editors/space_action/action_ops.c

index 2d362149d8b1544d006987dc95cf61c7573094e9..27fc73f88b6baa9d37bf4a97726b2a9684881446 100644 (file)
@@ -116,6 +116,10 @@ class DOPESHEET_HT_header(Header):
         if st.mode in {'ACTION', 'SHAPEKEY'}:
             layout.template_ID(st, "action", new="action.new")
 
+            row = layout.row(align=True)
+            row.operator("action.layer_prev", text="", icon='TRIA_DOWN')
+            row.operator("action.layer_next", text="", icon='TRIA_UP')
+
             row = layout.row(align=True)
             row.operator("action.push_down", text="Push Down", icon='NLA_PUSHDOWN')
             row.operator("action.stash", text="Stash", icon='FREEZE')
index d3693608a1f7ae01bd9d98318888942263177b81..111cb5257da89f30eaea1762452df66c2e769323 100644 (file)
@@ -51,6 +51,7 @@
 #include "RNA_define.h"
 #include "RNA_enum_types.h"
 
+#include "BKE_animsys.h"
 #include "BKE_action.h"
 #include "BKE_fcurve.h"
 #include "BKE_global.h"
@@ -58,6 +59,7 @@
 #include "BKE_key.h"
 #include "BKE_main.h"
 #include "BKE_nla.h"
+#include "BKE_scene.h"
 #include "BKE_context.h"
 #include "BKE_report.h"
 
@@ -507,3 +509,323 @@ void ACTION_OT_stash_and_create(wmOperatorType *ot)
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
+/* ************************************************************************** */
+/* ACTION BROWSING */
+
+/* Get the NLA Track that the active action comes from, since this is not stored in AnimData */
+/* TODO: Move this to blenkernel/nla.c */
+static NlaTrack *nla_tweak_track_get(AnimData *adt)
+{
+       NlaTrack *nlt;
+       
+       /* sanity check */
+       if (adt == NULL)
+               return NULL;
+       
+       /* Since the track itself gets disabled, we want the first disabled... */
+       for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) {
+               if (nlt->flag & (NLATRACK_ACTIVE | NLATRACK_DISABLED)) {
+                       /* For good measure, make sure that strip actually exists there */
+                       if (BLI_findindex(&nlt->strips, adt->actstrip) != -1) {
+                               return nlt;
+                       }
+                       else if (G.debug & G_DEBUG) {
+                               printf("%s: Active strip (%p, %s) not in NLA track found (%p, %s)\n",
+                                      __func__, 
+                                      adt->actstrip, (adt->actstrip) ? adt->actstrip->name : "<None>",
+                                          nlt,           (nlt) ? nlt->name : "<None>");
+                       }
+               }
+       }
+       
+       /* Not found! */
+       return NULL;
+}
+
+/* ********************** One Layer Up Operator ************************** */
+
+static int action_layer_next_poll(bContext *C)
+{
+       /* Action Editor's action editing modes only */
+       if (ED_operator_action_active(C)) {
+               AnimData *adt = actedit_animdata_from_context(C);
+               if (adt) {
+                       /* only allow if we're in tweakmode, and there's something above us... */
+                       if (adt->flag & ADT_NLA_EDIT_ON) {
+                               /* We need to check if there are any tracks above the active one
+                                * since the track the action comes from is not stored in AnimData
+                                */
+                               if (adt->nla_tracks.last) {
+                                       NlaTrack *nlt = (NlaTrack *)adt->nla_tracks.last;
+                                       
+                                       if (nlt->flag & NLATRACK_DISABLED) {
+                                               /* A disabled track will either be the track itself,
+                                                * or one of the ones above it.
+                                                *
+                                                * If this is the top-most one, there is the possibility
+                                                * that there is no active action. For now, we let this
+                                                * case return true too, so that there is a natural way
+                                                * to "move to an empty layer", even though this means
+                                                * that we won't actually have an action.
+                                                */
+                                               // return (adt->tmpact != NULL);
+                                               return true;
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       /* something failed... */
+       return false;
+}
+
+static int action_layer_next_exec(bContext *C, wmOperator *op)
+{
+       AnimData *adt = actedit_animdata_from_context(C);
+       NlaTrack *act_track;
+       
+       Scene *scene = CTX_data_scene(C);
+       float ctime = BKE_scene_frame_get(scene);
+       
+       /* Get active track */
+       act_track = nla_tweak_track_get(adt);
+       
+       if (act_track == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "Could not find current NLA Track");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* Find next action, and hook it up */
+       if (act_track->next) {
+               NlaTrack *nlt;
+               NlaStrip *strip;
+               bool found = false;
+               
+               /* Find next action to use */
+               for (nlt = act_track->next; nlt && !found; nlt = nlt->next) {
+                       for (strip = nlt->strips.first; strip; strip = strip->next) {
+                               /* Can we use this? */
+                               if (IN_RANGE_INCL(ctime, strip->start, strip->end)) {
+                                       /* in range - use this one */
+                                       found = true;
+                               }
+                               else if ((ctime < strip->start) && (strip->prev == NULL)) {
+                                       /* before first - use this one */
+                                       found = true;
+                               }
+                               else if ((ctime > strip->end) && (strip->next == NULL)) {
+                                       /* after last - use this one */
+                                       found = true;
+                               }
+                               
+                               /* Apply... */
+                               if (found) {
+                                       NlaStrip *old_strip = adt->actstrip;
+                                       
+                                       /* Exit tweakmode on old strip
+                                        * NOTE: We need to manually clear this stuff ourselves, as tweakmode exit doesn't do it
+                                        */
+                                       BKE_nla_tweakmode_exit(adt);
+                                       
+                                       old_strip->flag &= ~(NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT);
+                                       act_track->flag &= ~(NLATRACK_ACTIVE | NLATRACK_SELECTED);
+                                       
+                                       /* Make this one the active one instead */
+                                       strip->flag |= (NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT);
+                                       nlt->flag |= NLATRACK_ACTIVE;
+                                       
+                                       /* Copy over "solo" flag - This is useful for stashed actions... */
+                                       if (act_track->flag & NLATRACK_SOLO) {
+                                               act_track->flag &= ~NLATRACK_SOLO;
+                                               nlt->flag |= NLATRACK_SOLO;
+                                       }
+                                       
+                                       /* Enter tweakmode again - hopefully we're now "it" */
+                                       BKE_nla_tweakmode_enter(adt);
+                                       BLI_assert(adt->actstrip == strip);
+                                       
+                                       break;
+                               }
+                       }
+               }
+       }
+       else {
+               /* No more actions - Go back to editing the original active action
+                * NOTE: This will mean exiting tweakmode...
+                */
+               BKE_nla_tweakmode_exit(adt);
+               
+               /* Deal with solo flags... */
+               // XXX: if solo, turn off NLA while we edit this action?
+               act_track->flag &= ~NLATRACK_SOLO;
+               adt->flag &= ~ADT_NLA_SOLO_TRACK;
+       }
+       
+       /* Update the action that this editor now uses
+        * NOTE: The calls above have already handled the usercount/animdata side of things
+        */
+       actedit_change_action(C, adt->action);
+       return OPERATOR_FINISHED;
+}
+
+void ACTION_OT_layer_next(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Next Layer";
+       ot->idname = "ACTION_OT_layer_next";
+       ot->description = "Edit action in animation layer above the current action in the NLA Stack";
+       
+       /* callbacks */
+       ot->exec = action_layer_next_exec;
+       ot->poll = action_layer_next_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* ********************* One Layer Down Operator ************************* */
+
+static int action_layer_prev_poll(bContext *C)
+{
+       /* Action Editor's action editing modes only */
+       if (ED_operator_action_active(C)) {
+               AnimData *adt = actedit_animdata_from_context(C);
+               if (adt) {
+                       if (adt->flag & ADT_NLA_EDIT_ON) {
+                               /* Tweak Mode: We need to check if there are any tracks below the active one that we can move to */
+                               if (adt->nla_tracks.first) {
+                                       NlaTrack *nlt = (NlaTrack *)adt->nla_tracks.first;
+                                       
+                                       /* Since the first disabled track is the track being tweaked/edited,
+                                        * we can simplify things by only checking the first track:
+                                        *    - If it is disabled, this is the track being tweaked,
+                                        *      so there can't be anything below it
+                                        *    - Otherwise, there is at least 1 track below the tweaking
+                                        *      track that we can descend to
+                                        */
+                                       if ((nlt->flag & NLATRACK_DISABLED) == 0) {
+                                               /* not disabled = there are actions below the one being tweaked */
+                                               return true;
+                                       }
+                               }
+                       }
+                       else {
+                               /* Normal Mode: If there are any tracks, we can try moving to those */
+                               return (adt->nla_tracks.first != NULL);
+                       }
+               }
+       }
+       
+       /* something failed... */
+       return false;
+}
+
+static int action_layer_prev_exec(bContext *C, wmOperator *op)
+{
+       AnimData *adt = actedit_animdata_from_context(C);
+       NlaTrack *act_track;
+       
+       NlaTrack *nlt;
+       NlaStrip *strip;
+       bool found = false;
+       
+       Scene *scene = CTX_data_scene(C);
+       float ctime = BKE_scene_frame_get(scene);
+       
+       /* Sanity Check */
+       if (adt == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "Internal Error: Could not find Animation Data/NLA Stack to use");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* Get active track */
+       act_track = nla_tweak_track_get(adt);
+       
+       /* If there is no active track, that means we are using the active action... */
+       if (act_track) {
+               /* Active Track - Start from the one below it */
+               nlt = act_track->prev;
+       }
+       else {
+               /* Active Action - Use the top-most track */
+               nlt = adt->nla_tracks.last;
+       }
+       
+       /* Find previous action and hook it up */
+       for (; nlt && !found; nlt = nlt->prev) {
+               for (strip = nlt->strips.first; strip; strip = strip->next) {
+                       /* Can we use this? */
+                       if (IN_RANGE_INCL(ctime, strip->start, strip->end)) {
+                               /* in range - use this one */
+                               found = true;
+                       }
+                       else if ((ctime < strip->start) && (strip->prev == NULL)) {
+                               /* before first - use this one */
+                               found = true;
+                       }
+                       else if ((ctime > strip->end) && (strip->next == NULL)) {
+                               /* after last - use this one */
+                               found = true;
+                       }
+                       
+                       /* Apply... */
+                       if (found) {
+                               NlaStrip *old_strip = adt->actstrip;
+                               
+                               /* Exit tweakmode on old strip
+                                * NOTE: We need to manually clear this stuff ourselves, as tweakmode exit doesn't do it
+                                */
+                               BKE_nla_tweakmode_exit(adt);
+                               
+                               if (old_strip) {
+                                       old_strip->flag &= ~(NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT);
+                               }
+                               if (act_track) {
+                                       act_track->flag &= ~(NLATRACK_ACTIVE | NLATRACK_SELECTED);
+                               }
+                               
+                               /* Make this one the active one instead */
+                               strip->flag |= (NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT);
+                               nlt->flag |= NLATRACK_ACTIVE;
+                               
+                               /* Copy over "solo" flag - This is useful for stashed actions... */
+                               if (act_track) {
+                                       if (act_track->flag & NLATRACK_SOLO) {
+                                               act_track->flag &= ~NLATRACK_SOLO;
+                                               nlt->flag |= NLATRACK_SOLO;
+                                       }
+                               }
+                               
+                               /* Enter tweakmode again - hopefully we're now "it" */
+                               BKE_nla_tweakmode_enter(adt);
+                               BLI_assert(adt->actstrip == strip);
+                               
+                               break;
+                       }
+               }
+       }
+       
+       /* Update the action that this editor now uses
+        * NOTE: The calls above have already handled the usercount/animdata side of things
+        */
+       actedit_change_action(C, adt->action);
+       return OPERATOR_FINISHED;
+}
+
+void ACTION_OT_layer_prev(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Previous Layer";
+       ot->idname = "ACTION_OT_layer_prev";
+       ot->description = "Edit action in animation layer below the current action in the NLA Stack";
+       
+       /* callbacks */
+       ot->exec = action_layer_prev_exec;
+       ot->poll = action_layer_prev_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* ************************************************************************** */
index 9a19e4a926b29a2b2646c31113543d393fef9317..3b0da089b9b323aea493984dfbea4ea12deaf675 100644 (file)
@@ -102,6 +102,9 @@ void ACTION_OT_push_down(struct wmOperatorType *ot);
 void ACTION_OT_stash(struct wmOperatorType *ot);
 void ACTION_OT_stash_and_create(struct wmOperatorType *ot);
 
+void ACTION_OT_layer_next(struct wmOperatorType *ot);
+void ACTION_OT_layer_prev(struct wmOperatorType *ot);
+
 void ACTION_OT_markers_make_local(struct wmOperatorType *ot);
 
 /* defines for snap keyframes 
index 8c706d8da5773a2edc75277307822ae1e347cc4f..431dd27d06428f9de71c6f85f038f6248fdae43b 100644 (file)
@@ -83,6 +83,9 @@ void action_operatortypes(void)
        WM_operatortype_append(ACTION_OT_stash);
        WM_operatortype_append(ACTION_OT_stash_and_create);
        
+       WM_operatortype_append(ACTION_OT_layer_next);
+       WM_operatortype_append(ACTION_OT_layer_prev);
+       
        WM_operatortype_append(ACTION_OT_previewrange_set);
        WM_operatortype_append(ACTION_OT_view_all);
        WM_operatortype_append(ACTION_OT_view_selected);