Anim: hierarchical visibility for bone collections #116784

Manually merged
Sybren A. Stüvel merged 6 commits from dr.sybren/blender:anim/bone-collections-hierarchical-visibility into main 2024-01-05 15:35:37 +01:00
9 changed files with 300 additions and 25 deletions

View File

@ -214,8 +214,33 @@ void ANIM_armature_bonecoll_name_set(struct bArmature *armature,
struct BoneCollection *bcoll,
const char *name);
void ANIM_bonecoll_show(struct BoneCollection *bcoll);
void ANIM_bonecoll_hide(struct BoneCollection *bcoll);
/**
* Show this bone collection.
*
* This marks the bone collection as 'visible'. Whether it is effectively
* visible also depends on the visibility state of its ancestors. */
void ANIM_bonecoll_show(bArmature *armature, BoneCollection *bcoll);
/**
* Hide this bone collection.
*
* This marks the bone collection as 'hidden'. This also effectively hides its descendants,
dr.sybren marked this conversation as resolved Outdated

Spelling, throughout this PR: decendant -> descendant

(Don't feel bad: I almost didn't catch it myself, ha ha.)

Spelling, throughout this PR: decendant -> descendant (Don't feel bad: I almost didn't catch it myself, ha ha.)
* regardless of their visibility state. */
void ANIM_bonecoll_hide(bArmature *armature, BoneCollection *bcoll);
/**
* Show or hide this bone collection.
*
* Calling this with a hard-coded `is_visible` parameter is equivalent to
dr.sybren marked this conversation as resolved Outdated

Nit: IMO "Do not use..." gives the impression that the results will be incorrect if you do so. It's fairly obvious that that's not the case here, but it did give me minor cognitive dissonance for a moment.

Maybe something like this would be better:

"Calling this with a hard-coded is_visible parameter is equivalent to calling the dedicated show/hide functions. Prefer the dedicated functions for clarity."

Nit: IMO "Do not use..." gives the impression that the results will be incorrect if you do so. It's fairly obvious that that's not the case here, but it did give me minor cognitive dissonance for a moment. Maybe something like this would be better: "Calling this with a hard-coded `is_visible` parameter is equivalent to calling the dedicated show/hide functions. Prefer the dedicated functions for clarity."
* calling the dedicated show/hide functions. Prefer the dedicated functions for
* clarity.
*
* \see ANIM_bonecoll_show
* \see ANIM_bonecoll_hide
*/
void ANIM_armature_bonecoll_is_visible_set(bArmature *armature,
BoneCollection *bcoll,
bool is_visible);
/**
* Assign the bone to the bone collection.

View File

@ -44,11 +44,16 @@ namespace {
/** Default flags for new bone collections. */
constexpr eBoneCollection_Flag default_flags = BONE_COLLECTION_VISIBLE |
BONE_COLLECTION_SELECTABLE;
BONE_COLLECTION_SELECTABLE |
BONE_COLLECTION_ANCESTORS_VISIBLE;
constexpr auto bonecoll_default_name = "Bones";
} // namespace
static void ancestors_visible_update(bArmature *armature,
const BoneCollection *parent_bcoll,
BoneCollection *bcoll);
BoneCollection *ANIM_bonecoll_new(const char *name)
{
if (name == nullptr || name[0] == '\0') {
@ -98,6 +103,11 @@ void ANIM_armature_runtime_refresh(bArmature *armature)
ANIM_armature_runtime_free(armature);
ANIM_armature_bonecoll_active_runtime_refresh(armature);
/* Make sure the BONE_COLLECTION_ANCESTORS_VISIBLE flags are set correctly. */
for (BoneCollection *bcoll : armature->collections_roots()) {
ancestors_visible_update(armature, nullptr, bcoll);
}
/* Construct the bone-to-collections mapping. */
for (BoneCollection *bcoll : armature->collections_span()) {
add_reverse_pointers(bcoll);
@ -182,6 +192,8 @@ static void bonecoll_insert_as_root(bArmature *armature, BoneCollection *bcoll,
bonecoll_insert_at_index(armature, bcoll, at_index);
armature->collection_root_count++;
ancestors_visible_update(armature, nullptr, bcoll);
}
static int bonecoll_insert_as_child(bArmature *armature,
@ -201,6 +213,8 @@ static int bonecoll_insert_as_child(bArmature *armature,
bonecoll_insert_at_index(armature, bcoll, insert_at_index);
parent->child_count++;
ancestors_visible_update(armature, parent, bcoll);
return insert_at_index;
}
@ -707,14 +721,70 @@ int ANIM_armature_bonecoll_get_index_by_name(bArmature *armature, const char *na
return -1;
}
void ANIM_bonecoll_show(BoneCollection *bcoll)
/* Clear BONE_COLLECTION_ANCESTORS_VISIBLE on all decendents of this bone collection. */
static void ancestors_visible_descendants_clear(bArmature *armature, BoneCollection *parent_bcoll)
{
bcoll->flags |= BONE_COLLECTION_VISIBLE;
for (BoneCollection *bcoll : armature->collection_children(parent_bcoll)) {
bcoll->flags &= ~BONE_COLLECTION_ANCESTORS_VISIBLE;
ancestors_visible_descendants_clear(armature, bcoll);
}
}
void ANIM_bonecoll_hide(BoneCollection *bcoll)
/* Set or clear BONE_COLLECTION_ANCESTORS_VISIBLE on all decendents of this bone collection. */
static void ancestors_visible_descendants_update(bArmature *armature, BoneCollection *parent_bcoll)
{
if (!parent_bcoll->is_visible_effectively()) {
/* If this bone collection is not visible itself, or any of its ancestors are
* invisible, all descendants have an invisible ancestor. */
ancestors_visible_descendants_clear(armature, parent_bcoll);
return;
}
/* parent_bcoll is visible, and so are its ancestors. This means that all direct children have
* visible ancestors. The grandchildren depend on the children's visibility as well, hence the
* recursion. */
for (BoneCollection *bcoll : armature->collection_children(parent_bcoll)) {
bcoll->flags |= BONE_COLLECTION_ANCESTORS_VISIBLE;
ancestors_visible_descendants_update(armature, bcoll);
}
}
/* Set/clear BONE_COLLECTION_ANCESTORS_VISIBLE on this bone collection and all its decendents. */

minor, but this could also read "Set or clear" just like the function above

minor, but this could also read "Set or clear" just like the function above

I shortened it, as with "Set or clear" it will rewrap the line and put the closing */ on a line by itself. Now it juuuuust fits.

I shortened it, as with "Set or clear" it will rewrap the line and put the closing `*/` on a line by itself. Now it juuuuust fits.
static void ancestors_visible_update(bArmature *armature,
nathanvegdahl marked this conversation as resolved
Review

Just noting a possible alternative, rather than actually suggesting a change per se:

I was waffling back-and-forth on how I feel about the redundancy between this function and ancestors_visible_decendants_update() above.

I believe if we want to we could reduce both to a single function. Basically, we would effectively move the logic of ancestors_visible_decendants_update() into this function. Then at the top of this function, if (and only if) parent_bcoll is null we scan to find its parent (leaving it null if bcoll is a root).

That would make this function slower due to the additional linear scan at the initial call (but not subsequent recursive calls), and also because it would always update bcoll itself even when not strictly needed. But it would also reduce the API surface. So it's a trade-off.

I don't feel strongly either way, so I'm happy with the code as-is. But just wanted to mention the alternative.

Just noting a possible alternative, rather than actually suggesting a change per se: I was waffling back-and-forth on how I feel about the redundancy between this function and `ancestors_visible_decendants_update()` above. I believe if we want to we could reduce both to a single function. Basically, we would effectively move the logic of `ancestors_visible_decendants_update()` into this function. Then at the top of this function, if (and only if) `parent_bcoll` is null we scan to find its parent (leaving it null if `bcoll` is a root). That would make this function slower due to the additional linear scan at the initial call (but not subsequent recursive calls), and also because it would always update `bcoll` itself even when not strictly needed. But it would also reduce the API surface. So it's a trade-off. I don't feel strongly either way, so I'm happy with the code as-is. But just wanted to mention the alternative.

I've been pondering the same thing, and to me it was easier to comprehend what was going on with separate "update this bcoll and its descendents" vs. "make sure the descendents are in sync with their parent" functions.

Since these are static functions, and thus not part of any public API, I think I'd like to keep them smaller and more specific.

I've been pondering the same thing, and to me it was easier to comprehend what was going on with separate "update this bcoll and its descendents" vs. "make sure the descendents are in sync with their parent" functions. Since these are `static` functions, and thus not part of any public API, I think I'd like to keep them smaller and more specific.
const BoneCollection *parent_bcoll,
BoneCollection *bcoll)
{
if (parent_bcoll == nullptr || parent_bcoll->is_visible_effectively()) {
bcoll->flags |= BONE_COLLECTION_ANCESTORS_VISIBLE;
}
else {
bcoll->flags &= ~BONE_COLLECTION_ANCESTORS_VISIBLE;
}
ancestors_visible_descendants_update(armature, bcoll);
}
void ANIM_bonecoll_show(bArmature *armature, BoneCollection *bcoll)
{
bcoll->flags |= BONE_COLLECTION_VISIBLE;
ancestors_visible_descendants_update(armature, bcoll);
}
void ANIM_bonecoll_hide(bArmature *armature, BoneCollection *bcoll)
{
bcoll->flags &= ~BONE_COLLECTION_VISIBLE;
ancestors_visible_descendants_update(armature, bcoll);
}
void ANIM_armature_bonecoll_is_visible_set(bArmature *armature,
BoneCollection *bcoll,
const bool is_visible)
{
if (is_visible) {
ANIM_bonecoll_show(armature, bcoll);
}
else {
ANIM_bonecoll_hide(armature, bcoll);
}
}
/* Store the bone's membership on the collection. */
@ -859,7 +929,7 @@ static bool any_bone_collection_visible(const ListBase /*BoneCollectionRef*/ *co
LISTBASE_FOREACH (const BoneCollectionReference *, bcoll_ref, collection_refs) {
const BoneCollection *bcoll = bcoll_ref->bcoll;
if (bcoll->flags & BONE_COLLECTION_VISIBLE) {
if (bcoll->is_visible_effectively()) {
return true;
}
}
@ -881,14 +951,14 @@ bool ANIM_bonecoll_is_visible_editbone(const bArmature * /*armature*/, const Edi
void ANIM_armature_bonecoll_show_all(bArmature *armature)
{
for (BoneCollection *bcoll : armature->collections_span()) {
ANIM_bonecoll_show(bcoll);
ANIM_bonecoll_show(armature, bcoll);
}
}
void ANIM_armature_bonecoll_hide_all(bArmature *armature)
{
for (BoneCollection *bcoll : armature->collections_span()) {
ANIM_bonecoll_hide(bcoll);
ANIM_bonecoll_hide(armature, bcoll);
}
}
@ -1145,6 +1215,9 @@ int armature_bonecoll_move_to_parent(bArmature *armature,
armature->collection_root_count = armature_root.child_count;
BLI_assert(armature_root.child_index == 0);
/* Since the parent changed, the effective visibility might change too. */
ancestors_visible_update(armature, to_parent, armature->collection_array[to_bcoll_index]);
return to_bcoll_index;
}

View File

@ -22,7 +22,9 @@ TEST(ANIM_bone_collections, bonecoll_new_free)
EXPECT_NE(nullptr, bcoll);
EXPECT_EQ("some name", std::string(bcoll->name));
EXPECT_TRUE(BLI_listbase_is_empty(&bcoll->bones));
EXPECT_EQ(BONE_COLLECTION_VISIBLE | BONE_COLLECTION_SELECTABLE, bcoll->flags);
EXPECT_EQ(BONE_COLLECTION_VISIBLE | BONE_COLLECTION_SELECTABLE |
BONE_COLLECTION_ANCESTORS_VISIBLE,
bcoll->flags);
ANIM_bonecoll_free(bcoll);
}
@ -453,6 +455,71 @@ TEST_F(ANIM_armature_bone_collections, find_parent_index)
EXPECT_EQ(2, armature_bonecoll_find_parent_index(&arm, 5));
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_visibility)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root0 = ANIM_armature_bonecoll_new(&arm, "root0");
BoneCollection *bcoll_root1 = ANIM_armature_bonecoll_new(&arm, "root1");
const int root0_index = armature_bonecoll_find_index(&arm, bcoll_root0);
BoneCollection *bcoll_r0_child0 = ANIM_armature_bonecoll_new(&arm, "r0_child0", root0_index);
BoneCollection *bcoll_r0_child1 = ANIM_armature_bonecoll_new(&arm, "r0_child1", root0_index);
const int child0_index = armature_bonecoll_find_index(&arm, bcoll_r0_child0);
BoneCollection *bcoll_c0_child0 = ANIM_armature_bonecoll_new(&arm, "c0_child0", child0_index);
dr.sybren marked this conversation as resolved Outdated

Not sure if this is a naming mistake or a choice-of-parent mistake. With the name c1_child0, I would expect its parent to be r0_child1, but in fact the parent is being set to r0_child0.

Not sure if this is a naming mistake or a choice-of-parent mistake. With the name `c1_child0`, I would expect its parent to be `r0_child1`, but in fact the parent is being set to `r0_child0`.
/* Initially, all bone collections should be marked as visible. */
EXPECT_TRUE(bcoll_root0->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_root1->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_r0_child0->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_r0_child1->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_c0_child0->flags & BONE_COLLECTION_VISIBLE);
/* Initially, all bone collections should have visible ancestors. */
EXPECT_TRUE(bcoll_root0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_TRUE(bcoll_root1->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_TRUE(bcoll_r0_child0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_TRUE(bcoll_r0_child1->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_TRUE(bcoll_c0_child0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
/* Mark root_0 as invisible, this should also update its children. */
ANIM_bonecoll_hide(&arm, bcoll_root0);
EXPECT_FALSE(bcoll_root0->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_root1->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_r0_child0->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_r0_child1->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_c0_child0->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_root0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_TRUE(bcoll_root1->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_FALSE(bcoll_r0_child0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_FALSE(bcoll_r0_child1->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_FALSE(bcoll_c0_child0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
/* Move r0_child0 to root1, that should change its BONE_COLLECTION_ANCESTORS_VISIBLE */
const int root1_index = armature_bonecoll_find_index(&arm, bcoll_root1);
armature_bonecoll_move_to_parent(&arm, child0_index, 0, root0_index, root1_index);
EXPECT_FALSE(bcoll_root0->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_root1->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_r0_child0->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_r0_child1->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_c0_child0->flags & BONE_COLLECTION_VISIBLE);
EXPECT_TRUE(bcoll_root0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_TRUE(bcoll_root1->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
EXPECT_TRUE(bcoll_r0_child0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE)
<< "The child that was moved to a visible root should be affected";
EXPECT_FALSE(bcoll_r0_child1->flags & BONE_COLLECTION_ANCESTORS_VISIBLE)
<< "The child that wasn't moved should not be affected.";
EXPECT_TRUE(bcoll_c0_child0->flags & BONE_COLLECTION_ANCESTORS_VISIBLE)
<< "The grandchild that was indirectly moved to a visible root should be affected";
/* Add a new child to root0, it should have the right flags. */
BoneCollection *bcoll_r0_child2 = ANIM_armature_bonecoll_new(&arm, "r0_child2", root0_index);
EXPECT_TRUE(bcoll_r0_child2->flags & BONE_COLLECTION_VISIBLE);
EXPECT_FALSE(bcoll_r0_child2->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
}
TEST_F(ANIM_armature_bone_collections, bones_assign_unassign)
{
BoneCollection *bcoll = ANIM_armature_bonecoll_new(&arm, "collection");

View File

@ -3149,4 +3149,37 @@ blender::Span<BoneCollection *> bArmature::collections_span()
return blender::Span(collection_array, collection_array_num);
}
blender::Span<const BoneCollection *> bArmature::collections_roots() const
{
return blender::Span(collection_array, collection_root_count);
}
blender::Span<BoneCollection *> bArmature::collections_roots()
{
return blender::Span(collection_array, collection_root_count);
}
blender::Span<const BoneCollection *> bArmature::collection_children(
const BoneCollection *parent) const
{
return blender::Span(&collection_array[parent->child_index], parent->child_count);
}
blender::Span<BoneCollection *> bArmature::collection_children(BoneCollection *parent)
{
return blender::Span(&collection_array[parent->child_index], parent->child_count);
}
bool BoneCollection::is_visible() const
{
return this->flags & BONE_COLLECTION_VISIBLE;
}
bool BoneCollection::is_visible_ancestors() const
{
return this->flags & BONE_COLLECTION_ANCESTORS_VISIBLE;
}
bool BoneCollection::is_visible_effectively() const
{
return this->is_visible() && this->is_visible_ancestors();
}
/** \} */

View File

@ -183,7 +183,7 @@ static void version_bonelayers_to_bonecollections(Main *bmain)
layermask_collection.append(std::make_pair(layer_mask, bcoll));
if ((arm->layer & layer_mask) == 0) {
ANIM_bonecoll_hide(bcoll);
ANIM_bonecoll_hide(arm, bcoll);
}
}
@ -223,7 +223,7 @@ static void version_bonegroups_to_bonecollections(Main *bmain)
* groups did not have any impact on this. To retain the behavior, that
* hiding all layers a bone is on hides the bone, the
* bone-group-collections should be created hidden. */
ANIM_bonecoll_hide(bcoll);
ANIM_bonecoll_hide(arm, bcoll);
}
/* Assign the bones to their bone group based collection. */

View File

@ -105,7 +105,7 @@ class BoneCollectionDropTarget : public TreeViewItemDropTarget {
return false;
}
/* Do not allow dropping onto its own decendants. */
/* Do not allow dropping onto its own descendants. */
if (armature_bonecoll_is_decendent_of(
drag_arm_bcoll->armature, drag_arm_bcoll->bcoll_index, drop_bonecoll_.bcoll_index))
{
@ -221,14 +221,12 @@ class BoneCollectionItem : public AbstractTreeViewItem {
/* Visibility eyecon. */
{
const bool is_visible = bone_collection_.flags & BONE_COLLECTION_VISIBLE;
uiLayout *visibility_sub = uiLayoutRow(sub, true);
uiLayoutSetActive(visibility_sub, bone_collection_.is_visible_ancestors());
const int icon = bone_collection_.is_visible() ? ICON_HIDE_OFF : ICON_HIDE_ON;
PointerRNA bcoll_ptr = rna_pointer();
uiItemR(sub,
&bcoll_ptr,
"is_visible",
UI_ITEM_R_ICON_ONLY,
"",
is_visible ? ICON_HIDE_OFF : ICON_HIDE_ON);
uiItemR(visibility_sub, &bcoll_ptr, "is_visible", UI_ITEM_R_ICON_ONLY, "", icon);
}
}

View File

@ -226,10 +226,10 @@ static void bc_add_armature_collections(COLLADAFW::Node *node,
for (const std::string &name : collection_names) {
BoneCollection *bcoll = ANIM_armature_bonecoll_new(arm, name.c_str());
if (visible_names_set.find(name) == visible_names_set.end()) {
ANIM_bonecoll_hide(bcoll);
ANIM_bonecoll_hide(arm, bcoll);
}
else {
ANIM_bonecoll_show(bcoll);
ANIM_bonecoll_show(arm, bcoll);
}
}

View File

@ -226,6 +226,14 @@ typedef struct bArmature {
/* Collection array access for convenient for-loop iteration. */
blender::Span<const BoneCollection *> collections_span() const;
blender::Span<BoneCollection *> collections_span();
/* Span of all root collections. */
blender::Span<const BoneCollection *> collections_roots() const;
blender::Span<BoneCollection *> collections_roots();
/* Return the span of children of the given bone collection. */
blender::Span<const BoneCollection *> collection_children(const BoneCollection *parent) const;
blender::Span<BoneCollection *> collection_children(BoneCollection *parent);
#endif
} bArmature;
@ -264,6 +272,35 @@ typedef struct BoneCollection {
/** Custom properties. */
struct IDProperty *prop;
#ifdef __cplusplus
/**
* Return whether this collection is marked as 'visible'.
*
* Note that its effective visibility depends on the visibility of its ancestors as well.
*
* \see is_visible_effectively
* \see ANIM_bonecoll_show
* \see ANIM_bonecoll_hide
*/
bool is_visible() const;
/**
* Return whether this collection's ancestors are visible or not.
*
* \see is_visible_effectively
*/
bool is_visible_ancestors() const;
/**
* Return whether this collection is effectively visible.
*
* \return true when this collection and all its ancestors are visible.
*
* \see is_visible
dr.sybren marked this conversation as resolved Outdated

typo in visisble vs visible

typo in `visisble` vs `visible`
*/
bool is_visible_effectively() const;

I usually went for e.g. is_visible_in_hierarchy in such cases.
"effectively" doesn't tell me much tbh

There is also another way of doing it, where is_visible returns the effective visibility
and you have another function get_collection_visibility (or similar) to get the actual flag

I usually went for e.g. `is_visible_in_hierarchy` in such cases. "effectively" doesn't tell me much tbh There is also another way of doing it, where `is_visible` returns the effective visibility and you have another function `get_collection_visibility` (or similar) to get the actual flag

This is something I've been struggling with indeed, as there are now three "visibilities":

  1. the flag
  2. the effective visibility
  3. whether the bone collection can be seen in the tree view (i.e. its parents are expanded)

I don't think there's names without any downside; is_visible_in_hierarchy to me feels more like the 3rd option, because "the hierarchy" conjures the image of the tree structure.

I'll keep the naming as is, and double-check the documentation to ensure things are as clearly described as possible.

This is something I've been struggling with indeed, as there are now three "visibilities": 1. the flag 2. the effective visibility 3. whether the bone collection can be seen in the tree view (i.e. its parents are expanded) I don't think there's names without any downside; `is_visible_in_hierarchy` to me feels more like the 3rd option, because "the hierarchy" conjures the image of the tree structure. I'll keep the naming as is, and double-check the documentation to ensure things are as clearly described as possible.

hm, imo the bone collection shouldn't need to know if its expanded or not.
but this is an issue of blender in general where the core datastructure and the GUI are too closely connected.

hm, imo the bone collection shouldn't need to know if its expanded or not. but this is an issue of blender in general where the core datastructure and the GUI are too closely connected.
#endif
} BoneCollection;
/** Membership relation of a bone with a bone collection. */
@ -461,11 +498,19 @@ typedef enum eBone_BBoneHandleFlag {
/** #BoneCollection.flag */
typedef enum eBoneCollection_Flag {
BONE_COLLECTION_VISIBLE = (1 << 0),
BONE_COLLECTION_VISIBLE = (1 << 0), /* Visibility flag of this particular collection. */
BONE_COLLECTION_SELECTABLE = (1 << 1), /* Intended to be implemented in the not-so-far future. */
BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL = (1 << 2), /* Added by a local library override. */
/**
* Set when all ancestors are visible.
*
* This would actually be a runtime flag, but bone collections don't have a
* runtime struct yet, and the addition of one more flag doesn't seem worth
* the effort. */
BONE_COLLECTION_ANCESTORS_VISIBLE = (1 << 3),
} eBoneCollection_Flag;
ENUM_OPERATORS(eBoneCollection_Flag, BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL)
ENUM_OPERATORS(eBoneCollection_Flag, BONE_COLLECTION_ANCESTORS_VISIBLE)
#ifdef __cplusplus

View File

@ -345,6 +345,23 @@ static void rna_BoneCollection_name_set(PointerRNA *ptr, const char *name)
WM_main_add_notifier(NC_OBJECT | ND_BONE_COLLECTION, &arm->id);
}
static void rna_BoneCollection_is_visible_set(PointerRNA *ptr, const bool is_visible)
{
bArmature *arm = (bArmature *)ptr->owner_id;
BoneCollection *bcoll = (BoneCollection *)ptr->data;
ANIM_armature_bonecoll_is_visible_set(arm, bcoll, is_visible);
WM_main_add_notifier(NC_OBJECT | ND_BONE_COLLECTION, &arm->id);
WM_main_add_notifier(NC_OBJECT | ND_POSE, &arm->id);
}
static bool rna_BoneCollection_is_visible_effectively_get(PointerRNA *ptr)
{
const BoneCollection *bcoll = (BoneCollection *)ptr->data;
return bcoll->is_visible_effectively();
}
static char *rna_BoneCollection_path(const PointerRNA *ptr)
{
const BoneCollection *bcoll = (const BoneCollection *)ptr->data;
@ -2216,7 +2233,24 @@ static void rna_def_bonecollection(BlenderRNA *brna)
prop, "Visible", "Bones in this collection will be visible in pose/object mode");
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, nullptr);
RNA_def_property_boolean_funcs(prop, nullptr, "rna_BoneCollection_is_visible_set");
prop = RNA_def_property(srna, "is_visible_ancestors", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flags", BONE_COLLECTION_ANCESTORS_VISIBLE);
RNA_def_property_ui_text(prop,
"Ancestors Effectively Visible",
"True when all of the ancestors of this bone collection are marked as "
"visible; always True for root bone collections");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "is_visible_effectively", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop, "rna_BoneCollection_is_visible_effectively_get", nullptr);
RNA_def_property_ui_text(
prop,
"Effective Visibility",
"Whether this bone collection is effectively visible in the viewport. This is True when "
"this bone collection and all of its ancestors are visible");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "is_local_override", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flags", BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL);