merge from trunk 38379
[blender-staging.git] / source / blender / editors / armature / poseSlide.c
1 /*
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * The Original Code is Copyright (C) 2009, Blender Foundation, Joshua Leung
21  * This is a new part of Blender
22  *
23  * Contributor(s): Joshua Leung
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/armature/poseSlide.c
29  *  \ingroup edarmature
30  */
31
32  
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <stddef.h>
36 #include <string.h>
37 #include <math.h>
38 #include <float.h>
39
40 #include "MEM_guardedalloc.h"
41
42 #include "BLI_math.h"
43 #include "BLI_blenlib.h"
44 #include "BLI_dynstr.h"
45 #include "BLI_dlrbTree.h"
46 #include "BLI_utildefines.h"
47
48 #include "BLF_api.h"
49
50 #include "DNA_anim_types.h"
51 #include "DNA_armature_types.h"
52 #include "DNA_object_types.h"
53 #include "DNA_scene_types.h"
54
55 #include "BKE_fcurve.h"
56
57 #include "BKE_context.h"
58 #include "BKE_report.h"
59
60 #include "RNA_access.h"
61 #include "RNA_define.h"
62
63 #include "WM_api.h"
64 #include "WM_types.h"
65
66 #include "ED_armature.h"
67 #include "ED_keyframes_draw.h"
68 #include "ED_markers.h"
69 #include "ED_screen.h"
70
71 #include "armature_intern.h"
72
73 /* **************************************************** */
74 /* == POSE 'SLIDING' TOOLS == 
75  *
76  * A) Push & Relax, Breakdowner
77  * These tools provide the animator with various capabilities
78  * for interactively controlling the spacing of poses, but also
79  * for 'pushing' and/or 'relaxing' extremes as they see fit.
80  *
81  * B) Propagate
82  * This tool copies elements of the selected pose to successive
83  * keyframes, allowing the animator to go back and modify the poses
84  * for some "static" pose controls, without having to repeatedly
85  * doing a "next paste" dance.
86  *
87  * C) Pose Sculpting
88  * This is yet to be implemented, but the idea here is to use
89  * sculpting techniques to make it easier to pose rigs by allowing
90  * rigs to be manipulated using a familiar paint-based interface. 
91  */
92 /* **************************************************** */
93 /* A) Push & Relax, Breakdowner */
94
95 /* Temporary data shared between these operators */
96 typedef struct tPoseSlideOp {
97         Scene *scene;           /* current scene */
98         ARegion *ar;            /* region that we're operating in (needed for  */
99         Object *ob;                     /* active object that Pose Info comes from */
100         bArmature *arm;         /* armature for pose */
101         
102         ListBase pfLinks;       /* links between posechannels and f-curves  */
103         DLRBT_Tree keys;        /* binary tree for quicker searching for keyframes (when applicable) */
104         
105         int cframe;                     /* current frame number */
106         int prevFrame;          /* frame before current frame (blend-from) */
107         int nextFrame;          /* frame after current frame (blend-to) */
108         
109         int mode;                       /* sliding mode (ePoseSlide_Modes) */
110         int flag;                       // unused for now, but can later get used for storing runtime settings....
111         
112         float percentage;       /* 0-1 value for determining the influence of whatever is relevant */
113 } tPoseSlideOp;
114
115 /* Pose Sliding Modes */
116 typedef enum ePoseSlide_Modes {
117         POSESLIDE_PUSH  = 0,            /* exaggerate the pose... */
118         POSESLIDE_RELAX,                        /* soften the pose... */
119         POSESLIDE_BREAKDOWN,            /* slide between the endpoint poses, finding a 'soft' spot */
120 } ePoseSlide_Modes;
121
122 /* ------------------------------------ */
123
124 /* operator init */
125 static int pose_slide_init (bContext *C, wmOperator *op, short mode)
126 {
127         tPoseSlideOp *pso;
128         bAction *act= NULL;
129         
130         /* init slide-op data */
131         pso= op->customdata= MEM_callocN(sizeof(tPoseSlideOp), "tPoseSlideOp");
132         
133         /* get info from context */
134         pso->scene= CTX_data_scene(C);
135         pso->ob= ED_object_pose_armature(CTX_data_active_object(C));
136         pso->arm= (pso->ob)? pso->ob->data : NULL;
137         pso->ar= CTX_wm_region(C); /* only really needed when doing modal() */
138         
139         pso->cframe= pso->scene->r.cfra;
140         pso->mode= mode;
141         
142         /* set range info from property values - these may get overridden for the invoke() */
143         pso->percentage= RNA_float_get(op->ptr, "percentage");
144         pso->prevFrame= RNA_int_get(op->ptr, "prev_frame");
145         pso->nextFrame= RNA_int_get(op->ptr, "next_frame");
146         
147         /* check the settings from the context */
148         if (ELEM4(NULL, pso->ob, pso->arm, pso->ob->adt, pso->ob->adt->action))
149                 return 0;
150         else
151                 act= pso->ob->adt->action;
152         
153         /* for each Pose-Channel which gets affected, get the F-Curves for that channel 
154          * and set the relevant transform flags...
155          */
156         poseAnim_mapping_get(C, &pso->pfLinks, pso->ob, act);
157         
158         /* set depsgraph flags */
159                 /* make sure the lock is set OK, unlock can be accidentally saved? */
160         pso->ob->pose->flag |= POSE_LOCKED;
161         pso->ob->pose->flag &= ~POSE_DO_UNLOCK;
162         
163         /* do basic initialise of RB-BST used for finding keyframes, but leave the filling of it up 
164          * to the caller of this (usually only invoke() will do it, to make things more efficient).
165          */
166         BLI_dlrbTree_init(&pso->keys);
167         
168         /* return status is whether we've got all the data we were requested to get */
169         return 1;
170 }
171
172 /* exiting the operator - free data */
173 static void pose_slide_exit(wmOperator *op)
174 {
175         tPoseSlideOp *pso= op->customdata;
176         
177         /* if data exists, clear its data and exit */
178         if (pso) {
179                 /* free the temp pchan links and their data */
180                 poseAnim_mapping_free(&pso->pfLinks);
181                 
182                 /* free RB-BST for keyframes (if it contained data) */
183                 BLI_dlrbTree_free(&pso->keys);
184                 
185                 /* free data itself */
186                 MEM_freeN(pso);
187         }
188         
189         /* cleanup */
190         op->customdata= NULL;
191 }
192
193 /* ------------------------------------ */
194
195 /* helper for apply() / reset() - refresh the data */
196 static void pose_slide_refresh (bContext *C, tPoseSlideOp *pso)
197 {
198         /* wrapper around the generic version, allowing us to add some custom stuff later still */
199         poseAnim_mapping_refresh(C, pso->scene, pso->ob);
200 }
201
202 /* helper for apply() - perform sliding for some value */
203 static void pose_slide_apply_val (tPoseSlideOp *pso, FCurve *fcu, float *val)
204 {
205         float cframe = (float)pso->cframe;
206         float sVal, eVal;
207         float w1, w2;
208         
209         /* get keyframe values for endpoint poses to blend with */
210                 /* previous/start */
211         sVal= evaluate_fcurve(fcu, (float)pso->prevFrame);
212                 /* next/end */
213         eVal= evaluate_fcurve(fcu, (float)pso->nextFrame);
214         
215         /* calculate the relative weights of the endpoints */
216         if (pso->mode == POSESLIDE_BREAKDOWN) {
217                 /* get weights from the percentage control */
218                 w1= pso->percentage;    /* this must come second */
219                 w2= 1.0f - w1;                  /* this must come first */
220         }
221         else {
222                 /*      - these weights are derived from the relative distance of these 
223                  *        poses from the current frame
224                  *      - they then get normalised so that they only sum up to 1
225                  */
226                 float wtot; 
227                 
228                 w1 = cframe - (float)pso->prevFrame;
229                 w2 = (float)pso->nextFrame - cframe;
230                 
231                 wtot = w1 + w2;
232                 w1 = (w1/wtot);
233                 w2 = (w2/wtot);
234         }
235         
236         /* depending on the mode, calculate the new value
237          *      - in all of these, the start+end values are multiplied by w2 and w1 (respectively),
238          *        since multiplication in another order would decrease the value the current frame is closer to
239          */
240         switch (pso->mode) {
241                 case POSESLIDE_PUSH: /* make the current pose more pronounced */
242                 {
243                         /* perform a weighted average here, favouring the middle pose 
244                          *      - numerator should be larger than denominator to 'expand' the result
245                          *      - perform this weighting a number of times given by the percentage...
246                          */
247                         int iters= (int)ceil(10.0f*pso->percentage); // TODO: maybe a sensitivity ctrl on top of this is needed
248                         
249                         while (iters-- > 0) {
250                                 (*val)= ( -((sVal * w2) + (eVal * w1)) + ((*val) * 6.0f) ) / 5.0f; 
251                         }
252                 }
253                         break;
254                         
255                 case POSESLIDE_RELAX: /* make the current pose more like its surrounding ones */
256                 {
257                         /* perform a weighted average here, favouring the middle pose 
258                          *      - numerator should be smaller than denominator to 'relax' the result
259                          *      - perform this weighting a number of times given by the percentage...
260                          */
261                         int iters= (int)ceil(10.0f*pso->percentage); // TODO: maybe a sensitivity ctrl on top of this is needed
262                         
263                         while (iters-- > 0) {
264                                 (*val)= ( ((sVal * w2) + (eVal * w1)) + ((*val) * 5.0f) ) / 6.0f;
265                         }
266                 }
267                         break;
268                         
269                 case POSESLIDE_BREAKDOWN: /* make the current pose slide around between the endpoints */
270                 {
271                         /* perform simple linear interpolation - coefficient for start must come from pso->percentage... */
272                         // TODO: make this use some kind of spline interpolation instead?
273                         (*val)= ((sVal * w2) + (eVal * w1));
274                 }
275                         break;
276         }
277 }
278
279 /* helper for apply() - perform sliding for some 3-element vector */
280 static void pose_slide_apply_vec3 (tPoseSlideOp *pso, tPChanFCurveLink *pfl, float vec[3], const char propName[])
281 {
282         LinkData *ld=NULL;
283         char *path=NULL;
284         
285         /* get the path to use... */
286         path= BLI_sprintfN("%s.%s", pfl->pchan_path, propName);
287         
288         /* using this path, find each matching F-Curve for the variables we're interested in */
289         while ( (ld= poseAnim_mapping_getNextFCurve(&pfl->fcurves, ld, path)) ) {
290                 FCurve *fcu= (FCurve *)ld->data;
291                 
292                 /* just work on these channels one by one... there's no interaction between values */
293                 pose_slide_apply_val(pso, fcu, &vec[fcu->array_index]);
294         }
295         
296         /* free the temp path we got */
297         MEM_freeN(path);
298 }
299
300 /* helper for apply() - perform sliding for custom properties */
301 static void pose_slide_apply_props (tPoseSlideOp *pso, tPChanFCurveLink *pfl)
302 {
303         PointerRNA ptr = {{NULL}};
304         LinkData *ld;
305         int len = strlen(pfl->pchan_path);
306         
307         /* setup pointer RNA for resolving paths */
308         RNA_pointer_create(NULL, &RNA_PoseBone, pfl->pchan, &ptr);
309         
310         /* custom properties are just denoted using ["..."][etc.] after the end of the base path, 
311          * so just check for opening pair after the end of the path
312          */
313         for (ld = pfl->fcurves.first; ld; ld = ld->next) {
314                 FCurve *fcu = (FCurve *)ld->data;
315                 char *bPtr, *pPtr;
316                 
317                 if (fcu->rna_path == NULL)
318                         continue;
319                 
320                 /* do we have a match? 
321                  *      - bPtr is the RNA Path with the standard part chopped off
322                  *      - pPtr is the chunk of the path which is left over
323                  */
324                 bPtr = strstr(fcu->rna_path, pfl->pchan_path) + len;
325                 pPtr = strstr(bPtr, "[\"");   /* dummy " for texteditor bugs */
326                 
327                 if (pPtr) {
328                         /* use RNA to try and get a handle on this property, then, assuming that it is just
329                          * numerical, try and grab the value as a float for temp editing before setting back
330                          */
331                         PropertyRNA *prop = RNA_struct_find_property(&ptr, pPtr);
332                         
333                         if (prop) {
334                                 switch (RNA_property_type(prop)) {
335                                         case PROP_FLOAT:
336                                         {
337                                                 float tval = RNA_property_float_get(&ptr, prop);
338                                                 pose_slide_apply_val(pso, fcu, &tval);
339                                                 RNA_property_float_set(&ptr, prop, tval);
340                                         }
341                                                 break;
342                                         case PROP_BOOLEAN:
343                                         case PROP_ENUM:
344                                         case PROP_INT:
345                                         {
346                                                 float tval = (float)RNA_property_int_get(&ptr, prop);
347                                                 pose_slide_apply_val(pso, fcu, &tval);
348                                                 RNA_property_int_set(&ptr, prop, (int)tval);
349                                         }
350                                                 break;
351                                         default:
352                                                 /* cannot handle */
353                                                 //printf("Cannot Pose Slide non-numerical property\n");
354                                                 break;
355                                 }
356                         }
357                 }
358         }
359 }
360
361 /* helper for apply() - perform sliding for quaternion rotations (using quat blending) */
362 static void pose_slide_apply_quat (tPoseSlideOp *pso, tPChanFCurveLink *pfl)
363 {
364         FCurve *fcu_w=NULL, *fcu_x=NULL, *fcu_y=NULL, *fcu_z=NULL;
365         bPoseChannel *pchan= pfl->pchan;
366         LinkData *ld=NULL;
367         char *path=NULL;
368         float cframe;
369         
370         /* get the path to use - this should be quaternion rotations only (needs care) */
371         path= BLI_sprintfN("%s.%s", pfl->pchan_path, "rotation_quaternion");
372         
373         /* get the current frame number */
374         cframe= (float)pso->cframe;
375         
376         /* using this path, find each matching F-Curve for the variables we're interested in */
377         while ( (ld= poseAnim_mapping_getNextFCurve(&pfl->fcurves, ld, path)) ) {
378                 FCurve *fcu= (FCurve *)ld->data;
379                 
380                 /* assign this F-Curve to one of the relevant pointers... */
381                 switch (fcu->array_index) {
382                         case 3: /* z */
383                                 fcu_z= fcu;
384                                 break;
385                         case 2: /* y */
386                                 fcu_y= fcu;
387                                 break;
388                         case 1: /* x */
389                                 fcu_x= fcu;
390                                 break;
391                         case 0: /* w */
392                                 fcu_w= fcu;
393                                 break;
394                 }
395         }
396         
397         /* only if all channels exist, proceed */
398         if (fcu_w && fcu_x && fcu_y && fcu_z) {
399                 float quat_prev[4], quat_next[4];
400                 
401                 /* get 2 quats */
402                 quat_prev[0] = evaluate_fcurve(fcu_w, pso->prevFrame);
403                 quat_prev[1] = evaluate_fcurve(fcu_x, pso->prevFrame);
404                 quat_prev[2] = evaluate_fcurve(fcu_y, pso->prevFrame);
405                 quat_prev[3] = evaluate_fcurve(fcu_z, pso->prevFrame);
406                 
407                 quat_next[0] = evaluate_fcurve(fcu_w, pso->nextFrame);
408                 quat_next[1] = evaluate_fcurve(fcu_x, pso->nextFrame);
409                 quat_next[2] = evaluate_fcurve(fcu_y, pso->nextFrame);
410                 quat_next[3] = evaluate_fcurve(fcu_z, pso->nextFrame);
411                 
412                 /* perform blending */
413                 if (pso->mode == POSESLIDE_BREAKDOWN) {
414                         /* just perform the interpol between quat_prev and quat_next using pso->percentage as a guide */
415                         interp_qt_qtqt(pchan->quat, quat_prev, quat_next, pso->percentage);
416                 }
417                 else if (pso->mode == POSESLIDE_PUSH) {
418                         float quat_diff[4], quat_orig[4];
419                         
420                         /* calculate the delta transform from the previous to the current */
421                         // TODO: investigate ways to favour one transform more?
422                         sub_qt_qtqt(quat_diff, pchan->quat, quat_prev);
423                         
424                         /* make a copy of the original rotation */
425                         QUATCOPY(quat_orig, pchan->quat);
426                         
427                         /* increase the original by the delta transform, by an amount determined by percentage */
428                         add_qt_qtqt(pchan->quat, quat_orig, quat_diff, pso->percentage);
429                 }
430                 else {
431                         float quat_interp[4], quat_orig[4];
432                         int iters= (int)ceil(10.0f*pso->percentage); // TODO: maybe a sensitivity ctrl on top of this is needed
433                         
434                         /* perform this blending several times until a satisfactory result is reached */
435                         while (iters-- > 0) {
436                                 /* calculate the interpolation between the endpoints */
437                                 interp_qt_qtqt(quat_interp, quat_prev, quat_next, (cframe-pso->prevFrame) / (pso->nextFrame-pso->prevFrame) );
438                                 
439                                 /* make a copy of the original rotation */
440                                 QUATCOPY(quat_orig, pchan->quat);
441                                 
442                                 /* tricky interpolations - blending between original and new */
443                                 interp_qt_qtqt(pchan->quat, quat_orig, quat_interp, 1.0f/6.0f);
444                         }
445                 }
446         }
447         
448         /* free the path now */
449         MEM_freeN(path);
450 }
451
452 /* apply() - perform the pose sliding based on weighting various poses */
453 static void pose_slide_apply(bContext *C, tPoseSlideOp *pso)
454 {
455         tPChanFCurveLink *pfl;
456         
457         /* sanitise the frame ranges */
458         if (pso->prevFrame == pso->nextFrame) {
459                 /* move out one step either side */
460                 pso->prevFrame--;
461                 pso->nextFrame++;
462         }
463         
464         /* for each link, handle each set of transforms */
465         for (pfl= pso->pfLinks.first; pfl; pfl= pfl->next) {
466                 /* valid transforms for each PoseChannel should have been noted already 
467                  *      - sliding the pose should be a straightforward exercise for location+rotation, 
468                  *        but rotations get more complicated since we may want to use quaternion blending 
469                  *        for quaternions instead...
470                  */
471                 bPoseChannel *pchan= pfl->pchan;
472                  
473                 if (pchan->flag & POSE_LOC) {
474                         /* calculate these for the 'location' vector, and use location curves */
475                         pose_slide_apply_vec3(pso, pfl, pchan->loc, "location");
476                 }
477                 
478                 if (pchan->flag & POSE_SIZE) {
479                         /* calculate these for the 'scale' vector, and use scale curves */
480                         pose_slide_apply_vec3(pso, pfl, pchan->size, "scale");
481                 }
482                 
483                 if (pchan->flag & POSE_ROT) {
484                         /* everything depends on the rotation mode */
485                         if (pchan->rotmode > 0) {
486                                 /* eulers - so calculate these for the 'eul' vector, and use euler_rotation curves */
487                                 pose_slide_apply_vec3(pso, pfl, pchan->eul, "rotation_euler");
488                         }
489                         else if (pchan->rotmode == ROT_MODE_AXISANGLE) {
490                                 // TODO: need to figure out how to do this!
491                         }
492                         else {
493                                 /* quaternions - use quaternion blending */
494                                 pose_slide_apply_quat(pso, pfl);
495                         }
496                 }
497                 
498                 if (pfl->oldprops) {
499                         /* not strictly a transform, but contributes to the pose produced in many rigs */
500                         pose_slide_apply_props(pso, pfl);
501                 }
502         }
503         
504         /* depsgraph updates + redraws */
505         pose_slide_refresh(C, pso);
506 }
507
508 /* perform autokeyframing after changes were made + confirmed */
509 static void pose_slide_autoKeyframe (bContext *C, tPoseSlideOp *pso)
510 {
511         /* wrapper around the generic call */
512         poseAnim_mapping_autoKeyframe(C, pso->scene, pso->ob, &pso->pfLinks, (float)pso->cframe);
513 }
514
515 /* reset changes made to current pose */
516 static void pose_slide_reset (tPoseSlideOp *pso)
517 {
518         /* wrapper around the generic call, so that custom stuff can be added later */
519         poseAnim_mapping_reset(&pso->pfLinks);
520 }
521
522 /* ------------------------------------ */
523
524 /* common code for invoke() methods */
525 static int pose_slide_invoke_common (bContext *C, wmOperator *op, tPoseSlideOp *pso)
526 {
527         tPChanFCurveLink *pfl;
528         AnimData *adt= pso->ob->adt;
529         wmWindow *win= CTX_wm_window(C);
530         
531         /* for each link, add all its keyframes to the search tree */
532         for (pfl= pso->pfLinks.first; pfl; pfl= pfl->next) {
533                 LinkData *ld;
534                 
535                 /* do this for each F-Curve */
536                 for (ld= pfl->fcurves.first; ld; ld= ld->next) {
537                         FCurve *fcu= (FCurve *)ld->data;
538                         fcurve_to_keylist(adt, fcu, &pso->keys, NULL);
539                 }
540         }
541         
542         /* consolidate these keyframes, and figure out the nearest ones */
543         BLI_dlrbTree_linkedlist_sync(&pso->keys);
544         
545                 /* cancel if no keyframes found... */
546         if (pso->keys.root) {
547                 ActKeyColumn *ak;
548                 float cframe= (float)pso->cframe;
549                 
550                 /* firstly, check if the current frame is a keyframe... */
551                 ak= (ActKeyColumn *)BLI_dlrbTree_search_exact(&pso->keys, compare_ak_cfraPtr, &cframe);
552                 
553                 if (ak == NULL) {
554                         /* current frame is not a keyframe, so search */
555                         ActKeyColumn *pk= (ActKeyColumn *)BLI_dlrbTree_search_prev(&pso->keys, compare_ak_cfraPtr, &cframe);
556                         ActKeyColumn *nk= (ActKeyColumn *)BLI_dlrbTree_search_next(&pso->keys, compare_ak_cfraPtr, &cframe);
557                         
558                         /* new set the frames */
559                                 /* prev frame */
560                         pso->prevFrame= (pk)? (pk->cfra) : (pso->cframe - 1);
561                         RNA_int_set(op->ptr, "prev_frame", pso->prevFrame);
562                                 /* next frame */
563                         pso->nextFrame= (nk)? (nk->cfra) : (pso->cframe + 1);
564                         RNA_int_set(op->ptr, "next_frame", pso->nextFrame);
565                 }
566                 else {
567                         /* current frame itself is a keyframe, so just take keyframes on either side */
568                                 /* prev frame */
569                         pso->prevFrame= (ak->prev)? (ak->prev->cfra) : (pso->cframe - 1);
570                         RNA_int_set(op->ptr, "prev_frame", pso->prevFrame);
571                                 /* next frame */
572                         pso->nextFrame= (ak->next)? (ak->next->cfra) : (pso->cframe + 1);
573                         RNA_int_set(op->ptr, "next_frame", pso->nextFrame);
574                 }
575         }
576         else {
577                 BKE_report(op->reports, RPT_ERROR, "No keyframes to slide between.");
578                 pose_slide_exit(op);
579                 return OPERATOR_CANCELLED;
580         }
581         
582         /* initial apply for operator... */
583         // TODO: need to calculate percentage for initial round too...
584         pose_slide_apply(C, pso);
585         
586         /* depsgraph updates + redraws */
587         pose_slide_refresh(C, pso);
588         
589         /* set cursor to indicate modal */
590         WM_cursor_modal(win, BC_EW_SCROLLCURSOR);
591         
592         /* add a modal handler for this operator */
593         WM_event_add_modal_handler(C, op);
594         return OPERATOR_RUNNING_MODAL;
595 }
596
597 /* common code for modal() */
598 static int pose_slide_modal (bContext *C, wmOperator *op, wmEvent *evt)
599 {
600         tPoseSlideOp *pso= op->customdata;
601         wmWindow *win= CTX_wm_window(C);
602         
603         switch (evt->type) {
604                 case LEFTMOUSE: /* confirm */
605                 {
606                         /* return to normal cursor */
607                         WM_cursor_restore(win);
608                         
609                         /* insert keyframes as required... */
610                         pose_slide_autoKeyframe(C, pso);
611                         pose_slide_exit(op);
612                         
613                         /* done! */
614                         return OPERATOR_FINISHED;
615                 }
616                 
617                 case ESCKEY:    /* cancel */
618                 case RIGHTMOUSE: 
619                 {
620                         /* return to normal cursor */
621                         WM_cursor_restore(win);
622                         
623                         /* reset transforms back to original state */
624                         pose_slide_reset(pso);
625                         
626                         /* depsgraph updates + redraws */
627                         pose_slide_refresh(C, pso);
628                         
629                         /* clean up temp data */
630                         pose_slide_exit(op);
631                         
632                         /* cancelled! */
633                         return OPERATOR_CANCELLED;
634                 }
635                         
636                 case MOUSEMOVE: /* calculate new position */
637                 {
638                         /* calculate percentage based on position of mouse (we only use x-axis for now.
639                          * since this is more conveninent for users to do), and store new percentage value 
640                          */
641                         pso->percentage= (evt->x - pso->ar->winrct.xmin) / ((float)pso->ar->winx);
642                         RNA_float_set(op->ptr, "percentage", pso->percentage);
643                         
644                         /* reset transforms (to avoid accumulation errors) */
645                         pose_slide_reset(pso);
646                         
647                         /* apply... */
648                         pose_slide_apply(C, pso);
649                 }
650                         break;
651                         
652                 default: /* unhandled event (maybe it was some view manip? */
653                         /* allow to pass through */
654                         return OPERATOR_RUNNING_MODAL|OPERATOR_PASS_THROUGH;
655         }
656         
657         /* still running... */
658         return OPERATOR_RUNNING_MODAL;
659 }
660
661 /* common code for cancel() */
662 static int pose_slide_cancel (bContext *UNUSED(C), wmOperator *op)
663 {
664         /* cleanup and done */
665         pose_slide_exit(op);
666         return OPERATOR_CANCELLED;
667 }
668
669 /* common code for exec() methods */
670 static int pose_slide_exec_common (bContext *C, wmOperator *op, tPoseSlideOp *pso)
671 {
672         /* settings should have been set up ok for applying, so just apply! */
673         pose_slide_apply(C, pso);
674         
675         /* insert keyframes if needed */
676         pose_slide_autoKeyframe(C, pso);
677         
678         /* cleanup and done */
679         pose_slide_exit(op);
680         
681         return OPERATOR_FINISHED;
682 }
683
684 /* common code for defining RNA properties */
685 static void pose_slide_opdef_properties (wmOperatorType *ot)
686 {
687         RNA_def_int(ot->srna, "prev_frame", 0, MINAFRAME, MAXFRAME, _("Previous Keyframe"), _("Frame number of keyframe immediately before the current frame."), 0, 50);
688         RNA_def_int(ot->srna, "next_frame", 0, MINAFRAME, MAXFRAME, _("Next Keyframe"), _("Frame number of keyframe immediately after the current frame."), 0, 50);
689         RNA_def_float_percentage(ot->srna, "percentage", 0.5f, 0.0f, 1.0f, _("Percentage"), _("Weighting factor for the sliding operation"), 0.3, 0.7);
690 }
691
692 /* ------------------------------------ */
693
694 /* invoke() - for 'push' mode */
695 static int pose_slide_push_invoke (bContext *C, wmOperator *op, wmEvent *UNUSED(evt))
696 {
697         tPoseSlideOp *pso;
698         
699         /* initialise data  */
700         if (pose_slide_init(C, op, POSESLIDE_PUSH) == 0) {
701                 pose_slide_exit(op);
702                 return OPERATOR_CANCELLED;
703         }
704         else
705                 pso= op->customdata;
706         
707         /* do common setup work */
708         return pose_slide_invoke_common(C, op, pso);
709 }
710
711 /* exec() - for push */
712 static int pose_slide_push_exec (bContext *C, wmOperator *op)
713 {
714         tPoseSlideOp *pso;
715         
716         /* initialise data (from RNA-props) */
717         if (pose_slide_init(C, op, POSESLIDE_PUSH) == 0) {
718                 pose_slide_exit(op);
719                 return OPERATOR_CANCELLED;
720         }
721         else
722                 pso= op->customdata;
723                 
724         /* do common exec work */
725         return pose_slide_exec_common(C, op, pso);
726 }
727
728 void POSE_OT_push (wmOperatorType *ot)
729 {
730         /* identifiers */
731         ot->name= _("Push Pose");
732         ot->idname= "POSE_OT_push";
733         ot->description= _("Exaggerate the current pose");
734         
735         /* callbacks */
736         ot->exec= pose_slide_push_exec;
737         ot->invoke= pose_slide_push_invoke;
738         ot->modal= pose_slide_modal;
739         ot->cancel= pose_slide_cancel;
740         ot->poll= ED_operator_posemode;
741         
742         /* flags */
743         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING;
744         
745         /* Properties */
746         pose_slide_opdef_properties(ot);
747 }
748
749 /* ........................ */
750
751 /* invoke() - for 'relax' mode */
752 static int pose_slide_relax_invoke (bContext *C, wmOperator *op, wmEvent *UNUSED(evt))
753 {
754         tPoseSlideOp *pso;
755         
756         /* initialise data  */
757         if (pose_slide_init(C, op, POSESLIDE_RELAX) == 0) {
758                 pose_slide_exit(op);
759                 return OPERATOR_CANCELLED;
760         }
761         else
762                 pso= op->customdata;
763         
764         /* do common setup work */
765         return pose_slide_invoke_common(C, op, pso);
766 }
767
768 /* exec() - for relax */
769 static int pose_slide_relax_exec (bContext *C, wmOperator *op)
770 {
771         tPoseSlideOp *pso;
772         
773         /* initialise data (from RNA-props) */
774         if (pose_slide_init(C, op, POSESLIDE_RELAX) == 0) {
775                 pose_slide_exit(op);
776                 return OPERATOR_CANCELLED;
777         }
778         else
779                 pso= op->customdata;
780                 
781         /* do common exec work */
782         return pose_slide_exec_common(C, op, pso);
783 }
784
785 void POSE_OT_relax (wmOperatorType *ot)
786 {
787         /* identifiers */
788         ot->name= _("Relax Pose");
789         ot->idname= "POSE_OT_relax";
790         ot->description= _("Make the current pose more similar to its surrounding ones");
791         
792         /* callbacks */
793         ot->exec= pose_slide_relax_exec;
794         ot->invoke= pose_slide_relax_invoke;
795         ot->modal= pose_slide_modal;
796         ot->cancel= pose_slide_cancel;
797         ot->poll= ED_operator_posemode;
798         
799         /* flags */
800         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING;
801         
802         /* Properties */
803         pose_slide_opdef_properties(ot);
804 }
805
806 /* ........................ */
807
808 /* invoke() - for 'breakdown' mode */
809 static int pose_slide_breakdown_invoke (bContext *C, wmOperator *op, wmEvent *UNUSED(evt))
810 {
811         tPoseSlideOp *pso;
812         
813         /* initialise data  */
814         if (pose_slide_init(C, op, POSESLIDE_BREAKDOWN) == 0) {
815                 pose_slide_exit(op);
816                 return OPERATOR_CANCELLED;
817         }
818         else
819                 pso= op->customdata;
820         
821         /* do common setup work */
822         return pose_slide_invoke_common(C, op, pso);
823 }
824
825 /* exec() - for breakdown */
826 static int pose_slide_breakdown_exec (bContext *C, wmOperator *op)
827 {
828         tPoseSlideOp *pso;
829         
830         /* initialise data (from RNA-props) */
831         if (pose_slide_init(C, op, POSESLIDE_BREAKDOWN) == 0) {
832                 pose_slide_exit(op);
833                 return OPERATOR_CANCELLED;
834         }
835         else
836                 pso= op->customdata;
837                 
838         /* do common exec work */
839         return pose_slide_exec_common(C, op, pso);
840 }
841
842 void POSE_OT_breakdown (wmOperatorType *ot)
843 {
844         /* identifiers */
845         ot->name= _("Pose Breakdowner");
846         ot->idname= "POSE_OT_breakdown";
847         ot->description= _("Create a suitable breakdown pose on the current frame");
848         
849         /* callbacks */
850         ot->exec= pose_slide_breakdown_exec;
851         ot->invoke= pose_slide_breakdown_invoke;
852         ot->modal= pose_slide_modal;
853         ot->cancel= pose_slide_cancel;
854         ot->poll= ED_operator_posemode;
855         
856         /* flags */
857         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING;
858         
859         /* Properties */
860         pose_slide_opdef_properties(ot);
861 }
862
863 /* **************************************************** */
864 /* B) Pose Propagate */
865
866 /* "termination conditions" - i.e. when we stop */
867 typedef enum ePosePropagate_Termination {
868                 /* stop after the current hold ends */
869         POSE_PROPAGATE_SMART_HOLDS = 0,
870                 /* only do on the last keyframe */
871         POSE_PROPAGATE_LAST_KEY,
872                 /* stop after the next keyframe */
873         POSE_PROPAGATE_NEXT_KEY,
874                 /* stop after the specified frame */
875         POSE_PROPAGATE_BEFORE_FRAME,
876                 /* stop when we run out of keyframes */
877         POSE_PROPAGATE_BEFORE_END,
878         
879                 /* only do on the frames where markers are selected */
880         POSE_PROPAGATE_SELECTED_MARKERS
881 } ePosePropagate_Termination;
882
883 /* termination data needed for some modes - assumes only one of these entries will be needed at a time */
884 typedef union tPosePropagate_ModeData {
885         /* smart holds + before frame: frame number to stop on */
886         float end_frame;
887         
888         /* selected markers: listbase for CfraElem's marking these frames */
889         ListBase sel_markers;
890 } tPosePropagate_ModeData;
891
892 /* --------------------------------- */
893
894 /* get frame on which the "hold" for the bone ends 
895  * XXX: this may not really work that well if a bone moves on some channels and not others
896  *              if this happens to be a major issue, scrap this, and just make this happen 
897  *              independently per F-Curve
898  */
899 static float pose_propagate_get_boneHoldEndFrame (Object *ob, tPChanFCurveLink *pfl, float startFrame)
900 {
901         DLRBT_Tree keys, blocks;
902         ActKeyBlock *ab;
903         
904         AnimData *adt= ob->adt;
905         LinkData *ld;
906         float endFrame = startFrame;
907         
908         /* set up optimised data-structures for searching for relevant keyframes + holds */
909         BLI_dlrbTree_init(&keys);
910         BLI_dlrbTree_init(&blocks);
911         
912         for (ld = pfl->fcurves.first; ld; ld = ld->next) {
913                 FCurve *fcu = (FCurve *)ld->data;
914                 fcurve_to_keylist(adt, fcu, &keys, &blocks);
915         }
916         
917         BLI_dlrbTree_linkedlist_sync(&keys);
918         BLI_dlrbTree_linkedlist_sync(&blocks);
919         
920         /* find the long keyframe (i.e. hold), and hence obtain the endFrame value 
921          *      - the best case would be one that starts on the frame itself
922          */
923         ab = (ActKeyBlock *)BLI_dlrbTree_search_exact(&blocks, compare_ab_cfraPtr, &startFrame);
924         
925         if (actkeyblock_is_valid(ab, &keys) == 0) {
926                 /* There are only two cases for no-exact match:
927                  *      1) the current frame is just before another key but not on a key itself
928                  *      2) the current frame is on a key, but that key doesn't link to the next
929                  *
930                  * If we've got the first case, then we can search for another block, 
931                  * otherwise forget it, as we'd be overwriting some valid data.
932                  */
933                 if (BLI_dlrbTree_search_exact(&keys, compare_ak_cfraPtr, &startFrame) == NULL) {
934                         /* we've got case 1, so try the one after */
935                         ab = (ActKeyBlock *)BLI_dlrbTree_search_next(&blocks, compare_ab_cfraPtr, &startFrame);
936                         
937                         if (actkeyblock_is_valid(ab, &keys) == 0) {
938                                 /* try the block before this frame then as last resort */
939                                 ab = (ActKeyBlock *)BLI_dlrbTree_search_prev(&blocks, compare_ab_cfraPtr, &startFrame);
940                                 
941                                 /* whatever happens, stop searching now... */
942                                 if (actkeyblock_is_valid(ab, &keys) == 0) {
943                                         /* restrict range to just the frame itself 
944                                          * i.e. everything is in motion, so no holds to safely overwrite
945                                          */
946                                         ab = NULL;
947                                 }
948                         }
949                 }
950                 else {
951                         /* we've got case 2 - set ab to NULL just in case, since we shouldn't do anything in this case */
952                         ab = NULL;
953                 }
954         }
955         
956         /* check if we can go any further than we've already gone */
957         if (ab) {
958                 /* go to next if it is also valid and meets "extension" criteria */
959                 while (ab->next) {
960                         ActKeyBlock *abn = (ActKeyBlock *)ab->next;
961                         
962                         /* must be valid */
963                         if (actkeyblock_is_valid(abn, &keys) == 0)
964                                 break;
965                         /* should start on the same frame that the last ended on */
966                         if (ab->end != abn->start)
967                                 break;
968                         /* should have the same number of curves */
969                         if (ab->totcurve != abn->totcurve)
970                                 break;
971                         /* should have the same value 
972                          * XXX: this may be a bit fuzzy on larger data sets, so be careful
973                          */
974                         if (ab->val != abn->val)
975                                 break;
976                                 
977                         /* we can extend the bounds to the end of this "next" block now */
978                         ab = abn;
979                 }
980                 
981                 /* end frame can now take the value of the end of the block */
982                 endFrame = ab->end;
983         }
984         
985         /* free temp memory */
986         BLI_dlrbTree_free(&keys);
987         BLI_dlrbTree_free(&blocks);
988         
989         /* return the end frame we've found */
990         return endFrame;
991 }
992
993 /* get reference value from F-Curve using RNA */
994 static short pose_propagate_get_refVal (Object *ob, FCurve *fcu, float *value)
995 {
996         PointerRNA id_ptr, ptr;
997         PropertyRNA *prop;
998         short found= FALSE;
999         
1000         /* base pointer is always the object -> id_ptr */
1001         RNA_id_pointer_create(&ob->id, &id_ptr);
1002         
1003         /* resolve the property... */
1004         if (RNA_path_resolve(&id_ptr, fcu->rna_path, &ptr, &prop)) {
1005                 if (RNA_property_array_check(&ptr, prop)) {
1006                         /* array */
1007                         if (fcu->array_index < RNA_property_array_length(&ptr, prop)) {
1008                                 found= TRUE;
1009                                 switch (RNA_property_type(prop)) {
1010                                         case PROP_BOOLEAN:
1011                                                 *value= (float)RNA_property_boolean_get_index(&ptr, prop, fcu->array_index);
1012                                                 break;
1013                                         case PROP_INT:
1014                                                 *value= (float)RNA_property_int_get_index(&ptr, prop, fcu->array_index);
1015                                                 break;
1016                                         case PROP_FLOAT:
1017                                                 *value= RNA_property_float_get_index(&ptr, prop, fcu->array_index);
1018                                                 break;
1019                                         default:
1020                                                 found= FALSE;
1021                                                 break;
1022                                 }
1023                         }
1024                 }
1025                 else {
1026                         /* not an array */
1027                         found= TRUE;
1028                         switch (RNA_property_type(prop)) {
1029                                 case PROP_BOOLEAN:
1030                                         *value= (float)RNA_property_boolean_get(&ptr, prop);
1031                                         break;
1032                                 case PROP_INT:
1033                                         *value= (float)RNA_property_int_get(&ptr, prop);
1034                                         break;
1035                                 case PROP_ENUM:
1036                                         *value= (float)RNA_property_enum_get(&ptr, prop);
1037                                         break;
1038                                 case PROP_FLOAT:
1039                                         *value= RNA_property_float_get(&ptr, prop);
1040                                         break;
1041                                 default:
1042                                         found= FALSE;
1043                                         break;
1044                         }
1045                 }
1046         }
1047         
1048         return found;
1049 }
1050
1051 /* propagate just works along each F-Curve in turn */
1052 static void pose_propagate_fcurve (wmOperator *op, Object *ob, FCurve *fcu, 
1053                                 float startFrame, tPosePropagate_ModeData modeData)
1054 {
1055         const int mode = RNA_enum_get(op->ptr, "mode");
1056         
1057         BezTriple *bezt;
1058         float refVal = 0.0f;
1059         short keyExists;
1060         int i, match;
1061         short first=1;
1062         
1063         /* skip if no keyframes to edit */
1064         if ((fcu->bezt == NULL) || (fcu->totvert < 2))
1065                 return;
1066                 
1067         /* find the reference value from bones directly, which means that the user
1068          * doesn't need to firstly keyframe the pose (though this doesn't mean that 
1069          * they can't either)
1070          */
1071         if( !pose_propagate_get_refVal(ob, fcu, &refVal))
1072                 return;
1073         
1074         /* find the first keyframe to start propagating from 
1075          *      - if there's a keyframe on the current frame, we probably want to save this value there too
1076          *        since it may be as of yet unkeyed
1077          *      - if starting before the starting frame, don't touch the key, as it may have had some valid 
1078          *        values
1079          */
1080         match = binarysearch_bezt_index(fcu->bezt, startFrame, fcu->totvert, &keyExists);
1081         
1082         if (fcu->bezt[match].vec[1][0] < startFrame)
1083                 i = match + 1;
1084         else
1085                 i = match;
1086         
1087         for (bezt = &fcu->bezt[i]; i < fcu->totvert; i++, bezt++) {
1088                 /* additional termination conditions based on the operator 'mode' property go here... */
1089                 if (ELEM(mode, POSE_PROPAGATE_BEFORE_FRAME, POSE_PROPAGATE_SMART_HOLDS)) {
1090                         /* stop if keyframe is outside the accepted range */
1091                         if (bezt->vec[1][0] > modeData.end_frame)
1092                                 break; 
1093                 }
1094                 else if (mode == POSE_PROPAGATE_NEXT_KEY) {
1095                         /* stop after the first keyframe has been processed */
1096                         if (first == 0)
1097                                 break;
1098                 }
1099                 else if (mode == POSE_PROPAGATE_LAST_KEY) {
1100                         /* only affect this frame if it will be the last one */
1101                         if (i != (fcu->totvert-1))
1102                                 continue;
1103                 }
1104                 else if (mode == POSE_PROPAGATE_SELECTED_MARKERS) {
1105                         /* only allow if there's a marker on this frame */
1106                         CfraElem *ce = NULL;
1107                         
1108                         /* stop on matching marker if there is one */
1109                         for (ce = modeData.sel_markers.first; ce; ce = ce->next) {
1110                                 if (ce->cfra == (int)(floor(bezt->vec[1][0] + 0.5f)))
1111                                         break;
1112                         }
1113                         
1114                         /* skip this keyframe if no marker */
1115                         if (ce == NULL)
1116                                 continue;
1117                 }
1118                 
1119                 /* just flatten handles, since values will now be the same either side... */
1120                 // TODO: perhaps a fade-out modulation of the value is required here (optional once again)?
1121                 bezt->vec[0][1] = bezt->vec[1][1] = bezt->vec[2][1] = refVal;
1122                 
1123                 /* select keyframe to indicate that it's been changed */
1124                 bezt->f2 |= SELECT;
1125                 first = 0;
1126         }
1127 }
1128
1129 /* --------------------------------- */
1130
1131 static int pose_propagate_exec (bContext *C, wmOperator *op)
1132 {
1133         Scene *scene = CTX_data_scene(C);
1134         Object *ob= ED_object_pose_armature(CTX_data_active_object(C));
1135         bAction *act= (ob && ob->adt)? ob->adt->action : NULL;
1136         
1137         ListBase pflinks = {NULL, NULL};
1138         tPChanFCurveLink *pfl;
1139         
1140         tPosePropagate_ModeData modeData;
1141         const int mode = RNA_enum_get(op->ptr, "mode");
1142         
1143         /* sanity checks */
1144         if (ob == NULL) {
1145                 BKE_report(op->reports, RPT_ERROR, "No object to propagate poses for");
1146                 return OPERATOR_CANCELLED;
1147         }
1148         if (act == NULL) {
1149                 BKE_report(op->reports, RPT_ERROR, "No keyframed poses to propagate to");
1150                 return OPERATOR_CANCELLED;
1151         }
1152         
1153         /* isolate F-Curves related to the selected bones */
1154         poseAnim_mapping_get(C, &pflinks, ob, act);
1155         
1156         /* mode-specific data preprocessing (requiring no access to curves) */
1157         if (mode == POSE_PROPAGATE_SELECTED_MARKERS) {
1158                 /* get a list of selected markers */
1159                 ED_markers_make_cfra_list(&scene->markers, &modeData.sel_markers, SELECT);
1160         }
1161         else {
1162                 /* assume everything else wants endFrame */
1163                 modeData.end_frame = RNA_float_get(op->ptr, "end_frame");
1164         }
1165         
1166         /* for each bone, perform the copying required */
1167         for (pfl = pflinks.first; pfl; pfl = pfl->next) {
1168                 LinkData *ld;
1169                 
1170                 /* mode-specific data preprocessing (requiring access to all curves) */
1171                 if (mode == POSE_PROPAGATE_SMART_HOLDS) {
1172                         /* we store in endFrame the end frame of the "long keyframe" (i.e. a held value) starting
1173                          * from the keyframe that occurs after the current frame
1174                          */
1175                         modeData.end_frame = pose_propagate_get_boneHoldEndFrame(ob, pfl, (float)CFRA);
1176                 }
1177                 
1178                 /* go through propagating pose to keyframes, curve by curve */
1179                 for (ld = pfl->fcurves.first; ld; ld= ld->next)
1180                         pose_propagate_fcurve(op, ob, (FCurve *)ld->data, (float)CFRA, modeData);
1181         }
1182         
1183         /* free temp data */
1184         poseAnim_mapping_free(&pflinks);
1185         
1186         if (mode == POSE_PROPAGATE_SELECTED_MARKERS)
1187                 BLI_freelistN(&modeData.sel_markers);
1188         
1189         /* updates + notifiers */
1190         poseAnim_mapping_refresh(C, scene, ob);
1191         
1192         return OPERATOR_FINISHED;
1193 }
1194
1195 /* --------------------------------- */
1196
1197 void POSE_OT_propagate (wmOperatorType *ot)
1198 {
1199         static EnumPropertyItem terminate_items[]= {
1200                 {POSE_PROPAGATE_SMART_HOLDS, "WHILE_HELD", 0, N_("While Held"), N_("Propagate pose to all keyframes after current frame that don't change (Default behaviour)")},
1201                 {POSE_PROPAGATE_NEXT_KEY, "NEXT_KEY", 0, N_("To Next Keyframe"), N_("Propagate pose to first keyframe following the current frame only")},
1202                 {POSE_PROPAGATE_LAST_KEY, "LAST_KEY", 0, N_("To Last Keyframe"), N_("Propagate pose to the last keyframe only (i.e. making action cyclic)")},
1203                 {POSE_PROPAGATE_BEFORE_FRAME, "BEFORE_FRAME", 0, N_("Before Frame"), N_("Propagate pose to all keyframes between current frame and 'Frame' property")},
1204                 {POSE_PROPAGATE_BEFORE_END, "BEFORE_END", 0, N_("Before Last Keyframe"), N_("Propagate pose to all keyframes from current frame until no more are found")},
1205                 {POSE_PROPAGATE_SELECTED_MARKERS, "SELECTED_MARKERS", 0, N_("On Selected Markers"), N_("Propagate pose to all keyframes occurring on frames with Scene Markers after the current frame")},
1206                 {0, NULL, 0, NULL, NULL}};
1207                 
1208         /* identifiers */
1209         ot->name= _("Propagate Pose");
1210         ot->idname= "POSE_OT_propagate";
1211         ot->description= _("Copy selected aspects of the current pose to subsequent poses already keyframed");
1212         
1213         /* callbacks */
1214         ot->exec= pose_propagate_exec;
1215         ot->poll= ED_operator_posemode; // XXX: needs selected bones!
1216         
1217         /* flag */
1218         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
1219         
1220         /* properties */
1221         // TODO: add "fade out" control for tapering off amount of propagation as time goes by?
1222         ot->prop= RNA_def_enum(ot->srna, "mode", RNA_enum_items_gettexted(terminate_items), POSE_PROPAGATE_SMART_HOLDS, _("Terminate Mode"), _("Method used to determine when to stop propagating pose to keyframes"));
1223         RNA_def_float(ot->srna, "end_frame", 250.0, FLT_MIN, FLT_MAX, _("End Frame"), _("Frame to stop propagating frames to (for 'Before Frame' mode)"), 1.0, 250.0);
1224 }
1225
1226 /* **************************************************** */