252 lines
8.5 KiB
C++
252 lines
8.5 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup animrig
|
|
*/
|
|
|
|
#include "BLI_bit_vector.hh"
|
|
#include "BLI_dynstr.h"
|
|
|
|
#include "BKE_animsys.h"
|
|
#include "BKE_fcurve.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_types.hh"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "ANIM_keyframing.hh"
|
|
#include "ANIM_nla.hh"
|
|
|
|
namespace blender::animrig::nla {
|
|
|
|
bool assign_action(NlaStrip &strip, Action &action, ID &animated_id)
|
|
{
|
|
if (!generic_assign_action(
|
|
animated_id, &action, strip.act, strip.action_slot_handle, strip.last_slot_identifier))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* For the NLA, the auto slot selection gets one more fallback option (compared to the generic
|
|
* code). This is to support the following scenario:
|
|
*
|
|
* - Python script creates an Action, and adds some F-Curves via the legacy API.
|
|
* - This creates a slot 'XXSlot'.
|
|
* - The script creates multiple NLA strips for that Action.
|
|
* - The desired result is that these strips get the same Slot assigned as well.
|
|
*
|
|
* The generic code doesn't work for this. The first strip assignment would see the slot
|
|
* `XXSlot`, and because it has never been used, just use it. This would change its name to, for
|
|
* example, `OBSlot`. The second strip assignment would not see a 'virgin' slot, and thus not
|
|
* auto-select `OBSlot`. This behavior makes sense when assigning Actions in the Action editor
|
|
* (it shouldn't automatically pick the first slot of matching ID type), but for the NLA I
|
|
* (Sybren) feel that it could be a bit more 'enthusiastic' in auto-picking a slot.
|
|
*/
|
|
if (strip.action_slot_handle == Slot::unassigned && action.slots().size() == 1) {
|
|
Slot *first_slot = action.slot(0);
|
|
if (first_slot->is_suitable_for(animated_id)) {
|
|
const ActionSlotAssignmentResult result = assign_action_slot(strip, first_slot, animated_id);
|
|
BLI_assert_msg(result == ActionSlotAssignmentResult::OK,
|
|
"Assigning a slot that we know is suitable should work");
|
|
UNUSED_VARS_NDEBUG(result);
|
|
}
|
|
}
|
|
|
|
/* Regardless of slot auto-selection, the Action assignment worked just fine. */
|
|
return true;
|
|
}
|
|
|
|
void unassign_action(NlaStrip &strip, ID &animated_id)
|
|
{
|
|
const bool ok = generic_assign_action(
|
|
animated_id, nullptr, strip.act, strip.action_slot_handle, strip.last_slot_identifier);
|
|
BLI_assert_msg(ok, "Un-assigning an Action from an NLA strip should always work.");
|
|
UNUSED_VARS_NDEBUG(ok);
|
|
}
|
|
|
|
ActionSlotAssignmentResult assign_action_slot(NlaStrip &strip,
|
|
Slot *slot_to_assign,
|
|
ID &animated_id)
|
|
{
|
|
BLI_assert(strip.act);
|
|
|
|
return generic_assign_action_slot(slot_to_assign,
|
|
animated_id,
|
|
strip.act,
|
|
strip.action_slot_handle,
|
|
strip.last_slot_identifier);
|
|
}
|
|
|
|
ActionSlotAssignmentResult assign_action_slot_handle(NlaStrip &strip,
|
|
const slot_handle_t slot_handle,
|
|
ID &animated_id)
|
|
{
|
|
BLI_assert(strip.act);
|
|
|
|
Action &action = strip.act->wrap();
|
|
Slot *slot_to_assign = action.slot_for_handle(slot_handle);
|
|
|
|
return assign_action_slot(strip, slot_to_assign, animated_id);
|
|
}
|
|
|
|
/* Check indices that were intended to be remapped and report any failed remaps. */
|
|
static void get_keyframe_values_create_reports(ReportList *reports,
|
|
const PointerRNA &ptr,
|
|
const PropertyRNA *prop,
|
|
const int index,
|
|
const int count,
|
|
const bool force_all,
|
|
const BitSpan successful_remaps)
|
|
{
|
|
|
|
DynStr *ds_failed_indices = BLI_dynstr_new();
|
|
|
|
int total_failed = 0;
|
|
for (int i = 0; i < count; i++) {
|
|
const bool cur_index_evaluated = ELEM(index, i, -1) || force_all;
|
|
if (!cur_index_evaluated) {
|
|
/* `values[i]` was never intended to be remapped. */
|
|
continue;
|
|
}
|
|
|
|
if (successful_remaps[i]) {
|
|
/* `values[i]` successfully remapped. */
|
|
continue;
|
|
}
|
|
|
|
total_failed++;
|
|
/* Report that `values[i]` were intended to be remapped but failed remapping process. */
|
|
BLI_dynstr_appendf(ds_failed_indices, "%d, ", i);
|
|
}
|
|
|
|
if (total_failed == 0) {
|
|
BLI_dynstr_free(ds_failed_indices);
|
|
return;
|
|
}
|
|
|
|
char *str_failed_indices = BLI_dynstr_get_cstring(ds_failed_indices);
|
|
BLI_dynstr_free(ds_failed_indices);
|
|
|
|
BKE_reportf(reports,
|
|
RPT_WARNING,
|
|
"Could not insert %i keyframe(s) due to zero NLA influence, base value, or value "
|
|
"remapping failed: %s.%s for indices [%s]",
|
|
total_failed,
|
|
ptr.owner_id->name,
|
|
RNA_property_ui_name(prop),
|
|
str_failed_indices);
|
|
|
|
MEM_delete(str_failed_indices);
|
|
}
|
|
|
|
static BitVector<> nla_map_keyframe_values_and_generate_reports(
|
|
const MutableSpan<float> values,
|
|
const int index,
|
|
PointerRNA &ptr,
|
|
PropertyRNA &prop,
|
|
NlaKeyframingContext *nla_context,
|
|
const AnimationEvalContext *anim_eval_context,
|
|
ReportList *reports,
|
|
bool *force_all)
|
|
{
|
|
BitVector<> successful_remaps(values.size(), false);
|
|
BKE_animsys_nla_remap_keyframe_values(
|
|
nla_context, &ptr, &prop, values, index, anim_eval_context, force_all, successful_remaps);
|
|
get_keyframe_values_create_reports(
|
|
reports, ptr, &prop, index, values.size(), false, successful_remaps);
|
|
return successful_remaps;
|
|
}
|
|
|
|
bool insert_keyframe_direct(ReportList *reports,
|
|
PointerRNA ptr,
|
|
PropertyRNA *prop,
|
|
FCurve *fcu,
|
|
const AnimationEvalContext *anim_eval_context,
|
|
eBezTriple_KeyframeType keytype,
|
|
NlaKeyframingContext *nla_context,
|
|
eInsertKeyFlags flag)
|
|
{
|
|
if (fcu == nullptr) {
|
|
BKE_report(reports, RPT_ERROR, "No F-Curve to add keyframes to");
|
|
return false;
|
|
}
|
|
|
|
if (!BKE_fcurve_is_keyframable(*fcu)) {
|
|
BKE_report(reports, RPT_ERROR, "FCurve is not keyable. Cannot insert keyframes");
|
|
return false;
|
|
}
|
|
|
|
if ((ptr.owner_id == nullptr) && (ptr.data == nullptr)) {
|
|
BKE_report(
|
|
reports, RPT_ERROR, "No RNA pointer available to retrieve values for keyframing from");
|
|
return false;
|
|
}
|
|
|
|
if (prop == nullptr) {
|
|
PointerRNA tmp_ptr;
|
|
|
|
if (RNA_path_resolve_property(&ptr, fcu->rna_path, &tmp_ptr, &prop) == false) {
|
|
const char *idname = (ptr.owner_id) ? ptr.owner_id->name : RPT_("<No ID pointer>");
|
|
|
|
BKE_reportf(reports,
|
|
RPT_ERROR,
|
|
"Could not insert keyframe, as RNA path is invalid for the given ID (ID = %s, "
|
|
"path = %s)",
|
|
idname,
|
|
fcu->rna_path);
|
|
return false;
|
|
}
|
|
|
|
/* Property found, so overwrite 'ptr' to make later code easier. */
|
|
ptr = tmp_ptr;
|
|
}
|
|
|
|
/* Update F-Curve flags to ensure proper behavior for property type. */
|
|
update_autoflags_fcurve_direct(fcu, RNA_property_type(prop));
|
|
|
|
const int index = fcu->array_index;
|
|
const bool visual_keyframing = flag & INSERTKEY_MATRIX;
|
|
Vector<float> values = get_property_values(&ptr, prop, visual_keyframing);
|
|
|
|
BitVector<> successful_remaps = nla_map_keyframe_values_and_generate_reports(
|
|
values.as_mutable_span(),
|
|
index,
|
|
ptr,
|
|
*prop,
|
|
nla_context,
|
|
anim_eval_context,
|
|
reports,
|
|
nullptr);
|
|
|
|
float current_value = 0.0f;
|
|
if (index >= 0 && index < values.size()) {
|
|
current_value = values[index];
|
|
}
|
|
|
|
/* This happens if NLA rejects this insertion. */
|
|
if (!successful_remaps[index]) {
|
|
return false;
|
|
}
|
|
|
|
KeyframeSettings settings = get_keyframe_settings((flag & INSERTKEY_NO_USERPREF) == 0);
|
|
settings.keyframe_type = keytype;
|
|
|
|
const SingleKeyingResult result = insert_vert_fcurve(
|
|
fcu, {anim_eval_context->eval_time, current_value}, settings, flag);
|
|
|
|
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 "
|
|
"locked or sampled, and try removing F-Modifiers",
|
|
fcu->rna_path,
|
|
fcu->array_index);
|
|
}
|
|
return result == SingleKeyingResult::SUCCESS;
|
|
}
|
|
|
|
} // namespace blender::animrig::nla
|