Anim: Store pose bone visibility flag on pose bone

This PR adds a flag to the pose bone that determines its visibility.
Doing so allows to hide a pose bone and
* don't affect other instances of the same armature
* save the visibility even if the rig is linked

The visibility of the bone in object mode is now also determined by the
pose bone whereas before it was the `Bone`.

**This breaks backwards compatibility** on the Python API since the visibility property,
on the `Bone` behaves differently now as it hides the edit bone instead of the pose bone.

In order to remove all active uses of `BONE_HIDDEN_P` the changes in `armature_skinning.cc` are required.

Part of #138482

Pull Request: https://projects.blender.org/blender/blender/pulls/139167
This commit is contained in:
Christoph Lendenfeld
2025-08-07 15:54:58 +02:00
committed by Christoph Lendenfeld
parent 6f57268e9a
commit a43359eb88
17 changed files with 200 additions and 113 deletions

View File

@@ -323,17 +323,18 @@ class BONE_PT_display(BoneButtonsPanel, Panel):
bone = context.bone
col = layout.column()
col.prop(bone, "hide", text="Hide", toggle=False)
# Figure out the pose bone.
ob = context.object
pose_bone = ob and ob.pose.bones[bone.name]
hide_select_sub = col.column()
hide_select_sub.active = not bone.hide
if pose_bone:
col.prop(pose_bone, "hide", text="Hide", toggle=False)
hide_select_sub.active = not pose_bone.hide
hide_select_sub.prop(bone, "hide_select", invert_checkbox=True)
col.prop(bone, "display_type", text="Display As")
# Figure out the pose bone.
ob = context.object
if not ob:
if not pose_bone:
return
pose_bone = ob.pose.bones[bone.name]
# Allow the layout to use the space normally occupied by the 'set a key' diamond.
layout.use_property_decorate = False

View File

@@ -27,7 +27,8 @@ inline bool bone_is_visible(const bArmature *armature, const Bone *bone)
inline bool bone_is_visible_pchan(const bArmature *armature, const bPoseChannel *pchan)
{
return bone_is_visible(armature, pchan->bone);
const bool bone_itself_visible = (pchan->drawflag & PCHAN_DRAW_HIDDEN) == 0;
return bone_itself_visible && ANIM_bone_in_visible_collection(armature, pchan->bone);
}
inline bool bone_is_visible_editbone(const bArmature *armature, const EditBone *ebone)
@@ -55,4 +56,12 @@ inline bool bone_is_selected(const bArmature *armature, const EditBone *ebone)
return (ebone->flag & BONE_SELECTED) && bone_is_visible_editbone(armature, ebone);
}
/**
* Iterates all descendents of the given pose bone including the bone itself. Iterates breadth
* first.
*/
void pose_bone_descendent_iterator(bPose &pose,
bPoseChannel &pose_bone,
FunctionRef<void(bPoseChannel &child_bone)> callback);
} // namespace blender::animrig

View File

@@ -24,6 +24,7 @@ set(SRC
intern/action_selection.cc
intern/anim_rna.cc
intern/animdata.cc
intern/armature.cc
intern/bone_collections.cc
intern/bonecolor.cc
intern/driver.cc

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*/
#include <deque>
#include "ANIM_armature.hh"
#include "BKE_action.hh"
#include "BLI_listbase.h"
namespace blender::animrig {
void pose_bone_descendent_iterator(bPose &pose,
bPoseChannel &pose_bone,
FunctionRef<void(bPoseChannel &child_bone)> callback)
{
/* Needed for fast name lookups. */
BKE_pose_channels_hash_ensure(&pose);
std::deque<bPoseChannel *> to_visit = {&pose_bone};
while (!to_visit.empty()) {
bPoseChannel *descendant = to_visit.front();
to_visit.pop_front();
callback(*descendant);
LISTBASE_FOREACH (Bone *, child_bone, &descendant->bone->childbase) {
bPoseChannel *child_pose_bone = BKE_pose_channel_find_name(&pose, child_bone->name);
if (!child_pose_bone) {
/* Can happen if the pose is not rebuilt. */
BLI_assert_unreachable();
continue;
}
to_visit.push_back(child_pose_bone);
}
}
};
} // namespace blender::animrig

View File

@@ -576,7 +576,7 @@ void BKE_pchan_bbone_deform_segment_index(const bPoseChannel *pchan,
for (bPoseChannel *_pchan = (bPoseChannel *)(_ob)->pose->chanbase.first; _pchan; \
_pchan = _pchan->next) \
{ \
if (blender::animrig::bone_is_visible(((bArmature *)(_ob)->data), (_pchan)->bone) && \
if (blender::animrig::bone_is_visible_pchan(((bArmature *)(_ob)->data), _pchan) && \
((_pchan)->bone->flag & BONE_SELECTED)) \
{
#define FOREACH_PCHAN_SELECTED_IN_OBJECT_END \
@@ -588,7 +588,7 @@ void BKE_pchan_bbone_deform_segment_index(const bPoseChannel *pchan,
for (bPoseChannel *_pchan = (bPoseChannel *)(_ob)->pose->chanbase.first; _pchan; \
_pchan = _pchan->next) \
{ \
if (blender::animrig::bone_is_visible(((bArmature *)(_ob)->data), (_pchan)->bone)) {
if (blender::animrig::bone_is_visible_pchan(((bArmature *)(_ob)->data), _pchan)) {
#define FOREACH_PCHAN_VISIBLE_IN_OBJECT_END \
} \
} \

View File

@@ -1585,6 +1585,22 @@ void do_versions_after_linking_500(FileData *fd, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 54)) {
LISTBASE_FOREACH (Object *, object, &bmain->objects) {
if (object->type != OB_ARMATURE) {
continue;
}
LISTBASE_FOREACH (bPoseChannel *, pose_bone, &object->pose->chanbase) {
if (pose_bone->bone->flag & BONE_HIDDEN_P) {
pose_bone->drawflag |= PCHAN_DRAW_HIDDEN;
}
else {
pose_bone->drawflag &= ~PCHAN_DRAW_HIDDEN;
}
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -2022,11 +2022,11 @@ void Armatures::draw_armature_pose(Armatures::DrawContext *ctx)
for (bPoseChannel *pchan = static_cast<bPoseChannel *>(ob->pose->chanbase.first); pchan;
pchan = pchan->next, index += 0x10000)
{
Bone *bone = pchan->bone;
if (!blender::animrig::bone_is_visible(&arm, bone)) {
if (!blender::animrig::bone_is_visible_pchan(&arm, pchan)) {
continue;
}
Bone *bone = pchan->bone;
const bool draw_dofs = !is_pose_select && ctx->show_relations &&
(ctx->draw_mode == ARM_DRAW_MODE_POSE) &&
(bone->flag & BONE_SELECTED) &&
@@ -2042,7 +2042,7 @@ void Armatures::draw_armature_pose(Armatures::DrawContext *ctx)
}
eBone_Flag boneflag = eBone_Flag(bone->flag);
if (bone->parent && !blender::animrig::bone_is_visible(&arm, bone->parent)) {
if (pchan->parent && !blender::animrig::bone_is_visible_pchan(&arm, pchan->parent)) {
/* Avoid drawing connection line to hidden parent. */
boneflag &= ~BONE_CONNECTED;
}

View File

@@ -76,11 +76,14 @@ static int bone_skinnable_cb(Object * /*ob*/, Bone *bone, void *datap)
bool is_weight_paint;
} *data = static_cast<Arg *>(datap);
if (!(data->is_weight_paint) || !(bone->flag & BONE_HIDDEN_P)) {
bPoseChannel *pose_bone = BKE_pose_channel_find_name(data->armob->pose, bone->name);
if (!pose_bone) {
return 0;
}
if (!(data->is_weight_paint) || !(pose_bone->drawflag & PCHAN_DRAW_HIDDEN)) {
if (!(bone->flag & BONE_NO_DEFORM)) {
if (data->heat && data->armob->pose &&
BKE_pose_channel_find_name(data->armob->pose, bone->name))
{
if (data->heat && data->armob->pose && pose_bone) {
segments = bone->segments;
}
else {
@@ -149,43 +152,49 @@ static int dgroup_skinnable_cb(Object *ob, Bone *bone, void *datap)
int heat;
bool is_weight_paint;
} *data = static_cast<Arg *>(datap);
if (bone->flag & BONE_NO_DEFORM) {
return 0;
}
bArmature *arm = static_cast<bArmature *>(data->armob->data);
const bPoseChannel *pose_bone = BKE_pose_channel_find_name(data->armob->pose, bone->name);
if (!pose_bone) {
return 0;
}
if (!data->is_weight_paint || !(bone->flag & BONE_HIDDEN_P)) {
if (!(bone->flag & BONE_NO_DEFORM)) {
if (data->heat && data->armob->pose &&
BKE_pose_channel_find_name(data->armob->pose, bone->name))
{
segments = bone->segments;
}
else {
segments = 1;
}
if (data->is_weight_paint && (pose_bone->drawflag & PCHAN_DRAW_HIDDEN)) {
return 0;
}
if (!data->is_weight_paint ||
(ANIM_bone_in_visible_collection(arm, bone) && (bone->flag & BONE_SELECTED)))
{
if (!(defgroup = BKE_object_defgroup_find_name(ob, bone->name))) {
defgroup = BKE_object_defgroup_add_name(ob, bone->name);
}
else if (defgroup->flag & DG_LOCK_WEIGHT) {
/* In case vgroup already exists and is locked, do not modify it here. See #43814. */
defgroup = nullptr;
}
}
if (data->heat) {
segments = bone->segments;
}
else {
segments = 1;
}
if (data->list != nullptr) {
hgroup = (bDeformGroup ***)&data->list;
for (a = 0; a < segments; a++) {
**hgroup = defgroup;
(*hgroup)++;
}
}
return segments;
if (!data->is_weight_paint ||
(ANIM_bone_in_visible_collection(arm, bone) && (bone->flag & BONE_SELECTED)))
{
if (!(defgroup = BKE_object_defgroup_find_name(ob, bone->name))) {
defgroup = BKE_object_defgroup_add_name(ob, bone->name);
}
else if (defgroup->flag & DG_LOCK_WEIGHT) {
/* In case vgroup already exists and is locked, do not modify it here. See #43814. */
defgroup = nullptr;
}
}
return 0;
if (data->list != nullptr) {
hgroup = (bDeformGroup ***)&data->list;
for (a = 0; a < segments; a++) {
**hgroup = defgroup;
(*hgroup)++;
}
}
return segments;
}
static void envelope_bone_weighting(Object *ob,
@@ -305,6 +314,10 @@ static void add_verts_to_dgroups(ReportList *reports,
looper_data.list = nullptr;
looper_data.is_weight_paint = wpmode;
if (!par->pose) {
BKE_pose_rebuild(nullptr, par, arm, false);
}
BKE_pose_channels_hash_ensure(par->pose);
/* count the number of skinnable bones */
numbones = bone_looper(
ob, static_cast<Bone *>(arm->bonebase.first), &looper_data, bone_skinnable_cb);

View File

@@ -663,22 +663,6 @@ void POSE_OT_rotation_mode_set(wmOperatorType *ot)
/* ********************************************** */
/* Show/Hide Bones */
static int hide_pose_bone_fn(Object *ob, Bone *bone, void *ptr)
{
bArmature *arm = static_cast<bArmature *>(ob->data);
const bool hide_select = bool(POINTER_AS_INT(ptr));
int count = 0;
if (ANIM_bone_in_visible_collection(arm, bone)) {
if (((bone->flag & BONE_SELECTED) != 0) == hide_select) {
bone->flag |= BONE_HIDDEN_P;
/* only needed when 'hide_select' is true, but harmless. */
bone->flag &= ~BONE_SELECTED;
count += 1;
}
}
return count;
}
/* active object is armature in posemode, poll checked */
static wmOperatorStatus pose_hide_exec(bContext *C, wmOperator *op)
{
@@ -688,15 +672,22 @@ static wmOperatorStatus pose_hide_exec(bContext *C, wmOperator *op)
bool changed_multi = false;
const int hide_select = !RNA_boolean_get(op->ptr, "unselected");
void *hide_select_p = POINTER_FROM_INT(hide_select);
for (Object *ob_iter : objects) {
bool changed = false;
bArmature *arm = static_cast<bArmature *>(ob_iter->data);
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_iter->pose->chanbase) {
if (!ANIM_bone_in_visible_collection(arm, pchan->bone)) {
continue;
}
if (((pchan->bone->flag & BONE_SELECTED) != 0) != hide_select) {
continue;
}
pchan->drawflag |= PCHAN_DRAW_HIDDEN;
pchan->bone->flag &= ~BONE_SELECTED;
changed = true;
}
bool changed = bone_looper(ob_iter,
static_cast<Bone *>(arm->bonebase.first),
hide_select_p,
hide_pose_bone_fn) != 0;
if (changed) {
changed_multi = true;
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob_iter);
@@ -725,25 +716,6 @@ void POSE_OT_hide(wmOperatorType *ot)
RNA_def_boolean(ot->srna, "unselected", false, "Unselected", "");
}
static int show_pose_bone_cb(Object *ob, Bone *bone, void *data)
{
const bool select = POINTER_AS_INT(data);
bArmature *arm = static_cast<bArmature *>(ob->data);
int count = 0;
if (ANIM_bone_in_visible_collection(arm, bone)) {
if (bone->flag & BONE_HIDDEN_P) {
if (!(bone->flag & BONE_UNSELECTABLE)) {
SET_FLAG_FROM_TEST(bone->flag, select, BONE_SELECTED);
}
bone->flag &= ~BONE_HIDDEN_P;
count += 1;
}
}
return count;
}
/* active object is armature in posemode, poll checked */
static wmOperatorStatus pose_reveal_exec(bContext *C, wmOperator *op)
{
@@ -752,13 +724,25 @@ static wmOperatorStatus pose_reveal_exec(bContext *C, wmOperator *op)
Vector<Object *> objects = BKE_object_pose_array_get_unique(scene, view_layer, CTX_wm_view3d(C));
bool changed_multi = false;
const bool select = RNA_boolean_get(op->ptr, "select");
void *select_p = POINTER_FROM_INT(select);
for (Object *ob_iter : objects) {
bArmature *arm = static_cast<bArmature *>(ob_iter->data);
bool changed = bone_looper(
ob_iter, static_cast<Bone *>(arm->bonebase.first), select_p, show_pose_bone_cb);
bool changed = false;
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_iter->pose->chanbase) {
if (!ANIM_bone_in_visible_collection(arm, pchan->bone)) {
continue;
}
if ((pchan->drawflag & PCHAN_DRAW_HIDDEN) == 0) {
continue;
}
if (!(pchan->bone->flag & BONE_UNSELECTABLE)) {
SET_FLAG_FROM_TEST(pchan->bone->flag, select, BONE_SELECTED);
}
pchan->drawflag &= ~PCHAN_DRAW_HIDDEN;
changed = true;
}
if (changed) {
changed_multi = true;
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob_iter);

View File

@@ -624,7 +624,9 @@ static wmOperatorStatus pose_select_parent_exec(bContext *C, wmOperator * /*op*/
pchan = CTX_data_active_pose_bone(C);
if (pchan) {
parent = pchan->parent;
if ((parent) && !(parent->bone->flag & (BONE_HIDDEN_P | BONE_UNSELECTABLE))) {
if ((parent) && !(parent->drawflag & PCHAN_DRAW_HIDDEN) &&
!(parent->bone->flag & BONE_UNSELECTABLE))
{
parent->bone->flag |= BONE_SELECTED;
arm->act_bone = parent->bone;
}

View File

@@ -326,7 +326,7 @@ bool jump_to_bone(bContext *C, Object *ob, const char *bone_name, const bool rev
if (pchan != nullptr) {
if (reveal_hidden) {
/* Unhide the bone. */
pchan->bone->flag &= ~BONE_HIDDEN_P;
pchan->drawflag &= ~PCHAN_DRAW_HIDDEN;
ANIM_armature_bonecoll_show_from_pchan(arm, pchan);
}

View File

@@ -29,6 +29,7 @@
#include "BLT_translation.hh"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "BKE_context.hh"
#include "BKE_curve.hh"
@@ -49,6 +50,7 @@
#include "BKE_report.hh"
#include "BKE_scene.hh"
#include "ANIM_armature.hh"
#include "ANIM_bone_collections.hh"
#include "ANIM_keyframing.hh"
@@ -177,12 +179,20 @@ static void restrictbutton_r_lay_fn(bContext *C, void *poin, void * /*poin2*/)
WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, poin);
}
static void restrictbutton_bone_visibility_fn(bContext *C, void *poin, void * /*poin2*/)
static void restrictbutton_bone_visibility_fn(bContext *C, void *poin, void *poin2)
{
Bone *bone = (Bone *)poin;
const Object *ob = (Object *)poin;
bPoseChannel *pchan = (bPoseChannel *)poin2;
if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
restrictbutton_recursive_bone(bone, BONE_HIDDEN_P, (bone->flag & BONE_HIDDEN_P) != 0);
blender::animrig::pose_bone_descendent_iterator(
*ob->pose, *pchan, [&](bPoseChannel &descendent) {
if (pchan->drawflag & PCHAN_DRAW_HIDDEN) {
descendent.drawflag |= PCHAN_DRAW_HIDDEN;
}
else {
descendent.drawflag &= ~PCHAN_DRAW_HIDDEN;
}
});
}
}
@@ -1135,7 +1145,7 @@ static void outliner_draw_restrictbuts(uiBlock *block,
props.constraint_enable = RNA_struct_type_find_property(&RNA_Constraint, "mute");
props.bone_hide_viewport = RNA_struct_type_find_property(&RNA_Bone, "hide");
props.bone_hide_viewport = RNA_struct_type_find_property(&RNA_PoseBone, "hide");
props.initialized = true;
}
@@ -1401,7 +1411,7 @@ static void outliner_draw_restrictbuts(uiBlock *block,
Object *ob = (Object *)tselem->id;
bArmature *arm = static_cast<bArmature *>(ob->data);
PointerRNA ptr = RNA_pointer_create_discrete(&arm->id, &RNA_Bone, bone);
PointerRNA ptr = RNA_pointer_create_discrete(&arm->id, &RNA_PoseBone, pchan);
if (space_outliner->show_restrict_flags & SO_RESTRICT_VIEWPORT) {
bt = uiDefIconButR_prop(block,
@@ -1419,7 +1429,7 @@ static void outliner_draw_restrictbuts(uiBlock *block,
0,
TIP_("Restrict visibility in the 3D View\n"
" \u2022 Shift to set children"));
UI_but_func_set(bt, restrictbutton_bone_visibility_fn, bone, nullptr);
UI_but_func_set(bt, restrictbutton_bone_visibility_fn, ob, pchan);
UI_but_flag_enable(bt, UI_BUT_DRAG_LOCK);
UI_but_drawflag_enable(bt, UI_BUT_ICON_REVERSE);
}

View File

@@ -577,7 +577,7 @@ static void tree_element_posechannel_activate(bContext *C,
pchan->bone->flag &= ~BONE_SELECTED;
}
else {
if (blender::animrig::bone_is_visible(arm, pchan->bone)) {
if (blender::animrig::bone_is_visible_pchan(arm, pchan)) {
pchan->bone->flag |= BONE_SELECTED;
}
arm->act_bone = pchan->bone;

View File

@@ -2096,11 +2096,11 @@ static void pchan_fn(int event, TreeElement *te, TreeStoreElem * /*tselem*/, voi
pchan->bone->flag &= ~BONE_SELECTED;
}
else if (event == OL_DOP_HIDE) {
pchan->bone->flag |= BONE_HIDDEN_P;
pchan->drawflag |= PCHAN_DRAW_HIDDEN;
pchan->bone->flag &= ~BONE_SELECTED;
}
else if (event == OL_DOP_UNHIDE) {
pchan->bone->flag &= ~BONE_HIDDEN_P;
pchan->drawflag &= ~PCHAN_DRAW_HIDDEN;
}
}

View File

@@ -504,6 +504,7 @@ typedef enum ePchan_IkFlag {
/* PoseChannel->drawflag */
typedef enum ePchan_DrawFlag {
PCHAN_DRAW_NO_CUSTOM_BONE_SIZE = (1 << 0),
PCHAN_DRAW_HIDDEN = (1 << 1),
} ePchan_DrawFlag;
/* NOTE: It doesn't take custom_scale_xyz into account. */

View File

@@ -720,11 +720,9 @@ static void rna_Bone_hide_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA
{
bArmature *arm = (bArmature *)ptr->owner_id;
Bone *bone = (Bone *)ptr->data;
if (bone->flag & (BONE_HIDDEN_P | BONE_UNSELECTABLE)) {
if (bone->flag & (BONE_HIDDEN_A | BONE_UNSELECTABLE)) {
bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
}
WM_main_add_notifier(NC_OBJECT | ND_POSE, arm);
DEG_id_tag_update(&arm->id, ID_RECALC_SYNC_TO_EVAL);
}
@@ -1778,16 +1776,11 @@ static void rna_def_bone(BlenderRNA *brna)
RNA_define_lib_overridable(true);
/* XXX should we define this in PoseChannel wrapping code instead?
* But PoseChannels directly get some of their flags from here... */
prop = RNA_def_property(srna, "hide", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", BONE_HIDDEN_P);
RNA_def_property_ui_text(
prop,
"Hide",
"Bone is not visible when it is not in Edit Mode (i.e. in Object or Pose Modes)");
RNA_def_property_boolean_sdna(prop, nullptr, "flag", BONE_HIDDEN_A);
RNA_def_property_ui_text(prop, "Hide", "Bone is not visible when it is in Edit Mode");
RNA_def_property_ui_icon(prop, ICON_RESTRICT_VIEW_OFF, -1);
RNA_def_property_update(prop, 0, "rna_Bone_hide_update");
RNA_def_property_update(prop, 0, "rna_EditBone_hide_update");
prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", BONE_SELECTED);

View File

@@ -95,6 +95,14 @@ static void rna_Pose_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr
WM_main_add_notifier(NC_OBJECT | ND_POSE, ptr->owner_id);
}
static void rna_PoseBone_visibility_update(Main * /* bmain */,
Scene * /* scene */,
PointerRNA *ptr)
{
DEG_id_tag_update(ptr->owner_id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_OBJECT | ND_POSE, ptr->owner_id);
}
static void rna_Pose_dependency_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr)
{
DEG_relations_tag_update(bmain);
@@ -1169,6 +1177,14 @@ static void rna_def_pose_channel(BlenderRNA *brna)
prop, "Scale to Bone Length", "Scale the custom object by the bone length");
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update");
prop = RNA_def_property(srna, "hide", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_boolean_sdna(prop, nullptr, "drawflag", PCHAN_DRAW_HIDDEN);
RNA_def_property_ui_text(prop, "Hide", "Bone is not visible except for Edit Mode");
RNA_def_property_ui_icon(prop, ICON_RESTRICT_VIEW_OFF, -1);
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_PoseBone_visibility_update");
prop = RNA_def_property(srna, "custom_shape_transform", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, nullptr, "custom_tx");
RNA_def_property_struct_type(prop, "PoseBone");