Animation Tool: Propagate Pose
authorJoshua Leung <aligorith@gmail.com>
Thu, 24 Mar 2011 03:02:34 +0000 (03:02 +0000)
committerJoshua Leung <aligorith@gmail.com>
Thu, 24 Mar 2011 03:02:34 +0000 (03:02 +0000)
This tool automates the process of copying a pose to successive
keyframes, making it easier for animators to go back and change the
pose for some controls which remain "static" for periods of time.
Previously, animators would need to do a "{Ctrl-Pageup Ctrl-V} *
number_of_static_keyframes" dance for each set of controls that this
happened on, which is not too good ergonomically speaking.

There are two modes exposed via the menu (Pose->Propagate):
- "Pose Propagate" - also known as the 'WHILE_HELD' mode, which
propagates to all keyframes that are holding the same value
- "To Next Keyframe" - which only propagates the pose to the closest
keyframe in the occurring after (but not including) the current frame

Additionally, there are a few other modes that can be used, though
they are less useful for direct use from the UI, though they can be
used via the PyAPI as need be.

---

Also, I did some cleanups in the "Pose" menu to bring it more into
line with the Object mode one. There are some more tweaks that could
still be done here, such as bringing the keyframing operator entries
under a submenu too (as in the Object mode version) to get the length
of this under control.

release/scripts/startup/bl_ui/space_view3d.py
source/blender/editors/animation/keyframes_draw.c
source/blender/editors/armature/armature_intern.h
source/blender/editors/armature/armature_ops.c
source/blender/editors/armature/poseSlide.c
source/blender/editors/armature/poseobject.c
source/blender/editors/include/ED_keyframes_draw.h

index 3170417..bbeb76d 100644 (file)
@@ -177,10 +177,6 @@ class VIEW3D_MT_transform(bpy.types.Menu):
         layout.operator("object.origin_set", text="Origin to Geometry").type = 'ORIGIN_GEOMETRY'
         layout.operator("object.origin_set", text="Origin to 3D Cursor").type = 'ORIGIN_CURSOR'
 
-        if context.mode == 'OBJECT':
-            layout.operator("object.align")
-            layout.operator("object.randomize_transform")
-
 
 class VIEW3D_MT_mirror(bpy.types.Menu):
     bl_label = "Mirror"
@@ -254,14 +250,6 @@ class VIEW3D_MT_uv_map(bpy.types.Menu):
 
         layout.operator("uv.reset")
 
-        layout.separator()
-
-        # python scripts
-        layout.operator_context = 'INVOKE_REGION_WIN'
-        layout.operator("uv.smart_project")
-        layout.operator("uv.lightmap_pack")
-        layout.operator("uv.follow_active_quads")
-
 # ********** View menus **********
 
 
@@ -1172,23 +1160,23 @@ class VIEW3D_MT_pose(bpy.types.Menu):
         layout.separator()
 
         layout.menu("VIEW3D_MT_transform")
-        layout.menu("VIEW3D_MT_snap")
 
         layout.menu("VIEW3D_MT_pose_transform")
+        layout.menu("VIEW3D_MT_pose_apply")    
+               
+        layout.menu("VIEW3D_MT_snap")
 
         layout.separator()
 
+        # TODO: make this an "Animation" menu like we have for object?
         layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe...")
         layout.operator("anim.keyframe_delete_v3d", text="Delete Keyframe...")
         layout.operator("anim.keying_set_active_set", text="Change Keying Set...")
 
         layout.separator()
 
-        layout.operator("pose.relax")
-
-        layout.separator()
-
-        layout.menu("VIEW3D_MT_pose_apply")
+        layout.menu("VIEW3D_MT_pose_slide")
+        layout.menu("VIEW3D_MT_pose_propagate")
 
         layout.separator()
 
@@ -1198,7 +1186,7 @@ class VIEW3D_MT_pose(bpy.types.Menu):
 
         layout.separator()
 
-        layout.menu("VIEW3D_MT_pose_pose")
+        layout.menu("VIEW3D_MT_pose_library")
         layout.menu("VIEW3D_MT_pose_motion")
         layout.menu("VIEW3D_MT_pose_group")
 
@@ -1246,7 +1234,28 @@ class VIEW3D_MT_pose_transform(bpy.types.Menu):
         layout.label(text="Origin")
 
 
-class VIEW3D_MT_pose_pose(bpy.types.Menu):
+class VIEW3D_MT_pose_slide(bpy.types.Menu):
+    bl_label = "In-Betweens"
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.operator("pose.push")
+        layout.operator("pose.relax")
+        layout.operator("pose.breakdown")
+
+
+class VIEW3D_MT_pose_propagate(bpy.types.Menu):
+    bl_label = "Propagate"
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.operator("pose.propagate")
+        layout.operator("pose.propagate", text="To Next Keyframe").mode = 'NEXT_KEY'
+
+
+class VIEW3D_MT_pose_library(bpy.types.Menu):
     bl_label = "Pose Library"
 
     def draw(self, context):
@@ -2313,3 +2322,14 @@ class VIEW3D_PT_context_properties(bpy.types.Panel):
         if member:
             # Draw with no edit button
             rna_prop_ui.draw(self.layout, context, member, object, False)
+
+
+def register():
+    bpy.utils.register_module(__name__)
+
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+
+if __name__ == "__main__":
+    register()
index aab3879..3f8f8dc 100644 (file)
@@ -80,7 +80,7 @@
 /* ActKeyColumns (Keyframe Columns) ------------------------------------------ */
 
 /* Comparator callback used for ActKeyColumns and cframe float-value pointer */
-// NOTE: this is exported to other modules that use the ActKeyColumns for finding keyframes
+/* NOTE: this is exported to other modules that use the ActKeyColumns for finding keyframes */
 short compare_ak_cfraPtr (void *node, void *data)
 {
        ActKeyColumn *ak= (ActKeyColumn *)node;
@@ -311,6 +311,23 @@ static BezTriple *abk_get_bezt_with_value (ActBeztColumn *abk, float value)
 
 /* ActKeyBlocks (Long Keyframes) ------------------------------------------ */
 
+/* Comparator callback used for ActKeyBlock and cframe float-value pointer */
+/* NOTE: this is exported to other modules that use the ActKeyBlocks for finding long-keyframes */
+short compare_ab_cfraPtr (void *node, void *data)
+{
+       ActKeyBlock *ab= (ActKeyBlock *)node;
+       float *cframe= data;
+       
+       if (*cframe < ab->start)
+               return -1;
+       else if (*cframe > ab->start)
+               return 1;
+       else
+               return 0;
+}
+
+/* --------------- */
+
 /* Create a ActKeyColumn for a pair of BezTriples */
 static ActKeyBlock *bezts_to_new_actkeyblock(BezTriple *prev, BezTriple *beztn)
 {
index b72cf75..7a61415 100644 (file)
@@ -200,6 +200,8 @@ void POSE_OT_push(struct wmOperatorType *ot);
 void POSE_OT_relax(struct wmOperatorType *ot);
 void POSE_OT_breakdown(struct wmOperatorType *ot);
 
+void POSE_OT_propagate(struct wmOperatorType *ot);
+
 /* ******************************************************* */
 /* editarmature.c */
 
index 34942a2..545cff8 100644 (file)
@@ -146,6 +146,8 @@ void ED_operatortypes_armature(void)
        WM_operatortype_append(POSE_OT_armature_layers);
        WM_operatortype_append(POSE_OT_bone_layers);
        
+       WM_operatortype_append(POSE_OT_propagate);
+       
        /* POSELIB */
        WM_operatortype_append(POSELIB_OT_browse_interactive);
        WM_operatortype_append(POSELIB_OT_apply_pose);
index 39d65a4..680cd4b 100644 (file)
@@ -61,8 +61,6 @@
 #include "WM_api.h"
 #include "WM_types.h"
 
-
-
 #include "ED_armature.h"
 #include "ED_keyframes_draw.h"
 #include "ED_screen.h"
  * for interactively controlling the spacing of poses, but also
  * for 'pushing' and/or 'relaxing' extremes as they see fit.
  *
- * B) Pose Sculpting
+ * B) Propagate
+ * This tool copies elements of the selected pose to successive
+ * keyframes, allowing the animator to go back and modify the poses
+ * for some "static" pose controls, without having to repeatedly
+ * doing a "next paste" dance.
+ *
+ * C) Pose Sculpting
  * This is yet to be implemented, but the idea here is to use
  * sculpting techniques to make it easier to pose rigs by allowing
  * rigs to be manipulated using a familiar paint-based interface. 
@@ -854,3 +858,340 @@ void POSE_OT_breakdown (wmOperatorType *ot)
 }
 
 /* **************************************************** */
+/* B) Pose Propagate */
+
+/* "termination conditions" - i.e. when we stop */
+typedef enum ePosePropagate_Termination {
+               /* stop when we run out of keyframes */
+       POSE_PROPAGATE_LAST_KEY = 0,
+               /* stop after the next keyframe */
+       POSE_PROPAGATE_NEXT_KEY,
+               /* stop after the specified frame */
+       POSE_PROPAGATE_BEFORE_FRAME,
+               /* stop after */
+       POSE_PROPAGATE_SMART_HOLDS
+} ePosePropagate_Termination;
+
+/* --------------------------------- */
+
+/* helper for pose_propagate_get_boneHoldEndFrame() 
+ * Checks if ActKeyBlock should exist...
+ */
+// TODO: move to keyframes drawing API...
+static short actkeyblock_is_valid (ActKeyBlock *ab, DLRBT_Tree *keys)
+{
+       ActKeyColumn *ak;
+       short startCurves, endCurves, totCurves;
+       
+       /* check that block is valid */
+       if (ab == NULL)
+               return 0;
+       
+       /* find out how many curves occur at each keyframe */
+       ak= (ActKeyColumn *)BLI_dlrbTree_search_exact(keys, compare_ak_cfraPtr, &ab->start);
+       startCurves = (ak)? ak->totcurve: 0;
+       
+       ak= (ActKeyColumn *)BLI_dlrbTree_search_exact(keys, compare_ak_cfraPtr, &ab->end);
+       endCurves = (ak)? ak->totcurve: 0;
+       
+       /* only draw keyblock if it appears in at all of the keyframes at lowest end */
+       if (!startCurves && !endCurves) 
+               return 0;
+       
+       totCurves = (startCurves>endCurves)? endCurves: startCurves;
+       return (ab->totcurve >= totCurves);
+}
+
+/* get frame on which the "hold" for the bone ends 
+ * XXX: this may not really work that well if a bone moves on some channels and not others
+ *             if this happens to be a major issue, scrap this, and just make this happen 
+ *             independently per F-Curve
+ */
+static float pose_propagate_get_boneHoldEndFrame (Object *ob, tPChanFCurveLink *pfl, float startFrame)
+{
+       DLRBT_Tree keys, blocks;
+       ActKeyBlock *ab;
+       
+       AnimData *adt= ob->adt;
+       LinkData *ld;
+       float endFrame = startFrame;
+       
+       /* set up optimised data-structures for searching for relevant keyframes + holds */
+       BLI_dlrbTree_init(&keys);
+       BLI_dlrbTree_init(&blocks);
+       
+       for (ld = pfl->fcurves.first; ld; ld = ld->next) {
+               FCurve *fcu = (FCurve *)ld->data;
+               fcurve_to_keylist(adt, fcu, &keys, &blocks);
+       }
+       
+       BLI_dlrbTree_linkedlist_sync(&keys);
+       BLI_dlrbTree_linkedlist_sync(&blocks);
+       
+       /* find the long keyframe (i.e. hold), and hence obtain the endFrame value 
+        *      - the best case would be one that starts on the frame itself
+        */
+       ab = (ActKeyBlock *)BLI_dlrbTree_search_exact(&blocks, compare_ab_cfraPtr, &startFrame);
+       
+       if (actkeyblock_is_valid(ab, &keys) == 0) {
+               /* There are only two cases for no-exact match:
+                *      1) the current frame is just before another key but not on a key itself
+                *      2) the current frame is on a key, but that key doesn't link to the next
+                *
+                * If we've got the first case, then we can search for another block, 
+                * otherwise forget it, as we'd be overwriting some valid data.
+                */
+               if (BLI_dlrbTree_search_exact(&keys, compare_ak_cfraPtr, &startFrame) == NULL) {
+                       /* we've got case 1, so try the one after */
+                       ab = (ActKeyBlock *)BLI_dlrbTree_search_next(&blocks, compare_ab_cfraPtr, &startFrame);
+                       
+                       if (actkeyblock_is_valid(ab, &keys) == 0) {
+                               /* try the block before this frame then as last resort */
+                               ab = (ActKeyBlock *)BLI_dlrbTree_search_prev(&blocks, compare_ab_cfraPtr, &startFrame);
+                               
+                               /* whatever happens, stop searching now... */
+                               if (actkeyblock_is_valid(ab, &keys) == 0) {
+                                       /* restrict range to just the frame itself 
+                                        * i.e. everything is in motion, so no holds to safely overwrite
+                                        */
+                                       ab = NULL;
+                               }
+                       }
+               }
+               else {
+                       /* we've got case 2 - set ab to NULL just in case, since we shouldn't do anything in this case */
+                       ab = NULL;
+               }
+       }
+       
+       /* check if we can go any further than we've already gone */
+       if (ab) {
+               /* go to next if it is also valid and meets "extension" criteria */
+               while (ab->next) {
+                       ActKeyBlock *abn = (ActKeyBlock *)ab->next;
+                       
+                       /* must be valid */
+                       if (actkeyblock_is_valid(abn, &keys) == 0)
+                               break;
+                       /* should start on the same frame that the last ended on */
+                       if (ab->end != abn->start)
+                               break;
+                       /* should have the same number of curves */
+                       if (ab->totcurve != abn->totcurve)
+                               break;
+                       /* should have the same value 
+                        * XXX: this may be a bit fuzzy on larger data sets, so be careful
+                        */
+                       if (ab->val != abn->val)
+                               break;
+                               
+                       /* we can extend the bounds to the end of this "next" block now */
+                       ab = abn;
+               }
+               
+               /* end frame can now take the value of the end of the block */
+               endFrame = ab->end;
+       }
+       
+       /* free temp memory */
+       BLI_dlrbTree_free(&keys);
+       BLI_dlrbTree_free(&blocks);
+       
+       /* return the end frame we've found */
+       return endFrame;
+}
+
+/* get reference value from F-Curve using RNA */
+static float pose_propagate_get_refVal (Object *ob, FCurve *fcu)
+{
+       PointerRNA id_ptr, ptr;
+       PropertyRNA *prop;
+       float value;
+       
+       /* base pointer is always the object -> id_ptr */
+       RNA_id_pointer_create(&ob->id, &id_ptr);
+       
+       /* resolve the property... */
+       if (RNA_path_resolve(&id_ptr, fcu->rna_path, &ptr, &prop)) {
+               if (RNA_property_array_check(&ptr, prop)) {
+                       /* array */
+                       if (fcu->array_index < RNA_property_array_length(&ptr, prop)) { 
+                               switch (RNA_property_type(prop)) {
+                                       case PROP_BOOLEAN:
+                                               value= (float)RNA_property_boolean_get_index(&ptr, prop, fcu->array_index);
+                                               break;
+                                       case PROP_INT:
+                                               value= (float)RNA_property_int_get_index(&ptr, prop, fcu->array_index);
+                                               break;
+                                       case PROP_FLOAT:
+                                               value= RNA_property_float_get_index(&ptr, prop, fcu->array_index);
+                                               break;
+                                       default:
+                                               break;
+                               }
+                       }
+               }
+               else {
+                       /* not an array */
+                       switch (RNA_property_type(prop)) {
+                               case PROP_BOOLEAN:
+                                       value= (float)RNA_property_boolean_get(&ptr, prop);
+                                       break;
+                               case PROP_INT:
+                                       value= (float)RNA_property_int_get(&ptr, prop);
+                                       break;
+                               case PROP_ENUM:
+                                       value= (float)RNA_property_enum_get(&ptr, prop);
+                                       break;
+                               case PROP_FLOAT:
+                                       value= RNA_property_float_get(&ptr, prop);
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+       }
+       
+       return value;
+}
+
+/* propagate just works along each F-Curve in turn */
+static void pose_propagate_fcurve (wmOperator *op, Object *ob, tPChanFCurveLink *pfl, FCurve *fcu, float startFrame, float endFrame)
+{
+       const int mode = RNA_enum_get(op->ptr, "mode");
+       
+       BezTriple *bezt;
+       float refVal = 0.0f;
+       short keyExists;
+       int i, match;
+       short first=1;
+       
+       /* skip if no keyframes to edit */
+       if ((fcu->bezt == NULL) || (fcu->totvert < 2))
+               return;
+               
+       /* find the reference value from bones directly, which means that the user
+        * doesn't need to firstly keyframe the pose (though this doesn't mean that 
+        * they can't either)
+        */
+       refVal = pose_propagate_get_refVal(ob, fcu);
+       
+       /* find the first keyframe to start propagating from 
+        *      - if there's a keyframe on the current frame, we probably want to save this value there too
+        *        since it may be as of yet unkeyed
+        *      - if starting before the starting frame, don't touch the key, as it may have had some valid 
+        *        values
+        */
+       match = binarysearch_bezt_index(fcu->bezt, startFrame, fcu->totvert, &keyExists);
+       
+       if (fcu->bezt[match].vec[1][0] < startFrame)
+               i = match + 1;
+       else
+               i = match;
+       
+       for (bezt = &fcu->bezt[i]; i < fcu->totvert; i++, bezt++) {
+               /* additional termination conditions based on the operator 'mode' property go here... */
+               if (ELEM(mode, POSE_PROPAGATE_BEFORE_FRAME, POSE_PROPAGATE_SMART_HOLDS)) {
+                       /* stop if keyframe is outside the accepted range */
+                       if (bezt->vec[1][0] > endFrame)
+                               break;
+               }
+               else if (mode == POSE_PROPAGATE_NEXT_KEY) {
+                       /* stop after the first keyframe has been processed */
+                       if (first == 0)
+                               break;
+               }
+               
+               /* just flatten handles, since values will now be the same either side... */
+               // TODO: perhaps a fade-out modulation of the value is required here (optional once again)?
+               bezt->vec[0][1] = bezt->vec[1][1] = bezt->vec[2][1] = refVal;
+               
+               /* select keyframe to indicate that it's been changed */
+               bezt->f2 |= SELECT;
+               first = 0;
+       }
+}
+
+/* --------------------------------- */
+
+static int pose_propagate_exec (bContext *C, wmOperator *op)
+{
+       Scene *scene = CTX_data_scene(C);
+       Object *ob= ED_object_pose_armature(CTX_data_active_object(C));
+       bAction *act= (ob && ob->adt)? ob->adt->action : NULL;
+       
+       ListBase pflinks = {NULL, NULL};
+       tPChanFCurveLink *pfl;
+       
+       float endFrame = RNA_float_get(op->ptr, "end_frame");
+       const int mode = RNA_enum_get(op->ptr, "mode");
+       
+       /* sanity checks */
+       if (ob == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No object to propagate poses for");
+               return OPERATOR_CANCELLED;
+       }
+       if (act == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No keyframed poses to propagate to");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* isolate F-Curves related to the selected bones */
+       poseAnim_mapping_get(C, &pflinks, ob, act);
+       
+       /* for each bone, perform the copying required */
+       for (pfl = pflinks.first; pfl; pfl = pfl->next) {
+               LinkData *ld;
+               
+               /* mode-specific data preprocessing (requiring access to all curves) */
+               if (mode == POSE_PROPAGATE_SMART_HOLDS) {
+                       /* we store in endFrame the end frame of the "long keyframe" (i.e. a held value) starting
+                        * from the keyframe that occurs after the current frame
+                        */
+                       endFrame = pose_propagate_get_boneHoldEndFrame(ob, pfl, (float)CFRA);
+               }
+               
+               /* go through propagating pose to keyframes, curve by curve */
+               for (ld = pfl->fcurves.first; ld; ld= ld->next)
+                       pose_propagate_fcurve(op, ob, pfl, (FCurve *)ld->data, (float)CFRA, endFrame);
+       }
+       
+       /* free temp data */
+       poseAnim_mapping_free(&pflinks);
+       
+       /* updates + notifiers */
+       poseAnim_mapping_refresh(C, scene, ob);
+       
+       return OPERATOR_FINISHED;
+}
+
+/* --------------------------------- */
+
+void POSE_OT_propagate (wmOperatorType *ot)
+{
+       static EnumPropertyItem terminate_items[]= {
+               {POSE_PROPAGATE_LAST_KEY, "LAST_KEY", 0, "Last Keyframe", ""},
+               {POSE_PROPAGATE_NEXT_KEY, "NEXT_KEY", 0, "Next Keyframe", ""},
+               {POSE_PROPAGATE_BEFORE_FRAME, "BEFORE_FRAME", 0, "Before Frame", "Propagate pose to all keyframes between current frame and 'Frame' property"},
+               {POSE_PROPAGATE_SMART_HOLDS, "WHILE_HELD", 0, "While Held", "Propagate pose to all keyframes after current frame that don't change (Default behaviour)"},
+               {0, NULL, 0, NULL, NULL}};
+               
+       /* identifiers */
+       ot->name= "Propagate Pose";
+       ot->idname= "POSE_OT_propagate";
+       ot->description= "Copy selected aspects of the current pose to subsequent poses already keyframed";
+       
+       /* callbacks */
+       ot->exec= pose_propagate_exec;
+       ot->poll= ED_operator_posemode; // XXX: needs selected bones!
+       
+       /* flag */
+       ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+       
+       /* properties */
+       // TODO: add "fade out" control for tapering off amount of propagation as time goes by?
+       ot->prop= RNA_def_enum(ot->srna, "mode", terminate_items, POSE_PROPAGATE_SMART_HOLDS, "Terminate Mode", "Method used to determine when to stop propagating pose to keyframes");
+       RNA_def_float(ot->srna, "end_frame", 250.0, FLT_MIN, FLT_MAX, "End Frame", "Frame to stop propagating frames to", 1.0, 250.0);
+}
+
+/* **************************************************** */
index 23253c0..e9892c6 100644 (file)
@@ -57,6 +57,7 @@
 #include "BKE_constraint.h"
 #include "BKE_deform.h"
 #include "BKE_depsgraph.h"
+#include "BKE_fcurve.h"
 #include "BKE_modifier.h"
 #include "BKE_report.h"
 
@@ -1600,7 +1601,7 @@ static int pose_autoside_names_exec (bContext *C, wmOperator *op)
 void POSE_OT_autoside_names (wmOperatorType *ot)
 {
        static EnumPropertyItem axis_items[]= {
-                {0, "XAXIS", 0, "X-Axis", "Left/Right"},
+               {0, "XAXIS", 0, "X-Axis", "Left/Right"},
                {1, "YAXIS", 0, "Y-Axis", "Front/Back"},
                {2, "ZAXIS", 0, "Z-Axis", "Top/Bottom"},
                {0, NULL, 0, NULL, NULL}};
index c697f8c..544c5c4 100644 (file)
@@ -146,5 +146,8 @@ void gpl_to_keylist(struct bDopeSheet *ads, struct bGPDlayer *gpl, struct DLRBT_
 /* Comparator callback used for ActKeyColumns and cframe float-value pointer */
 short compare_ak_cfraPtr(void *node, void *data);
 
+/* Comparator callback used for ActKeyBlocks and cframe float-value pointer */
+short compare_ab_cfraPtr(void *node, void *data);
+
 #endif  /*  ED_KEYFRAMES_DRAW_H */