Files
workinf_Blender_Wasm/blender-5.2.0/source/blender/animrig/intern/keyframing.cc
2026-08-12 04:47:48 -04:00

916 lines
32 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*/
#include <cmath>
#include <string>
#include <fmt/format.h>
#include "ANIM_action.hh"
#include "ANIM_action_iterators.hh"
#include "ANIM_animdata.hh"
#include "ANIM_fcurve.hh"
#include "ANIM_keyframing.hh"
#include "ANIM_rna.hh"
#include "ANIM_visualkey.hh"
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_animsys.h"
#include "BKE_fcurve.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_nla.hh"
#include "BKE_report.hh"
#include "DNA_scene_types.h"
#include "BLI_math_base.h"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_anim_types.h"
#include "MEM_guardedalloc.h"
#include "RNA_access.hh"
#include "RNA_path.hh"
#include "RNA_prototypes.hh"
#include "WM_types.hh"
namespace blender::animrig {
void generate_single_keying_result_report(const SingleKeyingResult result, ReportList *reports)
{
switch (result) {
case SingleKeyingResult::SUCCESS:
BKE_reportf(reports, RPT_INFO, "Successfully inserted a key.");
break;
case SingleKeyingResult::UNKNOWN_FAILURE:
BKE_reportf(reports, RPT_ERROR, "Keyframe insertion failed for an unknown reason.");
break;
case SingleKeyingResult::CANNOT_CREATE_FCURVE:
BKE_reportf(reports, RPT_ERROR, "Failed to create the F-Curve.");
break;
case SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE:
BKE_reportf(reports, RPT_ERROR, "The F-Curve is not keyable. It may be locked or sampled.");
break;
case SingleKeyingResult::NO_KEY_NEEDED:
BKE_reportf(
reports, RPT_ERROR, "Due to the setting 'Only Insert Needed' no keyframe was inserted.");
break;
case SingleKeyingResult::UNABLE_TO_INSERT_TO_NLA_STACK:
BKE_reportf(reports, RPT_ERROR, "Due to the NLA stack setup, no key was inserted.");
break;
case SingleKeyingResult::ID_NOT_EDITABLE:
BKE_reportf(
reports, RPT_ERROR, "Inserting key has been skipped because the ID cannot be edited.");
break;
case SingleKeyingResult::ID_NOT_ANIMATABLE:
BKE_reportf(
reports, RPT_ERROR, "Inserting key has been skipped because the ID cannot be keyed.");
break;
case SingleKeyingResult::NO_VALID_LAYER:
BKE_reportf(reports, RPT_ERROR, "No valid layer. Cannot insert key.");
break;
case SingleKeyingResult::NO_VALID_STRIP:
BKE_reportf(reports, RPT_ERROR, "No valid strip. Cannot insert key.");
break;
case SingleKeyingResult::NO_VALID_SLOT:
BKE_reportf(reports, RPT_ERROR, "No valid slot. Cannot insert key.");
break;
case SingleKeyingResult::CANNOT_RESOLVE_PATH:
BKE_reportf(reports, RPT_ERROR, "Invalid RNA path. Cannot insert key.");
break;
case SingleKeyingResult::_KEYING_RESULT_MAX:
break;
}
}
CombinedKeyingResult::CombinedKeyingResult()
{
result_counter.fill(0);
}
void CombinedKeyingResult::add(const SingleKeyingResult result, const int count)
{
result_counter[int(result)] += count;
}
void CombinedKeyingResult::merge(const CombinedKeyingResult &other)
{
for (int i = 0; i < result_counter.size(); i++) {
result_counter[i] += other.result_counter[i];
}
}
int CombinedKeyingResult::get_count(const SingleKeyingResult result) const
{
return result_counter[int(result)];
}
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;
}
void CombinedKeyingResult::generate_reports(ReportList *reports, const eReportType report_level)
{
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::UNKNOWN_FAILURE) > 0) {
const int error_count = this->get_count(SingleKeyingResult::UNKNOWN_FAILURE);
errors.append(fmt::format(
fmt::runtime(RPT_("There were {:d} keying failures for unknown reasons.")), error_count));
}
if (this->get_count(SingleKeyingResult::CANNOT_CREATE_FCURVE) > 0) {
const int error_count = this->get_count(SingleKeyingResult::CANNOT_CREATE_FCURVE);
errors.append(fmt::format(
fmt::runtime(RPT_("Could not create {:d} F-Curve(s). This can happen when only "
"inserting to available F-Curves.")),
error_count));
}
if (this->get_count(SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE) > 0) {
const int error_count = this->get_count(SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE);
errors.append(
fmt::format(fmt::runtime(RPT_(
"{:d} F-Curve(s) 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(
fmt::runtime(RPT_(
"Due to the setting 'Only Insert Needed', {:d} keyframe(s) have not been inserted.")),
error_count));
}
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(
fmt::runtime(RPT_("Due to the NLA stack setup, {:d} keyframe(s) have not been inserted.")),
error_count));
}
if (this->get_count(SingleKeyingResult::ID_NOT_EDITABLE) > 0) {
const int error_count = this->get_count(SingleKeyingResult::ID_NOT_EDITABLE);
errors.append(fmt::format(
fmt::runtime(RPT_("Inserting keys on {:d} data-block(s) has been skipped because "
"they are not editable.")),
error_count));
}
if (this->get_count(SingleKeyingResult::ID_NOT_ANIMATABLE) > 0) {
const int error_count = this->get_count(SingleKeyingResult::ID_NOT_ANIMATABLE);
errors.append(fmt::format(
fmt::runtime(RPT_("Inserting keys on {:d} data-block(s) has been skipped because "
"they cannot be animated.")),
error_count));
}
if (this->get_count(SingleKeyingResult::CANNOT_RESOLVE_PATH) > 0) {
const int error_count = this->get_count(SingleKeyingResult::CANNOT_RESOLVE_PATH);
errors.append(fmt::format(
fmt::runtime(RPT_("Inserting keys on {:d} data-block(s) has been skipped because "
"the RNA path wasn't valid for them.")),
error_count));
}
if (this->get_count(SingleKeyingResult::NO_VALID_LAYER) > 0) {
const int error_count = this->get_count(SingleKeyingResult::NO_VALID_LAYER);
errors.append(fmt::format(
fmt::runtime(RPT_("Inserting keys on {:d} data-block(s) has been skipped because "
"there were no layers that could accept the keys.")),
error_count));
}
if (this->get_count(SingleKeyingResult::NO_VALID_STRIP) > 0) {
const int error_count = this->get_count(SingleKeyingResult::NO_VALID_STRIP);
errors.append(fmt::format(
fmt::runtime(RPT_("Inserting keys on {:d} data-block(s) has been skipped because "
"there were no strips that could accept the keys.")),
error_count));
}
if (this->get_count(SingleKeyingResult::NO_VALID_SLOT) > 0) {
const int error_count = this->get_count(SingleKeyingResult::NO_VALID_SLOT);
errors.append(fmt::format(
fmt::runtime(RPT_("Inserting keys on {:d} data-block(s) has been skipped because "
"of missing action slots.")),
error_count));
}
if (errors.is_empty()) {
BKE_report(reports, RPT_WARNING, "Encountered unhandled error during keyframing");
return;
}
if (errors.size() == 1) {
BKE_report(reports, report_level, errors[0].c_str());
return;
}
std::string error_message = RPT_("Inserting keyframes failed:");
for (const std::string &error : errors) {
error_message.append(fmt::format("\n- {}", error));
}
BKE_report(reports, report_level, error_message.c_str());
}
std::optional<StringRefNull> default_channel_group_for_path(const PointerRNA *animated_struct,
const StringRef prop_rna_path)
{
if (animated_struct->type == RNA_PoseBone) {
bPoseChannel *pose_channel = static_cast<bPoseChannel *>(animated_struct->data);
return pose_channel->name;
}
if (animated_struct->type == RNA_Object) {
if (prop_rna_path.find("location") != StringRef::not_found ||
prop_rna_path.find("rotation") != StringRef::not_found ||
prop_rna_path.find("scale") != StringRef::not_found)
{
/* NOTE: Keep this label in sync with the "ID" case in
* _keyingsets_utils.py :: get_transform_generators_base_info()
*/
return "Object Transforms";
}
}
return std::nullopt;
}
void update_autoflags_fcurve_direct(FCurve *fcu, const PropertyType prop_type)
{
/* First clear out all the flags that should be updated by this function, before setting just the
* ones suitable for this property type. */
fcu->flag &= ~(FCURVE_INT_VALUES | FCURVE_DISCRETE_VALUES);
fcu->flag |= fcurve_flags_for_property_type(prop_type);
}
bool is_keying_flag(const Scene *scene, const eKeying_Flag flag)
{
if (scene) {
return (scene->toolsettings->keying_flag & flag) || (U.keying_flag & flag);
}
return U.keying_flag & flag;
}
eInsertKeyFlags get_keyframing_flags(Scene *scene)
{
eInsertKeyFlags flag = INSERTKEY_NOFLAGS;
/* Visual keying. */
if (is_keying_flag(scene, KEYING_FLAG_VISUALKEY)) {
flag |= INSERTKEY_MATRIX;
}
/* Cycle-aware keyframe insertion - preserve cycle period and flow. */
if (is_keying_flag(scene, KEYING_FLAG_CYCLEAWARE)) {
flag |= INSERTKEY_CYCLE_AWARE;
}
if (is_keying_flag(scene, MANUALKEY_FLAG_INSERTNEEDED)) {
flag |= INSERTKEY_NEEDED;
}
return flag;
}
/**
* Checks whether the Action assigned to `adt` (if any) has any keyframes at the
* given frame. Since we're only concerned whether a keyframe exists, we can
* simply loop until a match is found.
*
* For layered actions, this only checks for keyframes in the assigned slot.
*/
static bool assigned_action_has_keyframe_at(AnimData &adt, const float frame)
{
if (adt.action == nullptr) {
return false;
}
if (adt.action->flag & ACT_MUTED) {
return false;
}
const Span<FCurve *> fcurves = animrig::fcurves_for_assigned_action(&adt);
/* 1024 is a common value for memory bandwidth limited tasks. The number isn't critical: 512
* works fine here, but 128 and 4096 seem to work equally well in testing. */
return threading::parallel_reduce<bool>(
fcurves.index_range(),
512,
false,
[&](const IndexRange range, const bool is_found) {
if (is_found) {
return true;
}
for (FCurve *fcu : fcurves.slice(range)) {
if (fcurve_frame_has_keyframe(fcu, frame)) {
return true;
}
}
return false;
},
std::logical_or<bool>());
}
/* Checks whether an Object has a keyframe for a given frame. */
static bool object_frame_has_keyframe(Object *ob, const float frame)
{
if (ob == nullptr) {
return false;
}
/* Check its own animation data - specifically, the action it contains. */
if ((ob->adt) && (ob->adt->action)) {
/* #41525 - When the active action is a NLA strip being edited,
* we need to correct the frame number to "look inside" the
* remapped action
*/
const float ob_frame = BKE_nla_tweakedit_remap(ob->adt, frame, NLATIME_CONVERT_UNMAP);
if (assigned_action_has_keyframe_at(*ob->adt, ob_frame)) {
return true;
}
}
/* nothing found */
return false;
}
bool id_frame_has_keyframe(ID *id, float frame)
{
if (id == nullptr) {
return false;
}
/* Perform special checks for 'macro' types. */
switch (GS(id->name)) {
case ID_OB:
return object_frame_has_keyframe(id_cast<Object *>(id), frame);
default: {
AnimData *adt = BKE_animdata_from_id(id);
/* only check keyframes in active action */
if (adt) {
return assigned_action_has_keyframe_at(*adt, frame);
}
break;
}
}
return false;
}
bool key_insertion_may_create_fcurve(const eInsertKeyFlags insert_key_flags)
{
return (insert_key_flags & (INSERTKEY_REPLACE | INSERTKEY_AVAILABLE)) == 0;
}
Vector<float> get_property_values(PointerRNA *ptr, PropertyRNA *prop, const bool visual_key)
{
Vector<float> values;
if (visual_key && visualkey_can_use(ptr, prop)) {
/* Visual-keying is only available for object data-blocks and pose-channels,
* as it works by key-framing using a value extracted from the final matrix
* instead of using the kt system to extract a value. */
values = visualkey_get_values(ptr, prop);
}
else {
values = get_rna_values(ptr, prop);
}
return values;
}
static float nla_time_remap(float time,
const AnimationEvalContext *anim_eval_context,
PointerRNA *id_ptr,
AnimData *adt,
bAction *act,
ListBaseT<NlaKeyframingContext> *nla_cache,
NlaKeyframingContext **r_nla_context)
{
if (adt && adt->action == act) {
*r_nla_context = BKE_animsys_get_nla_keyframing_context(
nla_cache, id_ptr, adt, anim_eval_context);
const float remapped_frame = BKE_nla_tweakedit_remap(adt, time, NLATIME_CONVERT_UNMAP);
return remapped_frame;
}
*r_nla_context = nullptr;
return time;
}
SingleKeyingResult insert_keyframe_direct(PointerRNA &ptr,
PropertyRNA &prop,
FCurve &fcu,
const float fcurve_frame,
const eBezTriple_KeyframeType keytype,
const eInsertKeyFlags flag)
{
if ((ptr.owner_id == nullptr) && (ptr.data == nullptr)) {
BLI_assert_unreachable();
return SingleKeyingResult::UNKNOWN_FAILURE;
}
if (!BKE_fcurve_is_keyframable(fcu)) {
return SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE;
}
/* Update F-Curve flags to ensure proper behavior for property type. */
update_autoflags_fcurve_direct(&fcu, RNA_property_type(&prop));
const bool visual_keyframing = flag & INSERTKEY_MATRIX;
Vector<float> values = get_property_values(&ptr, &prop, visual_keyframing);
const int index = fcu.array_index;
if (index < 0 || index >= values.size()) {
/* Can only happen if the FCurve and PropertyRNA do not match which
* should never be the case. */
BLI_assert_unreachable();
return SingleKeyingResult::UNKNOWN_FAILURE;
}
KeyframeSettings settings = get_keyframe_settings((flag & INSERTKEY_NO_USERPREF) == 0);
settings.keyframe_type = keytype;
return insert_vert_fcurve(&fcu, {fcurve_frame, values[index]}, settings, flag);
}
/* ************************************************** */
/* KEYFRAME DELETION */
/* Main Keyframing API call:
* Use this when validation of necessary animation data isn't necessary as it
* already exists. It will delete a keyframe at the current frame.
*
* The flag argument is used for special settings that alter the behavior of
* the keyframe deletion. These include the quick refresh options.
*/
static void deg_tag_after_keyframe_delete(Main *bmain, ID *id, AnimData *adt)
{
if (adt->action == nullptr) {
/* In the case last f-curve was removed need to inform dependency graph
* about relations update, since it needs to get rid of animation operation
* for this data-block. */
DEG_id_tag_update_ex(bmain, id, ID_RECALC_ANIMATION_NO_FLUSH);
DEG_relations_tag_update(bmain);
}
else {
DEG_id_tag_update_ex(bmain, &adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH);
}
}
int delete_keyframe(Main *bmain, ReportList *reports, ID *id, const RNAPath &rna_path, float cfra)
{
AnimData *adt = BKE_animdata_from_id(id);
if (ELEM(nullptr, id, adt)) {
BKE_report(reports, RPT_ERROR, "No ID block and/or AnimData to delete keyframe from");
return 0;
}
PointerRNA ptr;
PropertyRNA *prop;
PointerRNA id_ptr = RNA_id_pointer_create(id);
if (RNA_path_resolve_property(&id_ptr, rna_path.path.c_str(), &ptr, &prop) == false) {
BKE_reportf(
reports,
RPT_ERROR,
"Could not delete keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)",
id->name,
rna_path.path.c_str());
return 0;
}
if (!adt->action) {
BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name);
return 0;
}
bAction *act = adt->action;
cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP);
int array_index = rna_path.index.value_or(0);
int array_index_max = array_index + 1;
if (!rna_path.index.has_value()) {
array_index_max = RNA_property_array_length(&ptr, prop);
/* For single properties, increase max_index so that the property itself gets included,
* but don't do this for standard arrays since that can cause corruption issues
* (extra unused curves).
*/
if (array_index_max == array_index) {
array_index_max++;
}
}
Action &action = act->wrap();
Vector<FCurve *> modified_fcurves;
/* Just being defensive in the face of the NLA shenanigans above. This
* probably isn't necessary, but it doesn't hurt. */
BLI_assert(adt->action == act && action.slot_for_handle(adt->slot_handle) != nullptr);
Span<FCurve *> fcurves = fcurves_for_action_slot(action, adt->slot_handle);
/* This loop's clause is copied from the pre-existing code for legacy
* actions below, to ensure behavioral consistency between the two code
* paths. In the future when legacy actions are removed, we can restructure
* it to be clearer. */
for (; array_index < array_index_max; array_index++) {
FCurve *fcurve = fcurve_find(fcurves, {rna_path.path, array_index});
if (fcurve == nullptr) {
continue;
}
if (fcurve_delete_keyframe_at_time(fcurve, cfra)) {
modified_fcurves.append(fcurve);
}
}
if (!modified_fcurves.is_empty()) {
for (FCurve *fcurve : modified_fcurves) {
if (BKE_fcurve_is_empty(fcurve)) {
animdata_fcurve_delete(adt, fcurve);
}
}
deg_tag_after_keyframe_delete(bmain, id, adt);
}
return modified_fcurves.size();
}
/* ************************************************** */
/* KEYFRAME CLEAR */
int clear_keyframe(Main *bmain, ReportList *reports, ID *id, const RNAPath &rna_path)
{
AnimData *adt = BKE_animdata_from_id(id);
if (ELEM(nullptr, id, adt)) {
BKE_report(reports, RPT_ERROR, "No ID block and/or AnimData to delete keyframe from");
return 0;
}
PointerRNA ptr;
PropertyRNA *prop;
PointerRNA id_ptr = RNA_id_pointer_create(id);
if (RNA_path_resolve_property(&id_ptr, rna_path.path.c_str(), &ptr, &prop) == false) {
BKE_reportf(
reports,
RPT_ERROR,
"Could not clear keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)",
id->name,
rna_path.path.c_str());
return 0;
}
if (!adt->action) {
BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name);
return 0;
}
bAction *act = adt->action;
Action &action = act->wrap();
int key_count = 0;
if (adt->slot_handle) {
Vector<FCurve *> fcurves;
foreach_fcurve_in_action_slot_editable(action, adt->slot_handle, [&](FCurve &fcurve) {
if (rna_path.index.has_value() && rna_path.index.value() != fcurve.array_index) {
return;
}
if (rna_path.path != fcurve.rna_path) {
return;
}
fcurves.append(&fcurve);
});
for (FCurve *fcu : fcurves) {
if (action_fcurve_remove(action, *fcu)) {
key_count++;
}
}
}
if (key_count) {
deg_tag_after_keyframe_delete(bmain, id, adt);
}
return key_count;
}
struct KeyInsertData {
float2 position;
int array_index;
};
static SingleKeyingResult insert_key_layer(Main *bmain,
Action &action,
Layer &layer,
const Slot &slot,
const std::string &rna_path,
PropertyRNA *prop,
const std::optional<StringRefNull> channel_group,
const KeyInsertData &key_data,
const KeyframeSettings &key_settings,
const eInsertKeyFlags insert_key_flags)
{
assert_baklava_phase_1_invariants(layer);
BLI_assert(layer.strips().size() == 1);
const bool do_cyclic = (insert_key_flags & INSERTKEY_CYCLE_AWARE) && action.is_cyclic();
const PropertyType prop_type = RNA_property_type(prop);
const PropertySubType prop_subtype = RNA_property_subtype(prop);
Strip *strip = layer.strip(0);
return strip->data<StripKeyframeData>(action).keyframe_insert(
bmain,
slot,
{rna_path, key_data.array_index, prop_type, prop_subtype, channel_group},
key_data.position,
key_settings,
insert_key_flags,
do_cyclic ? std::optional(action.get_frame_range()) : std::nullopt);
}
static std::pair<Layer *, Slot *> prep_action_layer_for_keying(Action &action, ID &animated_id)
{
BLI_assert_msg(
ELEM(get_action(animated_id), &action, nullptr),
"The animated ID should not be using another Action than the one passed to this function");
Slot *slot = assign_action_ensure_slot_for_keying(action, animated_id);
BLI_assert_msg(
slot,
"The conditions that would cause this Slot assignment to fail (such as the ID not being "
"animatible) should have been caught and handled by higher-level functions.");
action.layer_keystrip_ensure();
/* TODO: we currently assume this will always successfully find a layer.
* However, that may not be true in the future when we implement features like
* layer locking: if layers already exist, but they are all locked, then the
* default layer won't be added by the line above, but there also won't be any
* layers we can insert keys into. */
Layer *layer = action.get_layer_for_keyframing();
BLI_assert(layer != nullptr);
return std::make_pair(layer, slot);
}
static CombinedKeyingResult insert_key_layered_action(
Main *bmain,
Action &action,
Layer &layer,
const Slot &slot,
PropertyRNA *prop,
const std::optional<StringRefNull> channel_group,
const std::string &rna_path,
const float frame,
const Span<float> values,
const eInsertKeyFlags insert_key_flags,
const KeyframeSettings &key_settings,
const BitSpan keying_mask)
{
BLI_assert(bmain != nullptr);
int property_array_index = 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 KeyInsertData key_data = {{frame, value}, property_array_index};
const SingleKeyingResult result = insert_key_layer(bmain,
action,
layer,
slot,
rna_path,
prop,
channel_group,
key_data,
key_settings,
insert_key_flags);
combined_result.add(result);
property_array_index++;
}
return combined_result;
}
CombinedKeyingResult insert_keyframes(Main *bmain,
PointerRNA *struct_pointer,
const std::optional<StringRefNull> channel_group,
const Span<RNAPath> rna_paths,
const std::optional<float> scene_frame,
const AnimationEvalContext &anim_eval_context,
const eBezTriple_KeyframeType key_type,
const eInsertKeyFlags insert_key_flags)
{
ID *id = struct_pointer->owner_id;
PointerRNA id_pointer = RNA_id_pointer_create(id);
CombinedKeyingResult combined_result;
/* Init animdata if none available yet. */
AnimData *adt = BKE_animdata_ensure_id(id);
if (adt == nullptr) {
combined_result.add(SingleKeyingResult::ID_NOT_ANIMATABLE);
return combined_result;
}
if ((adt->action == nullptr) && (insert_key_flags & INSERTKEY_AVAILABLE)) {
combined_result.add(SingleKeyingResult::CANNOT_CREATE_FCURVE, rna_paths.size());
return combined_result;
}
if (const bAction *action = adt->action) {
if (ID_IS_LINKED(action) || ID_IS_OVERRIDE_LIBRARY(action)) {
combined_result.add(SingleKeyingResult::ID_NOT_EDITABLE, rna_paths.size());
return combined_result;
}
}
bAction *dna_action = id_action_ensure(bmain, id);
BLI_assert(dna_action != nullptr);
Action &action = dna_action->wrap();
KeyframeSettings key_settings = get_keyframe_settings(
(insert_key_flags & INSERTKEY_NO_USERPREF) == 0);
key_settings.keyframe_type = key_type;
/* NOTE: keyframing functions can deal with the nla_context being a nullptr. */
ListBaseT<NlaKeyframingContext> nla_cache = {nullptr, nullptr};
NlaKeyframingContext *nla_context = nullptr;
const float nla_frame = nla_time_remap(scene_frame.value_or(anim_eval_context.eval_time),
&anim_eval_context,
&id_pointer,
adt,
dna_action,
&nla_cache,
&nla_context);
const bool visual_keyframing = insert_key_flags & INSERTKEY_MATRIX;
auto [layer, slot] = prep_action_layer_for_keying(action, *struct_pointer->owner_id);
for (const RNAPath &rna_path : rna_paths) {
PointerRNA ptr;
PropertyRNA *prop = nullptr;
const bool path_resolved = RNA_path_resolve_property(
struct_pointer, rna_path.path.c_str(), &ptr, &prop);
if (!path_resolved) {
combined_result.add(SingleKeyingResult::CANNOT_RESOLVE_PATH);
continue;
}
Vector<float> rna_values = get_property_values(&ptr, prop, visual_keyframing);
BitVector<> rna_values_mask(rna_values.size(), false);
bool force_all;
/* NOTE: this function call is complex with interesting/non-obvious effects.
* Please see its documentation for details. */
BKE_animsys_nla_remap_keyframe_values(nla_context,
&ptr,
prop,
rna_values.as_mutable_span(),
rna_path.index.value_or(-1),
&anim_eval_context,
&force_all,
rna_values_mask);
std::optional<std::string> rna_path_id_to_prop = RNA_path_from_ID_to_property(&ptr, prop);
if (!rna_path_id_to_prop.has_value()) {
/* In the case of nested RNA properties the path cannot be reconstructed in all cases. There
* may be a system in place in the future, see #122427. */
if (struct_pointer->data != id) {
continue;
}
/* However if the struct pointer happens to be an ID pointer we can use the path that was
* passed in. This fixes issues like #132195. */
rna_path_id_to_prop = rna_path.path;
}
/* Handle the `force_all` condition mentioned above, ensuring the
* "all-or-nothing" behavior if needed.
*
* TODO: this currently doesn't account for the "Only Insert Available"
* flag, which also needs to be accounted for to actually ensure
* all-or-nothing behavior. This is because the function this part of the
* code originally came from (see #122053) also didn't account for it.
* Presumably that was an oversight, and should be addressed. But for now
* we're faithfully reproducing the original behavior.
*/
eInsertKeyFlags insert_key_flags_adjusted = insert_key_flags;
if (force_all && (insert_key_flags & (INSERTKEY_REPLACE | INSERTKEY_AVAILABLE))) {
/* Determine if at least one element would succeed getting keyed. */
bool at_least_one_would_succeed = false;
for (int i = 0; i < rna_values.size(); i++) {
const FCurve *fcu = fcurve_find_in_action(dna_action, {*rna_path_id_to_prop, i});
if (!fcu) {
continue;
}
/* We found an fcurve, and "Only Replace" is not on, so a key insertion
* would succeed according to the two flags we're accounting for. */
if (!(insert_key_flags & INSERTKEY_REPLACE)) {
at_least_one_would_succeed = true;
break;
}
/* "Only Replace" *is* on, so a key insertion would succeed only if we
* actually replace an existing keyframe. */
bool replace;
BKE_fcurve_bezt_binarysearch_index(fcu->bezt, nla_frame, fcu->totvert, &replace);
if (replace) {
at_least_one_would_succeed = true;
break;
}
}
/* If at least one would succeed, then we disable all keying flags that
* would prevent the other elements from getting keyed as well. */
if (at_least_one_would_succeed) {
insert_key_flags_adjusted &= ~(INSERTKEY_REPLACE | INSERTKEY_AVAILABLE);
}
}
CombinedKeyingResult result;
const std::optional<StringRefNull> this_rna_path_channel_group =
channel_group.has_value() ? *channel_group :
default_channel_group_for_path(&ptr, *rna_path_id_to_prop);
result = insert_key_layered_action(bmain,
action,
*layer,
*slot,
prop,
this_rna_path_channel_group,
*rna_path_id_to_prop,
nla_frame,
rna_values,
insert_key_flags,
key_settings,
rna_values_mask);
combined_result.merge(result);
}
BKE_animsys_free_nla_keyframing_context_cache(&nla_cache);
if (combined_result.get_count(SingleKeyingResult::SUCCESS) > 0) {
/* NOTE: this is NOT using ID_RECALC_ANIMATION on purpose, because that would be quite annoying
* in the following case:
*
* - Key Cube's loc/rot/scale.
* - Go to another frame.
* - Translate, rotate, and scale the cube.
* - Hover over the loc/rot/scale properties and one by one press 'I' to
* insert a key there.
*
* If ID_RECALC_ANIMATION were used, keying the location would immediately cause a flush of the
* animation data, popping the rotation and scale back to their animated values. */
DEG_id_tag_update(&dna_action->id, ID_RECALC_ANIMATION_NO_FLUSH);
/* TODO: it's not entirely clear why the action we got wouldn't be the same
* as the action in AnimData. Further, it's not clear why it would need to
* be tagged for a depsgraph update regardless. This code is here because it
* was part of the function this one was refactored from, but at some point
* this should be investigated and either documented or removed. */
if (!ELEM(adt->action, nullptr, dna_action)) {
DEG_id_tag_update(&adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH);
}
}
return combined_result;
}
} // namespace blender::animrig