mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Anim: Blend in copied pose
Instead of pasting with 100% influence, allow users to blend from the current pose to the one in the clipboard. This makes use of the AnimTransformable API to easily implement the blending functionality. For ID properties I had to write a new function that deals with all ID property types and blends them based on their type. Note that ID pointer properties are not modified. That was already the case before this PR. In order to not change behavior for `POSE_OT_user_transforms_clear` I had to duplicate parts of the function. That function should move the bone back to its keyed position. The blend code from AnimTransformable returns a normalized quaternion which may not be what the keys are. Pull Request: https://projects.blender.org/blender/blender/pulls/161378
This commit is contained in:
parent
df3e4aab14
commit
668c6b008b
3 changed files with 205 additions and 78 deletions
|
|
@ -252,6 +252,7 @@ AnimTransformable::AnimTransformable(Object &owner_id, bPoseChannel &pchan)
|
|||
owner_id_(&owner_id.id),
|
||||
data_(&pchan),
|
||||
fcurve_group_name_(pchan.name),
|
||||
name_(pchan.name),
|
||||
location_({pchan.loc, 3}),
|
||||
rotation_mode_(&pchan.rotmode),
|
||||
scale_({pchan.scale, 3})
|
||||
|
|
@ -265,6 +266,7 @@ AnimTransformable::AnimTransformable(Object &obj)
|
|||
owner_id_(&obj.id),
|
||||
data_(&obj),
|
||||
fcurve_group_name_("Object Transforms"),
|
||||
name_(&obj.id.name[2]),
|
||||
location_({obj.loc, 3}),
|
||||
rotation_mode_(reinterpret_cast<eRotationModes *>(&obj.rotmode)),
|
||||
scale_({obj.scale, 3})
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
#include "WM_types.hh"
|
||||
|
||||
#include "ED_anim_api.hh"
|
||||
#include "ED_anim_transformable.hh"
|
||||
#include "ED_armature.hh"
|
||||
#include "ED_keyframing.hh"
|
||||
#include "ED_screen.hh"
|
||||
|
|
@ -608,31 +609,146 @@ void POSE_OT_visual_transform_apply(wmOperatorType *ot)
|
|||
/** \name Copy/Paste Utilities
|
||||
* \{ */
|
||||
|
||||
/**
|
||||
* Blend bbone values on `paste_bone` towards `copy_bone`. At factor 1 the values of `copy_bone`
|
||||
* are copied.
|
||||
*/
|
||||
static void blend_bbone_values(const bPoseChannel ©_bone,
|
||||
bPoseChannel &paste_bone,
|
||||
const float factor)
|
||||
{
|
||||
/* B-Bone posing options should also be included... */
|
||||
paste_bone.curve_in_x = interpf(copy_bone.curve_in_x, paste_bone.curve_in_x, factor);
|
||||
paste_bone.curve_in_z = interpf(copy_bone.curve_in_z, paste_bone.curve_in_z, factor);
|
||||
paste_bone.curve_out_x = interpf(copy_bone.curve_out_x, paste_bone.curve_out_x, factor);
|
||||
paste_bone.curve_out_z = interpf(copy_bone.curve_out_z, paste_bone.curve_out_z, factor);
|
||||
|
||||
paste_bone.roll1 = interpf(copy_bone.roll1, paste_bone.roll1, factor);
|
||||
paste_bone.roll2 = interpf(copy_bone.roll2, paste_bone.roll2, factor);
|
||||
paste_bone.ease1 = interpf(copy_bone.ease1, paste_bone.ease1, factor);
|
||||
paste_bone.ease2 = interpf(copy_bone.ease2, paste_bone.ease2, factor);
|
||||
|
||||
interp_v3_v3v3(paste_bone.scale_in, paste_bone.scale_in, copy_bone.scale_in, factor);
|
||||
interp_v3_v3v3(paste_bone.scale_out, paste_bone.scale_out, copy_bone.scale_out, factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blend the values of `prop` towards `target` based on the given `factor`. At 1.0, the target
|
||||
* values are copied 100%. Properties that cannot be interpolated (booleans, ID pointers, arrays of
|
||||
* arrays, etc. ) will just be copied when the factor is > 0.5.
|
||||
*/
|
||||
static void blend_id_property(IDProperty &prop, const IDProperty &target, const float factor)
|
||||
{
|
||||
BLI_assert(prop.type == target.type);
|
||||
BLI_assert(prop.subtype == target.subtype);
|
||||
|
||||
switch (prop.type) {
|
||||
case IDP_INT: {
|
||||
const int prop_val = IDP_int_get(&prop);
|
||||
const int target_val = IDP_int_get(&target);
|
||||
IDP_int_set(&prop, round_fl_to_int(interpf(target_val, prop_val, factor)));
|
||||
return;
|
||||
}
|
||||
case IDP_FLOAT: {
|
||||
const float prop_val = IDP_float_get(&prop);
|
||||
const float target_val = IDP_float_get(&target);
|
||||
IDP_float_set(&prop, interpf(target_val, prop_val, factor));
|
||||
return;
|
||||
}
|
||||
case IDP_DOUBLE: {
|
||||
const double prop_val = IDP_double_get(&prop);
|
||||
const double target_val = IDP_double_get(&target);
|
||||
IDP_double_set(&prop, interpd(target_val, prop_val, factor));
|
||||
return;
|
||||
}
|
||||
case IDP_ARRAY: {
|
||||
if (prop.len != target.len) {
|
||||
/* For arrays, their length has to match too. */
|
||||
return;
|
||||
}
|
||||
switch (prop.subtype) {
|
||||
case IDP_INT: {
|
||||
int *prop_val = IDP_array_int_get(&prop);
|
||||
const int *target_val = IDP_array_int_get(&target);
|
||||
for (int i = 0; i < prop.len; i++) {
|
||||
prop_val[i] = round_fl_to_int(interpf(target_val[i], prop_val[i], factor));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case IDP_FLOAT: {
|
||||
float *prop_val = IDP_array_float_get(&prop);
|
||||
const float *target_val = IDP_array_float_get(&target);
|
||||
for (int i = 0; i < prop.len; i++) {
|
||||
prop_val[i] = interpf(target_val[i], prop_val[i], factor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case IDP_DOUBLE: {
|
||||
double *prop_val = IDP_array_double_get(&prop);
|
||||
const double *target_val = IDP_array_double_get(&target);
|
||||
for (int i = 0; i < prop.len; i++) {
|
||||
prop_val[i] = interpd(target_val[i], prop_val[i], factor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Default case for properties that can't be interpolated. */
|
||||
if (factor > 0.5) {
|
||||
IDP_CopyPropertyContent(&prop, &target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blend the given `copy_props` towards `paste_props`. At factor 1, paste_props is used 100%.
|
||||
*/
|
||||
static void blend_properties(IDProperty *copy_props, IDProperty *paste_props, const float factor)
|
||||
{
|
||||
IDP_foreach_property(copy_props, 0, [&](IDProperty *copy_prop) {
|
||||
IDProperty *paste_prop = IDP_GetPropertyFromGroup(paste_props, copy_prop->name);
|
||||
if (!paste_prop || copy_prop->type != paste_prop->type ||
|
||||
copy_prop->subtype != paste_prop->subtype)
|
||||
{
|
||||
return;
|
||||
}
|
||||
blend_id_property(*paste_prop, *copy_prop, factor);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform paste pose, for a single bone.
|
||||
*
|
||||
* \param paste_ob: Object where bone to paste to lives.
|
||||
* \param copy_bone: Bone that data to paste comes from.
|
||||
* \param paste_ob: Object where bone to paste to lives
|
||||
* \param copy_transformable: Transformable that the data comes from. Assumed to be a bPoseChannel.
|
||||
* \param selected_only: Only paste on selected bones
|
||||
* \param flip: Flip on x-axis
|
||||
* \param r_is_found: optional return param, indicates whether the expected bone was found. This
|
||||
* helps to distinguish between "not found" and "found, but skipped because not selected" cases.
|
||||
* \return The channel of the bone that was pasted to, or nullptr if no paste was performed.
|
||||
*/
|
||||
static bPoseChannel *pose_bone_do_paste(Object &paste_ob,
|
||||
const bPoseChannel ©_bone,
|
||||
static bPoseChannel *pose_bone_blend_to(Object &paste_ob,
|
||||
const ed::AnimTransformable ©_transformable,
|
||||
const bool selected_only,
|
||||
const bool flip,
|
||||
const float factor,
|
||||
bool *r_is_found = nullptr)
|
||||
{
|
||||
BLI_assert(copy_transformable.type() == ed::AnimTransformable::Type::POSE_BONE);
|
||||
char name[MAXBONENAME];
|
||||
|
||||
/* Get the name of the bone to paste to - if flipping, we must flip this first. */
|
||||
if (flip) {
|
||||
BLI_string_flip_side_name(name, copy_bone.name, false, sizeof(name));
|
||||
BLI_string_flip_side_name(name, copy_transformable.name().c_str(), false, sizeof(name));
|
||||
}
|
||||
else {
|
||||
STRNCPY_UTF8(name, copy_bone.name);
|
||||
STRNCPY_UTF8(name, copy_transformable.name().c_str());
|
||||
}
|
||||
|
||||
bPoseChannel *paste_bone = BKE_pose_channel_find_name(paste_ob.pose, name);
|
||||
|
|
@ -648,66 +764,23 @@ static bPoseChannel *pose_bone_do_paste(Object &paste_ob,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
copy_v3_v3(paste_bone->loc, copy_bone.loc);
|
||||
copy_v3_v3(paste_bone->scale, copy_bone.scale);
|
||||
|
||||
/* Check if rotation modes are compatible (i.e. do they need any conversions). */
|
||||
if (paste_bone->rotmode == copy_bone.rotmode) {
|
||||
/* Copy the type of rotation in use. */
|
||||
if (paste_bone->rotmode > 0) {
|
||||
copy_v3_v3(paste_bone->eul, copy_bone.eul);
|
||||
}
|
||||
else if (paste_bone->rotmode == ROT_MODE_AXISANGLE) {
|
||||
copy_v3_v3(paste_bone->rotAxis, copy_bone.rotAxis);
|
||||
paste_bone->rotAngle = copy_bone.rotAngle;
|
||||
}
|
||||
else {
|
||||
copy_qt_qt(paste_bone->quat, copy_bone.quat);
|
||||
}
|
||||
}
|
||||
else if (paste_bone->rotmode > 0) {
|
||||
/* quat/axis-angle to euler */
|
||||
if (copy_bone.rotmode == ROT_MODE_AXISANGLE) {
|
||||
axis_angle_to_eulO(
|
||||
paste_bone->eul, paste_bone->rotmode, copy_bone.rotAxis, copy_bone.rotAngle);
|
||||
}
|
||||
else {
|
||||
quat_to_eulO(paste_bone->eul, paste_bone->rotmode, copy_bone.quat);
|
||||
}
|
||||
}
|
||||
else if (paste_bone->rotmode == ROT_MODE_AXISANGLE) {
|
||||
/* quat/euler to axis angle */
|
||||
if (copy_bone.rotmode > 0) {
|
||||
eulO_to_axis_angle(
|
||||
paste_bone->rotAxis, &paste_bone->rotAngle, copy_bone.eul, copy_bone.rotmode);
|
||||
}
|
||||
else {
|
||||
quat_to_axis_angle(paste_bone->rotAxis, &paste_bone->rotAngle, copy_bone.quat);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* euler/axis-angle to quat */
|
||||
if (copy_bone.rotmode > 0) {
|
||||
eulO_to_quat(paste_bone->quat, copy_bone.eul, copy_bone.rotmode);
|
||||
}
|
||||
else {
|
||||
axis_angle_to_quat(paste_bone->quat, copy_bone.rotAxis, paste_bone->rotAngle);
|
||||
}
|
||||
}
|
||||
ed::AnimTransformable paste_transformable(paste_ob, *paste_bone);
|
||||
paste_transformable.blend_property_to(
|
||||
ed::AnimTransformable::PropertyType::LOCATION,
|
||||
copy_transformable.get_property(ed::AnimTransformable::PropertyType::LOCATION),
|
||||
factor,
|
||||
ed::AXIS_MUTABLE_ALL);
|
||||
paste_transformable.blend_rotation_to(
|
||||
copy_transformable.get_rotation(), factor, ed::AXIS_MUTABLE_ALL);
|
||||
paste_transformable.blend_property_to(
|
||||
ed::AnimTransformable::PropertyType::SCALE,
|
||||
copy_transformable.get_property(ed::AnimTransformable::PropertyType::SCALE),
|
||||
factor,
|
||||
ed::AXIS_MUTABLE_ALL);
|
||||
|
||||
bPoseChannel *copy_bone = copy_transformable.data<bPoseChannel *>();
|
||||
/* B-Bone posing options should also be included... */
|
||||
paste_bone->curve_in_x = copy_bone.curve_in_x;
|
||||
paste_bone->curve_in_z = copy_bone.curve_in_z;
|
||||
paste_bone->curve_out_x = copy_bone.curve_out_x;
|
||||
paste_bone->curve_out_z = copy_bone.curve_out_z;
|
||||
|
||||
paste_bone->roll1 = copy_bone.roll1;
|
||||
paste_bone->roll2 = copy_bone.roll2;
|
||||
paste_bone->ease1 = copy_bone.ease1;
|
||||
paste_bone->ease2 = copy_bone.ease2;
|
||||
|
||||
copy_v3_v3(paste_bone->scale_in, copy_bone.scale_in);
|
||||
copy_v3_v3(paste_bone->scale_out, copy_bone.scale_out);
|
||||
blend_bbone_values(*copy_bone, *paste_bone, factor);
|
||||
|
||||
/* Flips pose directly by modifying transform parameters. */
|
||||
if (flip) {
|
||||
|
|
@ -741,26 +814,23 @@ static bPoseChannel *pose_bone_do_paste(Object &paste_ob,
|
|||
}
|
||||
}
|
||||
|
||||
/* ID properties */
|
||||
if (copy_bone.prop) {
|
||||
/* ID properties. */
|
||||
if (copy_bone->prop) {
|
||||
if (paste_bone->prop) {
|
||||
/* If we have existing properties on a bone, just copy over the values of
|
||||
* matching properties (i.e. ones which will have some impact) on to the target
|
||||
* instead of just blindly replacing all. */
|
||||
IDP_SyncGroupValues(paste_bone->prop, copy_bone.prop);
|
||||
blend_properties(copy_bone->prop, paste_bone->prop, factor);
|
||||
}
|
||||
else {
|
||||
/* No existing properties, so assume that we want copies too? */
|
||||
paste_bone->prop = IDP_CopyProperty(copy_bone.prop);
|
||||
/* no existing properties, so assume that we want copies too? */
|
||||
paste_bone->prop = IDP_CopyProperty(copy_bone->prop);
|
||||
}
|
||||
}
|
||||
if (copy_bone.system_properties) {
|
||||
if (copy_bone->system_properties) {
|
||||
/* Same logic as above for system IDProperties, for now. */
|
||||
if (paste_bone->system_properties) {
|
||||
IDP_SyncGroupValues(paste_bone->system_properties, copy_bone.system_properties);
|
||||
blend_properties(copy_bone->system_properties, paste_bone->system_properties, factor);
|
||||
}
|
||||
else {
|
||||
paste_bone->system_properties = IDP_CopyProperty(copy_bone.system_properties);
|
||||
paste_bone->system_properties = IDP_CopyProperty(copy_bone->system_properties);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -916,7 +986,8 @@ static wmOperatorStatus pose_paste_exec(bContext *C, wmOperator *op)
|
|||
int num_pasted_bones = 0;
|
||||
int num_skipped_bones = 0;
|
||||
int num_copied_bones = 0;
|
||||
for (const bPoseChannel &pchan_from : pose_from->chanbase) {
|
||||
const float factor = RNA_float_get(op->ptr, "factor");
|
||||
for (bPoseChannel &pchan_from : pose_from->chanbase) {
|
||||
if ((pchan_from.flag & POSE_SELECTED) == 0) {
|
||||
/* This code pretends that bones that were not selected at copy time do not exist. */
|
||||
continue;
|
||||
|
|
@ -926,7 +997,8 @@ static wmOperatorStatus pose_paste_exec(bContext *C, wmOperator *op)
|
|||
|
||||
/* Try to perform paste on this bone. */
|
||||
bool is_found;
|
||||
bPoseChannel *pchan_to = pose_bone_do_paste(*ob, pchan_from, selected_only, flip, &is_found);
|
||||
bPoseChannel *pchan_to = pose_bone_blend_to(
|
||||
*ob, {*object_from, pchan_from}, selected_only, flip, factor, &is_found);
|
||||
if (!pchan_to) {
|
||||
if (is_found) {
|
||||
/* The bone was found, but not selected (and selected_only), so nothing was pasted to it.
|
||||
|
|
@ -1030,6 +1102,18 @@ void POSE_OT_paste(wmOperatorType *ot)
|
|||
false,
|
||||
"On Selected Only",
|
||||
"Only paste the stored pose on to selected bones in the current pose");
|
||||
|
||||
RNA_def_float_factor(
|
||||
ot->srna,
|
||||
"factor",
|
||||
1.0f,
|
||||
-FLT_MAX,
|
||||
FLT_MAX,
|
||||
"Factor",
|
||||
"Blends the current pose to the pose to paste. At 1, the pasted pose completely "
|
||||
"overwrites the current pose",
|
||||
0.0f,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
@ -1439,6 +1523,40 @@ void POSE_OT_transforms_clear(wmOperatorType *ot)
|
|||
/** \name Clear User Transforms Operator
|
||||
* \{ */
|
||||
|
||||
static void pose_bone_copy_transform_values(Object &paste_ob,
|
||||
const bPoseChannel ©_bone,
|
||||
const bool selected_only)
|
||||
{
|
||||
bPoseChannel *paste_bone = BKE_pose_channel_find_name(paste_ob.pose, copy_bone.name);
|
||||
if (paste_bone == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (selected_only && (paste_bone->flag & POSE_SELECTED) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
copy_v3_v3(paste_bone->loc, copy_bone.loc);
|
||||
copy_v3_v3(paste_bone->eul, copy_bone.eul);
|
||||
copy_v3_v3(paste_bone->rotAxis, copy_bone.rotAxis);
|
||||
paste_bone->rotAngle = copy_bone.rotAngle;
|
||||
copy_v4_v4(paste_bone->quat, copy_bone.quat);
|
||||
copy_v3_v3(paste_bone->scale, copy_bone.scale);
|
||||
|
||||
blend_bbone_values(copy_bone, *paste_bone, 1.0);
|
||||
|
||||
/* ID properties */
|
||||
if (copy_bone.prop && paste_bone->prop) {
|
||||
/* If we have existing properties on a bone, just copy over the values of
|
||||
* matching properties (i.e. ones which will have some impact) on to the target
|
||||
* instead of just blindly replacing all. */
|
||||
IDP_SyncGroupValues(paste_bone->prop, copy_bone.prop);
|
||||
}
|
||||
if (copy_bone.system_properties && paste_bone->system_properties) {
|
||||
/* Same logic as above for system IDProperties. */
|
||||
IDP_SyncGroupValues(paste_bone->system_properties, copy_bone.system_properties);
|
||||
}
|
||||
}
|
||||
|
||||
static wmOperatorStatus pose_clear_user_transforms_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
|
|
@ -1470,7 +1588,7 @@ static wmOperatorStatus pose_clear_user_transforms_exec(bContext *C, wmOperator
|
|||
&workob.id, workob.adt, &anim_eval_context, ADT_RECALC_ANIM, false);
|
||||
|
||||
for (bPoseChannel &pchan : dummyPose->chanbase) {
|
||||
pose_bone_do_paste(*ob, pchan, only_select, false);
|
||||
pose_bone_copy_transform_values(*ob, pchan, only_select);
|
||||
}
|
||||
|
||||
/* Free temp data - free manually as was copied without constraints. */
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "DNA_action_types.h"
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ class AnimTransformable {
|
|||
* be created in the constructor. For structs that are an ID this is an empty string. */
|
||||
std::string rna_path_from_id_;
|
||||
std::string fcurve_group_name_;
|
||||
StringRefNull name_;
|
||||
|
||||
/* We are assuming here that the ground truth of transforms is store in separate loc rot scale
|
||||
* and not in a matrix, thus skew is not supported. */
|
||||
|
|
@ -126,6 +128,11 @@ class AnimTransformable {
|
|||
return fcurve_group_name_;
|
||||
}
|
||||
|
||||
StringRefNull name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
template<typename T> T data() const;
|
||||
|
||||
/* Returns the rna path from the ID to the struct represented by this transformable. If the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue