NLA: Keyframe Remap Through Upper Strips

Add a new operator, "Start Tweaking Strip Actions (Full Stack)", which
allows you to insert keyframes and preserve the pose that you visually
keyed while upper strips are evaluating,

The old operator has been renamed from "Start Tweaking Strip Actions" to
"Start Tweaking Strip Actions (Lower Stack)" and remains the default for
the hotkey {key TAB}.

**Limitations, Keyframe Remapping Failure Cases**:
1. For *transitions* above the tweaked strip, keyframe remapping will
   fail for channel values that are affected by the transition. A work
   around is to tweak the active strip without evaluating the upper NLA
   stack.

   It's not supported because it's non-trivial and I couldn't figure it
   out for all transition combinations of blend modes. In the future, it
   would be nice if transitions (and metas) supported nested tracks
   instead of using the left/right strips for the transitions. That
   would allow the transitioned strips to overlap in time. It would also
   allow  N strips to be part of the (previously) left and right strips,
   or perhaps even N strips being transitioned in sequence (similar to a
   blend tree). Proper keyframe remapping through all that is currently
   beyond my mathematical ability. And even if I could figure it out,
   would it make sense to keyframe remap through a transition?

   //This case is reported to the user for failed keyframe insertions.//

2. Full replace upper strip that contains the keyed channels.

   //This case is reported to the user for failed keyframe insertions.//

3. When the same action clip occurs multiple times (colored Red to
   denote it's a linked strip) and vertically overlaps the tweaked
   strip, then the remapping will generally fail and is expected to
   fail.

   I don't plan on adding support for this case as it's also non-trivial
   and (hopefully) not a common or expected use case so it shouldn't be
   much of an issue to lack support here.

   For anyone curious on the cases that would work, it works when the
   linked strips aren't time-aligned and when we can insert a keyframe
   into the tweaked strip without modifying the current frame output of
   the other linked strips. Having all frames sampled and the strip
   non-time aligned leads to a working case. But if all key handles are
   AUTO, then it's likely to fail.

   //This case is not reported to the user for failed keyframe
   insertions.//

4. When using Quaternions and a small strip influence on the tweaked
   Combine strip. This was an existing failure case before this patch
   too but worth a mention in case it causes confusion. D10504 has an
   example file with instructions.

   //This case is not reported to the user for failed keyframe insertions. //

5. When an upper Replace strip with high influence and animator keys to
   Quaternion Combine (Replace is fine). This case is similar to (4)
   where Quaternion 180 degree rotation limitations prevent a solution.

   //This case is not reported to the user for failed keyframe insertions.//

Reviewed By: sybren, RiggingDojo

Differential Revision: https://developer.blender.org/D10504
This commit is contained in:
Wayde Moss 2022-04-14 11:38:36 +02:00 committed by Sybren A. Stüvel
parent b0dc3aff2c
commit 3acbe2d1e9
Notes: blender-bot 2023-06-07 10:31:13 +02:00
Referenced by commit ad324316ce, Fix: Missing Null Check
9 changed files with 1046 additions and 211 deletions

View File

@ -2509,7 +2509,8 @@ def km_nla_generic(_params):
*_template_space_region_type_toggle(
sidebar_key={"type": 'N', "value": 'PRESS'},
),
("nla.tweakmode_enter", {"type": 'TAB', "value": 'PRESS'}, None),
("nla.tweakmode_enter", {"type": 'TAB', "value": 'PRESS'},
{"properties": [("use_upper_stack_evaluation", False)]}),
("nla.tweakmode_exit", {"type": 'TAB', "value": 'PRESS'}, None),
("nla.tweakmode_enter", {"type": 'TAB', "value": 'PRESS', "shift": True},
{"properties": [("isolate_action", True)]}),

View File

@ -214,7 +214,8 @@ class NLA_MT_edit(Menu):
layout.operator("nla.tweakmode_exit", text="Stop Tweaking Strip Actions")
else:
layout.operator("nla.tweakmode_enter", text="Start Editing Stashed Action").isolate_action = True
layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions")
layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True
layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False
class NLA_MT_add(Menu):
@ -288,7 +289,8 @@ class NLA_MT_context_menu(Menu):
layout.operator("nla.tweakmode_exit", text="Stop Tweaking Strip Actions")
else:
layout.operator("nla.tweakmode_enter", text="Start Editing Stashed Action").isolate_action = True
layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions")
layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True
layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False
layout.separator()

View File

@ -7,6 +7,7 @@
* \ingroup bke
*/
#include "BLI_bitmap.h"
#include "BLI_sys_types.h" /* for bool */
#ifdef __cplusplus
@ -258,16 +259,20 @@ struct NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context(
* \param count: Number of values in the array.
* \param index: Index of the element about to be updated, or -1.
* \param[out] r_force_all: Set to true if all channels must be inserted. May be NULL.
* \return False if correction fails due to a division by zero,
* or null r_force_all when all channels are required.
* \param[out] r_successful_remaps: Bits will be enabled for indices that are both intended to be
* remapped and succeeded remapping. With both, it allows caller to check successfully remapped
* indices without having to explicitly check whether the index was intended to be remapped.
*/
bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context,
void BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context,
struct PointerRNA *prop_ptr,
struct PropertyRNA *prop,
float *values,
int count,
int index,
bool *r_force_all);
const struct AnimationEvalContext *anim_eval_context,
bool *r_force_all,
BLI_bitmap *r_successful_remaps);
/**
* Free all cached contexts from the list.
*/

File diff suppressed because it is too large Load Diff

View File

@ -1977,8 +1977,11 @@ bool BKE_nla_tweakmode_enter(AnimData *adt)
/* go over all the tracks after AND INCLUDING the active one, tagging them as being disabled
* - the active track needs to also be tagged, otherwise, it'll overlap with the tweaks going on
*/
for (nlt = activeTrack; nlt; nlt = nlt->next) {
nlt->flag |= NLATRACK_DISABLED;
activeTrack->flag |= NLATRACK_DISABLED;
if ((adt->flag & ADT_NLA_EVAL_UPPER_TRACKS) == 0) {
for (nlt = activeTrack->next; nlt; nlt = nlt->next) {
nlt->flag |= NLATRACK_DISABLED;
}
}
/* handle AnimData level changes:

View File

@ -142,7 +142,11 @@ typedef struct NlaKeyframingContext {
/* Data of the currently edited strip (copy, or fake strip for the main action). */
NlaStrip strip;
NlaEvalStrip *eval_strip;
/* Storage for the action track as a strip. */
NlaStrip action_track_strip;
/* Strips above tweaked strip. */
ListBase upper_estrips;
/* Evaluated NLA stack below the tweak strip. */
NlaEvalData lower_eval_data;
} NlaKeyframingContext;
@ -173,7 +177,22 @@ NlaEvalStrip *nlastrips_ctime_get_strip(ListBase *list,
/**
* Evaluates the given evaluation strip.
*/
void nlastrip_evaluate(PointerRNA *ptr,
enum eNlaStripEvaluate_Mode {
/* Blend upper strip with lower stack. */
STRIP_EVAL_BLEND,
/* Given upper strip and blended snapshot, solve for lower stack. */
STRIP_EVAL_BLEND_GET_INVERTED_LOWER_SNAPSHOT,
/* Store strip fcurve values in snapshot, properly marking blend_domain values.
*
* Currently only used for transitions to distinguish fcurve sampled values from default or lower
* stack values.
*/
STRIP_EVAL_NOBLEND,
};
void nlastrip_evaluate(const int evaluation_mode,
PointerRNA *ptr,
NlaEvalData *channels,
ListBase *modifiers,
NlaEvalStrip *nes,
@ -222,6 +241,36 @@ void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data,
float upper_influence,
NlaEvalSnapshot *r_upper_snapshot);
void nlasnapshot_blend_get_inverted_lower_snapshot(NlaEvalData *eval_data,
NlaEvalSnapshot *blended_snapshot,
NlaEvalSnapshot *upper_snapshot,
const short upper_blendmode,
const float upper_influence,
NlaEvalSnapshot *r_lower_snapshot);
void nlasnapshot_blend_strip(PointerRNA *ptr,
NlaEvalData *channels,
ListBase *modifiers,
NlaEvalStrip *nes,
NlaEvalSnapshot *snapshot,
const struct AnimationEvalContext *anim_eval_context,
const bool flush_to_original);
void nlasnapshot_blend_strip_get_inverted_lower_snapshot(
PointerRNA *ptr,
NlaEvalData *eval_data,
ListBase *modifiers,
NlaEvalStrip *nes,
NlaEvalSnapshot *snapshot,
const struct AnimationEvalContext *anim_eval_context);
void nlasnapshot_blend_strip_no_blend(PointerRNA *ptr,
NlaEvalData *channels,
ListBase *modifiers,
NlaEvalStrip *nes,
NlaEvalSnapshot *snapshot,
const struct AnimationEvalContext *anim_eval_context);
#ifdef __cplusplus
}
#endif

View File

@ -14,6 +14,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_dynstr.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
@ -1109,9 +1110,62 @@ static float *visualkey_get_values(
/* ------------------------- Insert Key API ------------------------- */
/* Check indices that were intended to be remapped and report any failed remaps. */
static void get_keyframe_values_create_reports(ReportList *reports,
PointerRNA ptr,
PropertyRNA *prop,
const int index,
const int count,
const bool force_all,
const BLI_bitmap *successful_remaps)
{
DynStr *ds_failed_indices = BLI_dynstr_new();
int total_failed = 0;
for (int i = 0; i < count; i++) {
const bool cur_index_evaluated = ELEM(index, i, -1) || force_all;
if (!cur_index_evaluated) {
/* values[i] was never intended to be remapped. */
continue;
}
if (BLI_BITMAP_TEST_BOOL(successful_remaps, i)) {
/* values[i] succesfully remapped. */
continue;
}
total_failed++;
/* Report that values[i] were intended to be remapped but failed remapping process. */
BLI_dynstr_appendf(ds_failed_indices, "%d, ", i);
}
if (total_failed == 0) {
BLI_dynstr_free(ds_failed_indices);
return;
}
char *str_failed_indices = BLI_dynstr_get_cstring(ds_failed_indices);
BLI_dynstr_free(ds_failed_indices);
BKE_reportf(reports,
RPT_WARNING,
"Could not insert %i keyframe(s) due to zero NLA influence, base value, or value "
"remapping failed: %s.%s for indices [%s]",
total_failed,
ptr.owner_id->name,
RNA_property_ui_name(prop),
str_failed_indices);
MEM_freeN(str_failed_indices);
}
/**
* Retrieve current property values to keyframe,
* possibly applying NLA correction when necessary.
*
* \param r_successful_remaps: Enables bits for indices which are both intended to be remapped and
* were successfully remapped. Bitmap allocated so it must be freed afterward.
*/
static float *get_keyframe_values(ReportList *reports,
PointerRNA ptr,
@ -1121,8 +1175,10 @@ static float *get_keyframe_values(ReportList *reports,
eInsertKeyFlags flag,
float *buffer,
int buffer_size,
const struct AnimationEvalContext *anim_eval_context,
int *r_count,
bool *r_force_all)
bool *r_force_all,
BLI_bitmap **r_successful_remaps)
{
float *values;
@ -1138,17 +1194,20 @@ static float *get_keyframe_values(ReportList *reports,
values = setting_get_rna_values(&ptr, prop, buffer, buffer_size, r_count);
}
/* adjust the value for NLA factors */
if (!BKE_animsys_nla_remap_keyframe_values(
nla_context, &ptr, prop, values, *r_count, index, r_force_all)) {
BKE_report(
reports, RPT_ERROR, "Could not insert keyframe due to zero NLA influence or base value");
*r_successful_remaps = BLI_BITMAP_NEW(*r_count, __func__);
if (values != buffer) {
MEM_freeN(values);
}
return NULL;
}
/* adjust the value for NLA factors */
BKE_animsys_nla_remap_keyframe_values(nla_context,
&ptr,
prop,
values,
*r_count,
index,
anim_eval_context,
r_force_all,
*r_successful_remaps);
get_keyframe_values_create_reports(
reports, ptr, prop, index, *r_count, *r_force_all, *r_successful_remaps);
return values;
}
@ -1284,6 +1343,7 @@ bool insert_keyframe_direct(ReportList *reports,
int value_count;
int index = fcu->array_index;
BLI_bitmap *successful_remaps = NULL;
float *values = get_keyframe_values(reports,
ptr,
prop,
@ -1292,13 +1352,10 @@ bool insert_keyframe_direct(ReportList *reports,
flag,
value_buffer,
RNA_MAX_ARRAY_LENGTH,
anim_eval_context,
&value_count,
NULL);
if (values == NULL) {
/* This happens if NLA rejects this insertion. */
return false;
}
NULL,
&successful_remaps);
if (index >= 0 && index < value_count) {
curval = values[index];
@ -1308,6 +1365,14 @@ bool insert_keyframe_direct(ReportList *reports,
MEM_freeN(values);
}
const bool curval_valid = BLI_BITMAP_TEST_BOOL(successful_remaps, index);
MEM_freeN(successful_remaps);
/* This happens if NLA rejects this insertion. */
if (!curval_valid) {
return false;
}
return insert_keyframe_value(reports, &ptr, prop, fcu, anim_eval_context, curval, keytype, flag);
}
@ -1461,6 +1526,7 @@ int insert_keyframe(Main *bmain,
int value_count;
bool force_all;
BLI_bitmap *successful_remaps = NULL;
float *values = get_keyframe_values(reports,
ptr,
prop,
@ -1469,77 +1535,72 @@ int insert_keyframe(Main *bmain,
flag,
value_buffer,
RNA_MAX_ARRAY_LENGTH,
anim_eval_context,
&value_count,
&force_all);
&force_all,
&successful_remaps);
if (values != NULL) {
/* Key the entire array. */
if (array_index == -1 || force_all) {
/* In force mode, if any of the curves succeeds, drop the replace mode and restart. */
if (force_all && (flag & (INSERTKEY_REPLACE | INSERTKEY_AVAILABLE)) != 0) {
int exclude = -1;
/* Key the entire array. */
if (array_index == -1 || force_all) {
/* In force mode, if any of the curves succeeds, drop the replace mode and restart. */
if (force_all && (flag & (INSERTKEY_REPLACE | INSERTKEY_AVAILABLE)) != 0) {
int exclude = -1;
for (array_index = 0; array_index < value_count; array_index++) {
if (insert_keyframe_fcurve_value(bmain,
reports,
&ptr,
prop,
act,
group,
rna_path,
array_index,
&remapped_context,
values[array_index],
keytype,
flag)) {
ret++;
exclude = array_index;
break;
}
for (array_index = 0; array_index < value_count; array_index++) {
if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) {
continue;
}
if (exclude != -1) {
flag &= ~(INSERTKEY_REPLACE | INSERTKEY_AVAILABLE);
for (array_index = 0; array_index < value_count; array_index++) {
if (array_index != exclude) {
ret += insert_keyframe_fcurve_value(bmain,
reports,
&ptr,
prop,
act,
group,
rna_path,
array_index,
&remapped_context,
values[array_index],
keytype,
flag);
}
}
if (insert_keyframe_fcurve_value(bmain,
reports,
&ptr,
prop,
act,
group,
rna_path,
array_index,
&remapped_context,
values[array_index],
keytype,
flag)) {
ret++;
exclude = array_index;
break;
}
}
/* Simply insert all channels. */
else {
if (exclude != -1) {
flag &= ~(INSERTKEY_REPLACE | INSERTKEY_AVAILABLE);
for (array_index = 0; array_index < value_count; array_index++) {
ret += insert_keyframe_fcurve_value(bmain,
reports,
&ptr,
prop,
act,
group,
rna_path,
array_index,
&remapped_context,
values[array_index],
keytype,
flag);
if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) {
continue;
}
if (array_index != exclude) {
ret += insert_keyframe_fcurve_value(bmain,
reports,
&ptr,
prop,
act,
group,
rna_path,
array_index,
&remapped_context,
values[array_index],
keytype,
flag);
}
}
}
}
/* Key a single index. */
/* Simply insert all channels. */
else {
if (array_index >= 0 && array_index < value_count) {
for (array_index = 0; array_index < value_count; array_index++) {
if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) {
continue;
}
ret += insert_keyframe_fcurve_value(bmain,
reports,
&ptr,
@ -1554,12 +1615,31 @@ int insert_keyframe(Main *bmain,
flag);
}
}
if (values != value_buffer) {
MEM_freeN(values);
}
/* Key a single index. */
else {
if (array_index >= 0 && array_index < value_count &&
BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) {
ret += insert_keyframe_fcurve_value(bmain,
reports,
&ptr,
prop,
act,
group,
rna_path,
array_index,
&remapped_context,
values[array_index],
keytype,
flag);
}
}
if (values != value_buffer) {
MEM_freeN(values);
}
MEM_freeN(successful_remaps);
BKE_animsys_free_nla_keyframing_context_cache(&tmp_nla_cache);
if (ret) {

View File

@ -98,6 +98,7 @@ static int nlaedit_enable_tweakmode_exec(bContext *C, wmOperator *op)
int filter;
const bool do_solo = RNA_boolean_get(op->ptr, "isolate_action");
const bool use_upper_stack_evaluation = RNA_boolean_get(op->ptr, "use_upper_stack_evaluation");
bool ok = false;
/* get editor data */
@ -119,6 +120,13 @@ static int nlaedit_enable_tweakmode_exec(bContext *C, wmOperator *op)
for (ale = anim_data.first; ale; ale = ale->next) {
AnimData *adt = ale->data;
if (use_upper_stack_evaluation) {
adt->flag |= ADT_NLA_EVAL_UPPER_TRACKS;
}
else {
adt->flag &= ~ADT_NLA_EVAL_UPPER_TRACKS;
}
/* Try entering tweak-mode if valid. */
ok |= BKE_nla_tweakmode_enter(adt);
@ -181,6 +189,13 @@ void NLA_OT_tweakmode_enter(wmOperatorType *ot)
"Enable 'solo' on the NLA Track containing the active strip, "
"to edit it without seeing the effects of the NLA stack");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"use_upper_stack_evaluation",
false,
"Evaluate Upper Stack",
"In tweak mode, display the effects of the tracks above the tweak strip");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */

View File

@ -1128,6 +1128,8 @@ typedef enum eAnimData_Flag {
ADT_NLA_EDIT_NOMAP = (1 << 3),
/** NLA-Strip F-Curves are expanded in UI. */
ADT_NLA_SKEYS_COLLAPSED = (1 << 4),
/* Evaluate tracks above tweaked strip. Only relevant in tweak mode. */
ADT_NLA_EVAL_UPPER_TRACKS = (1 << 5),
/** Drivers expanded in UI. */
ADT_DRIVERS_COLLAPSED = (1 << 10),