diff --git a/source/blender/animrig/ANIM_bone_collections.hh b/source/blender/animrig/ANIM_bone_collections.hh index 463a62534ce..3e11e709c0a 100644 --- a/source/blender/animrig/ANIM_bone_collections.hh +++ b/source/blender/animrig/ANIM_bone_collections.hh @@ -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. diff --git a/source/blender/animrig/intern/bone_collections.cc b/source/blender/animrig/intern/bone_collections.cc index 34182372e3a..3558ebded8c 100644 --- a/source/blender/animrig/intern/bone_collections.cc +++ b/source/blender/animrig/intern/bone_collections.cc @@ -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; } diff --git a/source/blender/animrig/intern/bone_collections_test.cc b/source/blender/animrig/intern/bone_collections_test.cc index eb7ed7456d9..cd58af3668f 100644 --- a/source/blender/animrig/intern/bone_collections_test.cc +++ b/source/blender/animrig/intern/bone_collections_test.cc @@ -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"); diff --git a/source/blender/blenkernel/intern/armature.cc b/source/blender/blenkernel/intern/armature.cc index 9319406ef0c..d384cc6b952 100644 --- a/source/blender/blenkernel/intern/armature.cc +++ b/source/blender/blenkernel/intern/armature.cc @@ -3149,4 +3149,37 @@ blender::Span bArmature::collections_span() return blender::Span(collection_array, collection_array_num); } +blender::Span bArmature::collections_roots() const +{ + return blender::Span(collection_array, collection_root_count); +} +blender::Span bArmature::collections_roots() +{ + return blender::Span(collection_array, collection_root_count); +} + +blender::Span bArmature::collection_children( + const BoneCollection *parent) const +{ + return blender::Span(&collection_array[parent->child_index], parent->child_count); +} + +blender::Span 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(); +} + /** \} */ diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 454b583a99e..95498a84d07 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -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. */ diff --git a/source/blender/editors/interface/interface_template_bone_collection_tree.cc b/source/blender/editors/interface/interface_template_bone_collection_tree.cc index 919cea61a43..5451f0c06e7 100644 --- a/source/blender/editors/interface/interface_template_bone_collection_tree.cc +++ b/source/blender/editors/interface/interface_template_bone_collection_tree.cc @@ -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); } } diff --git a/source/blender/io/collada/collada_utils.cpp b/source/blender/io/collada/collada_utils.cpp index 4c2ca78994e..82c402812d9 100644 --- a/source/blender/io/collada/collada_utils.cpp +++ b/source/blender/io/collada/collada_utils.cpp @@ -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); } } diff --git a/source/blender/makesdna/DNA_armature_types.h b/source/blender/makesdna/DNA_armature_types.h index 26db8a41aa1..ad2fbd06897 100644 --- a/source/blender/makesdna/DNA_armature_types.h +++ b/source/blender/makesdna/DNA_armature_types.h @@ -226,6 +226,14 @@ typedef struct bArmature { /* Collection array access for convenient for-loop iteration. */ blender::Span collections_span() const; blender::Span collections_span(); + + /* Span of all root collections. */ + blender::Span collections_roots() const; + blender::Span collections_roots(); + + /* Return the span of children of the given bone collection. */ + blender::Span collection_children(const BoneCollection *parent) const; + blender::Span 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 diff --git a/source/blender/makesrna/intern/rna_armature.cc b/source/blender/makesrna/intern/rna_armature.cc index 2e8d9df4c13..4a3046195d4 100644 --- a/source/blender/makesrna/intern/rna_armature.cc +++ b/source/blender/makesrna/intern/rna_armature.cc @@ -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);