Refactor: keyframing functions return more detailed status #117449
|
@ -11,6 +11,7 @@ set(INC
|
|||
../editors/include
|
||||
../makesrna
|
||||
../windowmanager
|
||||
../../../extern/fmtlib/include
|
||||
# RNA_prototypes.h
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
#include "ANIM_animdata.hh"
|
||||
#include "ANIM_fcurve.hh"
|
||||
|
@ -46,6 +48,45 @@
|
|||
|
||||
namespace blender::animrig {
|
||||
ChrisLend marked this conversation as resolved
Outdated
|
||||
|
||||
enum class SingleKeyingResult {
|
||||
SUCCESS = 0,
|
||||
CANNOT_CREATE_FCURVE,
|
||||
FCURVE_NOT_KEYFRAMEABLE,
|
||||
ChrisLend marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
There is no constructor of There is no constructor of `KeyingResult`, so this comment is a bit misleading. It might be better to add an extra `_KEYING_RESULT_MAX`, which you can then use instead of `int(KeyingResultOptions::NO_KEY_NEEDED) + 1)` to remove the interdependency of the code. Do add a comment that that should be the last item in the enum.
|
||||
NO_KEY_NEEDED,
|
||||
/* Make sure to always keep this at the end of the enum. */
|
||||
_KEYING_RESULT_MAX,
|
||||
};
|
||||
|
||||
ChrisLend marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
I think this can be better described as "Mapping from I think this can be better described as "Mapping from `SingleKeyingResult` to the number of times that that result occurred". Or something along those lines.
|
||||
class CombinedKeyingResult {
|
||||
ChrisLend marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
Rename to Rename to `result_counter_` (trailing `_` because `private`).
|
||||
private:
|
||||
/* The index to the array maps a `SingleKeyingResult` to the number of times this result has
|
||||
* occurred. */
|
||||
ChrisLend marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
I think I think `add()` would be enough, given that it's clear what it adds from its name `AggregatedKeyingResult` and the parameter.
|
||||
std::array<int, int(SingleKeyingResult::_KEYING_RESULT_MAX)> result_counter{0};
|
||||
|
||||
public:
|
||||
void add(const SingleKeyingResult result)
|
||||
{
|
||||
ChrisLend marked this conversation as resolved
Sybren A. Stüvel
commented
For reporting I think it's better if this is changed to
Without this, I don't think it's worth going through the efforts of using the array and keeping the separate counts. For reporting I think it's better if this is changed to `int get_count(const SingleKeyingResult result)`. This has a few advantages:
- Not all results are errors, so `has_error(SUCCESS)` is weird.
- Instead of "One or more FCurves ..." you can write "{count} FCurves".
Without this, I don't think it's worth going through the efforts of using the array and keeping the separate counts.
how does how does `{count} FCurves` work?
as far as I can tell that is a C++ 20 feature
Sybren A. Stüvel
commented
Oh sorry, that was just me typing shorthand. But, Oh sorry, that was just me typing shorthand. But, `fmt::format("{} F-Curves…", the_count)` will bring you quite close.
Of course there's also the always-annoying handling of the singular form :/
done that, had to include done that, had to include `../../../extern/fmtlib/include` in the `CMakeLists.txt` for that
Let me know if that is ok
Sybren A. Stüvel
commented
Yeah that seems to be the way to do it. Yeah that seems to be the way to do it.
|
||||
result_counter[int(result)]++;
|
||||
}
|
||||
|
||||
int get_count(const SingleKeyingResult result) const
|
||||
{
|
||||
return result_counter[int(result)];
|
||||
}
|
||||
ChrisLend marked this conversation as resolved
Sybren A. Stüvel
commented
This can be simplified to:
This can be simplified to:
```cpp
for (const int result_count : result_counter) {
if (result_count) {
return true;
}
}
```
since since `SUCCESS` is at index 0 I don't think I can do that.
That's why I have `int i=1`
There might be a `[1:]` equivalent in C++ that I haven't found though
Sybren A. Stüvel
commented
Hmmm good point. You could keep track of errors & successes in a different way, that way the array would only contain errors. I don't think it's worth the trouble though, juts keep the code as-is. Maybe add a comment for people who overlook the And add a comment that this code assumes that 0 is Hmmm good point. You could keep track of errors & successes in a different way, that way the array would only contain errors. I don't think it's worth the trouble though, juts keep the code as-is. Maybe add a comment for people who overlook the `i=1` bit ;-)
And add a comment that this code assumes that 0 is `SUCCESS` and the rest of the enum has sequential values. That's not always a given.
|
||||
|
||||
bool has_errors() const
|
||||
{
|
||||
/* For loop starts at 1 to skip the SUCCESS flag. Assumes that SUCCESS is 0 and the rest of the
|
||||
* enum are sequential values. */
|
||||
for (int i = 1; i < result_counter.size(); i++) {
|
||||
if (result_counter[i] > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void update_autoflags_fcurve_direct(FCurve *fcu, PropertyRNA *prop)
|
||||
{
|
||||
/* Set additional flags for the F-Curve (i.e. only integer values). */
|
||||
|
@ -309,11 +350,11 @@ static float nla_time_remap(const AnimationEvalContext *anim_eval_context,
|
|||
}
|
||||
|
||||
/* Insert the specified keyframe value into a single F-Curve. */
|
||||
static bool insert_keyframe_value(
|
||||
static SingleKeyingResult insert_keyframe_value(
|
||||
FCurve *fcu, float cfra, float curval, eBezTriple_KeyframeType keytype, eInsertKeyFlags flag)
|
||||
{
|
||||
if (!BKE_fcurve_is_keyframable(fcu)) {
|
||||
return false;
|
||||
return SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE;
|
||||
}
|
||||
|
||||
/* Adjust coordinates for cycle aware insertion. */
|
||||
|
@ -329,17 +370,19 @@ static bool insert_keyframe_value(
|
|||
|
||||
if (flag & INSERTKEY_NEEDED) {
|
||||
if (!new_key_needed(fcu, cfra, curval)) {
|
||||
return false;
|
||||
return SingleKeyingResult::NO_KEY_NEEDED;
|
||||
}
|
||||
|
||||
if (insert_vert_fcurve(fcu, {cfra, curval}, settings, flag) < 0) {
|
||||
Sybren A. Stüvel
commented
This code got popped out of the This code got popped out of the `if (flag & INSERTKEY_NEEDED)`. Even though the new code might do the same thing, I would advice against making such changes in this PR. It's much easier to see what's going on when *only* the result type changes, and not the structure of the code itself.
|
||||
return false;
|
||||
return SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE;
|
||||
}
|
||||
|
||||
return true;
|
||||
return SingleKeyingResult::SUCCESS;
|
||||
}
|
||||
|
||||
return insert_vert_fcurve(fcu, {cfra, curval}, settings, flag) >= 0;
|
||||
if (insert_vert_fcurve(fcu, {cfra, curval}, settings, flag) < 0) {
|
||||
return SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE;
|
||||
}
|
||||
|
||||
return SingleKeyingResult::SUCCESS;
|
||||
}
|
||||
|
||||
bool insert_keyframe_direct(ReportList *reports,
|
||||
|
@ -401,9 +444,9 @@ bool insert_keyframe_direct(ReportList *reports,
|
|||
}
|
||||
|
||||
const float cfra = anim_eval_context->eval_time;
|
||||
const bool success = insert_keyframe_value(fcu, cfra, current_value, keytype, flag);
|
||||
const SingleKeyingResult result = insert_keyframe_value(fcu, cfra, current_value, keytype, flag);
|
||||
|
||||
if (!success) {
|
||||
if (result != SingleKeyingResult::SUCCESS) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"Failed to insert keys on F-Curve with path '%s[%d]', ensure that it is not "
|
||||
|
@ -411,22 +454,21 @@ bool insert_keyframe_direct(ReportList *reports,
|
|||
fcu->rna_path,
|
||||
fcu->array_index);
|
||||
}
|
||||
return success;
|
||||
return result == SingleKeyingResult::SUCCESS;
|
||||
}
|
||||
|
||||
/** Find or create the FCurve based on the given path, and insert the specified value into it. */
|
||||
static bool insert_keyframe_fcurve_value(Main *bmain,
|
||||
ReportList *reports,
|
||||
PointerRNA *ptr,
|
||||
PropertyRNA *prop,
|
||||
bAction *act,
|
||||
const char group[],
|
||||
const char rna_path[],
|
||||
int array_index,
|
||||
const float fcurve_frame,
|
||||
float curval,
|
||||
eBezTriple_KeyframeType keytype,
|
||||
eInsertKeyFlags flag)
|
||||
static SingleKeyingResult insert_keyframe_fcurve_value(Main *bmain,
|
||||
PointerRNA *ptr,
|
||||
PropertyRNA *prop,
|
||||
bAction *act,
|
||||
const char group[],
|
||||
const char rna_path[],
|
||||
int array_index,
|
||||
const float fcurve_frame,
|
||||
float curval,
|
||||
eBezTriple_KeyframeType keytype,
|
||||
eInsertKeyFlags flag)
|
||||
{
|
||||
/* Make sure the F-Curve exists.
|
||||
* - if we're replacing keyframes only, DO NOT create new F-Curves if they do not exist yet
|
||||
|
@ -439,7 +481,7 @@ static bool insert_keyframe_fcurve_value(Main *bmain,
|
|||
|
||||
/* We may not have a F-Curve when we're replacing only. */
|
||||
if (!fcu) {
|
||||
return false;
|
||||
return SingleKeyingResult::CANNOT_CREATE_FCURVE;
|
||||
}
|
||||
|
||||
const bool is_new_curve = (fcu->totvert == 0);
|
||||
|
@ -454,23 +496,51 @@ static bool insert_keyframe_fcurve_value(Main *bmain,
|
|||
/* Update F-Curve flags to ensure proper behavior for property type. */
|
||||
update_autoflags_fcurve_direct(fcu, prop);
|
||||
|
||||
const bool success = insert_keyframe_value(fcu, fcurve_frame, curval, keytype, flag);
|
||||
|
||||
if (!success && reports != nullptr) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"Failed to insert keys on F-Curve with path '%s[%d]', ensure that it is not "
|
||||
"locked or sampled, and try removing F-Modifiers",
|
||||
fcu->rna_path,
|
||||
fcu->array_index);
|
||||
}
|
||||
const SingleKeyingResult result = insert_keyframe_value(
|
||||
fcu, fcurve_frame, curval, keytype, flag);
|
||||
|
||||
/* If the curve is new, make it cyclic if appropriate. */
|
||||
if (is_cyclic_action && is_new_curve) {
|
||||
make_new_fcurve_cyclic(fcu, {act->frame_start, act->frame_end});
|
||||
}
|
||||
|
||||
return success;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void generate_keyframe_reports_from_result(ReportList *reports,
|
||||
const CombinedKeyingResult &result)
|
||||
{
|
||||
std::string error = "Inserting keyframes failed due to the following reasons:";
|
||||
|
||||
if (result.get_count(SingleKeyingResult::CANNOT_CREATE_FCURVE) > 0) {
|
||||
const int error_count = result.get_count(SingleKeyingResult::CANNOT_CREATE_FCURVE);
|
||||
error.append(
|
||||
fmt::format("\n- Could not create {} F-Curve{}. This can happen when only inserting to "
|
||||
"available F-Curves.",
|
||||
error_count,
|
||||
error_count > 1 ? "s" : ""));
|
||||
}
|
||||
|
||||
if (result.get_count(SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE) > 0) {
|
||||
const int error_count = result.get_count(SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE);
|
||||
if (error_count == 1) {
|
||||
Sybren A. Stüvel
commented
I think this text is slightly misleading. "Cannot create F-Curves" is a broad statement, which could be interpreted as "It was not possible to create any F-Curves". However, Maybe something like For consistency, change I think this text is slightly misleading. "Cannot create F-Curves" is a broad statement, which could be interpreted as "It was not possible to create any F-Curves". However, `CANNOT_CREATE_FCURVE` indicates that there was that many F-Curves that could not be created. There might have been more that *could* be created, but at this point that is not known.
Maybe something like `Could not create {count} F-Curves…` ?
For consistency, change `Can happen` to `This can happen` or `This might happen`.
|
||||
error.append("\n- One F-Curve is not keyframeable. It might be locked or sampled.");
|
||||
}
|
||||
else {
|
||||
error.append(fmt::format(
|
||||
"\n- {} F-Curves are not keyframeable. They might be locked or sampled.", error_count));
|
||||
}
|
||||
}
|
||||
|
||||
if (result.get_count(SingleKeyingResult::NO_KEY_NEEDED) > 0) {
|
||||
const int error_count = result.get_count(SingleKeyingResult::NO_KEY_NEEDED);
|
||||
error.append(fmt::format(
|
||||
"\n- Due to the setting 'Only Insert Needed', {} keyframe{} not been inserted.",
|
||||
error_count,
|
||||
error_count > 1 ? "s have" : " has"));
|
||||
Sybren A. Stüvel
commented
Same as above: for the text to be true, the code assumes that Same as above: for the text to be true, the code assumes that `count(SUCCESS) == 0`. That's not checked for, though.
|
||||
}
|
||||
|
||||
BKE_reportf(reports, RPT_ERROR, "%s", error.c_str());
|
||||
}
|
||||
|
||||
int insert_keyframe(Main *bmain,
|
||||
|
@ -540,6 +610,8 @@ int insert_keyframe(Main *bmain,
|
|||
&force_all,
|
||||
successful_remaps);
|
||||
|
||||
CombinedKeyingResult combined_result;
|
||||
|
||||
/* Key the entire array. */
|
||||
int key_count = 0;
|
||||
if (array_index == -1 || force_all) {
|
||||
|
@ -551,20 +623,19 @@ int insert_keyframe(Main *bmain,
|
|||
if (!successful_remaps[array_index]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insert_keyframe_fcurve_value(bmain,
|
||||
reports,
|
||||
&ptr,
|
||||
prop,
|
||||
act,
|
||||
group,
|
||||
rna_path,
|
||||
array_index,
|
||||
nla_mapped_frame,
|
||||
values[array_index],
|
||||
keytype,
|
||||
flag))
|
||||
{
|
||||
const SingleKeyingResult result = insert_keyframe_fcurve_value(bmain,
|
||||
&ptr,
|
||||
prop,
|
||||
nathanvegdahl marked this conversation as resolved
Outdated
Nathan Vegdahl
commented
Love that this turned into an increment. The previous code that just added the Love that this turned into an increment. The previous code that just added the `bool` returned from `insert_keyframe_fcurve_value()` tripped me up for a bit, because I thought that must mean that `insert_keyframe_fcurve_value()` returned an int, which then made me think it might insert multiple keys, etc. This is way clearer to me, in addition to giving a better error handling flow. :-)
|
||||
act,
|
||||
group,
|
||||
rna_path,
|
||||
array_index,
|
||||
nla_mapped_frame,
|
||||
values[array_index],
|
||||
keytype,
|
||||
flag);
|
||||
combined_result.add(result);
|
||||
if (result == SingleKeyingResult::SUCCESS) {
|
||||
key_count++;
|
||||
exclude = array_index;
|
||||
break;
|
||||
|
@ -580,18 +651,21 @@ int insert_keyframe(Main *bmain,
|
|||
}
|
||||
|
||||
if (array_index != exclude) {
|
||||
key_count += insert_keyframe_fcurve_value(bmain,
|
||||
reports,
|
||||
&ptr,
|
||||
prop,
|
||||
act,
|
||||
group,
|
||||
rna_path,
|
||||
array_index,
|
||||
nla_mapped_frame,
|
||||
values[array_index],
|
||||
keytype,
|
||||
flag);
|
||||
const SingleKeyingResult result = insert_keyframe_fcurve_value(bmain,
|
||||
&ptr,
|
||||
prop,
|
||||
act,
|
||||
group,
|
||||
rna_path,
|
||||
array_index,
|
||||
nla_mapped_frame,
|
||||
values[array_index],
|
||||
keytype,
|
||||
flag);
|
||||
combined_result.add(result);
|
||||
if (result == SingleKeyingResult::SUCCESS) {
|
||||
key_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -603,36 +677,42 @@ int insert_keyframe(Main *bmain,
|
|||
continue;
|
||||
}
|
||||
|
||||
key_count += insert_keyframe_fcurve_value(bmain,
|
||||
reports,
|
||||
&ptr,
|
||||
prop,
|
||||
act,
|
||||
group,
|
||||
rna_path,
|
||||
array_index,
|
||||
nla_mapped_frame,
|
||||
values[array_index],
|
||||
keytype,
|
||||
flag);
|
||||
const SingleKeyingResult result = insert_keyframe_fcurve_value(bmain,
|
||||
&ptr,
|
||||
prop,
|
||||
act,
|
||||
group,
|
||||
rna_path,
|
||||
array_index,
|
||||
nla_mapped_frame,
|
||||
values[array_index],
|
||||
keytype,
|
||||
flag);
|
||||
combined_result.add(result);
|
||||
if (result == SingleKeyingResult::SUCCESS) {
|
||||
key_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Key a single index. */
|
||||
else {
|
||||
if (array_index >= 0 && array_index < values.size() && successful_remaps[array_index]) {
|
||||
key_count += insert_keyframe_fcurve_value(bmain,
|
||||
reports,
|
||||
&ptr,
|
||||
prop,
|
||||
act,
|
||||
group,
|
||||
rna_path,
|
||||
array_index,
|
||||
nla_mapped_frame,
|
||||
values[array_index],
|
||||
keytype,
|
||||
flag);
|
||||
const SingleKeyingResult result = insert_keyframe_fcurve_value(bmain,
|
||||
&ptr,
|
||||
prop,
|
||||
act,
|
||||
group,
|
||||
rna_path,
|
||||
array_index,
|
||||
nla_mapped_frame,
|
||||
values[array_index],
|
||||
keytype,
|
||||
flag);
|
||||
combined_result.add(result);
|
||||
if (result == SingleKeyingResult::SUCCESS) {
|
||||
key_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -647,6 +727,10 @@ int insert_keyframe(Main *bmain,
|
|||
}
|
||||
}
|
||||
|
||||
if (key_count == 0) {
|
||||
generate_keyframe_reports_from_result(reports, combined_result);
|
||||
}
|
||||
|
||||
return key_count;
|
||||
}
|
||||
|
||||
|
@ -872,19 +956,18 @@ int insert_key_action(Main *bmain,
|
|||
property_array_index++;
|
||||
continue;
|
||||
}
|
||||
const bool inserted_key = insert_keyframe_fcurve_value(bmain,
|
||||
nullptr,
|
||||
ptr,
|
||||
prop,
|
||||
action,
|
||||
group.c_str(),
|
||||
rna_path.c_str(),
|
||||
property_array_index,
|
||||
frame,
|
||||
value,
|
||||
key_type,
|
||||
insert_key_flag);
|
||||
if (inserted_key) {
|
||||
const SingleKeyingResult inserted_key = insert_keyframe_fcurve_value(bmain,
|
||||
ptr,
|
||||
prop,
|
||||
action,
|
||||
group.c_str(),
|
||||
rna_path.c_str(),
|
||||
property_array_index,
|
||||
frame,
|
||||
value,
|
||||
key_type,
|
||||
insert_key_flag);
|
||||
if (inserted_key == SingleKeyingResult::SUCCESS) {
|
||||
inserted_keys++;
|
||||
}
|
||||
property_array_index++;
|
||||
|
|
This will pollute the namespace, as it'll create
blender::animrig::SUCESS
. Better to use anenum class
, as that limits the scope toblender::animrig::KeyframingResult::SUCESS
.I've put the enum into a namespace to combat that.
enum class
doesn't work with bitwise logicMaybe this needs a completely different solution. Probably worth waiting for the result of https://devtalk.blender.org/t/developer-discussion-handling-errors/33054/5
I think those names need some work, as it's not clear how these two classes interact with each other and how they'd be used.
My suggestion:
KeyingResultOptions
→SingleKeyingResult
KeyingResult
→AggregatedKeyingResult
It'll also help in the rest of the code to be more explicit about whether a variable represents a single result or an aggregated one. For example in a line like this:
When read out of context, it's unclear that
keying_result
andresult
are of different semantic levels. Something likeaggregated_result
vsresult
might work well, although it is a bit long.I used
CombinedKeyingResult
That felt like a simpler word to me that conveys the same meaning