Anim: Place Pose Bone Gizmo at Custom Transform

This PR adds an option "Affect Gizmo" for custom shape transforms to affect
the transform gizmos. If enabled, this will place the gizmo at the
location and orientation of the "Override Transform" (i.e. in its local space).

For Orientations: The gizmo mode *is* respected. I.e. global mode is aligned to the world etc.
There is a special case for "Gimbal" where it will not follow the orientation of the
"Override Transform" bone. I think it makes sense to keep it that way since this
is about the channels of the bone you are actually manipulating.

The other option is "Use as Pivot" with which the bone is actually rotated
around the override bone. This can be useful for rigs in which shapekeys and
armature deformation is combined

Taken over from #136468
For design task #135429

Co-authored-by: Wayde Moss <wbmoss_dev@yahoo.com>
Pull Request: https://projects.blender.org/blender/blender/pulls/142847
This commit is contained in:
Christoph Lendenfeld
2025-09-26 13:25:01 +02:00
committed by Christoph Lendenfeld
parent 8e992a15e8
commit b2653be057
11 changed files with 133 additions and 15 deletions

View File

@@ -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()

View File

@@ -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 --------------------- */
/**

View File

@@ -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) {

View File

@@ -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<bArmature *>(ob->data);
BKE_pose_channel_transform_location(arm, pchan, r_center);
return true;
}
return false;

View File

@@ -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 {

View File

@@ -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<bArmature *>(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++);

View File

@@ -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<bArmature *>(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) {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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),

View File

@@ -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);