Fix: Autokeyframe with Insert Needed with no keyframes #116219

Merged
Christoph Lendenfeld merged 4 commits from ChrisLend/blender:fix_autokeyframe_keyed_channels into main 2023-12-21 10:50:26 +01:00
6 changed files with 239 additions and 80 deletions

View File

@ -142,7 +142,12 @@ bool is_autokey_flag(const Scene *scene, eKeyInsert_Flag flag);
*/
bool autokeyframe_cfra_can_key(const Scene *scene, ID *id);
void autokeyframe_object(bContext *C, Scene *scene, Object *ob);
/**

Maybe add some documentation for the rna_paths parameter. It's otherwise hard to know whether it's "always key all these paths" or "never key anything outside this set".

Maybe add some documentation for the `rna_paths` parameter. It's otherwise hard to know whether it's "always key all these paths" or "never key anything outside this set".
* Insert keyframes on the given object `ob` based on the auto-keying settings.
*
* \param rna_paths: Only inserts keys on those RNA paths.
*/
void autokeyframe_object(bContext *C, Scene *scene, Object *ob, Span<std::string> rna_paths);
/**
* Auto-keyframing feature - for objects
*
@ -154,12 +159,18 @@ bool autokeyframe_pchan(bContext *C, Scene *scene, Object *ob, bPoseChannel *pch
/**
* Auto-keyframing feature - for poses/pose-channels
*
* targetless_ik: has targetless ik been done on any channels?
* \param targetless_ik: Has targetless ik been done on any channels?
* \param rna_paths: Only inserts keys on those RNA paths.
*
* \note Context may not always be available,
* so must check before using it as it's a luxury for a few cases.

Same request as above, to document rna_paths. I think the rest is either already documented or clear enough from the context.

Same request as above, to document `rna_paths`. I think the rest is either already documented or clear enough from the context.
*/
void autokeyframe_pose(bContext *C, Scene *scene, Object *ob, short targetless_ik);
void autokeyframe_pose_channel(bContext *C,
Scene *scene,
Object *ob,
bPoseChannel *pose_channel,
Span<std::string> rna_paths,
short targetless_ik);
/**
* Use for auto-key-framing.
* \param only_if_property_keyed: if true, auto-key-framing only creates keyframes on already keyed

View File

@ -9,6 +9,7 @@
*/
#include "BLI_vector.hh"
#include "DNA_action_types.h"
#include "RNA_types.hh"
namespace blender::animrig {
@ -16,4 +17,7 @@ namespace blender::animrig {
/** Get the values of the given property. Casts non-float properties to float. */
Vector<float> get_rna_values(PointerRNA *ptr, PropertyRNA *prop);
/** Get the rna path for the given rotation mode. */
std::string get_rotation_mode_path(eRotationModes rotation_mode);

"Helper function to" can be removed; "Get the rna path for the given rotation mode." is clear enough.

"Helper function to" can be removed; "Get the rna path for the given rotation mode." is clear enough.
} // namespace blender::animrig

View File

@ -8,6 +8,7 @@
#include "ANIM_rna.hh"
#include "BLI_vector.hh"
#include "DNA_action_types.h"
#include "RNA_access.hh"
namespace blender::animrig {
@ -68,4 +69,16 @@ Vector<float> get_rna_values(PointerRNA *ptr, PropertyRNA *prop)
return values;
}
std::string get_rotation_mode_path(const eRotationModes rotation_mode)

Low prio: this might not need to be std::string, maybe std::string_view or blender::StringRef works just as well, and avoids a copy.

Not for this PR though, as just moving the function as-is is better here.

Low prio: this might not need to be `std::string`, maybe `std::string_view` or `blender::StringRef` works just as well, and avoids a copy. Not for this PR though, as just moving the function as-is is better here.
{
switch (rotation_mode) {
case ROT_MODE_QUAT:
return "rotation_quaternion";
case ROT_MODE_AXISANGLE:
return "rotation_axis_angle";
default:
return "rotation_euler";
}
}
} // namespace blender::animrig

View File

@ -26,6 +26,7 @@
#include "ED_transform.hh"
#include "ANIM_keyframing.hh"
#include "ANIM_rna.hh"
#include "WM_api.hh"
#include "WM_types.hh"
@ -82,19 +83,7 @@ bool autokeyframe_cfra_can_key(const Scene *scene, ID *id)
return true;
}
static std::string get_rotation_mode_path(const eRotationModes rotmode)
{
switch (rotmode) {
case ROT_MODE_QUAT:
return "rotation_quaternion";
case ROT_MODE_AXISANGLE:
return "rotation_axis_angle";
default:
return "rotation_euler";
}
}
void autokeyframe_object(bContext *C, Scene *scene, Object *ob)
void autokeyframe_object(bContext *C, Scene *scene, Object *ob, Span<std::string> rna_paths)
{
ID *id = &ob->id;
if (!autokeyframe_cfra_can_key(scene, id)) {
@ -148,13 +137,11 @@ void autokeyframe_object(bContext *C, Scene *scene, Object *ob)
}
const float scene_frame = BKE_scene_frame_get(scene);
std::string rotation_rna_path = get_rotation_mode_path(eRotationModes(ob->rotmode));
Vector<std::string> rna_paths = {"location", rotation_rna_path, "scale"};
Main *bmain = CTX_data_main(C);
for (PointerRNA ptr : sources) {
insert_key_rna(&ptr,
rna_paths.as_span(),
rna_paths,
scene_frame,
flag,
eBezTriple_KeyframeType(scene->toolsettings->keyframe_type),
@ -199,13 +186,17 @@ bool autokeyframe_pchan(bContext *C, Scene *scene, Object *ob, bPoseChannel *pch
return true;
}
void autokeyframe_pose(bContext *C, Scene *scene, Object *ob, short targetless_ik)
void autokeyframe_pose_channel(bContext *C,

This function now grabs stuff from the context and creates the AnimationEvalContext for every pose channel:

ReportList *reports = CTX_wm_reports(C);
ToolSettings *ts = scene->toolsettings;
KeyingSet *active_ks = ANIM_scene_get_active_keyingset(scene);
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
const float scene_frame = BKE_scene_frame_get(scene);
const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, scene_frame);

For this PR I think this is fine, but it might be worth looking into creating some AutoKeyframeContext struct that contains all of those, and pass that into this function instead.

This function now grabs stuff from the context and creates the `AnimationEvalContext` for every pose channel: ```cpp ReportList *reports = CTX_wm_reports(C); ToolSettings *ts = scene->toolsettings; KeyingSet *active_ks = ANIM_scene_get_active_keyingset(scene); Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); const float scene_frame = BKE_scene_frame_get(scene); const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, scene_frame); ``` For this PR I think this is fine, but it might be worth looking into creating some `AutoKeyframeContext` struct that contains all of those, and pass that into this function instead.

In the long term I'd like to remove the need for AnimationEvalContext from this function entirely

In the long term I'd like to remove the need for `AnimationEvalContext` from this function entirely
Scene *scene,
Object *ob,
bPoseChannel *pose_channel,
Span<std::string> rna_paths,
short targetless_ik)
{
Main *bmain = CTX_data_main(C);
ID *id = &ob->id;
AnimData *adt = ob->adt;
bAction *act = (adt) ? adt->action : nullptr;
bPose *pose = ob->pose;
if (!blender::animrig::autokeyframe_cfra_can_key(scene, id)) {
return;
@ -230,68 +221,57 @@ void autokeyframe_pose(bContext *C, Scene *scene, Object *ob, short targetless_i
flag |= INSERTKEY_MATRIX;
}
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
if ((pchan->bone->flag & BONE_TRANSFORM) == 0 &&
!((pose->flag & POSE_MIRROR_EDIT) && (pchan->bone->flag & BONE_TRANSFORM_MIRROR)))
{
continue;
blender::Vector<PointerRNA> sources;
/* Add data-source override for the camera object. */
ANIM_relative_keyingset_add_source(sources, id, &RNA_PoseBone, pose_channel);
/* only insert into active keyingset? */
if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_ONLYKEYINGSET) && (active_ks)) {
/* Run the active Keying Set on the current data-source. */
ANIM_apply_keyingset(
C, &sources, active_ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time);
return;
}
/* only insert into available channels? */
if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_INSERTAVAILABLE)) {
if (!act) {
return;
}
blender::Vector<PointerRNA> sources;
/* Add data-source override for the camera object. */
ANIM_relative_keyingset_add_source(sources, id, &RNA_PoseBone, pchan);
/* only insert into active keyingset? */
if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_ONLYKEYINGSET) && (active_ks)) {
/* Run the active Keying Set on the current data-source. */
ANIM_apply_keyingset(
C, &sources, active_ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time);
continue;
}
/* only insert into available channels? */
if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_INSERTAVAILABLE)) {
if (!act) {
LISTBASE_FOREACH (FCurve *, fcu, &act->curves) {
/* only insert keyframes for this F-Curve if it affects the current bone */
char pchan_name[sizeof(pose_channel->name)];
if (!BLI_str_quoted_substr(fcu->rna_path, "bones[", pchan_name, sizeof(pchan_name))) {
continue;
}
LISTBASE_FOREACH (FCurve *, fcu, &act->curves) {
/* only insert keyframes for this F-Curve if it affects the current bone */
char pchan_name[sizeof(pchan->name)];
if (!BLI_str_quoted_substr(fcu->rna_path, "bones[", pchan_name, sizeof(pchan_name))) {
continue;
}
/* only if bone name matches too...
* NOTE: this will do constraints too, but those are ok to do here too?
*/
if (STREQ(pchan_name, pchan->name)) {
blender::animrig::insert_keyframe(bmain,
reports,
id,
act,
((fcu->grp) ? (fcu->grp->name) : (nullptr)),
fcu->rna_path,
fcu->array_index,
&anim_eval_context,
eBezTriple_KeyframeType(ts->keyframe_type),
flag);
}
/* only if bone name matches too...
* NOTE: this will do constraints too, but those are ok to do here too?
*/
if (STREQ(pchan_name, pose_channel->name)) {
blender::animrig::insert_keyframe(bmain,
reports,
id,
act,
((fcu->grp) ? (fcu->grp->name) : (nullptr)),
fcu->rna_path,
fcu->array_index,
&anim_eval_context,
eBezTriple_KeyframeType(ts->keyframe_type),
flag);
}
continue;
}
return;
}
Main *bmain = CTX_data_main(C);
std::string rotation_rna_path = get_rotation_mode_path(eRotationModes(pchan->rotmode));
Vector<std::string> rna_paths = {"location", rotation_rna_path, "scale"};
for (PointerRNA &ptr : sources) {
insert_key_rna(&ptr,
rna_paths,
scene_frame,
flag,
eBezTriple_KeyframeType(scene->toolsettings->keyframe_type),
bmain,
reports);
}
for (PointerRNA &ptr : sources) {
insert_key_rna(&ptr,
rna_paths,
scene_frame,
flag,
eBezTriple_KeyframeType(scene->toolsettings->keyframe_type),
bmain,
reports);
}
}

View File

@ -39,6 +39,7 @@
#include "ANIM_bone_collections.hh"
#include "ANIM_keyframing.hh"
#include "ANIM_rna.hh"
#include "transform.hh"
#include "transform_orientations.hh"
@ -1266,6 +1267,81 @@ static void restoreMirrorPoseBones(TransDataContainer *tc)
}
}
/* Given the transform mode `tmode` return a Vector of RNA paths that were possibly modified during
* that transformation. */
static blender::Vector<std::string> get_affected_rna_paths_from_transform_mode(

rotation_path can be a blender::StringRef.

`rotation_path` can be a `blender::StringRef`.
const eTfmMode tmode,
ToolSettings *toolsettings,
const blender::StringRef rotation_path,
const bool targetless_ik)

This should be a switch (tmode) instead.

This should be a `switch (tmode)` instead.
{
blender::Vector<std::string> rna_paths;
switch (tmode) {
case TFM_TRANSLATION:
if (targetless_ik) {
rna_paths.append(rotation_path);
}
else {
rna_paths.append("location");
}
break;
case TFM_ROTATION:
case TFM_TRACKBALL:
if (ELEM(toolsettings->transform_pivot_point, V3D_AROUND_CURSOR, V3D_AROUND_ACTIVE)) {
rna_paths.append("location");
}
if ((toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) == 0) {
rna_paths.append(rotation_path);
}
break;
case TFM_RESIZE:
if (ELEM(toolsettings->transform_pivot_point, V3D_AROUND_CURSOR, V3D_AROUND_ACTIVE)) {
rna_paths.append("location");
}
if ((toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) == 0) {
rna_paths.append("scale");
}
break;
default:
break;
}
return rna_paths;
}
static void autokeyframe_pose(
bContext *C, Scene *scene, Object *ob, short targetless_ik, const eTfmMode tmode)
{

const, and possibly a blender::StringRef

`const`, and possibly a `blender::StringRef`
bPose *pose = ob->pose;
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
if ((pchan->bone->flag & BONE_TRANSFORM) == 0 &&
!((pose->flag & POSE_MIRROR_EDIT) && (pchan->bone->flag & BONE_TRANSFORM_MIRROR)))
{
continue;
}
blender::Vector<std::string> rna_paths;
const blender::StringRef rotation_path = blender::animrig::get_rotation_mode_path(
eRotationModes(pchan->rotmode));
if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_INSERTNEEDED)) {
rna_paths = get_affected_rna_paths_from_transform_mode(
tmode, scene->toolsettings, rotation_path, targetless_ik);
}
else {
rna_paths = {"location", rotation_path, "scale"};
}
blender::animrig::autokeyframe_pose_channel(
C, scene, ob, pchan, rna_paths.as_span(), targetless_ik);
}
}
static void recalcData_pose(TransInfo *t)
{
if (t->mode == TFM_BONESIZE) {
@ -1326,7 +1402,7 @@ static void recalcData_pose(TransInfo *t)
int targetless_ik = (t->flag & T_AUTOIK);
animrecord_check_state(t, &ob->id);
blender::animrig::autokeyframe_pose(t->context, t->scene, ob, targetless_ik);
autokeyframe_pose(t->context, t->scene, ob, targetless_ik, t->mode);
}
if (motionpath_need_update_pose(t->scene, ob)) {
@ -1589,7 +1665,7 @@ static void special_aftertrans_update__pose(bContext *C, TransInfo *t)
/* automatic inserting of keys and unkeyed tagging -
* only if transform wasn't canceled (or TFM_DUMMY) */
if (!canceled && (t->mode != TFM_DUMMY)) {
blender::animrig::autokeyframe_pose(C, t->scene, ob, targetless_ik);
autokeyframe_pose(C, t->scene, ob, targetless_ik, t->mode);
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
else {

View File

@ -25,6 +25,7 @@
#include "BKE_scene.h"
#include "ANIM_keyframing.hh"
#include "ANIM_rna.hh"
#include "ED_keyframing.hh"
#include "ED_object.hh"
@ -755,6 +756,80 @@ static bool motionpath_need_update_object(Scene *scene, Object *ob)
/** \name Recalc Data object
* \{ */
/* Given the transform mode `tmode` return a Vector of RNA paths that were possibly modified during

The function name could be clearer. It's likely meant as "the RNA paths that were modified by the transform system", but that's only clear once you know what it does. Maybe rna_paths_touched_by_transform_system or something along those lines?

This code also looks very much dependent on specifics of other areas of the code. I don't think we can totally avoid that without major rewrites of other areas. It would be nice, if it's doable, to add a comment here that explains which function(s) this logic should be in sync with.

The function name could be clearer. It's likely meant as "the RNA paths that were modified by the transform system", but that's only clear once you know what it does. Maybe `rna_paths_touched_by_transform_system` or something along those lines? This code also looks very much dependent on specifics of other areas of the code. I don't think we can totally avoid that without major rewrites of other areas. It would be nice, if it's doable, to add a comment here that explains which function(s) this logic should be in sync with.

named it get_affected_rna_paths_from_transform_mode and also used a switch here instead of the if/else chain

About which functions this should be in sync with. Not sure what you mean. You mean that it needs to be in sync with the transform system in general?

named it `get_affected_rna_paths_from_transform_mode` and also used a `switch` here instead of the if/else chain About which functions this should be in sync with. Not sure what you mean. You mean that it needs to be in sync with the transform system in general?

Yeah, and with the code as it is now, that's obvious enough 👍

Yeah, and with the code as it is now, that's obvious enough :+1:
* that transformation. */
static blender::Vector<std::string> get_affected_rna_paths_from_transform_mode(
const eTfmMode tmode,
Scene *scene,
ViewLayer *view_layer,
Object *ob,
const blender::StringRef rotation_path)
{
blender::Vector<std::string> rna_paths;
switch (tmode) {
case TFM_TRANSLATION:
rna_paths.append("location");
break;
case TFM_ROTATION:
case TFM_TRACKBALL:
if (scene->toolsettings->transform_pivot_point == V3D_AROUND_ACTIVE) {
BKE_view_layer_synced_ensure(scene, view_layer);
if (ob != BKE_view_layer_active_object_get(view_layer)) {
rna_paths.append("location");
}
}
else if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CURSOR) {
rna_paths.append("location");
}
if ((scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) == 0) {
rna_paths.append(rotation_path);
}
break;
case TFM_RESIZE:
if (scene->toolsettings->transform_pivot_point == V3D_AROUND_ACTIVE) {
BKE_view_layer_synced_ensure(scene, view_layer);
if (ob != BKE_view_layer_active_object_get(view_layer)) {
rna_paths.append("location");
}
}
else if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CURSOR) {
rna_paths.append("location");
}
if ((scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) == 0) {
rna_paths.append("scale");
}
break;
default:
rna_paths.append("location");
rna_paths.append(rotation_path);
rna_paths.append("scale");
}
return rna_paths;
}
static void autokeyframe_object(bContext *C, Scene *scene, Object *ob, const eTfmMode tmode)
{
blender::Vector<std::string> rna_paths;
ViewLayer *view_layer = CTX_data_view_layer(C);
const blender::StringRef rotation_path = blender::animrig::get_rotation_mode_path(
eRotationModes(ob->rotmode));
if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_INSERTNEEDED)) {
rna_paths = get_affected_rna_paths_from_transform_mode(
tmode, scene, view_layer, ob, rotation_path);
}
else {
rna_paths = {"location", rotation_path, "scale"};
}
blender::animrig::autokeyframe_object(C, scene, ob, rna_paths.as_span());
}
static void recalcData_objects(TransInfo *t)
{
bool motionpath_update = false;
@ -780,7 +855,7 @@ static void recalcData_objects(TransInfo *t)
* (FPoints) instead of keyframes? */
if ((t->animtimer) && blender::animrig::is_autokey_on(t->scene)) {
animrecord_check_state(t, &ob->id);
blender::animrig::autokeyframe_object(t->context, t->scene, ob);
autokeyframe_object(t->context, t->scene, ob, t->mode);
}
motionpath_update |= motionpath_need_update_object(t->scene, ob);
@ -855,7 +930,7 @@ static void special_aftertrans_update__object(bContext *C, TransInfo *t)
/* Set auto-key if necessary. */
if (!canceled) {
blender::animrig::autokeyframe_object(C, t->scene, ob);
autokeyframe_object(C, t->scene, ob, t->mode);
}
motionpath_update |= motionpath_need_update_object(t->scene, ob);