Anim: Bake Channel operator
This is a replacement for the workflow that uses "Bake Curve" and "Unbake Curve" to quickly generate dense key data. Compared to the existing workflow it has the advantage of allowing the user more control over the key types, and distance between keys, as well as the frame range affected. Operator options * Range: the range that will be baked. Defaults to the scene range or preview range. * Step: Distance between keyframes. Can be used to bake on 2s or even bake to subframes. * Remove Existing Keys: Boolean option that if enabled also removes keys outside the specified baking range * Interpolation Type: Choose a interpolation mode used for new keys e.g. Constant or Bezier * Bake Modifiers: If enabled bakes the effect of the modifier stack to keys and deletes the modifier stack. If false, the code disables the modifiers before baking, so the resulting keys will behave as if the modifiers didn't exist The operator can be found in the Graph Editor under `Channel->Bake Channels` Part of: #111050 Pull Request: https://projects.blender.org/blender/blender/pulls/111263
This commit is contained in:
committed by
Christoph Lendenfeld
parent
1a998c73eb
commit
1e931f5bd7
@@ -295,6 +295,7 @@ class GRAPH_MT_channel(Menu):
|
||||
layout.operator("graph.keys_to_samples")
|
||||
layout.operator("graph.samples_to_keys")
|
||||
layout.operator("graph.sound_to_samples")
|
||||
layout.operator("anim.channels_bake")
|
||||
|
||||
layout.separator()
|
||||
layout.operator("graph.euler_filter", text="Discontinuity (Euler) Filter")
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "DNA_curve_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -483,11 +484,31 @@ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt,
|
||||
*/
|
||||
void BKE_fcurve_bezt_shrink(struct FCurve *fcu, int new_totvert);
|
||||
|
||||
/**
|
||||
* Merge the two given BezTriple arrays `a` and `b` into a newly allocated BezTriple array of size
|
||||
* `r_merged_size`. In case of keys on identical frames, `a` takes precedence.
|
||||
* Does not free `a` or `b`.
|
||||
* Assumes that both arrays are sorted for the x-position.
|
||||
* Has a complexity of O(N) with respect to the length of `size_a` + `size_b`.
|
||||
*
|
||||
* \return The merged BezTriple array of length `r_merged_size`.
|
||||
*/
|
||||
BezTriple *BKE_bezier_array_merge(
|
||||
const BezTriple *a, int size_a, const BezTriple *b, int size_b, int *r_merged_size);
|
||||
|
||||
/**
|
||||
* Delete a keyframe from an F-curve at a specific index.
|
||||
*/
|
||||
void BKE_fcurve_delete_key(struct FCurve *fcu, int index);
|
||||
|
||||
/** Delete an index range of keyframes from an F-curve. This is more performant than individually
|
||||
* removing keys.
|
||||
* Has a complexity of O(N) with respect to number of keys in `fcu`.
|
||||
*
|
||||
* \param index_range is right exclusive.
|
||||
*/
|
||||
void BKE_fcurve_delete_keys(FCurve *fcu, blender::uint2 index_range);
|
||||
|
||||
/**
|
||||
* Delete selected keyframes from an F-curve.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "BLI_easing.h"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_sort_utils.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
@@ -1705,6 +1706,81 @@ void BKE_fcurve_delete_key(FCurve *fcu, int index)
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_fcurve_delete_keys(FCurve *fcu, blender::uint2 index_range)
|
||||
{
|
||||
BLI_assert(fcu != nullptr);
|
||||
BLI_assert(fcu->bezt != nullptr);
|
||||
BLI_assert(index_range[1] > index_range[0]);
|
||||
BLI_assert(index_range[1] <= fcu->totvert);
|
||||
|
||||
const int removed_index_count = index_range[1] - index_range[0];
|
||||
memmove(&fcu->bezt[index_range[0]],
|
||||
&fcu->bezt[index_range[1]],
|
||||
sizeof(BezTriple) * (fcu->totvert - index_range[1]));
|
||||
fcu->totvert -= removed_index_count;
|
||||
|
||||
if (fcu->totvert == 0) {
|
||||
fcurve_bezt_free(fcu);
|
||||
}
|
||||
}
|
||||
|
||||
BezTriple *BKE_bezier_array_merge(
|
||||
const BezTriple *a, const int size_a, const BezTriple *b, const int size_b, int *r_merged_size)
|
||||
{
|
||||
BezTriple *large_array = static_cast<BezTriple *>(
|
||||
MEM_callocN((size_a + size_b) * sizeof(BezTriple), "beztriple"));
|
||||
|
||||
int iterator_a = 0;
|
||||
int iterator_b = 0;
|
||||
*r_merged_size = 0;
|
||||
|
||||
/* For comparing if keyframes are at the same x-value. */
|
||||
const int max_ulps = 32;
|
||||
|
||||
while (iterator_a < size_a || iterator_b < size_b) {
|
||||
if (iterator_a >= size_a) {
|
||||
const int remaining_keys = size_b - iterator_b;
|
||||
memcpy(&large_array[*r_merged_size], &b[iterator_b], sizeof(BezTriple) * remaining_keys);
|
||||
(*r_merged_size) += remaining_keys;
|
||||
break;
|
||||
}
|
||||
if (iterator_b >= size_b) {
|
||||
const int remaining_keys = size_a - iterator_a;
|
||||
memcpy(&large_array[*r_merged_size], &a[iterator_a], sizeof(BezTriple) * remaining_keys);
|
||||
(*r_merged_size) += remaining_keys;
|
||||
break;
|
||||
}
|
||||
|
||||
if (compare_ff_relative(
|
||||
a[iterator_a].vec[1][0], b[iterator_b].vec[1][0], BEZT_BINARYSEARCH_THRESH, max_ulps))
|
||||
{
|
||||
memcpy(&large_array[*r_merged_size], &a[iterator_a], sizeof(BezTriple));
|
||||
iterator_a++;
|
||||
iterator_b++;
|
||||
}
|
||||
else if (a[iterator_a].vec[1][0] < b[iterator_b].vec[1][0]) {
|
||||
memcpy(&large_array[*r_merged_size], &a[iterator_a], sizeof(BezTriple));
|
||||
iterator_a++;
|
||||
}
|
||||
else {
|
||||
memcpy(&large_array[*r_merged_size], &b[iterator_b], sizeof(BezTriple));
|
||||
iterator_b++;
|
||||
}
|
||||
(*r_merged_size)++;
|
||||
}
|
||||
|
||||
BezTriple *minimal_array;
|
||||
if (*r_merged_size < size_a + size_b) {
|
||||
minimal_array = static_cast<BezTriple *>(
|
||||
MEM_reallocN(large_array, sizeof(BezTriple) * (*r_merged_size)));
|
||||
}
|
||||
else {
|
||||
minimal_array = large_array;
|
||||
}
|
||||
|
||||
return minimal_array;
|
||||
}
|
||||
|
||||
bool BKE_fcurve_delete_keys_selected(FCurve *fcu)
|
||||
{
|
||||
if (fcu->bezt == nullptr) { /* ignore baked curves */
|
||||
|
||||
@@ -4310,6 +4310,189 @@ static void ANIM_OT_channel_view_pick(wmOperatorType *ot)
|
||||
"Ignore frames outside of the preview range");
|
||||
}
|
||||
|
||||
static const EnumPropertyItem channel_bake_key_options[] = {
|
||||
{BEZT_IPO_BEZ, "BEZIER", 0, "Bezier", "New keys will be beziers"},
|
||||
{BEZT_IPO_LIN, "LIN", 0, "Linear", "New keys will be linear"},
|
||||
{BEZT_IPO_CONST, "CONST", 0, "Constant", "New keys will be constant"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem channel_bake_remove_options[] = {
|
||||
{int(BakeCurveRemove::REMOVE_NONE), "NONE", 0, "None", "Keep all keys"},
|
||||
{int(BakeCurveRemove::REMOVE_IN_RANGE),
|
||||
"IN_RANGE",
|
||||
0,
|
||||
"In Range",
|
||||
"Remove all keys within the defined range"},
|
||||
{int(BakeCurveRemove::REMOVE_OUT_RANGE),
|
||||
"OUT_RANGE",
|
||||
0,
|
||||
"Outside Range",
|
||||
"Remove all keys outside the defined range"},
|
||||
{int(BakeCurveRemove::REMOVE_ALL), "ALL", 0, "All", "Remove all existing keys"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static int channels_bake_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
bAnimContext ac;
|
||||
|
||||
/* Get editor data. */
|
||||
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
ListBase anim_data = {nullptr, nullptr};
|
||||
const int filter = (ANIMFILTER_SEL | ANIMFILTER_NODUPLIS | ANIMFILTER_DATA_VISIBLE |
|
||||
ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FCURVESONLY);
|
||||
size_t anim_data_length = ANIM_animdata_filter(
|
||||
&ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
|
||||
|
||||
if (anim_data_length == 0) {
|
||||
WM_report(RPT_WARNING, "No channels to operate on");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
|
||||
/* The range will default to the scene or preview range, but only if it hasn't been set before.
|
||||
* If a range is set here, the redo panel wouldn't work properly because the range would
|
||||
* constantly be overridden. */
|
||||
blender::int2 frame_range;
|
||||
RNA_int_get_array(op->ptr, "range", frame_range);
|
||||
if (frame_range[1] < frame_range[0]) {
|
||||
frame_range[1] = frame_range[0];
|
||||
}
|
||||
const float step = RNA_float_get(op->ptr, "step");
|
||||
if (frame_range[0] == 0 && frame_range[1] == 0) {
|
||||
if (scene->r.flag & SCER_PRV_RANGE) {
|
||||
frame_range = {scene->r.psfra, scene->r.pefra};
|
||||
}
|
||||
else {
|
||||
frame_range = {scene->r.sfra, scene->r.efra};
|
||||
}
|
||||
RNA_int_set_array(op->ptr, "range", frame_range);
|
||||
}
|
||||
|
||||
const bool remove_outside_range = RNA_boolean_get(op->ptr, "remove_outside_range");
|
||||
const BakeCurveRemove remove_existing = remove_outside_range ? BakeCurveRemove::REMOVE_ALL :
|
||||
BakeCurveRemove::REMOVE_IN_RANGE;
|
||||
const int interpolation_type = RNA_enum_get(op->ptr, "interpolation_type");
|
||||
const bool bake_modifiers = RNA_boolean_get(op->ptr, "bake_modifiers");
|
||||
|
||||
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
||||
FCurve *fcu = static_cast<FCurve *>(ale->data);
|
||||
if (!fcu->bezt) {
|
||||
continue;
|
||||
}
|
||||
AnimData *adt = ANIM_nla_mapping_get(&ac, ale);
|
||||
blender::int2 nla_mapped_range;
|
||||
nla_mapped_range[0] = int(BKE_nla_tweakedit_remap(adt, frame_range[0], NLATIME_CONVERT_UNMAP));
|
||||
nla_mapped_range[1] = int(BKE_nla_tweakedit_remap(adt, frame_range[1], NLATIME_CONVERT_UNMAP));
|
||||
/* Save current state of modifier flags so they can be reapplied after baking. */
|
||||
blender::Vector<short> modifier_flags;
|
||||
if (!bake_modifiers) {
|
||||
LISTBASE_FOREACH (FModifier *, modifier, &fcu->modifiers) {
|
||||
modifier_flags.append(modifier->flag);
|
||||
modifier->flag |= FMODIFIER_FLAG_MUTED;
|
||||
}
|
||||
}
|
||||
|
||||
bool replace;
|
||||
const int last_index = BKE_fcurve_bezt_binarysearch_index(
|
||||
fcu->bezt, nla_mapped_range[1], fcu->totvert, &replace);
|
||||
|
||||
/* Since the interpolation of a key defines the curve following it, the last key in the baked
|
||||
* segment needs to keep the interpolation mode that existed previously so the curve isn't
|
||||
* changed. */
|
||||
const char segment_end_interpolation = fcu->bezt[min_ii(last_index, fcu->totvert - 1)].ipo;
|
||||
|
||||
bake_fcurve(fcu, nla_mapped_range, step, remove_existing);
|
||||
|
||||
if (bake_modifiers) {
|
||||
free_fmodifiers(&fcu->modifiers);
|
||||
}
|
||||
else {
|
||||
int modifier_index = 0;
|
||||
LISTBASE_FOREACH (FModifier *, modifier, &fcu->modifiers) {
|
||||
modifier->flag = modifier_flags[modifier_index];
|
||||
modifier_index++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < fcu->totvert; i++) {
|
||||
BezTriple *key = &fcu->bezt[i];
|
||||
if (key->vec[1][0] < nla_mapped_range[0]) {
|
||||
continue;
|
||||
}
|
||||
if (key->vec[1][0] > nla_mapped_range[1]) {
|
||||
fcu->bezt[max_ii(i - 1, 0)].ipo = segment_end_interpolation;
|
||||
break;
|
||||
}
|
||||
key->ipo = interpolation_type;
|
||||
}
|
||||
}
|
||||
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, nullptr);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void ANIM_OT_channels_bake(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers */
|
||||
ot->name = "Bake Channels";
|
||||
ot->idname = "ANIM_OT_channels_bake";
|
||||
ot->description =
|
||||
"Create keyframes following the current shape of F-Curves of selected channels";
|
||||
|
||||
/* API callbacks */
|
||||
ot->exec = channels_bake_exec;
|
||||
ot->poll = channel_view_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
RNA_def_int_array(ot->srna,
|
||||
"range",
|
||||
2,
|
||||
nullptr,
|
||||
INT_MIN,
|
||||
INT_MAX,
|
||||
"Frame Range",
|
||||
"The range in which to create new keys",
|
||||
0,
|
||||
INT_MAX);
|
||||
|
||||
RNA_def_float(ot->srna,
|
||||
"step",
|
||||
1.0f,
|
||||
0.01f,
|
||||
FLT_MAX,
|
||||
"Frame Step",
|
||||
"At which interval to add keys",
|
||||
1.0f,
|
||||
16.0f);
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"remove_outside_range",
|
||||
false,
|
||||
"Remove Outside Range",
|
||||
"Removes keys outside the given range, leaving only the newly baked");
|
||||
|
||||
RNA_def_enum(ot->srna,
|
||||
"interpolation_type",
|
||||
channel_bake_key_options,
|
||||
BEZT_IPO_BEZ,
|
||||
"Interpolation Type",
|
||||
"Choose the interpolation type with which new keys will be added");
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"bake_modifiers",
|
||||
true,
|
||||
"Bake Modifiers",
|
||||
"Bake Modifiers into keyframes and delete them after");
|
||||
}
|
||||
|
||||
/* Find a Graph Editor area and modify the given context to be the window region of it. */
|
||||
static bool move_context_to_graph_editor(bContext *C)
|
||||
{
|
||||
@@ -4584,6 +4767,8 @@ void ED_operatortypes_animchannels()
|
||||
|
||||
WM_operatortype_append(ANIM_OT_channels_group);
|
||||
WM_operatortype_append(ANIM_OT_channels_ungroup);
|
||||
|
||||
WM_operatortype_append(ANIM_OT_channels_bake);
|
||||
}
|
||||
|
||||
void ED_keymap_animchannels(wmKeyConfig *keyconf)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "BLI_blenlib.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_string_utils.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
@@ -1211,7 +1212,7 @@ struct TempFrameValCache {
|
||||
|
||||
void sample_fcurve_segment(FCurve *fcu,
|
||||
const float start_frame,
|
||||
const int sample_rate,
|
||||
const float sample_rate,
|
||||
float *samples,
|
||||
const int sample_count)
|
||||
{
|
||||
@@ -1221,6 +1222,104 @@ void sample_fcurve_segment(FCurve *fcu,
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_fcurve_key_range(FCurve *fcu,
|
||||
const blender::int2 range,
|
||||
const BakeCurveRemove removal_mode)
|
||||
{
|
||||
switch (removal_mode) {
|
||||
|
||||
case BakeCurveRemove::REMOVE_ALL: {
|
||||
BKE_fcurve_delete_keys_all(fcu);
|
||||
break;
|
||||
}
|
||||
|
||||
case BakeCurveRemove::REMOVE_OUT_RANGE: {
|
||||
bool replace;
|
||||
|
||||
int before_index = BKE_fcurve_bezt_binarysearch_index(
|
||||
fcu->bezt, range[0], fcu->totvert, &replace);
|
||||
|
||||
if (before_index > 0) {
|
||||
BKE_fcurve_delete_keys(fcu, {0, uint(before_index)});
|
||||
}
|
||||
|
||||
int after_index = BKE_fcurve_bezt_binarysearch_index(
|
||||
fcu->bezt, range[1], fcu->totvert, &replace);
|
||||
/* REMOVE_OUT_RANGE is treated as exlusive on both ends. */
|
||||
if (replace) {
|
||||
after_index++;
|
||||
}
|
||||
if (after_index < fcu->totvert) {
|
||||
BKE_fcurve_delete_keys(fcu, {uint(after_index), fcu->totvert});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BakeCurveRemove::REMOVE_IN_RANGE: {
|
||||
bool replace;
|
||||
const int range_start_index = BKE_fcurve_bezt_binarysearch_index(
|
||||
fcu->bezt, range[0], fcu->totvert, &replace);
|
||||
int range_end_index = BKE_fcurve_bezt_binarysearch_index(
|
||||
fcu->bezt, range[1], fcu->totvert, &replace);
|
||||
if (replace) {
|
||||
range_end_index++;
|
||||
}
|
||||
|
||||
if (range_end_index > range_start_index) {
|
||||
BKE_fcurve_delete_keys(fcu, {uint(range_start_index), uint(range_end_index)});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void bake_fcurve(FCurve *fcu,
|
||||
const blender::int2 range,
|
||||
const float step,
|
||||
const BakeCurveRemove remove_existing)
|
||||
{
|
||||
using namespace blender::animrig;
|
||||
BLI_assert(step > 0);
|
||||
const int sample_count = (range[1] - range[0]) / step + 1;
|
||||
float *samples = static_cast<float *>(
|
||||
MEM_callocN(sample_count * sizeof(float), "Channel Bake Samples"));
|
||||
const float sample_rate = 1.0f / step;
|
||||
sample_fcurve_segment(fcu, range[0], sample_rate, samples, sample_count);
|
||||
|
||||
if (remove_existing != BakeCurveRemove::REMOVE_NONE) {
|
||||
remove_fcurve_key_range(fcu, range, remove_existing);
|
||||
}
|
||||
|
||||
BezTriple *baked_keys = static_cast<BezTriple *>(
|
||||
MEM_callocN(sample_count * sizeof(BezTriple), "beztriple"));
|
||||
|
||||
const KeyframeSettings settings = get_keyframe_settings(true);
|
||||
|
||||
for (int i = 0; i < sample_count; i++) {
|
||||
BezTriple *key = &baked_keys[i];
|
||||
blender::float2 key_position = {range[0] + i * step, samples[i]};
|
||||
initialize_bezt(key, key_position, settings, eFCurve_Flags(fcu->flag));
|
||||
}
|
||||
|
||||
int merged_size;
|
||||
BezTriple *merged_bezt = BKE_bezier_array_merge(
|
||||
baked_keys, sample_count, fcu->bezt, fcu->totvert, &merged_size);
|
||||
|
||||
if (fcu->bezt != nullptr) {
|
||||
/* Can happen if we removed all keys beforehand. */
|
||||
MEM_freeN(fcu->bezt);
|
||||
}
|
||||
MEM_freeN(baked_keys);
|
||||
fcu->bezt = merged_bezt;
|
||||
fcu->totvert = merged_size;
|
||||
|
||||
MEM_freeN(samples);
|
||||
BKE_fcurve_handles_recalc(fcu);
|
||||
}
|
||||
|
||||
void bake_fcurve_segments(FCurve *fcu)
|
||||
{
|
||||
using namespace blender::animrig;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "ED_anim_api.hh" /* for enum eAnimFilter_Flags */
|
||||
|
||||
struct BezTriple;
|
||||
@@ -497,7 +498,14 @@ void bake_fcurve_segments(FCurve *fcu);
|
||||
* \param sample_rate: indicates how many samples per frame should be generated.
|
||||
*/
|
||||
void sample_fcurve_segment(
|
||||
FCurve *fcu, float start_frame, int sample_rate, float *r_samples, int sample_count);
|
||||
FCurve *fcu, float start_frame, float sample_rate, float *r_samples, int sample_count);
|
||||
|
||||
enum class BakeCurveRemove { REMOVE_NONE, REMOVE_IN_RANGE, REMOVE_OUT_RANGE, REMOVE_ALL };
|
||||
/** Creates keyframes in the given range at the given step interval.
|
||||
* \param range: start and end frame to bake. Is inclusive on both ends.
|
||||
* \param remove_existing: choice which keys to remove in relation to the given range.
|
||||
*/
|
||||
void bake_fcurve(FCurve *fcu, blender::int2 range, float step, BakeCurveRemove remove_existing);
|
||||
|
||||
/* ----------- */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user