Animation: Paste Keys in Graph Editor with value offset #104512

Merged
Christoph Lendenfeld merged 12 commits from ChrisLend/blender:copy_paste_frame_range into main 2023-02-23 09:47:00 +01:00
5 changed files with 126 additions and 21 deletions

View File

@ -20,12 +20,14 @@
#include "DNA_anim_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "BKE_action.h"
#include "BKE_curve.h"
#include "BKE_fcurve.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "RNA_access.h"
#include "RNA_enum_types.h"
@ -1074,7 +1076,7 @@ static void do_curve_mirror_flippping(tAnimCopybufItem *aci, BezTriple *bezt)
/* 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, bool flip)
FCurve *fcu, tAnimCopybufItem *aci, float offset[2], const eKeyMergeMode merge_mode, bool flip)
{
BezTriple *bezt;
int i;
@ -1101,12 +1103,12 @@ static void paste_animedit_keys_fcurve(
float f_max;
if (merge_mode == KEYFRAME_PASTE_MERGE_OVER_RANGE) {
f_min = aci->bezt[0].vec[1][0] + offset;
f_max = aci->bezt[aci->totvert - 1].vec[1][0] + offset;
f_min = aci->bezt[0].vec[1][0] + offset[0];
f_max = aci->bezt[aci->totvert - 1].vec[1][0] + offset[0];
}
else { /* Entire Range */
f_min = animcopy_firstframe + offset;
f_max = animcopy_lastframe + offset;
f_min = animcopy_firstframe + offset[0];
f_max = animcopy_lastframe + offset[0];
}
/* remove keys in range */
@ -1132,9 +1134,9 @@ static void paste_animedit_keys_fcurve(
do_curve_mirror_flippping(aci, bezt);
}
bezt->vec[0][0] += offset;
bezt->vec[1][0] += offset;
bezt->vec[2][0] += offset;
add_v2_v2(bezt->vec[0], offset);
add_v2_v2(bezt->vec[1], offset);
add_v2_v2(bezt->vec[2], offset);
/* insert the keyframe
* NOTE: we do not want to inherit handles from existing keyframes in this case!
@ -1143,9 +1145,9 @@ static void paste_animedit_keys_fcurve(
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;
sub_v2_v2(bezt->vec[0], offset);
sub_v2_v2(bezt->vec[1], offset);
sub_v2_v2(bezt->vec[2], offset);
if (flip) {
do_curve_mirror_flippping(aci, bezt);
@ -1172,6 +1174,34 @@ const EnumPropertyItem rna_enum_keyframe_paste_offset_items[] = {
{0, NULL, 0, NULL, NULL},
};
const EnumPropertyItem rna_enum_keyframe_paste_offset_value[] = {
{KEYFRAME_PASTE_VALUE_OFFSET_LEFT_KEY,
"LEFT_KEY",
0,
"Left Key",
"Paste keys with the first key matching the key left of the cursor"},
{KEYFRAME_PASTE_VALUE_OFFSET_RIGHT_KEY,
"RIGHT_KEY",
0,
"Right Key",
"Paste keys with the last key matching the key right of the cursor"},
{KEYFRAME_PASTE_VALUE_OFFSET_CFRA,
"CURRENT_FRAME",
0,
"Current Frame Value",
"Paste keys relative to the value of the curve under the cursor"},
{KEYFRAME_PASTE_VALUE_OFFSET_CURSOR,
"CURSOR_VALUE",
0,
"Cursor Value",
"Paste keys relative to the Y-Position of the cursor"},
{KEYFRAME_PASTE_VALUE_OFFSET_NONE,
"NONE",
0,
"No Offset",
"Paste keys with the same value as they were copied"},
};
const EnumPropertyItem rna_enum_keyframe_paste_merge_items[] = {
{KEYFRAME_PASTE_MERGE_MIX, "MIX", 0, "Mix", "Overlay existing with new keys"},
{KEYFRAME_PASTE_MERGE_OVER, "OVER_ALL", 0, "Overwrite All", "Replace all keys"},
@ -1188,9 +1218,56 @@ const EnumPropertyItem rna_enum_keyframe_paste_merge_items[] = {
{0, NULL, 0, NULL, NULL},
};
static float paste_get_y_offset(bAnimContext *ac,
tAnimCopybufItem *aci,
bAnimListElem *ale,
const eKeyPasteValueOffset value_offset_mode)
{
FCurve *fcu = (FCurve *)ale->data;
const float cfra = BKE_scene_frame_get(ac->scene);
switch (value_offset_mode) {
case KEYFRAME_PASTE_VALUE_OFFSET_CURSOR: {
SpaceGraph *sipo = (SpaceGraph *)ac->sl;
const float offset = sipo->cursorVal - aci->bezt[0].vec[1][1];
return offset;
}
dr.sybren marked this conversation as resolved

Instead of having a variable offset, and having to track how it's changed in the code below, IMO it's better to just replace this

offset = ...;
break;

with

return ...;
Instead of having a variable `offset`, and having to track how it's changed in the code below, IMO it's better to just replace this ```c offset = ...; break; ``` with ```c return ...; ```

With this change, you don't even need the top-level variable float offset any more. If you want to keep a variable between the calculation and the return (can aid in debugging the code), use a const float offset = ... constant scoped to the case statement instead. Or just return sipo->cursorVal - aci->bezt[0].vec[1][1]; if you don't need the extra variable.

With this change, you don't even need the top-level variable `float offset` any more. If you want to keep a variable between the calculation and the `return` (can aid in debugging the code), use a `const float offset = ...` constant scoped to the `case` statement instead. Or just `return sipo->cursorVal - aci->bezt[0].vec[1][1];` if you don't need the extra variable.
case KEYFRAME_PASTE_VALUE_OFFSET_CFRA: {
const float cfra_y = evaluate_fcurve(fcu, cfra);
const float offset = cfra_y - aci->bezt[0].vec[1][1];
return offset;
}
case KEYFRAME_PASTE_VALUE_OFFSET_LEFT_KEY: {
bool replace;
const int fcu_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, cfra, fcu->totvert, &replace);
BezTriple left_key = fcu->bezt[max_ii(fcu_index - 1, 0)];
const float offset = left_key.vec[1][1] - aci->bezt[0].vec[1][1];
return offset;
}
case KEYFRAME_PASTE_VALUE_OFFSET_RIGHT_KEY: {
bool replace;
const int fcu_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, cfra, fcu->totvert, &replace);
BezTriple right_key = fcu->bezt[min_ii(fcu_index, fcu->totvert - 1)];
const float offset = right_key.vec[1][1] - aci->bezt[aci->totvert - 1].vec[1][1];
return offset;
}
case KEYFRAME_PASTE_VALUE_OFFSET_NONE:
ChrisLend marked this conversation as resolved

You can remove the default:, that way the compiler can complain when a new value was added to the enum, but not handled here.

You can remove the `default:`, that way the compiler can complain when a new value was added to the enum, but not handled here.
break;
}
return 0.0f;
}
eKeyPasteError paste_animedit_keys(bAnimContext *ac,
ListBase *anim_data,
const eKeyPasteOffset offset_mode,
const eKeyPasteValueOffset value_offset_mode,
const eKeyMergeMode merge_mode,
bool flip)
{
@ -1201,7 +1278,7 @@ eKeyPasteError paste_animedit_keys(bAnimContext *ac,
const bool from_single = BLI_listbase_is_single(&animcopybuf);
const bool to_simple = BLI_listbase_is_single(anim_data);
float offset = 0.0f;
float offset[2];
int pass;
/* check if buffer is empty */
@ -1216,16 +1293,16 @@ eKeyPasteError paste_animedit_keys(bAnimContext *ac,
/* methods of offset */
switch (offset_mode) {
case KEYFRAME_PASTE_OFFSET_CFRA_START:
offset = (float)(scene->r.cfra - animcopy_firstframe);
offset[0] = (float)(scene->r.cfra - animcopy_firstframe);
break;
case KEYFRAME_PASTE_OFFSET_CFRA_END:
offset = (float)(scene->r.cfra - animcopy_lastframe);
offset[0] = (float)(scene->r.cfra - animcopy_lastframe);
break;
case KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE:
offset = (float)(scene->r.cfra - animcopy_cfra);
offset[0] = (float)(scene->r.cfra - animcopy_cfra);
break;
case KEYFRAME_PASTE_OFFSET_NONE:
offset = 0.0f;
offset[0] = 0.0f;
break;
}
@ -1238,6 +1315,7 @@ eKeyPasteError paste_animedit_keys(bAnimContext *ac,
fcu = (FCurve *)ale->data; /* destination F-Curve */
aci = animcopybuf.first;
offset[1] = paste_get_y_offset(ac, aci, ale, value_offset_mode);
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode, false);
ale->update |= ANIM_UPDATE_DEFAULT;
}
@ -1282,6 +1360,7 @@ eKeyPasteError paste_animedit_keys(bAnimContext *ac,
if (aci) {
totmatch++;
offset[1] = paste_get_y_offset(ac, aci, ale, value_offset_mode);
if (adt) {
ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0);
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode, flip);

View File

@ -218,6 +218,19 @@ typedef enum eKeyPasteOffset {
KEYFRAME_PASTE_OFFSET_NONE,
} eKeyPasteOffset;
typedef enum eKeyPasteValueOffset {
/* Paste keys with the first key matching the key left of the cursor. */
KEYFRAME_PASTE_VALUE_OFFSET_LEFT_KEY,
/* Paste keys with the last key matching the key right of the cursor. */
KEYFRAME_PASTE_VALUE_OFFSET_RIGHT_KEY,
/* Paste keys relative to the value of the curve under the cursor. */
KEYFRAME_PASTE_VALUE_OFFSET_CFRA,
/* Paste values relative to the cursor position. */
KEYFRAME_PASTE_VALUE_OFFSET_CURSOR,
/* Paste keys with the exact copied value. */
KEYFRAME_PASTE_VALUE_OFFSET_NONE,
} eKeyPasteValueOffset;
typedef enum eKeyMergeMode {
/* overlay existing with new keys */
KEYFRAME_PASTE_MERGE_MIX,
@ -427,6 +440,7 @@ short copy_animedit_keys(struct bAnimContext *ac, ListBase *anim_data);
eKeyPasteError paste_animedit_keys(struct bAnimContext *ac,
ListBase *anim_data,
eKeyPasteOffset offset_mode,
eKeyPasteValueOffset value_offset_mode,
eKeyMergeMode merge_mode,
bool flip);

View File

@ -533,8 +533,9 @@ static eKeyPasteError paste_action_keys(bAnimContext *ac,
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
}
/* paste keyframes */
const eKeyPasteError ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode, flip);
/* Value offset is always None because the user cannot see the effect of it. */
const eKeyPasteError ok = paste_animedit_keys(
ac, &anim_data, offset_mode, KEYFRAME_PASTE_VALUE_OFFSET_NONE, merge_mode, flip);
/* clean up */
ANIM_animdata_freelist(&anim_data);

View File

@ -475,6 +475,7 @@ static short copy_graph_keys(bAnimContext *ac)
static eKeyPasteError paste_graph_keys(bAnimContext *ac,
const eKeyPasteOffset offset_mode,
const eKeyPasteValueOffset value_offset_mode,
const eKeyMergeMode merge_mode,
bool flip)
{
@ -495,7 +496,8 @@ static eKeyPasteError paste_graph_keys(bAnimContext *ac,
}
/* Paste keyframes. */
const eKeyPasteError ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode, flip);
const eKeyPasteError ok = paste_animedit_keys(
ac, &anim_data, offset_mode, value_offset_mode, merge_mode, flip);
/* Clean up. */
ANIM_animdata_freelist(&anim_data);
@ -544,6 +546,7 @@ static int graphkeys_paste_exec(bContext *C, wmOperator *op)
bAnimContext ac;
const eKeyPasteOffset offset_mode = RNA_enum_get(op->ptr, "offset");
const eKeyPasteValueOffset value_offset_mode = RNA_enum_get(op->ptr, "value_offset");
const eKeyMergeMode merge_mode = RNA_enum_get(op->ptr, "merge");
const bool flipped = RNA_boolean_get(op->ptr, "flipped");
@ -555,7 +558,8 @@ static int graphkeys_paste_exec(bContext *C, wmOperator *op)
/* Ac.reports by default will be the global reports list, which won't show warnings. */
ac.reports = op->reports;
const eKeyPasteError kf_empty = paste_graph_keys(&ac, offset_mode, merge_mode, flipped);
const eKeyPasteError kf_empty = paste_graph_keys(
&ac, offset_mode, value_offset_mode, merge_mode, flipped);
switch (kf_empty) {
case KEYFRAME_PASTE_OK:
break;
@ -614,8 +618,14 @@ void GRAPH_OT_paste(wmOperatorType *ot)
"offset",
rna_enum_keyframe_paste_offset_items,
KEYFRAME_PASTE_OFFSET_CFRA_START,
"Offset",
"Frame Offset",
"Paste time offset of keys");
RNA_def_enum(ot->srna,
"value_offset",
rna_enum_keyframe_paste_offset_value,
KEYFRAME_PASTE_VALUE_OFFSET_NONE,
"Value Offset",
"Paste keys with a value offset");
RNA_def_enum(ot->srna,
"merge",
rna_enum_keyframe_paste_merge_items,

View File

@ -242,6 +242,7 @@ DEF_ENUM(rna_enum_particle_edit_hair_brush_items)
DEF_ENUM(rna_enum_particle_edit_disconnected_hair_brush_items)
DEF_ENUM(rna_enum_keyframe_paste_offset_items)
DEF_ENUM(rna_enum_keyframe_paste_offset_value)
DEF_ENUM(rna_enum_keyframe_paste_merge_items)
DEF_ENUM(rna_enum_transform_pivot_items_full)