Apply Pose as Rest Pose: implement an Only Selected bones option.
The most difficult part is handling parent-child relations correctly: when a parent is applied, the children should be moved accordingly, and when applying a child, it should not include transformation from unapplied parents. All this requires walking bones as a tree, instead of a flat list. Limitation: Applying bones with non-uniform scaling without also applying children will distort non-rest posing on said children for reasons related to T54159 (basically, non-uniform scale plus rotation creates shear, and Blender matrix decomposition utilities don't have tools to deal with it). Reviewers: campbellbarton, brecht, mont29 Differential Revision: https://developer.blender.org/D3775
This commit is contained in:
parent
93cabce3a1
commit
0264d8390f
|
@ -3143,7 +3143,8 @@ class VIEW3D_MT_pose_apply(Menu):
|
|||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("pose.armature_apply")
|
||||
layout.operator("pose.armature_apply").selected = False
|
||||
layout.operator("pose.armature_apply", text="Apply Selected as Rest Pose").selected = True
|
||||
layout.operator("pose.visual_transform_apply")
|
||||
|
||||
layout.separator()
|
||||
|
|
|
@ -59,6 +59,9 @@
|
|||
#include "ED_screen.h"
|
||||
#include "ED_util.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_resources.h"
|
||||
|
||||
#include "armature_intern.h"
|
||||
|
||||
/* ********************************************** */
|
||||
|
@ -87,6 +90,229 @@ static void applyarmature_fix_boneparents(const bContext *C, Scene *scene, Objec
|
|||
}
|
||||
}
|
||||
|
||||
/* Sets the bone head, tail and roll to match the supplied parameters. */
|
||||
static void applyarmature_set_edit_position(EditBone *curbone,
|
||||
const float pose_mat[4][4],
|
||||
const float new_tail[3],
|
||||
float r_new_arm_mat[4][4])
|
||||
{
|
||||
/* Simply copy the head/tail values from pchan over to curbone. */
|
||||
copy_v3_v3(curbone->head, pose_mat[3]);
|
||||
copy_v3_v3(curbone->tail, new_tail);
|
||||
|
||||
/* Fix roll:
|
||||
* 1. find auto-calculated roll value for this bone now
|
||||
* 2. remove this from the 'visual' y-rotation
|
||||
*/
|
||||
{
|
||||
float premat[3][3], pmat[3][3];
|
||||
float delta[3];
|
||||
|
||||
/* Obtain new auto y-rotation. */
|
||||
sub_v3_v3v3(delta, curbone->tail, curbone->head);
|
||||
|
||||
copy_m3_m4(pmat, pose_mat);
|
||||
mat3_vec_to_roll(pmat, delta, &curbone->roll);
|
||||
|
||||
/* Compute new rest pose matrix if requested. */
|
||||
if (r_new_arm_mat) {
|
||||
vec_roll_to_mat3(delta, curbone->roll, premat);
|
||||
copy_m4_m3(r_new_arm_mat, premat);
|
||||
copy_v3_v3(r_new_arm_mat[3], pose_mat[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy properties over from pchan to curbone and reset channels. */
|
||||
static void applyarmature_transfer_properties(EditBone *curbone,
|
||||
bPoseChannel *pchan,
|
||||
const bPoseChannel *pchan_eval)
|
||||
{
|
||||
/* Combine pose and rest values for bendy bone settings,
|
||||
* then clear the pchan values (so we don't get a double-up).
|
||||
*/
|
||||
if (pchan->bone->segments > 1) {
|
||||
/* Combine rest/pose values. */
|
||||
curbone->curve_in_x += pchan_eval->curve_in_x;
|
||||
curbone->curve_in_y += pchan_eval->curve_in_y;
|
||||
curbone->curve_out_x += pchan_eval->curve_out_x;
|
||||
curbone->curve_out_y += pchan_eval->curve_out_y;
|
||||
curbone->roll1 += pchan_eval->roll1;
|
||||
curbone->roll2 += pchan_eval->roll2;
|
||||
curbone->ease1 += pchan_eval->ease1;
|
||||
curbone->ease2 += pchan_eval->ease2;
|
||||
|
||||
curbone->scale_in_x *= pchan_eval->scale_in_x;
|
||||
curbone->scale_in_y *= pchan_eval->scale_in_y;
|
||||
curbone->scale_out_x *= pchan_eval->scale_out_x;
|
||||
curbone->scale_out_y *= pchan_eval->scale_out_y;
|
||||
|
||||
/* Reset pose values. */
|
||||
pchan->curve_in_x = pchan->curve_out_x = 0.0f;
|
||||
pchan->curve_in_y = pchan->curve_out_y = 0.0f;
|
||||
pchan->roll1 = pchan->roll2 = 0.0f;
|
||||
pchan->ease1 = pchan->ease2 = 0.0f;
|
||||
pchan->scale_in_x = pchan->scale_in_y = 1.0f;
|
||||
pchan->scale_out_x = pchan->scale_out_y = 1.0f;
|
||||
}
|
||||
|
||||
/* Clear transform values for pchan. */
|
||||
zero_v3(pchan->loc);
|
||||
zero_v3(pchan->eul);
|
||||
unit_qt(pchan->quat);
|
||||
unit_axis_angle(pchan->rotAxis, &pchan->rotAngle);
|
||||
pchan->size[0] = pchan->size[1] = pchan->size[2] = 1.0f;
|
||||
|
||||
/* Set anim lock. */
|
||||
curbone->flag |= BONE_UNKEYED;
|
||||
}
|
||||
|
||||
/* Adjust the current edit position of the bone using the pose space matrix. */
|
||||
static void applyarmature_adjust_edit_position(bArmature *arm,
|
||||
bPoseChannel *pchan,
|
||||
const float delta_mat[4][4],
|
||||
float r_new_arm_mat[4][4])
|
||||
{
|
||||
EditBone *curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name);
|
||||
float delta[3], new_tail[3], premat[3][3], new_pose[4][4];
|
||||
|
||||
/* Current orientation matrix. */
|
||||
sub_v3_v3v3(delta, curbone->tail, curbone->head);
|
||||
vec_roll_to_mat3(delta, curbone->roll, premat);
|
||||
|
||||
/* New location and orientation. */
|
||||
mul_m4_m4m3(new_pose, delta_mat, premat);
|
||||
mul_v3_m4v3(new_pose[3], delta_mat, curbone->head);
|
||||
mul_v3_m4v3(new_tail, delta_mat, curbone->tail);
|
||||
|
||||
applyarmature_set_edit_position(curbone, new_pose, new_tail, r_new_arm_mat);
|
||||
}
|
||||
|
||||
/* Data about parent position for Apply To Selected mode. */
|
||||
typedef struct ApplyArmature_ParentState {
|
||||
Bone *bone;
|
||||
|
||||
/* New rest position of the bone with scale included. */
|
||||
float new_rest_mat[4][4];
|
||||
/* New arm_mat of the bone == new_rest_mat without scale. */
|
||||
float new_arm_mat[4][4];
|
||||
} ApplyArmature_ParentState;
|
||||
|
||||
/* Recursive walk for Apply To Selected mode; pstate NULL unless child of an applied bone. */
|
||||
static void applyarmature_process_selected_rec(bArmature *arm,
|
||||
bPose *pose,
|
||||
bPose *pose_eval,
|
||||
Bone *bone,
|
||||
ListBase *selected,
|
||||
ApplyArmature_ParentState *pstate)
|
||||
{
|
||||
bPoseChannel *pchan = BKE_pose_channel_find_name(pose, bone->name);
|
||||
const bPoseChannel *pchan_eval = BKE_pose_channel_find_name(pose_eval, bone->name);
|
||||
|
||||
if (!pchan || !pchan_eval)
|
||||
return;
|
||||
|
||||
ApplyArmature_ParentState new_pstate = {.bone = bone};
|
||||
|
||||
if (BLI_findptr(selected, pchan, offsetof(CollectionPointerLink, ptr.data))) {
|
||||
/* SELECTED BONE: Snap to final pose transform minus unapplied parent effects.
|
||||
*
|
||||
* I.e. bone position with accumulated parent effects but no local
|
||||
* transformation will match the original final pose_mat.
|
||||
*
|
||||
* Pose channels are reset as expected.
|
||||
*/
|
||||
EditBone *curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name);
|
||||
BoneParentTransform invparent;
|
||||
float new_tail[3];
|
||||
|
||||
if (pchan->parent) {
|
||||
BoneParentTransform old_bpt, new_bpt;
|
||||
float offs_bone[4][4];
|
||||
|
||||
/* Parent effects on the bone transform that have to be removed. */
|
||||
BKE_bone_offset_matrix_get(bone, offs_bone);
|
||||
BKE_bone_parent_transform_calc_from_matrices(
|
||||
bone->flag, offs_bone, bone->parent->arm_mat, pchan_eval->parent->pose_mat, &old_bpt);
|
||||
|
||||
/* Applied parent effects that have to be kept, if any. */
|
||||
float(*new_parent_pose)[4] = pstate ? pstate->new_rest_mat : bone->parent->arm_mat;
|
||||
BKE_bone_parent_transform_calc_from_matrices(
|
||||
bone->flag, offs_bone, bone->parent->arm_mat, new_parent_pose, &new_bpt);
|
||||
|
||||
BKE_bone_parent_transform_invert(&old_bpt);
|
||||
BKE_bone_parent_transform_combine(&new_bpt, &old_bpt, &invparent);
|
||||
}
|
||||
else {
|
||||
BKE_bone_parent_transform_clear(&invparent);
|
||||
}
|
||||
|
||||
/* Apply change without inherited unapplied parent transformations. */
|
||||
BKE_bone_parent_transform_apply(&invparent, pchan_eval->pose_mat, new_pstate.new_rest_mat);
|
||||
|
||||
copy_v3_fl3(new_tail, 0.0, bone->length, 0.0);
|
||||
mul_m4_v3(new_pstate.new_rest_mat, new_tail);
|
||||
|
||||
applyarmature_set_edit_position(
|
||||
curbone, new_pstate.new_rest_mat, new_tail, new_pstate.new_arm_mat);
|
||||
applyarmature_transfer_properties(curbone, pchan, pchan_eval);
|
||||
|
||||
pstate = &new_pstate;
|
||||
}
|
||||
else if (pstate) {
|
||||
/* UNSELECTED CHILD OF SELECTED: Include applied parent effects.
|
||||
*
|
||||
* The inherited transform of applied (selected) bones is baked
|
||||
* into the rest pose so that the final bone position doesn't
|
||||
* change.
|
||||
*
|
||||
* Pose channels are not changed, with the exception of the inherited
|
||||
* applied parent scale being baked into the location pose channel.
|
||||
*/
|
||||
BoneParentTransform bpt;
|
||||
float offs_bone[4][4], delta[4][4], old_chan_loc[3];
|
||||
|
||||
/* Include applied parent effects. */
|
||||
BKE_bone_offset_matrix_get(bone, offs_bone);
|
||||
BKE_bone_parent_transform_calc_from_matrices(
|
||||
bone->flag, offs_bone, pstate->bone->arm_mat, pstate->new_rest_mat, &bpt);
|
||||
|
||||
unit_m4(new_pstate.new_rest_mat);
|
||||
BKE_bone_parent_transform_apply(&bpt, new_pstate.new_rest_mat, new_pstate.new_rest_mat);
|
||||
|
||||
/* Bone location channel in pose space relative to bone head. */
|
||||
mul_v3_mat3_m4v3(old_chan_loc, bpt.loc_mat, pchan_eval->loc);
|
||||
|
||||
/* Apply the change to the rest bone position. */
|
||||
invert_m4_m4(delta, bone->arm_mat);
|
||||
mul_m4_m4m4(delta, new_pstate.new_rest_mat, delta);
|
||||
|
||||
applyarmature_adjust_edit_position(arm, pchan, delta, new_pstate.new_arm_mat);
|
||||
|
||||
/* Location pose channel has to be updated, because it is affected
|
||||
* by parent scaling, and the rest pose has no scale by definition. */
|
||||
if (!(bone->flag & BONE_CONNECTED) && !is_zero_v3(old_chan_loc)) {
|
||||
float inv_parent_arm[4][4];
|
||||
|
||||
/* Compute the channel coordinate space matrices for the new rest state. */
|
||||
invert_m4_m4(inv_parent_arm, pstate->new_arm_mat);
|
||||
mul_m4_m4m4(offs_bone, inv_parent_arm, new_pstate.new_arm_mat);
|
||||
BKE_bone_parent_transform_calc_from_matrices(
|
||||
bone->flag, offs_bone, pstate->new_arm_mat, pstate->new_arm_mat, &bpt);
|
||||
|
||||
/* Re-apply the location to keep the final effect. */
|
||||
invert_m4(bpt.loc_mat);
|
||||
mul_v3_mat3_m4v3(pchan->loc, bpt.loc_mat, old_chan_loc);
|
||||
}
|
||||
|
||||
pstate = &new_pstate;
|
||||
}
|
||||
|
||||
for (Bone *child = bone->childbase.first; child; child = child->next) {
|
||||
applyarmature_process_selected_rec(arm, pose, pose_eval, child, selected, pstate);
|
||||
}
|
||||
}
|
||||
|
||||
/* set the current pose as the restpose */
|
||||
static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
|
@ -99,7 +325,9 @@ static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op)
|
|||
bArmature *arm = BKE_armature_from_object(ob);
|
||||
bPose *pose;
|
||||
bPoseChannel *pchan;
|
||||
EditBone *curbone;
|
||||
ListBase selected_bones;
|
||||
|
||||
const bool use_selected = RNA_boolean_get(op->ptr, "selected");
|
||||
|
||||
/* don't check if editmode (should be done by caller) */
|
||||
if (ob->type != OB_ARMATURE) {
|
||||
|
@ -119,80 +347,37 @@ static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op)
|
|||
"transforms stored are relative to the old rest pose");
|
||||
}
|
||||
|
||||
/* Find selected bones before switching to edit mode. */
|
||||
if (use_selected) {
|
||||
CTX_data_selected_pose_bones(C, &selected_bones);
|
||||
|
||||
if (!selected_bones.first) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get editbones of active armature to alter */
|
||||
ED_armature_to_edit(arm);
|
||||
|
||||
/* get pose of active object and move it out of posemode */
|
||||
pose = ob->pose;
|
||||
|
||||
for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) {
|
||||
const bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name);
|
||||
curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name);
|
||||
|
||||
/* simply copy the head/tail values from pchan over to curbone */
|
||||
copy_v3_v3(curbone->head, pchan_eval->pose_head);
|
||||
copy_v3_v3(curbone->tail, pchan_eval->pose_tail);
|
||||
|
||||
/* fix roll:
|
||||
* 1. find auto-calculated roll value for this bone now
|
||||
* 2. remove this from the 'visual' y-rotation
|
||||
*/
|
||||
{
|
||||
float premat[3][3], imat[3][3], pmat[3][3], tmat[3][3];
|
||||
float delta[3], eul[3];
|
||||
|
||||
/* obtain new auto y-rotation */
|
||||
sub_v3_v3v3(delta, curbone->tail, curbone->head);
|
||||
vec_roll_to_mat3(delta, 0.0f, premat);
|
||||
invert_m3_m3(imat, premat);
|
||||
|
||||
/* get pchan 'visual' matrix */
|
||||
copy_m3_m4(pmat, pchan_eval->pose_mat);
|
||||
|
||||
/* remove auto from visual and get euler rotation */
|
||||
mul_m3_m3m3(tmat, imat, pmat);
|
||||
mat3_to_eul(eul, tmat);
|
||||
|
||||
/* just use this euler-y as new roll value */
|
||||
curbone->roll = eul[1];
|
||||
if (use_selected) {
|
||||
/* The selected only mode requires a recursive walk to handle parent-child relations. */
|
||||
for (Bone *bone = arm->bonebase.first; bone; bone = bone->next) {
|
||||
applyarmature_process_selected_rec(arm, pose, ob_eval->pose, bone, &selected_bones, NULL);
|
||||
}
|
||||
|
||||
/* combine pose and rest values for bendy bone settings,
|
||||
* then clear the pchan values (so we don't get a double-up)
|
||||
*/
|
||||
if (pchan->bone->segments > 1) {
|
||||
/* combine rest/pose values */
|
||||
curbone->curve_in_x += pchan_eval->curve_in_x;
|
||||
curbone->curve_in_y += pchan_eval->curve_in_y;
|
||||
curbone->curve_out_x += pchan_eval->curve_out_x;
|
||||
curbone->curve_out_y += pchan_eval->curve_out_y;
|
||||
curbone->roll1 += pchan_eval->roll1;
|
||||
curbone->roll2 += pchan_eval->roll2;
|
||||
curbone->ease1 += pchan_eval->ease1;
|
||||
curbone->ease2 += pchan_eval->ease2;
|
||||
curbone->scale_in_x *= pchan_eval->scale_in_x;
|
||||
curbone->scale_in_y *= pchan_eval->scale_in_y;
|
||||
curbone->scale_out_x *= pchan_eval->scale_out_x;
|
||||
curbone->scale_out_y *= pchan_eval->scale_out_y;
|
||||
BLI_freelistN(&selected_bones);
|
||||
}
|
||||
else {
|
||||
for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) {
|
||||
const bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name);
|
||||
EditBone *curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name);
|
||||
|
||||
/* reset pose values */
|
||||
pchan->curve_in_x = pchan->curve_out_x = 0.0f;
|
||||
pchan->curve_in_y = pchan->curve_out_y = 0.0f;
|
||||
pchan->roll1 = pchan->roll2 = 0.0f;
|
||||
pchan->ease1 = pchan->ease2 = 0.0f;
|
||||
pchan->scale_in_x = pchan->scale_in_y = 1.0f;
|
||||
pchan->scale_out_x = pchan->scale_out_y = 1.0f;
|
||||
applyarmature_set_edit_position(curbone, pchan_eval->pose_mat, pchan_eval->pose_tail, NULL);
|
||||
applyarmature_transfer_properties(curbone, pchan, pchan_eval);
|
||||
}
|
||||
|
||||
/* clear transform values for pchan */
|
||||
zero_v3(pchan->loc);
|
||||
zero_v3(pchan->eul);
|
||||
unit_qt(pchan->quat);
|
||||
unit_axis_angle(pchan->rotAxis, &pchan->rotAngle);
|
||||
pchan->size[0] = pchan->size[1] = pchan->size[2] = 1.0f;
|
||||
|
||||
/* set anim lock */
|
||||
curbone->flag |= BONE_UNKEYED;
|
||||
}
|
||||
|
||||
/* convert editbones back to bones, and then free the edit-data */
|
||||
|
@ -212,6 +397,17 @@ static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op)
|
|||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void apply_armature_pose2bones_ui(bContext *C, wmOperator *op)
|
||||
{
|
||||
uiLayout *layout = op->layout;
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
PointerRNA ptr;
|
||||
|
||||
RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
|
||||
|
||||
uiItemR(layout, &ptr, "selected", 0, NULL, ICON_NONE);
|
||||
}
|
||||
|
||||
void POSE_OT_armature_apply(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
|
@ -222,9 +418,16 @@ void POSE_OT_armature_apply(wmOperatorType *ot)
|
|||
/* callbacks */
|
||||
ot->exec = apply_armature_pose2bones_exec;
|
||||
ot->poll = ED_operator_posemode;
|
||||
ot->ui = apply_armature_pose2bones_ui;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"selected",
|
||||
false,
|
||||
"Selected Only",
|
||||
"Only apply the selected bones (with propagation to children)");
|
||||
}
|
||||
|
||||
/* set the current pose as the restpose */
|
||||
|
|
Loading…
Reference in New Issue