/* SPDX-FileCopyrightText: 2023 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup animrig */ #include #include #include #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 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 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(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 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( 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()); } /* 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(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 get_property_values(PointerRNA *ptr, PropertyRNA *prop, const bool visual_key) { Vector 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 *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 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 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 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 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 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(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 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 channel_group, const std::string &rna_path, const float frame, const Span 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 channel_group, const Span rna_paths, const std::optional 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 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 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 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 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