Anim: expose pose blending & backup system to RNA

Expose `BKE_pose_apply_action_blend` and a simplified pose backup system
to RNA. This will make it possible to easily create some interactive
tools in Python for pose blending.

When creating a backup via this API, it is stored on the
`Object::runtime` struct. Any backup that was there before is freed
first. This way the Python code doesn't need access to the actual
`PoseBackup *`, simplifying memory management.

The limitation of having only a single backup shouldn't be too
problematic, as it is meant for things like interactive manipulation of
the current pose. Typical use looks like:

- Interactive operator starts, and creates a backup of the current pose.
- While the operator is running:
    - The pose backup is restored, so that the next steps always use the
      same reference pose.
    - Depending on user input, determine a blend factor.
    - Blend some pose from the pose library into the current pose.
- On confirmation, leave the pose as-is.
- On cancellation, restore the backup.
- Free the backup.

`BKE_pose_apply_action_blend` is exposed to RNA to make the above
possible.

An alternative approach would be to rely on the operator redo system.
However, since for poses this would use the global undo, it can get
prohibitively slow. This change is to make it easier to prototype
things; further into the future the undo system for poses should be
improved, but that's an entire project on its own.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D16900
This commit is contained in:
Sybren A. Stüvel 2023-01-02 16:39:51 +01:00
parent da4e2fe7fe
commit 94155fb6ff
5 changed files with 172 additions and 2 deletions

View File

@ -39,6 +39,28 @@ bool BKE_pose_backup_is_selection_relevant(const struct PoseBackup *pose_backup)
void BKE_pose_backup_restore(const struct PoseBackup *pbd);
void BKE_pose_backup_free(struct PoseBackup *pbd);
/**
* Create a backup of those bones that are animated in the given action.
*
* The backup is owned by the Object, and there can be only one backup at a time.
* It should be freed with `BKE_pose_backup_clear(ob)`.
*/
void BKE_pose_backup_create_on_object(struct Object *ob, const struct bAction *action);
/**
* Restore the pose backup owned by this OBject.
*
* \return true on success, false if there was no pose backup to restore.
*
* \see #BKE_pose_backup_create_on_object
*/
bool BKE_pose_backup_restore_on_object(struct Object *ob);
/**
* Free the pose backup that was stored on this object's runtime data.
*/
void BKE_pose_backup_clear(struct Object *ob);
#ifdef __cplusplus
}
#endif

View File

@ -117,6 +117,7 @@
#include "BKE_pbvh.h"
#include "BKE_pointcache.h"
#include "BKE_pointcloud.h"
#include "BKE_pose_backup.h"
#include "BKE_rigidbody.h"
#include "BKE_scene.h"
#include "BKE_shader_fx.h"
@ -1814,6 +1815,7 @@ void BKE_object_free_derived_caches(Object *ob)
}
BKE_object_to_mesh_clear(ob);
BKE_pose_backup_clear(ob);
BKE_object_to_curve_clear(ob);
BKE_object_free_curve_cache(ob);
@ -5132,6 +5134,7 @@ void BKE_object_runtime_reset_on_copy(Object *object, const int /*flag*/)
runtime->mesh_deform_eval = nullptr;
runtime->curve_cache = nullptr;
runtime->object_as_temp_mesh = nullptr;
runtime->pose_backup = nullptr;
runtime->object_as_temp_curve = nullptr;
runtime->geometry_set_eval = nullptr;

View File

@ -135,3 +135,29 @@ void BKE_pose_backup_free(PoseBackup *pbd)
}
MEM_freeN(pbd);
}
void BKE_pose_backup_create_on_object(Object *ob, const bAction *action)
{
BKE_pose_backup_clear(ob);
PoseBackup *pose_backup = BKE_pose_backup_create_all_bones(ob, action);
ob->runtime.pose_backup = pose_backup;
}
bool BKE_pose_backup_restore_on_object(struct Object *ob)
{
if (ob->runtime.pose_backup == nullptr) {
return false;
}
BKE_pose_backup_restore(ob->runtime.pose_backup);
return true;
}
void BKE_pose_backup_clear(Object *ob)
{
if (ob->runtime.pose_backup == nullptr) {
return;
}
BKE_pose_backup_free(ob->runtime.pose_backup);
ob->runtime.pose_backup = nullptr;
}

View File

@ -192,6 +192,14 @@ typedef struct Object_Runtime {
*/
struct Mesh *object_as_temp_mesh;
/**
* Backup of the object's pose (might be a subset, i.e. not contain all bones).
*
* Created by `BKE_pose_backup_create_on_object()`. This memory is owned by the Object.
* It is freed along with the object, or when `BKE_pose_backup_clear()` is called.
*/
struct PoseBackup *pose_backup;
/**
* This is a curve representation of corresponding object.
* It created when Python calls `object.to_curve()`.
@ -200,6 +208,7 @@ typedef struct Object_Runtime {
/** Runtime evaluated curve-specific data, not stored in the file. */
struct CurveCache *curve_cache;
void *_pad4;
unsigned short local_collections_bits;
short _pad2[3];

View File

@ -25,6 +25,7 @@
# include "BKE_animsys.h"
# include "BKE_armature.h"
# include "BKE_context.h"
# include "BKE_pose_backup.h"
# include "DNA_action_types.h"
# include "DNA_anim_types.h"
@ -108,6 +109,56 @@ static void rna_Pose_apply_pose_from_action(ID *pose_owner,
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, pose_owner);
}
static void rna_Pose_blend_pose_from_action(ID *pose_owner,
bContext *C,
bAction *action,
const float blend_factor,
const float evaluation_time)
{
BLI_assert(GS(pose_owner->name) == ID_OB);
Object *pose_owner_ob = (Object *)pose_owner;
AnimationEvalContext anim_eval_context = {CTX_data_depsgraph_pointer(C), evaluation_time};
BKE_pose_apply_action_blend(pose_owner_ob, action, &anim_eval_context, blend_factor);
/* Do NOT tag with ID_RECALC_ANIMATION, as that would overwrite the just-applied pose. */
DEG_id_tag_update(pose_owner, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, pose_owner);
}
static void rna_Pose_backup_create(ID *pose_owner, bAction *action)
{
BLI_assert(GS(pose_owner->name) == ID_OB);
Object *pose_owner_ob = (Object *)pose_owner;
BKE_pose_backup_create_on_object(pose_owner_ob, action);
}
static bool rna_Pose_backup_restore(ID *pose_owner, bContext *C)
{
BLI_assert(GS(pose_owner->name) == ID_OB);
Object *pose_owner_ob = (Object *)pose_owner;
const bool success = BKE_pose_backup_restore_on_object(pose_owner_ob);
if (!success) {
return false;
}
/* Do NOT tag with ID_RECALC_ANIMATION, as that would overwrite the just-applied pose. */
DEG_id_tag_update(pose_owner, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, pose_owner);
return true;
}
static void rna_Pose_backup_clear(ID *pose_owner)
{
BLI_assert(GS(pose_owner->name) == ID_OB);
Object *pose_owner_ob = (Object *)pose_owner;
BKE_pose_backup_clear(pose_owner_ob);
}
#else
void RNA_api_pose(StructRNA *srna)
@ -121,10 +172,8 @@ void RNA_api_pose(StructRNA *srna)
func,
"Apply the given action to this pose by evaluating it at a specific time. Only updates the "
"pose of selected bones, or all bones if none are selected.");
parm = RNA_def_pointer(func, "action", "Action", "Action", "The Action containing the pose");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
parm = RNA_def_float(func,
"evaluation_time",
0.0f,
@ -134,6 +183,67 @@ void RNA_api_pose(StructRNA *srna)
"Time at which the given action is evaluated to obtain the pose",
-FLT_MAX,
FLT_MAX);
func = RNA_def_function(srna, "blend_pose_from_action", "rna_Pose_blend_pose_from_action");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_NO_SELF | FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func,
"Blend the given action into this pose by evaluating it at a "
"specific time. Only updates the "
"pose of selected bones, or all bones if none are selected.");
parm = RNA_def_pointer(func, "action", "Action", "Action", "The Action containing the pose");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
parm = RNA_def_float(func,
"blend_factor",
1.0f,
0.0f,
1.0f,
"Blend Factor",
"How much the given Action affects the final pose",
0.0f,
1.0f);
parm = RNA_def_float(func,
"evaluation_time",
0.0f,
-FLT_MAX,
FLT_MAX,
"Evaluation Time",
"Time at which the given action is evaluated to obtain the pose",
-FLT_MAX,
FLT_MAX);
func = RNA_def_function(srna, "backup_create", "rna_Pose_backup_create");
RNA_def_function_ui_description(
func,
"Create a backup of the current pose. Only those bones that are animated in the Action are "
"backed up. The object owns the backup, and each object can have only one backup at a time. "
"When you no longer need it, it must be freed use `backup_clear()`.");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_NO_SELF);
parm = RNA_def_pointer(func,
"action",
"Action",
"Action",
"An Action with animation data for the bones. Only the animated bones "
"will be included in the backup.");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
func = RNA_def_function(srna, "backup_restore", "rna_Pose_backup_restore");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_NO_SELF | FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func,
"Restore the previously made pose backup. This can be called "
"multiple times. See `Pose.backup_create()` for more info.");
/* return value */
parm = RNA_def_boolean(
func,
"success",
false,
"",
"`True` when the backup was restored, `False` if there was no backup to restore.");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "backup_clear", "rna_Pose_backup_clear");
RNA_def_function_ui_description(
func, "Free a previously made pose backup. See `Pose.backup_create()` for more info.");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_NO_SELF);
}
void RNA_api_pose_channel(StructRNA *srna)