Anim: make bone collections hierarchical
Make it possible to nest bone collections. The data structure on the armature is still a flat array. It is organised as follows: - Sibling collections (i.e. ones with the same parent) are stored sequentially in the array. - Each bone collection keep track of the number of children, and the index of the first child. - Root collections (i.e. ones without parent) are stored as the first elements in the array. - The number of root collections is stored on the Armature. This commit also contains the following: - Replaced the flat UIList of bone collections with a tree view. - Updated the M/Shift+M operators (move/assign to collection) to work with hierarchical bone collections. - Updated RNA interface to expose only root collections at `armature.collections`. All collections are available on `armature.collections.all`, and children at `bonecollection.children`. - Library override support. Only new roots + their subtrees can be added via overrides. See https://projects.blender.org/blender/blender/issues/115934 Co-authored with @nathanvegdahl and @nrupsis. Pull Request: https://projects.blender.org/blender/blender/pulls/115945
This commit is contained in:
parent
423ddac25f
commit
129fb2eab8
|
@ -4839,7 +4839,7 @@ def km_pose(params):
|
||||||
op_menu("VIEW3D_MT_bone_options_enable", {"type": 'W', "value": 'PRESS', "shift": True, "ctrl": True}),
|
op_menu("VIEW3D_MT_bone_options_enable", {"type": 'W', "value": 'PRESS', "shift": True, "ctrl": True}),
|
||||||
op_menu("VIEW3D_MT_bone_options_disable", {"type": 'W', "value": 'PRESS', "alt": True}),
|
op_menu("VIEW3D_MT_bone_options_disable", {"type": 'W', "value": 'PRESS', "alt": True}),
|
||||||
("armature.collection_show_all", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True}, None),
|
("armature.collection_show_all", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True}, None),
|
||||||
op_menu("VIEW3D_MT_bone_collections", {"type": 'M', "value": 'PRESS', "shift": True}),
|
("armature.assign_to_collection", {"type": 'M', "value": 'PRESS', "shift": True}, None),
|
||||||
("armature.move_to_collection", {"type": 'M', "value": 'PRESS'}, None),
|
("armature.move_to_collection", {"type": 'M', "value": 'PRESS'}, None),
|
||||||
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
|
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
|
||||||
("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None),
|
("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None),
|
||||||
|
@ -5779,7 +5779,7 @@ def km_edit_armature(params):
|
||||||
op_menu("VIEW3D_MT_bone_options_disable", {"type": 'W', "value": 'PRESS', "alt": True}),
|
op_menu("VIEW3D_MT_bone_options_disable", {"type": 'W', "value": 'PRESS', "alt": True}),
|
||||||
# Armature/bone layers.
|
# Armature/bone layers.
|
||||||
("armature.collection_show_all", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True}, None),
|
("armature.collection_show_all", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True}, None),
|
||||||
op_menu("VIEW3D_MT_bone_collections", {"type": 'M', "value": 'PRESS', "shift": True}),
|
("armature.assign_to_collection", {"type": 'M', "value": 'PRESS', "shift": True}, None),
|
||||||
("armature.move_to_collection", {"type": 'M', "value": 'PRESS'}, None),
|
("armature.move_to_collection", {"type": 'M', "value": 'PRESS'}, None),
|
||||||
# Special transforms.
|
# Special transforms.
|
||||||
op_tool_optional(
|
op_tool_optional(
|
||||||
|
|
|
@ -113,20 +113,7 @@ class DATA_PT_bone_collections(ArmatureButtonsPanel, Panel):
|
||||||
active_bcoll = arm.collections.active
|
active_bcoll = arm.collections.active
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
|
row.template_bone_collection_tree()
|
||||||
rows = 1
|
|
||||||
if active_bcoll:
|
|
||||||
rows = 4
|
|
||||||
|
|
||||||
row.template_list(
|
|
||||||
"DATA_UL_bone_collections",
|
|
||||||
"collections",
|
|
||||||
arm,
|
|
||||||
"collections",
|
|
||||||
arm.collections,
|
|
||||||
"active_index",
|
|
||||||
rows=rows,
|
|
||||||
)
|
|
||||||
|
|
||||||
col = row.column(align=True)
|
col = row.column(align=True)
|
||||||
col.operator("armature.collection_add", icon='ADD', text="")
|
col.operator("armature.collection_add", icon='ADD', text="")
|
||||||
|
@ -164,6 +151,35 @@ class ARMATURE_MT_collection_context_menu(Menu):
|
||||||
layout.operator("armature.collection_show_all")
|
layout.operator("armature.collection_show_all")
|
||||||
|
|
||||||
|
|
||||||
|
class ARMATURE_MT_collection_tree_context_menu(Menu):
|
||||||
|
bl_label = "Bone Collections"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
arm = context.armature
|
||||||
|
|
||||||
|
props = layout.operator(
|
||||||
|
"armature.collection_add", text="Add Child Collection"
|
||||||
|
)
|
||||||
|
props.parent_index = arm.collections.active_index
|
||||||
|
layout.operator("armature.collection_remove")
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
layout.operator("armature.collection_solo_visibility")
|
||||||
|
layout.operator("armature.collection_show_all")
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
layout.operator("armature.collection_assign", text="Assign Selected Bones")
|
||||||
|
layout.operator("armature.collection_unassign", text="Remove Selected Bones")
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
layout.operator("armature.collection_select", text="Select Bones")
|
||||||
|
layout.operator("armature.collection_deselect", text="Deselect Bones")
|
||||||
|
|
||||||
|
|
||||||
class DATA_PT_iksolver_itasc(ArmatureButtonsPanel, Panel):
|
class DATA_PT_iksolver_itasc(ArmatureButtonsPanel, Panel):
|
||||||
bl_label = "Inverse Kinematics"
|
bl_label = "Inverse Kinematics"
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
@ -291,6 +307,7 @@ classes = (
|
||||||
DATA_PT_bone_collections,
|
DATA_PT_bone_collections,
|
||||||
DATA_UL_bone_collections,
|
DATA_UL_bone_collections,
|
||||||
ARMATURE_MT_collection_context_menu,
|
ARMATURE_MT_collection_context_menu,
|
||||||
|
ARMATURE_MT_collection_tree_context_menu,
|
||||||
DATA_PT_motion_paths,
|
DATA_PT_motion_paths,
|
||||||
DATA_PT_motion_paths_display,
|
DATA_PT_motion_paths_display,
|
||||||
DATA_PT_display,
|
DATA_PT_display,
|
||||||
|
|
|
@ -3984,7 +3984,6 @@ class VIEW3D_MT_pose(Menu):
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
|
||||||
layout.menu("VIEW3D_MT_pose_motion")
|
layout.menu("VIEW3D_MT_pose_motion")
|
||||||
layout.operator("armature.move_to_collection", text="Move to Bone Collection")
|
|
||||||
layout.menu("VIEW3D_MT_bone_collections")
|
layout.menu("VIEW3D_MT_bone_collections")
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
@ -4078,46 +4077,12 @@ class VIEW3D_MT_bone_collections(Menu):
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
if not context.selected_pose_bones and not context.selected_bones:
|
layout.operator("armature.move_to_collection")
|
||||||
# If there are no bones to assign to any collection, there's no need
|
layout.operator("armature.assign_to_collection")
|
||||||
# to go over all the bone collections & try to build up the menu.
|
|
||||||
#
|
layout.separator()
|
||||||
# The poll function shouldn't test for this, because returning False
|
|
||||||
# there will hide this menu completely from the Pose menu, and
|
|
||||||
# that's going too far.
|
|
||||||
layout.enabled = False
|
|
||||||
layout.label(text="- select bones to operate on first -")
|
|
||||||
return
|
|
||||||
|
|
||||||
layout.operator("armature.collection_show_all")
|
layout.operator("armature.collection_show_all")
|
||||||
layout.separator()
|
|
||||||
|
|
||||||
arm = context.object.data
|
|
||||||
bone = context.active_bone
|
|
||||||
|
|
||||||
found_editable_bcoll = False
|
|
||||||
for bcoll in arm.collections:
|
|
||||||
if not bcoll.is_editable:
|
|
||||||
continue
|
|
||||||
found_editable_bcoll = True
|
|
||||||
|
|
||||||
if bcoll.name in bone.collections:
|
|
||||||
props = layout.operator("armature.collection_unassign",
|
|
||||||
text=bcoll.name,
|
|
||||||
icon='REMOVE')
|
|
||||||
else:
|
|
||||||
props = layout.operator("armature.collection_assign",
|
|
||||||
text=bcoll.name,
|
|
||||||
icon='ADD')
|
|
||||||
props.name = bcoll.name
|
|
||||||
|
|
||||||
if arm.collections and not found_editable_bcoll:
|
|
||||||
row = layout.row()
|
|
||||||
row.enabled = False
|
|
||||||
row.label(text="All bone collections are read-only")
|
|
||||||
|
|
||||||
layout.separator()
|
|
||||||
|
|
||||||
props = layout.operator("armature.collection_create_and_assign",
|
props = layout.operator("armature.collection_create_and_assign",
|
||||||
text="Assign to New Collection")
|
text="Assign to New Collection")
|
||||||
props.name = "New Collection"
|
props.name = "New Collection"
|
||||||
|
|
|
@ -68,9 +68,14 @@ void ANIM_armature_runtime_free(struct bArmature *armature);
|
||||||
/**
|
/**
|
||||||
* Add a new bone collection to the given armature.
|
* Add a new bone collection to the given armature.
|
||||||
*
|
*
|
||||||
|
* \param parent_index Index into the Armature's `collections_array`. -1 adds it
|
||||||
|
* as a root (i.e. parentless) collection.
|
||||||
|
*
|
||||||
* The Armature owns the returned pointer.
|
* The Armature owns the returned pointer.
|
||||||
*/
|
*/
|
||||||
BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature, const char *name);
|
BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature,
|
||||||
|
const char *name,
|
||||||
|
int parent_index = -1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a bone collection to the Armature.
|
* Add a bone collection to the Armature.
|
||||||
|
@ -81,10 +86,10 @@ BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature, const char *name
|
||||||
* NOTE: this should not typically be used. It is only used by the library overrides system to
|
* NOTE: this should not typically be used. It is only used by the library overrides system to
|
||||||
* apply override operations.
|
* apply override operations.
|
||||||
*/
|
*/
|
||||||
struct BoneCollection *ANIM_armature_bonecoll_insert_copy_after(
|
BoneCollection *ANIM_armature_bonecoll_insert_copy_after(bArmature *armature_dst,
|
||||||
struct bArmature *armature,
|
const bArmature *armature_src,
|
||||||
struct BoneCollection *anchor,
|
const BoneCollection *anchor_in_dst,
|
||||||
const struct BoneCollection *bcoll_to_copy);
|
const BoneCollection *bcoll_to_copy);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the bone collection at `index` from the armature.
|
* Remove the bone collection at `index` from the armature.
|
||||||
|
@ -153,6 +158,10 @@ bool ANIM_armature_bonecoll_is_editable(const struct bArmature *armature,
|
||||||
* \return true if the collection was successfully moved, false otherwise.
|
* \return true if the collection was successfully moved, false otherwise.
|
||||||
* The latter happens if either index is out of bounds, or if the indices
|
* The latter happens if either index is out of bounds, or if the indices
|
||||||
* are equal.
|
* are equal.
|
||||||
|
*
|
||||||
|
* \note This function is limited to moving between siblings of the bone
|
||||||
|
* collection at `from_index`.
|
||||||
|
*
|
||||||
* \note This function ensures that the element at index `from_index` (before
|
* \note This function ensures that the element at index `from_index` (before
|
||||||
* the call) will end up at `to_index` (after the call). The element at
|
* the call) will end up at `to_index` (after the call). The element at
|
||||||
* `to_index` before the call will shift towards `from_index`; in other words,
|
* `to_index` before the call will shift towards `from_index`; in other words,
|
||||||
|
@ -160,13 +169,32 @@ bool ANIM_armature_bonecoll_is_editable(const struct bArmature *armature,
|
||||||
* before or after that one.
|
* before or after that one.
|
||||||
*
|
*
|
||||||
* TODO: add ASCII-art illustration of left & right movement.
|
* TODO: add ASCII-art illustration of left & right movement.
|
||||||
|
*
|
||||||
|
* \see blender::animrig::armature_bonecoll_move_to_parent() to move bone
|
||||||
|
* collections between different parents.
|
||||||
*/
|
*/
|
||||||
bool ANIM_armature_bonecoll_move_to_index(bArmature *armature, int from_index, int to_index);
|
bool ANIM_armature_bonecoll_move_to_index(bArmature *armature, int from_index, int to_index);
|
||||||
|
|
||||||
|
enum class MoveLocation {
|
||||||
|
Before, /* Move to before the item at the given index. */
|
||||||
|
After, /* Move to after the item at the given index. */
|
||||||
|
};
|
||||||
|
|
||||||
|
int ANIM_armature_bonecoll_move_before_after_index(bArmature *armature,
|
||||||
|
int from_index,
|
||||||
|
int to_index,
|
||||||
|
MoveLocation before_after);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the bone collection by \a step places up/down.
|
* Move the bone collection by \a step places up/down.
|
||||||
*
|
*
|
||||||
* \return whether the move actually happened.
|
* \return whether the move actually happened.
|
||||||
|
*
|
||||||
|
* \note This function is limited to moving between siblings of the bone
|
||||||
|
* collection at `from_index`.
|
||||||
|
*
|
||||||
|
* \see blender::animrig::armature_bonecoll_move_to_parent() to move bone
|
||||||
|
* collections between different parents.
|
||||||
*/
|
*/
|
||||||
bool ANIM_armature_bonecoll_move(struct bArmature *armature,
|
bool ANIM_armature_bonecoll_move(struct bArmature *armature,
|
||||||
struct BoneCollection *bcoll,
|
struct BoneCollection *bcoll,
|
||||||
|
@ -175,6 +203,13 @@ bool ANIM_armature_bonecoll_move(struct bArmature *armature,
|
||||||
struct BoneCollection *ANIM_armature_bonecoll_get_by_name(
|
struct BoneCollection *ANIM_armature_bonecoll_get_by_name(
|
||||||
struct bArmature *armature, const char *name) ATTR_WARN_UNUSED_RESULT;
|
struct bArmature *armature, const char *name) ATTR_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
/** Scan the bone collections to find the one with the given name.
|
||||||
|
*
|
||||||
|
* \return the index of the bone collection, or -1 if not found.
|
||||||
|
*/
|
||||||
|
int ANIM_armature_bonecoll_get_index_by_name(struct bArmature *armature,
|
||||||
|
const char *name) ATTR_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
void ANIM_armature_bonecoll_name_set(struct bArmature *armature,
|
void ANIM_armature_bonecoll_name_set(struct bArmature *armature,
|
||||||
struct BoneCollection *bcoll,
|
struct BoneCollection *bcoll,
|
||||||
const char *name);
|
const char *name);
|
||||||
|
@ -205,6 +240,12 @@ void ANIM_armature_bonecoll_unassign_all_editbone(struct EditBone *ebone);
|
||||||
void ANIM_armature_bonecoll_assign_active(const struct bArmature *armature,
|
void ANIM_armature_bonecoll_assign_active(const struct bArmature *armature,
|
||||||
struct EditBone *ebone);
|
struct EditBone *ebone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the Armature's active bone is assigned to the given bone collection.
|
||||||
|
*/
|
||||||
|
bool ANIM_armature_bonecoll_contains_active_bone(const struct bArmature *armature,
|
||||||
|
const struct BoneCollection *bcoll);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconstruct the bone collection memberships, based on the bone runtime data.
|
* Reconstruct the bone collection memberships, based on the bone runtime data.
|
||||||
*
|
*
|
||||||
|
@ -270,6 +311,42 @@ namespace blender::animrig {
|
||||||
*/
|
*/
|
||||||
int armature_bonecoll_find_index(const bArmature *armature, const ::BoneCollection *bcoll);
|
int armature_bonecoll_find_index(const bArmature *armature, const ::BoneCollection *bcoll);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the index of the given bone collection's parent, or -1 if it has no parent.
|
||||||
|
*/
|
||||||
|
int armature_bonecoll_find_parent_index(const bArmature *armature, int bcoll_index);
|
||||||
|
|
||||||
|
bool armature_bonecoll_is_root(const bArmature *armature, int bcoll_index);
|
||||||
|
|
||||||
|
bool armature_bonecoll_is_child_of(const bArmature *armature,
|
||||||
|
int potential_parent_index,
|
||||||
|
int potential_child_index);
|
||||||
|
|
||||||
|
bool armature_bonecoll_is_decendent_of(const bArmature *armature,
|
||||||
|
int potential_parent_index,
|
||||||
|
int potential_decendent_index);
|
||||||
|
|
||||||
|
bool bonecoll_has_children(const BoneCollection *bcoll);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a bone collection from one parent to another.
|
||||||
|
*
|
||||||
|
* \param from_bcoll_index index of the bone collection to move.
|
||||||
|
* \param to_child_num gap index of where to insert the collection; 0 to make it
|
||||||
|
* the first child, and parent->child_count to make it the last child. -1 also
|
||||||
|
* works as an indicator for the last child, as that makes it possible to call
|
||||||
|
* this function without requiring the caller to find the BoneCollection* of the
|
||||||
|
* parent.
|
||||||
|
* \param from_parent_index index of its current parent (-1 if it is a root collection).
|
||||||
|
* \param to_parent_index index of the new parent (-1 if it is to become a root collection).
|
||||||
|
* \return the collection's new index in the collections_array.
|
||||||
|
*/
|
||||||
|
int armature_bonecoll_move_to_parent(bArmature *armature,
|
||||||
|
int from_bcoll_index,
|
||||||
|
int to_child_num,
|
||||||
|
int from_parent_index,
|
||||||
|
int to_parent_index);
|
||||||
|
|
||||||
/* --------------------------------------------------------------------
|
/* --------------------------------------------------------------------
|
||||||
* The following functions are only used by edit-mode Armature undo:
|
* The following functions are only used by edit-mode Armature undo:
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -38,6 +38,7 @@ set(SRC
|
||||||
ANIM_keyframing.hh
|
ANIM_keyframing.hh
|
||||||
ANIM_rna.hh
|
ANIM_rna.hh
|
||||||
ANIM_visualkey.hh
|
ANIM_visualkey.hh
|
||||||
|
intern/bone_collections_internal.hh
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIB
|
set(LIB
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#include "ANIM_armature_iter.hh"
|
#include "ANIM_armature_iter.hh"
|
||||||
#include "ANIM_bone_collections.hh"
|
#include "ANIM_bone_collections.hh"
|
||||||
|
|
||||||
|
#include "intern/bone_collections_internal.hh"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -44,6 +46,7 @@ namespace {
|
||||||
constexpr eBoneCollection_Flag default_flags = BONE_COLLECTION_VISIBLE |
|
constexpr eBoneCollection_Flag default_flags = BONE_COLLECTION_VISIBLE |
|
||||||
BONE_COLLECTION_SELECTABLE;
|
BONE_COLLECTION_SELECTABLE;
|
||||||
constexpr auto bonecoll_default_name = "Bones";
|
constexpr auto bonecoll_default_name = "Bones";
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
BoneCollection *ANIM_bonecoll_new(const char *name)
|
BoneCollection *ANIM_bonecoll_new(const char *name)
|
||||||
|
@ -155,31 +158,55 @@ static void bonecoll_insert_at_index(bArmature *armature, BoneCollection *bcoll,
|
||||||
sizeof(BoneCollection *) * (armature->collection_array_num + 1),
|
sizeof(BoneCollection *) * (armature->collection_array_num + 1),
|
||||||
__func__);
|
__func__);
|
||||||
|
|
||||||
/* Shift over the collections to make room at the given index. */
|
/* To keep the memory consistent, insert the new element at the end of the
|
||||||
if (index < armature->collection_array_num) {
|
* now-grown array, then rotate it into place. */
|
||||||
BoneCollection **start = armature->collection_array + index;
|
armature->collection_array[armature->collection_array_num] = bcoll;
|
||||||
const size_t count = armature->collection_array_num - index;
|
|
||||||
memmove((void *)(start + 1), (void *)start, count * sizeof(BoneCollection *));
|
|
||||||
}
|
|
||||||
|
|
||||||
armature->collection_array[index] = bcoll;
|
|
||||||
armature->collection_array_num++;
|
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) {
|
if (armature->runtime.active_collection_index >= index) {
|
||||||
ANIM_armature_bonecoll_active_index_set(armature,
|
ANIM_armature_bonecoll_active_index_set(armature,
|
||||||
armature->runtime.active_collection_index + 1);
|
armature->runtime.active_collection_index + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void bonecoll_insert_as_root(bArmature *armature, BoneCollection *bcoll, int at_index)
|
||||||
* Appends bcoll to the end of armature's array of bone collections.
|
|
||||||
*/
|
|
||||||
static void bonecoll_append(bArmature *armature, BoneCollection *bcoll)
|
|
||||||
{
|
{
|
||||||
bonecoll_insert_at_index(armature, bcoll, armature->collection_array_num);
|
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++;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature, const char *name)
|
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++;
|
||||||
|
|
||||||
|
return insert_at_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature,
|
||||||
|
const char *name,
|
||||||
|
const int parent_index)
|
||||||
{
|
{
|
||||||
BoneCollection *bcoll = ANIM_bonecoll_new(name);
|
BoneCollection *bcoll = ANIM_bonecoll_new(name);
|
||||||
|
|
||||||
|
@ -189,38 +216,165 @@ BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature, const char *name
|
||||||
}
|
}
|
||||||
|
|
||||||
bonecoll_ensure_name_unique(armature, bcoll);
|
bonecoll_ensure_name_unique(armature, bcoll);
|
||||||
bonecoll_append(armature, bcoll);
|
|
||||||
|
if (parent_index < 0) {
|
||||||
|
bonecoll_insert_as_root(armature, bcoll, -1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bonecoll_insert_as_child(armature, bcoll, parent_index);
|
||||||
|
}
|
||||||
|
|
||||||
return bcoll;
|
return bcoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoneCollection *ANIM_armature_bonecoll_insert_copy_after(bArmature *armature,
|
/**
|
||||||
BoneCollection *anchor,
|
* Copy a BoneCollection to a new armature, updating its internal pointers to
|
||||||
const BoneCollection *bcoll_to_copy)
|
* 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));
|
BoneCollection *bcoll = static_cast<BoneCollection *>(MEM_dupallocN(bcoll_to_copy));
|
||||||
|
|
||||||
/* Remap the bone pointers to the given armature, as `bcoll_to_copy` will
|
/* Reset the child_index and child_count properties. These are unreliable when
|
||||||
* likely be owned by another copy of the armature. */
|
* coming from an override, as the original array might have been completely
|
||||||
BLI_duplicatelist(&bcoll->bones, &bcoll->bones);
|
* reshuffled. Children will have to be copied separately. */
|
||||||
BLI_assert_msg(armature->bonehash, "Expected armature bone hash to be there");
|
bcoll->child_index = 0;
|
||||||
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
|
bcoll->child_count = 0;
|
||||||
member->bone = BKE_armature_find_bone_name(armature, member->bone->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bcoll_to_copy->prop) {
|
if (bcoll->prop) {
|
||||||
bcoll->prop = IDP_CopyProperty_ex(bcoll_to_copy->prop,
|
bcoll->prop = IDP_CopyProperty_ex(bcoll_to_copy->prop,
|
||||||
0 /*do_id_user ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT*/);
|
0 /*do_id_user ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int anchor_index = armature_bonecoll_find_index(armature, anchor);
|
/* Remap the bone pointers to the given armature, as `bcoll_to_copy` is
|
||||||
bonecoll_insert_at_index(armature, bcoll, anchor_index + 1);
|
* assumed to be owned by another armature. */
|
||||||
bonecoll_ensure_name_unique(armature, bcoll);
|
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);
|
add_reverse_pointers(bcoll);
|
||||||
|
|
||||||
return 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)
|
static void armature_bonecoll_active_clear(bArmature *armature)
|
||||||
{
|
{
|
||||||
armature->runtime.active_collection_index = -1;
|
armature->runtime.active_collection_index = -1;
|
||||||
|
@ -318,39 +472,88 @@ bool ANIM_armature_bonecoll_move_to_index(bArmature *armature,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoneCollection *bcoll = armature->collection_array[from_index];
|
/* 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.
|
||||||
/* Shift collections over to fill the gap at from_index and make room at to_index. */
|
*/
|
||||||
if (from_index < to_index) {
|
const int parent_index = armature_bonecoll_find_parent_index(armature, from_index);
|
||||||
BoneCollection **start = armature->collection_array + from_index + 1;
|
if (!armature_bonecoll_is_child_of(armature, parent_index, to_index)) {
|
||||||
const size_t count = to_index - from_index;
|
return false;
|
||||||
memmove((void *)(start - 1), (void *)start, count * sizeof(BoneCollection *));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
BoneCollection **start = armature->collection_array + to_index;
|
|
||||||
const size_t count = from_index - to_index;
|
|
||||||
memmove((void *)(start + 1), (void *)start, count * sizeof(BoneCollection *));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
armature->collection_array[to_index] = bcoll;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/* Adjust the active collection index. */
|
/* Store the parent's child_index, as that might move if to_index is the first child
|
||||||
if (from_index == armature->runtime.active_collection_index) {
|
* (bonecolls_move_to_index() will keep it pointing at that first child). */
|
||||||
/* If the active collection is the collection we moved. */
|
BoneCollection *parent_bcoll = armature->collection_array[parent_index];
|
||||||
ANIM_armature_bonecoll_active_index_set(armature, to_index);
|
const int old_parent_child_index = parent_bcoll->child_index;
|
||||||
}
|
|
||||||
else if (from_index <= armature->runtime.active_collection_index &&
|
internal::bonecolls_move_to_index(armature, from_index, to_index);
|
||||||
armature->runtime.active_collection_index <= to_index)
|
|
||||||
{
|
parent_bcoll->child_index = old_parent_child_index;
|
||||||
/* If the active collection is within the span of shifted collections. */
|
|
||||||
const int offset = from_index < to_index ? -1 : 1;
|
|
||||||
ANIM_armature_bonecoll_active_index_set(armature,
|
|
||||||
armature->runtime.active_collection_index + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
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)
|
bool ANIM_armature_bonecoll_move(bArmature *armature, BoneCollection *bcoll, const int step)
|
||||||
{
|
{
|
||||||
if (bcoll == nullptr) {
|
if (bcoll == nullptr) {
|
||||||
|
@ -388,30 +591,61 @@ void ANIM_armature_bonecoll_name_set(bArmature *armature, BoneCollection *bcoll,
|
||||||
BKE_animdata_fix_paths_rename_all(&armature->id, "collections", old_name, bcoll->name);
|
BKE_animdata_fix_paths_rename_all(&armature->id, "collections", old_name, bcoll->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ANIM_armature_bonecoll_remove_from_index(bArmature *armature, const int index)
|
void ANIM_armature_bonecoll_remove_from_index(bArmature *armature, int index)
|
||||||
{
|
{
|
||||||
BLI_assert(0 <= index && index < armature->collection_array_num);
|
BLI_assert(0 <= index && index < armature->collection_array_num);
|
||||||
|
|
||||||
BoneCollection *bcoll = armature->collection_array[index];
|
BoneCollection *bcoll = armature->collection_array[index];
|
||||||
|
|
||||||
/* Remove bone membership. */
|
/* The parent needs updating, so better to find it before this bone collection is removed. */
|
||||||
LISTBASE_FOREACH_MUTABLE (BoneCollectionMember *, member, &bcoll->bones) {
|
int parent_bcoll_index = armature_bonecoll_find_parent_index(armature, index);
|
||||||
ANIM_armature_bonecoll_unassign(bcoll, member->bone);
|
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++;
|
||||||
}
|
}
|
||||||
if (armature->edbo) {
|
|
||||||
LISTBASE_FOREACH (EditBone *, ebone, armature->edbo) {
|
/* Adjust the parent for the removal of its child. */
|
||||||
ANIM_armature_bonecoll_unassign_editbone(bcoll, ebone);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ANIM_bonecoll_free(bcoll);
|
/* Rotate the to-be-removed collection to the last array element. */
|
||||||
|
internal::bonecolls_move_to_index(armature, index, armature->collection_array_num - 1);
|
||||||
/* Shift over the collections to fill the gap. */
|
|
||||||
if (index < (armature->collection_array_num - 1)) {
|
|
||||||
BoneCollection **start = armature->collection_array + index;
|
|
||||||
const size_t count = armature->collection_array_num - index - 1;
|
|
||||||
memmove((void *)start, (void *)(start + 1), count * sizeof(BoneCollection *));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Note: we don't bother to shrink the allocation. It's okay if the
|
/* 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. */
|
* capacity has extra space, because the number of valid items is tracked. */
|
||||||
|
@ -419,19 +653,31 @@ void ANIM_armature_bonecoll_remove_from_index(bArmature *armature, const int ind
|
||||||
armature->collection_array[armature->collection_array_num] = nullptr;
|
armature->collection_array[armature->collection_array_num] = nullptr;
|
||||||
|
|
||||||
/* Update the active BoneCollection. */
|
/* Update the active BoneCollection. */
|
||||||
if (index <= armature->runtime.active_collection_index) {
|
const int active_collection_index = armature->runtime.active_collection_index;
|
||||||
int active_index = armature->runtime.active_collection_index;
|
if (active_collection_index >= 0) {
|
||||||
|
/* Default: select the next sibling.
|
||||||
if (index == armature->collection_array_num) {
|
* If there is none: select the previous sibling.
|
||||||
/* Removing the last element: activate the now-last element. */
|
* If there is none: select the parent.
|
||||||
active_index--;
|
*/
|
||||||
|
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 (index < active_index) {
|
else if (active_collection_index > 0 &&
|
||||||
/* The active collection shifted, because a collection before it was removed. */
|
armature_bonecoll_is_child_of(
|
||||||
active_index--;
|
armature, parent_bcoll_index, active_collection_index - 1))
|
||||||
|
{
|
||||||
|
/* The child preceeding 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);
|
||||||
}
|
}
|
||||||
ANIM_armature_bonecoll_active_index_set(armature, active_index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal::bonecoll_unassign_and_free(armature, bcoll);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ANIM_armature_bonecoll_remove(bArmature *armature, BoneCollection *bcoll)
|
void ANIM_armature_bonecoll_remove(bArmature *armature, BoneCollection *bcoll)
|
||||||
|
@ -450,6 +696,17 @@ BoneCollection *ANIM_armature_bonecoll_get_by_name(bArmature *armature, const ch
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
void ANIM_bonecoll_show(BoneCollection *bcoll)
|
void ANIM_bonecoll_show(BoneCollection *bcoll)
|
||||||
{
|
{
|
||||||
bcoll->flags |= BONE_COLLECTION_VISIBLE;
|
bcoll->flags |= BONE_COLLECTION_VISIBLE;
|
||||||
|
@ -642,15 +899,39 @@ void ANIM_armature_bonecoll_assign_active(const bArmature *armature, EditBone *e
|
||||||
{
|
{
|
||||||
if (armature->runtime.active_collection == nullptr) {
|
if (armature->runtime.active_collection == nullptr) {
|
||||||
/* No active collection, do not assign to any. */
|
/* No active collection, do not assign to any. */
|
||||||
printf("ANIM_armature_bonecoll_assign_active(%s, %s): no active collection\n",
|
|
||||||
ebone->name,
|
|
||||||
armature->id.name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ANIM_armature_bonecoll_assign_editbone(armature->runtime.active_collection, ebone);
|
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 struct bArmature *armature,
|
||||||
|
const struct 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)
|
void ANIM_armature_bonecoll_show_from_bone(bArmature *armature, const Bone *bone)
|
||||||
{
|
{
|
||||||
if (ANIM_bonecoll_is_visible(armature, bone)) {
|
if (ANIM_bonecoll_is_visible(armature, bone)) {
|
||||||
|
@ -703,6 +984,170 @@ int armature_bonecoll_find_index(const bArmature *armature, const BoneCollection
|
||||||
return -1;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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]. */
|
||||||
|
const bool is_root = armature_bonecoll_is_root(armature, potential_child_index);
|
||||||
|
if (is_root) {
|
||||||
|
return potential_parent_index == -1;
|
||||||
|
}
|
||||||
|
if (potential_parent_index < 0) {
|
||||||
|
return is_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_decendent_of(const bArmature *armature,
|
||||||
|
const int potential_parent_index,
|
||||||
|
const int potential_decendent_index)
|
||||||
|
{
|
||||||
|
if (armature_bonecoll_is_child_of(armature, potential_parent_index, potential_decendent_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_decendent_of(armature, visit_index, potential_decendent_index)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bonecoll_has_children(const BoneCollection *bcoll)
|
||||||
|
{
|
||||||
|
return bcoll->child_count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return to_bcoll_index;
|
||||||
|
}
|
||||||
|
|
||||||
/* Utility functions for Armature edit-mode undo. */
|
/* Utility functions for Armature edit-mode undo. */
|
||||||
|
|
||||||
blender::Map<BoneCollection *, BoneCollection *> ANIM_bonecoll_array_copy_no_membership(
|
blender::Map<BoneCollection *, BoneCollection *> ANIM_bonecoll_array_copy_no_membership(
|
||||||
|
@ -766,4 +1211,131 @@ void ANIM_bonecoll_array_free(BoneCollection ***bcoll_array,
|
||||||
*bcoll_array_num = 0;
|
*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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace blender::animrig
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup animrig
|
||||||
|
*
|
||||||
|
* \brief Internal C++ functions to deal with bone collections. These are mostly here for internal
|
||||||
|
* use in `bone_collections.cc` and have them testable by unit tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace blender::animrig::internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a block of BoneCollections in the Armature's `collections_array`, from
|
||||||
|
* `start_index` to `start_index + direction`.
|
||||||
|
*
|
||||||
|
* The move operation is actually implemented as a rotation, so that no
|
||||||
|
* `BoneCollection*` is lost. In other words, one of these operations is
|
||||||
|
* performed, depending on `direction`. Here `B` indicates an element in the
|
||||||
|
* moved block, and `X` indicates the rotated element.
|
||||||
|
*
|
||||||
|
* direction = +1: [. . . X B B B B . . .] -> [. . . B B B B X . . .]
|
||||||
|
* direction = -1: [. . . B B B B X . . .] -> [. . . X B B B B . . .]
|
||||||
|
*
|
||||||
|
* This function does not alter the length of `collections_array`.
|
||||||
|
* It only performs the rotation, and updates any `child_index` when they
|
||||||
|
* reference elements of the moved block.
|
||||||
|
*
|
||||||
|
* It also does not touch any `child_count` properties of bone collections.
|
||||||
|
* Updating those, as well as any references to the rotated element, is the
|
||||||
|
* responsibility of the caller.
|
||||||
|
*
|
||||||
|
* \param direction must be either -1 or 1.
|
||||||
|
*/
|
||||||
|
void bonecolls_rotate_block(bArmature *armature, int start_index, int count, int direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a bone collection to another index.
|
||||||
|
*
|
||||||
|
* This is implemented via a call to #bonecolls_rotate_block, so all the
|
||||||
|
* documentation of that function (including its invariants and caveats) applies
|
||||||
|
* here too.
|
||||||
|
*/
|
||||||
|
void bonecolls_move_to_index(bArmature *armature, int from_index, int to_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the given bone collection in the armature's collections, and return its index.
|
||||||
|
*
|
||||||
|
* The bone collection is only searched for at the given index, index+1, and index-1.
|
||||||
|
*
|
||||||
|
* If the bone collection cannot be found, -1 is returned.
|
||||||
|
*/
|
||||||
|
int bonecolls_find_index_near(bArmature *armature, BoneCollection *bcoll, int index);
|
||||||
|
|
||||||
|
void bonecolls_debug_list(const bArmature *armature);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unassign all (edit)bones from this bone collection, and free it.
|
||||||
|
*
|
||||||
|
* Note that this does NOT take care of updating the collection hierarchy information. See
|
||||||
|
* #ANIM_armature_bonecoll_remove_from_index and #ANIM_armature_bonecoll_remove for that.
|
||||||
|
*/
|
||||||
|
void bonecoll_unassign_and_free(bArmature *armature, BoneCollection *bcoll);
|
||||||
|
|
||||||
|
} // namespace blender::animrig::internal
|
File diff suppressed because it is too large
Load Diff
|
@ -388,8 +388,26 @@ static void read_bone_collections(BlendDataReader *reader, bArmature *arm)
|
||||||
arm->collection_array_num, sizeof(BoneCollection *), __func__);
|
arm->collection_array_num, sizeof(BoneCollection *), __func__);
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
int min_child_index = 0;
|
||||||
LISTBASE_FOREACH_INDEX (BoneCollection *, bcoll, &arm->collections_legacy, i) {
|
LISTBASE_FOREACH_INDEX (BoneCollection *, bcoll, &arm->collections_legacy, i) {
|
||||||
arm->collection_array[i] = bcoll;
|
arm->collection_array[i] = bcoll;
|
||||||
|
|
||||||
|
if (bcoll->child_index > 0) {
|
||||||
|
min_child_index = min_ii(min_child_index, bcoll->child_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arm->collection_root_count == 0 && arm->collection_array_num > 0) {
|
||||||
|
/* There cannot be zero roots when there are any bone collections. This means the root count
|
||||||
|
* likely got lost for some reason, and should be reconstructed to avoid data corruption when
|
||||||
|
* modifying the array. */
|
||||||
|
if (min_child_index == 0) {
|
||||||
|
/* None of the bone collections had any children, so all are roots. */
|
||||||
|
arm->collection_root_count = arm->collection_array_num;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
arm->collection_root_count = min_child_index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "BLI_string.h"
|
||||||
|
|
||||||
#include "ANIM_bone_collections.hh"
|
#include "ANIM_bone_collections.hh"
|
||||||
|
|
||||||
#include "DNA_ID.h"
|
#include "DNA_ID.h"
|
||||||
|
@ -109,7 +111,7 @@ static bool active_bone_collection_poll(bContext *C)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bone_collection_add_exec(bContext *C, wmOperator * /*op*/)
|
static int bone_collection_add_exec(bContext *C, wmOperator *op)
|
||||||
{
|
{
|
||||||
Object *ob = ED_object_context(C);
|
Object *ob = ED_object_context(C);
|
||||||
if (ob == nullptr) {
|
if (ob == nullptr) {
|
||||||
|
@ -117,7 +119,24 @@ static int bone_collection_add_exec(bContext *C, wmOperator * /*op*/)
|
||||||
}
|
}
|
||||||
|
|
||||||
bArmature *armature = static_cast<bArmature *>(ob->data);
|
bArmature *armature = static_cast<bArmature *>(ob->data);
|
||||||
BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, nullptr);
|
|
||||||
|
const int parent_index = RNA_int_get(op->ptr, "parent_index");
|
||||||
|
printf("Adding bone collection to parent index %d\n", parent_index);
|
||||||
|
if (parent_index < -1) {
|
||||||
|
BKE_reportf(
|
||||||
|
op->reports, RPT_ERROR, "parent_index should not be less than -1: %d", parent_index);
|
||||||
|
return OPERATOR_CANCELLED;
|
||||||
|
}
|
||||||
|
if (parent_index >= armature->collection_array_num) {
|
||||||
|
BKE_reportf(op->reports,
|
||||||
|
RPT_ERROR,
|
||||||
|
"parent_index (%d) should be less than the number of bone collections (%d)",
|
||||||
|
parent_index,
|
||||||
|
armature->collection_array_num);
|
||||||
|
return OPERATOR_CANCELLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, nullptr, parent_index);
|
||||||
ANIM_armature_bonecoll_active_set(armature, bcoll);
|
ANIM_armature_bonecoll_active_set(armature, bcoll);
|
||||||
|
|
||||||
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
|
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
|
||||||
|
@ -137,6 +156,19 @@ void ARMATURE_OT_collection_add(wmOperatorType *ot)
|
||||||
|
|
||||||
/* flags */
|
/* flags */
|
||||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||||
|
|
||||||
|
PropertyRNA *prop;
|
||||||
|
prop = RNA_def_int(
|
||||||
|
ot->srna,
|
||||||
|
"parent_index",
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
INT_MAX,
|
||||||
|
"Parent Index",
|
||||||
|
"Index of the parent bone collection, or -1 if the new bone collection should be a root",
|
||||||
|
-1,
|
||||||
|
INT_MAX);
|
||||||
|
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bone_collection_remove_exec(bContext *C, wmOperator * /*op*/)
|
static int bone_collection_remove_exec(bContext *C, wmOperator * /*op*/)
|
||||||
|
@ -189,7 +221,9 @@ static int bone_collection_move_exec(bContext *C, wmOperator *op)
|
||||||
return OPERATOR_CANCELLED;
|
return OPERATOR_CANCELLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
|
ANIM_armature_bonecoll_active_runtime_refresh(armature);
|
||||||
|
|
||||||
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_COLLECTION, ob);
|
||||||
return OPERATOR_FINISHED;
|
return OPERATOR_FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,8 +494,6 @@ void ARMATURE_OT_collection_assign(wmOperatorType *ot)
|
||||||
ot->description = "Add selected bones to the chosen bone collection";
|
ot->description = "Add selected bones to the chosen bone collection";
|
||||||
|
|
||||||
/* api callbacks */
|
/* api callbacks */
|
||||||
// TODO: reinstate the menu?
|
|
||||||
// ot->invoke = bone_collections_menu_invoke;
|
|
||||||
ot->exec = bone_collection_assign_exec;
|
ot->exec = bone_collection_assign_exec;
|
||||||
ot->poll = bone_collection_assign_poll;
|
ot->poll = bone_collection_assign_poll;
|
||||||
|
|
||||||
|
@ -861,14 +893,15 @@ void ARMATURE_OT_collection_deselect(wmOperatorType *ot)
|
||||||
|
|
||||||
static BoneCollection *add_or_move_to_collection_bcoll(wmOperator *op, bArmature *arm)
|
static BoneCollection *add_or_move_to_collection_bcoll(wmOperator *op, bArmature *arm)
|
||||||
{
|
{
|
||||||
const int collection_index = RNA_enum_get(op->ptr, "collection");
|
const int collection_index = RNA_int_get(op->ptr, "collection_index");
|
||||||
BoneCollection *target_bcoll;
|
BoneCollection *target_bcoll;
|
||||||
|
|
||||||
if (collection_index < 0) {
|
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "new_collection_name");
|
||||||
|
if (RNA_property_is_set(op->ptr, prop)) {
|
||||||
/* TODO: check this with linked, non-overridden armatures. */
|
/* TODO: check this with linked, non-overridden armatures. */
|
||||||
char new_collection_name[MAX_NAME];
|
char new_collection_name[MAX_NAME];
|
||||||
RNA_string_get(op->ptr, "new_collection_name", new_collection_name);
|
RNA_string_get(op->ptr, "new_collection_name", new_collection_name);
|
||||||
target_bcoll = ANIM_armature_bonecoll_new(arm, new_collection_name);
|
target_bcoll = ANIM_armature_bonecoll_new(arm, new_collection_name, collection_index);
|
||||||
BLI_assert_msg(target_bcoll,
|
BLI_assert_msg(target_bcoll,
|
||||||
"It should always be possible to create a new bone collection on an armature");
|
"It should always be possible to create a new bone collection on an armature");
|
||||||
ANIM_armature_bonecoll_active_set(arm, target_bcoll);
|
ANIM_armature_bonecoll_active_set(arm, target_bcoll);
|
||||||
|
@ -988,98 +1021,164 @@ static bool move_to_collection_poll(bContext *C)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool bone_collection_enum_itemf_for_object(Object *ob,
|
/**
|
||||||
EnumPropertyItem **item,
|
* Encode the parameters into an integer, and return as void*.
|
||||||
int *totitem)
|
*
|
||||||
|
* This makes it possible to use these values and pass them directly as 'custom data' pointer to
|
||||||
|
* `uiItemMenuF()`. This makes it possible to give every menu a unique bone collection index for
|
||||||
|
* which it should show the child collections, without having to allocate memory or use static
|
||||||
|
* variables. See `move_to_collection_invoke()` in `object_edit.cc` for the alternative that I
|
||||||
|
* (Sybren) wanted to avoid. */
|
||||||
|
static void *menu_custom_data_encode(const int bcoll_index, const bool is_move_operation)
|
||||||
{
|
{
|
||||||
EnumPropertyItem item_tmp = {0};
|
/* Add 1 to the index, so that it's never negative (it can be -1 to indicate 'all roots'). */
|
||||||
bArmature *arm = static_cast<bArmature *>(ob->data);
|
const uintptr_t index_and_move_bit = ((bcoll_index + 1) << 1) | (is_move_operation << 0);
|
||||||
|
return reinterpret_cast<void *>(index_and_move_bit);
|
||||||
for (int bcoll_index = 0; bcoll_index < arm->collection_array_num; bcoll_index++) {
|
|
||||||
BoneCollection *bcoll = arm->collection_array[bcoll_index];
|
|
||||||
if (!ANIM_armature_bonecoll_is_editable(arm, bcoll)) {
|
|
||||||
/* Skip bone collections that cannot be assigned to because they're
|
|
||||||
* linked and thus uneditable. If there is a way to still show these, but in a disabled
|
|
||||||
* state, that would be preferred. */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
item_tmp.identifier = bcoll->name;
|
|
||||||
item_tmp.name = bcoll->name;
|
|
||||||
item_tmp.value = bcoll_index;
|
|
||||||
RNA_enum_item_add(item, totitem, &item_tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const EnumPropertyItem *bone_collection_enum_itemf(bContext *C,
|
/**
|
||||||
PointerRNA * /*ptr*/,
|
* Decode the `void*` back into a bone collection index and a boolean `is_move_operation`.
|
||||||
PropertyRNA * /*prop*/,
|
*
|
||||||
bool *r_free)
|
* \see menu_custom_data_encode for rationale.
|
||||||
|
*/
|
||||||
|
static std::pair<int, bool> menu_custom_data_decode(void *menu_custom_data)
|
||||||
{
|
{
|
||||||
*r_free = false;
|
const uintptr_t index_and_move_bit = reinterpret_cast<intptr_t>(menu_custom_data);
|
||||||
|
const bool is_move_operation = (index_and_move_bit & 1) == 1;
|
||||||
|
const int bcoll_index = int(index_and_move_bit >> 1) - 1;
|
||||||
|
return std::make_pair(bcoll_index, is_move_operation);
|
||||||
|
}
|
||||||
|
|
||||||
if (!C) {
|
static int icon_for_bone_collection(const bool collection_contains_active_bone)
|
||||||
/* This happens when operators are being tested, and not during normal invocation. */
|
{
|
||||||
return rna_enum_dummy_NULL_items;
|
return collection_contains_active_bone ? ICON_REMOVE : ICON_ADD;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void menu_add_item_for_move_assign_unassign(uiLayout *layout,
|
||||||
|
const bArmature *arm,
|
||||||
|
const BoneCollection *bcoll,
|
||||||
|
const int bcoll_index,
|
||||||
|
const bool is_move_operation)
|
||||||
|
{
|
||||||
|
if (is_move_operation) {
|
||||||
|
uiItemIntO(layout,
|
||||||
|
bcoll->name,
|
||||||
|
ICON_NONE,
|
||||||
|
"ARMATURE_OT_move_to_collection",
|
||||||
|
"collection_index",
|
||||||
|
bcoll_index);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object *ob = ED_object_context(C);
|
const bool contains_active_bone = ANIM_armature_bonecoll_contains_active_bone(arm, bcoll);
|
||||||
if (!ob || ob->type != OB_ARMATURE) {
|
const int icon = icon_for_bone_collection(contains_active_bone);
|
||||||
return rna_enum_dummy_NULL_items;
|
|
||||||
|
if (contains_active_bone) {
|
||||||
|
uiItemStringO(
|
||||||
|
layout, bcoll->name, icon, "ARMATURE_OT_collection_unassign", "name", bcoll->name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uiItemStringO(layout, bcoll->name, icon, "ARMATURE_OT_collection_assign", "name", bcoll->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add menu items to the layout, for a set of bone collections.
|
||||||
|
*
|
||||||
|
* \param menu_custom_data contains two values, encoded as void* to match the signature required by
|
||||||
|
* `uiItemMenuF`. It contains the parent bone collection index (either -1 to show all roots, or
|
||||||
|
* another value to show the children of that collection), as well as a boolean that indicates
|
||||||
|
* whether the menu is created for the "move to collection" or "assign to collection" operator.
|
||||||
|
*
|
||||||
|
* \see menu_custom_data_encode
|
||||||
|
*/
|
||||||
|
static void move_to_collection_menu_create(bContext *C, uiLayout *layout, void *menu_custom_data)
|
||||||
|
{
|
||||||
|
int parent_bcoll_index;
|
||||||
|
bool is_move_operation;
|
||||||
|
std::tie(parent_bcoll_index, is_move_operation) = menu_custom_data_decode(menu_custom_data);
|
||||||
|
|
||||||
|
const Object *ob = ED_object_context(C);
|
||||||
|
const bArmature *arm = static_cast<bArmature *>(ob->data);
|
||||||
|
|
||||||
|
/* The "Create a new collection" mode of this operator has its own menu, and should thus be
|
||||||
|
* invoked. */
|
||||||
|
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
|
||||||
|
uiItemIntO(layout,
|
||||||
|
"New Bone Collection",
|
||||||
|
ICON_NONE,
|
||||||
|
is_move_operation ? "ARMATURE_OT_move_to_collection" :
|
||||||
|
"ARMATURE_OT_assign_to_collection",
|
||||||
|
"collection_index",
|
||||||
|
parent_bcoll_index);
|
||||||
|
|
||||||
|
/* The remaining operators in this menu should be executed on click. Invoking
|
||||||
|
* them would show this same menu again. */
|
||||||
|
uiLayoutSetOperatorContext(layout, WM_OP_EXEC_DEFAULT);
|
||||||
|
|
||||||
|
int child_index, child_count;
|
||||||
|
if (parent_bcoll_index == -1) {
|
||||||
|
child_index = 0;
|
||||||
|
child_count = arm->collection_root_count;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Add a menu item to assign to the parent first, before listing the children. */
|
||||||
|
const BoneCollection *parent = arm->collection_array[parent_bcoll_index];
|
||||||
|
menu_add_item_for_move_assign_unassign(
|
||||||
|
layout, arm, parent, parent_bcoll_index, is_move_operation);
|
||||||
|
uiItemS(layout);
|
||||||
|
|
||||||
|
child_index = parent->child_index;
|
||||||
|
child_count = parent->child_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnumPropertyItem *item = nullptr;
|
/* Loop over the children. There should be at least one, otherwise this parent
|
||||||
int totitem = 0;
|
* bone collection wouldn't have been drawn as a menu.*/
|
||||||
switch (ob->mode) {
|
for (int index = child_index; index < child_index + child_count; index++) {
|
||||||
case OB_MODE_POSE: {
|
const BoneCollection *bcoll = arm->collection_array[index];
|
||||||
Object *obpose = ED_pose_object_from_context(C);
|
|
||||||
if (!obpose) {
|
if (blender::animrig::bonecoll_has_children(bcoll)) {
|
||||||
return nullptr;
|
uiItemMenuF(layout,
|
||||||
}
|
bcoll->name,
|
||||||
bone_collection_enum_itemf_for_object(obpose, &item, &totitem);
|
ICON_NONE,
|
||||||
break;
|
move_to_collection_menu_create,
|
||||||
|
menu_custom_data_encode(index, is_move_operation));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
menu_add_item_for_move_assign_unassign(layout, arm, bcoll, index, is_move_operation);
|
||||||
}
|
}
|
||||||
case OB_MODE_EDIT:
|
|
||||||
bone_collection_enum_itemf_for_object(ob, &item, &totitem);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return rna_enum_dummy_NULL_items;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* New Collection. */
|
static int move_to_collection_regular_invoke(bContext *C, wmOperator *op)
|
||||||
EnumPropertyItem item_tmp = {0};
|
{
|
||||||
item_tmp.identifier = "__NEW__";
|
const char *title = CTX_IFACE_(op->type->translation_context, op->type->name);
|
||||||
item_tmp.name = CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "New Collection");
|
uiPopupMenu *pup = UI_popup_menu_begin(C, title, ICON_NONE);
|
||||||
item_tmp.value = -1;
|
uiLayout *layout = UI_popup_menu_layout(pup);
|
||||||
RNA_enum_item_add(&item, &totitem, &item_tmp);
|
|
||||||
|
|
||||||
RNA_enum_item_end(&item, &totitem);
|
const bool is_move_operation = STREQ(op->type->idname, "ARMATURE_OT_move_to_collection");
|
||||||
*r_free = true;
|
move_to_collection_menu_create(C, layout, menu_custom_data_encode(-1, is_move_operation));
|
||||||
|
|
||||||
return item;
|
UI_popup_menu_end(C, pup);
|
||||||
|
|
||||||
|
return OPERATOR_INTERFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int move_to_new_collection_invoke(bContext *C, wmOperator *op)
|
||||||
|
{
|
||||||
|
return WM_operator_props_dialog_popup(C, op, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
|
static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
|
||||||
{
|
{
|
||||||
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection");
|
/* Invoking with `collection_index` set has a special meaning: show the menu to create a new bone
|
||||||
|
* collection as the child of this one. */
|
||||||
|
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index");
|
||||||
if (RNA_property_is_set(op->ptr, prop)) {
|
if (RNA_property_is_set(op->ptr, prop)) {
|
||||||
const int collection_index = RNA_property_enum_get(op->ptr, prop);
|
return move_to_new_collection_invoke(C, op);
|
||||||
if (collection_index < 0) {
|
|
||||||
return WM_operator_props_dialog_popup(C, op, 200);
|
|
||||||
}
|
|
||||||
/* Either call move_to_collection_exec() or assign_to_collection_exec(), depending on which
|
|
||||||
* operator got invoked. */
|
|
||||||
return op->type->exec(C, op);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *title = CTX_IFACE_(op->type->translation_context, op->type->name);
|
return move_to_collection_regular_invoke(C, op);
|
||||||
uiPopupMenu *pup = UI_popup_menu_begin(C, title, ICON_NONE);
|
|
||||||
uiLayout *layout = UI_popup_menu_layout(pup);
|
|
||||||
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
|
|
||||||
uiItemsEnumO(layout, op->idname, "collection");
|
|
||||||
UI_popup_menu_end(C, pup);
|
|
||||||
return OPERATOR_INTERFACE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARMATURE_OT_move_to_collection(wmOperatorType *ot)
|
void ARMATURE_OT_move_to_collection(wmOperatorType *ot)
|
||||||
|
@ -1102,24 +1201,28 @@ void ARMATURE_OT_move_to_collection(wmOperatorType *ot)
|
||||||
* 'Name' property, without any choice for another collection. */
|
* 'Name' property, without any choice for another collection. */
|
||||||
ot->flag = OPTYPE_UNDO;
|
ot->flag = OPTYPE_UNDO;
|
||||||
|
|
||||||
prop = RNA_def_enum(ot->srna,
|
prop = RNA_def_int(
|
||||||
"collection",
|
ot->srna,
|
||||||
rna_enum_dummy_DEFAULT_items,
|
"collection_index",
|
||||||
0,
|
-1,
|
||||||
"Collection",
|
-1,
|
||||||
"The bone collection to move the selected bones to");
|
INT_MAX,
|
||||||
RNA_def_enum_funcs(prop, bone_collection_enum_itemf);
|
"Collection Index",
|
||||||
/* Translation of items is handled by bone_collection_enum_itemf if needed, most are actually
|
"Index of the collection to move selected bones to. When the operator should create a new "
|
||||||
* data (bone collections) names and therefore should not be translated at all. So disable
|
"bone collection, do not include this parameter and pass new_collection_name",
|
||||||
* automatic translation. */
|
-1,
|
||||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN | PROP_ENUM_NO_TRANSLATE);
|
INT_MAX);
|
||||||
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
|
||||||
|
|
||||||
prop = RNA_def_string(ot->srna,
|
prop = RNA_def_string(
|
||||||
"new_collection_name",
|
ot->srna,
|
||||||
nullptr,
|
"new_collection_name",
|
||||||
MAX_NAME,
|
nullptr,
|
||||||
"Name",
|
MAX_NAME,
|
||||||
"Name of the newly added bone collection");
|
"Name",
|
||||||
|
"Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
|
||||||
|
"collection and move the selected bones to it. To move to an existing collection, do not "
|
||||||
|
"include this parameter and use collection_index");
|
||||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||||
ot->prop = prop;
|
ot->prop = prop;
|
||||||
}
|
}
|
||||||
|
@ -1130,7 +1233,9 @@ void ARMATURE_OT_assign_to_collection(wmOperatorType *ot)
|
||||||
|
|
||||||
/* identifiers */
|
/* identifiers */
|
||||||
ot->name = "Assign to Collection";
|
ot->name = "Assign to Collection";
|
||||||
ot->description = "Assign bones to a collection";
|
ot->description =
|
||||||
|
"Assign all selected bones to a collection, or unassign them, depending on whether the "
|
||||||
|
"active bone is already assigned or not";
|
||||||
ot->idname = "ARMATURE_OT_assign_to_collection";
|
ot->idname = "ARMATURE_OT_assign_to_collection";
|
||||||
|
|
||||||
/* api callbacks */
|
/* api callbacks */
|
||||||
|
@ -1144,21 +1249,29 @@ void ARMATURE_OT_assign_to_collection(wmOperatorType *ot)
|
||||||
* 'Name' property, without any choice for another collection. */
|
* 'Name' property, without any choice for another collection. */
|
||||||
ot->flag = OPTYPE_UNDO;
|
ot->flag = OPTYPE_UNDO;
|
||||||
|
|
||||||
prop = RNA_def_enum(ot->srna,
|
prop = RNA_def_int(
|
||||||
"collection",
|
ot->srna,
|
||||||
rna_enum_dummy_DEFAULT_items,
|
"collection_index",
|
||||||
0,
|
-1,
|
||||||
"Collection",
|
-1,
|
||||||
"The bone collection to move the selected bones to");
|
INT_MAX,
|
||||||
RNA_def_enum_funcs(prop, bone_collection_enum_itemf);
|
"Collection Index",
|
||||||
|
"Index of the collection to assign selected bones to. When the operator should create a new "
|
||||||
|
"bone collection, use new_collection_name to define the collection name, and set this "
|
||||||
|
"parameter to the parent index of the new bone collection",
|
||||||
|
-1,
|
||||||
|
INT_MAX);
|
||||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
|
||||||
|
|
||||||
prop = RNA_def_string(ot->srna,
|
prop = RNA_def_string(
|
||||||
"new_collection_name",
|
ot->srna,
|
||||||
nullptr,
|
"new_collection_name",
|
||||||
MAX_NAME,
|
nullptr,
|
||||||
"Name",
|
MAX_NAME,
|
||||||
"Name of the newly added bone collection");
|
"Name",
|
||||||
|
"Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
|
||||||
|
"collection and assign the selected bones to it. To assign to an existing collection, do "
|
||||||
|
"not include this parameter and use collection_index");
|
||||||
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
||||||
ot->prop = prop;
|
ot->prop = prop;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2704,6 +2704,8 @@ void uiTemplateLightLinkingCollection(uiLayout *layout,
|
||||||
PointerRNA *ptr,
|
PointerRNA *ptr,
|
||||||
const char *propname);
|
const char *propname);
|
||||||
|
|
||||||
|
void uiTemplateBoneCollectionTree(uiLayout *layout, bContext *C);
|
||||||
|
|
||||||
#ifdef WITH_GREASE_PENCIL_V3
|
#ifdef WITH_GREASE_PENCIL_V3
|
||||||
void uiTemplateGreasePencilLayerTree(uiLayout *layout, bContext *C);
|
void uiTemplateGreasePencilLayerTree(uiLayout *layout, bContext *C);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -67,6 +67,7 @@ set(SRC
|
||||||
interface_style.cc
|
interface_style.cc
|
||||||
interface_template_asset_view.cc
|
interface_template_asset_view.cc
|
||||||
interface_template_attribute_search.cc
|
interface_template_attribute_search.cc
|
||||||
|
interface_template_bone_collection_tree.cc
|
||||||
interface_template_light_linking.cc
|
interface_template_light_linking.cc
|
||||||
interface_template_list.cc
|
interface_template_list.cc
|
||||||
interface_template_node_tree_interface.cc
|
interface_template_node_tree_interface.cc
|
||||||
|
|
|
@ -0,0 +1,390 @@
|
||||||
|
/* SPDX-FileCopyrightText: 2023 Blender Foundation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup edinterface
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BKE_context.hh"
|
||||||
|
|
||||||
|
#include "BLT_translation.h"
|
||||||
|
|
||||||
|
#include "ANIM_bone_collections.hh"
|
||||||
|
|
||||||
|
#include "UI_interface.hh"
|
||||||
|
#include "UI_tree_view.hh"
|
||||||
|
|
||||||
|
#include "RNA_access.hh"
|
||||||
|
#include "RNA_prototypes.h"
|
||||||
|
|
||||||
|
#include "ED_undo.hh"
|
||||||
|
|
||||||
|
#include "WM_api.hh"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
namespace blender::ui::bonecollections {
|
||||||
|
|
||||||
|
using namespace blender::animrig;
|
||||||
|
|
||||||
|
class BoneCollectionTreeView : public AbstractTreeView {
|
||||||
|
protected:
|
||||||
|
bArmature &armature_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BoneCollectionTreeView(bArmature &armature);
|
||||||
|
void build_tree() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void build_tree_node_recursive(TreeViewItemContainer &parent, const int bcoll_index);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bone collection and the Armature that owns it.
|
||||||
|
*/
|
||||||
|
struct ArmatureBoneCollection {
|
||||||
|
bArmature *armature;
|
||||||
|
int bcoll_index;
|
||||||
|
|
||||||
|
ArmatureBoneCollection() = default;
|
||||||
|
ArmatureBoneCollection(bArmature *armature, int bcoll_index)
|
||||||
|
: armature(armature), bcoll_index(bcoll_index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const BoneCollection &bcoll() const
|
||||||
|
{
|
||||||
|
return *armature->collection_array[bcoll_index];
|
||||||
|
}
|
||||||
|
BoneCollection &bcoll()
|
||||||
|
{
|
||||||
|
return *armature->collection_array[bcoll_index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BoneCollectionDragController : public AbstractViewItemDragController {
|
||||||
|
private:
|
||||||
|
ArmatureBoneCollection drag_arm_bcoll_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BoneCollectionDragController(BoneCollectionTreeView &tree_view,
|
||||||
|
bArmature &armature,
|
||||||
|
const int bcoll_index);
|
||||||
|
|
||||||
|
eWM_DragDataType get_drag_type() const override;
|
||||||
|
void *create_drag_data() const override;
|
||||||
|
void on_drag_start() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BoneCollectionDropTarget : public TreeViewItemDropTarget {
|
||||||
|
private:
|
||||||
|
ArmatureBoneCollection drop_bonecoll_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BoneCollectionDropTarget(AbstractTreeViewItem &item,
|
||||||
|
DropBehavior behavior,
|
||||||
|
const ArmatureBoneCollection &drop_bonecoll)
|
||||||
|
: TreeViewItemDropTarget(item, behavior), drop_bonecoll_(drop_bonecoll)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override
|
||||||
|
{
|
||||||
|
const ArmatureBoneCollection *drag_arm_bcoll = static_cast<const ArmatureBoneCollection *>(
|
||||||
|
drag.poin);
|
||||||
|
|
||||||
|
/* Do not allow dropping onto another armature. */
|
||||||
|
if (drag_arm_bcoll->armature != drop_bonecoll_.armature) {
|
||||||
|
*r_disabled_hint = "Cannot drag & drop bone collections between Armatures.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dragging onto itself doesn't do anything. */
|
||||||
|
if (drag_arm_bcoll->bcoll_index == drop_bonecoll_.bcoll_index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do not allow dropping onto its own decendants. */
|
||||||
|
if (armature_bonecoll_is_decendent_of(
|
||||||
|
drag_arm_bcoll->armature, drag_arm_bcoll->bcoll_index, drop_bonecoll_.bcoll_index))
|
||||||
|
{
|
||||||
|
*r_disabled_hint = "Cannot drag a collection onto a descendent";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string drop_tooltip(const DragInfo &drag_info) const override
|
||||||
|
{
|
||||||
|
const ArmatureBoneCollection *drag_bone_collection =
|
||||||
|
static_cast<const ArmatureBoneCollection *>(drag_info.drag_data.poin);
|
||||||
|
const BoneCollection &drag_bcoll = drag_bone_collection->bcoll();
|
||||||
|
const BoneCollection &drop_bcoll = drop_bonecoll_.bcoll();
|
||||||
|
|
||||||
|
std::string_view drag_name = drag_bcoll.name;
|
||||||
|
std::string_view drop_name = drop_bcoll.name;
|
||||||
|
|
||||||
|
switch (drag_info.drop_location) {
|
||||||
|
case DropLocation::Into:
|
||||||
|
return fmt::format(TIP_("Move {} into {}"), drag_name, drop_name);
|
||||||
|
case DropLocation::Before:
|
||||||
|
return fmt::format(TIP_("Move {} above {}"), drag_name, drop_name);
|
||||||
|
case DropLocation::After:
|
||||||
|
return fmt::format(TIP_("Move {} below {}"), drag_name, drop_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool on_drop(bContext *C, const DragInfo &drag_info) const override
|
||||||
|
{
|
||||||
|
const ArmatureBoneCollection *drag_arm_bcoll = static_cast<const ArmatureBoneCollection *>(
|
||||||
|
drag_info.drag_data.poin);
|
||||||
|
bArmature *arm = drop_bonecoll_.armature;
|
||||||
|
|
||||||
|
const int from_bcoll_index = drag_arm_bcoll->bcoll_index;
|
||||||
|
const int to_bcoll_index = drop_bonecoll_.bcoll_index;
|
||||||
|
|
||||||
|
int new_bcoll_index = -1;
|
||||||
|
switch (drag_info.drop_location) {
|
||||||
|
case DropLocation::Before:
|
||||||
|
new_bcoll_index = ANIM_armature_bonecoll_move_before_after_index(
|
||||||
|
arm, from_bcoll_index, to_bcoll_index, MoveLocation::Before);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DropLocation::Into: {
|
||||||
|
if (!ANIM_armature_bonecoll_is_editable(arm, &drop_bonecoll_.bcoll())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int from_parent_index = armature_bonecoll_find_parent_index(arm, from_bcoll_index);
|
||||||
|
/* The bone collection becomes the last child of the new parent, as
|
||||||
|
* that's consistent with the drag & drop of scene collections in the
|
||||||
|
* outliner. */
|
||||||
|
new_bcoll_index = armature_bonecoll_move_to_parent(
|
||||||
|
arm, from_bcoll_index, -1, from_parent_index, to_bcoll_index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DropLocation::After:
|
||||||
|
new_bcoll_index = ANIM_armature_bonecoll_move_before_after_index(
|
||||||
|
arm, from_bcoll_index, to_bcoll_index, MoveLocation::After);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_bcoll_index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ANIM_armature_bonecoll_active_index_set(arm, new_bcoll_index);
|
||||||
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_COLLECTION, &arm->id);
|
||||||
|
|
||||||
|
ED_undo_push(C, "Reorder Armature Bone Collections");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BoneCollectionItem : public AbstractTreeViewItem {
|
||||||
|
private:
|
||||||
|
bArmature &armature_;
|
||||||
|
int bcoll_index_;
|
||||||
|
BoneCollection &bone_collection_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BoneCollectionItem(bArmature &armature, const int bcoll_index)
|
||||||
|
: armature_(armature),
|
||||||
|
bcoll_index_(bcoll_index),
|
||||||
|
bone_collection_(*armature.collection_array[bcoll_index])
|
||||||
|
{
|
||||||
|
this->label_ = bone_collection_.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void build_row(uiLayout &row) override
|
||||||
|
{
|
||||||
|
uiLayout *sub = uiLayoutRow(&row, true);
|
||||||
|
|
||||||
|
uiBut *name_label = uiItemL_ex(sub, bone_collection_.name, ICON_NONE, false, false);
|
||||||
|
if (!ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_)) {
|
||||||
|
UI_but_flag_enable(name_label, UI_BUT_INACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contains Active Bone icon. */
|
||||||
|
/* Performance note: this check potentially loops over all bone collections the active bone is
|
||||||
|
* assigned to. And this happens for each redraw of each bone collection in the armature. */
|
||||||
|
{
|
||||||
|
const bool contains_active_bone = ANIM_armature_bonecoll_contains_active_bone(
|
||||||
|
&armature_, &bone_collection_);
|
||||||
|
const int icon = contains_active_bone ? ICON_DOT : ICON_BLANK1;
|
||||||
|
uiItemL(sub, "", icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Visibility eyecon. */
|
||||||
|
{
|
||||||
|
const bool is_visible = bone_collection_.flags & BONE_COLLECTION_VISIBLE;
|
||||||
|
PointerRNA bcoll_ptr = rna_pointer();
|
||||||
|
uiItemR(sub,
|
||||||
|
&bcoll_ptr,
|
||||||
|
"is_visible",
|
||||||
|
UI_ITEM_R_ICON_ONLY,
|
||||||
|
"",
|
||||||
|
is_visible ? ICON_HIDE_OFF : ICON_HIDE_ON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void build_context_menu(bContext &C, uiLayout &column) const override
|
||||||
|
{
|
||||||
|
MenuType *mt = WM_menutype_find("ARMATURE_MT_collection_tree_context_menu", true);
|
||||||
|
if (!mt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UI_menutype_draw(&C, mt, &column);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool supports_collapsing() const override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> should_be_active() const override
|
||||||
|
{
|
||||||
|
return armature_.runtime.active_collection_index == bcoll_index_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_activate(bContext &C) override
|
||||||
|
{
|
||||||
|
/* Let RNA handle the property change. This makes sure all the notifiers and DEG
|
||||||
|
* update calls are properly called. */
|
||||||
|
PointerRNA bcolls_ptr = RNA_pointer_create(&armature_.id, &RNA_BoneCollections, &armature_);
|
||||||
|
PropertyRNA *prop = RNA_struct_find_property(&bcolls_ptr, "active_index");
|
||||||
|
|
||||||
|
RNA_property_int_set(&bcolls_ptr, prop, bcoll_index_);
|
||||||
|
RNA_property_update(&const_cast<bContext &>(C), &bcolls_ptr, prop);
|
||||||
|
|
||||||
|
ED_undo_push(&const_cast<bContext &>(C), "Change Armature's Active Bone Collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool supports_renaming() const override
|
||||||
|
{
|
||||||
|
return ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rename(const bContext &C, StringRefNull new_name) override
|
||||||
|
{
|
||||||
|
/* Let RNA handle the renaming. This makes sure all the notifiers and DEG
|
||||||
|
* update calls are properly called. */
|
||||||
|
PointerRNA bcoll_ptr = rna_pointer();
|
||||||
|
PropertyRNA *prop = RNA_struct_find_property(&bcoll_ptr, "name");
|
||||||
|
|
||||||
|
RNA_property_string_set(&bcoll_ptr, prop, new_name.c_str());
|
||||||
|
RNA_property_update(&const_cast<bContext &>(C), &bcoll_ptr, prop);
|
||||||
|
|
||||||
|
ED_undo_push(&const_cast<bContext &>(C), "Rename Armature Bone Collection");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringRef get_rename_string() const override
|
||||||
|
{
|
||||||
|
return bone_collection_.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override
|
||||||
|
{
|
||||||
|
/* Reject dragging linked (or otherwise uneditable) bone collections. */
|
||||||
|
if (!ANIM_armature_bonecoll_is_editable(&armature_, &bone_collection_)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BoneCollectionTreeView &tree_view = static_cast<BoneCollectionTreeView &>(get_tree_view());
|
||||||
|
return std::make_unique<BoneCollectionDragController>(tree_view, armature_, bcoll_index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override
|
||||||
|
{
|
||||||
|
ArmatureBoneCollection drop_bonecoll(&armature_, bcoll_index_);
|
||||||
|
/* For now, only support DropBehavior::Insert until there's code for actually reordering
|
||||||
|
* siblings. Currently only 'move to another parent' is implemented. */
|
||||||
|
return std::make_unique<BoneCollectionDropTarget>(
|
||||||
|
*this, DropBehavior::ReorderAndInsert, drop_bonecoll);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** RNA pointer to the BoneCollection. */
|
||||||
|
PointerRNA rna_pointer()
|
||||||
|
{
|
||||||
|
return RNA_pointer_create(&armature_.id, &RNA_BoneCollection, &bone_collection_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BoneCollectionTreeView::BoneCollectionTreeView(bArmature &armature) : armature_(armature) {}
|
||||||
|
|
||||||
|
void BoneCollectionTreeView::build_tree()
|
||||||
|
{
|
||||||
|
for (int bcoll_index = 0; bcoll_index < armature_.collection_root_count; bcoll_index++) {
|
||||||
|
build_tree_node_recursive(*this, bcoll_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoneCollectionTreeView::build_tree_node_recursive(TreeViewItemContainer &parent,
|
||||||
|
const int bcoll_index)
|
||||||
|
{
|
||||||
|
BoneCollection *bcoll = armature_.collection_array[bcoll_index];
|
||||||
|
BoneCollectionItem &bcoll_tree_item = parent.add_tree_item<BoneCollectionItem>(armature_,
|
||||||
|
bcoll_index);
|
||||||
|
bcoll_tree_item.set_collapsed(false);
|
||||||
|
|
||||||
|
for (int child_index = bcoll->child_index; child_index < bcoll->child_index + bcoll->child_count;
|
||||||
|
child_index++)
|
||||||
|
{
|
||||||
|
build_tree_node_recursive(bcoll_tree_item, child_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoneCollectionDragController::BoneCollectionDragController(BoneCollectionTreeView &tree_view,
|
||||||
|
bArmature &armature,
|
||||||
|
const int bcoll_index)
|
||||||
|
: AbstractViewItemDragController(tree_view), drag_arm_bcoll_(&armature, bcoll_index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
eWM_DragDataType BoneCollectionDragController::get_drag_type() const
|
||||||
|
{
|
||||||
|
return WM_DRAG_BONE_COLLECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *BoneCollectionDragController::create_drag_data() const
|
||||||
|
{
|
||||||
|
ArmatureBoneCollection *drag_data = MEM_new<ArmatureBoneCollection>(__func__);
|
||||||
|
*drag_data = drag_arm_bcoll_;
|
||||||
|
return drag_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoneCollectionDragController::on_drag_start()
|
||||||
|
{
|
||||||
|
ANIM_armature_bonecoll_active_index_set(drag_arm_bcoll_.armature, drag_arm_bcoll_.bcoll_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::ui::bonecollections
|
||||||
|
|
||||||
|
void uiTemplateBoneCollectionTree(uiLayout *layout, bContext *C)
|
||||||
|
{
|
||||||
|
using namespace blender;
|
||||||
|
|
||||||
|
Object *object = CTX_data_active_object(C);
|
||||||
|
if (!object || object->type != OB_ARMATURE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bArmature *arm = static_cast<bArmature *>(object->data);
|
||||||
|
BLI_assert(GS(arm->id.name) == ID_AR);
|
||||||
|
|
||||||
|
uiBlock *block = uiLayoutGetBlock(layout);
|
||||||
|
|
||||||
|
ui::AbstractTreeView *tree_view = UI_block_add_view(
|
||||||
|
*block,
|
||||||
|
"Bone Collection Tree View",
|
||||||
|
std::make_unique<blender::ui::bonecollections::BoneCollectionTreeView>(*arm));
|
||||||
|
tree_view->set_min_rows(3);
|
||||||
|
|
||||||
|
ui::TreeViewBuilder::build_tree_view(*tree_view, *layout);
|
||||||
|
}
|
|
@ -198,7 +198,12 @@ typedef struct bArmature {
|
||||||
|
|
||||||
struct BoneCollection **collection_array; /* Array of `collection_array_num` BoneCollections. */
|
struct BoneCollection **collection_array; /* Array of `collection_array_num` BoneCollections. */
|
||||||
int collection_array_num;
|
int collection_array_num;
|
||||||
char _pad2[4];
|
/**
|
||||||
|
* Number of root bone collections.
|
||||||
|
*
|
||||||
|
* `collection_array[0:collection_root_count]` are the collections without a parent collection.
|
||||||
|
*/
|
||||||
|
int collection_root_count;
|
||||||
|
|
||||||
/** Do not directly assign, use `ANIM_armature_bonecoll_active_set` instead.
|
/** Do not directly assign, use `ANIM_armature_bonecoll_active_set` instead.
|
||||||
* This is stored as a string to make it possible for the library overrides system to understand
|
* This is stored as a string to make it possible for the library overrides system to understand
|
||||||
|
@ -248,6 +253,15 @@ typedef struct BoneCollection {
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
uint8_t _pad0[7];
|
uint8_t _pad0[7];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hierarchy information. The Armature has an array of BoneCollection pointers. These are ordered
|
||||||
|
* such that siblings are always stored in consecutive array elements.
|
||||||
|
*/
|
||||||
|
/** Array index of the first child of this BoneCollection. */
|
||||||
|
int child_index;
|
||||||
|
/** Number of children of this BoneCollection. */
|
||||||
|
int child_count;
|
||||||
|
|
||||||
/** Custom properties. */
|
/** Custom properties. */
|
||||||
struct IDProperty *prop;
|
struct IDProperty *prop;
|
||||||
} BoneCollection;
|
} BoneCollection;
|
||||||
|
|
|
@ -185,7 +185,8 @@ static void rna_Armature_edit_bone_remove(bArmature *arm,
|
||||||
RNA_POINTER_INVALIDATE(ebone_ptr);
|
RNA_POINTER_INVALIDATE(ebone_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rna_iterator_bone_collections_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
|
static void rna_iterator_bone_collections_all_begin(CollectionPropertyIterator *iter,
|
||||||
|
PointerRNA *ptr)
|
||||||
{
|
{
|
||||||
bArmature *arm = (bArmature *)ptr->data;
|
bArmature *arm = (bArmature *)ptr->data;
|
||||||
rna_iterator_array_begin(iter,
|
rna_iterator_array_begin(iter,
|
||||||
|
@ -195,13 +196,29 @@ static void rna_iterator_bone_collections_begin(CollectionPropertyIterator *iter
|
||||||
false,
|
false,
|
||||||
nullptr);
|
nullptr);
|
||||||
}
|
}
|
||||||
|
static int rna_iterator_bone_collections_all_length(PointerRNA *ptr)
|
||||||
static int rna_iterator_bone_collections_length(PointerRNA *ptr)
|
|
||||||
{
|
{
|
||||||
bArmature *arm = (bArmature *)ptr->data;
|
bArmature *arm = (bArmature *)ptr->data;
|
||||||
return arm->collection_array_num;
|
return arm->collection_array_num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rna_iterator_bone_collections_roots_begin(CollectionPropertyIterator *iter,
|
||||||
|
PointerRNA *ptr)
|
||||||
|
{
|
||||||
|
bArmature *arm = (bArmature *)ptr->data;
|
||||||
|
rna_iterator_array_begin(iter,
|
||||||
|
arm->collection_array,
|
||||||
|
sizeof(BoneCollection *),
|
||||||
|
arm->collection_root_count,
|
||||||
|
false,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
static int rna_iterator_bone_collections_roots_length(PointerRNA *ptr)
|
||||||
|
{
|
||||||
|
bArmature *arm = (bArmature *)ptr->data;
|
||||||
|
return arm->collection_root_count;
|
||||||
|
}
|
||||||
|
|
||||||
static void rna_BoneCollections_active_set(PointerRNA *ptr,
|
static void rna_BoneCollections_active_set(PointerRNA *ptr,
|
||||||
PointerRNA value,
|
PointerRNA value,
|
||||||
struct ReportList * /*reports*/)
|
struct ReportList * /*reports*/)
|
||||||
|
@ -211,6 +228,24 @@ static void rna_BoneCollections_active_set(PointerRNA *ptr,
|
||||||
ANIM_armature_bonecoll_active_set(arm, bcoll);
|
ANIM_armature_bonecoll_active_set(arm, bcoll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rna_iterator_bone_collection_children_begin(CollectionPropertyIterator *iter,
|
||||||
|
PointerRNA *ptr)
|
||||||
|
{
|
||||||
|
bArmature *arm = (bArmature *)ptr->owner_id;
|
||||||
|
const BoneCollection *bcoll = (BoneCollection *)ptr->data;
|
||||||
|
rna_iterator_array_begin(iter,
|
||||||
|
arm->collection_array + bcoll->child_index,
|
||||||
|
sizeof(BoneCollection *),
|
||||||
|
bcoll->child_count,
|
||||||
|
false,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
static int rna_iterator_bone_collection_children_length(PointerRNA *ptr)
|
||||||
|
{
|
||||||
|
const BoneCollection *bcoll = (BoneCollection *)ptr->data;
|
||||||
|
return bcoll->child_count;
|
||||||
|
}
|
||||||
|
|
||||||
static int rna_BoneCollections_active_index_get(PointerRNA *ptr)
|
static int rna_BoneCollections_active_index_get(PointerRNA *ptr)
|
||||||
{
|
{
|
||||||
bArmature *arm = (bArmature *)ptr->data;
|
bArmature *arm = (bArmature *)ptr->data;
|
||||||
|
@ -236,6 +271,32 @@ static void rna_BoneCollections_active_index_range(
|
||||||
*max = max_ii(0, arm->collection_array_num - 1);
|
*max = max_ii(0, arm->collection_array_num - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static BoneCollection *rna_BoneCollections_new(bArmature *armature,
|
||||||
|
ReportList *reports,
|
||||||
|
const char *name,
|
||||||
|
BoneCollection *parent)
|
||||||
|
{
|
||||||
|
if (parent == nullptr) {
|
||||||
|
BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, name);
|
||||||
|
WM_main_add_notifier(NC_OBJECT | ND_BONE_COLLECTION, armature);
|
||||||
|
return bcoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t parent_index = blender::animrig::armature_bonecoll_find_index(armature, parent);
|
||||||
|
if (parent_index < 0) {
|
||||||
|
BKE_reportf(reports,
|
||||||
|
RPT_ERROR,
|
||||||
|
"Bone collection '%s' not found in Armature '%s'",
|
||||||
|
parent->name,
|
||||||
|
armature->id.name + 2);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, name, parent_index);
|
||||||
|
WM_main_add_notifier(NC_OBJECT | ND_BONE_COLLECTION, armature);
|
||||||
|
return bcoll;
|
||||||
|
}
|
||||||
|
|
||||||
static void rna_BoneCollections_active_name_set(PointerRNA *ptr, const char *name)
|
static void rna_BoneCollections_active_name_set(PointerRNA *ptr, const char *name)
|
||||||
{
|
{
|
||||||
bArmature *arm = (bArmature *)ptr->data;
|
bArmature *arm = (bArmature *)ptr->data;
|
||||||
|
@ -270,7 +331,7 @@ static char *rna_BoneCollection_path(const PointerRNA *ptr)
|
||||||
char name_esc[sizeof(bcoll->name) * 2];
|
char name_esc[sizeof(bcoll->name) * 2];
|
||||||
|
|
||||||
BLI_str_escape(name_esc, bcoll->name, sizeof(name_esc));
|
BLI_str_escape(name_esc, bcoll->name, sizeof(name_esc));
|
||||||
return BLI_sprintfN("collections[\"%s\"]", name_esc);
|
return BLI_sprintfN("collections.all[\"%s\"]", name_esc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static IDProperty **rna_BoneCollection_idprops(PointerRNA *ptr)
|
static IDProperty **rna_BoneCollection_idprops(PointerRNA *ptr)
|
||||||
|
@ -343,6 +404,7 @@ static void rna_EditBone_collections_begin(CollectionPropertyIterator *iter, Poi
|
||||||
static bool rna_Armature_collections_override_apply(Main *bmain,
|
static bool rna_Armature_collections_override_apply(Main *bmain,
|
||||||
RNAPropertyOverrideApplyContext &rnaapply_ctx)
|
RNAPropertyOverrideApplyContext &rnaapply_ctx)
|
||||||
{
|
{
|
||||||
|
PointerRNA *ptr_src = &rnaapply_ctx.ptr_src;
|
||||||
PointerRNA *ptr_dst = &rnaapply_ctx.ptr_dst;
|
PointerRNA *ptr_dst = &rnaapply_ctx.ptr_dst;
|
||||||
PropertyRNA *prop_dst = rnaapply_ctx.prop_dst;
|
PropertyRNA *prop_dst = rnaapply_ctx.prop_dst;
|
||||||
PointerRNA *ptr_item_dst = &rnaapply_ctx.ptr_item_dst;
|
PointerRNA *ptr_item_dst = &rnaapply_ctx.ptr_item_dst;
|
||||||
|
@ -354,11 +416,12 @@ static bool rna_Armature_collections_override_apply(Main *bmain,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bArmature *arm_src = (bArmature *)ptr_src->owner_id;
|
||||||
bArmature *arm_dst = (bArmature *)ptr_dst->owner_id;
|
bArmature *arm_dst = (bArmature *)ptr_dst->owner_id;
|
||||||
BoneCollection *bcoll_anchor = static_cast<BoneCollection *>(ptr_item_dst->data);
|
BoneCollection *bcoll_anchor = static_cast<BoneCollection *>(ptr_item_dst->data);
|
||||||
BoneCollection *bcoll_src = static_cast<BoneCollection *>(ptr_item_src->data);
|
BoneCollection *bcoll_src = static_cast<BoneCollection *>(ptr_item_src->data);
|
||||||
BoneCollection *bcoll = ANIM_armature_bonecoll_insert_copy_after(
|
BoneCollection *bcoll = ANIM_armature_bonecoll_insert_copy_after(
|
||||||
arm_dst, bcoll_anchor, bcoll_src);
|
arm_dst, arm_src, bcoll_anchor, bcoll_src);
|
||||||
|
|
||||||
if (!ID_IS_LINKED(&arm_dst->id)) {
|
if (!ID_IS_LINKED(&arm_dst->id)) {
|
||||||
/* Mark this bone collection as local override, so that certain operations can be allowed. */
|
/* Mark this bone collection as local override, so that certain operations can be allowed. */
|
||||||
|
@ -1814,6 +1877,25 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
|
||||||
RNA_def_struct_ui_text(
|
RNA_def_struct_ui_text(
|
||||||
srna, "Armature Bone Collections", "The Bone Collections of this Armature");
|
srna, "Armature Bone Collections", "The Bone Collections of this Armature");
|
||||||
|
|
||||||
|
prop = RNA_def_property(srna, "all", PROP_COLLECTION, PROP_NONE);
|
||||||
|
RNA_def_property_struct_type(prop, "BoneCollection");
|
||||||
|
RNA_def_property_collection_funcs(prop,
|
||||||
|
"rna_iterator_bone_collections_all_begin",
|
||||||
|
"rna_iterator_array_next",
|
||||||
|
"rna_iterator_array_end",
|
||||||
|
"rna_iterator_array_dereference_get",
|
||||||
|
"rna_iterator_bone_collections_all_length",
|
||||||
|
nullptr, /* TODO */
|
||||||
|
nullptr, /* TODO */
|
||||||
|
nullptr);
|
||||||
|
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||||
|
/* Bone collections are overridden via the `armature.collections` (the roots).
|
||||||
|
* It's up to the 'apply' function to also copy the children of a
|
||||||
|
* library-override-added root. */
|
||||||
|
RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON);
|
||||||
|
RNA_def_property_ui_text(
|
||||||
|
prop, "Root Bone Collections", "List of the top-level bone collections in the hierarchy");
|
||||||
|
|
||||||
prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
|
prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
|
||||||
RNA_def_property_struct_type(prop, "BoneCollection");
|
RNA_def_property_struct_type(prop, "BoneCollection");
|
||||||
RNA_def_property_pointer_sdna(prop, nullptr, "runtime.active_collection");
|
RNA_def_property_pointer_sdna(prop, nullptr, "runtime.active_collection");
|
||||||
|
@ -1827,10 +1909,16 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
|
||||||
RNA_def_property_int_sdna(prop, nullptr, "runtime.active_collection_index");
|
RNA_def_property_int_sdna(prop, nullptr, "runtime.active_collection_index");
|
||||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE);
|
RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE);
|
||||||
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);
|
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);
|
||||||
RNA_def_property_ui_text(prop,
|
RNA_def_property_ui_text(
|
||||||
"Active Collection Index",
|
prop,
|
||||||
"The index of the Armature's active bone collection; -1 when there "
|
"Active Collection Index",
|
||||||
"is no active collection");
|
"The index of the Armature's active bone collection; -1 when there "
|
||||||
|
"is no active collection. Note that this is indexing the underlying array of bone "
|
||||||
|
"collections, which may not be in the order you expect. Root collections are listed first, "
|
||||||
|
"and siblings are always sequential. Apart from that, bone collections can be in any order, "
|
||||||
|
"and thus incrementing or decrementing this index can make the active bone collection jump "
|
||||||
|
"around in unexpected ways. For a more predictable interface, use `active` or "
|
||||||
|
"`active_name`");
|
||||||
RNA_def_property_int_funcs(prop,
|
RNA_def_property_int_funcs(prop,
|
||||||
"rna_BoneCollections_active_index_get",
|
"rna_BoneCollections_active_index_get",
|
||||||
"rna_BoneCollections_active_index_set",
|
"rna_BoneCollections_active_index_set",
|
||||||
|
@ -1848,8 +1936,9 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
|
||||||
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_BoneCollections_active_name_set");
|
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_BoneCollections_active_name_set");
|
||||||
|
|
||||||
/* Armature.collections.new(...) */
|
/* Armature.collections.new(...) */
|
||||||
func = RNA_def_function(srna, "new", "ANIM_armature_bonecoll_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");
|
RNA_def_function_ui_description(func, "Add a new empty bone collection to the armature");
|
||||||
|
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||||
parm = RNA_def_string(func,
|
parm = RNA_def_string(func,
|
||||||
"name",
|
"name",
|
||||||
nullptr,
|
nullptr,
|
||||||
|
@ -1858,6 +1947,12 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
|
||||||
"Name of the new collection. Blender will ensure it is unique within the "
|
"Name of the new collection. Blender will ensure it is unique within the "
|
||||||
"collections of the Armature");
|
"collections of the Armature");
|
||||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||||
|
parm = RNA_def_pointer(
|
||||||
|
func,
|
||||||
|
"parent",
|
||||||
|
"BoneCollection",
|
||||||
|
"Parent Collection",
|
||||||
|
"If not None, the new bone collection becomes a child of this collection");
|
||||||
/* Return value. */
|
/* Return value. */
|
||||||
parm = RNA_def_pointer(
|
parm = RNA_def_pointer(
|
||||||
func, "bonecollection", "BoneCollection", "", "Newly created bone collection");
|
func, "bonecollection", "BoneCollection", "", "Newly created bone collection");
|
||||||
|
@ -1865,7 +1960,11 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
|
||||||
|
|
||||||
/* Armature.collections.remove(...) */
|
/* Armature.collections.remove(...) */
|
||||||
func = RNA_def_function(srna, "remove", "ANIM_armature_bonecoll_remove");
|
func = RNA_def_function(srna, "remove", "ANIM_armature_bonecoll_remove");
|
||||||
RNA_def_function_ui_description(func, "Remove the bone collection from the armature");
|
RNA_def_function_ui_description(
|
||||||
|
func,
|
||||||
|
"Remove the bone collection from the armature. If this bone collection has any children, "
|
||||||
|
"they will be reassigned to their grandparent; in other words, the children will take the "
|
||||||
|
"place of the removed bone collection");
|
||||||
parm = RNA_def_pointer(func,
|
parm = RNA_def_pointer(func,
|
||||||
"bone_collection",
|
"bone_collection",
|
||||||
"BoneCollection",
|
"BoneCollection",
|
||||||
|
@ -1875,8 +1974,10 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
|
||||||
|
|
||||||
/* Armature.collections.move(...) */
|
/* Armature.collections.move(...) */
|
||||||
func = RNA_def_function(srna, "move", "rna_BoneCollections_move");
|
func = RNA_def_function(srna, "move", "rna_BoneCollections_move");
|
||||||
RNA_def_function_ui_description(
|
RNA_def_function_ui_description(func,
|
||||||
func, "Move a bone collection to a different position in the collection list");
|
"Move a bone collection to a different position in the "
|
||||||
|
"collection list. This can only be used to reorder siblings, "
|
||||||
|
"and not to change parent-child relationships");
|
||||||
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||||
parm = RNA_def_int(
|
parm = RNA_def_int(
|
||||||
func, "from_index", -1, INT_MIN, INT_MAX, "From Index", "Index to move", 0, 10000);
|
func, "from_index", -1, INT_MIN, INT_MAX, "From Index", "Index to move", 0, 10000);
|
||||||
|
@ -1971,11 +2072,11 @@ static void rna_def_armature(BlenderRNA *brna)
|
||||||
prop = RNA_def_property(srna, "collections", PROP_COLLECTION, PROP_NONE);
|
prop = RNA_def_property(srna, "collections", PROP_COLLECTION, PROP_NONE);
|
||||||
RNA_def_property_struct_type(prop, "BoneCollection");
|
RNA_def_property_struct_type(prop, "BoneCollection");
|
||||||
RNA_def_property_collection_funcs(prop,
|
RNA_def_property_collection_funcs(prop,
|
||||||
"rna_iterator_bone_collections_begin",
|
"rna_iterator_bone_collections_roots_begin",
|
||||||
"rna_iterator_array_next",
|
"rna_iterator_array_next",
|
||||||
"rna_iterator_array_end",
|
"rna_iterator_array_end",
|
||||||
"rna_iterator_array_dereference_get",
|
"rna_iterator_array_dereference_get",
|
||||||
"rna_iterator_bone_collections_length",
|
"rna_iterator_bone_collections_roots_length",
|
||||||
nullptr, /* TODO */
|
nullptr, /* TODO */
|
||||||
nullptr, /* TODO */
|
nullptr, /* TODO */
|
||||||
nullptr);
|
nullptr);
|
||||||
|
@ -2125,6 +2226,20 @@ static void rna_def_bonecollection(BlenderRNA *brna)
|
||||||
"will always return an empty list of bones, as the bone collection "
|
"will always return an empty list of bones, as the bone collection "
|
||||||
"memberships are only synchronized when exiting edit mode");
|
"memberships are only synchronized when exiting edit mode");
|
||||||
|
|
||||||
|
prop = RNA_def_property(srna, "children", PROP_COLLECTION, PROP_NONE);
|
||||||
|
RNA_def_property_struct_type(prop, "BoneCollection");
|
||||||
|
RNA_def_property_collection_funcs(prop,
|
||||||
|
"rna_iterator_bone_collection_children_begin",
|
||||||
|
"rna_iterator_array_next",
|
||||||
|
"rna_iterator_array_end",
|
||||||
|
"rna_iterator_array_dereference_get",
|
||||||
|
"rna_iterator_bone_collection_children_length",
|
||||||
|
nullptr, /* TODO */
|
||||||
|
nullptr, /* TODO */
|
||||||
|
nullptr);
|
||||||
|
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||||
|
RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON);
|
||||||
|
|
||||||
RNA_api_bonecollection(srna);
|
RNA_api_bonecollection(srna);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,41 @@ static bool rna_BoneCollection_unassign(BoneCollection *bcoll,
|
||||||
ANIM_armature_bonecoll_unassign_editbone);
|
ANIM_armature_bonecoll_unassign_editbone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int rna_BoneCollection_move_to_parent(ID *owner_id,
|
||||||
|
BoneCollection *self,
|
||||||
|
bContext *C,
|
||||||
|
ReportList *reports,
|
||||||
|
BoneCollection *to_parent,
|
||||||
|
const int to_child_num)
|
||||||
|
{
|
||||||
|
using namespace blender::animrig;
|
||||||
|
|
||||||
|
bArmature *armature = (bArmature *)owner_id;
|
||||||
|
|
||||||
|
const int from_bcoll_index = armature_bonecoll_find_index(armature, self);
|
||||||
|
const int from_parent_index = armature_bonecoll_find_parent_index(armature, from_bcoll_index);
|
||||||
|
const int to_parent_index = armature_bonecoll_find_index(armature, to_parent);
|
||||||
|
|
||||||
|
if (to_parent_index == from_bcoll_index ||
|
||||||
|
armature_bonecoll_is_decendent_of(armature, from_bcoll_index, to_parent_index))
|
||||||
|
{
|
||||||
|
BKE_report(reports, RPT_ERROR, "Cannot make a bone collection a decendent of itself");
|
||||||
|
return from_bcoll_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int new_bcoll_index = armature_bonecoll_move_to_parent(
|
||||||
|
armature, from_bcoll_index, to_child_num, from_parent_index, to_parent_index);
|
||||||
|
|
||||||
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_COLLECTION, nullptr);
|
||||||
|
return new_bcoll_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rna_BoneCollection_find_index(ID *owner_id, BoneCollection *self)
|
||||||
|
{
|
||||||
|
bArmature *armature = (bArmature *)owner_id;
|
||||||
|
return blender::animrig::armature_bonecoll_find_index(armature, self);
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
void RNA_api_armature_edit_bone(StructRNA *srna)
|
void RNA_api_armature_edit_bone(StructRNA *srna)
|
||||||
|
@ -320,6 +355,60 @@ void RNA_api_bonecollection(StructRNA *srna)
|
||||||
"Whether the bone was actually removed; will be false if the bone was "
|
"Whether the bone was actually removed; will be false if the bone was "
|
||||||
"not a member of the collection to begin with");
|
"not a member of the collection to begin with");
|
||||||
RNA_def_function_return(func, parm);
|
RNA_def_function_return(func, parm);
|
||||||
|
|
||||||
|
/* collection.move_to_parent(parent, child_num) */
|
||||||
|
func = RNA_def_function(srna, "move_to_parent", "rna_BoneCollection_move_to_parent");
|
||||||
|
RNA_def_function_ui_description(
|
||||||
|
func,
|
||||||
|
"Change the hierarchy, by moving this bone collection to become a child of a different "
|
||||||
|
"parent. Use `parent=None` to make this collection a root collection");
|
||||||
|
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
|
||||||
|
|
||||||
|
parm = RNA_def_pointer(func,
|
||||||
|
"to_parent",
|
||||||
|
"BoneCollection",
|
||||||
|
"Parent Collection",
|
||||||
|
"The bone collection becomes a child of this collection. If `None`, the "
|
||||||
|
"bone collection becomes a root");
|
||||||
|
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||||
|
|
||||||
|
parm = RNA_def_int(func,
|
||||||
|
"to_child_num",
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
INT_MAX,
|
||||||
|
"Child Number",
|
||||||
|
"Place the bone collection before this child; `child_num=0` makes it the "
|
||||||
|
"first child, `child_num=1` the second, etc. Both `child_num=-1` and "
|
||||||
|
"`child_num=child_count` will move the bone collection to be the last child "
|
||||||
|
"of the new parent",
|
||||||
|
-1,
|
||||||
|
INT_MAX);
|
||||||
|
|
||||||
|
/* Return value. */
|
||||||
|
|
||||||
|
parm = RNA_def_int(func,
|
||||||
|
"new_index",
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
INT_MAX,
|
||||||
|
"New Index",
|
||||||
|
"Resulting index of the bone collection, after the move. This can be used "
|
||||||
|
"for manipulating the active bone collection index",
|
||||||
|
-1,
|
||||||
|
INT_MAX);
|
||||||
|
RNA_def_function_return(func, parm);
|
||||||
|
|
||||||
|
/* collection.find_index() */
|
||||||
|
func = RNA_def_function(srna, "find_index", "rna_BoneCollection_find_index");
|
||||||
|
RNA_def_function_ui_description(func,
|
||||||
|
"Find the index of this bone collection. This scans through all "
|
||||||
|
"the Armature's bone collections");
|
||||||
|
RNA_def_function_flag(func, FUNC_USE_SELF_ID);
|
||||||
|
/* Return value. */
|
||||||
|
parm = RNA_def_int(
|
||||||
|
func, "index", -1, -1, INT_MAX, "Index", "Index of the bone collection", -1, INT_MAX);
|
||||||
|
RNA_def_function_return(func, parm);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2112,6 +2112,10 @@ void RNA_api_ui_layout(StructRNA *srna)
|
||||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
|
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
|
||||||
api_ui_item_rna_common(func);
|
api_ui_item_rna_common(func);
|
||||||
|
|
||||||
|
func = RNA_def_function(srna, "template_bone_collection_tree", "uiTemplateBoneCollectionTree");
|
||||||
|
RNA_def_function_ui_description(func, "Show bone collections tree");
|
||||||
|
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
|
||||||
|
|
||||||
# ifdef WITH_GREASE_PENCIL_V3
|
# ifdef WITH_GREASE_PENCIL_V3
|
||||||
func = RNA_def_function(
|
func = RNA_def_function(
|
||||||
srna, "template_grease_pencil_layer_tree", "uiTemplateGreasePencilLayerTree");
|
srna, "template_grease_pencil_layer_tree", "uiTemplateGreasePencilLayerTree");
|
||||||
|
|
|
@ -1135,6 +1135,7 @@ enum eWM_DragDataType {
|
||||||
WM_DRAG_ASSET_CATALOG,
|
WM_DRAG_ASSET_CATALOG,
|
||||||
WM_DRAG_GREASE_PENCIL_LAYER,
|
WM_DRAG_GREASE_PENCIL_LAYER,
|
||||||
WM_DRAG_NODE_TREE_INTERFACE,
|
WM_DRAG_NODE_TREE_INTERFACE,
|
||||||
|
WM_DRAG_BONE_COLLECTION,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum eWM_DragFlags {
|
enum eWM_DragFlags {
|
||||||
|
|
Loading…
Reference in New Issue