Files
test/source/blender/animrig/intern/bone_collections.cc
Bastien Montagne f6a9f082e9 Core: Add data-level support for new 'system IDprops' storage for Blender 4.5 forward compatibility with 5.0 blendfiles
This mainly adds DNA level IDProp storage for system properties, their
handling in data management code, and the forward-versioning code
copying back content from system properties into 'all-in-one' single
IDProperties storage, for data types that will support both in Blender
5.0.

There is no user-facing changes expected here.

Part of #123232.

Pull Request: https://projects.blender.org/blender/blender/pulls/139257
2025-06-06 11:49:54 +02:00

1592 lines
56 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*/
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "DNA_armature_types.h"
#include "MEM_guardedalloc.h"
#include "BKE_animsys.h"
#include "BKE_idprop.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_override.hh"
#include "ANIM_armature_iter.hh"
#include "ANIM_bone_collections.hh"
#include "intern/bone_collections_internal.hh"
#include <cstring>
using std::strcmp;
using namespace blender::animrig;
namespace {
/** Default flags for new bone collections. */
constexpr eBoneCollection_Flag default_flags = BONE_COLLECTION_VISIBLE |
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') {
/* Use a default name if no name was given. */
name = DATA_(bonecoll_default_name);
}
/* NOTE: the collection name may change after the collection is added to an
* armature, to ensure it is unique within the armature. */
BoneCollection *bcoll = MEM_callocN<BoneCollection>(__func__);
STRNCPY_UTF8(bcoll->name, name);
bcoll->flags = default_flags;
return bcoll;
}
void ANIM_bonecoll_free(BoneCollection *bcoll, const bool do_id_user_count)
{
BLI_assert_msg(BLI_listbase_is_empty(&bcoll->bones),
"bone collection still has bones assigned to it, will cause dangling pointers in "
"bone runtime data");
if (bcoll->prop) {
IDP_FreeProperty_ex(bcoll->prop, do_id_user_count);
}
if (bcoll->system_properties) {
IDP_FreeProperty_ex(bcoll->system_properties, do_id_user_count);
}
MEM_delete(bcoll);
}
/**
* Construct the mapping from the bones to this collection.
*
* This assumes that the bones do not have such a pointer yet, i.e. calling this
* twice for the same bone collection will cause duplicate pointers. */
static void add_reverse_pointers(BoneCollection *bcoll)
{
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
BoneCollectionReference *ref = MEM_callocN<BoneCollectionReference>(__func__);
ref->bcoll = bcoll;
BLI_addtail(&member->bone->runtime.collections, ref);
}
}
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);
}
}
void ANIM_armature_runtime_free(bArmature *armature)
{
/* Free the bone-to-its-collections mapping. */
ANIM_armature_foreach_bone(&armature->bonebase,
[&](Bone *bone) { BLI_freelistN(&bone->runtime.collections); });
}
/**
* Ensure the bone collection's name is unique within the armature.
*
* This assumes that the bone collection has already been inserted into the array.
*/
static void bonecoll_ensure_name_unique(bArmature *armature, BoneCollection *bcoll)
{
/* Cannot capture armature & bcoll by reference in the lambda, as that would change its signature
* and no longer be compatible with BLI_uniquename_cb(). */
auto bonecoll_name_is_duplicate = [&](const blender::StringRef name) -> bool {
for (BoneCollection *bcoll_iter : armature->collections_span()) {
if (bcoll_iter != bcoll && bcoll_iter->name == name) {
return true;
}
}
return false;
};
BLI_uniquename_cb(bonecoll_name_is_duplicate,
DATA_(bonecoll_default_name),
'.',
bcoll->name,
sizeof(bcoll->name));
}
/**
* Inserts bcoll into armature's array of bone collections at index.
*
* NOTE: the specified index is where the given bone collection will end up.
* This means, for example, that for a collection array of length N, you can
* pass N as the index to append to the end.
*/
static void bonecoll_insert_at_index(bArmature *armature, BoneCollection *bcoll, const int index)
{
BLI_assert(index <= armature->collection_array_num);
armature->collection_array = reinterpret_cast<BoneCollection **>(
MEM_reallocN_id(armature->collection_array,
sizeof(BoneCollection *) * (armature->collection_array_num + 1),
__func__));
/* To keep the memory consistent, insert the new element at the end of the
* now-grown array, then rotate it into place. */
armature->collection_array[armature->collection_array_num] = bcoll;
armature->collection_array_num++;
const int rotate_count = armature->collection_array_num - index - 1;
internal::bonecolls_rotate_block(armature, index, rotate_count, +1);
if (armature->runtime.active_collection_index >= index) {
ANIM_armature_bonecoll_active_index_set(armature,
armature->runtime.active_collection_index + 1);
}
}
static void bonecoll_insert_as_root(bArmature *armature, BoneCollection *bcoll, int at_index)
{
BLI_assert(at_index >= -1);
BLI_assert(at_index <= armature->collection_root_count);
if (at_index < 0) {
at_index = armature->collection_root_count;
}
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,
BoneCollection *bcoll,
const int parent_index)
{
BLI_assert_msg(parent_index >= 0, "Armature bone collection index should be 0 or larger");
BLI_assert_msg(parent_index < armature->collection_array_num,
"Parent bone collection index should not point beyond the end of the array");
BoneCollection *parent = armature->collection_array[parent_index];
if (parent->child_index == 0) {
/* This parent doesn't have any children yet, so place them at the end of the array. */
parent->child_index = armature->collection_array_num;
}
const int insert_at_index = parent->child_index + parent->child_count;
bonecoll_insert_at_index(armature, bcoll, insert_at_index);
parent->child_count++;
ancestors_visible_update(armature, parent, bcoll);
return insert_at_index;
}
BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature,
const char *name,
const int parent_index)
{
BoneCollection *bcoll = ANIM_bonecoll_new(name);
if (!ID_IS_LINKED(&armature->id) && ID_IS_OVERRIDE_LIBRARY(&armature->id)) {
/* Mark this bone collection as local override, so that certain operations can be allowed. */
bcoll->flags |= BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL;
}
bonecoll_ensure_name_unique(armature, bcoll);
if (parent_index < 0) {
bonecoll_insert_as_root(armature, bcoll, -1);
}
else {
bonecoll_insert_as_child(armature, bcoll, parent_index);
}
/* Restore the active bone collection pointer, as its index might have changed. */
ANIM_armature_bonecoll_active_set(armature, armature->runtime.active_collection);
return bcoll;
}
/**
* Copy a BoneCollection to a new armature, updating its internal pointers to
* point to the new armature.
*
* This *only* updates the cloned BoneCollection, and does *not* actually add it
* to the armature.
*
* Child collections are not taken into account; the returned bone collection is
* without children, regardless of `bcoll_to_copy`.
*/
static BoneCollection *copy_and_update_ownership(const bArmature *armature_dst,
const BoneCollection *bcoll_to_copy)
{
BoneCollection *bcoll = static_cast<BoneCollection *>(MEM_dupallocN(bcoll_to_copy));
/* Reset the child_index and child_count properties. These are unreliable when
* coming from an override, as the original array might have been completely
* reshuffled. Children will have to be copied separately. */
bcoll->child_index = 0;
bcoll->child_count = 0;
if (bcoll->prop) {
bcoll->prop = IDP_CopyProperty_ex(bcoll_to_copy->prop,
0 /*do_id_user ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT*/);
}
if (bcoll->system_properties) {
bcoll->system_properties = IDP_CopyProperty_ex(
bcoll_to_copy->system_properties, 0 /*do_id_user ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT*/);
}
/* Remap the bone pointers to the given armature, as `bcoll_to_copy` is
* assumed to be owned by another armature. */
BLI_duplicatelist(&bcoll->bones, &bcoll->bones);
BLI_assert_msg(armature_dst->bonehash, "Expected armature bone hash to be there");
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
member->bone = BKE_armature_find_bone_name(const_cast<bArmature *>(armature_dst),
member->bone->name);
}
/* Now that the collection points to the right bones, these bones can be
* updated to point to this collection. */
add_reverse_pointers(bcoll);
return bcoll;
}
/**
* Copy all the child collections of the specified parent, from `armature_src` to `armature_dst`.
*
* This assumes that the parent itself has already been copied.
*/
static void liboverride_recursively_add_children(bArmature *armature_dst,
const bArmature *armature_src,
const int parent_bcoll_dst_index,
const BoneCollection *parent_bcoll_src)
{
BLI_assert_msg(parent_bcoll_dst_index >= 0,
"this function can only add children to another collection, it cannot add roots");
/* Iterate over the children in `armature_src`, and clone them one by one into `armature_dst`.
*
* This uses two loops. The first one adds all the children, the second loop iterates over those
* children for the recursion step. As this performs a "breadth-first insertion", it requires
* considerably less shuffling of the array as when the recursion was done immediately after
* inserting a child. */
BoneCollection *parent_bcoll_dst = armature_dst->collection_array[parent_bcoll_dst_index];
/* Big Fat Assumption: because this code runs as part of the library override system, it is
* assumed that the parent is either a newly added root, or another child that was also added by
* the liboverride system. Because this would never add a child to an original "sequence of
* siblings", insertions of children always happen at the end of the array. This means that
* `parent_bcoll_dst_index` remains constant during this entire function. */
/* Copy & insert all the children. */
for (int bcoll_src_index = parent_bcoll_src->child_index;
bcoll_src_index < parent_bcoll_src->child_index + parent_bcoll_src->child_count;
bcoll_src_index++)
{
const BoneCollection *bcoll_src = armature_src->collection_array[bcoll_src_index];
BoneCollection *bcoll_dst = copy_and_update_ownership(armature_dst, bcoll_src);
const int bcoll_index_dst = bonecoll_insert_as_child(
armature_dst, bcoll_dst, parent_bcoll_dst_index);
#ifndef NDEBUG
/* Check that the above Big Fat Assumption holds. */
BLI_assert_msg(bcoll_index_dst > parent_bcoll_dst_index,
"expecting children to be added to the array AFTER their parent");
#else
(void)bcoll_index_dst;
#endif
bonecoll_ensure_name_unique(armature_dst, bcoll_dst);
}
/* Double-check that the above Big Fat Assumption holds. */
#ifndef NDEBUG
const int new_parent_bcoll_dst_index = armature_bonecoll_find_index(armature_dst,
parent_bcoll_dst);
BLI_assert_msg(new_parent_bcoll_dst_index == parent_bcoll_dst_index,
"did not expect parent_bcoll_dst_index to change");
#endif
/* Recurse into the children to copy grandchildren. */
BLI_assert_msg(parent_bcoll_dst->child_count == parent_bcoll_src->child_count,
"all children should have been copied");
for (int child_num = 0; child_num < parent_bcoll_dst->child_count; child_num++) {
const int bcoll_src_index = parent_bcoll_src->child_index + child_num;
const int bcoll_dst_index = parent_bcoll_dst->child_index + child_num;
const BoneCollection *bcoll_src = armature_src->collection_array[bcoll_src_index];
liboverride_recursively_add_children(armature_dst, armature_src, bcoll_dst_index, bcoll_src);
}
}
BoneCollection *ANIM_armature_bonecoll_insert_copy_after(bArmature *armature_dst,
const bArmature *armature_src,
const BoneCollection *anchor_in_dst,
const BoneCollection *bcoll_to_copy)
{
#ifndef NDEBUG
/* Check that this bone collection is really a root, as this is assumed by the
* rest of this function. This is an O(n) check, though, so that's why it's
* only running in debug builds. */
const int bcoll_index_src = armature_bonecoll_find_index(armature_src, bcoll_to_copy);
if (!armature_bonecoll_is_root(armature_src, bcoll_index_src)) {
printf(
"Armature \"%s\" has library override operation that adds non-root bone collection "
"\"%s\". This is unexpected, please file a bug report.\n",
armature_src->id.name + 2,
bcoll_to_copy->name);
}
#endif
BoneCollection *bcoll = copy_and_update_ownership(armature_dst, bcoll_to_copy);
const int anchor_index = armature_bonecoll_find_index(armature_dst, anchor_in_dst);
const int bcoll_index = anchor_index + 1;
BLI_assert_msg(
bcoll_index <= armature_dst->collection_root_count,
"did not expect library override to add a child bone collection, only roots are expected");
bonecoll_insert_as_root(armature_dst, bcoll, bcoll_index);
bonecoll_ensure_name_unique(armature_dst, bcoll);
/* Library override operations are only constructed for the root bones. This means that handling
* this operation should also include copying the children. */
liboverride_recursively_add_children(armature_dst, armature_src, bcoll_index, bcoll_to_copy);
ANIM_armature_bonecoll_active_runtime_refresh(armature_dst);
return bcoll;
}
static void armature_bonecoll_active_clear(bArmature *armature)
{
armature->runtime.active_collection_index = -1;
armature->runtime.active_collection = nullptr;
armature->active_collection_name[0] = '\0';
}
void ANIM_armature_bonecoll_active_set(bArmature *armature, BoneCollection *bcoll)
{
if (bcoll == nullptr) {
armature_bonecoll_active_clear(armature);
return;
}
const int index = armature_bonecoll_find_index(armature, bcoll);
if (index == -1) {
/* TODO: print warning? Or just ignore this case? */
armature_bonecoll_active_clear(armature);
return;
}
STRNCPY(armature->active_collection_name, bcoll->name);
armature->runtime.active_collection_index = index;
armature->runtime.active_collection = bcoll;
}
void ANIM_armature_bonecoll_active_index_set(bArmature *armature, const int bone_collection_index)
{
if (bone_collection_index < 0 || bone_collection_index >= armature->collection_array_num) {
armature_bonecoll_active_clear(armature);
return;
}
BoneCollection *bcoll = armature->collection_array[bone_collection_index];
STRNCPY(armature->active_collection_name, bcoll->name);
armature->runtime.active_collection_index = bone_collection_index;
armature->runtime.active_collection = bcoll;
}
void ANIM_armature_bonecoll_active_name_set(bArmature *armature, const char *name)
{
BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(armature, name);
ANIM_armature_bonecoll_active_set(armature, bcoll);
}
void ANIM_armature_bonecoll_active_runtime_refresh(bArmature *armature)
{
const std::string_view active_name = armature->active_collection_name;
if (active_name.empty()) {
armature_bonecoll_active_clear(armature);
return;
}
int index = 0;
for (BoneCollection *bcoll : armature->collections_span()) {
if (bcoll->name == active_name) {
armature->runtime.active_collection_index = index;
armature->runtime.active_collection = bcoll;
return;
}
index++;
}
/* No bone collection with the name was found, so better to clear everything. */
armature_bonecoll_active_clear(armature);
}
bool ANIM_armature_bonecoll_is_editable(const bArmature *armature, const BoneCollection *bcoll)
{
const bool is_override = ID_IS_OVERRIDE_LIBRARY(armature);
if (ID_IS_LINKED(armature) && !is_override) {
return false;
}
if (is_override && BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
/* A system override is still not editable. */
return false;
}
if (is_override && (bcoll->flags & BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL) == 0) {
/* This particular collection was not added in the local override, so not editable. */
return false;
}
return true;
}
bool ANIM_armature_bonecoll_move_to_index(bArmature *armature,
const int from_index,
const int to_index)
{
if (from_index >= armature->collection_array_num || to_index >= armature->collection_array_num ||
from_index == to_index)
{
return false;
}
/* Only allow moving within the same parent. This is written a bit awkwardly to avoid two calls
* to `armature_bonecoll_find_parent_index()` as that is O(n) in the number of bone collections.
*/
const int parent_index = armature_bonecoll_find_parent_index(armature, from_index);
if (!armature_bonecoll_is_child_of(armature, parent_index, to_index)) {
return false;
}
if (parent_index < 0) {
/* Roots can just be moved around, as there is no `child_index` to update in this case. */
internal::bonecolls_move_to_index(armature, from_index, to_index);
return true;
}
/* Store the parent's child_index, as that might move if to_index is the first child
* (bonecolls_move_to_index() will keep it pointing at that first child). */
BoneCollection *parent_bcoll = armature->collection_array[parent_index];
const int old_parent_child_index = parent_bcoll->child_index;
internal::bonecolls_move_to_index(armature, from_index, to_index);
parent_bcoll->child_index = old_parent_child_index;
return true;
}
static int bonecoll_child_number(const bArmature *armature,
const int parent_bcoll_index,
const int bcoll_index)
{
if (parent_bcoll_index < 0) {
/* Root bone collections are always at the start of the array, and thus their index is the
* 'child number'. */
return bcoll_index;
}
const BoneCollection *parent_bcoll = armature->collection_array[parent_bcoll_index];
return bcoll_index - parent_bcoll->child_index;
}
int ANIM_armature_bonecoll_move_before_after_index(bArmature *armature,
const int from_index,
int to_index,
const MoveLocation before_after)
{
const int from_parent_index = armature_bonecoll_find_parent_index(armature, from_index);
const int to_parent_index = armature_bonecoll_find_parent_index(armature, to_index);
if (from_parent_index != to_parent_index) {
/* Moving between parents. */
int to_child_num = bonecoll_child_number(armature, to_parent_index, to_index);
if (before_after == MoveLocation::After) {
to_child_num++;
}
return armature_bonecoll_move_to_parent(
armature, from_index, to_child_num, from_parent_index, to_parent_index);
}
/* Moving between siblings. */
switch (before_after) {
case MoveLocation::Before:
if (to_index > from_index) {
/* Moving to the right, but needs to go before that one, so needs a decrement. */
to_index--;
}
break;
case MoveLocation::After:
if (to_index < from_index) {
/* Moving to the left, but needs to go after that one, so needs a decrement. */
to_index++;
}
break;
}
if (!ANIM_armature_bonecoll_move_to_index(armature, from_index, to_index)) {
return -1;
}
return to_index;
}
bool ANIM_armature_bonecoll_move(bArmature *armature, BoneCollection *bcoll, const int step)
{
if (bcoll == nullptr) {
return false;
}
const int bcoll_index = armature_bonecoll_find_index(armature, bcoll);
const int to_index = bcoll_index + step;
if (bcoll_index < 0 || to_index < 0 || to_index >= armature->collection_array_num) {
return false;
}
ANIM_armature_bonecoll_move_to_index(armature, bcoll_index, to_index);
return true;
}
void ANIM_armature_bonecoll_name_set(bArmature *armature, BoneCollection *bcoll, const char *name)
{
char old_name[sizeof(bcoll->name)];
STRNCPY(old_name, bcoll->name);
if (name[0] == '\0') {
/* Refuse to have nameless collections. The name of the active collection is stored in DNA, and
* an empty string means 'no active collection'. */
STRNCPY(bcoll->name, DATA_(bonecoll_default_name));
}
else {
STRNCPY_UTF8(bcoll->name, name);
}
bonecoll_ensure_name_unique(armature, bcoll);
/* Bone collections can be reached via .collections (4.0+) and .collections_all (4.1+).
* Animation data from 4.0 should have been versioned to only use `.collections_all`. */
BKE_animdata_fix_paths_rename_all(&armature->id, "collections", old_name, bcoll->name);
BKE_animdata_fix_paths_rename_all(&armature->id, "collections_all", old_name, bcoll->name);
}
void ANIM_armature_bonecoll_remove_from_index(bArmature *armature, int index)
{
BLI_assert(0 <= index && index < armature->collection_array_num);
BoneCollection *bcoll = armature->collection_array[index];
/* Get the active bone collection index before the armature is manipulated. */
const int active_collection_index = armature->runtime.active_collection_index;
/* The parent needs updating, so better to find it before this bone collection is removed. */
int parent_bcoll_index = armature_bonecoll_find_parent_index(armature, index);
BoneCollection *parent_bcoll = parent_bcoll_index >= 0 ?
armature->collection_array[parent_bcoll_index] :
nullptr;
/* Move all the children of the to-be-removed bone collection to their grandparent. */
int move_to_child_num = bonecoll_child_number(armature, parent_bcoll_index, index);
while (bcoll->child_count > 0) {
/* Move the child to its grandparent, at the same spot as the to-be-removed
* bone collection. The latter thus (potentially) shifts by 1 in the array.
* After removal, this effectively makes it appear like the removed bone
* collection is replaced by all its children. */
armature_bonecoll_move_to_parent(armature,
bcoll->child_index, /* Move from index... */
move_to_child_num, /* to this child number. */
index, /* From this parent... */
parent_bcoll_index /* to that parent. */
);
/* Both 'index' and 'parent_bcoll_index' can change each iteration. */
index = internal::bonecolls_find_index_near(armature, bcoll, index);
BLI_assert_msg(index >= 0, "could not find bone collection after moving things around");
if (parent_bcoll_index >= 0) { /* If there is no parent, its index should stay -1. */
parent_bcoll_index = internal::bonecolls_find_index_near(
armature, parent_bcoll, parent_bcoll_index);
BLI_assert_msg(parent_bcoll_index >= 0,
"could not find bone collection parent after moving things around");
}
move_to_child_num++;
}
/* Adjust the parent for the removal of its child. */
if (parent_bcoll_index < 0) {
/* Removing a root, so the armature itself needs to be updated. */
armature->collection_root_count--;
BLI_assert_msg(armature->collection_root_count >= 0, "armature root count cannot be negative");
}
else {
parent_bcoll->child_count--;
if (parent_bcoll->child_count == 0) {
parent_bcoll->child_index = 0;
}
}
/* Rotate the to-be-removed collection to the last array element. */
internal::bonecolls_move_to_index(armature, index, armature->collection_array_num - 1);
/* NOTE: we don't bother to shrink the allocation. It's okay if the
* capacity has extra space, because the number of valid items is tracked. */
armature->collection_array_num--;
armature->collection_array[armature->collection_array_num] = nullptr;
/* Update the active BoneCollection. */
if (active_collection_index >= 0) {
/* Default: select the next sibling.
* If there is none: select the previous sibling.
* If there is none: select the parent.
*/
if (armature_bonecoll_is_child_of(armature, parent_bcoll_index, active_collection_index)) {
/* active_collection_index still points to a sibling of the removed collection. */
ANIM_armature_bonecoll_active_index_set(armature, active_collection_index);
}
else if (active_collection_index > 0 &&
armature_bonecoll_is_child_of(
armature, parent_bcoll_index, active_collection_index - 1))
{
/* The child preceding active_collection_index is a sibling of the removed collection. */
ANIM_armature_bonecoll_active_index_set(armature, active_collection_index - 1);
}
else {
/* Select the parent, or nothing if this was a root collection. In that case, if there are no
* siblings either, this just means all bone collections have been removed. */
ANIM_armature_bonecoll_active_index_set(armature, parent_bcoll_index);
}
}
const bool is_solo = bcoll->is_solo();
internal::bonecoll_unassign_and_free(armature, bcoll);
if (is_solo) {
/* This might have been the last solo'ed bone collection, so check whether
* solo'ing should still be active on the armature. */
ANIM_armature_refresh_solo_active(armature);
}
}
void ANIM_armature_bonecoll_remove(bArmature *armature, BoneCollection *bcoll)
{
ANIM_armature_bonecoll_remove_from_index(armature,
armature_bonecoll_find_index(armature, bcoll));
}
template<typename MaybeConstBoneCollection>
static MaybeConstBoneCollection *bonecolls_get_by_name(
blender::Span<MaybeConstBoneCollection *> bonecolls, const char *name)
{
for (MaybeConstBoneCollection *bcoll : bonecolls) {
if (STREQ(bcoll->name, name)) {
return bcoll;
}
}
return nullptr;
}
BoneCollection *ANIM_armature_bonecoll_get_by_name(bArmature *armature, const char *name)
{
return bonecolls_get_by_name(armature->collections_span(), name);
}
int ANIM_armature_bonecoll_get_index_by_name(bArmature *armature, const char *name)
{
for (int index = 0; index < armature->collection_array_num; index++) {
const BoneCollection *bcoll = armature->collection_array[index];
if (STREQ(bcoll->name, name)) {
return index;
}
}
return -1;
}
/** Clear #BONE_COLLECTION_ANCESTORS_VISIBLE on all descendants of this bone collection. */
static void ancestors_visible_descendants_clear(bArmature *armature, BoneCollection *parent_bcoll)
{
for (BoneCollection *bcoll : armature->collection_children(parent_bcoll)) {
bcoll->flags &= ~BONE_COLLECTION_ANCESTORS_VISIBLE;
ancestors_visible_descendants_clear(armature, bcoll);
}
}
/** Set or clear #BONE_COLLECTION_ANCESTORS_VISIBLE on all descendants of this bone collection. */
static void ancestors_visible_descendants_update(bArmature *armature, BoneCollection *parent_bcoll)
{
if (!parent_bcoll->is_visible_with_ancestors()) {
/* 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 descendants. */
static void ancestors_visible_update(bArmature *armature,
const BoneCollection *parent_bcoll,
BoneCollection *bcoll)
{
if (parent_bcoll == nullptr || parent_bcoll->is_visible_with_ancestors()) {
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);
}
}
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();
}
void ANIM_armature_bonecoll_is_expanded_set(BoneCollection *bcoll, bool is_expanded)
{
if (is_expanded) {
bcoll->flags |= BONE_COLLECTION_EXPANDED;
}
else {
bcoll->flags &= ~BONE_COLLECTION_EXPANDED;
}
}
/* Store the bone's membership on the collection. */
static void add_membership(BoneCollection *bcoll, Bone *bone)
{
BoneCollectionMember *member = MEM_callocN<BoneCollectionMember>(__func__);
member->bone = bone;
BLI_addtail(&bcoll->bones, member);
}
/* Store reverse membership on the bone. */
static void add_reference(Bone *bone, BoneCollection *bcoll)
{
BoneCollectionReference *ref = MEM_callocN<BoneCollectionReference>(__func__);
ref->bcoll = bcoll;
BLI_addtail(&bone->runtime.collections, ref);
}
bool ANIM_armature_bonecoll_assign(BoneCollection *bcoll, Bone *bone)
{
/* Precondition check: bail out if already a member. */
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
if (member->bone == bone) {
return false;
}
}
add_membership(bcoll, bone);
add_reference(bone, bcoll);
return true;
}
bool ANIM_armature_bonecoll_assign_editbone(BoneCollection *bcoll, EditBone *ebone)
{
/* Precondition check: bail out if already a member. */
LISTBASE_FOREACH (BoneCollectionReference *, ref, &ebone->bone_collections) {
if (ref->bcoll == bcoll) {
return false;
}
}
/* Store membership on the edit bone. Bones will be rebuilt when the armature
* goes out of edit mode, and by then the newly created bones will be added to
* the actual collection on the Armature. */
BoneCollectionReference *ref = MEM_callocN<BoneCollectionReference>(__func__);
ref->bcoll = bcoll;
BLI_addtail(&ebone->bone_collections, ref);
return true;
}
bool ANIM_armature_bonecoll_assign_and_move(BoneCollection *bcoll, Bone *bone)
{
ANIM_armature_bonecoll_unassign_all(bone);
return ANIM_armature_bonecoll_assign(bcoll, bone);
}
bool ANIM_armature_bonecoll_assign_and_move_editbone(BoneCollection *bcoll, EditBone *ebone)
{
ANIM_armature_bonecoll_unassign_all_editbone(ebone);
return ANIM_armature_bonecoll_assign_editbone(bcoll, ebone);
}
bool ANIM_armature_bonecoll_unassign(BoneCollection *bcoll, Bone *bone)
{
bool was_found = false;
/* Remove membership from collection. */
LISTBASE_FOREACH_MUTABLE (BoneCollectionMember *, member, &bcoll->bones) {
if (member->bone == bone) {
BLI_freelinkN(&bcoll->bones, member);
was_found = true;
break;
}
}
/* Remove reverse membership from the bone.
* For data consistency sake, this is always done, regardless of whether the
* above loop found the membership. */
LISTBASE_FOREACH_MUTABLE (BoneCollectionReference *, ref, &bone->runtime.collections) {
if (ref->bcoll == bcoll) {
BLI_freelinkN(&bone->runtime.collections, ref);
break;
}
}
return was_found;
}
void ANIM_armature_bonecoll_unassign_all(Bone *bone)
{
LISTBASE_FOREACH_MUTABLE (BoneCollectionReference *, ref, &bone->runtime.collections) {
/* TODO: include Armature as parameter, and check that the bone collection to unassign from is
* actually editable. */
ANIM_armature_bonecoll_unassign(ref->bcoll, bone);
}
}
void ANIM_armature_bonecoll_unassign_all_editbone(EditBone *ebone)
{
LISTBASE_FOREACH_MUTABLE (BoneCollectionReference *, ref, &ebone->bone_collections) {
ANIM_armature_bonecoll_unassign_editbone(ref->bcoll, ebone);
}
}
bool ANIM_armature_bonecoll_unassign_editbone(BoneCollection *bcoll, EditBone *ebone)
{
bool was_found = false;
/* Edit bone membership is only stored on the edit bone itself. */
LISTBASE_FOREACH_MUTABLE (BoneCollectionReference *, ref, &ebone->bone_collections) {
if (ref->bcoll == bcoll) {
BLI_freelinkN(&ebone->bone_collections, ref);
was_found = true;
break;
}
}
return was_found;
}
void ANIM_armature_bonecoll_reconstruct(bArmature *armature)
{
/* Remove all the old collection memberships. */
for (BoneCollection *bcoll : armature->collections_span()) {
BLI_freelistN(&bcoll->bones);
}
/* For all bones, restore their collection memberships. */
ANIM_armature_foreach_bone(&armature->bonebase, [&](Bone *bone) {
LISTBASE_FOREACH (BoneCollectionReference *, ref, &bone->runtime.collections) {
add_membership(ref->bcoll, bone);
}
});
}
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)) {
return true;
}
LISTBASE_FOREACH (const BoneCollectionReference *, bcoll_ref, collection_refs) {
const BoneCollection *bcoll = bcoll_ref->bcoll;
if (ANIM_armature_bonecoll_is_visible_effectively(armature, bcoll)) {
return true;
}
}
return false;
}
bool ANIM_bone_in_visible_collection(const bArmature *armature, const Bone *bone)
{
return any_bone_collection_visible(armature, &bone->runtime.collections);
}
bool ANIM_bonecoll_is_visible_editbone(const bArmature *armature, const EditBone *ebone)
{
return any_bone_collection_visible(armature, &ebone->bone_collections);
}
void ANIM_armature_bonecoll_show_all(bArmature *armature)
{
for (BoneCollection *bcoll : armature->collections_span()) {
ANIM_bonecoll_show(armature, bcoll);
}
}
void ANIM_armature_bonecoll_hide_all(bArmature *armature)
{
for (BoneCollection *bcoll : armature->collections_span()) {
ANIM_bonecoll_hide(armature, bcoll);
}
}
/* ********************************* */
/* Armature Layers transitional API. */
void ANIM_armature_bonecoll_assign_active(const bArmature *armature, EditBone *ebone)
{
if (armature->runtime.active_collection == nullptr) {
/* No active collection, do not assign to any. */
return;
}
ANIM_armature_bonecoll_assign_editbone(armature->runtime.active_collection, ebone);
}
static bool bcoll_list_contains(const ListBase /*BoneCollectionRef*/ *collection_refs,
const BoneCollection *bcoll)
{
LISTBASE_FOREACH (const BoneCollectionReference *, bcoll_ref, collection_refs) {
if (bcoll == bcoll_ref->bcoll) {
return true;
}
}
return false;
}
bool ANIM_armature_bonecoll_contains_active_bone(const bArmature *armature,
const BoneCollection *bcoll)
{
if (armature->edbo) {
if (!armature->act_edbone) {
return false;
}
return bcoll_list_contains(&armature->act_edbone->bone_collections, bcoll);
}
if (!armature->act_bone) {
return false;
}
return bcoll_list_contains(&armature->act_bone->runtime.collections, bcoll);
}
void ANIM_armature_bonecoll_show_from_bone(bArmature *armature, const Bone *bone)
{
if (ANIM_bone_in_visible_collection(armature, bone)) {
return;
}
/* Making the first collection visible is enough to make the bone visible.
*
* Since bones without collection are considered visible,
* bone->runtime.collections.first is certainly a valid pointer. */
BoneCollectionReference *ref = static_cast<BoneCollectionReference *>(
bone->runtime.collections.first);
ref->bcoll->flags |= BONE_COLLECTION_VISIBLE;
}
void ANIM_armature_bonecoll_show_from_ebone(bArmature *armature, const EditBone *ebone)
{
if (ANIM_bonecoll_is_visible_editbone(armature, ebone)) {
return;
}
/* Making the first collection visible is enough to make the bone visible.
*
* Since bones without collection are considered visible,
* ebone->bone_collections.first is certainly a valid pointer. */
BoneCollectionReference *ref = static_cast<BoneCollectionReference *>(
ebone->bone_collections.first);
ref->bcoll->flags |= BONE_COLLECTION_VISIBLE;
}
void ANIM_armature_bonecoll_show_from_pchan(bArmature *armature, const bPoseChannel *pchan)
{
ANIM_armature_bonecoll_show_from_bone(armature, pchan->bone);
}
/* ********* */
/* C++ only. */
namespace blender::animrig {
int armature_bonecoll_find_index(const bArmature *armature, const BoneCollection *bcoll)
{
int index = 0;
for (const BoneCollection *arm_bcoll : armature->collections_span()) {
if (arm_bcoll == bcoll) {
return index;
}
index++;
}
return -1;
}
int armature_bonecoll_find_parent_index(const bArmature *armature, const int bcoll_index)
{
if (bcoll_index < armature->collection_root_count) {
/* Don't bother iterating all collections when it's known to be a root. */
return -1;
}
int index = 0;
for (const BoneCollection *potential_parent : armature->collections_span()) {
if (potential_parent->child_index <= bcoll_index &&
bcoll_index < potential_parent->child_index + potential_parent->child_count)
{
return index;
}
index++;
}
return -1;
}
int armature_bonecoll_child_number_find(const bArmature *armature, const ::BoneCollection *bcoll)
{
const int bcoll_index = armature_bonecoll_find_index(armature, bcoll);
const int parent_index = armature_bonecoll_find_parent_index(armature, bcoll_index);
return bonecoll_child_number(armature, parent_index, bcoll_index);
}
int armature_bonecoll_child_number_set(bArmature *armature,
::BoneCollection *bcoll,
int new_child_number)
{
const int bcoll_index = armature_bonecoll_find_index(armature, bcoll);
const int parent_index = armature_bonecoll_find_parent_index(armature, bcoll_index);
BoneCollection fake_armature_parent = {};
fake_armature_parent.child_count = armature->collection_root_count;
BoneCollection *parent_bcoll;
if (parent_index < 0) {
parent_bcoll = &fake_armature_parent;
}
else {
parent_bcoll = armature->collection_array[parent_index];
}
/* Bounds checks. */
if (new_child_number >= parent_bcoll->child_count) {
return -1;
}
if (new_child_number < 0) {
new_child_number = parent_bcoll->child_count - 1;
}
/* Store the parent's child_index, as that might move if to_index is the first child
* (bonecolls_move_to_index() will keep it pointing at that first child). */
const int old_parent_child_index = parent_bcoll->child_index;
const int to_index = parent_bcoll->child_index + new_child_number;
internal::bonecolls_move_to_index(armature, bcoll_index, to_index);
parent_bcoll->child_index = old_parent_child_index;
/* Make sure that if this was the active bone collection, its index also changes. */
if (armature->runtime.active_collection_index == bcoll_index) {
ANIM_armature_bonecoll_active_index_set(armature, to_index);
}
return to_index;
}
bool armature_bonecoll_is_root(const bArmature *armature, const int bcoll_index)
{
BLI_assert(bcoll_index >= 0);
return bcoll_index < armature->collection_root_count;
}
bool armature_bonecoll_is_child_of(const bArmature *armature,
const int potential_parent_index,
const int potential_child_index)
{
/* Check for roots, before we try and access collection_array[-1]. */
if (armature_bonecoll_is_root(armature, potential_child_index)) {
return potential_parent_index == -1;
}
if (potential_parent_index < 0) {
return false;
}
const BoneCollection *potential_parent = armature->collection_array[potential_parent_index];
const int upper_bound = potential_parent->child_index + potential_parent->child_count;
return potential_parent->child_index <= potential_child_index &&
potential_child_index < upper_bound;
}
bool armature_bonecoll_is_descendant_of(const bArmature *armature,
const int potential_parent_index,
const int potential_descendant_index)
{
BLI_assert_msg(potential_descendant_index >= 0,
"Potential descendant has to exist for this function call to make sense.");
if (armature_bonecoll_is_child_of(armature, potential_parent_index, potential_descendant_index))
{
/* Found a direct child. */
return true;
}
const BoneCollection *potential_parent = armature->collection_array[potential_parent_index];
const int upper_bound = potential_parent->child_index + potential_parent->child_count;
for (int visit_index = potential_parent->child_index; visit_index < upper_bound; visit_index++) {
if (armature_bonecoll_is_descendant_of(armature, visit_index, potential_descendant_index)) {
return true;
}
}
return false;
}
bool bonecoll_has_children(const BoneCollection *bcoll)
{
return bcoll->child_count > 0;
}
void bonecolls_copy_expanded_flag(Span<BoneCollection *> bcolls_dest,
Span<const BoneCollection *> bcolls_source)
{
/* Try to preserve the bone collection expanded/collapsed states. These are UI
* changes that shouldn't impact undo steps. Care has to be taken to match the
* old and the new bone collections, though, as they may have been reordered
* or renamed.
*
* Reordering is handled by looking up collections by name.
* Renames are handled by skipping those that cannot be found by name. */
auto find_old = [bcolls_source](const char *name, const int index) -> const BoneCollection * {
/* Only check index when it's valid in the old armature. */
if (index < bcolls_source.size()) {
const BoneCollection *bcoll = bcolls_source[index];
if (STREQ(bcoll->name, name)) {
/* Index and name matches, let's use */
return bcoll;
}
}
/* Try to find by name as a last resort. This function only works with
* non-const pointers, hence the const_cast. */
const BoneCollection *bcoll = bonecolls_get_by_name(bcolls_source, name);
return bcoll;
};
for (int i = 0; i < bcolls_dest.size(); i++) {
BoneCollection *bcoll_new = bcolls_dest[i];
const BoneCollection *bcoll_old = find_old(bcoll_new->name, i);
if (!bcoll_old) {
continue;
}
ANIM_armature_bonecoll_is_expanded_set(bcoll_new, bcoll_old->is_expanded());
}
}
int armature_bonecoll_move_to_parent(bArmature *armature,
const int from_bcoll_index,
int to_child_num,
const int from_parent_index,
const int to_parent_index)
{
BLI_assert(0 <= from_bcoll_index && from_bcoll_index < armature->collection_array_num);
BLI_assert(-1 <= from_parent_index && from_parent_index < armature->collection_array_num);
BLI_assert(-1 <= to_parent_index && to_parent_index < armature->collection_array_num);
if (from_parent_index == to_parent_index) {
/* TODO: use `to_child_num` to still move the child to the desired position. */
return from_bcoll_index;
}
/* The Armature itself acts like some sort of 'parent' for the root collections. By having this
* as a 'fake' BoneCollection, all the code below can just be blissfully unaware of the special
* 'all root collections should be at the start of the array' rule. */
BoneCollection armature_root;
armature_root.child_count = armature->collection_root_count;
armature_root.child_index = 0;
armature_root.flags = default_flags;
BoneCollection *from_parent = from_parent_index >= 0 ?
armature->collection_array[from_parent_index] :
&armature_root;
BoneCollection *to_parent = to_parent_index >= 0 ? armature->collection_array[to_parent_index] :
&armature_root;
BLI_assert_msg(-1 <= to_child_num && to_child_num <= to_parent->child_count,
"to_child_num must point to an index of a child of the new parent, or the index "
"of the last child + 1, or be -1 to indicate 'after last child'");
if (to_child_num < 0) {
to_child_num = to_parent->child_count;
}
/* The new parent might not have children yet. */
int to_bcoll_index;
if (to_parent->child_count == 0) {
/* New parents always get their children at the end of the array. */
to_bcoll_index = armature->collection_array_num - 1;
}
else {
to_bcoll_index = to_parent->child_index + to_child_num;
/* Check whether the new parent's children are to the left or right of bcoll_index.
* This determines which direction the collections have to shift, and thus which index to
* move the bcoll to. */
if (to_bcoll_index > from_bcoll_index) {
to_bcoll_index--;
}
}
/* In certain cases the 'from_parent' gets its first child removed, and needs to have its
* child_index incremented. This needs to be done by comparing these fields before the actual
* move happens (as that could also change the child_index). */
const bool needs_post_move_child_index_bump = from_parent->child_index == from_bcoll_index &&
to_bcoll_index <= from_bcoll_index;
/* bonecolls_move_to_index() will try and keep the hierarchy correct, and thus change
* to_parent->child_index to keep pointing to its current-first child. */
const bool becomes_new_first_child = to_child_num == 0 || to_parent->child_count == 0;
internal::bonecolls_move_to_index(armature, from_bcoll_index, to_bcoll_index);
/* Update child index & count of the old parent. */
from_parent->child_count--;
if (from_parent->child_count == 0) {
/* Clean up the child index when the parent has no more children. */
from_parent->child_index = 0;
}
else if (needs_post_move_child_index_bump) {
/* The start of the block of children of the old parent has moved, because
* we took out the first child. This only needs to be compensated for when
* moving it to the left (or staying put), as then its old siblings stay in
* place.
*
* This only needs to be done if there are any children left, though. */
from_parent->child_index++;
}
/* Update child index & count of the new parent. */
if (becomes_new_first_child) {
to_parent->child_index = to_bcoll_index;
}
to_parent->child_count++;
/* Copy the information from the 'fake' BoneCollection back to the 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;
}
/* Utility functions for Armature edit-mode undo. */
blender::Map<BoneCollection *, BoneCollection *> ANIM_bonecoll_array_copy_no_membership(
BoneCollection ***bcoll_array_dst,
int *bcoll_array_dst_num,
BoneCollection **bcoll_array_src,
const int bcoll_array_src_num,
const bool do_id_user)
{
BLI_assert(*bcoll_array_dst == nullptr);
BLI_assert(*bcoll_array_dst_num == 0);
*bcoll_array_dst = MEM_malloc_arrayN<BoneCollection *>(bcoll_array_src_num, __func__);
*bcoll_array_dst_num = bcoll_array_src_num;
blender::Map<BoneCollection *, BoneCollection *> bcoll_map{};
for (int i = 0; i < bcoll_array_src_num; i++) {
BoneCollection *bcoll_src = bcoll_array_src[i];
BoneCollection *bcoll_dst = static_cast<BoneCollection *>(MEM_dupallocN(bcoll_src));
/* This will be rebuilt from the edit bones, so we don't need to copy it. */
BLI_listbase_clear(&bcoll_dst->bones);
if (bcoll_src->prop) {
bcoll_dst->prop = IDP_CopyProperty_ex(bcoll_src->prop,
do_id_user ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT);
}
if (bcoll_src->system_properties) {
bcoll_dst->system_properties = IDP_CopyProperty_ex(
bcoll_src->system_properties, do_id_user ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT);
}
(*bcoll_array_dst)[i] = bcoll_dst;
bcoll_map.add(bcoll_src, bcoll_dst);
}
return bcoll_map;
}
void ANIM_bonecoll_array_free(BoneCollection ***bcoll_array,
int *bcoll_array_num,
const bool do_id_user)
{
for (int i = 0; i < *bcoll_array_num; i++) {
BoneCollection *bcoll = (*bcoll_array)[i];
if (bcoll->prop) {
IDP_FreeProperty_ex(bcoll->prop, do_id_user);
}
if (bcoll->system_properties) {
IDP_FreeProperty_ex(bcoll->system_properties, do_id_user);
}
/* This will usually already be empty, because the passed BoneCollection
* list is usually from ANIM_bonecoll_listbase_copy_no_membership().
* However, during undo this is also used to free the BoneCollection
* list on the Armature itself before copying over the undo BoneCollection
* list, in which case this of Bone pointers may not be empty. */
BLI_freelistN(&bcoll->bones);
MEM_freeN(bcoll);
}
MEM_SAFE_FREE(*bcoll_array);
*bcoll_array_num = 0;
}
/** Functions declared in bone_collections_internal.hh. */
namespace internal {
void bonecolls_rotate_block(bArmature *armature,
const int start_index,
const int count,
const int direction)
{
BLI_assert_msg(direction == 1 || direction == -1, "`direction` must be either -1 or +1");
if (count == 0) {
return;
}
/* When the block [start_index:start_index+count] is moved, it causes a duplication of one
* element and overwrites another element. For example: given an array [0, 1, 2, 3, 4], moving
* indices [1, 2] by +1 would result in one double element (1) and one missing element (3): [0,
* 1, 1, 2, 4].
*
* This is resolved by moving that element to the other side of the block, so the result will be
* [0, 3, 1, 2, 4]. This breaks the hierarchical information, so it's up to the caller to update
* this one moved element.
*/
const int move_from_index = (direction > 0 ? start_index + count : start_index - 1);
const int move_to_index = (direction > 0 ? start_index : start_index + count - 1);
BoneCollection *bcoll_to_move = armature->collection_array[move_from_index];
BoneCollection **start = armature->collection_array + start_index;
memmove((void *)(start + direction), (void *)start, count * sizeof(BoneCollection *));
armature->collection_array[move_to_index] = bcoll_to_move;
/* Update all child indices that reference something in the moved block. */
for (BoneCollection *bcoll : armature->collections_span()) {
/* Having both child_index and child_count zeroed out just means "no children"; these shouldn't
* be updated at all, as here child_index is not really referencing the element at index 0. */
if (bcoll->child_index == 0 && bcoll->child_count == 0) {
continue;
}
/* Compare to the original start & end of the block (i.e. pre-move). If a
* child_index is within this range, it'll need updating. */
if (start_index <= bcoll->child_index && bcoll->child_index < start_index + count) {
bcoll->child_index += direction;
}
}
/* Make sure the active bone collection index is moved as well. */
const int active_index = armature->runtime.active_collection_index;
if (active_index == move_from_index) {
armature->runtime.active_collection_index = move_to_index;
}
else if (start_index <= active_index && active_index < start_index + count) {
armature->runtime.active_collection_index += direction;
}
}
void bonecolls_move_to_index(bArmature *armature, const int from_index, const int to_index)
{
if (from_index == to_index) {
return;
}
BLI_assert(0 <= from_index);
BLI_assert(from_index < armature->collection_array_num);
BLI_assert(0 <= to_index);
BLI_assert(to_index < armature->collection_array_num);
if (from_index < to_index) {
const int block_start_index = from_index + 1;
const int block_count = to_index - from_index;
bonecolls_rotate_block(armature, block_start_index, block_count, -1);
}
else {
const int block_start_index = to_index;
const int block_count = from_index - to_index;
bonecolls_rotate_block(armature, block_start_index, block_count, +1);
}
}
int bonecolls_find_index_near(bArmature *armature, BoneCollection *bcoll, const int index)
{
BoneCollection **collections = armature->collection_array;
if (collections[index] == bcoll) {
return index;
}
if (index > 0 && collections[index - 1] == bcoll) {
return index - 1;
}
if (index < armature->collection_array_num - 1 && collections[index + 1] == bcoll) {
return index + 1;
}
return -1;
}
void bonecolls_debug_list(const bArmature *armature)
{
printf("\033[38;5;214mBone collections of armature \"%s\":\033[0m\n", armature->id.name + 2);
constexpr int root_ansi_color = 95;
printf(
" - \033[%dmroot\033[0m count: %d\n", root_ansi_color, armature->collection_root_count);
for (int i = 0; i < armature->collection_array_num; ++i) {
const BoneCollection *bcoll = armature->collection_array[i];
printf(" - \033[%dmcolls[%d] = %24s\033[0m ",
i < armature->collection_root_count ? root_ansi_color : 0,
i,
bcoll->name);
if (bcoll->child_index == 0 && bcoll->child_count == 0) {
printf("(leaf)");
}
else {
printf("(child index: %d, count: %d)", bcoll->child_index, bcoll->child_count);
}
printf("\n");
}
}
void bonecoll_unassign_and_free(bArmature *armature, BoneCollection *bcoll)
{
/* Remove bone membership. */
LISTBASE_FOREACH_MUTABLE (BoneCollectionMember *, member, &bcoll->bones) {
ANIM_armature_bonecoll_unassign(bcoll, member->bone);
}
if (armature->edbo) {
LISTBASE_FOREACH (EditBone *, ebone, armature->edbo) {
ANIM_armature_bonecoll_unassign_editbone(bcoll, ebone);
}
}
ANIM_bonecoll_free(bcoll);
}
} // namespace internal
} // namespace blender::animrig