Refactor: Anim, move Action queries from BKE to the animrig::Action class

Move the following BKE functions to the `animrig::Action` class. Some of
those will be extended to support slots in a future commit; for now they
still operate on all F-Curves in the Action.

| Old                             | New                                 |
|---------------------------------|-------------------------------------|
| `BKE_action_frame_range_calc()` | `Action::get_frame_range_of_keys()` |
| `BKE_action_frame_range_get()`  | `Action::get_frame_range()`         |
| `BKE_action_has_motion()`       | `Action::has_keyframes()`           |
| `BKE_action_has_single_frame()` | `Action::has_single_frame()`        |
| `BKE_action_is_cyclic()`        | `Action::is_cyclic()`               |

Implementations have been copied from the BKE functions. The frame range
functions now return `float2` instead of requiring two `float *r_…`
return parameters.

The `has_motion` function is now renamed to `has_keyframes`, as that is
what the implementation was actually testing for.

The functions now no longer are null-safe. The BKE functions handled a
null action pointer, but IMO that doesn't make sense, and in none of the
call sites I could find where this would actually be valid.

No functional changes.

Ref: #127489

Pull Request: https://projects.blender.org/blender/blender/pulls/127512
This commit is contained in:
Sybren A. Stüvel
2024-09-13 15:04:47 +02:00
parent bcac20a670
commit b952782a44
13 changed files with 408 additions and 335 deletions

View File

@@ -268,9 +268,34 @@ class Action : public ::bAction {
/**
* Return whether this Action actually has any animation data for the given slot.
*
* \see has_keyframes()
*/
bool is_slot_animated(slot_handle_t slot_handle) const;
/**
* Check if the slot with this handle has any keyframes.
*
* \see is_slot_animated()
*/
bool has_keyframes(slot_handle_t action_slot_handle) const ATTR_WARN_UNUSED_RESULT;
/**
* Return whether the action has one unique point in time keyed.
*
* This is mostly for the pose library, which will have different behavior depending on whether
* an Action corresponds to a "pose" (one keyframe) or "animation snippet" (multiple keyframes).
*
* \return `false` when there is no keyframe at all or keys on different points in time, `true`
* when exactly one point in time is keyed.
*/
bool has_single_frame() const ATTR_WARN_UNUSED_RESULT;
/**
* Returns whether this Action is configured as cyclic.
*/
bool is_cyclic() const ATTR_WARN_UNUSED_RESULT;
/**
* Get the layer that should be used for user-level keyframe insertion.
*
@@ -280,6 +305,25 @@ class Action : public ::bAction {
*/
Layer *get_layer_for_keyframing();
/**
* Retrieve the intended playback frame range of the entire Action.
*
* \return a tuple (start frame, end frame). This is either the manually set range (if enabled),
* or the result of a scan of all F-Curves for their first & last frames.
*
* \see get_frame_range_of_keys()
*/
float2 get_frame_range() const ATTR_WARN_UNUSED_RESULT;
/**
* Calculate the extents of this Action.
*
* Performs a scan of all F-Curves for their first & last key frames.
*
* \return tuple (first key frame, last key frame).
*/
float2 get_frame_range_of_keys(bool include_modifiers) const ATTR_WARN_UNUSED_RESULT;
protected:
/** Return the layer's index, or -1 if not found in this Action. */
int64_t find_layer_index(const Layer &layer) const;
@@ -755,6 +799,17 @@ class ChannelBag : public ::ActionChannelBag {
*/
FCurve *fcurve_create_unique(Main *bmain, FCurveDescriptor fcurve_descriptor);
/**
* Append an F-Curve to this ChannelBag.
*
* Ownership of the F-Curve is also transferred to the ChannelBag. The F-Curve
* will not belong to any channel group after appending.
*
* This is considered a low-level function. Things like depsgraph relations
* tagging is left to the caller.
*/
void fcurve_append(FCurve &fcurve);
/**
* Remove an F-Curve from the ChannelBag.
*

View File

@@ -11,6 +11,7 @@
#include "DNA_anim_types.h"
#include "DNA_array_utils.hh"
#include "DNA_defaults.h"
#include "DNA_scene_types.h"
#include "BLI_listbase.h"
#include "BLI_listbase_wrapper.hh"
@@ -702,6 +703,158 @@ void Action::unassign_id(ID &animated_id)
adt->action = nullptr;
}
bool Action::has_keyframes(const slot_handle_t action_slot_handle) const
{
if (this->is_action_legacy()) {
/* Old BKE_action_has_motion(const bAction *act) implementation. */
LISTBASE_FOREACH (const FCurve *, fcu, &this->curves) {
if (fcu->totvert) {
return true;
}
}
return false;
}
for (const FCurve *fcu : fcurves_for_action_slot(*this, action_slot_handle)) {
if (fcu->totvert) {
return true;
}
}
return false;
}
bool Action::has_single_frame() const
{
bool found_key = false;
float found_key_frame = 0.0f;
for (const FCurve *fcu : fcurves_all(*this)) {
switch (fcu->totvert) {
case 0:
/* No keys, so impossible to come to a conclusion on this curve alone. */
continue;
case 1:
/* Single key, which is the complex case, so handle below. */
break;
default:
/* Multiple keys, so there is animation. */
return false;
}
const float this_key_frame = fcu->bezt != nullptr ? fcu->bezt[0].vec[1][0] :
fcu->fpt[0].vec[0];
if (!found_key) {
found_key = true;
found_key_frame = this_key_frame;
continue;
}
/* The graph editor rounds to 1/1000th of a frame, so it's not necessary to be really precise
* with these comparisons. */
if (!compare_ff(found_key_frame, this_key_frame, 0.001f)) {
/* This key differs from the already-found key, so this Action represents animation. */
return false;
}
}
/* There is only a single frame if we found at least one key. */
return found_key;
}
bool Action::is_cyclic() const
{
return (this->flag & ACT_FRAME_RANGE) && (this->flag & ACT_CYCLIC);
}
float2 Action::get_frame_range() const
{
if (this->flag & ACT_FRAME_RANGE) {
return {this->frame_start, this->frame_end};
}
return this->get_frame_range_of_keys(false);
}
float2 Action::get_frame_range_of_keys(const bool include_modifiers) const
{
float min = 999999999.0f, max = -999999999.0f;
bool foundvert = false, foundmod = false;
for (const FCurve *fcu : fcurves_all(*this)) {
/* if curve has keyframes, consider them first */
if (fcu->totvert) {
float nmin, nmax;
/* get extents for this curve
* - no "selected only", since this is often used in the backend
* - no "minimum length" (we will apply this later), otherwise
* single-keyframe curves will increase the overall length by
* a phantom frame (#50354)
*/
BKE_fcurve_calc_range(fcu, &nmin, &nmax, false);
/* compare to the running tally */
min = min_ff(min, nmin);
max = max_ff(max, nmax);
foundvert = true;
}
/* if include_modifiers is enabled, need to consider modifiers too
* - only really care about the last modifier
*/
if ((include_modifiers) && (fcu->modifiers.last)) {
FModifier *fcm = static_cast<FModifier *>(fcu->modifiers.last);
/* only use the maximum sensible limits of the modifiers if they are more extreme */
switch (fcm->type) {
case FMODIFIER_TYPE_LIMITS: /* Limits F-Modifier */
{
FMod_Limits *fmd = (FMod_Limits *)fcm->data;
if (fmd->flag & FCM_LIMIT_XMIN) {
min = min_ff(min, fmd->rect.xmin);
}
if (fmd->flag & FCM_LIMIT_XMAX) {
max = max_ff(max, fmd->rect.xmax);
}
break;
}
case FMODIFIER_TYPE_CYCLES: /* Cycles F-Modifier */
{
FMod_Cycles *fmd = (FMod_Cycles *)fcm->data;
if (fmd->before_mode != FCM_EXTRAPOLATE_NONE) {
min = MINAFRAMEF;
}
if (fmd->after_mode != FCM_EXTRAPOLATE_NONE) {
max = MAXFRAMEF;
}
break;
}
/* TODO: function modifier may need some special limits */
default: /* all other standard modifiers are on the infinite range... */
min = MINAFRAMEF;
max = MAXFRAMEF;
break;
}
foundmod = true;
}
/* This block is here just so that editors/IDEs do not get confused about the two opening
* curly braces in the `#ifdef WITH_ANIM_BAKLAVA` block above, but one closing curly brace
* here. */
}
if (foundvert || foundmod) {
return float2{max_ff(min, MINAFRAMEF), min_ff(max, MAXFRAMEF)};
}
return float2{0.0f, 0.0f};
}
/* ----- ActionLayer implementation ----------- */
Layer::Layer(const Layer &other)
@@ -1361,6 +1514,15 @@ FCurve &ChannelBag::fcurve_create(Main *bmain, FCurveDescriptor fcurve_descripto
return *new_fcurve;
}
void ChannelBag::fcurve_append(FCurve &fcurve)
{
/* Appended F-Curves don't belong to any group yet, so better make sure their
* group pointer reflects that. */
fcurve.grp = nullptr;
grow_array_and_append(&this->fcurve_array, &this->fcurve_array_num, &fcurve);
}
static void fcurve_ptr_destructor(FCurve **fcurve_ptr)
{
BKE_fcurve_free(*fcurve_ptr);

View File

@@ -886,6 +886,134 @@ TEST_F(ActionLayersTest, empty_to_layered)
/*-----------------------------------------------------------*/
/* Allocate fcu->bezt, and also return a unique_ptr to it for easily freeing the memory. */
static void allocate_keyframes(FCurve &fcu, const size_t num_keyframes)
{
fcu.bezt = MEM_cnew_array<BezTriple>(num_keyframes, __func__);
}
/* Append keyframe, assumes that fcu->bezt is allocated and has enough space. */
static void add_keyframe(FCurve &fcu, float x, float y)
{
/* The insert_keyframe functions are in the editors, so we cannot link to those here. */
BezTriple the_keyframe;
memset(&the_keyframe, 0, sizeof(the_keyframe));
/* Copied from insert_vert_fcurve() in `keyframing.cc`. */
the_keyframe.vec[0][0] = x - 1.0f;
the_keyframe.vec[0][1] = y;
the_keyframe.vec[1][0] = x;
the_keyframe.vec[1][1] = y;
the_keyframe.vec[2][0] = x + 1.0f;
the_keyframe.vec[2][1] = y;
memcpy(&fcu.bezt[fcu.totvert], &the_keyframe, sizeof(the_keyframe));
fcu.totvert++;
}
static void add_fcurve_to_action(Action &action, FCurve &fcu)
{
Slot &slot = action.slot_array_num > 0 ? *action.slot(0) : action.slot_add();
action.layer_keystrip_ensure();
KeyframeStrip &strip = action.layer(0)->strip(0)->as<KeyframeStrip>();
ChannelBag &cbag = strip.channelbag_for_slot_ensure(slot);
cbag.fcurve_append(fcu);
}
class ActionQueryTest : public testing::Test {
public:
Main *bmain;
static void SetUpTestSuite()
{
/* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
CLG_init();
/* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
BKE_idtype_init();
}
static void TearDownTestSuite()
{
CLG_exit();
}
void SetUp() override
{
bmain = BKE_main_new();
}
void TearDown() override
{
BKE_main_free(bmain);
}
Action &action_new()
{
return *static_cast<Action *>(BKE_id_new(bmain, ID_AC, "ACÄnimåtië"));
}
};
TEST_F(ActionQueryTest, BKE_action_frame_range_calc)
{
/* No FCurves. */
{
const Action &empty = action_new();
EXPECT_EQ((float2{0.0f, 0.0f}), empty.get_frame_range_of_keys(false));
}
/* One curve with one key. */
{
FCurve &fcu = *MEM_cnew<FCurve>(__func__);
allocate_keyframes(fcu, 1);
add_keyframe(fcu, 1.0f, 2.0f);
Action &action = action_new();
add_fcurve_to_action(action, fcu);
const float2 frame_range = action.get_frame_range_of_keys(false);
EXPECT_FLOAT_EQ(frame_range[0], 1.0f);
EXPECT_FLOAT_EQ(frame_range[1], 1.0f);
}
/* Two curves with one key each on different frames. */
{
FCurve &fcu1 = *MEM_cnew<FCurve>(__func__);
FCurve &fcu2 = *MEM_cnew<FCurve>(__func__);
allocate_keyframes(fcu1, 1);
allocate_keyframes(fcu2, 1);
add_keyframe(fcu1, 1.0f, 2.0f);
add_keyframe(fcu2, 1.5f, 2.0f);
Action &action = action_new();
add_fcurve_to_action(action, fcu1);
add_fcurve_to_action(action, fcu2);
const float2 frame_range = action.get_frame_range_of_keys(false);
EXPECT_FLOAT_EQ(frame_range[0], 1.0f);
EXPECT_FLOAT_EQ(frame_range[1], 1.5f);
}
/* One curve with two keys. */
{
FCurve &fcu = *MEM_cnew<FCurve>(__func__);
allocate_keyframes(fcu, 2);
add_keyframe(fcu, 1.0f, 2.0f);
add_keyframe(fcu, 1.5f, 2.0f);
Action &action = action_new();
add_fcurve_to_action(action, fcu);
const float2 frame_range = action.get_frame_range_of_keys(false);
EXPECT_FLOAT_EQ(frame_range[0], 1.0f);
EXPECT_FLOAT_EQ(frame_range[1], 1.5f);
}
/* TODO: action with fcurve modifiers. */
}
/*-----------------------------------------------------------*/
class ChannelBagTest : public testing::Test {
public:
ChannelBag *channel_bag;

View File

@@ -533,7 +533,7 @@ static SingleKeyingResult insert_keyframe_fcurve_value(Main *bmain,
const bool is_new_curve = (fcu->totvert == 0);
/* If the curve has only one key, make it cyclic if appropriate. */
const bool is_cyclic_action = (flag & INSERTKEY_CYCLE_AWARE) && BKE_action_is_cyclic(act);
const bool is_cyclic_action = (flag & INSERTKEY_CYCLE_AWARE) && act->wrap().is_cyclic();
if (is_cyclic_action && fcu->totvert == 1) {
make_new_fcurve_cyclic(fcu, {act->frame_start, act->frame_end});

View File

@@ -42,31 +42,6 @@ bAction *BKE_action_add(Main *bmain, const char name[]);
/* Action API ----------------- */
/**
* Calculate the extents of given action.
*/
void BKE_action_frame_range_calc(const bAction *act,
bool include_modifiers,
float *r_start,
float *r_end) ATTR_NONNULL(3, 4);
/**
* Retrieve the intended playback frame range, using the manually set range if available,
* or falling back to scanning F-Curves for their first & last frames otherwise.
*/
void BKE_action_frame_range_get(const bAction *act, float *r_start, float *r_end)
ATTR_NONNULL(2, 3);
/**
* Check if the given action has any keyframes.
*/
bool BKE_action_has_motion(const bAction *act, int32_t action_slot_handle) ATTR_WARN_UNUSED_RESULT;
/**
* Is the action configured as cyclic.
*/
bool BKE_action_is_cyclic(const bAction *act) ATTR_WARN_UNUSED_RESULT;
/**
* Remove all fcurves from the action.
*/
@@ -138,17 +113,6 @@ bActionGroup *BKE_action_group_find_name(bAction *act, const char name[]);
*/
void action_groups_clear_tempflags(bAction *act);
/**
* Return whether the action has one unique point in time keyed.
*
* This is mostly for the pose library, which will have different behavior depending on whether an
* Action corresponds to a "pose" (one keyframe) or "animation snippet" (multiple keyframes).
*
* \return `false` when there is no keyframe at all or keys on different points in time, `true`
* when exactly one point in time is keyed.
*/
bool BKE_action_has_single_frame(const bAction *act) ATTR_WARN_UNUSED_RESULT;
/* Pose API ----------------- */
void BKE_pose_channel_free(bPoseChannel *pchan) ATTR_NONNULL(1);

View File

@@ -685,7 +685,7 @@ static void action_blend_read_data(BlendDataReader *reader, ID *id)
static IDProperty *action_asset_type_property(const bAction *action)
{
using namespace blender;
const bool is_single_frame = BKE_action_has_single_frame(action);
const bool is_single_frame = action && action->wrap().has_single_frame();
return bke::idprop::create("is_single_frame", int(is_single_frame)).release();
}
@@ -1767,196 +1767,6 @@ void BKE_pose_remove_group_index(bPose *pose, const int index)
}
}
/* ************** F-Curve Utilities for Actions ****************** */
bool BKE_action_has_motion(const bAction *act, const int32_t action_slot_handle)
{
using namespace blender;
if (!act) {
return false;
}
const animrig::Action &action = act->wrap();
if (action.is_action_legacy()) {
LISTBASE_FOREACH (FCurve *, fcu, &act->curves) {
if (fcu->totvert) {
return true;
}
}
return false;
}
#ifdef WITH_ANIM_BAKLAVA
for (const FCurve *fcu : animrig::fcurves_for_action_slot(action, action_slot_handle)) {
if (fcu->totvert) {
return true;
}
}
#endif
return false;
}
bool BKE_action_has_single_frame(const bAction *act)
{
if (act == nullptr || BLI_listbase_is_empty(&act->curves)) {
return false;
}
bool found_key = false;
float found_key_frame = 0.0f;
LISTBASE_FOREACH (FCurve *, fcu, &act->curves) {
switch (fcu->totvert) {
case 0:
/* No keys, so impossible to come to a conclusion on this curve alone. */
continue;
case 1:
/* Single key, which is the complex case, so handle below. */
break;
default:
/* Multiple keys, so there is animation. */
return false;
}
const float this_key_frame = fcu->bezt != nullptr ? fcu->bezt[0].vec[1][0] :
fcu->fpt[0].vec[0];
if (!found_key) {
found_key = true;
found_key_frame = this_key_frame;
continue;
}
/* The graph editor rounds to 1/1000th of a frame, so it's not necessary to be really precise
* with these comparisons. */
if (!compare_ff(found_key_frame, this_key_frame, 0.001f)) {
/* This key differs from the already-found key, so this Action represents animation. */
return false;
}
}
/* There is only a single frame if we found at least one key. */
return found_key;
}
void BKE_action_frame_range_calc(const bAction *act,
const bool include_modifiers,
float *r_start,
float *r_end)
{
if (!act) {
*r_start = 0.0f;
*r_end = 0.0f;
return;
}
float min = 999999999.0f, max = -999999999.0f;
bool foundvert = false, foundmod = false;
#ifdef WITH_ANIM_BAKLAVA
const blender::animrig::Action &action = act->wrap();
for (const FCurve *fcu : fcurves_all(action)) {
#else
LISTBASE_FOREACH (const FCurve *, fcu, &act->curves) {
#endif /* WITH_ANIM_BAKLAVA */
/* if curve has keyframes, consider them first */
if (fcu->totvert) {
float nmin, nmax;
/* get extents for this curve
* - no "selected only", since this is often used in the backend
* - no "minimum length" (we will apply this later), otherwise
* single-keyframe curves will increase the overall length by
* a phantom frame (#50354)
*/
BKE_fcurve_calc_range(fcu, &nmin, &nmax, false);
/* compare to the running tally */
min = min_ff(min, nmin);
max = max_ff(max, nmax);
foundvert = true;
}
/* if include_modifiers is enabled, need to consider modifiers too
* - only really care about the last modifier
*/
if ((include_modifiers) && (fcu->modifiers.last)) {
FModifier *fcm = static_cast<FModifier *>(fcu->modifiers.last);
/* only use the maximum sensible limits of the modifiers if they are more extreme */
switch (fcm->type) {
case FMODIFIER_TYPE_LIMITS: /* Limits F-Modifier */
{
FMod_Limits *fmd = (FMod_Limits *)fcm->data;
if (fmd->flag & FCM_LIMIT_XMIN) {
min = min_ff(min, fmd->rect.xmin);
}
if (fmd->flag & FCM_LIMIT_XMAX) {
max = max_ff(max, fmd->rect.xmax);
}
break;
}
case FMODIFIER_TYPE_CYCLES: /* Cycles F-Modifier */
{
FMod_Cycles *fmd = (FMod_Cycles *)fcm->data;
if (fmd->before_mode != FCM_EXTRAPOLATE_NONE) {
min = MINAFRAMEF;
}
if (fmd->after_mode != FCM_EXTRAPOLATE_NONE) {
max = MAXFRAMEF;
}
break;
}
/* TODO: function modifier may need some special limits */
default: /* all other standard modifiers are on the infinite range... */
min = MINAFRAMEF;
max = MAXFRAMEF;
break;
}
foundmod = true;
}
/* This block is here just so that editors/IDEs do not get confused about the two opening curly
* braces in the `#ifdef WITH_ANIM_BAKLAVA` block above, but one closing curly brace here. */
#ifdef WITH_ANIM_BAKLAVA
}
#else
}
#endif /* WITH_ANIM_BAKLAVA */
if (foundvert || foundmod) {
*r_start = max_ff(min, MINAFRAMEF);
*r_end = min_ff(max, MAXFRAMEF);
}
else {
*r_start = 0.0f;
*r_end = 0.0f;
}
}
void BKE_action_frame_range_get(const bAction *act, float *r_start, float *r_end)
{
if (act && (act->flag & ACT_FRAME_RANGE)) {
*r_start = act->frame_start;
*r_end = act->frame_end;
}
else {
BKE_action_frame_range_calc(act, false, r_start, r_end);
}
BLI_assert(*r_start <= *r_end);
}
bool BKE_action_is_cyclic(const bAction *act)
{
return act && (act->flag & ACT_FRAME_RANGE) && (act->flag & ACT_CYCLIC);
}
/* ************** Pose Management Tools ****************** */
void BKE_pose_rest(bPose *pose, bool selected_bones_only)

View File

@@ -6,6 +6,8 @@
#include "BKE_action.hh"
#include "ANIM_action.hh"
#include "DNA_action_types.h"
#include "DNA_anim_types.h"
@@ -163,13 +165,10 @@ void add_keyframe(FCurve *fcu, float x, float y)
TEST(action_assets, BKE_action_has_single_frame)
{
/* Null action. */
EXPECT_FALSE(BKE_action_has_single_frame(nullptr)) << "Null Action cannot have a single frame.";
/* No FCurves. */
{
const bAction empty = {{nullptr}};
EXPECT_FALSE(BKE_action_has_single_frame(&empty))
EXPECT_FALSE(empty.wrap().has_single_frame())
<< "Action without FCurves cannot have a single frame.";
}
@@ -182,7 +181,7 @@ TEST(action_assets, BKE_action_has_single_frame)
bAction action = {{nullptr}};
BLI_addtail(&action.curves, &fcu);
EXPECT_TRUE(BKE_action_has_single_frame(&action))
EXPECT_TRUE(action.wrap().has_single_frame())
<< "Action with one FCurve and one key should have single frame.";
}
@@ -199,12 +198,12 @@ TEST(action_assets, BKE_action_has_single_frame)
BLI_addtail(&action.curves, &fcu1);
BLI_addtail(&action.curves, &fcu2);
EXPECT_TRUE(BKE_action_has_single_frame(&action))
EXPECT_TRUE(action.wrap().has_single_frame())
<< "Two FCurves with keys on the same frame should have single frame.";
/* Modify the 2nd curve so it's keyed on a different frame. */
fcu2.bezt[0].vec[1][0] = 2.0f;
EXPECT_FALSE(BKE_action_has_single_frame(&action))
EXPECT_FALSE(action.wrap().has_single_frame())
<< "Two FCurves with keys on different frames should have animation.";
}
@@ -218,71 +217,9 @@ TEST(action_assets, BKE_action_has_single_frame)
bAction action = {{nullptr}};
BLI_addtail(&action.curves, &fcu);
EXPECT_FALSE(BKE_action_has_single_frame(&action))
EXPECT_FALSE(action.wrap().has_single_frame())
<< "Action with one FCurve and two keys must have animation.";
}
}
TEST(action, BKE_action_frame_range_calc)
{
float start, end;
/* No FCurves. */
{
const bAction empty = {{nullptr}};
BKE_action_frame_range_calc(&empty, false, &start, &end);
EXPECT_FLOAT_EQ(start, 0.0f);
EXPECT_FLOAT_EQ(end, 0.0f);
}
/* One curve with one key. */
{
FCurve fcu = {nullptr};
std::unique_ptr<BezTriple[]> bezt = allocate_keyframes(&fcu, 1);
add_keyframe(&fcu, 1.0f, 2.0f);
bAction action = {{nullptr}};
BLI_addtail(&action.curves, &fcu);
BKE_action_frame_range_calc(&action, false, &start, &end);
EXPECT_FLOAT_EQ(start, 1.0f);
EXPECT_FLOAT_EQ(end, 1.0f);
}
/* Two curves with one key each on different frames. */
{
FCurve fcu1 = {nullptr};
FCurve fcu2 = {nullptr};
std::unique_ptr<BezTriple[]> bezt1 = allocate_keyframes(&fcu1, 1);
std::unique_ptr<BezTriple[]> bezt2 = allocate_keyframes(&fcu2, 1);
add_keyframe(&fcu1, 1.0f, 2.0f);
add_keyframe(&fcu2, 1.5f, 2.0f);
bAction action = {{nullptr}};
BLI_addtail(&action.curves, &fcu1);
BLI_addtail(&action.curves, &fcu2);
BKE_action_frame_range_calc(&action, false, &start, &end);
EXPECT_FLOAT_EQ(start, 1.0f);
EXPECT_FLOAT_EQ(end, 1.5f);
}
/* One curve with two keys. */
{
FCurve fcu = {nullptr};
std::unique_ptr<BezTriple[]> bezt = allocate_keyframes(&fcu, 2);
add_keyframe(&fcu, 1.0f, 2.0f);
add_keyframe(&fcu, 1.5f, 2.0f);
bAction action = {{nullptr}};
BLI_addtail(&action.curves, &fcu);
BKE_action_frame_range_calc(&action, false, &start, &end);
EXPECT_FLOAT_EQ(start, 1.0f);
EXPECT_FLOAT_EQ(end, 1.5f);
}
/* TODO: action with fcurve modifiers. */
}
} // namespace blender::bke::tests

View File

@@ -22,6 +22,7 @@
#include "BLI_listbase_wrapper.hh"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string_utils.hh"
#include "BLI_utildefines.h"
@@ -3327,22 +3328,25 @@ static void animsys_create_action_track_strip(const AnimData *adt,
memset(r_action_strip, 0, sizeof(NlaStrip));
{ /* Set settings of dummy NLA strip from AnimData settings. */
bAction *action = adt->action;
slot_handle_t slot_handle = adt->slot_handle;
/* Set settings of dummy NLA strip from AnimData settings. */
bAction *action = adt->action;
slot_handle_t slot_handle = adt->slot_handle;
if (adt->flag & ADT_NLA_EDIT_ON) {
action = adt->tmpact;
slot_handle = adt->tmp_slot_handle;
}
r_action_strip->act = action;
r_action_strip->action_slot_handle = slot_handle;
if (adt->flag & ADT_NLA_EDIT_ON) {
action = adt->tmpact;
slot_handle = adt->tmp_slot_handle;
}
r_action_strip->act = action;
r_action_strip->action_slot_handle = slot_handle;
/* Action range is calculated taking F-Modifiers into account
* (which making new strips doesn't do due to the troublesome nature of that). */
BKE_action_frame_range_calc(
r_action_strip->act, true, &r_action_strip->actstart, &r_action_strip->actend);
const float2 frame_range = action ? action->wrap().get_frame_range_of_keys(true) :
float2{0.0f, 0.0f};
r_action_strip->actstart = frame_range[0];
r_action_strip->actend = frame_range[1];
BKE_nla_clip_length_ensure_nonzero(&r_action_strip->actstart, &r_action_strip->actend);
r_action_strip->start = r_action_strip->actstart;
r_action_strip->end = r_action_strip->actend;

View File

@@ -492,16 +492,19 @@ NlaStrip *BKE_nlastrip_new(bAction *act, ID &animated_id)
}
/* Enable cyclic time for known cyclic actions. */
if (BKE_action_is_cyclic(act)) {
Action &action = act->wrap();
if (action.is_cyclic()) {
strip->flag |= NLASTRIP_FLAG_USR_TIME_CYCLIC;
}
/* Assign the Action, and automatically choose a suitable slot. The caller can change the slot to
* something more specific later, if necessary. */
nla::assign_action(*strip, act->wrap(), animated_id);
nla::assign_action(*strip, action, animated_id);
/* determine initial range */
BKE_action_frame_range_get(strip->act, &strip->actstart, &strip->actend);
const float2 frame_range = action.get_frame_range();
strip->actstart = frame_range[0];
strip->actend = frame_range[1];
BKE_nla_clip_length_ensure_nonzero(&strip->actstart, &strip->actend);
strip->start = strip->actstart;
strip->end = strip->actend;
@@ -1639,7 +1642,10 @@ void BKE_nlastrip_recalculate_bounds_sync_action(NlaStrip *strip)
prev_actstart = strip->actstart;
BKE_action_frame_range_get(strip->act, &strip->actstart, &strip->actend);
const float2 frame_range = strip->act->wrap().get_frame_range();
strip->actstart = frame_range[0];
strip->actend = frame_range[1];
BKE_nla_clip_length_ensure_nonzero(&strip->actstart, &strip->actend);
/* Set start such that key's do not visually move, to preserve the overall animation result. */
@@ -2164,13 +2170,14 @@ void BKE_nla_action_pushdown(const OwnedAnimData owned_adt, const bool is_libove
* as that will cause us grief down the track
*/
/* TODO: what about modifiers? */
if (!BKE_action_has_motion(adt->action, adt->slot_handle)) {
animrig::Action &action = adt->action->wrap();
if (!action.has_keyframes(adt->slot_handle)) {
CLOG_ERROR(&LOG, "action has no data");
return;
}
/* Add a new NLA strip to the track, which references the active action + slot.*/
strip = BKE_nlastack_add_strip(owned_adt, adt->action, is_liboverride);
strip = BKE_nlastack_add_strip(owned_adt, &action, is_liboverride);
if (strip == nullptr) {
return;
}

View File

@@ -366,7 +366,7 @@ static int action_pushdown_exec(bContext *C, wmOperator *op)
if (adt) {
/* Perform the push-down operation
* - This will deal with all the AnimData-side user-counts. */
if (!BKE_action_has_motion(adt->action, adt->slot_handle)) {
if (!adt->action->wrap().has_keyframes(adt->slot_handle)) {
/* action may not be suitable... */
BKE_report(op->reports, RPT_WARNING, "Action must have at least one keyframe or F-Modifier");
return OPERATOR_CANCELLED;
@@ -423,7 +423,7 @@ static int action_stash_exec(bContext *C, wmOperator *op)
/* Perform stashing operation */
if (adt) {
/* don't do anything if this action is empty... */
if (!BKE_action_has_motion(adt->action, adt->slot_handle)) {
if (!adt->action->wrap().has_keyframes(adt->slot_handle)) {
/* action may not be suitable... */
BKE_report(op->reports, RPT_WARNING, "Action must have at least one keyframe or F-Modifier");
return OPERATOR_CANCELLED;
@@ -531,7 +531,7 @@ static int action_stash_create_exec(bContext *C, wmOperator *op)
}
else if (adt) {
/* Perform stashing operation */
if (!BKE_action_has_motion(adt->action, adt->slot_handle)) {
if (!adt->action->wrap().has_keyframes(adt->slot_handle)) {
/* don't do anything if this action is empty... */
BKE_report(op->reports, RPT_WARNING, "Action must have at least one keyframe or F-Modifier");
return OPERATOR_CANCELLED;

View File

@@ -44,6 +44,8 @@
#include "nla_intern.hh" /* own include */
#include "nla_private.h"
using namespace blender;
/* *********************************************** */
/* Strips */
@@ -881,12 +883,12 @@ void draw_nla_main_data(bAnimContext *ac, SpaceNla *snla, ARegion *region)
break;
}
case NLASTRIP_EXTEND_HOLD_FORWARD: {
float r_start;
float r_end;
BKE_action_frame_range_get(static_cast<bAction *>(ale->data), &r_start, &r_end);
BKE_nla_clip_length_ensure_nonzero(&r_start, &r_end);
const animrig::Action &action = static_cast<bAction *>(ale->data)->wrap();
float2 frame_range = action.get_frame_range();
BKE_nla_clip_length_ensure_nonzero(&frame_range[0], &frame_range[1]);
immRectf(pos, r_end, ymin + NLATRACK_SKIP, v2d->cur.xmax, ymax - NLATRACK_SKIP);
immRectf(
pos, frame_range[1], ymin + NLATRACK_SKIP, v2d->cur.xmax, ymax - NLATRACK_SKIP);
break;
}
case NLASTRIP_EXTEND_NOTHING:

View File

@@ -1199,11 +1199,9 @@ void animrecord_check_state(TransInfo *t, ID *id)
/* Perform push-down manually with some differences
* NOTE: #BKE_nla_action_pushdown() sync warning. */
if ((adt->action) && !(adt->flag & ADT_NLA_EDIT_ON)) {
float astart, aend;
/* Only push down if action is more than 1-2 frames long. */
BKE_action_frame_range_calc(adt->action, true, &astart, &aend);
if (aend > astart + 2.0f) {
const float2 frame_range = adt->action->wrap().get_frame_range_of_keys(true);
if (frame_range[1] > frame_range[0] + 2.0f) {
/* TODO: call BKE_nla_action_pushdown() instead? */
/* Add a new NLA strip to the track, which references the active action + slot.*/

View File

@@ -1301,7 +1301,9 @@ static bool rna_Action_is_action_layered_get(PointerRNA *ptr)
static void rna_Action_frame_range_get(PointerRNA *ptr, float *r_values)
{
BKE_action_frame_range_get((bAction *)ptr->owner_id, &r_values[0], &r_values[1]);
const float2 frame_range = rna_action(ptr).get_frame_range();
r_values[0] = frame_range[0];
r_values[1] = frame_range[1];
}
static void rna_Action_frame_range_set(PointerRNA *ptr, const float *values)
@@ -1317,23 +1319,27 @@ static void rna_Action_frame_range_set(PointerRNA *ptr, const float *values)
static void rna_Action_curve_frame_range_get(PointerRNA *ptr, float *values)
{ /* don't include modifiers because they too easily can have very large
* ranges: MINAFRAMEF to MAXFRAMEF. */
BKE_action_frame_range_calc((bAction *)ptr->owner_id, false, values, values + 1);
const float2 frame_range = rna_action(ptr).get_frame_range_of_keys(false);
values[0] = frame_range[0];
values[1] = frame_range[1];
}
static void rna_Action_use_frame_range_set(PointerRNA *ptr, bool value)
{
bAction *data = (bAction *)ptr->owner_id;
animrig::Action &action = rna_action(ptr);
if (value) {
/* If the frame range is blank, initialize it by scanning F-Curves. */
if ((data->frame_start == data->frame_end) && (data->frame_start == 0)) {
BKE_action_frame_range_calc(data, false, &data->frame_start, &data->frame_end);
if ((action.frame_start == action.frame_end) && (action.frame_start == 0)) {
const float2 frame_range = action.get_frame_range_of_keys(false);
action.frame_start = frame_range[0];
action.frame_end = frame_range[1];
}
data->flag |= ACT_FRAME_RANGE;
action.flag |= ACT_FRAME_RANGE;
}
else {
data->flag &= ~ACT_FRAME_RANGE;
action.flag &= ~ACT_FRAME_RANGE;
}
}