Animation: Equalize Handle Operator

The Equalize Handles operator allows users to make selected handle
lengths uniform: either respecting their original angle from the key
control point or by flattening their angle (removing the overshoot
sometimes produced by certain handle types).

Design: T94172

Reviewed by: sybren

Differential Revision: https://developer.blender.org/D13702
This commit is contained in:
Kevin C. Burke 2022-01-25 11:40:46 +01:00 committed by Sybren A. Stüvel
parent a000de7c2a
commit 17b0c06946
6 changed files with 183 additions and 0 deletions

View File

@ -335,6 +335,7 @@ class GRAPH_MT_key_snap(Menu):
layout.operator("graph.snap", text="Selection to Nearest Second").type = 'NEAREST_SECOND'
layout.operator("graph.snap", text="Selection to Nearest Marker").type = 'NEAREST_MARKER'
layout.operator("graph.snap", text="Flatten Handles").type = 'HORIZONTAL'
layout.operator("graph.equalize_handles", text="Equalize Handles").side = 'BOTH'
layout.separator()
layout.operator("graph.frame_jump", text="Cursor to Selection")
layout.operator("graph.snap_cursor_value", text="Cursor Value to Selection")

View File

@ -1283,6 +1283,61 @@ static short set_bezt_sine(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
return 0;
}
static void handle_flatten(float vec[3][3], const int idx, const float direction[2])
{
BLI_assert_msg(idx == 0 || idx == 2, "handle_flatten() expects a handle index");
add_v2_v2v2(vec[idx], vec[1], direction);
}
static void handle_set_length(float vec[3][3], const int idx, const float handle_length)
{
BLI_assert_msg(idx == 0 || idx == 2, "handle_set_length() expects a handle index");
float handle_direction[2];
sub_v2_v2v2(handle_direction, vec[idx], vec[1]);
normalize_v2_length(handle_direction, handle_length);
add_v2_v2v2(vec[idx], vec[1], handle_direction);
}
void ANIM_fcurve_equalize_keyframes_loop(FCurve *fcu,
const eEditKeyframes_Equalize mode,
const float handle_length,
const bool flatten)
{
uint i;
BezTriple *bezt;
const float flat_direction_left[2] = {-handle_length, 0.f};
const float flat_direction_right[2] = {handle_length, 0.f};
/* Loop through an F-Curves keyframes. */
for (bezt = fcu->bezt, i = 0; i < fcu->totvert; bezt++, i++) {
if ((bezt->f2 & SELECT) == 0) {
continue;
}
/* Perform handle equalization if mode is 'Both' or 'Left'. */
if (mode & EQUALIZE_HANDLES_LEFT) {
if (flatten) {
handle_flatten(bezt->vec, 0, flat_direction_left);
}
else {
handle_set_length(bezt->vec, 0, handle_length);
}
}
/* Perform handle equalization if mode is 'Both' or 'Right'. */
if (mode & EQUALIZE_HANDLES_RIGHT) {
if (flatten) {
handle_flatten(bezt->vec, 2, flat_direction_right);
}
else {
handle_set_length(bezt->vec, 2, handle_length);
}
}
}
}
KeyframeEditFunc ANIM_editkeyframes_ipo(short mode)
{
switch (mode) {

View File

@ -93,6 +93,13 @@ typedef enum eEditKeyframes_Snap {
SNAP_KEYS_TIME,
} eEditKeyframes_Snap;
/* equalizing tools */
typedef enum eEditKeyframes_Equalize {
EQUALIZE_HANDLES_LEFT = (1 << 0),
EQUALIZE_HANDLES_RIGHT = (1 << 1),
EQUALIZE_HANDLES_BOTH = (EQUALIZE_HANDLES_LEFT | EQUALIZE_HANDLES_RIGHT),
} eEditKeyframes_Equalize;
/* mirroring tools */
typedef enum eEditKeyframes_Mirror {
MIRROR_KEYS_CURFRAME = 1,
@ -258,6 +265,18 @@ short ANIM_fcurve_keyframes_loop(KeyframeEditData *ked,
KeyframeEditFunc key_ok,
KeyframeEditFunc key_cb,
FcuEditFunc fcu_cb);
/**
* Sets selected keyframes' bezier handles to an equal length and optionally makes
* the keyframes' handles horizontal.
* \param handle_length: Desired handle length, must be positive.
* \param flatten: Makes the keyframes' handles the same value as the keyframe,
* flattening the curve at that point.
*/
void ANIM_fcurve_equalize_keyframes_loop(struct FCurve *fcu,
eEditKeyframes_Equalize mode,
float handle_length,
bool flatten);
/**
* Function for working with any type (i.e. one of the known types) of animation channel.
*/

View File

@ -2352,6 +2352,103 @@ void GRAPH_OT_snap(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Equalize Handles Operator
* \{ */
/* Defines for equalize handles tool. */
static const EnumPropertyItem prop_graphkeys_equalize_handles_sides[] = {
{GRAPHKEYS_EQUALIZE_LEFT, "LEFT", 0, "Left", "Equalize selected keyframes' left handles"},
{GRAPHKEYS_EQUALIZE_RIGHT, "RIGHT", 0, "Right", "Equalize selected keyframes' right handles"},
{GRAPHKEYS_EQUALIZE_BOTH, "BOTH", 0, "Both", "Equalize both of a keyframe's handles"},
{0, NULL, 0, NULL, NULL},
};
/* ------------------- */
/* Equalize selected keyframes' bezier handles. */
static void equalize_graph_keys(bAnimContext *ac, int mode, float handle_length, bool flatten)
{
/* Filter data. */
const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT |
ANIMFILTER_NODUPLIS);
ListBase anim_data = {NULL, NULL};
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* Equalize keyframes. */
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
ANIM_fcurve_equalize_keyframes_loop(ale->key_data, mode, handle_length, flatten);
ale->update |= ANIM_UPDATE_DEFAULT;
}
ANIM_animdata_update(ac, &anim_data);
ANIM_animdata_freelist(&anim_data);
}
static int graphkeys_equalize_handles_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
/* Get editor data. */
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
/* Get equalize mode. */
int mode = RNA_enum_get(op->ptr, "side");
float handle_length = RNA_float_get(op->ptr, "handle_length");
bool flatten = RNA_boolean_get(op->ptr, "flatten");
/* Equalize graph keyframes. */
equalize_graph_keys(&ac, mode, handle_length, flatten);
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GRAPH_OT_equalize_handles(wmOperatorType *ot)
{
/* Identifiers */
ot->name = "Equalize Handles";
ot->idname = "GRAPH_OT_equalize_handles";
ot->description =
"Ensure selected keyframes' handles have equal length, optionally making them horizontal";
/* API callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = graphkeys_equalize_handles_exec;
ot->poll = graphop_editable_keyframes_poll;
/* Flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties */
ot->prop = RNA_def_enum(ot->srna,
"side",
prop_graphkeys_equalize_handles_sides,
0,
"Side",
"Side of the keyframes' bezier handles to affect");
RNA_def_float(ot->srna,
"handle_length",
5.0f,
0.1f,
FLT_MAX,
"Handle Length",
"Length to make selected keyframes' bezier handles",
1.0f,
50.0f);
RNA_def_boolean(
ot->srna,
"flatten",
false,
"Flatten",
"Make the values of the selected keyframes' handles the same as their respective keyframes");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mirror Keyframes Operator
* \{ */

View File

@ -144,6 +144,7 @@ void GRAPH_OT_easing_type(struct wmOperatorType *ot);
void GRAPH_OT_frame_jump(struct wmOperatorType *ot);
void GRAPH_OT_snap_cursor_value(struct wmOperatorType *ot);
void GRAPH_OT_snap(struct wmOperatorType *ot);
void GRAPH_OT_equalize_handles(struct wmOperatorType *ot);
void GRAPH_OT_mirror(struct wmOperatorType *ot);
/* defines for snap keyframes
@ -158,6 +159,15 @@ enum eGraphKeys_Snap_Mode {
GRAPHKEYS_SNAP_VALUE,
};
/* Defines for equalize keyframe handles.
* NOTE: Keep in sync with eEditKeyframes_Equalize (in ED_keyframes_edit.h).
*/
enum eGraphKeys_Equalize_Mode {
GRAPHKEYS_EQUALIZE_LEFT = 1,
GRAPHKEYS_EQUALIZE_RIGHT,
GRAPHKEYS_EQUALIZE_BOTH,
};
/* defines for mirror keyframes
* NOTE: keep in sync with eEditKeyframes_Mirror (in ED_keyframes_edit.h)
*/

View File

@ -456,6 +456,7 @@ void graphedit_operatortypes(void)
/* editing */
WM_operatortype_append(GRAPH_OT_snap);
WM_operatortype_append(GRAPH_OT_equalize_handles);
WM_operatortype_append(GRAPH_OT_mirror);
WM_operatortype_append(GRAPH_OT_frame_jump);
WM_operatortype_append(GRAPH_OT_snap_cursor_value);