Anim: bone collections, add 'solo' flag

Add the 'solo' flag to bone collections, effectively adding another
layer of visibility controls.

If there is _any_ bone collection with this flag enabled, only this
collection (and others with this flag enabled) will be visible.

In RNA, the following properties are exposed:
- `bone_collection.is_solo`: writable property to manage the solo flag.
- `armature.is_solo_active`: read-only property that is `True` when any
  bone collection has `is_solo = True`.

The RNA property `bone_collection.is_visible_effectively` now also takes
the solo flag into account.

Pull Request: https://projects.blender.org/blender/blender/pulls/117414
This commit is contained in:
Sybren A. Stüvel
2024-01-22 16:07:53 +01:00
parent 8a569a8da4
commit a7f41fc938
8 changed files with 174 additions and 17 deletions

View File

@@ -239,6 +239,23 @@ void ANIM_armature_bonecoll_is_visible_set(bArmature *armature,
BoneCollection *bcoll,
bool is_visible);
/**
* Set or clear this bone collection's solo flag.
*/
void ANIM_armature_bonecoll_solo_set(bArmature *armature, BoneCollection *bcoll, bool is_solo);
/**
* Refresh the ARM_BCOLL_SOLO_ACTIVE flag.
*/
void ANIM_armature_refresh_solo_active(bArmature *armature);
/**
* Determine whether this bone collection is visible, taking into account the visibility of its
* ancestors and the "solo" flags that are in use.
*/
bool ANIM_armature_bonecoll_is_visible_effectively(const bArmature *armature,
const BoneCollection *bcoll);
/**
* Assign the bone to the bone collection.
*

View File

@@ -792,6 +792,54 @@ void ANIM_armature_bonecoll_is_visible_set(bArmature *armature,
}
}
void ANIM_armature_bonecoll_solo_set(bArmature *armature,
BoneCollection *bcoll,
const bool is_solo)
{
if (is_solo) {
/* Enabling solo is simple. */
bcoll->flags |= BONE_COLLECTION_SOLO;
armature->flag |= ARM_BCOLL_SOLO_ACTIVE;
return;
}
/* Disabling is harder, as the armature flag can only be disabled when there
* are no more bone collections with the SOLO flag set. */
bcoll->flags &= ~BONE_COLLECTION_SOLO;
ANIM_armature_refresh_solo_active(armature);
}
void ANIM_armature_refresh_solo_active(bArmature *armature)
{
bool any_bcoll_solo = false;
for (const BoneCollection *bcoll : armature->collections_span()) {
if (bcoll->flags & BONE_COLLECTION_SOLO) {
any_bcoll_solo = true;
break;
}
}
if (any_bcoll_solo) {
armature->flag |= ARM_BCOLL_SOLO_ACTIVE;
}
else {
armature->flag &= ~ARM_BCOLL_SOLO_ACTIVE;
}
}
bool ANIM_armature_bonecoll_is_visible_effectively(const bArmature *armature,
const BoneCollection *bcoll)
{
const bool is_solo_active = armature->flag & ARM_BCOLL_SOLO_ACTIVE;
if (is_solo_active) {
/* If soloing is active, nothing in the hierarchy matters except the solo flag. */
return bcoll->is_solo();
}
return bcoll->is_visible_with_ancestors();
}
/* Store the bone's membership on the collection. */
static void add_membership(BoneCollection *bcoll, Bone *bone)
{
@@ -925,7 +973,8 @@ void ANIM_armature_bonecoll_reconstruct(bArmature *armature)
});
}
static bool any_bone_collection_visible(const ListBase /*BoneCollectionRef*/ *collection_refs)
static bool any_bone_collection_visible(const bArmature *armature,
const ListBase /*BoneCollectionRef*/ *collection_refs)
{
/* Special case: when a bone is not in any collection, it is visible. */
if (BLI_listbase_is_empty(collection_refs)) {
@@ -934,23 +983,20 @@ 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->is_visible_with_ancestors()) {
if (ANIM_armature_bonecoll_is_visible_effectively(armature, bcoll)) {
return true;
}
}
return false;
}
/* TODO: these two functions were originally implemented for armature layers, hence the armature
* parameters. These should be removed at some point. */
bool ANIM_bone_in_visible_collection(const bArmature * /*armature*/, const Bone *bone)
bool ANIM_bone_in_visible_collection(const bArmature *armature, const Bone *bone)
{
return any_bone_collection_visible(&bone->runtime.collections);
return any_bone_collection_visible(armature, &bone->runtime.collections);
}
bool ANIM_bonecoll_is_visible_editbone(const bArmature * /*armature*/, const EditBone *ebone)
bool ANIM_bonecoll_is_visible_editbone(const bArmature *armature, const EditBone *ebone)
{
return any_bone_collection_visible(&ebone->bone_collections);
return any_bone_collection_visible(armature, &ebone->bone_collections);
}
void ANIM_armature_bonecoll_show_all(bArmature *armature)

View File

@@ -1497,6 +1497,35 @@ TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__siblings)
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child0", "child1_0"}));
}
TEST_F(ANIM_armature_bone_collections_testlist, bone_collection_solo)
{
EXPECT_FALSE(arm.flag & ARM_BCOLL_SOLO_ACTIVE) << "By default no solo'ing should be active";
/* Enable solo. */
EXPECT_FALSE(child1->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1, true);
EXPECT_TRUE(child1->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Enable solo on another bone collection. */
EXPECT_FALSE(child1_0->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1_0, true);
EXPECT_TRUE(child1_0->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Disable the first solo flag. */
EXPECT_TRUE(child1->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1, false);
EXPECT_FALSE(child1->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Disable the second solo flag. This should also disable the ARM_BCOLL_SOLO_ACTIVE flag. */
EXPECT_TRUE(child1_0->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1_0, false);
EXPECT_FALSE(child1_0->flags & BONE_COLLECTION_SOLO);
EXPECT_FALSE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
}
class ANIM_armature_bone_collections_liboverrides
: public ANIM_armature_bone_collections_testlist {
protected:

View File

@@ -3186,5 +3186,9 @@ bool BoneCollection::is_visible_with_ancestors() const
{
return this->is_visible() && this->is_visible_ancestors();
}
bool BoneCollection::is_solo() const
{
return this->flags & BONE_COLLECTION_SOLO;
}
/** \} */

View File

@@ -4888,7 +4888,7 @@ void blo_do_versions_280(FileData *fd, Library * /*lib*/, Main *bmain)
}
LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) {
arm->flag &= ~(ARM_FLAG_UNUSED_1 | ARM_DRAW_RELATION_FROM_HEAD | ARM_FLAG_UNUSED_6 |
arm->flag &= ~(ARM_FLAG_UNUSED_1 | ARM_DRAW_RELATION_FROM_HEAD | ARM_BCOLL_SOLO_ACTIVE |
ARM_FLAG_UNUSED_7 | ARM_FLAG_UNUSED_12);
}
@@ -5367,7 +5367,7 @@ void blo_do_versions_280(FileData *fd, Library * /*lib*/, Main *bmain)
}
LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) {
arm->flag &= ~(ARM_FLAG_UNUSED_6);
arm->flag &= ~(ARM_BCOLL_SOLO_ACTIVE);
}
}

View File

@@ -236,13 +236,22 @@ class BoneCollectionItem : public AbstractTreeViewItem {
/* Visibility eye icon. */
{
const bool is_solo_active = armature_.flag & ARM_BCOLL_SOLO_ACTIVE;
uiLayout *visibility_sub = uiLayoutRow(sub, true);
uiLayoutSetActive(visibility_sub, bone_collection_.is_visible_ancestors());
uiLayoutSetActive(visibility_sub,
!is_solo_active && bone_collection_.is_visible_ancestors());
const int icon = bone_collection_.is_visible() ? ICON_HIDE_OFF : ICON_HIDE_ON;
PointerRNA bcoll_ptr = rna_pointer();
uiItemR(visibility_sub, &bcoll_ptr, "is_visible", UI_ITEM_R_ICON_ONLY, "", icon);
}
/* Solo icon. */
{
const int icon = bone_collection_.is_solo() ? ICON_SOLO_ON : ICON_SOLO_OFF;
PointerRNA bcoll_ptr = rna_pointer();
uiItemR(sub, &bcoll_ptr, "is_solo", UI_ITEM_R_ICON_ONLY, "", icon);
}
}
void build_context_menu(bContext &C, uiLayout &column) const override

View File

@@ -301,6 +301,11 @@ typedef struct BoneCollection {
* \see is_visible
*/
bool is_visible_with_ancestors() const;
/**
* Return whether this collection is marked as 'solo'.
*/
bool is_solo() const;
#endif
} BoneCollection;
@@ -334,8 +339,14 @@ typedef enum eArmature_Flag {
* from the tail, set = drawn from the head). Only controls the parent side of
* the line; the child side is always drawn to the head of the bone. */
ARM_DRAW_RELATION_FROM_HEAD = (1 << 5), /* Cleared in versioning of pre-2.80 files. */
ARM_FLAG_UNUSED_6 = (1 << 6), /* cleared */
ARM_FLAG_UNUSED_7 = (1 << 7), /* cleared */
/**
* Whether any bone collection is marked with the 'solo' flag.
* When this is the case, bone collection visibility flags don't matter any more, and only ones
* that have their 'solo' flag set will be visible.
*
* \see eBoneCollection_Flag::BONE_COLLECTION_SOLO */
ARM_BCOLL_SOLO_ACTIVE = (1 << 6), /* Cleared in versioning of pre-2.80 files. */
ARM_FLAG_UNUSED_7 = (1 << 7), /* cleared */
ARM_MIRROR_EDIT = (1 << 8),
ARM_FLAG_UNUSED_9 = (1 << 9),
/** Made option negative, for backwards compatibility. */
@@ -510,8 +521,21 @@ typedef enum eBoneCollection_Flag {
* runtime struct yet, and the addition of one more flag doesn't seem worth
* the effort. */
BONE_COLLECTION_ANCESTORS_VISIBLE = (1 << 3),
/**
* Whether this bone collection is marked as 'solo'.
*
* If no bone collections have this flag set, visibility is determined by
* BONE_COLLECTION_VISIBLE.
*
* If there is any bone collection with the BONE_COLLECTION_SOLO flag enabled, all bone
* collections are effectively hidden, except other collections with this flag enabled.
*
* \see eArmature_Flag::ARM_BCOLL_SOLO_ACTIVE
*/
BONE_COLLECTION_SOLO = (1 << 4),
} eBoneCollection_Flag;
ENUM_OPERATORS(eBoneCollection_Flag, BONE_COLLECTION_ANCESTORS_VISIBLE)
ENUM_OPERATORS(eBoneCollection_Flag, BONE_COLLECTION_SOLO)
#ifdef __cplusplus

View File

@@ -389,8 +389,20 @@ static void rna_BoneCollection_is_visible_set(PointerRNA *ptr, const bool is_vis
static bool rna_BoneCollection_is_visible_effectively_get(PointerRNA *ptr)
{
const bArmature *arm = (bArmature *)ptr->owner_id;
const BoneCollection *bcoll = (BoneCollection *)ptr->data;
return bcoll->is_visible_with_ancestors();
return ANIM_armature_bonecoll_is_visible_effectively(arm, bcoll);
}
static void rna_BoneCollection_is_solo_set(PointerRNA *ptr, const bool is_solo)
{
bArmature *arm = (bArmature *)ptr->owner_id;
BoneCollection *bcoll = (BoneCollection *)ptr->data;
ANIM_armature_bonecoll_solo_set(arm, bcoll, is_solo);
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)
@@ -2018,6 +2030,14 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
"is no active collection");
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_BoneCollections_active_name_set");
prop = RNA_def_property(srna, "is_solo_active", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", ARM_BCOLL_SOLO_ACTIVE);
RNA_def_property_ui_text(
prop,
"Solo Active",
"Read-ony flag that indicates there is at least one bone collection marked as 'solo'");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
/* Armature.collections.new(...) */
func = RNA_def_function(srna, "new", "rna_BoneCollections_new");
RNA_def_function_ui_description(func, "Add a new empty bone collection to the armature");
@@ -2311,9 +2331,17 @@ static void rna_def_bonecollection(BlenderRNA *brna)
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");
"this bone collection and all of its ancestors are visible, or when it is marked as 'solo'");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "is_solo", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flags", BONE_COLLECTION_SOLO);
RNA_def_property_ui_text(
prop, "Solo", "Show only this bone collection, and others also marked as 'solo'");
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_boolean_funcs(prop, nullptr, "rna_BoneCollection_is_solo_set");
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);
RNA_def_property_ui_text(