Animation: Allow NLA strips to be horizontally shuffled #105532

Merged
Nate Rupsis merged 22 commits from nrupsis/blender:NLA-horizontal-shuffle into main 2023-04-03 17:10:50 +02:00
4 changed files with 377 additions and 103 deletions

View File

@ -812,7 +812,7 @@ void BKE_nlastrips_sort_strips(ListBase *strips)
for (sstrip = tmp.last; sstrip; sstrip = sstrip->prev) {
/* check if add after */
if (sstrip->end <= strip->start) {
if (sstrip->start <= strip->start) {
BLI_insertlinkafter(&tmp, sstrip, strip);
not_added = 0;
break;
@ -841,7 +841,7 @@ void BKE_nlastrips_add_strip_unsafe(ListBase *strips, NlaStrip *strip)
/* find the right place to add the strip to the nominated track */
for (ns = strips->first; ns; ns = ns->next) {
/* if current strip occurs after the new strip, add it before */
if (ns->start >= strip->end) {
if (ns->start >= strip->start) {
BLI_insertlinkbefore(strips, ns, strip);
not_added = 0;
break;
@ -1952,9 +1952,30 @@ static void BKE_nlastrip_validate_autoblends(NlaTrack *nlt, NlaStrip *nls)
}
}
/* Ensure every transition's start/end properly set.
nrupsis marked this conversation as resolved Outdated

Document that this can even remove and free strip when it doesn't fit any more.

Document that this can even remove and free `strip` when it doesn't fit any more.
* Strip will be removed / freed if it doesn't fit (invalid).
nrupsis marked this conversation as resolved Outdated

Better name would be nlastrip_validate_transition_start_end, to show that it's about transitions only.

Better name would be `nlastrip_validate_transition_start_end`, to show that it's about transitions only.
* Return value indicates if passed strip is valid/fixed or invalid/removed. */
static bool nlastrip_validate_transition_start_end(NlaStrip *strip)
nrupsis marked this conversation as resolved

I feel the return value now contradicts the name of the function, as true is returned when the strip could not be validated. I think returning true when valid-or-repaired and false when invalid-beyond-repair is better.

I feel the return value now contradicts the name of the function, as `true` is returned when the strip could **not** be validated. I think returning `true` when valid-or-repaired and `false` when invalid-beyond-repair is better.
{
if (!(strip->type & NLASTRIP_TYPE_TRANSITION)) {
return true;
}
if (strip->prev) {
strip->start = strip->prev->end;
}
if (strip->next) {
strip->end = strip->next->start;
}
if (strip->start >= strip->end || strip->prev == NULL || strip->next == NULL) {
BKE_nlastrip_free(strip, true);
return false;
}
return true;
}
void BKE_nla_validate_state(AnimData *adt)
{
NlaStrip *strip = NULL;
NlaTrack *nlt;
/* sanity checks */
@ -1965,7 +1986,15 @@ void BKE_nla_validate_state(AnimData *adt)
/* Adjust blending values for auto-blending,
* and also do an initial pass to find the earliest strip. */
for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) {
for (strip = nlt->strips.first; strip; strip = strip->next) {
LISTBASE_FOREACH_MUTABLE (NlaStrip *, strip, &nlt->strips) {
if (!nlastrip_validate_transition_start_end(strip)) {
nrupsis marked this conversation as resolved Outdated

nlastrip_validate_transition_start_end() doesn't change the strip pointer variable, so the if (strip == NULL) check doesn't check for 'if the strip was freed'.

How about making it so that nlastrip_validate_transition_start_end() returns a boolean that indicates whether the strip was freed or not. Alternatively you could give it a NlaStrip **strip parameter so that it can modify the strip variable itself, but I'm not really a fan of those.

`nlastrip_validate_transition_start_end()` doesn't change the `strip` pointer variable, so the `if (strip == NULL)` check doesn't check for 'if the strip was freed'. How about making it so that `nlastrip_validate_transition_start_end()` returns a boolean that indicates whether the strip was freed or not. Alternatively you could give it a `NlaStrip **strip` parameter so that it can modify the `strip` variable itself, but I'm not really a fan of those.

Would it make more sense to have a <= condition in the for loop, and use the same iter_max variable for both conditional checks? I.e

short iter_max = 4;


 for (short iter = 0; iter <= iter_max; iter++) { 
 ...

 if ((p_exceeded && n_exceeded) || (iter == iter_max)) {
 ...


Would it make more sense to have a `<=` condition in the for loop, and use the same `iter_max` variable for both conditional checks? I.e ``` short iter_max = 4; for (short iter = 0; iter <= iter_max; iter++) { ... if ((p_exceeded && n_exceeded) || (iter == iter_max)) { ... ```

I think the boolean return type is more expressive, and arguably more readable (with an informative comment on the function)

I think the boolean return type is more expressive, and arguably more readable (with an informative comment on the function)
printf(
nrupsis marked this conversation as resolved Outdated

This can access memory after freeing it, crashing Blender. Better to let the caller know somehow whether nlastrip_validate_start_end() freed the strip.

A similar issue occurs in the for-loop with strip = strip->next. You'll probably want to replace the loop with a call to LISTBASE_FOREACH_MUTABLE(). That'll determine the next pointer before entering the loop body.

This can access memory after freeing it, crashing Blender. Better to let the caller know somehow whether `nlastrip_validate_start_end()` freed the strip. A similar issue occurs in the `for`-loop with `strip = strip->next`. You'll probably want to replace the loop with a call to `LISTBASE_FOREACH_MUTABLE()`. That'll determine the `next` pointer before entering the loop body.
"While moving NLA strips, a transition strip could no longer be applied to the new "
nrupsis marked this conversation as resolved Outdated

Such a warning is a good idea. It can be more specific, though, because we know it's an transition strip, so do mention that. Maybe also turn the message more positive, like "While moving NLA strips, a transition strip could no longer be applied to the new positions and was removed."

It would be better to have this as a warning in the UI though, and not just printed on the terminal. That would require some more work that's not necessarily related to this PR, though.

Such a warning is a good idea. It can be more specific, though, because we know it's an transition strip, so do mention that. Maybe also turn the message more positive, like "While moving NLA strips, a transition strip could no longer be applied to the new positions and was removed." It would be better to have this as a warning in the UI though, and not just printed on the terminal. That would require some more work that's not necessarily related to this PR, though.
"positions and was removed.\n");
nrupsis marked this conversation as resolved Outdated

Remove space before newline.

Remove space before newline.
continue;
nrupsis marked this conversation as resolved Outdated

Why return? Shouldn't the remaining strips be checked too?

Why return? Shouldn't the remaining strips be checked too?

oop, nope that was a dumb mistake on my part. Should be a continue.

oop, nope that was a dumb mistake on my part. Should be a continue.
}
/* auto-blending first */
BKE_nlastrip_validate_autoblends(nlt, strip);
BKE_nlastrip_recalculate_blend(strip);

View File

@ -487,7 +487,8 @@ static void nla_draw_strip(SpaceNla *snla,
}
nrupsis marked this conversation as resolved Outdated

<rant about existing code>
I really don't like negated booleans. if (non_solo == 0 ...) just makes my head spin. As a followup patch, if you want to change that to if (solo ...), by all means!
</rant>

`<rant about existing code>` I really don't like negated booleans. `if (non_solo == 0 ...)` just makes my head spin. As a followup patch, if you want to change that to `if (solo ...)`, by all means! `</rant>`
/* draw 'inside' of strip itself */
if (solo && is_nlastrip_enabled(adt, nlt, strip)) {
if (solo && is_nlastrip_enabled(adt, nlt, strip) &&
!(strip->flag & NLASTRIP_FLAG_INVALID_LOCATION)) {
immUnbindProgram();
/* strip is in normal track */
@ -534,7 +535,11 @@ static void nla_draw_strip(SpaceNla *snla,
/* draw strip outline
* - color used here is to indicate active vs non-active
*/
if (strip->flag & (NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT)) {
if (strip->flag & NLASTRIP_FLAG_INVALID_LOCATION) {
color[0] = 1.0f;
color[1] = color[2] = 0.15f;
}
else if (strip->flag & NLASTRIP_FLAG_ACTIVE) {
/* strip should appear 'sunken', so draw a light border around it */
color[0] = color[1] = color[2] = 1.0f; /* FIXME: hardcoded temp-hack colors */
}

View File

@ -5,6 +5,8 @@
* \ingroup edtransform
*/
#include <stdio.h>
#include "DNA_anim_types.h"
#include "DNA_space_types.h"
@ -13,6 +15,7 @@
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BKE_anim_data.h"
#include "BKE_context.h"
#include "BKE_nla.h"
@ -55,6 +58,86 @@ typedef struct TransDataNla {
int handle;
} TransDataNla;
static bool is_overlap(const float left_bound_a,
const float right_bound_a,
const float left_bound_b,
const float right_bound_b)
{
return (left_bound_a < right_bound_b) && (right_bound_a > left_bound_b);
}
static bool nlastrip_is_overlap(const NlaStrip *strip_a,
const float offset_a,
const NlaStrip *strip_b,
const float offset_b)
{
return is_overlap(strip_a->start + offset_a,
strip_a->end + offset_a,
strip_b->start + offset_b,
strip_b->end + offset_b);
}
/** Assumes strips to horizontally translate (shuffle) are tagged with
* NLASTRIP_FLAG_INVALID_LOCATION.
*
* \returns The total sided offset that results in no overlaps between tagged strips and non-tagged
* strips.
*/
static float transdata_get_time_shuffle_offset_side(ListBase *trans_datas, const bool shuffle_left)
{
float total_offset = 0;
float offset;
do {
offset = 0;
LISTBASE_FOREACH (LinkData *, link, trans_datas) {
TransDataNla *trans_data = (TransDataNla *)link->data;
NlaStrip *xformed_strip = trans_data->strip;
LISTBASE_FOREACH (NlaStrip *, non_xformed_strip, &trans_data->nlt->strips) {
if (non_xformed_strip->flag & NLASTRIP_FLAG_INVALID_LOCATION) {
continue;
}
/* Allow overlap with transitions. */
if (non_xformed_strip->type & NLASTRIP_TYPE_TRANSITION) {
continue;
}
if (!nlastrip_is_overlap(non_xformed_strip, 0, xformed_strip, total_offset)) {
continue;
}
offset = shuffle_left ?
fmin(offset, non_xformed_strip->start - (xformed_strip->end + total_offset)) :
fmax(offset, non_xformed_strip->end - (xformed_strip->start + total_offset));
}
}
total_offset += offset;
} while (!IS_EQF(offset, 0.0f));
return total_offset;
}
/** Assumes strips to horizontally translate (shuffle) are tagged with
* NLASTRIP_FLAG_INVALID_LOCATION.
*
* \returns The minimal total signed offset that results in no overlaps between tagged strips and
* non-tagged strips.
*/
static float transdata_get_time_shuffle_offset(ListBase *trans_datas)
{
const float offset_left = transdata_get_time_shuffle_offset_side(trans_datas, true);
const float offset_right = transdata_get_time_shuffle_offset_side(trans_datas, false);
BLI_assert(offset_left <= 0);
BLI_assert(offset_right >= 0);
nrupsis marked this conversation as resolved

Nice assertions, really helps to understand the math that follows.

Nice assertions, really helps to understand the math that follows.
return -offset_left < offset_right ? offset_left : offset_right;
}
/* -------------------------------------------------------------------- */
/** \name Transform application to NLA strips
* \{ */
@ -81,6 +164,117 @@ static void applyTransformNLA_timeScale(PointerRNA *strip_rna_ptr, const float v
RNA_float_set(strip_rna_ptr, "scale", value);
}
/** Reorder strips for proper nla stack evaluation while dragging. */
static void nlastrip_overlap_reorder(TransDataNla *tdn, NlaStrip *strip)
{
while (strip->prev != NULL && tdn->h1[0] < strip->prev->start) {
BLI_listbase_swaplinks(&tdn->nlt->strips, strip, strip->prev);
}
while (strip->next != NULL && tdn->h1[0] > strip->next->start) {
BLI_listbase_swaplinks(&tdn->nlt->strips, strip, strip->next);
}
}
/** Flag overlaps with adjacent strips.
*
* Since the strips are re-ordered as they're transformed, we only have to check adjacent
* strips for overlap instead of all of them. */
static void nlastrip_flag_overlaps(NlaStrip *strip)
{
nrupsis marked this conversation as resolved Outdated

I think one set of curlies is enough ;-)

I think one set of curlies is enough ;-)
NlaStrip *adj_strip = strip->prev;
if (adj_strip != NULL && !(adj_strip->flag & NLASTRIP_FLAG_SELECT) &&
nlastrip_is_overlap(strip, 0, adj_strip, 0)) {
strip->flag |= NLASTRIP_FLAG_INVALID_LOCATION;
}
adj_strip = strip->next;
if (adj_strip != NULL && !(adj_strip->flag & NLASTRIP_FLAG_SELECT) &&
nlastrip_is_overlap(strip, 0, adj_strip, 0)) {
strip->flag |= NLASTRIP_FLAG_INVALID_LOCATION;
}
}
/** Check the Transformation data for the given Strip, and fix any overlap. Then
* apply the Transformation.
*/
static void nlastrip_fix_overlapping(TransInfo *t, TransDataNla *tdn, NlaStrip *strip)
{
nrupsis marked this conversation as resolved Outdated

Maybe add a note in the PR description (and thus the commit message) that the contents of nlastrip_fix_overlapping() were just moved from recalcData_nla(), and that the logic was kept as-is. That way people won't hold you responsible for the code ;-)

Maybe add a note in the PR description (and thus the commit message) that the contents of `nlastrip_fix_overlapping()` were just moved from `recalcData_nla()`, and that the logic was kept as-is. That way people won't hold you responsible for the code ;-)
/* firstly, check if the proposed transform locations would overlap with any neighboring
* strips (barring transitions) which are absolute barriers since they are not being moved
*
* this is done as a iterative procedure (done 5 times max for now)
nrupsis marked this conversation as resolved

Well, with the for-loop as it is now, it's done 6 times.

Well, with the `for`-loop as it is now, it's done 6 times.
*/
short iter_max = 4;
NlaStrip *prev = BKE_nlastrip_prev_in_track(strip, true);
NlaStrip *next = BKE_nlastrip_next_in_track(strip, true);
PointerRNA strip_ptr;
nrupsis marked this conversation as resolved Outdated

Instead of hard-coding 5 (here) and 4 (below), create a constant and use it in both places. That way the relationship between the two values is explicit.

Instead of hard-coding `5` (here) and `4` (below), create a constant and use it in both places. That way the relationship between the two values is explicit.
for (short iter = 0; iter <= iter_max; iter++) {
nrupsis marked this conversation as resolved Outdated

Variables should use snake_case.

Variables should use `snake_case`.
const bool p_exceeded = (prev != NULL) && (tdn->h1[0] < prev->end);
const bool n_exceeded = (next != NULL) && (tdn->h2[0] > next->start);
if ((p_exceeded && n_exceeded) || (iter == iter_max)) {
nrupsis marked this conversation as resolved Outdated

Replace iter == 4 with iter == iter_max

Replace `iter == 4` with `iter == iter_max`
/* both endpoints exceeded (or iteration ping-pong'd meaning that we need a
* compromise)
* - Simply crop strip to fit within the bounds of the strips bounding it
* - If there were no neighbors, clear the transforms
* (make it default to the strip's current values).
*/
if (prev && next) {
tdn->h1[0] = prev->end;
tdn->h2[0] = next->start;
}
else {
tdn->h1[0] = strip->start;
tdn->h2[0] = strip->end;
}
}
else if (n_exceeded) {
/* move backwards */
float offset = tdn->h2[0] - next->start;
tdn->h1[0] -= offset;
tdn->h2[0] -= offset;
}
else if (p_exceeded) {
/* more forwards */
float offset = prev->end - tdn->h1[0];
tdn->h1[0] += offset;
tdn->h2[0] += offset;
}
else { /* all is fine and well */
break;
}
}
/* Use RNA to write the values to ensure that constraints on these are obeyed
* (e.g. for transition strips, the values are taken from the neighbors)
*/
RNA_pointer_create(NULL, &RNA_NlaStrip, strip, &strip_ptr);
switch (t->mode) {
case TFM_TIME_EXTEND:
case TFM_TIME_SCALE: {
/* The final scale is the product of the original strip scale (from before the
* transform operation started) and the current scale value of this transform
* operation. */
const float originalStripScale = tdn->h1[2];
const float newStripScale = originalStripScale * t->values_final[0];
applyTransformNLA_timeScale(&strip_ptr, newStripScale);
applyTransformNLA_translation(&strip_ptr, tdn);
break;
}
case TFM_TRANSLATION:
applyTransformNLA_translation(&strip_ptr, tdn);
break;
default:
printf("recalcData_nla: unsupported NLA transformation mode %d\n", t->mode);
break;
}
}
/** \} */
/* -------------------------------------------------------------------- */
@ -189,9 +383,9 @@ static void createTransNlaData(bContext *C, TransInfo *t)
/* our transform data is constructed as follows:
* - only the handles on the right side of the current-frame get included
* - td structs are transform-elements operated on by the transform system
* and represent a single handle. The storage/pointer used (val or loc) depends on
* whether we're scaling or transforming. Ultimately though, the handles
* the td writes to will simply be a dummy in tdn
* and represent a single handle. The storage/pointer used (val or loc) depends
* on whether we're scaling or transforming. Ultimately though, the handles the td
* writes to will simply be a dummy in tdn
* - for each strip being transformed, a single tdn struct is used, so in some
* cases, there will need to be 1 of these tdn elements in the array skipped...
*/
@ -304,13 +498,13 @@ static void recalcData_nla(TransInfo *t)
TransDataNla *tdn = tc->custom.type.data;
for (int i = 0; i < tc->data_len; i++, tdn++) {
NlaStrip *strip = tdn->strip;
PointerRNA strip_ptr;
int delta_y1, delta_y2;
/* if this tdn has no handles, that means it is just a dummy that should be skipped */
if (tdn->handle == 0) {
continue;
}
strip->flag &= ~NLASTRIP_FLAG_INVALID_LOCATION;
/* set refresh tags for objects using this animation,
* BUT only if realtime updates are enabled
@ -355,75 +549,18 @@ static void recalcData_nla(TransInfo *t)
continue;
}
/* firstly, check if the proposed transform locations would overlap with any neighboring strips
* (barring transitions) which are absolute barriers since they are not being moved
*
* this is done as a iterative procedure (done 5 times max for now)
*/
NlaStrip *prev = BKE_nlastrip_prev_in_track(strip, true);
NlaStrip *next = BKE_nlastrip_next_in_track(strip, true);
const bool nlatrack_isliboverride = BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, tdn->nlt);
const bool allow_overlap = !nlatrack_isliboverride && ELEM(t->mode, TFM_TRANSLATION);
for (short iter = 0; iter < 5; iter++) {
const bool pExceeded = (prev != NULL) && (tdn->h1[0] < prev->end);
const bool nExceeded = (next != NULL) && (tdn->h2[0] > next->start);
if (allow_overlap) {
nlastrip_overlap_reorder(tdn, strip);
if ((pExceeded && nExceeded) || (iter == 4)) {
/* both endpoints exceeded (or iteration ping-pong'd meaning that we need a
* compromise)
* - Simply crop strip to fit within the bounds of the strips bounding it
* - If there were no neighbors, clear the transforms
* (make it default to the strip's current values).
*/
if (prev && next) {
tdn->h1[0] = prev->end;
tdn->h2[0] = next->start;
}
else {
tdn->h1[0] = strip->start;
tdn->h2[0] = strip->end;
}
}
else if (nExceeded) {
/* move backwards */
float offset = tdn->h2[0] - next->start;
tdn->h1[0] -= offset;
tdn->h2[0] -= offset;
}
else if (pExceeded) {
/* more forwards */
float offset = prev->end - tdn->h1[0];
tdn->h1[0] += offset;
tdn->h2[0] += offset;
}
else { /* all is fine and well */
break;
}
/* Directly flush. */
strip->start = tdn->h1[0];
strip->end = tdn->h2[0];
}
/* Use RNA to write the values to ensure that constraints on these are obeyed
* (e.g. for transition strips, the values are taken from the neighbors)
*/
RNA_pointer_create(NULL, &RNA_NlaStrip, strip, &strip_ptr);
switch (t->mode) {
case TFM_TIME_EXTEND:
case TFM_TIME_SCALE: {
/* The final scale is the product of the original strip scale (from before the transform
* operation started) and the current scale value of this transform operation. */
const float originalStripScale = tdn->h1[2];
const float newStripScale = originalStripScale * t->values_final[0];
applyTransformNLA_timeScale(&strip_ptr, newStripScale);
applyTransformNLA_translation(&strip_ptr, tdn);
break;
}
case TFM_TRANSLATION:
applyTransformNLA_translation(&strip_ptr, tdn);
break;
default:
printf("recalcData_nla: unsupported NLA transformation mode %d\n", t->mode);
continue;
else {
nlastrip_fix_overlapping(t, tdn, strip);
}
/* flush transforms to child strips (since this should be a meta) */
@ -433,9 +570,10 @@ static void recalcData_nla(TransInfo *t)
* - we need to calculate both,
* as only one may have been altered by transform if only 1 handle moved.
*/
/* In LibOverride case, we cannot move strips across tracks that come from the linked data. */
/* In LibOverride case, we cannot move strips across tracks that come from the linked data.
*/
const bool is_liboverride = ID_IS_OVERRIDE_LIBRARY(tdn->id);
if (BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, tdn->nlt)) {
if (nlatrack_isliboverride) {
continue;
}
@ -489,6 +627,8 @@ static void recalcData_nla(TransInfo *t)
}
}
}
nlastrip_flag_overlaps(strip);
}
}
@ -498,7 +638,83 @@ static void recalcData_nla(TransInfo *t)
/** \name Special After Transform NLA
* \{ */
static void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t))
typedef struct IDGroupedTransData {
struct IDGroupedTransData *next, *prev;
ID *id;
ListBase trans_datas;
} IDGroupedTransData;
/** horizontally translate (shuffle) the transformed strip to a non-overlapping state. */
static void nlastrip_shuffle_transformed(TransDataContainer *tc, TransDataNla *first_trans_data)
{
/* Element: (IDGroupedTransData*) */
ListBase grouped_trans_datas = {NULL, NULL};
nrupsis marked this conversation as resolved

Don't use doxygen for comments inside functions.

Don't use doxygen for comments inside functions.
/* Flag all non-library-override transformed strips so we can distinguish them when
* shuffling.
*
* Group trans_datas by ID so shuffling is unique per ID.
*/
{
TransDataNla *tdn = first_trans_data;
for (int i = 0; i < tc->data_len; i++, tdn++) {
/* Skip dummy handles. */
if (tdn->handle == 0) {
continue;
}
/* For strips within library override tracks, don't do any shuffling at all. Unsure how
* library overrides should behave so, for now, they're treated as mostly immutable. */
if ((tdn->nlt->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0) {
continue;
}
tdn->strip->flag |= NLASTRIP_FLAG_INVALID_LOCATION;
IDGroupedTransData *dst_group = NULL;
/* Find dst_group with matching ID. */
LISTBASE_FOREACH (IDGroupedTransData *, group, &grouped_trans_datas) {
if (group->id == tdn->id) {
dst_group = group;
break;
}
}
if (dst_group == NULL) {
dst_group = MEM_callocN(sizeof(IDGroupedTransData), __func__);
dst_group->id = tdn->id;
BLI_addhead(&grouped_trans_datas, dst_group);
}
BLI_addtail(&dst_group->trans_datas, BLI_genericNodeN(tdn));
}
}
/* Apply shuffling. */
LISTBASE_FOREACH (IDGroupedTransData *, group, &grouped_trans_datas) {
ListBase *trans_datas = &group->trans_datas;
/* Apply horizontal shuffle. */
const float minimum_time_offset = transdata_get_time_shuffle_offset(trans_datas);
LISTBASE_FOREACH (LinkData *, link, trans_datas) {
TransDataNla *trans_data = (TransDataNla *)link->data;
NlaStrip *strip = trans_data->strip;
strip->start += minimum_time_offset;
strip->end += minimum_time_offset;
BKE_nlameta_flush_transforms(strip);
}
}
/* Memory cleanup. */
LISTBASE_FOREACH (IDGroupedTransData *, group, &grouped_trans_datas) {
BLI_freelistN(&group->trans_datas);
}
BLI_freelistN(&grouped_trans_datas);
}
static void special_aftertrans_update__nla(bContext *C, TransInfo *t)
{
bAnimContext ac;
@ -507,36 +723,55 @@ static void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t))
return;
}
if (ac.datatype) {
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY);
if (!ac.datatype) {
return;
}
/* get channels to work on */
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
TransDataNla *first_trans_data = tc->custom.type.data;
for (ale = anim_data.first; ale; ale = ale->next) {
NlaTrack *nlt = (NlaTrack *)ale->data;
/* Shuffle transformed strips. */
if (ELEM(t->mode, TFM_TRANSLATION)) {
nlastrip_shuffle_transformed(tc, first_trans_data);
}
/* make sure strips are in order again */
BKE_nlatrack_sort_strips(nlt);
/* remove the temp metas */
BKE_nlastrips_clear_metas(&nlt->strips, 0, 1);
/* Clear NLASTRIP_FLAG_INVALID_LOCATION flag. */
TransDataNla *tdn = first_trans_data;
for (int i = 0; i < tc->data_len; i++, tdn++) {
if (tdn->strip == NULL) {
continue;
}
/* General refresh for the outliner because the following might have happened:
* - strips moved between tracks
* - strips swapped order
* - duplicate-move moves to different track. */
WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL);
/* free temp memory */
ANIM_animdata_freelist(&anim_data);
/* Perform after-transform validation. */
ED_nla_postop_refresh(&ac);
tdn->strip->flag &= ~NLASTRIP_FLAG_INVALID_LOCATION;
}
ListBase anim_data = {NULL, NULL};
short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY);
/* get channels to work on */
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
NlaTrack *nlt = (NlaTrack *)ale->data;
/* make sure strips are in order again */
BKE_nlatrack_sort_strips(nlt);
nrupsis marked this conversation as resolved

Use LISTBASE_FOREACH()

Use `LISTBASE_FOREACH()`
/* remove the temp metas */
BKE_nlastrips_clear_metas(&nlt->strips, 0, 1);
}
/* General refresh for the outliner because the following might have happened:
* - strips moved between tracks
* - strips swapped order
* - duplicate-move moves to different track. */
WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL);
/* free temp memory */
ANIM_animdata_freelist(&anim_data);
/* Perform after-transform validation. */
ED_nla_postop_refresh(&ac);
}
/** \} */

View File

@ -832,6 +832,11 @@ typedef enum eNlaStrip_Flag {
/* NLASTRIP_FLAG_MIRROR = (1 << 13), */ /* UNUSED */
/* temporary editing flags */
/** When transforming strips, this flag is set when the strip is placed in an invalid location
* such as overlapping another strip or moved to a locked track. In such cases, the strip's
* location must be corrected after the transform operator is done. */
nrupsis marked this conversation as resolved Outdated

"fixed" can mean 'corrected' as well as 'prevented from moving' (as in 'to fix a shelf to the wall'). Probably better to replace it with "corrected after the transform operator is done" or something along those lines.

"fixed" can mean 'corrected' as well as 'prevented from moving' (as in 'to fix a shelf to the wall'). Probably better to replace it with "corrected after the transform operator is done" or something along those lines.
NLASTRIP_FLAG_INVALID_LOCATION = (1 << 28),
/** NLA strip should ignore frame range and hold settings, and evaluate at global time. */
NLASTRIP_FLAG_NO_TIME_MAP = (1 << 29),
/** NLA-Strip is really just a temporary meta used to facilitate easier transform code */