tornavis/source/blender/editors/armature/pose_slide.c

1361 lines
42 KiB
C
Raw Normal View History

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
2010-02-12 14:34:04 +01:00
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2009, Blender Foundation, Joshua Leung
* This is a new part of Blender
*
* Contributor(s): Joshua Leung
*
* ***** END GPL LICENSE BLOCK *****
*/
2011-02-27 21:29:51 +01:00
/** \file blender/editors/armature/pose_slide.c
2011-02-27 21:29:51 +01:00
* \ingroup edarmature
*/
#include "MEM_guardedalloc.h"
#include "BLI_math.h"
#include "BLI_blenlib.h"
#include "BLI_dlrbTree.h"
#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_fcurve.h"
#include "BKE_context.h"
#include "BKE_object.h"
#include "BKE_report.h"
#include "BKE_unit.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "WM_api.h"
#include "WM_types.h"
#include "UI_interface.h"
#include "ED_armature.h"
#include "ED_keyframes_draw.h"
#include "ED_markers.h"
#include "ED_numinput.h"
#include "ED_screen.h"
#include "armature_intern.h"
/* **************************************************** */
/* == POSE 'SLIDING' TOOLS ==
*
* A) Push & Relax, Breakdowner
* These tools provide the animator with various capabilities
* for interactively controlling the spacing of poses, but also
* for 'pushing' and/or 'relaxing' extremes as they see fit.
*
* 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.
*/
/* **************************************************** */
/* A) Push & Relax, Breakdowner */
/* Temporary data shared between these operators */
typedef struct tPoseSlideOp {
2012-05-08 22:18:33 +02:00
Scene *scene; /* current scene */
ScrArea *sa; /* area that we're operating in (needed for modal()) */
ARegion *ar; /* region that we're operating in (needed for modal()) */
Object *ob; /* active object that Pose Info comes from */
bArmature *arm; /* armature for pose */
ListBase pfLinks; /* links between posechannels and f-curves */
DLRBT_Tree keys; /* binary tree for quicker searching for keyframes (when applicable) */
int cframe; /* current frame number */
int prevFrame; /* frame before current frame (blend-from) */
int nextFrame; /* frame after current frame (blend-to) */
2012-05-08 22:18:33 +02:00
int mode; /* sliding mode (ePoseSlide_Modes) */
2012-10-20 22:20:02 +02:00
int flag; /* unused for now, but can later get used for storing runtime settings.... */
2012-05-08 22:18:33 +02:00
float percentage; /* 0-1 value for determining the influence of whatever is relevant */
NumInput num; /* numeric input */
} tPoseSlideOp;
/* Pose Sliding Modes */
typedef enum ePoseSlide_Modes {
2012-05-08 22:18:33 +02:00
POSESLIDE_PUSH = 0, /* exaggerate the pose... */
POSESLIDE_RELAX, /* soften the pose... */
POSESLIDE_BREAKDOWN, /* slide between the endpoint poses, finding a 'soft' spot */
} ePoseSlide_Modes;
/* ------------------------------------ */
/* operator init */
2012-05-08 22:18:33 +02:00
static int pose_slide_init(bContext *C, wmOperator *op, short mode)
{
tPoseSlideOp *pso;
2012-05-08 22:18:33 +02:00
bAction *act = NULL;
/* init slide-op data */
2012-05-08 22:18:33 +02:00
pso = op->customdata = MEM_callocN(sizeof(tPoseSlideOp), "tPoseSlideOp");
/* get info from context */
2012-05-08 22:18:33 +02:00
pso->scene = CTX_data_scene(C);
pso->ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
pso->arm = (pso->ob) ? pso->ob->data : NULL;
pso->sa = CTX_wm_area(C); /* only really needed when doing modal() */
pso->ar = CTX_wm_region(C); /* only really needed when doing modal() */
2012-05-08 22:18:33 +02:00
pso->cframe = pso->scene->r.cfra;
pso->mode = mode;
/* set range info from property values - these may get overridden for the invoke() */
2012-05-08 22:18:33 +02:00
pso->percentage = RNA_float_get(op->ptr, "percentage");
pso->prevFrame = RNA_int_get(op->ptr, "prev_frame");
pso->nextFrame = RNA_int_get(op->ptr, "next_frame");
/* check the settings from the context */
if (ELEM(NULL, pso->ob, pso->arm, pso->ob->adt, pso->ob->adt->action))
return 0;
else
2012-05-08 22:18:33 +02:00
act = pso->ob->adt->action;
/* for each Pose-Channel which gets affected, get the F-Curves for that channel
* and set the relevant transform flags...
*/
poseAnim_mapping_get(C, &pso->pfLinks, pso->ob, act);
/* set depsgraph flags */
2012-05-08 22:18:33 +02:00
/* make sure the lock is set OK, unlock can be accidentally saved? */
pso->ob->pose->flag |= POSE_LOCKED;
pso->ob->pose->flag &= ~POSE_DO_UNLOCK;
/* do basic initialize of RB-BST used for finding keyframes, but leave the filling of it up
* to the caller of this (usually only invoke() will do it, to make things more efficient).
*/
BLI_dlrbTree_init(&pso->keys);
/* initialise numeric input */
initNumInput(&pso->num);
pso->num.idx_max = 0; /* one axis */
pso->num.val_flag[0] |= NUM_NO_NEGATIVE;
pso->num.unit_type[0] = B_UNIT_NONE; /* percentages don't have any units... */
/* return status is whether we've got all the data we were requested to get */
return 1;
}
/* exiting the operator - free data */
static void pose_slide_exit(wmOperator *op)
{
2012-05-08 22:18:33 +02:00
tPoseSlideOp *pso = op->customdata;
/* if data exists, clear its data and exit */
if (pso) {
/* free the temp pchan links and their data */
poseAnim_mapping_free(&pso->pfLinks);
/* free RB-BST for keyframes (if it contained data) */
BLI_dlrbTree_free(&pso->keys);
/* free data itself */
MEM_freeN(pso);
}
/* cleanup */
2012-05-08 22:18:33 +02:00
op->customdata = NULL;
}
/* ------------------------------------ */
/* helper for apply() / reset() - refresh the data */
2012-05-08 22:18:33 +02:00
static void pose_slide_refresh(bContext *C, tPoseSlideOp *pso)
{
/* wrapper around the generic version, allowing us to add some custom stuff later still */
poseAnim_mapping_refresh(C, pso->scene, pso->ob);
}
/* helper for apply() - perform sliding for some value */
2012-05-08 22:18:33 +02:00
static void pose_slide_apply_val(tPoseSlideOp *pso, FCurve *fcu, float *val)
{
float cframe = (float)pso->cframe;
float sVal, eVal;
float w1, w2;
/* get keyframe values for endpoint poses to blend with */
2012-05-08 22:18:33 +02:00
/* previous/start */
sVal = evaluate_fcurve(fcu, (float)pso->prevFrame);
/* next/end */
eVal = evaluate_fcurve(fcu, (float)pso->nextFrame);
/* if both values are equal, don't do anything */
2015-04-08 08:13:00 +02:00
if (IS_EQF(sVal, eVal)) {
(*val) = sVal;
return;
}
/* calculate the relative weights of the endpoints */
if (pso->mode == POSESLIDE_BREAKDOWN) {
/* get weights from the percentage control */
2012-05-08 22:18:33 +02:00
w1 = pso->percentage; /* this must come second */
w2 = 1.0f - w1; /* this must come first */
}
else {
/* - these weights are derived from the relative distance of these
* poses from the current frame
* - they then get normalized so that they only sum up to 1
*/
float wtot;
w1 = cframe - (float)pso->prevFrame;
w2 = (float)pso->nextFrame - cframe;
wtot = w1 + w2;
2012-05-08 22:18:33 +02:00
w1 = (w1 / wtot);
w2 = (w2 / wtot);
}
/* depending on the mode, calculate the new value
* - in all of these, the start+end values are multiplied by w2 and w1 (respectively),
* since multiplication in another order would decrease the value the current frame is closer to
*/
switch (pso->mode) {
case POSESLIDE_PUSH: /* make the current pose more pronounced */
{
2012-03-08 05:12:11 +01:00
/* perform a weighted average here, favoring the middle pose
* - numerator should be larger than denominator to 'expand' the result
* - perform this weighting a number of times given by the percentage...
*/
int iters = (int)ceil(10.0f * pso->percentage); /* TODO: maybe a sensitivity ctrl on top of this is needed */
while (iters-- > 0) {
2012-05-08 22:18:33 +02:00
(*val) = (-((sVal * w2) + (eVal * w1)) + ((*val) * 6.0f) ) / 5.0f;
}
break;
}
case POSESLIDE_RELAX: /* make the current pose more like its surrounding ones */
{
2012-03-08 05:12:11 +01:00
/* perform a weighted average here, favoring the middle pose
* - numerator should be smaller than denominator to 'relax' the result
* - perform this weighting a number of times given by the percentage...
*/
int iters = (int)ceil(10.0f * pso->percentage); /* TODO: maybe a sensitivity ctrl on top of this is needed */
while (iters-- > 0) {
2012-05-08 22:18:33 +02:00
(*val) = ( ((sVal * w2) + (eVal * w1)) + ((*val) * 5.0f) ) / 6.0f;
}
break;
}
case POSESLIDE_BREAKDOWN: /* make the current pose slide around between the endpoints */
{
/* perform simple linear interpolation - coefficient for start must come from pso->percentage... */
/* TODO: make this use some kind of spline interpolation instead? */
2012-05-08 22:18:33 +02:00
(*val) = ((sVal * w2) + (eVal * w1));
break;
}
}
}
/* helper for apply() - perform sliding for some 3-element vector */
2012-05-08 22:18:33 +02:00
static void pose_slide_apply_vec3(tPoseSlideOp *pso, tPChanFCurveLink *pfl, float vec[3], const char propName[])
{
2012-05-08 22:18:33 +02:00
LinkData *ld = NULL;
char *path = NULL;
/* get the path to use... */
2012-05-08 22:18:33 +02:00
path = BLI_sprintfN("%s.%s", pfl->pchan_path, propName);
/* using this path, find each matching F-Curve for the variables we're interested in */
2012-05-08 22:18:33 +02:00
while ( (ld = poseAnim_mapping_getNextFCurve(&pfl->fcurves, ld, path)) ) {
FCurve *fcu = (FCurve *)ld->data;
/* just work on these channels one by one... there's no interaction between values */
BLI_assert(fcu->array_index < 3);
pose_slide_apply_val(pso, fcu, &vec[fcu->array_index]);
}
/* free the temp path we got */
MEM_freeN(path);
}
/* helper for apply() - perform sliding for custom properties */
2012-05-08 22:18:33 +02:00
static void pose_slide_apply_props(tPoseSlideOp *pso, tPChanFCurveLink *pfl)
{
PointerRNA ptr = {{NULL}};
LinkData *ld;
int len = strlen(pfl->pchan_path);
/* setup pointer RNA for resolving paths */
RNA_pointer_create(NULL, &RNA_PoseBone, pfl->pchan, &ptr);
/* custom properties are just denoted using ["..."][etc.] after the end of the base path,
* so just check for opening pair after the end of the path
*/
for (ld = pfl->fcurves.first; ld; ld = ld->next) {
FCurve *fcu = (FCurve *)ld->data;
const char *bPtr, *pPtr;
if (fcu->rna_path == NULL)
continue;
/* do we have a match?
* - bPtr is the RNA Path with the standard part chopped off
* - pPtr is the chunk of the path which is left over
*/
bPtr = strstr(fcu->rna_path, pfl->pchan_path) + len;
pPtr = strstr(bPtr, "[\""); /* dummy " for texteditor bugs */
if (pPtr) {
/* use RNA to try and get a handle on this property, then, assuming that it is just
* numerical, try and grab the value as a float for temp editing before setting back
*/
PropertyRNA *prop = RNA_struct_find_property(&ptr, pPtr);
if (prop) {
switch (RNA_property_type(prop)) {
/* continuous values that can be smoothly interpolated... */
case PROP_FLOAT:
{
float tval = RNA_property_float_get(&ptr, prop);
pose_slide_apply_val(pso, fcu, &tval);
RNA_property_float_set(&ptr, prop, tval);
break;
}
case PROP_INT:
{
float tval = (float)RNA_property_int_get(&ptr, prop);
pose_slide_apply_val(pso, fcu, &tval);
RNA_property_int_set(&ptr, prop, (int)tval);
break;
}
/* values which can only take discrete values */
case PROP_BOOLEAN:
{
float tval = (float)RNA_property_boolean_get(&ptr, prop);
pose_slide_apply_val(pso, fcu, &tval);
RNA_property_boolean_set(&ptr, prop, (int)tval); // XXX: do we need threshold clamping here?
break;
}
case PROP_ENUM:
{
/* don't handle this case - these don't usually represent interchangeable
* set of values which should be interpolated between
*/
break;
}
default:
/* cannot handle */
//printf("Cannot Pose Slide non-numerical property\n");
break;
}
}
}
}
}
/* helper for apply() - perform sliding for quaternion rotations (using quat blending) */
2012-05-08 22:18:33 +02:00
static void pose_slide_apply_quat(tPoseSlideOp *pso, tPChanFCurveLink *pfl)
{
2012-05-08 22:18:33 +02:00
FCurve *fcu_w = NULL, *fcu_x = NULL, *fcu_y = NULL, *fcu_z = NULL;
bPoseChannel *pchan = pfl->pchan;
LinkData *ld = NULL;
char *path = NULL;
float cframe;
/* get the path to use - this should be quaternion rotations only (needs care) */
2012-05-08 22:18:33 +02:00
path = BLI_sprintfN("%s.%s", pfl->pchan_path, "rotation_quaternion");
/* get the current frame number */
2012-05-08 22:18:33 +02:00
cframe = (float)pso->cframe;
/* using this path, find each matching F-Curve for the variables we're interested in */
2012-05-08 22:18:33 +02:00
while ( (ld = poseAnim_mapping_getNextFCurve(&pfl->fcurves, ld, path)) ) {
FCurve *fcu = (FCurve *)ld->data;
/* assign this F-Curve to one of the relevant pointers... */
switch (fcu->array_index) {
case 3: /* z */
2012-05-08 22:18:33 +02:00
fcu_z = fcu;
break;
case 2: /* y */
2012-05-08 22:18:33 +02:00
fcu_y = fcu;
break;
case 1: /* x */
2012-05-08 22:18:33 +02:00
fcu_x = fcu;
break;
case 0: /* w */
2012-05-08 22:18:33 +02:00
fcu_w = fcu;
break;
}
}
/* only if all channels exist, proceed */
if (fcu_w && fcu_x && fcu_y && fcu_z) {
float quat_prev[4], quat_next[4];
/* get 2 quats */
quat_prev[0] = evaluate_fcurve(fcu_w, pso->prevFrame);
quat_prev[1] = evaluate_fcurve(fcu_x, pso->prevFrame);
quat_prev[2] = evaluate_fcurve(fcu_y, pso->prevFrame);
quat_prev[3] = evaluate_fcurve(fcu_z, pso->prevFrame);
quat_next[0] = evaluate_fcurve(fcu_w, pso->nextFrame);
quat_next[1] = evaluate_fcurve(fcu_x, pso->nextFrame);
quat_next[2] = evaluate_fcurve(fcu_y, pso->nextFrame);
quat_next[3] = evaluate_fcurve(fcu_z, pso->nextFrame);
/* perform blending */
if (pso->mode == POSESLIDE_BREAKDOWN) {
/* just perform the interpol between quat_prev and quat_next using pso->percentage as a guide */
interp_qt_qtqt(pchan->quat, quat_prev, quat_next, pso->percentage);
}
else if (pso->mode == POSESLIDE_PUSH) {
float quat_diff[4], quat_orig[4];
/* calculate the delta transform from the previous to the current */
/* TODO: investigate ways to favour one transform more? */
sub_qt_qtqt(quat_diff, pchan->quat, quat_prev);
/* make a copy of the original rotation */
copy_qt_qt(quat_orig, pchan->quat);
/* increase the original by the delta transform, by an amount determined by percentage */
add_qt_qtqt(pchan->quat, quat_orig, quat_diff, pso->percentage);
}
else {
float quat_interp[4], quat_orig[4];
int iters = (int)ceil(10.0f * pso->percentage); /* TODO: maybe a sensitivity ctrl on top of this is needed */
/* perform this blending several times until a satisfactory result is reached */
while (iters-- > 0) {
/* calculate the interpolation between the endpoints */
2012-05-08 22:18:33 +02:00
interp_qt_qtqt(quat_interp, quat_prev, quat_next, (cframe - pso->prevFrame) / (pso->nextFrame - pso->prevFrame));
/* make a copy of the original rotation */
copy_qt_qt(quat_orig, pchan->quat);
/* tricky interpolations - blending between original and new */
2012-05-08 22:18:33 +02:00
interp_qt_qtqt(pchan->quat, quat_orig, quat_interp, 1.0f / 6.0f);
}
}
}
/* free the path now */
MEM_freeN(path);
}
/* apply() - perform the pose sliding based on weighting various poses */
static void pose_slide_apply(bContext *C, tPoseSlideOp *pso)
{
tPChanFCurveLink *pfl;
/* sanitise the frame ranges */
if (pso->prevFrame == pso->nextFrame) {
/* move out one step either side */
pso->prevFrame--;
pso->nextFrame++;
}
/* for each link, handle each set of transforms */
2012-05-08 22:18:33 +02:00
for (pfl = pso->pfLinks.first; pfl; pfl = pfl->next) {
/* valid transforms for each PoseChannel should have been noted already
* - sliding the pose should be a straightforward exercise for location+rotation,
* but rotations get more complicated since we may want to use quaternion blending
* for quaternions instead...
*/
2012-05-08 22:18:33 +02:00
bPoseChannel *pchan = pfl->pchan;
if (pchan->flag & POSE_LOC) {
/* calculate these for the 'location' vector, and use location curves */
pose_slide_apply_vec3(pso, pfl, pchan->loc, "location");
}
if (pchan->flag & POSE_SIZE) {
/* calculate these for the 'scale' vector, and use scale curves */
pose_slide_apply_vec3(pso, pfl, pchan->size, "scale");
}
if (pchan->flag & POSE_ROT) {
/* everything depends on the rotation mode */
if (pchan->rotmode > 0) {
/* eulers - so calculate these for the 'eul' vector, and use euler_rotation curves */
pose_slide_apply_vec3(pso, pfl, pchan->eul, "rotation_euler");
}
else if (pchan->rotmode == ROT_MODE_AXISANGLE) {
2012-10-20 22:20:02 +02:00
/* TODO: need to figure out how to do this! */
}
else {
/* quaternions - use quaternion blending */
pose_slide_apply_quat(pso, pfl);
}
}
if (pfl->oldprops) {
/* not strictly a transform, but contributes to the pose produced in many rigs */
pose_slide_apply_props(pso, pfl);
}
}
/* depsgraph updates + redraws */
pose_slide_refresh(C, pso);
}
/* perform autokeyframing after changes were made + confirmed */
2012-05-08 22:18:33 +02:00
static void pose_slide_autoKeyframe(bContext *C, tPoseSlideOp *pso)
{
/* wrapper around the generic call */
poseAnim_mapping_autoKeyframe(C, pso->scene, pso->ob, &pso->pfLinks, (float)pso->cframe);
}
/* reset changes made to current pose */
2012-05-08 22:18:33 +02:00
static void pose_slide_reset(tPoseSlideOp *pso)
{
/* wrapper around the generic call, so that custom stuff can be added later */
poseAnim_mapping_reset(&pso->pfLinks);
}
/* ------------------------------------ */
/* draw percentage indicator in header */
2012-05-08 22:18:33 +02:00
static void pose_slide_draw_status(tPoseSlideOp *pso)
{
char status_str[UI_MAX_DRAW_STR];
char mode_str[32];
switch (pso->mode) {
case POSESLIDE_PUSH:
strcpy(mode_str, "Push Pose");
break;
case POSESLIDE_RELAX:
strcpy(mode_str, "Relax Pose");
break;
case POSESLIDE_BREAKDOWN:
strcpy(mode_str, "Breakdown");
break;
default:
2012-10-20 22:20:02 +02:00
/* unknown */
strcpy(mode_str, "Sliding-Tool");
break;
}
if (hasNumInput(&pso->num)) {
Scene *scene = pso->scene;
char str_offs[NUM_STR_REP_LEN];
outputNumInput(&pso->num, str_offs, &scene->unit);
BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, str_offs);
}
else {
BLI_snprintf(status_str, sizeof(status_str), "%s: %d %%", mode_str, (int)(pso->percentage * 100.0f));
}
ED_area_headerprint(pso->sa, status_str);
}
/* common code for invoke() methods */
2012-05-08 22:18:33 +02:00
static int pose_slide_invoke_common(bContext *C, wmOperator *op, tPoseSlideOp *pso)
{
tPChanFCurveLink *pfl;
2012-05-08 22:18:33 +02:00
AnimData *adt = pso->ob->adt;
wmWindow *win = CTX_wm_window(C);
/* for each link, add all its keyframes to the search tree */
2012-05-08 22:18:33 +02:00
for (pfl = pso->pfLinks.first; pfl; pfl = pfl->next) {
LinkData *ld;
/* do this for each F-Curve */
2012-05-08 22:18:33 +02:00
for (ld = pfl->fcurves.first; ld; ld = ld->next) {
FCurve *fcu = (FCurve *)ld->data;
fcurve_to_keylist(adt, fcu, &pso->keys, NULL);
}
}
/* consolidate these keyframes, and figure out the nearest ones */
BLI_dlrbTree_linkedlist_sync(&pso->keys);
2012-05-08 22:18:33 +02:00
/* cancel if no keyframes found... */
if (pso->keys.root) {
ActKeyColumn *ak;
2012-05-08 22:18:33 +02:00
float cframe = (float)pso->cframe;
/* firstly, check if the current frame is a keyframe... */
2012-05-08 22:18:33 +02:00
ak = (ActKeyColumn *)BLI_dlrbTree_search_exact(&pso->keys, compare_ak_cfraPtr, &cframe);
if (ak == NULL) {
/* current frame is not a keyframe, so search */
2012-05-08 22:18:33 +02:00
ActKeyColumn *pk = (ActKeyColumn *)BLI_dlrbTree_search_prev(&pso->keys, compare_ak_cfraPtr, &cframe);
ActKeyColumn *nk = (ActKeyColumn *)BLI_dlrbTree_search_next(&pso->keys, compare_ak_cfraPtr, &cframe);
/* new set the frames */
2012-05-08 22:18:33 +02:00
/* prev frame */
pso->prevFrame = (pk) ? (pk->cfra) : (pso->cframe - 1);
RNA_int_set(op->ptr, "prev_frame", pso->prevFrame);
2012-05-08 22:18:33 +02:00
/* next frame */
pso->nextFrame = (nk) ? (nk->cfra) : (pso->cframe + 1);
RNA_int_set(op->ptr, "next_frame", pso->nextFrame);
}
else {
/* current frame itself is a keyframe, so just take keyframes on either side */
2012-05-08 22:18:33 +02:00
/* prev frame */
pso->prevFrame = (ak->prev) ? (ak->prev->cfra) : (pso->cframe - 1);
RNA_int_set(op->ptr, "prev_frame", pso->prevFrame);
2012-05-08 22:18:33 +02:00
/* next frame */
pso->nextFrame = (ak->next) ? (ak->next->cfra) : (pso->cframe + 1);
RNA_int_set(op->ptr, "next_frame", pso->nextFrame);
}
}
else {
BKE_report(op->reports, RPT_ERROR, "No keyframes to slide between");
pose_slide_exit(op);
return OPERATOR_CANCELLED;
}
/* initial apply for operator... */
2012-10-20 22:20:02 +02:00
/* TODO: need to calculate percentage for initial round too... */
pose_slide_apply(C, pso);
/* depsgraph updates + redraws */
pose_slide_refresh(C, pso);
/* set cursor to indicate modal */
WM_cursor_modal_set(win, BC_EW_SCROLLCURSOR);
/* header print */
pose_slide_draw_status(pso);
/* add a modal handler for this operator */
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
/* calculate percentage based on position of mouse (we only use x-axis for now.
* since this is more convenient for users to do), and store new percentage value
*/
static void pose_slide_mouse_update_percentage(tPoseSlideOp *pso, wmOperator *op, const wmEvent *event)
{
pso->percentage = (event->x - pso->ar->winrct.xmin) / ((float)pso->ar->winx);
RNA_float_set(op->ptr, "percentage", pso->percentage);
}
/* common code for modal() */
static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
2012-05-08 22:18:33 +02:00
tPoseSlideOp *pso = op->customdata;
wmWindow *win = CTX_wm_window(C);
const bool has_numinput = hasNumInput(&pso->num);
switch (event->type) {
2012-05-08 22:18:33 +02:00
case LEFTMOUSE: /* confirm */
case RETKEY:
{
/* return to normal cursor and header status */
ED_area_headerprint(pso->sa, NULL);
WM_cursor_modal_restore(win);
/* insert keyframes as required... */
pose_slide_autoKeyframe(C, pso);
pose_slide_exit(op);
/* done! */
return OPERATOR_FINISHED;
}
2012-05-08 22:18:33 +02:00
case ESCKEY: /* cancel */
case RIGHTMOUSE:
{
/* return to normal cursor and header status */
ED_area_headerprint(pso->sa, NULL);
WM_cursor_modal_restore(win);
/* reset transforms back to original state */
pose_slide_reset(pso);
/* depsgraph updates + redraws */
pose_slide_refresh(C, pso);
/* clean up temp data */
pose_slide_exit(op);
2012-03-08 05:12:11 +01:00
/* canceled! */
return OPERATOR_CANCELLED;
}
case MOUSEMOVE: /* calculate new position */
{
/* only handle mousemove if not doing numinput */
if (has_numinput == false) {
/* update percentage based on position of mouse */
pose_slide_mouse_update_percentage(pso, op, event);
/* update percentage indicator in header */
pose_slide_draw_status(pso);
/* reset transforms (to avoid accumulation errors) */
pose_slide_reset(pso);
/* apply... */
pose_slide_apply(C, pso);
}
break;
}
default:
if ((event->val == KM_PRESS) && handleNumInput(C, &pso->num, event)) {
float value;
/* Grab percentage from numeric input, and store this new value for redo
* NOTE: users see ints, while internally we use a 0-1 float
*/
value = pso->percentage * 100.0f;
applyNumInput(&pso->num, &value);
pso->percentage = value / 100.0f;
CLAMP(pso->percentage, 0.0f, 1.0f);
RNA_float_set(op->ptr, "percentage", pso->percentage);
/* update percentage indicator in header */
pose_slide_draw_status(pso);
/* reset transforms (to avoid accumulation errors) */
pose_slide_reset(pso);
/* apply... */
pose_slide_apply(C, pso);
break;
}
else {
/* unhandled event - maybe it was some view manip? */
/* allow to pass through */
return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
}
}
/* still running... */
return OPERATOR_RUNNING_MODAL;
}
/* common code for cancel() */
static void pose_slide_cancel(bContext *UNUSED(C), wmOperator *op)
{
/* cleanup and done */
pose_slide_exit(op);
}
/* common code for exec() methods */
2012-05-08 22:18:33 +02:00
static int pose_slide_exec_common(bContext *C, wmOperator *op, tPoseSlideOp *pso)
{
/* settings should have been set up ok for applying, so just apply! */
pose_slide_apply(C, pso);
/* insert keyframes if needed */
pose_slide_autoKeyframe(C, pso);
/* cleanup and done */
pose_slide_exit(op);
return OPERATOR_FINISHED;
}
/* common code for defining RNA properties */
2012-05-08 22:18:33 +02:00
static void pose_slide_opdef_properties(wmOperatorType *ot)
{
RNA_def_int(ot->srna, "prev_frame", 0, MINAFRAME, MAXFRAME, "Previous Keyframe", "Frame number of keyframe immediately before the current frame", 0, 50);
RNA_def_int(ot->srna, "next_frame", 0, MINAFRAME, MAXFRAME, "Next Keyframe", "Frame number of keyframe immediately after the current frame", 0, 50);
RNA_def_float_percentage(ot->srna, "percentage", 0.5f, 0.0f, 1.0f, "Percentage", "Weighting factor for the sliding operation", 0.3, 0.7);
}
/* ------------------------------------ */
/* invoke() - for 'push' mode */
static int pose_slide_push_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
tPoseSlideOp *pso;
/* initialize data */
if (pose_slide_init(C, op, POSESLIDE_PUSH) == 0) {
pose_slide_exit(op);
return OPERATOR_CANCELLED;
}
else
2012-05-08 22:18:33 +02:00
pso = op->customdata;
/* initialise percentage so that it won't pop on first mouse move */
pose_slide_mouse_update_percentage(pso, op, event);
/* do common setup work */
return pose_slide_invoke_common(C, op, pso);
}
/* exec() - for push */
2012-05-08 22:18:33 +02:00
static int pose_slide_push_exec(bContext *C, wmOperator *op)
{
tPoseSlideOp *pso;
/* initialize data (from RNA-props) */
if (pose_slide_init(C, op, POSESLIDE_PUSH) == 0) {
pose_slide_exit(op);
return OPERATOR_CANCELLED;
}
else
2012-05-08 22:18:33 +02:00
pso = op->customdata;
/* do common exec work */
return pose_slide_exec_common(C, op, pso);
}
void POSE_OT_push(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Push Pose";
ot->idname = "POSE_OT_push";
ot->description = "Exaggerate the current pose";
/* callbacks */
ot->exec = pose_slide_push_exec;
ot->invoke = pose_slide_push_invoke;
ot->modal = pose_slide_modal;
ot->cancel = pose_slide_cancel;
ot->poll = ED_operator_posemode;
/* flags */
2012-05-08 22:18:33 +02:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
/* Properties */
pose_slide_opdef_properties(ot);
}
/* ........................ */
/* invoke() - for 'relax' mode */
static int pose_slide_relax_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
tPoseSlideOp *pso;
/* initialize data */
if (pose_slide_init(C, op, POSESLIDE_RELAX) == 0) {
pose_slide_exit(op);
return OPERATOR_CANCELLED;
}
else
2012-05-08 22:18:33 +02:00
pso = op->customdata;
/* initialise percentage so that it won't pop on first mouse move */
pose_slide_mouse_update_percentage(pso, op, event);
/* do common setup work */
return pose_slide_invoke_common(C, op, pso);
}
/* exec() - for relax */
2012-05-08 22:18:33 +02:00
static int pose_slide_relax_exec(bContext *C, wmOperator *op)
{
tPoseSlideOp *pso;
/* initialize data (from RNA-props) */
if (pose_slide_init(C, op, POSESLIDE_RELAX) == 0) {
pose_slide_exit(op);
return OPERATOR_CANCELLED;
}
else
2012-05-08 22:18:33 +02:00
pso = op->customdata;
/* do common exec work */
return pose_slide_exec_common(C, op, pso);
}
void POSE_OT_relax(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Relax Pose";
ot->idname = "POSE_OT_relax";
ot->description = "Make the current pose more similar to its surrounding ones";
/* callbacks */
ot->exec = pose_slide_relax_exec;
ot->invoke = pose_slide_relax_invoke;
ot->modal = pose_slide_modal;
ot->cancel = pose_slide_cancel;
ot->poll = ED_operator_posemode;
/* flags */
2012-05-08 22:18:33 +02:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
/* Properties */
pose_slide_opdef_properties(ot);
}
/* ........................ */
/* invoke() - for 'breakdown' mode */
static int pose_slide_breakdown_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
tPoseSlideOp *pso;
/* initialize data */
if (pose_slide_init(C, op, POSESLIDE_BREAKDOWN) == 0) {
pose_slide_exit(op);
return OPERATOR_CANCELLED;
}
else
2012-05-08 22:18:33 +02:00
pso = op->customdata;
/* initialise percentage so that it won't pop on first mouse move */
pose_slide_mouse_update_percentage(pso, op, event);
/* do common setup work */
return pose_slide_invoke_common(C, op, pso);
}
/* exec() - for breakdown */
2012-05-08 22:18:33 +02:00
static int pose_slide_breakdown_exec(bContext *C, wmOperator *op)
{
tPoseSlideOp *pso;
/* initialize data (from RNA-props) */
if (pose_slide_init(C, op, POSESLIDE_BREAKDOWN) == 0) {
pose_slide_exit(op);
return OPERATOR_CANCELLED;
}
else
2012-05-08 22:18:33 +02:00
pso = op->customdata;
/* do common exec work */
return pose_slide_exec_common(C, op, pso);
}
void POSE_OT_breakdown(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Pose Breakdowner";
ot->idname = "POSE_OT_breakdown";
ot->description = "Create a suitable breakdown pose on the current frame";
/* callbacks */
ot->exec = pose_slide_breakdown_exec;
ot->invoke = pose_slide_breakdown_invoke;
ot->modal = pose_slide_modal;
ot->cancel = pose_slide_cancel;
ot->poll = ED_operator_posemode;
/* flags */
2012-05-08 22:18:33 +02:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
/* Properties */
pose_slide_opdef_properties(ot);
}
/* **************************************************** */
/* B) Pose Propagate */
/* "termination conditions" - i.e. when we stop */
typedef enum ePosePropagate_Termination {
2012-05-08 22:18:33 +02:00
/* stop after the current hold ends */
POSE_PROPAGATE_SMART_HOLDS = 0,
2012-05-08 22:18:33 +02:00
/* only do on the last keyframe */
POSE_PROPAGATE_LAST_KEY,
2012-05-08 22:18:33 +02:00
/* stop after the next keyframe */
POSE_PROPAGATE_NEXT_KEY,
2012-05-08 22:18:33 +02:00
/* stop after the specified frame */
POSE_PROPAGATE_BEFORE_FRAME,
2012-05-08 22:18:33 +02:00
/* stop when we run out of keyframes */
POSE_PROPAGATE_BEFORE_END,
/* only do on keyframes that are selected */
POSE_PROPAGATE_SELECTED_KEYS,
2012-05-08 22:18:33 +02:00
/* only do on the frames where markers are selected */
POSE_PROPAGATE_SELECTED_MARKERS
} ePosePropagate_Termination;
/* termination data needed for some modes - assumes only one of these entries will be needed at a time */
typedef union tPosePropagate_ModeData {
/* smart holds + before frame: frame number to stop on */
float end_frame;
/* selected markers: listbase for CfraElem's marking these frames */
ListBase sel_markers;
} tPosePropagate_ModeData;
/* --------------------------------- */
/* 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
2012-05-08 22:18:33 +02:00
* if this happens to be a major issue, scrap this, and just make this happen
* independently per F-Curve
*/
2012-05-08 22:18:33 +02:00
static float pose_propagate_get_boneHoldEndFrame(Object *ob, tPChanFCurveLink *pfl, float startFrame)
{
DLRBT_Tree keys, blocks;
ActKeyBlock *ab;
2012-05-08 22:18:33 +02:00
AnimData *adt = ob->adt;
LinkData *ld;
float endFrame = startFrame;
/* set up optimized 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:
2012-05-08 22:18:33 +02:00
* 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 bool pose_propagate_get_refVal(Object *ob, FCurve *fcu, float *value)
{
PointerRNA id_ptr, ptr;
PropertyRNA *prop;
bool found = false;
/* base pointer is always the object -> id_ptr */
RNA_id_pointer_create(&ob->id, &id_ptr);
/* resolve the property... */
if (RNA_path_resolve_property(&id_ptr, fcu->rna_path, &ptr, &prop)) {
if (RNA_property_array_check(prop)) {
/* array */
if (fcu->array_index < RNA_property_array_length(&ptr, prop)) {
found = true;
switch (RNA_property_type(prop)) {
case PROP_BOOLEAN:
2012-05-08 22:18:33 +02:00
*value = (float)RNA_property_boolean_get_index(&ptr, prop, fcu->array_index);
break;
case PROP_INT:
2012-05-08 22:18:33 +02:00
*value = (float)RNA_property_int_get_index(&ptr, prop, fcu->array_index);
break;
case PROP_FLOAT:
2012-05-08 22:18:33 +02:00
*value = RNA_property_float_get_index(&ptr, prop, fcu->array_index);
break;
default:
found = false;
break;
}
}
}
else {
/* not an array */
found = true;
switch (RNA_property_type(prop)) {
case PROP_BOOLEAN:
2012-05-08 22:18:33 +02:00
*value = (float)RNA_property_boolean_get(&ptr, prop);
break;
case PROP_INT:
2012-05-08 22:18:33 +02:00
*value = (float)RNA_property_int_get(&ptr, prop);
break;
case PROP_ENUM:
2012-05-08 22:18:33 +02:00
*value = (float)RNA_property_enum_get(&ptr, prop);
break;
case PROP_FLOAT:
2012-05-08 22:18:33 +02:00
*value = RNA_property_float_get(&ptr, prop);
break;
default:
found = false;
break;
}
}
}
return found;
}
/* propagate just works along each F-Curve in turn */
2012-05-08 22:18:33 +02:00
static void pose_propagate_fcurve(wmOperator *op, Object *ob, FCurve *fcu,
float startFrame, tPosePropagate_ModeData modeData)
{
const int mode = RNA_enum_get(op->ptr, "mode");
BezTriple *bezt;
float refVal = 0.0f;
bool keyExists;
int i, match;
2012-05-08 22:18:33 +02:00
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)
*/
2012-05-08 22:18:33 +02:00
if (!pose_propagate_get_refVal(ob, fcu, &refVal))
return;
/* 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
2012-05-08 22:18:33 +02:00
* - if starting before the starting frame, don't touch the key, as it may have had some valid
* values
* - if only doing selected keyframes, start from the first one
*/
if (mode != POSE_PROPAGATE_SELECTED_KEYS) {
match = binarysearch_bezt_index(fcu->bezt, startFrame, fcu->totvert, &keyExists);
if (fcu->bezt[match].vec[1][0] < startFrame)
i = match + 1;
else
i = match;
}
else {
/* selected - start from first keyframe */
i = 0;
}
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] > modeData.end_frame)
break;
}
else if (mode == POSE_PROPAGATE_NEXT_KEY) {
/* stop after the first keyframe has been processed */
if (first == 0)
break;
}
else if (mode == POSE_PROPAGATE_LAST_KEY) {
/* only affect this frame if it will be the last one */
2012-05-08 22:18:33 +02:00
if (i != (fcu->totvert - 1))
continue;
}
else if (mode == POSE_PROPAGATE_SELECTED_MARKERS) {
/* only allow if there's a marker on this frame */
CfraElem *ce = NULL;
/* stop on matching marker if there is one */
for (ce = modeData.sel_markers.first; ce; ce = ce->next) {
if (ce->cfra == iroundf(bezt->vec[1][0]))
break;
}
/* skip this keyframe if no marker */
if (ce == NULL)
continue;
}
else if (mode == POSE_PROPAGATE_SELECTED_KEYS) {
/* only allow if this keyframe is already selected - skip otherwise */
if (BEZT_ISSEL_ANY(bezt) == 0)
continue;
}
/* just flatten handles, since values will now be the same either side... */
2012-10-20 22:20:02 +02:00
/* 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;
}
}
/* --------------------------------- */
2012-05-08 22:18:33 +02:00
static int pose_propagate_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
2012-05-08 22:18:33 +02:00
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
bAction *act = (ob && ob->adt) ? ob->adt->action : NULL;
ListBase pflinks = {NULL, NULL};
tPChanFCurveLink *pfl;
tPosePropagate_ModeData modeData;
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);
/* mode-specific data preprocessing (requiring no access to curves) */
if (mode == POSE_PROPAGATE_SELECTED_MARKERS) {
/* get a list of selected markers */
ED_markers_make_cfra_list(&scene->markers, &modeData.sel_markers, SELECT);
}
else {
/* assume everything else wants endFrame */
modeData.end_frame = RNA_float_get(op->ptr, "end_frame");
}
/* 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
*/
modeData.end_frame = pose_propagate_get_boneHoldEndFrame(ob, pfl, (float)CFRA);
}
/* go through propagating pose to keyframes, curve by curve */
2012-05-08 22:18:33 +02:00
for (ld = pfl->fcurves.first; ld; ld = ld->next)
pose_propagate_fcurve(op, ob, (FCurve *)ld->data, (float)CFRA, modeData);
}
/* free temp data */
poseAnim_mapping_free(&pflinks);
if (mode == POSE_PROPAGATE_SELECTED_MARKERS)
BLI_freelistN(&modeData.sel_markers);
/* updates + notifiers */
poseAnim_mapping_refresh(C, scene, ob);
return OPERATOR_FINISHED;
}
/* --------------------------------- */
void POSE_OT_propagate(wmOperatorType *ot)
{
2012-05-08 22:18:33 +02:00
static EnumPropertyItem terminate_items[] = {
{POSE_PROPAGATE_SMART_HOLDS, "WHILE_HELD", 0, "While Held",
"Propagate pose to all keyframes after current frame that don't change (Default behavior)"},
{POSE_PROPAGATE_NEXT_KEY, "NEXT_KEY", 0, "To Next Keyframe",
"Propagate pose to first keyframe following the current frame only"},
{POSE_PROPAGATE_LAST_KEY, "LAST_KEY", 0, "To Last Keyframe",
"Propagate pose to the last keyframe only (i.e. making action cyclic)"},
{POSE_PROPAGATE_BEFORE_FRAME, "BEFORE_FRAME", 0, "Before Frame",
"Propagate pose to all keyframes between current frame and 'Frame' property"},
{POSE_PROPAGATE_BEFORE_END, "BEFORE_END", 0, "Before Last Keyframe",
"Propagate pose to all keyframes from current frame until no more are found"},
{POSE_PROPAGATE_SELECTED_KEYS, "SELECTED_KEYS", 0, "On Selected Keyframes",
"Propagate pose to all selected keyframes"},
{POSE_PROPAGATE_SELECTED_MARKERS, "SELECTED_MARKERS", 0, "On Selected Markers",
"Propagate pose to all keyframes occurring on frames with Scene Markers after the current frame"},
{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;
2012-10-20 22:20:02 +02:00
ot->poll = ED_operator_posemode; /* XXX: needs selected bones! */
/* flag */
2012-05-08 22:18:33 +02:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
2012-10-20 22:20:02 +02:00
/* 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 (for 'Before Frame' mode)", 1.0, 250.0);
}
/* **************************************************** */