/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ /** \file * \ingroup edarmature */ #include "DNA_anim_types.h" #include "DNA_armature_types.h" #include "DNA_constraint_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_math.h" #include "BLI_string_utils.h" #include "BKE_action.h" #include "BKE_animsys.h" #include "BKE_appdir.h" #include "BKE_armature.h" #include "BKE_blender_copybuffer.h" #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_idprop.h" #include "BKE_layer.h" #include "BKE_main.h" #include "BKE_object.h" #include "BKE_report.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" #include "RNA_access.h" #include "RNA_define.h" #include "RNA_prototypes.h" #include "WM_api.h" #include "WM_types.h" #include "ED_armature.h" #include "ED_keyframing.h" #include "ED_screen.h" #include "ED_util.h" #include "UI_interface.h" #include "UI_resources.h" #include "armature_intern.h" /* -------------------------------------------------------------------- */ /** \name Apply Pose as Rest Pose * \{ */ /* helper for apply_armature_pose2bones - fixes parenting of objects * that are bone-parented to armature */ static void applyarmature_fix_boneparents(const bContext *C, Scene *scene, Object *armob) { /* Depsgraph has been ensured to be evaluated at the beginning of the operator. * * Must not evaluate depsgraph here yet, since this will ruin object matrix which we want to * preserve after other changes has been done in the operator. * * TODO(sergey): This seems very similar to `ignore_parent_tx()`, which was now ensured to work * quite reliably. Can we de-duplicate the code? Or at least verify we don't need an extra logic * in this function. */ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Main *bmain = CTX_data_main(C); Object workob, *ob; /* go through all objects in database */ for (ob = bmain->objects.first; ob; ob = ob->id.next) { /* if parent is bone in this armature, apply corrections */ if ((ob->parent == armob) && (ob->partype == PARBONE)) { /* apply current transform from parent (not yet destroyed), * then calculate new parent inverse matrix */ BKE_object_apply_mat4(ob, ob->obmat, false, false); BKE_object_workob_calc_parent(depsgraph, scene, ob, &workob); invert_m4_m4(ob->parentinv, workob.obmat); } } } /* 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_z += pchan_eval->curve_in_z; curbone->curve_out_x += pchan_eval->curve_out_x; curbone->curve_out_z += pchan_eval->curve_out_z; curbone->roll1 += pchan_eval->roll1; curbone->roll2 += pchan_eval->roll2; curbone->ease1 += pchan_eval->ease1; curbone->ease2 += pchan_eval->ease2; mul_v3_v3(curbone->scale_in, pchan_eval->scale_in); mul_v3_v3(curbone->scale_out, pchan_eval->scale_out); /* Reset pose values. */ pchan->curve_in_x = pchan->curve_out_x = 0.0f; pchan->curve_in_z = pchan->curve_out_z = 0.0f; pchan->roll1 = pchan->roll2 = 0.0f; pchan->ease1 = pchan->ease2 = 0.0f; copy_v3_fl(pchan->scale_in, 1.0f); copy_v3_fl(pchan->scale_out, 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; } /* 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_recursive(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 un-applied 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, bone->inherit_scale_mode, 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, bone->inherit_scale_mode, 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, bone->inherit_scale_mode, 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, bone->inherit_scale_mode, 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; } LISTBASE_FOREACH (Bone *, child, &bone->childbase) { applyarmature_process_selected_recursive(arm, pose, pose_eval, child, selected, pstate); } } /* Reset bone constraint so that it is correct after the pose has been applied. */ static void applyarmature_reset_bone_constraint(const bConstraint *constraint) { /* TODO(Sybren): This function needs too much knowledge of the internals of specific constraints. * When it is extended with one or two more constraints, move the functionality into a * bConstraintTypeInfo callback function. */ switch (constraint->type) { case CONSTRAINT_TYPE_STRETCHTO: { bStretchToConstraint *stretch_to = (bStretchToConstraint *)constraint->data; stretch_to->orglength = 0.0f; /* Force recalculation on next evaluation. */ break; } default: /* Most constraints don't need resetting. */ break; } } /* Reset bone constraints of the given pose channel so that they are correct after the pose has * been applied. */ static void applyarmature_reset_bone_constraints(const bPoseChannel *pchan) { LISTBASE_FOREACH (bConstraint *, constraint, &pchan->constraints) { applyarmature_reset_bone_constraint(constraint); } } /* Reset all (or only selected) bone constraints so that they are correct after the pose has been * applied. */ static void applyarmature_reset_constraints(bPose *pose, const bool use_selected) { for (bPoseChannel *pchan = pose->chanbase.first; pchan; pchan = pchan->next) { BLI_assert(pchan->bone != NULL); if (use_selected && (pchan->bone->flag & BONE_SELECTED) == 0) { continue; } applyarmature_reset_bone_constraints(pchan); } } /* set the current pose as the restpose */ static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); /* must be active object, not edit-object */ Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C)); const Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); bArmature *arm = BKE_armature_from_object(ob); bPose *pose; bPoseChannel *pchan; 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) { return OPERATOR_CANCELLED; } if (BKE_object_obdata_is_libdata(ob)) { BKE_report(op->reports, RPT_ERROR, "Cannot apply pose to lib-linked armature"); return OPERATOR_CANCELLED; } /* helpful warnings... */ /* TODO: add warnings to be careful about actions, applying deforms first, etc. */ if (ob->adt && ob->adt->action) { BKE_report(op->reports, RPT_WARNING, "Actions on this armature will be destroyed by this new rest pose as the " "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; if (use_selected) { /* The selected only mode requires a recursive walk to handle parent-child relations. */ LISTBASE_FOREACH (Bone *, bone, &arm->bonebase) { applyarmature_process_selected_recursive( arm, pose, ob_eval->pose, bone, &selected_bones, NULL); } 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); applyarmature_set_edit_position(curbone, pchan_eval->pose_mat, pchan_eval->pose_tail, NULL); applyarmature_transfer_properties(curbone, pchan, pchan_eval); } } /* convert editbones back to bones, and then free the edit-data */ ED_armature_from_edit(bmain, arm); ED_armature_edit_free(arm); /* flush positions of posebones */ BKE_pose_where_is(depsgraph, scene, ob); /* fix parenting of objects which are bone-parented */ applyarmature_fix_boneparents(C, scene, ob); /* For the affected bones, reset specific constraints that are now known to be invalid. */ applyarmature_reset_constraints(pose, use_selected); /* NOTE: notifier might evolve. */ WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob); DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); 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 */ ot->name = "Apply Pose as Rest Pose"; ot->idname = "POSE_OT_armature_apply"; ot->description = "Apply the current pose as the new rest pose"; /* 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; RNA_def_boolean(ot->srna, "selected", false, "Selected Only", "Only apply the selected bones (with propagation to children)"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Apply Visual Transform Operator * \{ */ /* set the current pose as the restpose */ static int pose_visual_transform_apply_exec(bContext *C, wmOperator *UNUSED(op)) { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); /* Needed to ensure #bPoseChannel.pose_mat are up to date. */ CTX_data_ensure_evaluated_depsgraph(C); FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { const bArmature *arm = ob->data; int chanbase_len = BLI_listbase_count(&ob->pose->chanbase); /* Storage for the calculated matrices to prevent reading from modified values. * NOTE: this could be avoided if children were always calculated before parents * however ensuring this is involved and doesn't give any significant advantage. */ struct { float matrix[4][4]; bool is_set; } *pchan_xform_array = MEM_mallocN(sizeof(*pchan_xform_array) * chanbase_len, __func__); bool changed = false; int i; LISTBASE_FOREACH_INDEX (bPoseChannel *, pchan, &ob->pose->chanbase, i) { if (!((pchan->bone->flag & BONE_SELECTED) && PBONE_VISIBLE(arm, pchan->bone))) { pchan_xform_array[i].is_set = false; continue; } /* `chan_mat` already contains the delta transform from rest pose to pose-mode pose * as that is baked into there so that B-Bones will work. Once we've set this as the * new raw-transform components, don't recalculate the poses yet, otherwise IK result will * change, thus changing the result we may be trying to record. */ /* NOTE: For some reason `pchan->chan_mat` can't be used here as it gives odd * rotation/offset, see T38251. * Using `pchan->pose_mat` and bringing it back in bone space seems to work as expected! * This matches how visual key-framing works. */ BKE_armature_mat_pose_to_bone(pchan, pchan->pose_mat, pchan_xform_array[i].matrix); pchan_xform_array[i].is_set = true; changed = true; } if (changed) { /* Perform separately to prevent feedback loop. */ LISTBASE_FOREACH_INDEX (bPoseChannel *, pchan, &ob->pose->chanbase, i) { if (!pchan_xform_array[i].is_set) { continue; } BKE_pchan_apply_mat4(pchan, pchan_xform_array[i].matrix, true); } DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); /* NOTE: notifier might evolve. */ WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob); } MEM_freeN(pchan_xform_array); } FOREACH_OBJECT_IN_MODE_END; return OPERATOR_FINISHED; } void POSE_OT_visual_transform_apply(wmOperatorType *ot) { /* identifiers */ ot->name = "Apply Visual Transform to Pose"; ot->idname = "POSE_OT_visual_transform_apply"; ot->description = "Apply final constrained position of pose bones to their transform"; /* callbacks */ ot->exec = pose_visual_transform_apply_exec; ot->poll = ED_operator_posemode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Copy/Paste Utilities * \{ */ /* This function is used to indicate that a bone is selected * and needs to be included in copy buffer (used to be for inserting keys) */ static void set_pose_keys(Object *ob) { bArmature *arm = ob->data; bPoseChannel *chan; if (ob->pose) { for (chan = ob->pose->chanbase.first; chan; chan = chan->next) { Bone *bone = chan->bone; if ((bone) && (bone->flag & BONE_SELECTED) && (arm->layer & bone->layer)) { chan->flag |= POSE_KEY; } else { chan->flag &= ~POSE_KEY; } } } } /** * Perform paste pose, for a single bone. * * \param ob: Object where bone to paste to lives * \param chan: Bone that pose to paste comes from * \param selOnly: Only paste on selected bones * \param flip: Flip on x-axis * \return The channel of the bone that was pasted to, or NULL if no paste was performed. */ static bPoseChannel *pose_bone_do_paste(Object *ob, bPoseChannel *chan, const bool selOnly, const bool flip) { char name[MAXBONENAME]; /* get the name - if flipping, we must flip this first */ if (flip) { BLI_string_flip_side_name(name, chan->name, false, sizeof(name)); } else { BLI_strncpy(name, chan->name, sizeof(name)); } /* only copy when: * 1) channel exists - poses are not meant to add random channels to anymore * 2) if selection-masking is on, channel is selected - * only selected bones get pasted on, allowing making both sides symmetrical. */ bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, name); if (pchan == NULL) { return NULL; } if (selOnly && (pchan->bone->flag & BONE_SELECTED) == 0) { return NULL; } /* only loc rot size * - only copies transform info for the pose */ copy_v3_v3(pchan->loc, chan->loc); copy_v3_v3(pchan->size, chan->size); pchan->flag = chan->flag; /* check if rotation modes are compatible (i.e. do they need any conversions) */ if (pchan->rotmode == chan->rotmode) { /* copy the type of rotation in use */ if (pchan->rotmode > 0) { copy_v3_v3(pchan->eul, chan->eul); } else if (pchan->rotmode == ROT_MODE_AXISANGLE) { copy_v3_v3(pchan->rotAxis, chan->rotAxis); pchan->rotAngle = chan->rotAngle; } else { copy_qt_qt(pchan->quat, chan->quat); } } else if (pchan->rotmode > 0) { /* quat/axis-angle to euler */ if (chan->rotmode == ROT_MODE_AXISANGLE) { axis_angle_to_eulO(pchan->eul, pchan->rotmode, chan->rotAxis, chan->rotAngle); } else { quat_to_eulO(pchan->eul, pchan->rotmode, chan->quat); } } else if (pchan->rotmode == ROT_MODE_AXISANGLE) { /* quat/euler to axis angle */ if (chan->rotmode > 0) { eulO_to_axis_angle(pchan->rotAxis, &pchan->rotAngle, chan->eul, chan->rotmode); } else { quat_to_axis_angle(pchan->rotAxis, &pchan->rotAngle, chan->quat); } } else { /* euler/axis-angle to quat */ if (chan->rotmode > 0) { eulO_to_quat(pchan->quat, chan->eul, chan->rotmode); } else { axis_angle_to_quat(pchan->quat, chan->rotAxis, pchan->rotAngle); } } /* B-Bone posing options should also be included... */ pchan->curve_in_x = chan->curve_in_x; pchan->curve_in_z = chan->curve_in_z; pchan->curve_out_x = chan->curve_out_x; pchan->curve_out_z = chan->curve_out_z; pchan->roll1 = chan->roll1; pchan->roll2 = chan->roll2; pchan->ease1 = chan->ease1; pchan->ease2 = chan->ease2; copy_v3_v3(pchan->scale_in, chan->scale_in); copy_v3_v3(pchan->scale_out, chan->scale_out); /* paste flipped pose? */ if (flip) { pchan->loc[0] *= -1; pchan->curve_in_x *= -1; pchan->curve_out_x *= -1; pchan->roll1 *= -1; /* XXX? */ pchan->roll2 *= -1; /* XXX? */ /* has to be done as eulers... */ if (pchan->rotmode > 0) { pchan->eul[1] *= -1; pchan->eul[2] *= -1; } else if (pchan->rotmode == ROT_MODE_AXISANGLE) { float eul[3]; axis_angle_to_eulO(eul, EULER_ORDER_DEFAULT, pchan->rotAxis, pchan->rotAngle); eul[1] *= -1; eul[2] *= -1; eulO_to_axis_angle(pchan->rotAxis, &pchan->rotAngle, eul, EULER_ORDER_DEFAULT); } else { float eul[3]; normalize_qt(pchan->quat); quat_to_eul(eul, pchan->quat); eul[1] *= -1; eul[2] *= -1; eul_to_quat(pchan->quat, eul); } } /* ID properties */ if (chan->prop) { if (pchan->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 blinding replacing all [ */ IDP_SyncGroupValues(pchan->prop, chan->prop); } else { /* no existing properties, so assume that we want copies too? */ pchan->prop = IDP_CopyProperty(chan->prop); } } return pchan; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Copy Pose Operator * \{ */ static int pose_copy_exec(bContext *C, wmOperator *op) { Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C)); char str[FILE_MAX]; /* Sanity checking. */ if (ELEM(NULL, ob, ob->pose)) { BKE_report(op->reports, RPT_ERROR, "No pose to copy"); return OPERATOR_CANCELLED; } /* Sets chan->flag to POSE_KEY if bone selected. */ set_pose_keys(ob); /* Construct a local bmain and only put object and its data into it, * o this way we don't expand any other objects into the copy buffer * file. * * TODO(sergey): Find an easier way to tell copy buffer to only store * data we are actually interested in. Maybe pass it a flag to skip * any datablock expansion? */ Main *temp_bmain = BKE_main_new(); STRNCPY(temp_bmain->filepath, BKE_main_blendfile_path_from_global()); Object ob_copy = *ob; ob_copy.adt = NULL; bArmature arm_copy = *((bArmature *)ob->data); arm_copy.adt = NULL; ob_copy.data = &arm_copy; BLI_addtail(&temp_bmain->objects, &ob_copy); BLI_addtail(&temp_bmain->armatures, &arm_copy); /* begin copy buffer on a temp bmain. */ BKE_copybuffer_copy_begin(temp_bmain); /* Store the whole object to the copy buffer because pose can't be * existing on its own. */ BKE_copybuffer_copy_tag_ID(&ob_copy.id); BLI_path_join(str, sizeof(str), BKE_tempdir_base(), "copybuffer_pose.blend"); BKE_copybuffer_copy_end(temp_bmain, str, op->reports); /* We clear the lists so no datablocks gets freed, * This is required because objects in temp bmain shares same pointers * as the real ones. */ BLI_listbase_clear(&temp_bmain->objects); BLI_listbase_clear(&temp_bmain->armatures); BKE_main_free(temp_bmain); /* We are all done! */ BKE_report(op->reports, RPT_INFO, "Copied pose to buffer"); return OPERATOR_FINISHED; } void POSE_OT_copy(wmOperatorType *ot) { /* identifiers */ ot->name = "Copy Pose"; ot->idname = "POSE_OT_copy"; ot->description = "Copies the current pose of the selected bones to copy/paste buffer"; /* api callbacks */ ot->exec = pose_copy_exec; ot->poll = ED_operator_posemode; /* flag */ ot->flag = OPTYPE_REGISTER; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Paste Pose Operator * \{ */ static int pose_paste_exec(bContext *C, wmOperator *op) { Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C)); Scene *scene = CTX_data_scene(C); bPoseChannel *chan; const bool flip = RNA_boolean_get(op->ptr, "flipped"); bool selOnly = RNA_boolean_get(op->ptr, "selected_mask"); /* Get KeyingSet to use. */ KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_WHOLE_CHARACTER_ID); /* Sanity checks. */ if (ELEM(NULL, ob, ob->pose)) { return OPERATOR_CANCELLED; } /* Read copy buffer .blend file. */ char str[FILE_MAX]; Main *tmp_bmain = BKE_main_new(); STRNCPY(tmp_bmain->filepath, BKE_main_blendfile_path_from_global()); BLI_path_join(str, sizeof(str), BKE_tempdir_base(), "copybuffer_pose.blend"); if (!BKE_copybuffer_read(tmp_bmain, str, op->reports, FILTER_ID_OB)) { BKE_report(op->reports, RPT_ERROR, "Copy buffer is empty"); BKE_main_free(tmp_bmain); return OPERATOR_CANCELLED; } /* Make sure data from this file is usable for pose paste. */ if (BLI_listbase_count_at_most(&tmp_bmain->objects, 2) != 1) { BKE_report(op->reports, RPT_ERROR, "Copy buffer is not from pose mode"); BKE_main_free(tmp_bmain); return OPERATOR_CANCELLED; } Object *object_from = tmp_bmain->objects.first; bPose *pose_from = object_from->pose; if (pose_from == NULL) { BKE_report(op->reports, RPT_ERROR, "Copy buffer has no pose"); BKE_main_free(tmp_bmain); return OPERATOR_CANCELLED; } /* If selOnly option is enabled, if user hasn't selected any bones, * just go back to default behavior to be more in line with other * pose tools. */ if (selOnly) { if (CTX_DATA_COUNT(C, selected_pose_bones) == 0) { selOnly = false; } } /* Safely merge all of the channels in the buffer pose into any * existing pose. */ for (chan = pose_from->chanbase.first; chan; chan = chan->next) { if (chan->flag & POSE_KEY) { /* Try to perform paste on this bone. */ bPoseChannel *pchan = pose_bone_do_paste(ob, chan, selOnly, flip); if (pchan != NULL) { /* Keyframing tagging for successful paste, */ ED_autokeyframe_pchan(C, scene, ob, pchan, ks); } } } BKE_main_free(tmp_bmain); /* Update event for pose and deformation children. */ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); /* Recalculate paths if any of the bones have paths... */ if (ob->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) { ED_pose_recalculate_paths(C, scene, ob, POSE_PATH_CALC_RANGE_FULL); } /* Notifiers for updates, */ WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob); return OPERATOR_FINISHED; } void POSE_OT_paste(wmOperatorType *ot) { PropertyRNA *prop; /* identifiers */ ot->name = "Paste Pose"; ot->idname = "POSE_OT_paste"; ot->description = "Paste the stored pose on to the current pose"; /* api callbacks */ ot->exec = pose_paste_exec; ot->poll = ED_operator_posemode; /* flag */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ prop = RNA_def_boolean(ot->srna, "flipped", false, "Flipped on X-Axis", "Paste the stored pose flipped on to current pose"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); RNA_def_boolean(ot->srna, "selected_mask", false, "On Selected Only", "Only paste the stored pose on to selected bones in the current pose"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Clear Pose Transforms Utilities * \{ */ /* clear scale of pose-channel */ static void pchan_clear_scale(bPoseChannel *pchan) { if ((pchan->protectflag & OB_LOCK_SCALEX) == 0) { pchan->size[0] = 1.0f; } if ((pchan->protectflag & OB_LOCK_SCALEY) == 0) { pchan->size[1] = 1.0f; } if ((pchan->protectflag & OB_LOCK_SCALEZ) == 0) { pchan->size[2] = 1.0f; } pchan->ease1 = 0.0f; pchan->ease2 = 0.0f; copy_v3_fl(pchan->scale_in, 1.0f); copy_v3_fl(pchan->scale_out, 1.0f); } /* Clear the scale. When X-mirror is enabled, * also clear the scale of the mirrored pose channel. */ static void pchan_clear_scale_with_mirrored(const bPose *pose, bPoseChannel *pchan) { if (pose->flag & POSE_MIRROR_EDIT) { bPoseChannel *pchan_mirror = BKE_pose_channel_get_mirrored(pose, pchan->name); if (pchan_mirror != NULL) { pchan_clear_scale(pchan_mirror); } } pchan_clear_scale(pchan); } /* clear location of pose-channel */ static void pchan_clear_loc(bPoseChannel *pchan) { if ((pchan->protectflag & OB_LOCK_LOCX) == 0) { pchan->loc[0] = 0.0f; } if ((pchan->protectflag & OB_LOCK_LOCY) == 0) { pchan->loc[1] = 0.0f; } if ((pchan->protectflag & OB_LOCK_LOCZ) == 0) { pchan->loc[2] = 0.0f; } } /* Clear the Location. When X-mirror is enabled, * also clear the location of the mirrored pose channel. */ static void pchan_clear_loc_with_mirrored(const bPose *pose, bPoseChannel *pchan) { if (pose->flag & POSE_MIRROR_EDIT) { bPoseChannel *pchan_mirror = BKE_pose_channel_get_mirrored(pose, pchan->name); if (pchan_mirror != NULL) { pchan_clear_loc(pchan_mirror); } } pchan_clear_loc(pchan); } /* clear rotation of pose-channel */ static void pchan_clear_rot(bPoseChannel *pchan) { if (pchan->protectflag & (OB_LOCK_ROTX | OB_LOCK_ROTY | OB_LOCK_ROTZ | OB_LOCK_ROTW)) { /* check if convert to eulers for locking... */ if (pchan->protectflag & OB_LOCK_ROT4D) { /* perform clamping on a component by component basis */ if (pchan->rotmode == ROT_MODE_AXISANGLE) { if ((pchan->protectflag & OB_LOCK_ROTW) == 0) { pchan->rotAngle = 0.0f; } if ((pchan->protectflag & OB_LOCK_ROTX) == 0) { pchan->rotAxis[0] = 0.0f; } if ((pchan->protectflag & OB_LOCK_ROTY) == 0) { pchan->rotAxis[1] = 0.0f; } if ((pchan->protectflag & OB_LOCK_ROTZ) == 0) { pchan->rotAxis[2] = 0.0f; } /* check validity of axis - axis should never be 0,0,0 * (if so, then we make it rotate about y). */ if (IS_EQF(pchan->rotAxis[0], pchan->rotAxis[1]) && IS_EQF(pchan->rotAxis[1], pchan->rotAxis[2])) { pchan->rotAxis[1] = 1.0f; } } else if (pchan->rotmode == ROT_MODE_QUAT) { if ((pchan->protectflag & OB_LOCK_ROTW) == 0) { pchan->quat[0] = 1.0f; } if ((pchan->protectflag & OB_LOCK_ROTX) == 0) { pchan->quat[1] = 0.0f; } if ((pchan->protectflag & OB_LOCK_ROTY) == 0) { pchan->quat[2] = 0.0f; } if ((pchan->protectflag & OB_LOCK_ROTZ) == 0) { pchan->quat[3] = 0.0f; } } else { /* the flag may have been set for the other modes, so just ignore the extra flag... */ if ((pchan->protectflag & OB_LOCK_ROTX) == 0) { pchan->eul[0] = 0.0f; } if ((pchan->protectflag & OB_LOCK_ROTY) == 0) { pchan->eul[1] = 0.0f; } if ((pchan->protectflag & OB_LOCK_ROTZ) == 0) { pchan->eul[2] = 0.0f; } } } else { /* perform clamping using euler form (3-components) */ float eul[3], oldeul[3], quat1[4] = {0}; float qlen = 0.0f; if (pchan->rotmode == ROT_MODE_QUAT) { qlen = normalize_qt_qt(quat1, pchan->quat); quat_to_eul(oldeul, quat1); } else if (pchan->rotmode == ROT_MODE_AXISANGLE) { axis_angle_to_eulO(oldeul, EULER_ORDER_DEFAULT, pchan->rotAxis, pchan->rotAngle); } else { copy_v3_v3(oldeul, pchan->eul); } eul[0] = eul[1] = eul[2] = 0.0f; if (pchan->protectflag & OB_LOCK_ROTX) { eul[0] = oldeul[0]; } if (pchan->protectflag & OB_LOCK_ROTY) { eul[1] = oldeul[1]; } if (pchan->protectflag & OB_LOCK_ROTZ) { eul[2] = oldeul[2]; } if (pchan->rotmode == ROT_MODE_QUAT) { eul_to_quat(pchan->quat, eul); /* restore original quat size */ mul_qt_fl(pchan->quat, qlen); /* quaternions flip w sign to accumulate rotations correctly */ if ((quat1[0] < 0.0f && pchan->quat[0] > 0.0f) || (quat1[0] > 0.0f && pchan->quat[0] < 0.0f)) { mul_qt_fl(pchan->quat, -1.0f); } } else if (pchan->rotmode == ROT_MODE_AXISANGLE) { eulO_to_axis_angle(pchan->rotAxis, &pchan->rotAngle, eul, EULER_ORDER_DEFAULT); } else { copy_v3_v3(pchan->eul, eul); } } } /* Duplicated in source/blender/editors/object/object_transform.cc */ else { if (pchan->rotmode == ROT_MODE_QUAT) { unit_qt(pchan->quat); } else if (pchan->rotmode == ROT_MODE_AXISANGLE) { /* by default, make rotation of 0 radians around y-axis (roll) */ unit_axis_angle(pchan->rotAxis, &pchan->rotAngle); } else { zero_v3(pchan->eul); } } /* Clear also Bendy Bone stuff - Roll is obvious, * but Curve X/Y stuff is also kind of rotational in nature... */ pchan->roll1 = 0.0f; pchan->roll2 = 0.0f; pchan->curve_in_x = 0.0f; pchan->curve_in_z = 0.0f; pchan->curve_out_x = 0.0f; pchan->curve_out_z = 0.0f; } /* Clear the rotation. When X-mirror is enabled, * also clear the rotation of the mirrored pose channel. */ static void pchan_clear_rot_with_mirrored(const bPose *pose, bPoseChannel *pchan) { if (pose->flag & POSE_MIRROR_EDIT) { bPoseChannel *pchan_mirror = BKE_pose_channel_get_mirrored(pose, pchan->name); if (pchan_mirror != NULL) { pchan_clear_rot(pchan_mirror); } } pchan_clear_rot(pchan); } /* clear loc/rot/scale of pose-channel */ static void pchan_clear_transforms(const bPose *pose, bPoseChannel *pchan) { pchan_clear_loc_with_mirrored(pose, pchan); pchan_clear_rot_with_mirrored(pose, pchan); pchan_clear_scale_with_mirrored(pose, pchan); } /* --------------- */ /* generic exec for clear-pose operators */ static int pose_clear_transform_generic_exec(bContext *C, wmOperator *op, void (*clear_func)(const bPose *, bPoseChannel *), const char default_ksName[]) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); bool changed_multi = false; /* sanity checks */ if (ELEM(NULL, clear_func, default_ksName)) { BKE_report(op->reports, RPT_ERROR, "Programming error: missing clear transform function or keying set name"); return OPERATOR_CANCELLED; } /* only clear relevant transforms for selected bones */ ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { /* XXX: UGLY HACK (for auto-key + clear transforms). */ Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter); ListBase dsources = {NULL, NULL}; bool changed = false; FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (ob_iter, pchan) { /* run provided clearing function */ clear_func(ob_iter->pose, pchan); changed = true; /* do auto-keyframing as appropriate */ if (autokeyframe_cfra_can_key(scene, &ob_iter->id)) { /* tag for autokeying later */ ANIM_relative_keyingset_add_source(&dsources, &ob_iter->id, &RNA_PoseBone, pchan); #if 1 /* XXX: Ugly Hack - Run clearing function on evaluated copy of pchan */ bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); clear_func(ob_iter->pose, pchan_eval); #endif } } FOREACH_PCHAN_SELECTED_IN_OBJECT_END; if (changed) { changed_multi = true; /* perform autokeying on the bones if needed */ if (!BLI_listbase_is_empty(&dsources)) { /* get KeyingSet to use */ KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, default_ksName); /* insert keyframes */ ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)scene->r.cfra); /* now recalculate paths */ if (ob_iter->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) { ED_pose_recalculate_paths(C, scene, ob_iter, POSE_PATH_CALC_RANGE_FULL); } BLI_freelistN(&dsources); } DEG_id_tag_update(&ob_iter->id, ID_RECALC_GEOMETRY); /* NOTE: notifier might evolve. */ WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, ob_iter); } } FOREACH_OBJECT_IN_MODE_END; return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Clear Pose Scale Operator * \{ */ static int pose_clear_scale_exec(bContext *C, wmOperator *op) { return pose_clear_transform_generic_exec( C, op, pchan_clear_scale_with_mirrored, ANIM_KS_SCALING_ID); } void POSE_OT_scale_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "Clear Pose Scale"; ot->idname = "POSE_OT_scale_clear"; ot->description = "Reset scaling of selected bones to their default values"; /* api callbacks */ ot->exec = pose_clear_scale_exec; ot->poll = ED_operator_posemode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Clear Pose Rotation Operator * \{ */ static int pose_clear_rot_exec(bContext *C, wmOperator *op) { return pose_clear_transform_generic_exec( C, op, pchan_clear_rot_with_mirrored, ANIM_KS_ROTATION_ID); } void POSE_OT_rot_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "Clear Pose Rotation"; ot->idname = "POSE_OT_rot_clear"; ot->description = "Reset rotations of selected bones to their default values"; /* api callbacks */ ot->exec = pose_clear_rot_exec; ot->poll = ED_operator_posemode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Clear Pose Location Operator * \{ */ static int pose_clear_loc_exec(bContext *C, wmOperator *op) { return pose_clear_transform_generic_exec( C, op, pchan_clear_loc_with_mirrored, ANIM_KS_LOCATION_ID); } void POSE_OT_loc_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "Clear Pose Location"; ot->idname = "POSE_OT_loc_clear"; ot->description = "Reset locations of selected bones to their default values"; /* api callbacks */ ot->exec = pose_clear_loc_exec; ot->poll = ED_operator_posemode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Clear Pose Transforms Operator * \{ */ static int pose_clear_transforms_exec(bContext *C, wmOperator *op) { return pose_clear_transform_generic_exec( C, op, pchan_clear_transforms, ANIM_KS_LOC_ROT_SCALE_ID); } void POSE_OT_transforms_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "Clear Pose Transforms"; ot->idname = "POSE_OT_transforms_clear"; ot->description = "Reset location, rotation, and scaling of selected bones to their default values"; /* api callbacks */ ot->exec = pose_clear_transforms_exec; ot->poll = ED_operator_posemode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Clear User Transforms Operator * \{ */ static int pose_clear_user_transforms_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( depsgraph, (float)scene->r.cfra); const bool only_select = RNA_boolean_get(op->ptr, "only_selected"); FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) { if ((ob->adt) && (ob->adt->action)) { /* XXX: this is just like this to avoid contaminating anything else; * just pose values should change, so this should be fine */ bPose *dummyPose = NULL; Object workob = {{NULL}}; bPoseChannel *pchan; /* execute animation step for current frame using a dummy copy of the pose */ BKE_pose_copy_data(&dummyPose, ob->pose, 0); BLI_strncpy(workob.id.name, "OB", sizeof(workob.id.name)); workob.type = OB_ARMATURE; workob.data = ob->data; workob.adt = ob->adt; workob.pose = dummyPose; BKE_animsys_evaluate_animdata( &workob.id, workob.adt, &anim_eval_context, ADT_RECALC_ANIM, false); /* Copy back values, but on selected bones only. */ for (pchan = dummyPose->chanbase.first; pchan; pchan = pchan->next) { pose_bone_do_paste(ob, pchan, only_select, 0); } /* free temp data - free manually as was copied without constraints */ for (pchan = dummyPose->chanbase.first; pchan; pchan = pchan->next) { if (pchan->prop) { IDP_FreeProperty(pchan->prop); } } /* was copied without constraints */ BLI_freelistN(&dummyPose->chanbase); MEM_freeN(dummyPose); } else { /* No animation, so just reset to the rest pose. */ BKE_pose_rest(ob->pose, only_select); } /* notifiers and updates */ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, ob); } FOREACH_OBJECT_IN_MODE_END; return OPERATOR_FINISHED; } void POSE_OT_user_transforms_clear(wmOperatorType *ot) { /* identifiers */ ot->name = "Clear User Transforms"; ot->idname = "POSE_OT_user_transforms_clear"; ot->description = "Reset pose bone transforms to keyframed state"; /* callbacks */ ot->exec = pose_clear_user_transforms_exec; ot->poll = ED_operator_posemode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ RNA_def_boolean(ot->srna, "only_selected", true, "Only Selected", "Only visible/selected bones"); } /** \} */