diff --git a/scripts/startup/bl_ui/properties_data_bone.py b/scripts/startup/bl_ui/properties_data_bone.py index 2d5a6223bed..f969a1a4180 100644 --- a/scripts/startup/bl_ui/properties_data_bone.py +++ b/scripts/startup/bl_ui/properties_data_bone.py @@ -418,6 +418,12 @@ class BONE_PT_display_custom_shape(BoneButtonsPanel, Panel): sub.prop(pchan, "custom_shape_scale_xyz", text="Scale") sub.prop_search(pchan, "custom_shape_transform", ob.pose, "bones", text="Override Transform") + subsub = sub.column() + subsub.active = bool(pchan and pchan.custom_shape and pchan.custom_shape_transform) + subsub.prop(pchan, "use_transform_at_custom_shape") + subsubsub = subsub.column() + subsubsub.active = subsub.active and pchan.use_transform_at_custom_shape + subsubsub.prop(pchan, "use_transform_around_custom_shape") sub.prop(pchan, "use_custom_shape_bone_size") sub.separator() diff --git a/source/blender/blenkernel/BKE_action.hh b/source/blender/blenkernel/BKE_action.hh index 98e53dcb49e..7cf9188b0f1 100644 --- a/source/blender/blenkernel/BKE_action.hh +++ b/source/blender/blenkernel/BKE_action.hh @@ -16,6 +16,7 @@ struct BlendDataReader; struct BlendLibReader; struct BlendWriter; struct bArmature; +struct BoneParentTransform; /* The following structures are defined in DNA_action_types.h, and DNA_anim_types.h */ struct AnimationEvalContext; @@ -308,6 +309,21 @@ void BKE_pose_itasc_init(bItasc *itasc); */ bool BKE_pose_channel_in_IK_chain(Object *ob, bPoseChannel *pchan); +/** + * Get the transform location, accounting for POSE_TRANSFORM_AT_CUSTOM_TX. + */ +void BKE_pose_channel_transform_location(const bArmature *arm, + const bPoseChannel *pose_bone, + float r_pose_space_pivot[3]); + +/** + * Get the transform pose orientation, accounting for + * POSE_TRANSFORM_AT_CUSTOM_TX. + */ +void BKE_pose_channel_transform_orientation(const bArmature *arm, + const bPoseChannel *pose_bone, + float r_pose_orientation[3][3]); + /* Bone Groups API --------------------- */ /** diff --git a/source/blender/blenkernel/intern/action.cc b/source/blender/blenkernel/intern/action.cc index 4fe27af5a1b..08f33456219 100644 --- a/source/blender/blenkernel/intern/action.cc +++ b/source/blender/blenkernel/intern/action.cc @@ -1398,6 +1398,46 @@ bool BKE_pose_channel_in_IK_chain(Object *ob, bPoseChannel *pchan) return pose_channel_in_IK_chain(ob, pchan, 0); } +static bool transform_follows_custom_tx(const bArmature *arm, const bPoseChannel *pchan) +{ + if (arm->flag & ARM_NO_CUSTOM) { + return false; + } + + if (!pchan->custom || !pchan->custom_tx) { + return false; + } + + return pchan->flag & POSE_TRANSFORM_AT_CUSTOM_TX; +} + +void BKE_pose_channel_transform_orientation(const bArmature *arm, + const bPoseChannel *pose_bone, + float r_pose_orientation[3][3]) +{ + if (!transform_follows_custom_tx(arm, pose_bone)) { + copy_m3_m4(r_pose_orientation, pose_bone->pose_mat); + return; + } + + BLI_assert(pose_bone->custom_tx); + + const bPoseChannel *custom_tx_bone = pose_bone->custom_tx; + copy_m3_m4(r_pose_orientation, custom_tx_bone->pose_mat); +} + +void BKE_pose_channel_transform_location(const bArmature *arm, + const bPoseChannel *pose_bone, + float r_pose_space_pivot[3]) +{ + if (!transform_follows_custom_tx(arm, pose_bone)) { + copy_v3_v3(r_pose_space_pivot, pose_bone->pose_mat[3]); + return; + } + + copy_v3_v3(r_pose_space_pivot, pose_bone->custom_tx->pose_mat[3]); +} + void BKE_pose_channels_hash_ensure(bPose *pose) { if (!pose->chanhash) { diff --git a/source/blender/editors/object/object_utils.cc b/source/blender/editors/object/object_utils.cc index b16c443cc2e..31ccf00f6f2 100644 --- a/source/blender/editors/object/object_utils.cc +++ b/source/blender/editors/object/object_utils.cc @@ -126,7 +126,8 @@ bool calc_active_center_for_posemode(Object *ob, const bool select_only, float r { bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob); if (pchan && (!select_only || (pchan->bone->flag & BONE_SELECTED))) { - copy_v3_v3(r_center, pchan->pose_head); + const bArmature *arm = static_cast(ob->data); + BKE_pose_channel_transform_location(arm, pchan, r_center); return true; } return false; diff --git a/source/blender/editors/transform/transform.hh b/source/blender/editors/transform/transform.hh index 9c7011d5149..38a35c4e2b2 100644 --- a/source/blender/editors/transform/transform.hh +++ b/source/blender/editors/transform/transform.hh @@ -471,6 +471,12 @@ struct TransDataExtension { int rotOrder; /** Original object transformation used for rigid bodies. */ float oloc[3], orot[3], oquat[4], orotAxis[3], orotAngle; + + /** + * Use when #TransDataBasic::center has been overridden but the real center is still needed + * for internal calculations. + */ + float center_no_override[3]; }; struct TransData2D { diff --git a/source/blender/editors/transform/transform_convert_armature.cc b/source/blender/editors/transform/transform_convert_armature.cc index c4d5ee9eec4..15f6d36b45c 100644 --- a/source/blender/editors/transform/transform_convert_armature.cc +++ b/source/blender/editors/transform/transform_convert_armature.cc @@ -395,10 +395,15 @@ static void add_pose_transdata( Bone *bone = pchan->bone; float pmat[3][3], omat[3][3]; float cmat[3][3], tmat[3][3]; - float vec[3]; - copy_v3_v3(vec, pchan->pose_mat[3]); - copy_v3_v3(td->center, vec); + const bArmature *arm = static_cast(ob->data); + BKE_pose_channel_transform_location(arm, pchan, td->center); + if (pchan->flag & POSE_TRANSFORM_AROUND_CUSTOM_TX) { + copy_v3_v3(td_ext->center_no_override, pchan->pose_mat[3]); + } + else { + copy_v3_v3(td_ext->center_no_override, td->center); + } td->flag = TD_SELECTED; if (bone->flag & BONE_HINGE_CHILD_TRANSFORM) { @@ -449,11 +454,12 @@ static void add_pose_transdata( /* Proper way to get parent transform + our own transform + constraints transform. */ copy_m3_m4(omat, ob->object_to_world().ptr()); - /* New code, using "generic" BKE_bone_parent_transform_calc_from_pchan(). */ { BoneParentTransform bpt; float rpmat[3][3]; + /* Not using the pchan->custom_tx here because we need the transformation to be + * relative to the actual bone being modified, not it's visual representation. */ BKE_bone_parent_transform_calc_from_pchan(pchan, &bpt); if (t->mode == TFM_TRANSLATION) { copy_m3_m4(pmat, bpt.loc_mat); @@ -498,7 +504,7 @@ static void add_pose_transdata( } /* For `axismtx` we use the bone's own transform. */ - copy_m3_m4(pmat, pchan->pose_mat); + BKE_pose_channel_transform_orientation(arm, pchan, pmat); mul_m3_m3m3(td->axismtx, omat, pmat); normalize_m3(td->axismtx); @@ -703,6 +709,9 @@ static void createTransPose(bContext * /*C*/, TransInfo *t) /* Use pose channels to fill trans data. */ td = tc->data; tdx = tc->data_ext; + tdx->center_no_override[0] = 0; + tdx->center_no_override[1] = 0; + tdx->center_no_override[2] = 0; LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) { if (pchan->bone->flag & BONE_TRANSFORM) { add_pose_transdata(t, pchan, ob, td++, tdx++); diff --git a/source/blender/editors/transform/transform_gizmo_3d.cc b/source/blender/editors/transform/transform_gizmo_3d.cc index 1d61149b8ad..531c295b72d 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.cc +++ b/source/blender/editors/transform/transform_gizmo_3d.cc @@ -22,6 +22,7 @@ #include "DNA_meta_types.h" #include "DNA_pointcloud_types.h" +#include "BKE_action.hh" #include "BKE_armature.hh" #include "BKE_context.hh" #include "BKE_crazyspace.hh" @@ -836,12 +837,16 @@ static int gizmo_3d_foreach_selected(const bContext *C, mul_m4_m4m4(mat_local, ob->world_to_object().ptr(), ob_iter->object_to_world().ptr()); } + bArmature *arm = static_cast(ob_iter->data); /* Use channels to get stats. */ LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_iter->pose->chanbase) { if (!(pchan->bone->flag & BONE_TRANSFORM)) { continue; } - run_coord_with_matrix(pchan->pose_head, use_mat_local, mat_local); + + float pchan_pivot[3]; + BKE_pose_channel_transform_location(arm, pchan, pchan_pivot); + run_coord_with_matrix(pchan_pivot, use_mat_local, mat_local); totsel++; if (r_drawflags) { diff --git a/source/blender/editors/transform/transform_mode.cc b/source/blender/editors/transform/transform_mode.cc index 9975990f0dd..b8588283807 100644 --- a/source/blender/editors/transform/transform_mode.cc +++ b/source/blender/editors/transform/transform_mode.cc @@ -651,7 +651,7 @@ void ElementRotation_ex(const TransInfo *t, /* Extract and invert armature object matrix. */ if ((td->flag & TD_NO_LOC) == 0) { - sub_v3_v3v3(vec, td->center, center); + sub_v3_v3v3(vec, td_ext->center_no_override, center); mul_m3_v3(tc->mat3, vec); /* To Global space. */ mul_m3_v3(mat, vec); /* Applying rotation. */ @@ -660,7 +660,8 @@ void ElementRotation_ex(const TransInfo *t, add_v3_v3(vec, center); /* `vec` now is the location where the object has to be. */ - sub_v3_v3v3(vec, vec, td->center); /* Translation needed from the initial location. */ + /* Translation needed from the initial location. */ + sub_v3_v3v3(vec, vec, td_ext->center_no_override); /* Special exception, see TD_PBONE_LOCAL_MTX definition comments. */ if (td->flag & TD_PBONE_LOCAL_MTX_P) { diff --git a/source/blender/editors/transform/transform_orientations.cc b/source/blender/editors/transform/transform_orientations.cc index 4066af2f5a5..be5371218c9 100644 --- a/source/blender/editors/transform/transform_orientations.cc +++ b/source/blender/editors/transform/transform_orientations.cc @@ -665,6 +665,7 @@ short calc_orientation_from_type_ex(const Scene *scene, if (ob) { if (ob->mode & OB_MODE_POSE) { const bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob); + if (pchan && gimbal_axis_pose(ob, pchan, r_mat)) { break; } @@ -1428,8 +1429,11 @@ int getTransformOrientation_ex(const Scene *scene, bool ok = false; if (activeOnly && (pchan = BKE_pose_channel_active_if_bonecoll_visible(ob))) { - add_v3_v3(r_normal, pchan->pose_mat[2]); - add_v3_v3(r_plane, pchan->pose_mat[1]); + float pose_mat[3][3]; + BKE_pose_channel_transform_orientation(arm, pchan, pose_mat); + + add_v3_v3(r_normal, pose_mat[2]); + add_v3_v3(r_plane, pose_mat[1]); ok = true; } else { @@ -1439,8 +1443,11 @@ int getTransformOrientation_ex(const Scene *scene, /* Use channels to get stats. */ LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) { if (pchan->bone && pchan->bone->flag & BONE_TRANSFORM) { - add_v3_v3(r_normal, pchan->pose_mat[2]); - add_v3_v3(r_plane, pchan->pose_mat[1]); + float pose_mat[3][3]; + BKE_pose_channel_transform_orientation(arm, pchan, pose_mat); + + add_v3_v3(r_normal, pose_mat[2]); + add_v3_v3(r_plane, pose_mat[1]); } } ok = true; diff --git a/source/blender/makesdna/DNA_action_types.h b/source/blender/makesdna/DNA_action_types.h index 4e97b7845bb..ec5fd02649c 100644 --- a/source/blender/makesdna/DNA_action_types.h +++ b/source/blender/makesdna/DNA_action_types.h @@ -303,7 +303,7 @@ typedef struct bPoseChannel { ListBase constraints; char name[/*MAXBONENAME*/ 64]; - /** Dynamic, for detecting transform changes. */ + /** Dynamic, for detecting transform changes (ePchan_Flag). */ short flag; /** Settings for IK bones. */ short ikflag; @@ -454,7 +454,14 @@ typedef enum ePchan_Flag { /* has BBone deforms */ POSE_BBONE_SHAPE = (1 << 3), - + /* When set and bPoseChan.custom_tx is not a nullptr, the gizmo will be drawn at the location and + orientation of the custom_tx instead of this bone. */ + POSE_TRANSFORM_AT_CUSTOM_TX = (1 << 4), + /* When set, transformations will modify the bone as if it was a child of the + bPoseChan.custom_tx. The flag only has an effect when `POSE_TRANSFORM_AT_CUSTOM_TX` and + `custom_tx` are set. This can be useful for rigs where the deformation is coming from + blendshapes in addition to the armature. */ + POSE_TRANSFORM_AROUND_CUSTOM_TX = (1 << 5), /* IK/Pose solving */ POSE_CHAIN = (1 << 9), POSE_DONE = (1 << 10), diff --git a/source/blender/makesrna/intern/rna_pose.cc b/source/blender/makesrna/intern/rna_pose.cc index 4af602e375f..31897d8028b 100644 --- a/source/blender/makesrna/intern/rna_pose.cc +++ b/source/blender/makesrna/intern/rna_pose.cc @@ -1170,6 +1170,26 @@ static void rna_def_pose_channel(BlenderRNA *brna) RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 100, RNA_TRANSLATION_PREC_DEFAULT); RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + prop = RNA_def_property(srna, "use_transform_at_custom_shape", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", POSE_TRANSFORM_AT_CUSTOM_TX); + RNA_def_property_ui_text( + prop, + "Affect Gizmo", + "The location and orientation of the Custom Shape Transform bone will be used for transform " + "gizmos and for other transform operators in the 3D Viewport. When disabled, the 3D " + "Viewport will still use the actual bone transform for these, even when the custom bone " + "shape transform is overridden."); + RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + + prop = RNA_def_property(srna, "use_transform_around_custom_shape", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", POSE_TRANSFORM_AROUND_CUSTOM_TX); + RNA_def_property_ui_text( + prop, + "Use As Pivot", + "Transform the bone as if it was a child of the Custom Shape Transform bone. This can be " + "useful when combining shapekey and armature deformations."); + RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + prop = RNA_def_property(srna, "use_custom_shape_bone_size", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna( prop, nullptr, "drawflag", PCHAN_DRAW_NO_CUSTOM_BONE_SIZE);