Anim: Detailed report if no keyframes have been inserted

With this PR, when pressing `I` in the viewport and the code
is unable to insert **ANY** keyframes, the user will be presented
with a single message detailing exactly why it has failed.

This PR promotes the functionality introduced in
#117449 into the header file so it can be used elsewhere.

The `CombinedKeyingResult` class is returned
from `insert_key_action` and `insert_key_rna`, and
used to produce a single report from the operator if it
failed to insert any keyframes.

In order to easily create a report from a `CombinedKeyingResult`
the function `generate_keyframe_reports_from_result`
has been moved into the class as `generate_reports`.

In addition to that the `UNABLE_TO_INSERT_TO_NLA_STACK` result
has been added. This notifies the user if keyframe insertion is not
possible due to NLA stack settings.

Pull Request: https://projects.blender.org/blender/blender/pulls/119201
This commit is contained in:
Christoph Lendenfeld
2024-04-09 09:39:13 +02:00
committed by Christoph Lendenfeld
parent 38e4e9c68b
commit 956d8379a4
4 changed files with 218 additions and 158 deletions

View File

@@ -12,6 +12,7 @@
#include <string>
#include "BLI_array.hh"
#include "BLI_bit_span.hh"
#include "BLI_vector.hh"
#include "DNA_anim_types.h"
@@ -29,6 +30,41 @@ struct NlaKeyframingContext;
namespace blender::animrig {
enum class SingleKeyingResult {
SUCCESS = 0,
CANNOT_CREATE_FCURVE,
FCURVE_NOT_KEYFRAMEABLE,
NO_KEY_NEEDED,
UNABLE_TO_INSERT_TO_NLA_STACK,
/* Make sure to always keep this at the end of the enum. */
_KEYING_RESULT_MAX,
};
/**
* Class for tracking the result of inserting keyframes. Tracks how often each of
* `SingleKeyingResult` has happened.
* */
class CombinedKeyingResult {
private:
/* The index to the array maps a `SingleKeyingResult` to the number of times this result has
* occurred. */
Array<int> result_counter;
public:
CombinedKeyingResult();
void add(const SingleKeyingResult result);
/* Add values of the given result to this result. */
void merge(const CombinedKeyingResult &combined_result);
int get_count(const SingleKeyingResult result) const;
bool has_errors() const;
void generate_reports(ReportList *reports);
};
/* -------------------------------------------------------------------- */
/** \name Key-Framing Management
* \{ */
@@ -194,31 +230,31 @@ bool autokeyframe_property(bContext *C,
* already.
* \param keying_mask is expected to have the same size as `rna_path`. A false bit means that index
* will be skipped.
* \returns The number of keys inserted.
* \returns How often keyframe insertion was successful and how often it failed / for which reason.
*/
int insert_key_action(Main *bmain,
bAction *action,
PointerRNA *ptr,
PropertyRNA *prop,
const std::string &rna_path,
float frame,
Span<float> values,
eInsertKeyFlags insert_key_flag,
eBezTriple_KeyframeType key_type,
BitSpan keying_mask);
CombinedKeyingResult insert_key_action(Main *bmain,
bAction *action,
PointerRNA *ptr,
PropertyRNA *prop,
const std::string &rna_path,
float frame,
Span<float> values,
eInsertKeyFlags insert_key_flag,
eBezTriple_KeyframeType key_type,
BitSpan keying_mask);
/**
* Insert keys to the ID of the given PointerRNA for the given RNA paths. Tries to create an
* action if none exists yet.
* \param scene_frame: is expected to be not NLA mapped as that happens within the function.
* \returns How often keyframe insertion was successful and how often it failed / for which reason.
*/
void insert_key_rna(PointerRNA *rna_pointer,
const blender::Span<std::string> rna_paths,
float scene_frame,
eInsertKeyFlags insert_key_flags,
eBezTriple_KeyframeType key_type,
Main *bmain,
ReportList *reports,
const AnimationEvalContext &anim_eval_context);
CombinedKeyingResult insert_key_rna(PointerRNA *rna_pointer,
const blender::Span<std::string> rna_paths,
float scene_frame,
eInsertKeyFlags insert_key_flags,
eBezTriple_KeyframeType key_type,
Main *bmain,
const AnimationEvalContext &anim_eval_context);
} // namespace blender::animrig

View File

@@ -48,44 +48,102 @@
namespace blender::animrig {
enum class SingleKeyingResult {
SUCCESS = 0,
CANNOT_CREATE_FCURVE,
FCURVE_NOT_KEYFRAMEABLE,
NO_KEY_NEEDED,
/* Make sure to always keep this at the end of the enum. */
_KEYING_RESULT_MAX,
};
CombinedKeyingResult::CombinedKeyingResult()
{
result_counter = Array<int>(int(SingleKeyingResult::_KEYING_RESULT_MAX));
result_counter.fill(0);
}
class CombinedKeyingResult {
private:
/* The index to the array maps a `SingleKeyingResult` to the number of times this result has
* occurred. */
std::array<int, int(SingleKeyingResult::_KEYING_RESULT_MAX)> result_counter{0};
void CombinedKeyingResult::add(const SingleKeyingResult result)
{
result_counter[int(result)]++;
}
public:
void add(const SingleKeyingResult result)
{
result_counter[int(result)]++;
void CombinedKeyingResult::merge(const CombinedKeyingResult &other)
{
for (int i = 0; i < result_counter.size(); i++) {
result_counter[i] += other.result_counter[i];
}
}
int get_count(const SingleKeyingResult result) const
{
return result_counter[int(result)];
}
int CombinedKeyingResult::get_count(const SingleKeyingResult result) const
{
return result_counter[int(result)];
}
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;
}
bool CombinedKeyingResult::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. */
static_assert(int(SingleKeyingResult::SUCCESS) == 0);
for (int i = 1; i < result_counter.size(); i++) {
if (result_counter[i] > 0) {
return true;
}
return false;
}
};
return false;
}
void CombinedKeyingResult::generate_reports(ReportList *reports)
{
if (!this->has_errors() && this->get_count(SingleKeyingResult::SUCCESS) == 0) {
BKE_reportf(
reports, RPT_WARNING, "No keys have been inserted and no errors have been reported.");
return;
}
Vector<std::string> errors;
if (this->get_count(SingleKeyingResult::CANNOT_CREATE_FCURVE) > 0) {
const int error_count = this->get_count(SingleKeyingResult::CANNOT_CREATE_FCURVE);
errors.append(
fmt::format("Could not create {} F-Curve{}. This can happen when only inserting to "
"available F-Curves.",
error_count,
error_count > 1 ? "s" : ""));
}
if (this->get_count(SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE) > 0) {
const int error_count = this->get_count(SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE);
if (error_count == 1) {
errors.append("One F-Curve is not keyframeable. It might be locked or sampled.");
}
else {
errors.append(fmt::format(
"{} F-Curves are not keyframeable. They might be locked or sampled.", error_count));
}
}
if (this->get_count(SingleKeyingResult::NO_KEY_NEEDED) > 0) {
const int error_count = this->get_count(SingleKeyingResult::NO_KEY_NEEDED);
errors.append(
fmt::format("Due to the setting 'Only Insert Needed', {} keyframe{} not been inserted.",
error_count,
error_count > 1 ? "s have" : " has"));
}
if (this->get_count(SingleKeyingResult::UNABLE_TO_INSERT_TO_NLA_STACK) > 0) {
const int error_count = this->get_count(SingleKeyingResult::UNABLE_TO_INSERT_TO_NLA_STACK);
errors.append(fmt::format("Due to the NLA stack setup, {} keyframe{} not been inserted.",
error_count,
error_count > 1 ? "s have" : " has"));
}
if (errors.is_empty()) {
BKE_report(reports, RPT_WARNING, "Encountered unhandled error during keyframing");
return;
}
if (errors.size() == 1) {
BKE_report(reports, RPT_ERROR, errors[0].c_str());
return;
}
std::string error_message = "Inserting keyframes failed:";
for (const std::string &error : errors) {
error_message.append(fmt::format("\n- {}", error));
}
BKE_report(reports, RPT_ERROR, error_message.c_str());
}
void update_autoflags_fcurve_direct(FCurve *fcu, PropertyRNA *prop)
{
@@ -499,42 +557,6 @@ static SingleKeyingResult insert_keyframe_fcurve_value(Main *bmain,
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) {
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"));
}
BKE_reportf(reports, RPT_ERROR, "%s", error.c_str());
}
int insert_keyframe(Main *bmain,
ReportList *reports,
ID *id,
@@ -718,7 +740,7 @@ int insert_keyframe(Main *bmain,
}
if (key_count == 0) {
generate_keyframe_reports_from_result(reports, combined_result);
combined_result.generate_reports(reports);
}
return key_count;
@@ -916,16 +938,16 @@ int clear_keyframe(Main *bmain,
return key_count;
}
int insert_key_action(Main *bmain,
bAction *action,
PointerRNA *ptr,
PropertyRNA *prop,
const std::string &rna_path,
const float frame,
const Span<float> values,
eInsertKeyFlags insert_key_flag,
eBezTriple_KeyframeType key_type,
const BitSpan keying_mask)
CombinedKeyingResult insert_key_action(Main *bmain,
bAction *action,
PointerRNA *ptr,
PropertyRNA *prop,
const std::string &rna_path,
const float frame,
const Span<float> values,
eInsertKeyFlags insert_key_flag,
eBezTriple_KeyframeType key_type,
const BitSpan keying_mask)
{
BLI_assert(bmain != nullptr);
BLI_assert(action != nullptr);
@@ -940,49 +962,44 @@ int insert_key_action(Main *bmain,
}
int property_array_index = 0;
int inserted_keys = 0;
CombinedKeyingResult combined_result;
for (float value : values) {
if (!keying_mask[property_array_index]) {
combined_result.add(SingleKeyingResult::UNABLE_TO_INSERT_TO_NLA_STACK);
property_array_index++;
continue;
}
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++;
}
const SingleKeyingResult keying_result = 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);
combined_result.add(keying_result);
property_array_index++;
}
return inserted_keys;
return combined_result;
}
void insert_key_rna(PointerRNA *rna_pointer,
const blender::Span<std::string> rna_paths,
const float scene_frame,
const eInsertKeyFlags insert_key_flags,
const eBezTriple_KeyframeType key_type,
Main *bmain,
ReportList *reports,
const AnimationEvalContext &anim_eval_context)
CombinedKeyingResult insert_key_rna(PointerRNA *rna_pointer,
const blender::Span<std::string> rna_paths,
const float scene_frame,
const eInsertKeyFlags insert_key_flags,
const eBezTriple_KeyframeType key_type,
Main *bmain,
const AnimationEvalContext &anim_eval_context)
{
ID *id = rna_pointer->owner_id;
bAction *action = id_action_ensure(bmain, id);
CombinedKeyingResult combined_result;
if (action == nullptr) {
BKE_reportf(reports,
RPT_ERROR,
"Could not insert keyframe, as this type does not support animation data (ID = "
"%s)",
id->name);
return;
return combined_result;
}
AnimData *adt = BKE_animdata_from_id(id);
@@ -1000,19 +1017,12 @@ void insert_key_rna(PointerRNA *rna_pointer,
const float nla_frame = BKE_nla_tweakedit_remap(adt, scene_frame, NLATIME_CONVERT_UNMAP);
const bool visual_keyframing = insert_key_flags & INSERTKEY_MATRIX;
int insert_key_count = 0;
for (const std::string &rna_path : rna_paths) {
PointerRNA ptr;
PropertyRNA *prop = nullptr;
const bool path_resolved = RNA_path_resolve_property(
rna_pointer, rna_path.c_str(), &ptr, &prop);
if (!path_resolved) {
BKE_reportf(reports,
RPT_ERROR,
"Could not insert keyframe, as this property does not exist (ID = "
"%s, path = %s)",
id->name,
rna_path.c_str());
continue;
}
const std::optional<std::string> rna_path_id_to_prop = RNA_path_from_ID_to_property(&ptr,
@@ -1028,23 +1038,21 @@ void insert_key_rna(PointerRNA *rna_pointer,
&anim_eval_context,
nullptr,
successful_remaps);
insert_key_count += insert_key_action(bmain,
action,
rna_pointer,
prop,
rna_path_id_to_prop->c_str(),
nla_frame,
rna_values.as_span(),
insert_key_flags,
key_type,
successful_remaps);
const CombinedKeyingResult result = insert_key_action(bmain,
action,
rna_pointer,
prop,
rna_path_id_to_prop->c_str(),
nla_frame,
rna_values.as_span(),
insert_key_flags,
key_type,
successful_remaps);
combined_result.merge(result);
}
BKE_animsys_free_nla_keyframing_context_cache(&nla_cache);
if (insert_key_count == 0) {
BKE_reportf(reports, RPT_ERROR, "Failed to insert any keys");
}
return combined_result;
}
} // namespace blender::animrig

View File

@@ -159,7 +159,6 @@ void autokeyframe_object(bContext *C, Scene *scene, Object *ob, Span<std::string
flag,
eBezTriple_KeyframeType(scene->toolsettings->keyframe_type),
bmain,
reports,
anim_eval_context);
}
}
@@ -284,7 +283,6 @@ void autokeyframe_pose_channel(bContext *C,
flag,
eBezTriple_KeyframeType(scene->toolsettings->keyframe_type),
bmain,
reports,
anim_eval_context);
}
}

View File

@@ -338,6 +338,12 @@ static int insert_key(bContext *C, wmOperator *op)
const bool found_selection = get_selection(C, &selection);
if (!found_selection) {
BKE_reportf(op->reports, RPT_ERROR, "Unsupported context mode");
return OPERATOR_CANCELLED;
}
if (selection.is_empty()) {
BKE_reportf(op->reports, RPT_WARNING, "Nothing selected to key");
return OPERATOR_CANCELLED;
}
Main *bmain = CTX_data_main(C);
@@ -351,22 +357,34 @@ static int insert_key(bContext *C, wmOperator *op)
const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
depsgraph, BKE_scene_frame_get(scene));
animrig::CombinedKeyingResult combined_result;
for (PointerRNA &id_ptr : selection) {
ID *selected_id = id_ptr.owner_id;
if (!id_can_have_animdata(selected_id)) {
BKE_reportf(op->reports,
RPT_ERROR,
"Could not insert keyframe, as this type does not support animation data (ID = "
"%s)",
selected_id->name);
continue;
}
if (!BKE_id_is_editable(bmain, selected_id)) {
BKE_reportf(op->reports, RPT_ERROR, "'%s' is not editable", selected_id->name + 2);
continue;
}
Vector<std::string> rna_paths = construct_rna_paths(&id_ptr);
animrig::insert_key_rna(&id_ptr,
rna_paths.as_span(),
scene_frame,
insert_key_flags,
key_type,
bmain,
op->reports,
anim_eval_context);
combined_result.merge(animrig::insert_key_rna(&id_ptr,
rna_paths.as_span(),
scene_frame,
insert_key_flags,
key_type,
bmain,
anim_eval_context));
}
if (combined_result.get_count(animrig::SingleKeyingResult::SUCCESS) == 0) {
combined_result.generate_reports(op->reports);
}
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_ADDED, nullptr);