Gooseberry animation request: Paste flipped pose in action

and graph editor.

This was a tricky commit that was not so straightforward to make work.
The information for bones is not easy to come by in the animation curves,
however we do have some string manipulation tricks to make it happen.

Testing in gooseberry worked for the rigs there, commiting to master now
This commit is contained in:
Antonis Ryakiotakis 2015-01-07 22:25:33 +01:00
parent 95847f6ac7
commit 2f16098d20
10 changed files with 170 additions and 17 deletions

View File

@ -131,6 +131,8 @@ class DOPESHEET_HT_header(Header):
row = layout.row(align=True)
row.operator("action.copy", text="", icon='COPYDOWN')
row.operator("action.paste", text="", icon='PASTEDOWN')
row.operator("action.paste", text="", icon='PASTEFLIPDOWN').flipped = True
class DOPESHEET_MT_editor_menus(Menu):

View File

@ -52,6 +52,7 @@ class GRAPH_HT_header(Header):
row = layout.row(align=True)
row.operator("graph.copy", text="", icon='COPYDOWN')
row.operator("graph.paste", text="", icon='PASTEDOWN')
row.operator("graph.paste", text="", icon='PASTEFLIPDOWN').flipped = True
row = layout.row(align=True)
if st.has_ghost_curves:

View File

@ -85,6 +85,9 @@ int BLI_str_rstrip_float_zero(char *str, const char pad) ATTR_NONNULL();
int BLI_str_index_in_array_n(const char *__restrict str, const char **__restrict str_array, const int str_array_len) ATTR_NONNULL();
int BLI_str_index_in_array(const char *__restrict str, const char **__restrict str_array) ATTR_NONNULL();
bool BLI_str_ends_with(const char *str,const char *end) ATTR_NONNULL();
bool BLI_strn_ends_with(const char *str,const char *end, int length) ATTR_NONNULL();
size_t BLI_str_partition(const char *str, const char delim[], char **sep, char **suf) ATTR_NONNULL();
size_t BLI_str_rpartition(const char *str, const char delim[], char **sep, char **suf) ATTR_NONNULL();
size_t BLI_str_partition_ex(const char *str, const char delim[], char **sep, char **suf, const bool from_right) ATTR_NONNULL();

View File

@ -773,6 +773,35 @@ int BLI_str_index_in_array(const char *__restrict str, const char **__restrict s
return -1;
}
bool BLI_strn_ends_with(const char *str,const char *end, int slength)
{
size_t elength = strlen(end);
if (elength < slength) {
const char *iter = str + slength - elength;
while (*iter) {
if (*iter++ != *end++) {
return false;
}
}
return true;
}
return false;
}
/**
* Find if a string ends with another string.
*
* \param str The string to search within.
* \param end The string we look for at the end.
* \return If str ends with end.
*/
bool BLI_str_ends_with(const char *str,const char *end)
{
size_t slength = strlen(str);
return BLI_strn_ends_with(str, end, slength);
}
/**
* Find the first char matching one of the chars in \a delim, from left.
*

View File

@ -43,10 +43,12 @@
#include "DNA_scene_types.h"
#include "BKE_action.h"
#include "BKE_fcurve.h"
#include "BKE_report.h"
#include "BKE_library.h"
#include "BKE_global.h"
#include "BKE_deform.h"
#include "RNA_access.h"
@ -477,6 +479,7 @@ typedef struct tAnimCopybufItem {
BezTriple *bezt; /* keyframes in buffer */
short id_type; /* Result of GS(id->name)*/
bool is_bone; /* special flag for armature bones */
} tAnimCopybufItem;
@ -540,6 +543,31 @@ short copy_animedit_keys(bAnimContext *ac, ListBase *anim_data)
aci->grp = fcu->grp;
aci->rna_path = MEM_dupallocN(fcu->rna_path);
aci->array_index = fcu->array_index;
/* detect if this is a bone. We do that here rather than during pasting because ID pointers will get invalidated if we undo.
* storing the relavant information here helps avoiding crashes if we undo-repaste */
if ((aci->id_type == ID_OB) && (((Object *)aci->id)->type == OB_ARMATURE) && aci->rna_path) {
Object *ob = (Object *)aci->id;
char *str_start;
if ((str_start = strstr(aci->rna_path, "pose.bones["))) {
bPoseChannel *pchan;
int length = 0;
char *str_end;
str_start += 12;
str_end = strchr(str_start, '\"');
length = str_end - str_start;
str_start[length] = 0;
pchan = BKE_pose_channel_find_name(ob->pose, str_start);
str_start[length] = '\"';
if (pchan) {
aci->is_bone = true;
}
}
}
BLI_addtail(&animcopybuf, aci);
/* add selected keyframes to buffer */
@ -587,19 +615,64 @@ short copy_animedit_keys(bAnimContext *ac, ListBase *anim_data)
return 0;
}
static void flip_names(tAnimCopybufItem *aci, char **name) {
if (aci->is_bone) {
char *str_start;
if ((str_start = strstr(aci->rna_path, "pose.bones["))) {
/* ninja coding, try to change the name */
char bname_new[MAX_VGROUP_NAME];
char *str_iter, *str_end;
int length, prefix_l, postfix_l;
str_start += 12;
prefix_l = str_start - aci->rna_path;
str_end = strchr(str_start, '\"');
length = str_end - str_start;
postfix_l = strlen(str_end);
/* more ninja stuff, temporary substitute with NULL terminator */
str_start[length] = 0;
BKE_deform_flip_side_name(bname_new, str_start, false);
str_start[length] = '\"';
str_iter = *name = MEM_mallocN(sizeof(char) * (prefix_l + postfix_l + length + 1), "flipped_path");
BLI_strncpy(str_iter, aci->rna_path, prefix_l + 1);
str_iter += prefix_l ;
BLI_strncpy(str_iter, bname_new, length + 1);
str_iter += length;
BLI_strncpy(str_iter, str_end, postfix_l + 1);
str_iter[postfix_l] = 0;
}
}
}
/* ------------------- */
/* most strict method: exact matches only */
static tAnimCopybufItem *pastebuf_match_path_full(FCurve *fcu, const short from_single, const short to_simple)
static tAnimCopybufItem *pastebuf_match_path_full(FCurve *fcu, const short from_single, const short to_simple, bool flip)
{
tAnimCopybufItem *aci;
for (aci = animcopybuf.first; aci; aci = aci->next) {
/* check that paths exist */
if (to_simple || (aci->rna_path && fcu->rna_path)) {
if (to_simple || (strcmp(aci->rna_path, fcu->rna_path) == 0)) {
if ((from_single) || (aci->array_index == fcu->array_index))
if (!to_simple && flip && aci->is_bone && fcu->rna_path) {
if ((from_single) || (aci->array_index == fcu->array_index)) {
char *name = NULL;
flip_names(aci, &name);
if (strcmp(name, fcu->rna_path) == 0) {
MEM_freeN(name);
break;
}
MEM_freeN(name);
}
}
else if (to_simple || (strcmp(aci->rna_path, fcu->rna_path) == 0)) {
if ((from_single) || (aci->array_index == fcu->array_index)) {
break;
}
}
}
}
@ -670,8 +743,29 @@ static tAnimCopybufItem *pastebuf_match_index_only(FCurve *fcu, const short from
/* ................ */
static void do_curve_mirror_flippping(tAnimCopybufItem *aci, BezTriple *bezt) {
if (aci->is_bone) {
int slength = strlen(aci->rna_path);
bool flip = false;
if (BLI_strn_ends_with(aci->rna_path, "location", slength) && aci->array_index == 0)
flip = true;
else if (BLI_strn_ends_with(aci->rna_path, "rotation_quaternion", slength) && ELEM(aci->array_index, 2, 3))
flip = true;
else if (BLI_strn_ends_with(aci->rna_path, "rotation_euler", slength) && ELEM(aci->array_index, 1, 2))
flip = true;
else if (BLI_strn_ends_with(aci->rna_path, "rotation_axis_angle", slength) && ELEM(aci->array_index, 2, 3))
flip = true;
if (flip) {
bezt->vec[0][1] = -bezt->vec[0][1];
bezt->vec[1][1] = -bezt->vec[1][1];
bezt->vec[2][1] = -bezt->vec[2][1];
}
}
}
/* helper for paste_animedit_keys() - performs the actual pasting */
static void paste_animedit_keys_fcurve(FCurve *fcu, tAnimCopybufItem *aci, float offset, const eKeyMergeMode merge_mode)
static void paste_animedit_keys_fcurve(FCurve *fcu, tAnimCopybufItem *aci, float offset, const eKeyMergeMode merge_mode, bool flip)
{
BezTriple *bezt;
int i;
@ -726,6 +820,9 @@ static void paste_animedit_keys_fcurve(FCurve *fcu, tAnimCopybufItem *aci, float
/* just start pasting, with the first keyframe on the current frame, and so on */
for (i = 0, bezt = aci->bezt; i < aci->totvert; i++, bezt++) {
/* temporarily apply offset to src beztriple while copying */
if (flip)
do_curve_mirror_flippping(aci, bezt);
bezt->vec[0][0] += offset;
bezt->vec[1][0] += offset;
bezt->vec[2][0] += offset;
@ -733,12 +830,16 @@ static void paste_animedit_keys_fcurve(FCurve *fcu, tAnimCopybufItem *aci, float
/* insert the keyframe
* NOTE: we do not want to inherit handles from existing keyframes in this case!
*/
insert_bezt_fcurve(fcu, bezt, INSERTKEY_OVERWRITE_FULL);
insert_bezt_fcurve(fcu, bezt, INSERTKEY_OVERWRITE_FULL);
/* un-apply offset from src beztriple after copying */
bezt->vec[0][0] -= offset;
bezt->vec[1][0] -= offset;
bezt->vec[2][0] -= offset;
if (flip)
do_curve_mirror_flippping(aci, bezt);
}
/* recalculate F-Curve's handles? */
@ -768,7 +869,7 @@ EnumPropertyItem keyframe_paste_merge_items[] = {
* \return Status code is whether the method FAILED to do anything
*/
short paste_animedit_keys(bAnimContext *ac, ListBase *anim_data,
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode)
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode, bool flip)
{
bAnimListElem *ale;
@ -816,7 +917,7 @@ short paste_animedit_keys(bAnimContext *ac, ListBase *anim_data,
fcu = (FCurve *)ale->data; /* destination F-Curve */
aci = animcopybuf.first;
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode);
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode, false);
}
else {
/* from selected channels
@ -839,7 +940,7 @@ short paste_animedit_keys(bAnimContext *ac, ListBase *anim_data,
switch (pass) {
case 0:
/* most strict, must be exact path match data_path & index */
aci = pastebuf_match_path_full(fcu, from_single, to_simple);
aci = pastebuf_match_path_full(fcu, from_single, to_simple, flip);
break;
case 1:
@ -856,7 +957,7 @@ short paste_animedit_keys(bAnimContext *ac, ListBase *anim_data,
/* copy the relevant data from the matching buffer curve */
if (aci) {
totmatch++;
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode);
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode, flip);
}
ale->update |= ANIM_UPDATE_DEFAULT;

View File

@ -270,7 +270,7 @@ void sample_fcurve(struct FCurve *fcu);
void free_anim_copybuf(void);
short copy_animedit_keys(struct bAnimContext *ac, ListBase *anim_data);
short paste_animedit_keys(struct bAnimContext *ac, ListBase *anim_data,
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode);
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode, bool flip);
/* ************************************************ */

View File

@ -545,7 +545,7 @@ static short copy_action_keys(bAnimContext *ac)
static short paste_action_keys(bAnimContext *ac,
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode)
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode, bool flip)
{
ListBase anim_data = {NULL, NULL};
int filter, ok = 0;
@ -562,7 +562,7 @@ static short paste_action_keys(bAnimContext *ac,
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* paste keyframes */
ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode);
ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode, flip);
/* clean up */
ANIM_animdata_freelist(&anim_data);
@ -622,6 +622,7 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op)
const eKeyPasteOffset offset_mode = RNA_enum_get(op->ptr, "offset");
const eKeyMergeMode merge_mode = RNA_enum_get(op->ptr, "merge");
const bool flipped = RNA_boolean_get(op->ptr, "flipped");
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
@ -638,7 +639,7 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op)
}
else {
/* non-zero return means an error occurred while trying to paste */
if (paste_action_keys(&ac, offset_mode, merge_mode)) {
if (paste_action_keys(&ac, offset_mode, merge_mode, flipped)) {
return OPERATOR_CANCELLED;
}
}
@ -651,6 +652,7 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op)
void ACTION_OT_paste(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Paste Keyframes";
ot->idname = "ACTION_OT_paste";
@ -667,6 +669,8 @@ void ACTION_OT_paste(wmOperatorType *ot)
/* props */
RNA_def_enum(ot->srna, "offset", keyframe_paste_offset_items, KEYFRAME_PASTE_OFFSET_CFRA_START, "Offset", "Paste time offset of keys");
RNA_def_enum(ot->srna, "merge", keyframe_paste_merge_items, KEYFRAME_PASTE_MERGE_MIX, "Type", "Method of merging pasted keys and existing");
prop = RNA_def_boolean(ot->srna, "flipped", false, "Flipped", "Paste keyframes from mirrored bones if they exist");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/* ******************** Insert Keyframes Operator ************************* */

View File

@ -205,9 +205,13 @@ static void action_keymap_keyframes(wmKeyConfig *keyconf, wmKeyMap *keymap)
/* copy/paste */
WM_keymap_add_item(keymap, "ACTION_OT_copy", CKEY, KM_PRESS, KM_CTRL, 0);
WM_keymap_add_item(keymap, "ACTION_OT_paste", VKEY, KM_PRESS, KM_CTRL, 0);
kmi = WM_keymap_add_item(keymap, "ACTION_OT_paste", VKEY, KM_PRESS, KM_CTRL | KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "flipped", true);
#ifdef __APPLE__
WM_keymap_add_item(keymap, "ACTION_OT_copy", CKEY, KM_PRESS, KM_OSKEY, 0);
WM_keymap_add_item(keymap, "ACTION_OT_paste", VKEY, KM_PRESS, KM_OSKEY, 0);
kmi = WM_keymap_add_item(keymap, "ACTION_OT_paste", VKEY, KM_PRESS, KM_OSKEY | KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "flipped", true);
#endif
/* auto-set range */

View File

@ -697,7 +697,7 @@ static short copy_graph_keys(bAnimContext *ac)
}
static short paste_graph_keys(bAnimContext *ac,
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode)
const eKeyPasteOffset offset_mode, const eKeyMergeMode merge_mode, bool flip)
{
ListBase anim_data = {NULL, NULL};
int filter, ok = 0;
@ -714,7 +714,7 @@ static short paste_graph_keys(bAnimContext *ac,
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* paste keyframes */
ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode);
ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode, flip);
/* clean up */
ANIM_animdata_freelist(&anim_data);
@ -765,6 +765,7 @@ static int graphkeys_paste_exec(bContext *C, wmOperator *op)
const eKeyPasteOffset offset_mode = RNA_enum_get(op->ptr, "offset");
const eKeyMergeMode merge_mode = RNA_enum_get(op->ptr, "merge");
const bool flipped = RNA_boolean_get(op->ptr, "flipped");
/* get editor data */
if (ANIM_animdata_get_context(C, &ac) == 0)
@ -774,7 +775,7 @@ static int graphkeys_paste_exec(bContext *C, wmOperator *op)
ac.reports = op->reports;
/* paste keyframes - non-zero return means an error occurred while trying to paste */
if (paste_graph_keys(&ac, offset_mode, merge_mode)) {
if (paste_graph_keys(&ac, offset_mode, merge_mode, flipped)) {
return OPERATOR_CANCELLED;
}
@ -786,6 +787,8 @@ static int graphkeys_paste_exec(bContext *C, wmOperator *op)
void GRAPH_OT_paste(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Paste Keyframes";
ot->idname = "GRAPH_OT_paste";
@ -802,6 +805,8 @@ void GRAPH_OT_paste(wmOperatorType *ot)
/* props */
RNA_def_enum(ot->srna, "offset", keyframe_paste_offset_items, KEYFRAME_PASTE_OFFSET_CFRA_START, "Offset", "Paste time offset of keys");
RNA_def_enum(ot->srna, "merge", keyframe_paste_merge_items, KEYFRAME_PASTE_MERGE_MIX, "Type", "Method of merging pasted keys and existing");
prop = RNA_def_boolean(ot->srna, "flipped", false, "Flipped", "Paste keyframes from mirrored bones if they exist");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/* ******************** Duplicate Keyframes Operator ************************* */

View File

@ -580,9 +580,13 @@ static void graphedit_keymap_keyframes(wmKeyConfig *keyconf, wmKeyMap *keymap)
/* copy/paste */
WM_keymap_add_item(keymap, "GRAPH_OT_copy", CKEY, KM_PRESS, KM_CTRL, 0);
WM_keymap_add_item(keymap, "GRAPH_OT_paste", VKEY, KM_PRESS, KM_CTRL, 0);
kmi = WM_keymap_add_item(keymap, "GRAPH_OT_paste", VKEY, KM_PRESS, KM_CTRL | KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "flipped", true);
#ifdef __APPLE__
WM_keymap_add_item(keymap, "GRAPH_OT_copy", CKEY, KM_PRESS, KM_OSKEY, 0);
WM_keymap_add_item(keymap, "GRAPH_OT_paste", VKEY, KM_PRESS, KM_OSKEY, 0);
kmi = WM_keymap_add_item(keymap, "GRAPH_OT_paste", VKEY, KM_PRESS, KM_OSKEY | KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "flipped", true);
#endif
/* auto-set range */