Anim: hierarchical visibility for bone collections

Bone collection visibility now respects their hierarchy.

A bone collection is only visible when it is marked as visible and all
its ancestors (so parents, greatparents, etc.) are visible. Root bone
collections have no ancestors by definition, and only consider their own
visibility.

The effective ancestors' visibility is stored on each bone collection,
in its `BONE_COLLECTION_ANCESTORS_VISIBLE` flag. This makes it possible
to determine the effective visibility from just the flags of the bone
collection itself.

The `BONE_COLLECTION_ANCESTORS_VISIBLE` flag is now stored, with the
other flags, in `BoneCollection::flags`. This means that it's stored in
DNA, even though it's derived data and should actually be stored in a
runtime struct. However, `BoneCollection` doesn't have any runtime
struct yet, and I don't feel that the introduction of this flag is a
good enough reason to introduce that just yet.

Pull Request: https://projects.blender.org/blender/blender/pulls/116784
This commit is contained in:
Sybren A. Stüvel
2024-01-04 17:43:16 +01:00
parent 31001b67fd
commit 6cfbf9ef2f
9 changed files with 285 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,
* 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
* 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. */
static void ancestors_visible_update(bArmature *armature,
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);
/* 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_visisble
*/
bool is_visible_effectively() const;
#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

@@ -373,6 +373,17 @@ 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 char *rna_BoneCollection_path(const PointerRNA *ptr)
{
const BoneCollection *bcoll = (const BoneCollection *)ptr->data;
@@ -2244,7 +2255,15 @@ 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_local_override", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flags", BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL);