Replace Bone Layers+Groups with Bone Collections #109976

Merged
Nathan Vegdahl merged 7 commits from dr.sybren/blender:anim/armature-collections into main 2023-08-29 14:31:31 +02:00
75 changed files with 3005 additions and 490 deletions

View File

@ -4761,13 +4761,13 @@ def km_pose(params):
("pose.constraints_clear", {"type": 'C', "value": 'PRESS', "ctrl": True, "alt": True}, None),
("pose.ik_add", {"type": 'I', "value": 'PRESS', "shift": True}, None),
("pose.ik_clear", {"type": 'I', "value": 'PRESS', "ctrl": True, "alt": True}, None),
op_menu("VIEW3D_MT_pose_group", {"type": 'G', "value": 'PRESS', "ctrl": True}),
op_menu("VIEW3D_MT_bone_collections", {"type": 'G', "value": 'PRESS', "ctrl": True}),
op_menu("VIEW3D_MT_bone_options_toggle", {"type": 'W', "value": 'PRESS', "shift": 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}),
("armature.layers_show_all", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True}, None),
("armature.armature_layers", {"type": 'M', "value": 'PRESS', "shift": True}, None),
("pose.bone_layers", {"type": 'M', "value": 'PRESS'}, None),
("armature.assign_to_collection", {"type": 'M', "value": 'PRESS', "shift": True}, None),
("armature.move_to_collection", {"type": 'M', "value": 'PRESS'}, None),
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
("anim.keyframe_insert_menu", {"type": 'I', "value": 'PRESS'}, None),
("anim.keyframe_delete_v3d", {"type": 'I', "value": 'PRESS', "alt": True}, None),

View File

@ -49,12 +49,6 @@ class DATA_PT_skeleton(ArmatureButtonsPanel, Panel):
layout.row().prop(arm, "pose_position", expand=True)
col = layout.column()
col.label(text="Layers:")
col.prop(arm, "layers", text="")
col.label(text="Protected Layers:")
col.prop(arm, "layers_protected", text="")
class DATA_PT_display(ArmatureButtonsPanel, Panel):
bl_label = "Viewport Display"
@ -88,28 +82,19 @@ class DATA_PT_display(ArmatureButtonsPanel, Panel):
sub.prop(arm, "relation_line_position", text="Relations", expand=True)
class DATA_MT_bone_group_context_menu(Menu):
bl_label = "Bone Group Specials"
class DATA_UL_bone_collections(UIList):
def draw_item(self, _context, layout, armature, bcoll, _icon, _active_data, _active_propname, _index):
active_bone = armature.edit_bones.active or armature.bones.active
has_active_bone = active_bone and bcoll.name in active_bone.collections
def draw(self, _context):
layout = self.layout
layout.operator("pose.group_sort", icon='SORTALPHA')
layout.prop(bcoll, "name", text="", emboss=False,
icon='DOT' if has_active_bone else 'BLANK1')
layout.prop(bcoll, "is_visible", text="", emboss=False,
icon='HIDE_OFF' if bcoll.is_visible else 'HIDE_ON')
class DATA_UL_bone_groups(UIList):
def draw_item(self, _context, layout, _data, item, _icon, _active_data, _active_propname, _index):
layout.prop(item, "name", text="", emboss=False, icon='GROUP_BONE')
if item.is_custom_color_set or item.color_set == 'DEFAULT':
layout.prop(item, "color_set", icon_only=True, icon='COLOR')
else:
layout.prop(item, "color_set", icon_only=True)
class DATA_PT_bone_groups(ArmatureButtonsPanel, Panel):
bl_label = "Bone Groups"
bl_options = {'DEFAULT_CLOSED'}
class DATA_PT_bone_collections(ArmatureButtonsPanel, Panel):
bl_label = "Bone Collections"
@classmethod
def poll(cls, context):
@ -120,59 +105,42 @@ class DATA_PT_bone_groups(ArmatureButtonsPanel, Panel):
layout = self.layout
ob = context.object
pose = ob.pose
group = pose.bone_groups.active
arm = ob.data
active_bcoll = arm.collections.active
row = layout.row()
rows = 1
if group:
if active_bcoll:
rows = 4
row.template_list(
"DATA_UL_bone_groups",
"bone_groups",
pose,
"bone_groups",
pose.bone_groups,
"DATA_UL_bone_collections",
"collections",
arm,
"collections",
arm.collections,
"active_index",
rows=rows,
)
col = row.column(align=True)
col.operator("pose.group_add", icon='ADD', text="")
col.operator("pose.group_remove", icon='REMOVE', text="")
col.menu("DATA_MT_bone_group_context_menu", icon='DOWNARROW_HLT', text="")
if group:
col.operator("armature.collection_add", icon='ADD', text="")
col.operator("armature.collection_remove", icon='REMOVE', text="")
if active_bcoll:
col.separator()
col.operator("pose.group_move", icon='TRIA_UP', text="").direction = 'UP'
col.operator("pose.group_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
if group.is_custom_color_set:
col = layout.column()
split = col.split(factor=0.4)
col = split.column()
row = col.row()
row.alignment = 'RIGHT'
row.label(text="Custom Colors")
col = split.column(align=True)
row = col.row(align=True)
row.prop(group.colors, "normal", text="")
row.prop(group.colors, "select", text="")
row.prop(group.colors, "active", text="")
col.operator("armature.collection_move", icon='TRIA_UP', text="").direction = 'UP'
col.operator("armature.collection_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
row = layout.row()
sub = row.row(align=True)
sub.operator("pose.group_assign", text="Assign")
# row.operator("pose.bone_group_remove_from", text="Remove")
sub.operator("pose.group_unassign", text="Remove")
sub.operator("armature.collection_assign", text="Assign")
sub.operator("armature.collection_unassign", text="Remove")

These should be disabled if there is no active_bcoll. (Just needs to be added to poll.)

These should be disabled if there is no `active_bcoll`. (Just needs to be added to poll.)

When I looked at the actual poll functions, that doesn't actually look like the appropriate place, since these operators can be called with an explicitly specified collection rather than the active one. So it probably makes sense to disable them in the Python code here after all. Same for the other comment about this below.

When I looked at the actual poll functions, that doesn't actually look like the appropriate place, since these operators can be called with an explicitly specified collection rather than the active one. So it probably makes sense to disable them in the Python code here after all. Same for the other comment about this below.
sub = row.row(align=True)
sub.operator("pose.group_select", text="Select")
sub.operator("pose.group_deselect", text="Deselect")
sub.operator("armature.collection_select", text="Select")
sub.operator("armature.collection_deselect", text="Deselect")

These should be disabled if there is no active_bcoll. (Just needs to be added to poll.)

These should be disabled if there is no `active_bcoll`. (Just needs to be added to poll.)
class DATA_PT_iksolver_itasc(ArmatureButtonsPanel, Panel):
@ -278,9 +246,8 @@ class DATA_PT_custom_props_arm(ArmatureButtonsPanel, PropertyPanel, Panel):
classes = (
DATA_PT_context_arm,
DATA_PT_skeleton,
DATA_MT_bone_group_context_menu,
DATA_PT_bone_groups,
DATA_UL_bone_groups,
DATA_PT_bone_collections,
DATA_UL_bone_collections,
DATA_PT_motion_paths,
DATA_PT_motion_paths_display,
DATA_PT_display,

View File

@ -258,7 +258,7 @@ class BONE_PT_display(BoneButtonsPanel, Panel):
@classmethod
def poll(cls, context):
return context.bone
return context.bone or context.edit_bone
def draw(self, context):
# note. this works ok in edit-mode but isn't
@ -266,13 +266,55 @@ class BONE_PT_display(BoneButtonsPanel, Panel):
layout = self.layout
layout.use_property_split = True
bone = context.bone
if bone is None:
bone = context.edit_bone
if context.bone is None:
self.draw_edit_bone(context, layout)
else:
self.draw_bone(context, layout)
if bone:
col = layout.column()
col.prop(bone, "hide", text="Hide", toggle=False)
def draw_bone(self, context, layout):
bone = context.bone
col = layout.column()
col.prop(bone, "hide", text="Hide", toggle=False)
# Figure out the pose bone.
ob = context.object
if not ob:
return
pose_bone = ob.pose.bones[bone.name]
layout.prop(bone.color, 'palette', text='Edit Bone Color')
self.draw_bone_color_ui(layout, bone.color)
layout.prop(pose_bone.color, 'palette', text='Pose Bone Color')
self.draw_bone_color_ui(layout, pose_bone.color)
def draw_edit_bone(self, context, layout):
bone = context.edit_bone
if bone is None:
return
col = layout.column()
col.prop(bone, "hide", text="Hide", toggle=False)
layout.prop(bone.color, 'palette', text='Edit Bone Color')
self.draw_bone_color_ui(layout, bone.color)
def draw_bone_color_ui(self, layout, bone_color):
if not bone_color.is_custom:
return
layout.use_property_split = False
split = layout.split(factor=0.4)
col = split.column()
row = col.row()
row.alignment = 'RIGHT'
row.label(text="Custom Colors")
col = split.column(align=True)
row = col.row(align=True)
row.prop(bone_color.custom, "normal", text="")
row.prop(bone_color.custom, "select", text="")
row.prop(bone_color.custom, "active", text="")
class BONE_PT_display_custom_shape(BoneButtonsPanel, Panel):

View File

@ -3815,7 +3815,7 @@ class VIEW3D_MT_pose(Menu):
layout.separator()
layout.menu("VIEW3D_MT_pose_motion")
layout.menu("VIEW3D_MT_pose_group")
layout.menu("VIEW3D_MT_bone_collections")
layout.separator()
@ -3900,26 +3900,35 @@ class VIEW3D_MT_pose_motion(Menu):
layout.operator("pose.paths_clear", text="Clear")
class VIEW3D_MT_pose_group(Menu):
bl_label = "Bone Groups"
class VIEW3D_MT_bone_collections(Menu):
bl_label = "Bone Collections"
@classmethod
def poll(cls, context):
return context.active_object and context.active_object.type == 'ARMATURE'
def draw(self, context):
layout = self.layout
pose = context.active_object.pose
props = layout.operator("armature.collection_assign",
text="Assign to New Collection")
props.name = "New Collection"
layout.operator_context = 'EXEC_AREA'
layout.operator("pose.group_assign", text="Assign to New Group").type = 0
arm = context.active_object.data
if not arm.collections.active:
return
if pose.bone_groups:
active_group = pose.bone_groups.active_index + 1
layout.operator("pose.group_assign", text="Assign to Group").type = active_group
layout.separator()
layout.separator()
layout.operator("armature.collection_assign",
text="Assign to '%s'" % arm.collections.active.name)
layout.operator("armature.collection_unassign",
text="Unassign from '%s'" % arm.collections.active.name)
# layout.operator_context = 'INVOKE_AREA'
layout.operator("pose.group_unassign")
layout.operator("pose.group_remove")
layout.separator()
layout.operator("armature.collection_remove",
text="Remove Collection '%s'" % arm.collections.active.name)
class VIEW3D_MT_pose_ik(Menu):
@ -8441,7 +8450,7 @@ classes = (
VIEW3D_MT_pose_slide,
VIEW3D_MT_pose_propagate,
VIEW3D_MT_pose_motion,
VIEW3D_MT_pose_group,
VIEW3D_MT_bone_collections,
VIEW3D_MT_pose_ik,
VIEW3D_MT_pose_constraints,
VIEW3D_MT_pose_names,

View File

@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*
* \brief Iterators for armatures.
*/
#pragma once
#ifndef __cplusplus
# error This is a C++ header.
#endif
#include "DNA_armature_types.h"
#include "BLI_listbase_wrapper.hh"
namespace blender::animrig {
/**
* Call `callback(bone)` for each bone in the list of bones.
*
* Bones are visited in depth-first order.
*
* TODO: extend the callback with a `bool` return value to indicate whether the
* loop should continue or stop.
*/
template<typename CB>
static void ANIM_armature_foreach_bone(ListBase /* Bone */ *bones, CB callback)
{
for (Bone *bone : blender::ListBaseWrapper<Bone>(bones)) {
callback(bone);
ANIM_armature_foreach_bone(&bone->childbase, callback);
}
}
/**
* Call `callback(bone)` for each bone in the list of bones.
*
* Bones are visited in depth-first order.
*
* TODO: extend the callback with a `bool` return value to indicate whether the
* loop should continue or stop.
*/
template<typename CB>
static void ANIM_armature_foreach_bone(const ListBase /* Bone */ *bones, CB callback)
{
for (const Bone *bone : blender::ConstListBaseWrapper<Bone>(bones)) {
callback(bone);
ANIM_armature_foreach_bone(&bone->childbase, callback);
}
}
}; // namespace blender::animrig

View File

@ -25,10 +25,121 @@ extern "C" {
struct bArmature;
struct Bone;
struct BoneCollection;
struct bPoseChannel;
struct EditBone;
/**
* Construct a new #BoneCollection with the given name.
*
* The caller owns the returned pointer.
*
* You don't typically use this function directly, but rather create a bone collection on a
* bArmature.
*
* \see #ANIM_armature_bonecoll_new
*/
struct BoneCollection *ANIM_bonecoll_new(const char *name) ATTR_WARN_UNUSED_RESULT;
/**
* Free the bone collection.
*
* You don't typically need this function, unless you created a bone collection outside the scope
* of a bArmature. Normally bone collections are owned (and thus managed) by the armature.
*
* \see ANIM_armature_bonecoll_remove
*/
void ANIM_bonecoll_free(struct BoneCollection *bcoll);
/**
* Recalculate the armature & bone runtime data.
*
* NOTE: this should only be used when the runtime structs on the Armature and Bones are still
* empty. Any data allocated there will NOT be freed.
*
* TODO: move to BKE?
*/
void ANIM_armature_runtime_refresh(struct bArmature *armature);
/**
* Free armature & bone runtime data.
* TODO: move to BKE?
*/
void ANIM_armature_runtime_free(struct bArmature *armature);
/**
* Add a new bone collection to the given armature.
*
* The Armature owns the returned pointer.
*/
struct BoneCollection *ANIM_armature_bonecoll_new(struct bArmature *armature, const char *name);
/**
* Remove a bone collection from the armature.
*/
void ANIM_armature_bonecoll_remove(struct bArmature *armature, struct BoneCollection *bcoll);
/**
* Set the given bone collection as the active one.
*
* Pass `nullptr` to clear the active bone collection.
*/
void ANIM_armature_bonecoll_active_set(struct bArmature *armature, struct BoneCollection *bcoll);
/**
* Set the bone collection with the given index as the active one.
*
* Pass -1 to clear the active bone collection.
*/
void ANIM_armature_bonecoll_active_index_set(struct bArmature *armature,
int bone_collection_index);
/**
* Move the bone collection by \a step places up/down.
*
* \return whether the move actually happened.
*/
bool ANIM_armature_bonecoll_move(struct bArmature *armature,
struct BoneCollection *bcoll,
int step);
struct BoneCollection *ANIM_armature_bonecoll_get_by_name(
struct bArmature *armature, const char *name) ATTR_WARN_UNUSED_RESULT;
void ANIM_armature_bonecoll_name_set(struct bArmature *armature,
struct BoneCollection *bcoll,
const char *name);
void ANIM_bonecoll_hide(struct BoneCollection *bcoll);
/**
* Assign the bone to the bone collection.
*
* No-op if the bone is already a member of the collection.
*
* \return true if the bone was actually assigned, false if not (f.e. when it already was assigned
* previously).
*/
bool ANIM_armature_bonecoll_assign(struct BoneCollection *bcoll, struct Bone *bone);
bool ANIM_armature_bonecoll_assign_editbone(struct BoneCollection *bcoll, struct EditBone *ebone);
bool ANIM_armature_bonecoll_assign_and_move(struct BoneCollection *bcoll, struct Bone *bone);
bool ANIM_armature_bonecoll_unassign(struct BoneCollection *bcoll, struct Bone *bone);
bool ANIM_armature_bonecoll_unassign_editbone(struct BoneCollection *bcoll,
struct EditBone *ebone);
/* Assign the edit bone to the armature's active collection. */
void ANIM_armature_bonecoll_assign_active(const struct bArmature *armature,
struct EditBone *ebone);
/**
* Reconstruct the bone collection memberships, based on the bone runtime data.
*
* This is needed to transition out of armature edit mode. That removes all bones, and
* recreates them from the editbones.
*/
void ANIM_armature_bonecoll_reconstruct(struct bArmature *armature);
/*
* Armature/Bone Layer abstractions. These functions are intended as the sole
* accessors for `bone->layer`, `armature->layer`, etc. to get a grip on which
* queries & operations are performed.
@ -38,10 +149,8 @@ struct EditBone;
* first step towards replacement.
*/
inline bool ANIM_bonecoll_is_visible(const struct bArmature *armature, const struct Bone *bone)
{
return armature->layer & bone->layer;
}
/** Return true when any of the bone's collections is visible. */
bool ANIM_bonecoll_is_visible(const struct bArmature *armature, const struct Bone *bone);
inline bool ANIM_bone_is_visible(const struct bArmature *armature, const struct Bone *bone)
{
@ -49,10 +158,14 @@ inline bool ANIM_bone_is_visible(const struct bArmature *armature, const struct
return bone_itself_visible && ANIM_bonecoll_is_visible(armature, bone);
}
inline bool ANIM_bonecoll_is_visible_editbone(const struct bArmature *armature,
const struct EditBone *ebone)
bool ANIM_bonecoll_is_visible_editbone(const struct bArmature *armature,
const struct EditBone *ebone);
inline bool ANIM_bone_is_visible_editbone(const struct bArmature *armature,
const struct EditBone *ebone)
{
return armature->layer & ebone->layer;
const bool bone_itself_visible = (ebone->flag & BONE_HIDDEN_A) == 0;
return bone_itself_visible && ANIM_bonecoll_is_visible_editbone(armature, ebone);
}
inline bool ANIM_bonecoll_is_visible_pchan(const struct bArmature *armature,
@ -66,18 +179,18 @@ inline bool ANIM_bonecoll_is_visible_actbone(const struct bArmature *armature)
return ANIM_bonecoll_is_visible(armature, armature->act_bone);
}
void ANIM_armature_bonecoll_show_all(struct bArmature *armature);
void ANIM_armature_bonecoll_hide_all(struct bArmature *armature);
/* Only used by the Collada I/O code: */
void ANIM_armature_enable_layers(struct bArmature *armature, const int layers);
void ANIM_armature_disable_all_layers(struct bArmature *armature);
void ANIM_bone_set_layer_ebone(struct EditBone *ebone, int layer);
void ANIM_bone_set_ebone_layer_from_armature(struct EditBone *ebone,
const struct bArmature *armature);
void ANIM_armature_ensure_first_layer_enabled(struct bArmature *armature);
void ANIM_armature_ensure_layer_enabled_from_bone(struct bArmature *armature,
const struct Bone *bone);
void ANIM_armature_ensure_layer_enabled_from_ebone(struct bArmature *armature,
const struct EditBone *ebone);
void ANIM_armature_ensure_layer_enabled_from_pchan(struct bArmature *armature,
const struct bPoseChannel *pchan);
void ANIM_armature_bonecoll_show_from_bone(struct bArmature *armature, const struct Bone *bone);
void ANIM_armature_bonecoll_show_from_ebone(struct bArmature *armature,
const struct EditBone *ebone);
void ANIM_armature_bonecoll_show_from_pchan(struct bArmature *armature,
const struct bPoseChannel *pchan);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*
* \brief C++ part of the BoneColor DNA struct.
*/
#pragma once
#ifndef __cplusplus
# error This is a C++ header.
#endif
#include "DNA_anim_types.h"
struct ThemeWireColor;
nathanvegdahl marked this conversation as resolved Outdated

It looks like this is defined in DNA_userdef_types.h. I'd be more comfortable just including that instead to make it explicit where ThemeWireColor is coming from. I guess the concern here is bloating build times?

It looks like this is defined in `DNA_userdef_types.h`. I'd be more comfortable just including that instead to make it explicit where `ThemeWireColor` is coming from. I guess the concern here is bloating build times?
namespace blender::animrig {
/** C++ wrapper for the DNA BoneColor struct. */
class BoneColor : public ::BoneColor {
public:
BoneColor();
BoneColor(const BoneColor &other);
~BoneColor();
const ThemeWireColor *effective_color() const;
};
}; // namespace blender::animrig

View File

@ -7,6 +7,7 @@ set(INC
intern
../blenkernel
../editors/include
)
set(INC_SYS
@ -14,26 +15,31 @@ set(INC_SYS
set(SRC
intern/bone_collections.cc
intern/bonecolor.cc
ANIM_bone_collections.h
ANIM_bonecolor.hh
)
set(LIB
bf_blenkernel
bf::blenlib
bf::dna
PRIVATE bf_editor_interface
PRIVATE bf::intern::guardedalloc
)
blender_add_lib(bf_animrig "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_library(bf::animrig ALIAS bf_animrig)
# if(WITH_GTESTS)
# set(TEST_SRC
# )
# set(TEST_LIB
# PRIVATE bf::animrig
# )
# include(GTestTesting)
# blender_add_test_lib(bf_animrig_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
# endif()
if(WITH_GTESTS)
set(TEST_SRC
intern/bone_collections_test.cc
)
set(TEST_LIB
PRIVATE bf::animrig
)
include(GTestTesting)
blender_add_test_lib(bf_animrig_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
endif()

View File

@ -6,57 +6,426 @@
* \ingroup animrig
*/
#include "BLI_linklist.h"
#include "BLI_math_color.h"
#include "BLI_string.h"
#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
#include "DNA_armature_types.h"
#include "BLI_math_bits.h"
#include "MEM_guardedalloc.h"
#include "BKE_animsys.h"
#include "ANIM_armature_iter.hh"
#include "ANIM_bone_collections.h"
#include <cstring>
#include <string>
using std::strcmp;
using namespace blender::animrig;
namespace {
/** Default flags for new bone collections. */
constexpr eBoneCollection_Flag default_flags = BONE_COLLECTION_VISIBLE |
BONE_COLLECTION_SELECTABLE;
} // namespace
BoneCollection *ANIM_bonecoll_new(const char *name)
{
if (name == nullptr || name[0] == '\0') {
/* Use a default name if no name was given. */
name = "Bones";
}
/* Note: the collection name may change after the collection is added to an
* armature, to ensure it is unique within the armature. */
std::string alloc_name = std::string(__func__) + "('" + name + "')";
BoneCollection *bcoll = MEM_cnew<BoneCollection>(alloc_name.c_str());
BLI_strncpy(bcoll->name, name, sizeof(bcoll->name));
bcoll->flags = default_flags;
bcoll->prop = nullptr;
return bcoll;
}
void ANIM_bonecoll_free(BoneCollection *bcoll)
{
BLI_assert_msg(BLI_listbase_is_empty(&bcoll->bones),
"bone collection still has bones assigned to it, will cause dangling pointers in "
"bone runtime data");
MEM_delete(bcoll);
}
void ANIM_armature_runtime_refresh(bArmature *armature)
{
ANIM_armature_runtime_free(armature);
ANIM_armature_bonecoll_active_set(armature, armature->active_collection);
/* Construct the bone-to-collections mapping. */
LISTBASE_FOREACH (BoneCollection *, bcoll, &armature->collections) {
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
BoneCollectionReference *ref = MEM_cnew<BoneCollectionReference>(__func__);
ref->bcoll = bcoll;
BLI_addtail(&member->bone->runtime.collections, ref);
}
}
}
void ANIM_armature_runtime_free(bArmature *armature)
{
/* Free the bone-to-its-collections mapping. */
ANIM_armature_foreach_bone(&armature->bonebase,
[&](Bone *bone) { BLI_freelistN(&bone->runtime.collections); });
}
static void bonecoll_ensure_name_unique(bArmature *armature, BoneCollection *bcoll)
{
BLI_uniquename(&armature->collections,
bcoll,
bcoll->name,
'.',
offsetof(BoneCollection, name),
sizeof(bcoll->name));
}
BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature, const char *name)
{
BoneCollection *bcoll = ANIM_bonecoll_new(name);
bonecoll_ensure_name_unique(armature, bcoll);
BLI_addtail(&armature->collections, bcoll);
return bcoll;
}
static void armature_bonecoll_active_clear(bArmature *armature)
{
armature->runtime.active_collection_index = -1;
armature->active_collection = nullptr;
}
void ANIM_armature_bonecoll_active_set(bArmature *armature, BoneCollection *bcoll)
{
if (bcoll == nullptr) {
armature_bonecoll_active_clear(armature);
return;
}
const int index = BLI_findindex(&armature->collections, bcoll);
if (index == -1) {
/* TODO: print warning? Or just ignore this case? */
armature_bonecoll_active_clear(armature);
return;
}
armature->runtime.active_collection_index = index;
armature->active_collection = bcoll;
}
void ANIM_armature_bonecoll_active_index_set(bArmature *armature, const int bone_collection_index)
{
if (bone_collection_index < 0) {
armature_bonecoll_active_clear(armature);
return;
}
void *found_link = BLI_findlink(&armature->collections, bone_collection_index);
BoneCollection *bcoll = static_cast<BoneCollection *>(found_link);
if (bcoll == nullptr) {
/* TODO: print warning? Or just ignore this case? */
armature_bonecoll_active_clear(armature);
return;
}
armature->runtime.active_collection_index = bone_collection_index;
armature->active_collection = bcoll;
}
bool ANIM_armature_bonecoll_move(bArmature *armature, BoneCollection *bcoll, const int step)
{
if (bcoll == NULL) {
return false;
}
if (!BLI_listbase_link_move(&armature->collections, bcoll, step)) {
return false;
}
if (bcoll == armature->active_collection) {
armature->runtime.active_collection_index = BLI_findindex(&armature->collections, bcoll);
}
return true;
}
void ANIM_armature_bonecoll_name_set(bArmature *armature, BoneCollection *bcoll, const char *name)
{
char old_name[sizeof(bcoll->name)];
BLI_strncpy(old_name, bcoll->name, sizeof(bcoll->name));
BLI_strncpy(bcoll->name, name, sizeof(bcoll->name));
bonecoll_ensure_name_unique(armature, bcoll);
BKE_animdata_fix_paths_rename_all(&armature->id, "collections", old_name, bcoll->name);
}
void ANIM_armature_bonecoll_remove(bArmature *armature, BoneCollection *bcoll)
{
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);
}
}
BLI_remlink_safe(&armature->collections, bcoll);
ANIM_bonecoll_free(bcoll);
/* Make sure the active collection is correct. */
const int num_collections = BLI_listbase_count(&armature->collections);
const int active_index = min_ii(armature->runtime.active_collection_index, num_collections - 1);
ANIM_armature_bonecoll_active_index_set(armature, active_index);
}
BoneCollection *ANIM_armature_bonecoll_get_by_name(bArmature *armature, const char *name)
{
LISTBASE_FOREACH (BoneCollection *, bcoll, &armature->collections) {
if (STREQ(bcoll->name, name)) {
return bcoll;
}
}
return nullptr;
}
void ANIM_bonecoll_hide(BoneCollection *bcoll)
{
bcoll->flags &= ~BONE_COLLECTION_VISIBLE;
}
/* Store the bone's membership on the collection. */
static void add_membership(BoneCollection *bcoll, Bone *bone)
{
BoneCollectionMember *member = MEM_cnew<BoneCollectionMember>(__func__);
member->bone = bone;
BLI_addtail(&bcoll->bones, member);
}
bool ANIM_armature_bonecoll_assign(BoneCollection *bcoll, Bone *bone)
{
/* Precondition check: bail out if already a member. */
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
if (member->bone == bone) {
return false;
}
}
add_membership(bcoll, bone);
/* Store reverse membership on the bone. */
BoneCollectionReference *ref = MEM_cnew<BoneCollectionReference>(__func__);
ref->bcoll = bcoll;
BLI_addtail(&bone->runtime.collections, ref);
return true;
}
bool ANIM_armature_bonecoll_assign_editbone(BoneCollection *bcoll, EditBone *ebone)
{
/* Precondition check: bail out if already a member. */
LISTBASE_FOREACH (BoneCollectionReference *, ref, &ebone->bone_collections) {
if (ref->bcoll == bcoll) {
return false;
}
}
/* Store membership on the edit bone. Bones will be rebuilt when the armature
* goes out of edit mode, and by then the newly created bones will be added to
* the actual collection on the Armature. */
BoneCollectionReference *ref = MEM_cnew<BoneCollectionReference>(__func__);
ref->bcoll = bcoll;
BLI_addtail(&ebone->bone_collections, ref);
return true;
}
bool ANIM_armature_bonecoll_assign_and_move(BoneCollection *bcoll, Bone *bone)
{
/* Remove the bone from all its current collections. */
LISTBASE_FOREACH_MUTABLE (BoneCollectionReference *, ref, &bone->runtime.collections) {
ANIM_armature_bonecoll_unassign(ref->bcoll, bone);
}
/* Assign the new collection. */
return ANIM_armature_bonecoll_assign(bcoll, bone);
}
bool ANIM_armature_bonecoll_unassign(BoneCollection *bcoll, Bone *bone)
{
bool was_found = false;
/* Remove membersip from collection. */
LISTBASE_FOREACH_MUTABLE (BoneCollectionMember *, member, &bcoll->bones) {
if (member->bone == bone) {
BLI_freelinkN(&bcoll->bones, member);
was_found = true;
break;
}
}
/* Remove reverse membership from the bone.
* For data consistency sake, this is always done, regardless of whether the
* above loop found the membership. */
LISTBASE_FOREACH_MUTABLE (BoneCollectionReference *, ref, &bone->runtime.collections) {
if (ref->bcoll == bcoll) {
BLI_freelinkN(&bone->runtime.collections, ref);
break;
}
}
return was_found;
}
bool ANIM_armature_bonecoll_unassign_editbone(BoneCollection *bcoll, EditBone *ebone)
{
bool was_found = false;
/* Edit bone membership is only stored on the edit bone itself. */
LISTBASE_FOREACH_MUTABLE (BoneCollectionReference *, ref, &ebone->bone_collections) {
if (ref->bcoll == bcoll) {
BLI_freelinkN(&ebone->bone_collections, ref);
was_found = true;
break;
}
}
return was_found;
}
void ANIM_armature_bonecoll_reconstruct(struct bArmature *armature)
{
/* Remove all the old collection memberships. */
LISTBASE_FOREACH (BoneCollection *, bcoll, &armature->collections) {
BLI_freelistN(&bcoll->bones);
}
/* For all bones, restore their collection memberships. */
ANIM_armature_foreach_bone(&armature->bonebase, [&](Bone *bone) {
LISTBASE_FOREACH (BoneCollectionReference *, ref, &bone->runtime.collections) {
add_membership(ref->bcoll, bone);
}
});
}
static bool any_bone_collection_visible(const ListBase /*BoneCollectionRef*/ *collection_refs)
{
/* Special case: when a bone is not in any collection, it is visible. */
if (BLI_listbase_is_empty(collection_refs)) {
return true;
}
LISTBASE_FOREACH (const BoneCollectionReference *, bcoll_ref, collection_refs) {
const BoneCollection *bcoll = bcoll_ref->bcoll;
if (bcoll->flags & BONE_COLLECTION_VISIBLE) {
return true;
}
}
return false;
}
/* TODO: these two functions were originally implemented for armature layers, hence the armature
* parameters. These should be removed at some point. */
bool ANIM_bonecoll_is_visible(const struct bArmature * /*armature*/, const struct Bone *bone)
{
return any_bone_collection_visible(&bone->runtime.collections);
}
bool ANIM_bonecoll_is_visible_editbone(const struct bArmature * /*armature*/,
const struct EditBone *ebone)
{
return any_bone_collection_visible(&ebone->bone_collections);
}
void ANIM_armature_bonecoll_show_all(bArmature *armature)
{
LISTBASE_FOREACH (BoneCollection *, bcoll, &armature->collections) {
bcoll->flags |= BONE_COLLECTION_VISIBLE;
}
}
void ANIM_armature_bonecoll_hide_all(bArmature *armature)
{
LISTBASE_FOREACH (BoneCollection *, bcoll, &armature->collections) {
bcoll->flags &= ~BONE_COLLECTION_VISIBLE;
}
}
/* ********************************* */
/* Armature Layers transitional API. */
void ANIM_armature_enable_layers(bArmature *armature, const int layers)
void ANIM_armature_enable_layers(bArmature *armature, const int /*layers*/)
{
armature->layer |= layers;
}
void ANIM_armature_disable_all_layers(bArmature *armature)
{
armature->layer = 0;
// TODO: reimplement properly.
// armature->layer |= layers;
ANIM_armature_bonecoll_show_all(armature);
}
void ANIM_bone_set_layer_ebone(EditBone *ebone, const int layer)
{
// TODO: reimplement for bone collections.
ebone->layer = layer;
}
void ANIM_bone_set_ebone_layer_from_armature(EditBone *ebone, const bArmature *armature)
void ANIM_armature_bonecoll_assign_active(const bArmature *armature, EditBone *ebone)
{
ebone->layer = armature->layer;
if (armature->active_collection == nullptr) {
/* 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;
}
ANIM_armature_bonecoll_assign_editbone(armature->active_collection, ebone);
}
void ANIM_armature_ensure_first_layer_enabled(bArmature *armature)
{
armature->layer = 1;
}
void ANIM_armature_ensure_layer_enabled_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)) {
return;
}
armature->layer |= 1U << bitscan_forward_uint(bone->layer);
/* Making the first collection visible is enough to make the bone visible.
*
* Since bones without collection are considered visible,
* bone->runtime.collections.first is certainly a valid pointer. */
BoneCollectionReference *ref = static_cast<BoneCollectionReference *>(
bone->runtime.collections.first);
ref->bcoll->flags |= BONE_COLLECTION_VISIBLE;
}
void ANIM_armature_ensure_layer_enabled_from_ebone(bArmature *armature, const EditBone *ebone)
void ANIM_armature_bonecoll_show_from_ebone(bArmature *armature, const EditBone *ebone)
{
if (ANIM_bonecoll_is_visible_editbone(armature, ebone)) {
return;
}
armature->layer |= 1U << bitscan_forward_uint(ebone->layer);
/* Making the first collection visible is enough to make the bone visible.
*
* Since bones without collection are considered visible,
* ebone->bone_collections.first is certainly a valid pointer. */
BoneCollectionReference *ref = static_cast<BoneCollectionReference *>(
ebone->bone_collections.first);
ref->bcoll->flags |= BONE_COLLECTION_VISIBLE;
}
void ANIM_armature_ensure_layer_enabled_from_pchan(bArmature *armature, const bPoseChannel *pchan)
void ANIM_armature_bonecoll_show_from_pchan(bArmature *armature, const bPoseChannel *pchan)
{
ANIM_armature_ensure_layer_enabled_from_bone(armature, pchan->bone);
ANIM_armature_bonecoll_show_from_bone(armature, pchan->bone);
}

View File

@ -0,0 +1,115 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string.h"
#include "ANIM_bone_collections.h"
#include "testing/testing.h"
namespace blender::animrig::tests {
TEST(ANIM_bone_collections, bonecoll_new_free)
{
BoneCollection *bcoll = ANIM_bonecoll_new("some name");
EXPECT_NE(nullptr, bcoll);
EXPECT_EQ("some name", std::string(bcoll->name));
EXPECT_TRUE(BLI_listbase_is_empty(&bcoll->bones));
EXPECT_EQ(BONE_COLLECTION_VISIBLE | BONE_COLLECTION_SELECTABLE, bcoll->flags);
ANIM_bonecoll_free(bcoll);
}
TEST(ANIM_bone_collections, bonecoll_default_name)
{
{
BoneCollection *bcoll = ANIM_bonecoll_new("");
EXPECT_EQ("Bones", std::string(bcoll->name));
ANIM_bonecoll_free(bcoll);
}
{
BoneCollection *bcoll = ANIM_bonecoll_new(nullptr);
EXPECT_EQ("Bones", std::string(bcoll->name));
ANIM_bonecoll_free(bcoll);
}
}
class ANIM_armature_bone_collections : public testing::Test {
protected:
bArmature arm;
Bone bone1, bone2, bone3;
void SetUp() override
{
memset(&arm, 0, sizeof(arm));
memset(&bone1, 0, sizeof(Bone));
memset(&bone2, 0, sizeof(Bone));
memset(&bone3, 0, sizeof(Bone));
STRNCPY(bone1.name, "bone1");
STRNCPY(bone2.name, "bone2");
STRNCPY(bone3.name, "bone3");
BLI_addtail(&arm.bonebase, &bone1); /* bone1 is root bone. */
BLI_addtail(&arm.bonebase, &bone2); /* bone2 is root bone. */
BLI_addtail(&bone2.childbase, &bone3); /* bone3 has bone2 as parent. */
}
};
TEST_F(ANIM_armature_bone_collections, armature_owned_collections)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "collection");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "collection");
EXPECT_EQ(std::string("collection"), std::string(bcoll1->name));
EXPECT_EQ(std::string("collection.001"), std::string(bcoll2->name));
ANIM_armature_bonecoll_remove(&arm, bcoll1);
ANIM_armature_bonecoll_remove(&arm, bcoll2);
}
TEST_F(ANIM_armature_bone_collections, bones_assign_unassign)
{
BoneCollection *bcoll = ANIM_armature_bonecoll_new(&arm, "collection");
ANIM_armature_bonecoll_assign(bcoll, &bone1);
ANIM_armature_bonecoll_assign(bcoll, &bone2);
ASSERT_EQ(2, BLI_listbase_count(&bcoll->bones)) << "expecting two bones in collection";
EXPECT_EQ(&bone1, static_cast<BoneCollectionMember *>(BLI_findlink(&bcoll->bones, 0))->bone);
EXPECT_EQ(&bone2, static_cast<BoneCollectionMember *>(BLI_findlink(&bcoll->bones, 1))->bone);
EXPECT_EQ(bcoll, static_cast<BoneCollectionReference *>(bone1.runtime.collections.first)->bcoll)
<< "expecting back-reference to collection in bone1 runtime data";
EXPECT_EQ(bcoll, static_cast<BoneCollectionReference *>(bone2.runtime.collections.first)->bcoll)
<< "expecting back-reference to collection in bone2 runtime data";
ANIM_armature_bonecoll_unassign(bcoll, &bone1);
ANIM_armature_bonecoll_unassign(bcoll, &bone2);
EXPECT_EQ(0, BLI_listbase_count(&bone1.runtime.collections))
<< "expecting back-references in bone1 runtime data to be cleared when unassigned";
EXPECT_EQ(0, BLI_listbase_count(&bone2.runtime.collections))
<< "expecting back-references in bone2 runtime data to be cleared when unassigned";
ANIM_armature_bonecoll_remove(&arm, bcoll);
}
TEST_F(ANIM_armature_bone_collections, bones_assign_remove)
{
BoneCollection *bcoll = ANIM_armature_bonecoll_new(&arm, "collection");
ANIM_armature_bonecoll_assign(bcoll, &bone1);
ANIM_armature_bonecoll_assign(bcoll, &bone2);
ANIM_armature_bonecoll_remove(&arm, bcoll);
EXPECT_EQ(0, BLI_listbase_count(&bone1.runtime.collections))
<< "expecting back-references in bone1 runtime data to be cleared when the collection is "
"removed";
EXPECT_EQ(0, BLI_listbase_count(&bone2.runtime.collections))
<< "expecting back-references in bone2 runtime data to be cleared when the collection is "
"removed";
}
} // namespace blender::animrig::tests

View File

@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*/
#include "ANIM_bonecolor.hh"
#include "UI_resources.hh"
#include <cstring>
namespace blender::animrig {
BoneColor::BoneColor()
{
this->palette_index = 0;
}
BoneColor::BoneColor(const BoneColor &other)
{
this->palette_index = other.palette_index;
std::memcpy(&this->custom, &other.custom, sizeof(this->custom));
}
BoneColor::~BoneColor() {}
const ThemeWireColor *BoneColor::effective_color() const
{
const int8_t color_index = this->palette_index;
if (color_index == 0) {
return nullptr;
}
if (color_index < 0) {
return &this->custom;
}
const bTheme *btheme = UI_GetTheme();
return &btheme->tarm[(color_index - 1)];
}
}; // namespace blender::animrig

View File

@ -24,6 +24,7 @@ struct bArmature;
/* The following structures are defined in DNA_action_types.h, and DNA_anim_types.h */
struct AnimationEvalContext;
struct BoneColor;
struct FCurve;
struct ID;
struct Main;
@ -125,6 +126,19 @@ void set_active_action_group(struct bAction *act, struct bActionGroup *agrp, sho
*/
void action_group_colors_sync(struct bActionGroup *grp, const struct bActionGroup *ref_grp);
/**
* Set colors used on this action group.
*/
void action_group_colors_set(struct bActionGroup *grp, const struct BoneColor *color);
/**
* Set colors used on this action group, using the color of the pose bone.
*
* If `pchan->color` is set to a non-default color, that is used. Otherwise the
* armature bone color is used.
*/
void action_group_colors_set_from_posebone(bActionGroup *grp, const bPoseChannel *pchan);
/**
* Add a new action group with the given name to the action>
*/
@ -253,29 +267,29 @@ void BKE_pose_channel_session_uuid_generate(struct bPoseChannel *pchan);
*/
struct bPoseChannel *BKE_pose_channel_find_name(const struct bPose *pose, const char *name);
/**
* Checks if the bone is on a visible armature layer
* Checks if the bone is on a visible bone collection
*
* \return true if on a visible layer, false otherwise.
*/
bool BKE_pose_is_layer_visible(const struct bArmature *arm,
const struct bPoseChannel *pchan) ATTR_WARN_UNUSED_RESULT;
bool BKE_pose_is_bonecoll_visible(const struct bArmature *arm,
const struct bPoseChannel *pchan) ATTR_WARN_UNUSED_RESULT;
/**
* Find the active pose-channel for an object
*
* \param check_arm_layer: checks if the bone is on a visible armature layer (this might be skipped
* \param check_bonecoll: checks if the bone is on a visible bone collection (this might be skipped
* (e.g. for "Show Active" from the Outliner).
* \return #bPoseChannel if found or NULL.
* \note #Object, not #bPose is used here, as we need info (layer/active bone) from Armature.
* \note #Object, not #bPose is used here, as we need info (collection/active bone) from Armature.
*/
struct bPoseChannel *BKE_pose_channel_active(struct Object *ob, bool check_arm_layer);
struct bPoseChannel *BKE_pose_channel_active(struct Object *ob, bool check_bonecoll);
/**
* Find the active pose-channel for an object if it is on a visible armature layer
* (calls #BKE_pose_channel_active with check_arm_layer set to true)
* Find the active pose-channel for an object if it is on a visible bone collection
* (calls #BKE_pose_channel_active with check_bonecoll set to true)
*
* \return #bPoseChannel if found or NULL.
* \note #Object, not #bPose is used here, as we need info (layer/active bone) from Armature.
* \note #Object, not #bPose is used here, as we need info (collection/active bone) from Armature.
*/
struct bPoseChannel *BKE_pose_channel_active_if_layer_visible(struct Object *ob)
struct bPoseChannel *BKE_pose_channel_active_if_bonecoll_visible(struct Object *ob)
ATTR_WARN_UNUSED_RESULT;
/**
* Use this when detecting the "other selected bone",

View File

@ -8,6 +8,8 @@
*/
#include "BLI_listbase.h"
#include "DNA_armature_types.h"
#ifdef __cplusplus
extern "C" {
#endif
@ -101,6 +103,9 @@ typedef struct EditBone {
/** connected child temporary during drawing */
struct EditBone *bbone_child;
BoneColor color; /* MUST be named the same as in bPoseChannel and Bone structs. */
ListBase /*BoneCollectionReference*/ bone_collections;
/* Used to store temporary data */
union {
struct EditBone *ebone;
@ -200,8 +205,6 @@ void BKE_armature_bone_hash_free(struct bArmature *arm);
bool BKE_armature_bone_flag_test_recursive(const struct Bone *bone, int flag);
void BKE_armature_refresh_layer_used(struct Depsgraph *depsgraph, struct bArmature *arm);
/**
* Using `vec` with dist to bone `b1 - b2`.
*/
@ -565,11 +568,8 @@ void BKE_pchan_bbone_deform_segment_index(const struct bPoseChannel *pchan,
int *r_index,
float *r_blend_next);
/* like EBONE_VISIBLE */
#define PBONE_VISIBLE(arm, bone) \
(CHECK_TYPE_INLINE(arm, bArmature *), \
CHECK_TYPE_INLINE(bone, Bone *), \
(((bone)->layer & (arm)->layer) && !((bone)->flag & BONE_HIDDEN_P)))
/* like EBONE_VISIBLE, be sure to #include "ANIM_bone_collections.h". */
#define PBONE_VISIBLE(arm, bone) ANIM_bone_is_visible(arm, bone)
#define PBONE_SELECTABLE(arm, bone) \
(PBONE_VISIBLE(arm, bone) && !((bone)->flag & BONE_UNSELECTABLE))

View File

@ -261,6 +261,21 @@ PointerRNA CTX_data_pointer_get_type_silent(const bContext *C,
const char *member,
StructRNA *type);
ListBase CTX_data_collection_get(const bContext *C, const char *member);
/**
* For each pointer in collection_pointers, remap it to point to `ptr->propname`.
*
* Example:
*
* lb = CTX_data_collection_get(C, "selected_pose_bones"); // lb contains pose bones.
* CTX_data_collection_remap_property(lb, "color"); // lb now contains bone colors.
*
* NOTE: this alters the items contained in the given listbase.
* It does not change the listbase itself.
*/
void CTX_data_collection_remap_property(ListBase /*CollectionPointerLink*/ collection_pointers,
const char *propname);
/**
* \param C: Context.
* \param use_store: Use 'C->wm.store'.

View File

@ -534,6 +534,7 @@ set(SRC
)
set(LIB
PRIVATE bf::animrig
bf_asset_system
bf_blenfont
PRIVATE bf::blenlib

View File

@ -62,6 +62,9 @@
#include "BLO_read_write.h"
#include "ANIM_bone_collections.h"
#include "ANIM_bonecolor.hh"
#include "CLG_log.h"
static CLG_LogRef LOG = {"bke.action"};
@ -367,6 +370,32 @@ void action_group_colors_sync(bActionGroup *grp, const bActionGroup *ref_grp)
}
}
void action_group_colors_set_from_posebone(bActionGroup *grp, const bPoseChannel *pchan)
{
if (pchan->color.palette_index == 0) {
action_group_colors_set(grp, &pchan->bone->color);
}
else {
action_group_colors_set(grp, &pchan->color);
}
}
void action_group_colors_set(bActionGroup *grp, const BoneColor *color)
{
const blender::animrig::BoneColor &bone_color = color->wrap();
grp->customCol = bone_color.palette_index;
const ThemeWireColor *effective_color = bone_color.effective_color();
if (effective_color) {
/* The drawing code assumes that grp->cs always contains the effective
* color. This is why the effective color is always written to it, and why
* the above action_group_colors_sync() function exists: it needs to update
* grp->cs in case the theme changes. */
memcpy(&grp->cs, effective_color, sizeof(grp->cs));
}
}
bActionGroup *action_groups_add_new(bAction *act, const char name[])
{
bActionGroup *agrp;
@ -651,12 +680,12 @@ bool BKE_pose_channels_is_valid(const bPose *pose)
#endif
bool BKE_pose_is_layer_visible(const bArmature *arm, const bPoseChannel *pchan)
bool BKE_pose_is_bonecoll_visible(const bArmature *arm, const bPoseChannel *pchan)
{
return (pchan->bone->layer & arm->layer);
return pchan->bone && ANIM_bonecoll_is_visible(arm, pchan->bone);
}
bPoseChannel *BKE_pose_channel_active(Object *ob, const bool check_arm_layer)
bPoseChannel *BKE_pose_channel_active(Object *ob, const bool check_bonecoll)
{
bArmature *arm = static_cast<bArmature *>((ob) ? ob->data : nullptr);
if (ELEM(nullptr, ob, ob->pose, arm)) {
@ -666,7 +695,7 @@ bPoseChannel *BKE_pose_channel_active(Object *ob, const bool check_arm_layer)
/* find active */
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
if ((pchan->bone) && (pchan->bone == arm->act_bone)) {
if (!check_arm_layer || BKE_pose_is_layer_visible(arm, pchan)) {
if (!check_bonecoll || ANIM_bonecoll_is_visible(arm, pchan->bone)) {
return pchan;
}
}
@ -675,7 +704,7 @@ bPoseChannel *BKE_pose_channel_active(Object *ob, const bool check_arm_layer)
return nullptr;
}
bPoseChannel *BKE_pose_channel_active_if_layer_visible(Object *ob)
bPoseChannel *BKE_pose_channel_active_if_bonecoll_visible(Object *ob)
{
return BKE_pose_channel_active(ob, true);
}
@ -688,7 +717,7 @@ bPoseChannel *BKE_pose_channel_active_or_first_selected(Object *ob)
return nullptr;
}
bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob);
bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (pchan && (pchan->bone->flag & BONE_SELECTED) && PBONE_VISIBLE(arm, pchan->bone)) {
return pchan;
}

View File

@ -48,6 +48,8 @@
#include "BKE_object.h"
#include "BKE_scene.h"
#include "ANIM_bone_collections.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_query.h"
@ -81,6 +83,10 @@ static void armature_init_data(ID *id)
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(armature, id));
MEMCPY_STRUCT_AFTER(armature, DNA_struct_default_get(bArmature), id);
/* Give the Armature its default bone collection. */
BoneCollection *default_bonecoll = ANIM_bonecoll_new("");
BLI_addhead(&armature->collections, default_bonecoll);
}
/**
@ -131,12 +137,45 @@ static void armature_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, c
armature_dst->edbo = nullptr;
armature_dst->act_edbone = nullptr;
/* Duplicate bone collections & assignments. */
BLI_duplicatelist(&armature_dst->collections, &armature_src->collections);
LISTBASE_FOREACH (BoneCollection *, bcoll, &armature_dst->collections) {
/* ID properties. */
if (bcoll->prop) {
bcoll->prop = IDP_CopyProperty(bcoll->prop);
}
/* Bone references. */
BLI_duplicatelist(&bcoll->bones, &bcoll->bones);
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
member->bone = BKE_armature_find_bone_name(armature_dst, member->bone->name);
}
}
ANIM_armature_bonecoll_active_index_set(armature_dst,
armature_src->runtime.active_collection_index);
ANIM_armature_runtime_refresh(armature_dst);
}
/** Free (or release) any data used by this armature (does not free the armature itself). */
static void armature_free_data(ID *id)
{
bArmature *armature = (bArmature *)id;
ANIM_armature_runtime_free(armature);
/* Free all BoneCollectionMembership objects. */
LISTBASE_FOREACH_MUTABLE (BoneCollection *, bcoll, &armature->collections) {
/* ID properties. */
if (bcoll->prop) {
IDP_FreeProperty(bcoll->prop);
bcoll->prop = nullptr;
}
/* Bone references. */
BLI_freelistN(&bcoll->bones);
}
BLI_freelistN(&armature->collections);
BKE_armature_bone_hash_free(armature);
BKE_armature_bonelist_free(&armature->bonebase, false);
@ -171,6 +210,16 @@ static void armature_foreach_id_editbone(EditBone *edit_bone, LibraryForeachIDDa
data));
}
static void armature_foreach_id_bone_collection(BoneCollection *bcoll, LibraryForeachIDData *data)
{
BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(
data,
IDP_foreach_property(bcoll->prop,
IDP_TYPE_FILTER_ID,
BKE_lib_query_idpropertiesForeachIDLink_callback,
data));
}
static void armature_foreach_id(ID *id, LibraryForeachIDData *data)
{
bArmature *arm = (bArmature *)id;
@ -183,6 +232,11 @@ static void armature_foreach_id(ID *id, LibraryForeachIDData *data)
BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, armature_foreach_id_editbone(edit_bone, data));
}
}
LISTBASE_FOREACH (BoneCollection *, bcoll, &arm->collections) {
BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data,
armature_foreach_id_bone_collection(bcoll, data));
}
}
static void write_bone(BlendWriter *writer, Bone *bone)
@ -190,8 +244,11 @@ static void write_bone(BlendWriter *writer, Bone *bone)
/* PATCH for upward compatibility after 2.37+ armature recode */
bone->size[0] = bone->size[1] = bone->size[2] = 1.0f;
/* Write this bone */
/* Write this bone, except for its runtime data. */
const Bone_Runtime runtime_backup = bone->runtime;
memset(&bone->runtime, 0, sizeof(bone->runtime));
BLO_write_struct(writer, Bone, bone);
bone->runtime = runtime_backup;
/* Write ID Properties -- and copy this comment EXACTLY for easy finding
* of library blocks that implement this. */
@ -205,6 +262,20 @@ static void write_bone(BlendWriter *writer, Bone *bone)
}
}
static void write_bone_collection(BlendWriter *writer, BoneCollection *bcoll)
{
/* Write this bone collection. */
BLO_write_struct(writer, BoneCollection, bcoll);
/* Write ID Properties -- and copy this comment EXACTLY for easy finding
* of library blocks that implement this. */
if (bcoll->prop) {
IDP_BlendWrite(writer, bcoll->prop);
}
BLO_write_struct_list(writer, BoneCollectionMember, &bcoll->bones);
}
static void armature_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
bArmature *arm = (bArmature *)id;
@ -216,13 +287,21 @@ static void armature_blend_write(BlendWriter *writer, ID *id, const void *id_add
arm->needs_flush_to_id = 0;
arm->act_edbone = nullptr;
const bArmature_Runtime runtime_backup = arm->runtime;
memset(&arm->runtime, 0, sizeof(arm->runtime));
BLO_write_id_struct(writer, bArmature, id_address, &arm->id);
BKE_id_blend_write(writer, &arm->id);
arm->runtime = runtime_backup;
/* Direct data */
LISTBASE_FOREACH (Bone *, bone, &arm->bonebase) {
write_bone(writer, bone);
}
LISTBASE_FOREACH (BoneCollection *, bcoll, &arm->collections) {
write_bone_collection(writer, bcoll);
}
}
static void direct_link_bones(BlendDataReader *reader, Bone *bone)
@ -241,6 +320,19 @@ static void direct_link_bones(BlendDataReader *reader, Bone *bone)
LISTBASE_FOREACH (Bone *, child, &bone->childbase) {
direct_link_bones(reader, child);
}
memset(&bone->runtime, 0, sizeof(bone->runtime));
}
static void direct_link_bone_collection(BlendDataReader *reader, BoneCollection *bcoll)
{
BLO_read_data_address(reader, &bcoll->prop);
IDP_BlendDataRead(reader, &bcoll->prop);
BLO_read_list(reader, &bcoll->bones);
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
BLO_read_data_address(reader, &member->bone);
}
}
static void armature_blend_read_data(BlendDataReader *reader, ID *id)
@ -256,10 +348,19 @@ static void armature_blend_read_data(BlendDataReader *reader, ID *id)
direct_link_bones(reader, bone);
}
BLO_read_list(reader, &arm->collections);
LISTBASE_FOREACH (BoneCollection *, bcoll, &arm->collections) {
direct_link_bone_collection(reader, bcoll);
}
BLO_read_data_address(reader, &arm->active_collection);
BLO_read_data_address(reader, &arm->act_bone);
arm->act_edbone = nullptr;
BKE_armature_bone_hash_make(arm);
memset(&arm->runtime, 0, sizeof(arm->runtime));
ANIM_armature_runtime_refresh(arm);
}
IDTypeInfo IDType_ID_AR = {
@ -329,6 +430,7 @@ void BKE_armature_bonelist_free(ListBase *lb, const bool do_id_user)
if (bone->prop) {
IDP_FreeProperty_ex(bone->prop, do_id_user);
}
BLI_freelistN(&bone->runtime.collections);
BKE_armature_bonelist_free(&bone->childbase, do_id_user);
}
@ -362,6 +464,11 @@ static void copy_bonechildren(Bone *bone_dst,
bone_dst->prop = IDP_CopyProperty_ex(bone_src->prop, flag);
}
/* Clear the runtime cache of the collection relations, these will be
* reconstructed after the entire armature duplication is done. Don't free,
* just clear, as these pointers refer to the original and not the copy. */
BLI_listbase_clear(&bone_dst->runtime.collections);
/* Copy this bone's list */
BLI_duplicatelist(&bone_dst->childbase, &bone_src->childbase);
@ -626,38 +733,7 @@ bool BKE_armature_bone_flag_test_recursive(const Bone *bone, int flag)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Armature Layer Refresh Used
* \{ */
static void armature_refresh_layer_used_recursive(bArmature *arm, ListBase *bones)
{
LISTBASE_FOREACH (Bone *, bone, bones) {
arm->layer_used |= bone->layer;
armature_refresh_layer_used_recursive(arm, &bone->childbase);
}
}
void BKE_armature_refresh_layer_used(Depsgraph *depsgraph, bArmature *arm)
{
if (arm->edbo != nullptr) {
/* Don't perform this update when the armature is in edit mode. In that case it should be
* handled by ED_armature_edit_refresh_layer_used(). */
return;
}
arm->layer_used = 0;
armature_refresh_layer_used_recursive(arm, &arm->bonebase);
if (depsgraph == nullptr || DEG_is_active(depsgraph)) {
bArmature *arm_orig = (bArmature *)DEG_get_original_id(&arm->id);
arm_orig->layer_used = arm->layer_used;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Armature Layer Refresh Used
/** \name Bone auto-side name support
* \{ */
bool bone_autoside_name(

View File

@ -13,6 +13,8 @@
#include "DNA_action_types.h"
#include "DNA_armature_types.h"
#include "ANIM_bone_collections.h"
namespace blender::bke {
namespace {

View File

@ -11,6 +11,8 @@
#include "DNA_armature_types.h"
#include "ANIM_bone_collections.h"
#include "testing/testing.h"
namespace blender::bke::tests {
@ -357,21 +359,18 @@ class BKE_armature_find_selected_bones_test : public testing::Test {
void SetUp() override
{
memset(&arm, 0, sizeof(arm));
memset(&bone1, 0, sizeof(Bone));
memset(&bone2, 0, sizeof(Bone));
memset(&bone3, 0, sizeof(Bone));
STRNCPY(bone1.name, "bone1");
STRNCPY(bone2.name, "bone2");
STRNCPY(bone3.name, "bone3");
arm.bonebase = {nullptr, nullptr};
bone1.childbase = {nullptr, nullptr};
bone2.childbase = {nullptr, nullptr};
bone3.childbase = {nullptr, nullptr};
BLI_addtail(&arm.bonebase, &bone1); /* bone1 is root bone. */
BLI_addtail(&arm.bonebase, &bone2); /* bone2 is root bone. */
BLI_addtail(&bone2.childbase, &bone3); /* bone3 has bone2 as parent. */
/* Make sure the armature & its bones are visible, to make them selectable. */
arm.layer = bone1.layer = bone2.layer = bone3.layer = 1;
}
};

View File

@ -500,6 +500,16 @@ ListBase CTX_data_collection_get(const bContext *C, const char *member)
return list;
}
void CTX_data_collection_remap_property(ListBase /*CollectionPointerLink*/ collection_pointers,
const char *propname)
{
LISTBASE_FOREACH (CollectionPointerLink *, link, &collection_pointers) {
PointerRNA original_ptr = link->ptr;
PointerRNA remapped_ptr = RNA_pointer_get(&original_ptr, propname);
link->ptr = remapped_ptr;
}
}
int /*eContextResult*/ CTX_data_get(const bContext *C,
const char *member,
PointerRNA *r_ptr,

View File

@ -60,6 +60,7 @@ set(SRC
)
set(LIB
PRIVATE bf::animrig
bf_blenkernel
PRIVATE bf::blenlib
PRIVATE bf::dna

View File

@ -27,10 +27,12 @@
#include "BLI_assert.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_vector.h"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "BKE_armature.h"
#include "BKE_effect.h"
#include "BKE_grease_pencil.hh"
#include "BKE_idprop.hh"
@ -41,6 +43,11 @@
#include "BKE_scene.h"
#include "BKE_tracking.h"
#include "ANIM_armature_iter.hh"
#include "ANIM_bone_collections.h"
#include "ED_armature.hh"
#include "BLT_translation.h"
#include "BLO_read_write.h"
@ -64,6 +71,153 @@ static void version_composite_nodetree_null_id(bNodeTree *ntree, Scene *scene)
}
}
/* Move bonegroup color to the individual bones. */
static void version_bonegroup_migrate_color(Main *bmain)
{
using PoseSet = blender::Set<bPose *>;
blender::Map<bArmature *, PoseSet> armature_poses;
/* Gather a mapping from armature to the poses that use it. */
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
if (ob->type != OB_ARMATURE || !ob->pose) {
continue;
}
bArmature *arm = reinterpret_cast<bArmature *>(ob->data);
BLI_assert_msg(GS(arm->id.name) == ID_AR,
"Expected ARMATURE object to have an Armature as data");
PoseSet &pose_set = armature_poses.lookup_or_add_default(arm);
pose_set.add(ob->pose);
}
/* Move colors from the pose's bonegroup to either the armature bones or the
* pose bones, depending on how many poses use the Armature. */
for (const PoseSet &pose_set : armature_poses.values()) {
/* If the Armature is shared, the bone group colors might be different, and thus they have to
* be stored on the pose bones. If the Armature is NOT shared, the bone colors can be stored
* directly on the Armature bones. */
const bool store_on_armature = pose_set.size() == 1;
for (bPose *pose : pose_set) {
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
const bActionGroup *bgrp = (const bActionGroup *)BLI_findlink(&pose->agroups,
(pchan->agrp_index - 1));
if (!bgrp) {
continue;
}
BoneColor &bone_color = store_on_armature ? pchan->bone->color : pchan->color;
bone_color.palette_index = bgrp->customCol;
memcpy(&bone_color.custom, &bgrp->cs, sizeof(bone_color.custom));
}
}
}
}
static void version_bonelayers_to_bonecollections(Main *bmain)
{
char bcoll_name[MAX_NAME];
char custom_prop_name[MAX_NAME];
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
if (ob->type != OB_ARMATURE || !ob->pose) {
continue;
}
bArmature *arm = reinterpret_cast<bArmature *>(ob->data);
IDProperty *arm_idprops = IDP_GetProperties(&arm->id, false);
BLI_assert_msg(arm->edbo == nullptr, "did not expect an Armature to be saved in edit mode");
const uint layer_used = arm->layer_used;
/* Construct a bone collection for each layer that contains at least one bone. */
blender::Vector<std::pair<uint, BoneCollection *>> layermask_collection;
for (uint layer = 0; layer < 32; ++layer) {
const uint layer_mask = 1u << layer;
if ((layer_used & layer_mask) == 0) {
/* Layer is empty, so no need to convert to collection. */
continue;
}
/* Construct a suitable name for this bone layer. */
bcoll_name[0] = '\0';
if (arm_idprops) {
/* See if we can use the layer name from the Bone Manager add-on. This is a popular add-on
* for managing bone layers and giving them names. */
BLI_snprintf(custom_prop_name, sizeof(custom_prop_name), "layer_name_%u", layer);
IDProperty *prop = IDP_GetPropertyFromGroup(arm_idprops, custom_prop_name);
if (prop != nullptr && prop->type == IDP_STRING && IDP_String(prop)[0] != '\0') {
BLI_snprintf(
bcoll_name, sizeof(bcoll_name), "Layer %u - %s", layer + 1, IDP_String(prop));
}
}
if (bcoll_name[0] == '\0') {
/* Either there was no name defined in the custom property, or
* it was the empty string. */
BLI_snprintf(bcoll_name, sizeof(bcoll_name), "Layer %u", layer + 1);
}
/* Create a new bone collection for this layer. */
BoneCollection *bcoll = ANIM_armature_bonecoll_new(arm, bcoll_name);
layermask_collection.append(std::make_pair(layer_mask, bcoll));
if ((arm->layer & layer_mask) == 0) {
ANIM_bonecoll_hide(bcoll);
}
}
/* Iterate over the bones to assign them to their layers. */
blender::animrig::ANIM_armature_foreach_bone(&arm->bonebase, [&](Bone *bone) {
for (auto layer_bcoll : layermask_collection) {
const uint layer_mask = layer_bcoll.first;
if ((bone->layer & layer_mask) == 0) {
continue;
}
BoneCollection *bcoll = layer_bcoll.second;
ANIM_armature_bonecoll_assign(bcoll, bone);
}
});
}
}
static void version_bonegroups_to_bonecollections(Main *bmain)
{
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
if (ob->type != OB_ARMATURE || !ob->pose) {
continue;
}
/* Convert the bone groups on a bone-by-bone basis. */
bArmature *arm = reinterpret_cast<bArmature *>(ob->data);
bPose *pose = ob->pose;
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
/* Find the bone group of this pose channel. */
const bActionGroup *bgrp = (const bActionGroup *)BLI_findlink(&pose->agroups,
(pchan->agrp_index - 1));
if (!bgrp) {
continue;
}
/* Get or create the bone collection. */
BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(arm, bgrp->name);
if (!bcoll) {
bcoll = ANIM_armature_bonecoll_new(arm, bgrp->name);
ANIM_bonecoll_hide(bcoll);
}
/* Assign the bone. */
ANIM_armature_bonecoll_assign(bcoll, pchan->bone);
}
/* The list of bone groups (pose->agroups) is intentionally left alone here. This will allow
* for older versions of Blender to open the file with bone groups intact. Of course the bone
* groups will not be updated any more, but this way the data at least survives an accidental
* save with Blender 4.0. */
}
}
void do_versions_after_linking_400(FileData *fd, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 9)) {
@ -135,6 +289,15 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain)
*/
{
/* Keep this block, even when empty. */
if (!DNA_struct_elem_find(fd->filesdna, "bPoseChannel", "BoneColor", "color")) {
version_bonegroup_migrate_color(bmain);
}
if (!DNA_struct_elem_find(fd->filesdna, "bArmature", "ListBase", "collections")) {
version_bonelayers_to_bonecollections(bmain);
version_bonegroups_to_bonecollections(bmain);
}
}
}

View File

@ -1792,15 +1792,16 @@ void DepsgraphNodeBuilder::build_armature(bArmature *armature)
build_idproperties(armature->id.properties);
build_animdata(&armature->id);
build_parameters(&armature->id);
/* Make sure pose is up-to-date with armature updates. */
bArmature *armature_cow = (bArmature *)get_cow_id(&armature->id);
add_operation_node(&armature->id,
NodeType::ARMATURE,
OperationCode::ARMATURE_EVAL,
[armature_cow](::Depsgraph *depsgraph) {
BKE_armature_refresh_layer_used(depsgraph, armature_cow);
});
/* This operation is no longer necessary, as it was updating things with the bone layers (which
* got replaced by bone collections). However, it's still used by other depsgraph components as a
* dependency, so for now the node itself is kept as a no-op.
* TODO: remove this node & the references to it, if eventually it turns out we really don't need
* this.
*/
add_operation_node(
&armature->id, NodeType::ARMATURE, OperationCode::ARMATURE_EVAL, [](::Depsgraph *) {});
build_armature_bones(&armature->bonebase);
build_armature_bone_collections(&armature->collections);
}
void DepsgraphNodeBuilder::build_armature_bones(ListBase *bones)
@ -1811,6 +1812,13 @@ void DepsgraphNodeBuilder::build_armature_bones(ListBase *bones)
}
}
void DepsgraphNodeBuilder::build_armature_bone_collections(ListBase *collections)
{
LISTBASE_FOREACH (BoneCollection *, bcoll, collections) {
build_idproperties(bcoll->prop);
}
}
void DepsgraphNodeBuilder::build_camera(Camera *camera)
{
if (built_map_.checkIsBuiltAndTag(camera)) {

View File

@ -246,6 +246,7 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder {
virtual void build_rig(Object *object);
virtual void build_armature(bArmature *armature);
virtual void build_armature_bones(ListBase *bones);
virtual void build_armature_bone_collections(ListBase *collections);
virtual void build_shapekeys(Key *key);
virtual void build_camera(Camera *camera);
virtual void build_light(Light *lamp);

View File

@ -2716,6 +2716,7 @@ void DepsgraphRelationBuilder::build_armature(bArmature *armature)
build_animdata(&armature->id);
build_parameters(&armature->id);
build_armature_bones(&armature->bonebase);
build_armature_bone_collections(&armature->collections);
}
void DepsgraphRelationBuilder::build_armature_bones(ListBase *bones)
@ -2726,6 +2727,13 @@ void DepsgraphRelationBuilder::build_armature_bones(ListBase *bones)
}
}
void DepsgraphRelationBuilder::build_armature_bone_collections(ListBase *collections)
{
LISTBASE_FOREACH (BoneCollection *, bcoll, collections) {
build_idproperties(bcoll->prop);
}
}
void DepsgraphRelationBuilder::build_camera(Camera *camera)
{
if (built_map_.checkIsBuiltAndTag(camera)) {

View File

@ -225,6 +225,7 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder {
virtual void build_shapekeys(Key *key);
virtual void build_armature(bArmature *armature);
virtual void build_armature_bones(ListBase *bones);
virtual void build_armature_bone_collections(ListBase *collections);
virtual void build_camera(Camera *camera);
virtual void build_light(Light *lamp);
virtual void build_nodetree(bNodeTree *ntree);

View File

@ -37,6 +37,7 @@
#include "ED_view3d.hh"
#include "ANIM_bone_collections.h"
#include "ANIM_bonecolor.hh"
#include "UI_resources.hh"
@ -121,6 +122,11 @@ class UnifiedBonePtr {
UnifiedBonePtr(EditBone *eBone) : eBone_(eBone), is_editbone_(true) {}
UnifiedBonePtr(bPoseChannel *pchan) : pchan_(pchan), is_editbone_(false) {}
const char *name() const
{
return is_editbone_ ? eBone_->name : pchan_->name;
}
const EditBone *as_editbone() const
{
BLI_assert_msg(is_editbone_,
@ -218,6 +224,20 @@ class UnifiedBonePtr {
{
return is_editbone_ ? eBone_->rad_tail : pchan_->bone->rad_tail;
}
const blender::animrig::BoneColor &effective_bonecolor() const
{
if (is_editbone_) {
return eBone_->color.wrap();
}
if (pchan_->color.palette_index == 0) {
/* If the pchan has the 'default' color, treat it as a signal to use the underlying bone
* color. */
return pchan_->bone->color.wrap();
}
return pchan_->color.wrap();
}
};
/**
@ -1075,46 +1095,18 @@ static void drw_shgroup_bone_ik_spline_lines(const ArmatureDrawContext *ctx,
* \{ */
/* This function sets the color-set for coloring a certain bone */
static void set_pchan_colorset(ArmatureDrawContext *ctx, Object *ob, bPoseChannel *pchan)
static void set_ctx_bcolor(ArmatureDrawContext *ctx, const UnifiedBonePtr bone)
{
bPose *pose = (ob) ? ob->pose : nullptr;
bArmature *arm = (ob) ? static_cast<bArmature *>(ob->data) : nullptr;
bActionGroup *grp = nullptr;
short color_index = 0;
bArmature *arm = static_cast<bArmature *>(ctx->ob->data);
/* sanity check */
if (ELEM(nullptr, ob, arm, pose, pchan)) {
if ((arm->flag & ARM_COL_CUSTOM) == 0) {
/* Only set a custom color if that's enabled on this armature. */
ctx->bcolor = nullptr;
return;
}
/* only try to set custom color if enabled for armature */
if (arm->flag & ARM_COL_CUSTOM) {
/* currently, a bone can only use a custom color set if its group (if it has one),
* has been set to use one
*/
if (pchan->agrp_index) {
grp = (bActionGroup *)BLI_findlink(&pose->agroups, (pchan->agrp_index - 1));
if (grp) {
color_index = grp->customCol;
}
}
}
/* bcolor is a pointer to the color set to use. If nullptr, then the default
* color set (based on the theme colors for 3d-view) is used.
*/
if (color_index > 0) {
bTheme *btheme = UI_GetTheme();
ctx->bcolor = &btheme->tarm[(color_index - 1)];
}
else if (color_index == -1) {
/* use the group's own custom color set (grp is always != nullptr here) */
ctx->bcolor = &grp->cs;
}
else {
ctx->bcolor = nullptr;
}
const blender::animrig::BoneColor &bone_color = bone.effective_bonecolor();
ctx->bcolor = bone_color.effective_color();
}
/* This function is for brightening/darkening a given color (like UI_GetThemeColorShade3ubv()) */
@ -1253,18 +1245,14 @@ static const float *get_bone_solid_color(const ArmatureDrawContext *ctx, const e
return G_draw.block.color_bone_solid;
}
if (ctx->draw_mode == ARM_DRAW_MODE_POSE) {
static float disp_color[4];
get_pchan_color_solid(ctx->bcolor, disp_color);
static float disp_color[4];
get_pchan_color_solid(ctx->bcolor, disp_color);
if (boneflag & BONE_DRAW_LOCKED_WEIGHT) {
bone_locked_color_shade(disp_color);
}
return disp_color;
if (ctx->draw_mode == ARM_DRAW_MODE_POSE && (boneflag & BONE_DRAW_LOCKED_WEIGHT)) {
bone_locked_color_shade(disp_color);
}
return G_draw.block.color_bone_solid;
return disp_color;
}
static const float *get_bone_solid_with_consts_color(const ArmatureDrawContext *ctx,
@ -1643,13 +1631,12 @@ static void draw_axes(const ArmatureDrawContext *ctx,
static void draw_points(const ArmatureDrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const float col_solid[4],
const int select_id)
{
float col_solid_root[4], col_solid_tail[4], col_wire_root[4], col_wire_tail[4];
float col_wire_root[4], col_wire_tail[4];
float col_hint_root[4], col_hint_tail[4];
copy_v4_v4(col_solid_root, G_draw.block.color_bone_solid);
copy_v4_v4(col_solid_tail, G_draw.block.color_bone_solid);
copy_v4_v4(col_wire_root, (ctx->const_color) ? ctx->const_color : &G_draw.block.color_vertex.x);
copy_v4_v4(col_wire_tail, (ctx->const_color) ? ctx->const_color : &G_draw.block.color_vertex.x);
@ -1669,16 +1656,19 @@ static void draw_points(const ArmatureDrawContext *ctx,
}
}
else if (ctx->draw_mode == ARM_DRAW_MODE_POSE) {
const float *solid_color = get_bone_solid_color(ctx, boneflag);
const float *wire_color = get_bone_wire_color(ctx, boneflag);
copy_v4_v4(col_wire_tail, wire_color);
copy_v4_v4(col_wire_root, wire_color);
copy_v4_v4(col_solid_tail, solid_color);
copy_v4_v4(col_solid_root, solid_color);
}
bone_hint_color_shade(col_hint_root, (ctx->const_color) ? col_solid_root : col_wire_root);
bone_hint_color_shade(col_hint_tail, (ctx->const_color) ? col_solid_tail : col_wire_tail);
const float *hint_color_shade_root = (ctx->const_color) ?
(const float *)G_draw.block.color_bone_solid :
col_wire_root;
const float *hint_color_shade_tail = (ctx->const_color) ?
(const float *)G_draw.block.color_bone_solid :
col_wire_tail;
bone_hint_color_shade(col_hint_root, hint_color_shade_root);
bone_hint_color_shade(col_hint_tail, hint_color_shade_tail);
/* Draw root point if we are not connected to our parent */
@ -1690,14 +1680,14 @@ static void draw_points(const ArmatureDrawContext *ctx,
if (is_envelope_draw) {
drw_shgroup_bone_envelope(ctx,
bone.disp_mat(),
col_solid_root,
col_solid,
col_hint_root,
col_wire_root,
&bone.rad_head(),
&envelope_ignore);
}
else {
drw_shgroup_bone_point(ctx, bone.disp_mat(), col_solid_root, col_hint_root, col_wire_root);
drw_shgroup_bone_point(ctx, bone.disp_mat(), col_solid, col_hint_root, col_wire_root);
}
}
@ -1709,15 +1699,14 @@ static void draw_points(const ArmatureDrawContext *ctx,
if (is_envelope_draw) {
drw_shgroup_bone_envelope(ctx,
bone.disp_mat(),
col_solid_tail,
col_solid,
col_hint_tail,
col_wire_tail,
&envelope_ignore,
&bone.rad_tail());
}
else {
drw_shgroup_bone_point(
ctx, bone.disp_tail_mat(), col_solid_tail, col_hint_tail, col_wire_tail);
drw_shgroup_bone_point(ctx, bone.disp_tail_mat(), col_solid, col_hint_tail, col_wire_tail);
}
if (select_id != -1) {
@ -2179,7 +2168,7 @@ class ArmatureBoneDrawStrategyOcta : public ArmatureBoneDrawStrategy {
DRW_select_load_id(-1);
}
draw_points(ctx, bone, boneflag, select_id);
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
};
@ -2343,7 +2332,7 @@ class ArmatureBoneDrawStrategyBBone : public ArmatureBoneDrawStrategy {
}
if (bone.is_editbone()) {
draw_points(ctx, bone, boneflag, select_id);
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
}
};
@ -2424,7 +2413,7 @@ class ArmatureBoneDrawStrategyEnvelope : public ArmatureBoneDrawStrategy {
DRW_select_load_id(-1);
}
draw_points(ctx, bone, boneflag, select_id);
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
};
@ -2485,7 +2474,8 @@ class ArmatureBoneDrawStrategyWire : public ArmatureBoneDrawStrategy {
}
if (bone.is_editbone()) {
draw_points(ctx, bone, boneflag, select_id);
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
}
};
@ -2538,7 +2528,7 @@ static ArmatureBoneDrawStrategy &strategy_for_armature_drawtype(const eArmature_
/** \name Main Draw Loops
* \{ */
static void draw_armature_edit(const ArmatureDrawContext *ctx)
static void draw_armature_edit(ArmatureDrawContext *ctx)
{
Object *ob = ctx->ob;
EditBone *eBone;
@ -2563,10 +2553,7 @@ static void draw_armature_edit(const ArmatureDrawContext *ctx)
eBone;
eBone = eBone->next, index += 0x10000)
{
if (!ANIM_bonecoll_is_visible_editbone(arm, eBone)) {
continue;
}
if (eBone->flag & BONE_HIDDEN_A) {
if (!EBONE_VISIBLE(arm, eBone)) {
continue;
}
@ -2586,6 +2573,10 @@ static void draw_armature_edit(const ArmatureDrawContext *ctx)
boneflag &= ~BONE_DRAW_LOCKED_WEIGHT;
UnifiedBonePtr bone = eBone;
if (!ctx->const_color) {
set_ctx_bcolor(ctx, bone);
}
if (!is_select) {
draw_bone_relations(ctx, draw_strat, bone, boneflag);
}
@ -2689,11 +2680,7 @@ static void draw_armature_pose(ArmatureDrawContext *ctx)
pchan = pchan->next, index += 0x10000)
{
Bone *bone = pchan->bone;
const bool bone_visible = (bone->flag & (BONE_HIDDEN_P | BONE_HIDDEN_PG)) == 0;
if (!bone_visible) {
continue;
}
if ((bone->layer & arm->layer) == 0) {
if (!ANIM_bone_is_visible(arm, bone)) {
continue;
}
@ -2706,8 +2693,9 @@ static void draw_armature_pose(ArmatureDrawContext *ctx)
pchan_draw_data_init(pchan);
UnifiedBonePtr bone_ptr = pchan;
if (!ctx->const_color) {
set_pchan_colorset(ctx, ob, pchan);
set_ctx_bcolor(ctx, bone_ptr);
}
eBone_Flag boneflag = eBone_Flag(bone->flag);
@ -2726,8 +2714,6 @@ static void draw_armature_pose(ArmatureDrawContext *ctx)
const bool use_custom_shape = (pchan->custom) && !(arm->flag & ARM_NO_CUSTOM);
const ArmatureBoneDrawStrategy &draw_strat = use_custom_shape ? draw_strat_custom :
draw_strat_normal;
UnifiedBonePtr bone_ptr = pchan;
if (!is_pose_select) {
draw_bone_relations(ctx, draw_strat, bone_ptr, boneflag);
}

View File

@ -140,8 +140,6 @@ static void animchan_sync_group(bAnimContext *ac, bAnimListElem *ale, bActionGro
bArmature *arm = static_cast<bArmature *>(ob->data);
if (pchan) {
bActionGroup *bgrp;
/* if one matches, sync the selection status */
if ((pchan->bone) && (pchan->bone->flag & BONE_SELECTED)) {
agrp->flag |= AGRP_SELECTED;
@ -167,12 +165,8 @@ static void animchan_sync_group(bAnimContext *ac, bAnimListElem *ale, bActionGro
agrp->flag &= ~AGRP_ACTIVE;
}
/* sync group colors */
bgrp = (bActionGroup *)BLI_findlink(&ob->pose->agroups, (pchan->agrp_index - 1));
if (bgrp) {
agrp->customCol = bgrp->customCol;
action_group_colors_sync(agrp, bgrp);
}
/* sync bone color */
action_group_colors_set_from_posebone(agrp, pchan);
}
}
}

View File

@ -221,17 +221,8 @@ FCurve *ED_action_fcurve_ensure(Main *bmain,
/* sync bone group colors if applicable */
if (ptr && (ptr->type == &RNA_PoseBone)) {
Object *ob = (Object *)ptr->owner_id;
bPoseChannel *pchan = static_cast<bPoseChannel *>(ptr->data);
bPose *pose = ob->pose;
bActionGroup *grp;
/* find bone group (if present), and use the color from that */
grp = (bActionGroup *)BLI_findlink(&pose->agroups, (pchan->agrp_index - 1));
if (grp) {
agrp->customCol = grp->customCol;
action_group_colors_sync(agrp, grp);
}
action_group_colors_set_from_posebone(agrp, pchan);
}
}

View File

@ -31,6 +31,7 @@ set(SRC
armature_select.cc
armature_skinning.cc
armature_utils.cc
bone_collections.cc
editarmature_undo.cc
meshlaplacian.cc
pose_edit.cc

View File

@ -70,7 +70,7 @@ EditBone *ED_armature_ebone_add(bArmature *arm, const char *name)
bone->rad_head = 0.10f;
bone->rad_tail = 0.05f;
bone->segments = 1;
ANIM_bone_set_ebone_layer_from_armature(bone, arm);
ANIM_armature_bonecoll_assign_active(arm, bone);
/* Bendy-Bone parameters */
bone->roll1 = 0.0f;
@ -918,13 +918,19 @@ static void copy_pchan(EditBone *src_bone, EditBone *dst_bone, Object *src_ob, O
}
}
void ED_armature_ebone_copy(EditBone *dest, const EditBone *source)
{
memcpy(dest, source, sizeof(*dest));
BLI_duplicatelist(&dest->bone_collections, &dest->bone_collections);
}
EditBone *duplicateEditBoneObjects(
EditBone *cur_bone, const char *name, ListBase *editbones, Object *src_ob, Object *dst_ob)
{
EditBone *e_bone = static_cast<EditBone *>(MEM_mallocN(sizeof(EditBone), "addup_editbone"));
/* Copy data from old bone to new bone */
memcpy(e_bone, cur_bone, sizeof(EditBone));
ED_armature_ebone_copy(e_bone, cur_bone);
cur_bone->temp.ebone = e_bone;
e_bone->temp.ebone = cur_bone;
@ -1649,8 +1655,6 @@ static int armature_bone_primitive_add_exec(bContext *C, wmOperator *op)
add_v3_v3v3(bone->tail, bone->head, imat[2]); /* bone with unit length 1, pointing up Z */
}
ED_armature_edit_refresh_layer_used(static_cast<bArmature *>(obedit->data));
/* NOTE: notifier might evolve. */
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
DEG_id_tag_update(&obedit->id, ID_RECALC_SELECT);

View File

@ -846,7 +846,6 @@ static int armature_fill_bones_exec(bContext *C, wmOperator *op)
}
/* updates */
ED_armature_edit_refresh_layer_used(arm);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, obedit);
DEG_id_tag_update(&arm->id, ID_RECALC_COPY_ON_WRITE);
@ -1264,7 +1263,6 @@ static int armature_delete_selected_exec(bContext *C, wmOperator * /*op*/)
changed_multi = true;
ED_armature_edit_sync_selection(arm->edbo);
ED_armature_edit_refresh_layer_used(arm);
BKE_pose_tag_recalc(CTX_data_main(C), obedit->pose);
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
@ -1445,7 +1443,6 @@ static int armature_dissolve_selected_exec(bContext *C, wmOperator * /*op*/)
if (changed) {
changed_multi = true;
ED_armature_edit_sync_selection(arm->edbo);
ED_armature_edit_refresh_layer_used(arm);
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
ED_outliner_select_sync_from_edit_bone_tag(C);

View File

@ -74,6 +74,17 @@ void ARMATURE_OT_layers_show_all(struct wmOperatorType *ot);
void ARMATURE_OT_armature_layers(struct wmOperatorType *ot);
void ARMATURE_OT_bone_layers(struct wmOperatorType *ot);
void ARMATURE_OT_collection_add(struct wmOperatorType *ot);
void ARMATURE_OT_collection_remove(struct wmOperatorType *ot);
void ARMATURE_OT_collection_move(struct wmOperatorType *ot);
void ARMATURE_OT_collection_assign(struct wmOperatorType *ot);
void ARMATURE_OT_collection_unassign(struct wmOperatorType *ot);
void ARMATURE_OT_collection_select(struct wmOperatorType *ot);
void ARMATURE_OT_collection_deselect(struct wmOperatorType *ot);
void ARMATURE_OT_move_to_collection(struct wmOperatorType *ot);
void ARMATURE_OT_assign_to_collection(struct wmOperatorType *ot);
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -49,6 +49,8 @@
#include "ED_armature.hh"
#include "ED_screen.hh"
#include "ANIM_bone_collections.h"
#include "armature_intern.h"
/* -------------------------------------------------------------------- */

View File

@ -63,6 +63,17 @@ void ED_operatortypes_armature()
WM_operatortype_append(ARMATURE_OT_armature_layers);
WM_operatortype_append(ARMATURE_OT_bone_layers);
WM_operatortype_append(ARMATURE_OT_collection_add);
WM_operatortype_append(ARMATURE_OT_collection_remove);
WM_operatortype_append(ARMATURE_OT_collection_move);
WM_operatortype_append(ARMATURE_OT_collection_assign);
WM_operatortype_append(ARMATURE_OT_collection_unassign);
WM_operatortype_append(ARMATURE_OT_collection_select);
WM_operatortype_append(ARMATURE_OT_collection_deselect);
WM_operatortype_append(ARMATURE_OT_move_to_collection);
WM_operatortype_append(ARMATURE_OT_assign_to_collection);
/* POSE */
WM_operatortype_append(POSE_OT_hide);
WM_operatortype_append(POSE_OT_reveal);

View File

@ -17,6 +17,7 @@
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_map.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
@ -50,6 +51,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "ANIM_bone_collections.h"
#include "armature_intern.h"
/* -------------------------------------------------------------------- */
@ -291,6 +294,17 @@ int ED_armature_join_objects_exec(bContext *C, wmOperator *op)
* See #object_join_exec for detailed comment on why the safe version is used. */
invert_m4_m4_safe_ortho(oimat, ob_active->object_to_world);
/* Index bone collections by name. This is also used later to keep track
* of collections added from other armatures. */
blender::Map<std::string, BoneCollection *> bone_collection_by_name;
LISTBASE_FOREACH (BoneCollection *, bcoll, &arm->collections) {
bone_collection_by_name.add(bcoll->name, bcoll);
}
/* Used to track how bone collections should be remapped after merging
* other armatures. */
blender::Map<BoneCollection *, BoneCollection *> bone_collection_remap;
/* Get edit-bones of active armature to add edit-bones to */
ED_armature_to_edit(arm);
@ -313,7 +327,27 @@ int ED_armature_join_objects_exec(bContext *C, wmOperator *op)
afd.names_map = BLI_ghash_str_new("join_armature_adt_fix");
/* Make a list of edit-bones in current armature */
ED_armature_to_edit(static_cast<bArmature *>(ob_iter->data));
ED_armature_to_edit(curarm);
/* Move new bone collections, and store their remapping info.
* TODO: armatures can potentially have multiple users, so these should
* actually be copied, not moved. However, the armature join code is
* already broken in that situation. When that gets fixed, this should
* also get fixed. Note that copying the collections should include
* copying their custom properties. (Nathan Vegdahl) */
LISTBASE_FOREACH_MUTABLE (BoneCollection *, bcoll, &curarm->collections) {
BoneCollection *mapped = bone_collection_by_name.lookup_default(bcoll->name, nullptr);
if (!mapped) {
BLI_remlink(&curarm->collections, bcoll);
BLI_addtail(&arm->collections, bcoll);
bone_collection_by_name.add(bcoll->name, bcoll);
mapped = bcoll;
}
bone_collection_remap.add(bcoll, mapped);
}
/* Get Pose of current armature */
opose = ob_iter->pose;
@ -375,6 +409,11 @@ int ED_armature_join_objects_exec(bContext *C, wmOperator *op)
BLI_addtail(&pose->chanbase, pchan);
BKE_pose_channels_hash_free(opose);
BKE_pose_channels_hash_free(pose);
/* Remap collections. */
LISTBASE_FOREACH (BoneCollectionReference *, bcoll_ref, &curbone->bone_collections) {
bcoll_ref->bcoll = bone_collection_remap.lookup(bcoll_ref->bcoll);
}
}
/* Armature ID itself is not freed below, however it has been modified (and is now completely
@ -676,7 +715,6 @@ static int separate_armature_exec(bContext *C, wmOperator *op)
/* 5) restore original conditions */
ED_armature_to_edit(static_cast<bArmature *>(ob_old->data));
ED_armature_edit_refresh_layer_used(static_cast<bArmature *>(ob_old->data));
/* parents tips remain selected when connected children are removed. */
ED_armature_edit_deselect_all(ob_old);

View File

@ -43,6 +43,8 @@
#include "GPU_select.h"
#include "ANIM_bone_collections.h"
#include "armature_intern.h"
/* utility macros for storing a temp int in the bone (selection flag) */

View File

@ -33,6 +33,8 @@
#include "armature_intern.h"
#include <string.h>
/* -------------------------------------------------------------------- */
/** \name Validation
* \{ */
@ -72,14 +74,6 @@ void ED_armature_edit_validate_active(bArmature *arm)
}
}
void ED_armature_edit_refresh_layer_used(bArmature *arm)
{
arm->layer_used = 0;
LISTBASE_FOREACH (EditBone *, ebo, arm->edbo) {
arm->layer_used |= ebo->layer;
}
}
/** \} */
/* -------------------------------------------------------------------- */
@ -440,6 +434,16 @@ void ED_armature_edit_transform_mirror_update(Object *obedit)
/** \name Armature EditMode Conversions
* \{ */
/** Copy the bone collection membership info from the bones to the ebones.
*
* Operations on eBones (like subdividing, extruding, etc.) will have to deal
* with collection assignments of those eBones as well. */
static void copy_bonecollection_membership(EditBone *eBone, const Bone *bone)
{
BLI_assert(BLI_listbase_is_empty(&eBone->bone_collections));
BLI_duplicatelist(&eBone->bone_collections, &bone->runtime.collections);
}
/* converts Bones to EditBone list, used for tools as well */
static EditBone *make_boneList_recursive(ListBase *edbo,
ListBase *bones,
@ -517,6 +521,9 @@ static EditBone *make_boneList_recursive(ListBase *edbo,
eBone->bbone_prev_flag = curBone->bbone_prev_flag;
eBone->bbone_next_flag = curBone->bbone_next_flag;
eBone->color = curBone->color;
copy_bonecollection_membership(eBone, curBone);
if (curBone->prop) {
eBone->prop = IDP_CopyProperty(curBone->prop);
}
@ -728,6 +735,14 @@ void ED_armature_from_edit(Main *bmain, bArmature *arm)
newBone->bbone_prev_flag = eBone->bbone_prev_flag;
newBone->bbone_next_flag = eBone->bbone_next_flag;
newBone->color = eBone->color;
LISTBASE_FOREACH (BoneCollectionReference *, ref, &eBone->bone_collections) {
BoneCollectionReference *newBoneRef = MEM_cnew<BoneCollectionReference>(
"ED_armature_from_edit", *ref);
BLI_addtail(&newBone->runtime.collections, newBoneRef);
}
if (eBone->prop) {
newBone->prop = IDP_CopyProperty(eBone->prop);
}
@ -759,6 +774,7 @@ void ED_armature_from_edit(Main *bmain, bArmature *arm)
/* Finalize definition of rest-pose data (roll, bone_mat, arm_mat, head/tail...). */
armature_finalize_restpose(&arm->bonebase, arm->edbo);
ANIM_armature_bonecoll_reconstruct(arm);
BKE_armature_bone_hash_make(arm);
@ -783,6 +799,7 @@ void ED_armature_edit_free(bArmature *arm)
if (eBone->prop) {
IDP_FreeProperty(eBone->prop);
}
BLI_freelistN(&eBone->bone_collections);
}
BLI_freelistN(arm->edbo);
@ -848,6 +865,18 @@ void ED_armature_ebone_listbase_copy(ListBase *lb_dst, ListBase *lb_src, const b
if (ebone_dst->bbone_prev) {
ebone_dst->bbone_prev = ebone_dst->bbone_prev->temp.ebone;
}
/* TODO: WORKAROUND: this is a temporary hack to avoid segfaults when
* undoing, because bone collections are not handled properly by the
* armature undo code yet. This just discards all collection membership
* data to avoid dangling references. This MUST be addressed properly
* before release.
* See: https://projects.blender.org/blender/blender/pulls/109976#issuecomment-1008429
*/
BoneCollectionReference *bcoll_ref = (BoneCollectionReference *)(&ebone_dst->bone_collections);
bcoll_ref->next = nullptr;
bcoll_ref->prev = nullptr;
bcoll_ref->bcoll = nullptr;
}
}

View File

@ -0,0 +1,783 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
* Implementation of Bone Collection operators and editing API's.
*/
#include <string.h>
#include "ANIM_bone_collections.h"
#include "DNA_ID.h"
#include "DNA_object_types.h"
#include "BKE_context.h"
#include "BKE_layer.h"
#include "DEG_depsgraph.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_armature.hh"
#include "ED_object.hh"
#include "ED_outliner.hh"
#include "ED_screen.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "armature_intern.h"
struct wmOperator;
/* ********************************************** */
/* Bone collections */
static bool bone_collection_poll(bContext *C)
{
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return false;
}
if (ID_IS_OVERRIDE_LIBRARY(ob)) {
CTX_wm_operator_poll_msg_set(C, "Cannot edit bone collections for library overrides");
return false;
}
if (ob->type != OB_ARMATURE) {
CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
return false;
}
return true;
}
static bool active_bone_collection_poll(bContext *C)
{
if (!bone_collection_poll(C)) {
return false;
}
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return false;
}
bArmature *armature = static_cast<bArmature *>(ob->data);
if (armature->active_collection == nullptr) {
CTX_wm_operator_poll_msg_set(C, "Armature has no active bone collection, select one first");
return false;
}
return true;
}
static int bone_collection_add_exec(bContext *C, wmOperator * /* op */)
{
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return OPERATOR_CANCELLED;
}
bArmature *armature = static_cast<bArmature *>(ob->data);
ANIM_armature_bonecoll_new(armature, nullptr);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_collection_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Bone Collection";
ot->idname = "ARMATURE_OT_collection_add";
ot->description = "Add a new bone collection";
/* api callbacks */
ot->exec = bone_collection_add_exec;
ot->poll = bone_collection_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int bone_collection_remove_exec(bContext *C, wmOperator * /* op */)
{
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return OPERATOR_CANCELLED;
}
/* The poll function ensures armature->active_collection is not NULL. */
bArmature *armature = static_cast<bArmature *>(ob->data);
ANIM_armature_bonecoll_remove(armature, armature->active_collection);
/* notifiers for updates */
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
DEG_id_tag_update(&armature->id, ID_RECALC_SELECT);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_collection_remove(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Bone Collection";
ot->idname = "ARMATURE_OT_collection_remove";
ot->description = "Remove the active bone collection";
/* api callbacks */
ot->exec = bone_collection_remove_exec;
ot->poll = active_bone_collection_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int bone_collection_move_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return OPERATOR_CANCELLED;
}
const int direction = RNA_enum_get(op->ptr, "direction");
/* Poll function makes sure this is valid. */
bArmature *armature = static_cast<bArmature *>(ob->data);
const bool ok = ANIM_armature_bonecoll_move(armature, armature->active_collection, direction);
if (!ok) {
return OPERATOR_CANCELLED;
}
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_collection_move(wmOperatorType *ot)
{
static const EnumPropertyItem bcoll_slot_move[] = {
{-1, "UP", 0, "Up", ""},
{1, "DOWN", 0, "Down", ""},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Move Bone Collection";
ot->idname = "ARMATURE_OT_collection_move";
ot->description = "Change position of active Bone Collection in list of Bone collections";
/* api callbacks */
ot->exec = bone_collection_move_exec;
ot->poll = active_bone_collection_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_enum(ot->srna,
"direction",
bcoll_slot_move,
0,
"Direction",
"Direction to move the active Bone Collection towards");
}
typedef enum eMayCreate {
FAIL_IF_MISSING = 0,
CREATE_IF_MISSING = 1,
} eMayCreate;
static BoneCollection *get_bonecoll_named_or_active(bContext * /*C*/,
wmOperator *op,
Object *ob,
const eMayCreate may_create)
{
bArmature *armature = static_cast<bArmature *>(ob->data);
char bcoll_name[MAX_NAME];
RNA_string_get(op->ptr, "name", bcoll_name);
if (bcoll_name[0] == '\0') {
return armature->active_collection;
}
BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(armature, bcoll_name);
if (bcoll) {
return bcoll;
}
switch (may_create) {
case CREATE_IF_MISSING:
bcoll = ANIM_armature_bonecoll_new(armature, bcoll_name);
ANIM_armature_bonecoll_active_set(armature, bcoll);
return bcoll;
case FAIL_IF_MISSING:
WM_reportf(RPT_ERROR, "No bone collection named '%s'", bcoll_name);
return nullptr;
}
return nullptr;
}
using assign_bone_func = bool (*)(BoneCollection *bcoll, Bone *bone);
using assign_ebone_func = bool (*)(BoneCollection *bcoll, EditBone *ebone);
/* The following 3 functions either assign or unassign, depending on the
* 'assign_bone_func'/'assign_ebone_func' they get passed. */
static void bone_collection_assign_pchans(bContext *C,
Object *ob,
BoneCollection *bcoll,
assign_bone_func assign_func,
bool *made_any_changes,
bool *had_bones_to_assign)
{
/* TODO: support multi-object pose mode. */
FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (ob, pchan) {
*made_any_changes |= assign_func(bcoll, pchan->bone);
*had_bones_to_assign = true;
}
FOREACH_PCHAN_SELECTED_IN_OBJECT_END;
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
bArmature *arm = static_cast<bArmature *>(ob->data);
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
}
static void bone_collection_assign_editbones(bContext *C,
Object *ob,
BoneCollection *bcoll,
assign_ebone_func assign_func,
bool *made_any_changes,
bool *had_bones_to_assign)
{
bArmature *arm = static_cast<bArmature *>(ob->data);
ED_armature_edit_sync_selection(arm->edbo);
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (!EBONE_EDITABLE(ebone)) {
continue;
}
*made_any_changes |= assign_func(bcoll, ebone);
*had_bones_to_assign = true;
}
ED_armature_edit_sync_selection(arm->edbo);
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
/* Returns whether the current mode is actually supported. */
static bool bone_collection_assign_mode_specific(bContext *C,
Object *ob,
BoneCollection *bcoll,
assign_bone_func assign_bone_func,
assign_ebone_func assign_ebone_func,
bool *made_any_changes,
bool *had_bones_to_assign)
{
switch (CTX_data_mode_enum(C)) {
case CTX_MODE_POSE: {
bone_collection_assign_pchans(
C, ob, bcoll, assign_bone_func, made_any_changes, had_bones_to_assign);
return true;
}
case CTX_MODE_EDIT_ARMATURE: {
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
bone_collection_assign_editbones(
C, ob, bcoll, assign_ebone_func, made_any_changes, had_bones_to_assign);
}
MEM_freeN(objects);
ED_outliner_select_sync_from_edit_bone_tag(C);
return true;
}
default:
return false;
}
}
/* Assign selected pchans to the bone collection that the user selects */
static int bone_collection_assign_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return OPERATOR_CANCELLED;
}
BoneCollection *bcoll = get_bonecoll_named_or_active(C, op, ob, CREATE_IF_MISSING);
if (bcoll == nullptr) {
return OPERATOR_CANCELLED;
}
bool made_any_changes = false;
bool had_bones_to_assign = false;
const bool mode_is_supported = bone_collection_assign_mode_specific(
C,
ob,
bcoll,
ANIM_armature_bonecoll_assign,
ANIM_armature_bonecoll_assign_editbone,
&made_any_changes,
&had_bones_to_assign);
if (!mode_is_supported) {
WM_report(RPT_ERROR, "This operator only works in pose mode and armature edit mode");
return OPERATOR_CANCELLED;
}
if (!had_bones_to_assign) {
WM_report(RPT_WARNING, "No bones selected, nothing to assign to bone collection");
return OPERATOR_CANCELLED;
}
if (!made_any_changes) {
WM_report(RPT_WARNING, "All selected bones were already part of this collection");
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
void ARMATURE_OT_collection_assign(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Selected Bones to Collection";
ot->idname = "ARMATURE_OT_collection_assign";
ot->description = "Add selected bones to the chosen bone collection";
/* api callbacks */
// TODO: reinstate the menu?
// ot->invoke = bone_collections_menu_invoke;
ot->exec = bone_collection_assign_exec;
ot->poll = bone_collection_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_string(ot->srna,
"name",
NULL,
MAX_NAME,
"Bone Collection",
"Name of the bone collection to assign this bone to; empty to assign to the "
"active bone collection");
}
static int bone_collection_unassign_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return OPERATOR_CANCELLED;
}
BoneCollection *bcoll = get_bonecoll_named_or_active(C, op, ob, FAIL_IF_MISSING);
if (bcoll == nullptr) {
return OPERATOR_CANCELLED;
}
bool made_any_changes = false;
bool had_bones_to_unassign = false;
const bool mode_is_supported = bone_collection_assign_mode_specific(
C,
ob,
bcoll,
ANIM_armature_bonecoll_unassign,
ANIM_armature_bonecoll_unassign_editbone,
&made_any_changes,
&had_bones_to_unassign);
if (!mode_is_supported) {
WM_report(RPT_ERROR, "This operator only works in pose mode and armature edit mode");
return OPERATOR_CANCELLED;
}
if (!had_bones_to_unassign) {
WM_report(RPT_WARNING, "No bones selected, nothing to unassign from bone collection");
return OPERATOR_CANCELLED;
}
if (!made_any_changes) {
WM_report(RPT_WARNING, "None of the selected bones were assigned to this collection");
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
void ARMATURE_OT_collection_unassign(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Selected from Bone collections";
ot->idname = "ARMATURE_OT_collection_unassign";
ot->description = "Remove selected bones from the active bone collection";
/* api callbacks */
ot->exec = bone_collection_unassign_exec;
ot->poll = bone_collection_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_string(ot->srna,
"name",
NULL,
MAX_NAME,
"Bone Collection",
"Name of the bone collection to unassign this bone from; empty to unassign from "
"the active bone collection");
}
static bool editbone_is_member(const EditBone *ebone, const BoneCollection *bcoll)
{
LISTBASE_FOREACH (BoneCollectionReference *, ref, &ebone->bone_collections) {
if (ref->bcoll == bcoll) {
return true;
}
}
return false;
}
static void bone_collection_select(bContext *C,
Object *ob,
BoneCollection *bcoll,
const bool select)
{
bArmature *armature = static_cast<bArmature *>(ob->data);
const bool is_editmode = armature->edbo != nullptr;
if (is_editmode) {
LISTBASE_FOREACH (EditBone *, ebone, armature->edbo) {
if (!EBONE_SELECTABLE(armature, ebone)) {
continue;
}
if (!editbone_is_member(ebone, bcoll)) {
continue;
}
if (select) {
ebone->flag |= BONE_SELECTED;
}
else {
ebone->flag &= ~BONE_SELECTED;
}
}
}
else {
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
Bone *bone = member->bone;
if (!ANIM_bone_is_visible(armature, bone)) {
continue;
}
if (bone->flag & BONE_UNSELECTABLE) {
continue;
}
if (select) {
bone->flag |= BONE_SELECTED;
}
else {
bone->flag &= ~BONE_SELECTED;
}
}
}
DEG_id_tag_update(&armature->id, ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
if (is_editmode) {
ED_outliner_select_sync_from_edit_bone_tag(C);
}
else {
ED_outliner_select_sync_from_pose_bone_tag(C);
}
}
static int bone_collection_select_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return OPERATOR_CANCELLED;
}
BoneCollection *bcoll = get_bonecoll_named_or_active(C, op, ob, FAIL_IF_MISSING);
if (bcoll == nullptr) {
return OPERATOR_CANCELLED;
}
bone_collection_select(C, ob, bcoll, true);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_collection_select(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Bones of Bone Collection";
ot->idname = "ARMATURE_OT_collection_select";
ot->description = "Select bones in active Bone Collection";
/* api callbacks */
ot->exec = bone_collection_select_exec;
ot->poll = bone_collection_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
PropertyRNA *prop = RNA_def_string(
ot->srna,
"name",
NULL,
MAX_NAME,
"Bone Collection",
"Name of the bone collection to select bones from; empty use the active bone collection");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
static int bone_collection_deselect_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_context(C);
if (ob == nullptr) {
return OPERATOR_CANCELLED;
}
BoneCollection *bcoll = get_bonecoll_named_or_active(C, op, ob, FAIL_IF_MISSING);
if (bcoll == nullptr) {
return OPERATOR_CANCELLED;
}
bone_collection_select(C, ob, bcoll, false);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_collection_deselect(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Deselect Bone Collection";
ot->idname = "ARMATURE_OT_collection_deselect";
ot->description = "Deselect bones of active Bone Collection";
/* api callbacks */
ot->exec = bone_collection_deselect_exec;
ot->poll = bone_collection_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
PropertyRNA *prop = RNA_def_string(
ot->srna,
"name",
NULL,
MAX_NAME,
"Bone Collection",
"Name of the bone collection to deselect bones from; empty use the active bone collection");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
/* -------------------------- */
using assign_func = bool (*)(BoneCollection *, Bone *);
static int add_or_move_to_collection_exec(bContext *C,
wmOperator *op,
const assign_func assign_func)
{
Object *obpose = ED_pose_object_from_context(C);
bArmature *arm = static_cast<bArmature *>(obpose->data);
const int collection_index = RNA_enum_get(op->ptr, "collection");
BoneCollection *target_bcoll;
if (collection_index < 0) {
char new_collection_name[MAX_NAME];
RNA_string_get(op->ptr, "new_collection_name", new_collection_name);
target_bcoll = ANIM_armature_bonecoll_new(arm, new_collection_name);
BLI_assert_msg(target_bcoll,
"It should always be possible to create a new bone collection on an armature");
ANIM_armature_bonecoll_active_set(arm, target_bcoll);
}
else {
target_bcoll = static_cast<BoneCollection *>(
BLI_findlink(&arm->collections, collection_index));
if (target_bcoll == nullptr) {
WM_reportf(RPT_ERROR,
"Bone collection with index %d not found on Armature %s",
collection_index,
arm->id.name + 2);
return OPERATOR_CANCELLED;
}
}
FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (obpose, pchan) {
assign_func(target_bcoll, pchan->bone);
}
FOREACH_PCHAN_SELECTED_IN_OBJECT_END;
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
WM_event_add_notifier(C, NC_OBJECT | ND_DATA, obpose);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, obpose);
return OPERATOR_FINISHED;
}
static int move_to_collection_exec(bContext *C, wmOperator *op)
{
return add_or_move_to_collection_exec(C, op, ANIM_armature_bonecoll_assign_and_move);
}
static int assign_to_collection_exec(bContext *C, wmOperator *op)
{
return add_or_move_to_collection_exec(C, op, ANIM_armature_bonecoll_assign);
}
static bool move_to_collection_poll(bContext *C)
{
/* TODO: add outliner support.
if (CTX_wm_space_outliner(C) != nullptr) {
return ED_outliner_collections_editor_poll(C);
}
*/
// TODO: add armature edit mode support.
return ED_operator_object_active_local_editable_posemode_exclusive(C);
}
static const EnumPropertyItem *bone_collection_enum_items(bContext *C,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
Object *obpose = ED_pose_object_from_context(C);
bArmature *arm = static_cast<bArmature *>(obpose->data);
EnumPropertyItem *item = nullptr, item_tmp = {0};
int totitem = 0;
int bcoll_index = 0;
LISTBASE_FOREACH_INDEX (BoneCollection *, bcoll, &arm->collections, bcoll_index) {
item_tmp.identifier = bcoll->name;
item_tmp.name = bcoll->name;
item_tmp.value = bcoll_index;
RNA_enum_item_add(&item, &totitem, &item_tmp);
}
RNA_enum_item_add_separator(&item, &totitem);
/* New Collection. */
item_tmp.identifier = "__NEW__";
item_tmp.name = "New Collection";
item_tmp.value = -1;
RNA_enum_item_add(&item, &totitem, &item_tmp);
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection");
if (RNA_property_is_set(op->ptr, prop)) {
const int collection_index = RNA_property_enum_get(op->ptr, prop);
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);
}
uiPopupMenu *pup = UI_popup_menu_begin(C, op->type->name, 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)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Move to Collection";
ot->description = "Move bones to a collection";
ot->idname = "ARMATURE_OT_move_to_collection";
/* api callbacks */
ot->exec = move_to_collection_exec;
ot->invoke = move_to_collection_invoke;
ot->poll = move_to_collection_poll;
/* Flags don't include OPTYPE_REGISTER, as the redo panel doesn't make much sense for this
* operator. The visibility of the RNA properties is determined by the needs of the 'New Catalog'
* popup, so that a name can be entered. This means that the redo panel would also only show the
* 'Name' property, without any choice for another collection. */
ot->flag = OPTYPE_UNDO;
prop = RNA_def_enum(ot->srna,
"collection",
rna_enum_dummy_DEFAULT_items,
0,
"Collection",
"The bone collection to move the selected bones to");
RNA_def_enum_funcs(prop, bone_collection_enum_items);
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
prop = RNA_def_string(ot->srna,
"new_collection_name",
nullptr,
MAX_NAME,
"Name",
"Name of the newly added bone collection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
ot->prop = prop;
}
void ARMATURE_OT_assign_to_collection(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Assign to Collection";
ot->description = "Assign bones to a collection";
ot->idname = "ARMATURE_OT_assign_to_collection";
/* api callbacks */
ot->exec = assign_to_collection_exec;
ot->invoke = move_to_collection_invoke;
ot->poll = move_to_collection_poll;
/* Flags don't include OPTYPE_REGISTER, as the redo panel doesn't make much sense for this
* operator. The visibility of the RNA properties is determined by the needs of the 'New Catalog'
* popup, so that a name can be entered. This means that the redo panel would also only show the
* 'Name' property, without any choice for another collection. */
ot->flag = OPTYPE_UNDO;
prop = RNA_def_enum(ot->srna,
"collection",
rna_enum_dummy_DEFAULT_items,
0,
"Collection",
"The bone collection to move the selected bones to");
RNA_def_enum_funcs(prop, bone_collection_enum_items);
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
prop = RNA_def_string(ot->srna,
"new_collection_name",
nullptr,
MAX_NAME,
"Name",
"Name of the newly added bone collection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
ot->prop = prop;
}
/* ********************************************** */

View File

@ -941,31 +941,13 @@ static int armature_bone_layers_invoke(bContext *C, wmOperator *op, const wmEven
}
/* Set the visible layers for the active armature (edit and pose modes) */
static int armature_bone_layers_exec(bContext *C, wmOperator *op)
static int armature_bone_layers_exec(bContext * /*C*/, wmOperator * /*op*/)
{
Object *ob = CTX_data_edit_object(C);
PointerRNA ptr;
/* hardcoded for now - we can only have 32 armature layers, so this should be fine... */
bool layers[32];
/* get the values set in the operator properties */
RNA_boolean_get_array(op->ptr, "layers", layers);
/* set layers of pchans based on the values set in the operator props */
CTX_DATA_BEGIN_WITH_ID (C, EditBone *, ebone, selected_editable_bones, bArmature *, arm) {
/* get pointer for pchan, and write flags this way */
RNA_pointer_create((ID *)arm, &RNA_EditBone, ebone, &ptr);
RNA_boolean_set_array(&ptr, "layers", layers);
}
CTX_DATA_END;
ED_armature_edit_refresh_layer_used(static_cast<bArmature *>(ob->data));
/* NOTE: notifier might evolve. */
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
DEG_id_tag_update((ID *)ob->data, ID_RECALC_PARAMETERS);
return OPERATOR_FINISHED;
// TODO: remove this entire operator, replacing it with a similar one for bone collections.
WM_report(
RPT_ERROR,
"Bone Layers have been converted to Bone Collections. This operator will be removed soon.");
return OPERATOR_CANCELLED;
}
void ARMATURE_OT_bone_layers(wmOperatorType *ot)

View File

@ -35,6 +35,8 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "ANIM_bone_collections.h"
#include "armature_intern.h"
/* ********************************************** */

View File

@ -43,6 +43,8 @@
#include "ED_screen.hh"
#include "ED_util.hh"
#include "ANIM_bone_collections.h"
#include "armature_intern.h"
enum ePoseBlendState {

View File

@ -46,6 +46,8 @@
#include "ED_select_utils.hh"
#include "ED_view3d.hh"
#include "ANIM_bone_collections.h"
#include "armature_intern.h"
/* utility macros for storing a temp int in the bone (selection flag) */
@ -751,7 +753,7 @@ static int pose_select_hierarchy_exec(bContext *C, wmOperator *op)
const bool add_to_sel = RNA_boolean_get(op->ptr, "extend");
bool changed = false;
pchan_act = BKE_pose_channel_active_if_layer_visible(ob);
pchan_act = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (pchan_act == nullptr) {
return OPERATOR_CANCELLED;
}

View File

@ -796,7 +796,14 @@ static int pose_copy_exec(bContext *C, wmOperator *op)
Object ob_copy = blender::dna::shallow_copy(*ob);
ob_copy.adt = nullptr;
bArmature arm_copy = *((bArmature *)ob->data);
/* Copy the armature without using the default copy constructor. This prevents
* the compiler from complaining that the `layer`, `layer_used`, and
* `layer_protected` fields are DNA_DEPRECATED.
*/
bArmature arm_copy;
memcpy(&arm_copy, ob->data, sizeof(arm_copy));
arm_copy.adt = nullptr;
ob_copy.data = &arm_copy;
BLI_addtail(&temp_bmain->objects, &ob_copy);

View File

@ -39,11 +39,8 @@ struct wmOperator;
#define BONESEL_BONE (1u << 31)
#define BONESEL_ANY (BONESEL_TIP | BONESEL_ROOT | BONESEL_BONE)
/* useful macros */
#define EBONE_VISIBLE(arm, ebone) \
(CHECK_TYPE_INLINE(arm, bArmature *), \
CHECK_TYPE_INLINE(ebone, EditBone *), \
(((arm)->layer & (ebone)->layer) && !((ebone)->flag & BONE_HIDDEN_A)))
/* useful macros, be sure to #include "ANIM_bone_collections.h". */
#define EBONE_VISIBLE(arm, ebone) ANIM_bone_is_visible_editbone(arm, ebone)
#define EBONE_SELECTABLE(arm, ebone) \
(EBONE_VISIBLE(arm, ebone) && !((ebone)->flag & BONE_UNSELECTABLE))
@ -67,6 +64,8 @@ struct wmOperator;
EditBone *ED_armature_ebone_add(bArmature *arm, const char *name);
EditBone *ED_armature_ebone_add_primitive(Object *obedit_arm, float length, bool view_aligned);
void ED_armature_ebone_copy(EditBone *dest, const EditBone *source);
/* `armature_edit.cc` */
/**
@ -205,12 +204,6 @@ void ED_armature_undosys_type(UndoType *ut);
/** Sync selection to parent for connected children. */
void ED_armature_edit_sync_selection(ListBase *edbo);
void ED_armature_edit_validate_active(bArmature *arm);
/**
* Update the layers_used variable after bones are moved between layer
* \note Used to be done in drawing code in 2.7, but that won't work with
* Copy-on-Write, as drawing uses evaluated copies.
*/
void ED_armature_edit_refresh_layer_used(bArmature *arm);
/**
* \param clear_connected: When false caller is responsible for keeping the flag in a valid state.
*/

View File

@ -549,23 +549,6 @@ static void ui_item_array(uiLayout *layout,
const int butw = UI_UNIT_X * 0.75;
const int buth = UI_UNIT_X * 0.75;
if (ptr->type == &RNA_Armature) {
bArmature *arm = static_cast<bArmature *>(ptr->data);
layer_used = arm->layer_used;
if (arm->edbo) {
if (arm->act_edbone) {
layer_active |= arm->act_edbone->layer;
}
}
else {
if (arm->act_bone) {
layer_active |= arm->act_bone->layer;
}
}
}
for (int b = 0; b < cols; b++) {
UI_block_align_begin(block);

View File

@ -27,6 +27,7 @@
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_idprop.h"
#include "BKE_idtype.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_lib_override.hh"
@ -1131,6 +1132,44 @@ bool UI_context_copy_to_selected_list(bContext *C,
else if (RNA_struct_is_a(ptr->type, &RNA_Bone)) {
ui_context_selected_bones_via_pose(C, r_lb);
}
else if (RNA_struct_is_a(ptr->type, &RNA_BoneColor)) {
/* Get the things that own the bone color (bones, pose bones, or edit bones). */
ListBase list_of_things = {}; /* First this will be bones, then gets remapped to colors. */
switch (GS(ptr->owner_id->name)) {
case ID_OB:
list_of_things = CTX_data_collection_get(C, "selected_pose_bones");
break;
case ID_AR: {
/* Armature-owned bones can be accessed from both edit mode and pose mode.
* - Edit mode: visit selected edit bones.
* - Pose mode: visit the armature bones of selected pose bones.
*/
const bArmature *arm = reinterpret_cast<bArmature *>(ptr->owner_id);
if (arm->edbo) {
list_of_things = CTX_data_collection_get(C, "selected_editable_bones");
}
else {
list_of_things = CTX_data_collection_get(C, "selected_pose_bones");
CTX_data_collection_remap_property(list_of_things, "bone");
}
break;
}
default:
printf("BoneColor is unexpectedly owned by %s '%s'\n",
BKE_idtype_idcode_to_name(GS(ptr->owner_id->name)),
ptr->owner_id->name + 2);
BLI_assert(!"expected BoneColor to be owned by the Armature (bone & edit bone) or the Object (pose bone)");
return false;
}
/* Remap from some bone to its color, to ensure the items of r_lb are of
* type ptr->type. Since all three structs `bPoseChan`, `Bone`, and
* `EditBone` have the same name for their embedded `BoneColor` struct, this
* code is suitable for all of them. */
CTX_data_collection_remap_property(list_of_things, "color");
*r_lb = list_of_things;
}
else if (RNA_struct_is_a(ptr->type, &RNA_Sequence)) {
/* Special case when we do this for 'Sequence.lock'.
* (if the sequence is locked, it won't be in "selected_editable_sequences"). */

View File

@ -78,7 +78,7 @@ ListBase *ED_object_constraint_active_list(Object *ob)
if (ob->mode & OB_MODE_POSE) {
bPoseChannel *pchan;
pchan = BKE_pose_channel_active_if_layer_visible(ob);
pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (pchan) {
return &pchan->constraints;
}
@ -2204,7 +2204,7 @@ static bool get_new_constraint_target(
bContext *C, int con_type, Object **tar_ob, bPoseChannel **tar_pchan, bool add)
{
Object *obact = ED_object_active_context(C);
bPoseChannel *pchanact = BKE_pose_channel_active_if_layer_visible(obact);
bPoseChannel *pchanact = BKE_pose_channel_active_if_bonecoll_visible(obact);
bool only_curve = false, only_mesh = false, only_ob = false;
bool found = false;
@ -2362,7 +2362,7 @@ static int constraint_add_exec(
pchan = nullptr;
}
else {
pchan = BKE_pose_channel_active_if_layer_visible(ob);
pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
/* ensure not to confuse object/pose adding */
if (pchan == nullptr) {
@ -2636,7 +2636,7 @@ void POSE_OT_constraint_add_with_targets(wmOperatorType *ot)
static int pose_ik_add_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob);
bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
bConstraint *con = nullptr;
uiPopupMenu *pup;

View File

@ -577,7 +577,7 @@ static int add_hook_object(const bContext *C,
STRNCPY(hmd->subtarget, arm->act_bone->name);
pchan_act = BKE_pose_channel_active_if_layer_visible(ob);
pchan_act = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (LIKELY(pchan_act)) {
invert_m4_m4(pose_mat, pchan_act->pose_mat);
mul_v3_m4v3(cent, ob->object_to_world, pchan_act->pose_mat[3]);

View File

@ -2945,7 +2945,7 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain,
Object *arm_ob = BKE_object_add(bmain, scene, view_layer, OB_ARMATURE, nullptr);
BKE_object_transform_copy(arm_ob, skin_ob);
bArmature *arm = static_cast<bArmature *>(arm_ob->data);
ANIM_armature_ensure_first_layer_enabled(arm);
ANIM_armature_bonecoll_show_all(arm);
arm_ob->dtx |= OB_DRAW_IN_FRONT;
arm->drawtype = ARM_LINE;
arm->edbo = MEM_cnew<ListBase>("edbo armature");

View File

@ -571,8 +571,8 @@ bool ED_object_parent_set(ReportList *reports,
}
case PAR_BONE:
case PAR_BONE_RELATIVE:
pchan = BKE_pose_channel_active_if_layer_visible(par);
pchan_eval = BKE_pose_channel_active_if_layer_visible(parent_eval);
pchan = BKE_pose_channel_active_if_bonecoll_visible(par);
pchan_eval = BKE_pose_channel_active_if_bonecoll_visible(parent_eval);
if (pchan == nullptr || pchan_eval == nullptr) {
/* If pchan_eval is nullptr, pchan should also be nullptr. */

View File

@ -313,7 +313,7 @@ bool ED_object_jump_to_bone(bContext *C,
if (reveal_hidden) {
/* Unhide the bone. */
ebone->flag &= ~BONE_HIDDEN_A;
ANIM_armature_ensure_layer_enabled_from_ebone(arm, ebone);
ANIM_armature_bonecoll_show_from_ebone(arm, ebone);
}
/* Select it. */
@ -337,7 +337,7 @@ bool ED_object_jump_to_bone(bContext *C,
if (reveal_hidden) {
/* Unhide the bone. */
pchan->bone->flag &= ~BONE_HIDDEN_P;
ANIM_armature_ensure_layer_enabled_from_pchan(arm, pchan);
ANIM_armature_bonecoll_show_from_pchan(arm, pchan);
}
/* Select it. */

View File

@ -105,7 +105,7 @@ bool ED_object_calc_active_center_for_posemode(Object *ob,
const bool select_only,
float r_center[3])
{
bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob);
bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (pchan && (!select_only || (pchan->bone->flag & BONE_SELECTED))) {
copy_v3_v3(r_center, pchan->pose_head);
return true;

View File

@ -44,6 +44,7 @@ set(SRC
)
set(LIB
PRIVATE bf::animrig
PRIVATE bf::blenlib
PRIVATE bf::dna
bf_editor_datafiles

View File

@ -52,6 +52,8 @@
#include "UI_interface.hh"
#include "WM_api.hh"
#include "ANIM_bone_collections.h"
#include "screen_intern.h"
const char *screen_context_dir[] = {
@ -520,7 +522,7 @@ static eContextResult screen_ctx_active_pose_bone(const bContext *C, bContextDat
Object *obact = BKE_view_layer_active_object_get(view_layer);
Object *obpose = BKE_object_pose_armature_get(obact);
bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(obpose);
bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(obpose);
if (pchan) {
CTX_data_pointer_set(result, &obpose->id, &RNA_PoseBone, pchan);
return CTX_RESULT_OK;

View File

@ -325,7 +325,7 @@ static void stats_object_pose(const Object *ob, SceneStats *stats)
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
stats->totbone++;
if (pchan->bone && (pchan->bone->flag & BONE_SELECTED)) {
if (BKE_pose_is_layer_visible(arm, pchan)) {
if (BKE_pose_is_bonecoll_visible(arm, pchan)) {
stats->totbonesel++;
}
}

View File

@ -131,6 +131,7 @@ set(SRC
)
set(LIB
PRIVATE bf::animrig
bf_blenkernel
PRIVATE bf::blenlib
PRIVATE bf::dna

View File

@ -67,6 +67,8 @@
#include "RNA_define.hh"
#include "RNA_prototypes.h"
#include "ANIM_bone_collections.h"
#include "outliner_intern.hh"
#include "tree/tree_display.hh"
#include "tree/tree_element_grease_pencil_node.hh"

View File

@ -35,6 +35,8 @@
#include "WM_api.hh"
#include "WM_types.hh"
#include "ANIM_bone_collections.h"
#include "tree/tree_element_seq.hh"
#include "outliner_intern.hh"

View File

@ -1625,7 +1625,7 @@ static void v3d_posearmature_buts(uiLayout *layout, Object *ob)
PointerRNA pchanptr;
uiLayout *col;
pchan = BKE_pose_channel_active_if_layer_visible(ob);
pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (!pchan) {
uiItemL(layout, IFACE_("No Bone Active"), ICON_NONE);

View File

@ -127,7 +127,7 @@ static int view_lock_to_active_exec(bContext *C, wmOperator * /*op*/)
if (obact->mode & OB_MODE_POSE) {
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *obact_eval = DEG_get_evaluated_object(depsgraph, obact);
bPoseChannel *pcham_act = BKE_pose_channel_active_if_layer_visible(obact_eval);
bPoseChannel *pcham_act = BKE_pose_channel_active_if_bonecoll_visible(obact_eval);
if (pcham_act) {
STRNCPY(v3d->ob_center_bone, pcham_act->name);
}

View File

@ -123,7 +123,7 @@ static bool WIDGETGROUP_armature_spline_poll(const bContext *C, wmGizmoGroupType
if (ob) {
const bArmature *arm = static_cast<const bArmature *>(ob->data);
if (arm->drawtype == ARM_B_BONE) {
bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob);
bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (pchan && pchan->bone->segments > 1) {
return true;
}
@ -139,7 +139,7 @@ static void WIDGETGROUP_armature_spline_setup(const bContext *C, wmGizmoGroup *g
ViewLayer *view_layer = CTX_data_view_layer(C);
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob = BKE_object_pose_armature_get(BKE_view_layer_active_object_get(view_layer));
bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob);
bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
const wmGizmoType *gzt_move = WM_gizmotype_find("GIZMO_GT_move_3d", true);
@ -180,7 +180,7 @@ static void WIDGETGROUP_armature_spline_refresh(const bContext *C, wmGizmoGroup
}
BoneSplineWidgetGroup *bspline_group = static_cast<BoneSplineWidgetGroup *>(gzgroup->customdata);
bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob);
bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
/* Handles */
for (int i = 0; i < ARRAY_SIZE(bspline_group->handles); i++) {

View File

@ -35,6 +35,8 @@
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "ANIM_bone_collections.h"
#include "bmesh.h"
#include "ED_armature.hh"

View File

@ -98,6 +98,8 @@
#include "DRW_engine.h"
#include "DRW_select_buffer.h"
#include "ANIM_bone_collections.h"
#include "view3d_intern.h" /* own include */
// #include "PIL_time_utildefines.h"

View File

@ -37,6 +37,8 @@
#include "RNA_access.hh"
#include "RNA_prototypes.h"
#include "ANIM_bone_collections.h"
#include "transform.hh"
#include "transform_orientations.hh"
#include "transform_snap.hh"
@ -412,7 +414,7 @@ static short pose_grab_with_ik(Main *bmain, Object *ob)
/* Rule: allow multiple Bones
* (but they must be selected, and only one ik-solver per chain should get added) */
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
if (BKE_pose_is_layer_visible(arm, pchan)) {
if (BKE_pose_is_bonecoll_visible(arm, pchan)) {
if (pchan->bone->flag & (BONE_SELECTED | BONE_TRANSFORM_MIRROR)) {
/* Rule: no IK for solitary (unconnected) bones. */
for (bonec = static_cast<Bone *>(pchan->bone->childbase.first); bonec; bonec = bonec->next)

View File

@ -50,6 +50,8 @@
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "ANIM_bone_collections.h"
/* local module include */
#include "transform.hh"
#include "transform_convert.hh"

View File

@ -633,7 +633,7 @@ short ED_transform_calc_orientation_from_type_ex(const Scene *scene,
if (ob) {
if (ob->mode & OB_MODE_POSE) {
const bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob);
const bPoseChannel *pchan = BKE_pose_channel_active_if_bonecoll_visible(ob);
if (pchan && gimbal_axis_pose(ob, pchan, r_mat)) {
break;
}
@ -1361,7 +1361,7 @@ int getTransformOrientation_ex(const Scene *scene,
float imat[3][3], mat[3][3];
bool ok = false;
if (activeOnly && (pchan = BKE_pose_channel_active_if_layer_visible(ob))) {
if (activeOnly && (pchan = BKE_pose_channel_active_if_bonecoll_visible(ob))) {
add_v3_v3(normal, pchan->pose_mat[2]);
add_v3_v3(plane, pchan->pose_mat[1]);
ok = true;

View File

@ -491,7 +491,7 @@ void ArmatureImporter::create_armature_bones(Main *bmain, std::vector<Object *>
ED_armature_to_edit(armature);
/* Layers are enabled according to imported bone set in create_bone(). */
ANIM_armature_disable_all_layers(armature);
ANIM_armature_bonecoll_hide_all(armature);
create_bone(
nullptr, node, nullptr, node->getChildNodes().getCount(), nullptr, armature, layer_labels);

View File

@ -13,6 +13,7 @@
#pragma once
#include "DNA_ID.h"
#include "DNA_armature_types.h"
#include "DNA_listBase.h"
#include "DNA_session_uuid_types.h"
#include "DNA_userdef_types.h" /* ThemeWireColor */
@ -352,6 +353,8 @@ typedef struct bPoseChannel {
/** Points to an original pose channel. */
struct bPoseChannel *orig_pchan;
BoneColor color; /* MUST be named the same as in Bone and EditBone structs. */
/** Runtime data (keep last). */
struct bPoseChannel_Runtime runtime;
} bPoseChannel;

View File

@ -11,14 +11,22 @@
#include "DNA_ID.h"
#include "DNA_defs.h"
#include "DNA_listBase.h"
#include "DNA_userdef_types.h"
#include "BLI_utildefines.h"
#include "BLI_utildefines.h"
#ifdef __cplusplus
namespace blender::animrig {
class BoneColor;
}
extern "C" {
#endif
struct AnimData;
struct BoneCollection;
nathanvegdahl marked this conversation as resolved Outdated

Same comment as before, about preferring includes of the relevant header(s) to make it explicit where things come from. Although see this in other places as well, it appears this is a common pattern in Blender's code base. So maybe never mind.

Same comment as before, about preferring includes of the relevant header(s) to make it explicit where things come from. Although see this in other places as well, it appears this is a common pattern in Blender's code base. So maybe never mind.
/* this system works on different transformation space levels;
*
@ -28,6 +36,27 @@ struct AnimData;
* 4) World Space; Object matrix applied to Pose or Armature space
*/
typedef struct BoneColor {
/**
* Index of color palette to use when drawing bones.
* 0=default, >0 = predefined in theme, -1=custom color in #custom.
*
* For the predefined ones, see #rna_enum_color_sets_items in rna_armature.c.
*/
int8_t palette_index;
uint8_t _pad0[7];
ThemeWireColor custom;

When we were looking through this in person, I noted that we could eliminate this padding by arranging the fields from largest to smallest. However, looking at other DNA structs in this PR, it looks the strategy is to ensure a multiple-of-8-bytes size anyway, which I assume is a general pattern/requirement in DNA (which totally makes sense).

So moving palette_index to the end here wouldn't actually save us any space with these struct members.

When we were looking through this in person, I noted that we could eliminate this padding by arranging the fields from largest to smallest. However, looking at other DNA structs in this PR, it looks the strategy is to ensure a multiple-of-8-bytes size anyway, which I assume is a general pattern/requirement in DNA (which totally makes sense). So moving `palette_index` to the end here wouldn't actually save us any space with these struct members.
#ifdef __cplusplus
blender::animrig::BoneColor &wrap();
const blender::animrig::BoneColor &wrap() const;
#endif
} BoneColor;
typedef struct Bone_Runtime {
/* #BoneCollectionReference */
ListBase collections;
} Bone_Runtime;
typedef struct Bone {
/** Next/previous elements within this list. */
struct Bone *next, *prev;
@ -50,8 +79,11 @@ typedef struct Bone {
int flag;
char _pad1[4];
BoneColor color; /* MUST be named the same as in bPoseChannel and EditBone structs. */
char inherit_scale_mode;
char _pad[7];
char _pad[3];
float arm_head[3];
/** Head/tail in Armature Space (rest pose). */
@ -103,8 +135,22 @@ typedef struct Bone {
/** Next/prev bones to use as handle references when calculating bbones (optional). */
struct Bone *bbone_prev;
struct Bone *bbone_next;
/* Keep last. */
Bone_Runtime runtime;
} Bone;
typedef struct bArmature_Runtime {
/**
* Index of the active collection, -1 if there is no collection active.
*
* For UIList support in the user interface. Assigning here does nothing, use
* `ANIM_armature_bonecoll_active_set` to set the active bone collection.
*/
int active_collection_index;
uint8_t _pad0[4];
} bArmature_Runtime;
typedef struct bArmature {
ID id;
struct AnimData *adt;
@ -139,15 +185,68 @@ typedef struct bArmature {
short deformflag;
short pathflag;
/* BoneCollection. */
ListBase collections;
/* Do not directly assign, use `ANIM_armature_bonecoll_active_set` instead. */
struct BoneCollection *active_collection;
/** For UI, to show which layers are there. */
unsigned int layer_used;
unsigned int layer_used DNA_DEPRECATED;
/** For buttons to work, both variables in this order together. */
unsigned int layer, layer_protected;
unsigned int layer DNA_DEPRECATED, layer_protected DNA_DEPRECATED;
/** Relative position of the axes on the bone, from head (0.0f) to tail (1.0f). */
float axes_position;
/** Keep last, for consistency with the position of other DNA runtime structures. */
struct bArmature_Runtime runtime;
} bArmature;
/**
* Collection of Bones within an Armature.
*
* BoneCollections are owned by their Armature, and cannot be shared between
* different armatures.
*
* Bones can be in more than one collection at a time.
*
* Selectability and visibility of bones are determined by OR-ing the collection
* flags.
*/
typedef struct BoneCollection {
struct BoneCollection *next, *prev;
/** MAX_NAME. */
char name[64];
/** BoneCollectionMember. */
ListBase bones;
/** eBoneCollection_Flag. */
uint8_t flags;
uint8_t _pad0[7];
/** Custom properties. */
struct IDProperty *prop;
} BoneCollection;
/** Membership relation of a bone with a bone collection. */
typedef struct BoneCollectionMember {
struct BoneCollectionMember *next, *prev;
struct Bone *bone;
} BoneCollectionMember;
/**
* Membership relation of a bone with its collections.
*
* This is only bone-runtime data for easy lookups, the actual membership is
* stored on the #bArmature in #BoneCollectionMember structs.
*/
typedef struct BoneCollectionReference {
struct BoneCollectionReference *next, *prev;
struct BoneCollection *bcoll;
} BoneCollectionReference;
/* armature->flag */
/* don't use bit 7, was saved in files to disable stuff */
typedef enum eArmature_Flag {
@ -318,6 +417,22 @@ typedef enum eBone_BBoneHandleFlag {
#define MAXBONENAME 64
/** #BoneCollection.flag */
typedef enum eBoneCollection_Flag {
BONE_COLLECTION_VISIBLE = (1 << 0),
BONE_COLLECTION_SELECTABLE = (1 << 1), /* Intended to be implemented in the not-so-far future. */
} eBoneCollection_Flag;
ENUM_OPERATORS(eBoneCollection_Flag, BONE_COLLECTION_SELECTABLE)
#ifdef __cplusplus
inline blender::animrig::BoneColor &BoneColor::wrap()
{
return *reinterpret_cast<blender::animrig::BoneColor *>(this);
}
inline const blender::animrig::BoneColor &BoneColor::wrap() const
{
return *reinterpret_cast<const blender::animrig::BoneColor *>(this);
}
}
#endif

View File

@ -484,6 +484,7 @@ set(SRC
)
set(LIB
PRIVATE bf::animrig
PRIVATE bf::dna
bf_editor_space_api

View File

@ -22,9 +22,41 @@
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "ED_anim_api.hh"
#include "WM_api.hh"
#include "WM_types.hh"
/* Bone Collection Color Sets */
const EnumPropertyItem rna_enum_color_palettes_items[] = {
{0, "DEFAULT", 0, "Default Colors", ""},
{1, "THEME01", ICON_COLORSET_01_VEC, "01 - Theme Color Set", ""},
{2, "THEME02", ICON_COLORSET_02_VEC, "02 - Theme Color Set", ""},
{3, "THEME03", ICON_COLORSET_03_VEC, "03 - Theme Color Set", ""},
{4, "THEME04", ICON_COLORSET_04_VEC, "04 - Theme Color Set", ""},
{5, "THEME05", ICON_COLORSET_05_VEC, "05 - Theme Color Set", ""},
{6, "THEME06", ICON_COLORSET_06_VEC, "06 - Theme Color Set", ""},
{7, "THEME07", ICON_COLORSET_07_VEC, "07 - Theme Color Set", ""},
{8, "THEME08", ICON_COLORSET_08_VEC, "08 - Theme Color Set", ""},
{9, "THEME09", ICON_COLORSET_09_VEC, "09 - Theme Color Set", ""},
{10, "THEME10", ICON_COLORSET_10_VEC, "10 - Theme Color Set", ""},
{11, "THEME11", ICON_COLORSET_11_VEC, "11 - Theme Color Set", ""},
{12, "THEME12", ICON_COLORSET_12_VEC, "12 - Theme Color Set", ""},
{13, "THEME13", ICON_COLORSET_13_VEC, "13 - Theme Color Set", ""},
{14, "THEME14", ICON_COLORSET_14_VEC, "14 - Theme Color Set", ""},
{15, "THEME15", ICON_COLORSET_15_VEC, "15 - Theme Color Set", ""},
{16, "THEME16", ICON_COLORSET_16_VEC, "16 - Theme Color Set", ""},
{17, "THEME17", ICON_COLORSET_17_VEC, "17 - Theme Color Set", ""},
{18, "THEME18", ICON_COLORSET_18_VEC, "18 - Theme Color Set", ""},
{19, "THEME19", ICON_COLORSET_19_VEC, "19 - Theme Color Set", ""},
{20, "THEME20", ICON_COLORSET_20_VEC, "20 - Theme Color Set", ""},
{-1, "CUSTOM", 0, "Custom Color Set", ""},
{0, NULL, 0, NULL, NULL},
};
#ifdef RNA_RUNTIME
constexpr int COLOR_SETS_MAX_THEMED_INDEX = 20;
#endif
#ifdef RNA_RUNTIME
# include "BLI_math_vector.h"
@ -38,9 +70,15 @@
# include "BKE_armature.h"
# include "ED_armature.hh"
# include "ANIM_bone_collections.h"
# include "DEG_depsgraph.h"
# include "DEG_depsgraph_build.h"
# ifndef NDEBUG
# include "ANIM_armature_iter.hh"
# endif
static void rna_Armature_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr)
{
ID *id = ptr->owner_id;
@ -147,12 +185,216 @@ static void rna_Armature_edit_bone_remove(bArmature *arm,
RNA_POINTER_INVALIDATE(ebone_ptr);
}
static void rna_Armature_update_layers(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr)
static int rna_BoneCollections_active_index_get(PointerRNA *ptr)
{
bArmature *arm = (bArmature *)ptr->data;
return arm->runtime.active_collection_index;
}
static void rna_BoneCollections_active_index_set(PointerRNA *ptr, const int bone_collection_index)
{
bArmature *arm = (bArmature *)ptr->data;
ANIM_armature_bonecoll_active_index_set(arm, bone_collection_index);
// TODO: send notifiers?
}
static void rna_BoneCollections_active_index_range(
PointerRNA *ptr, int *min, int *max, int * /*softmin*/, int * /*softmax*/)
{
bArmature *arm = (bArmature *)ptr->data;
// TODO: Figure out what this function actually is used for, as we may want to protect the first
// collection (i.e. the default collection that should remain first).
*min = 0;
*max = max_ii(0, BLI_listbase_count(&arm->collections) - 1);
}
static void rna_BoneCollection_name_set(PointerRNA *ptr, const char *name)
{
bArmature *arm = (bArmature *)ptr->owner_id;
BoneCollection *bcoll = (BoneCollection *)ptr->data;
DEG_id_tag_update(&arm->id, ID_RECALC_COPY_ON_WRITE);
WM_main_add_notifier(NC_GEOM | ND_DATA, arm);
ANIM_armature_bonecoll_name_set(arm, bcoll, name);
// TODO: notifiers.
}
static char *rna_BoneCollection_path(const PointerRNA *ptr)
{
const BoneCollection *bcoll = (const BoneCollection *)ptr->data;
char name_esc[sizeof(bcoll->name) * 2];
BLI_str_escape(name_esc, bcoll->name, sizeof(name_esc));
return BLI_sprintfN("collections[\"%s\"]", name_esc);
}
static IDProperty **rna_BoneCollection_idprops(PointerRNA *ptr)
{
BoneCollection *bcoll = static_cast<BoneCollection *>(ptr->data);
return &bcoll->prop;
}
/* Bone.collections iterator functions. */
static void rna_Bone_collections_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
Bone *bone = (Bone *)ptr->data;
ListBase /*BoneCollectionReference*/ bone_collection_refs = bone->runtime.collections;
rna_iterator_listbase_begin(iter, &bone_collection_refs, nullptr);
}
static PointerRNA rna_Bone_collections_get(CollectionPropertyIterator *iter)
{
ListBaseIterator *lb_iter = &iter->internal.listbase;
BoneCollectionReference *bcoll_ref = (BoneCollectionReference *)lb_iter->link;
return rna_pointer_inherit_refine(&iter->parent, &RNA_BoneCollection, bcoll_ref->bcoll);
}
/* EditBone.collections iterator functions. */
static void rna_EditBone_collections_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
EditBone *ebone = (EditBone *)ptr->data;
ListBase /*BoneCollectionReference*/ bone_collection_refs = ebone->bone_collections;
rna_iterator_listbase_begin(iter, &bone_collection_refs, nullptr);
}
static char *rna_BoneColor_path_posebone(const PointerRNA *ptr)
{
/* Find the bPoseChan that owns this BoneColor. */
const uint8_t *bcolor_ptr = static_cast<const uint8_t *>(ptr->data);
const uint8_t *bone_ptr = bcolor_ptr - offsetof(bPoseChannel, color);
const bPoseChannel *bone = reinterpret_cast<const bPoseChannel *>(bone_ptr);
# ifndef NDEBUG
/* Sanity check that the above pointer magic actually worked. */
BLI_assert(GS(ptr->owner_id->name) == ID_OB);
const Object *ob = reinterpret_cast<const Object *>(ptr->owner_id);
bool found = false;
LISTBASE_FOREACH (bPoseChannel *, checkBone, &ob->pose->chanbase) {
if (&checkBone->color == ptr->data) {
BLI_assert_msg(checkBone == bone,
"pointer magic to find the pose bone failed (found the wrong bone)");
found = true;
break;
}
}
BLI_assert_msg(found, "pointer magic to find the pose bone failed (did not find the bone)");
# endif
char name_esc[sizeof(bone->name) * 2];
BLI_str_escape(name_esc, bone->name, sizeof(name_esc));
return BLI_sprintfN("pose.bones[\"%s\"].color", name_esc);
}
static char *rna_BoneColor_path_bone(const PointerRNA *ptr)
{
/* Find the Bone that owns this BoneColor. */
const uint8_t *bcolor_ptr = static_cast<const uint8_t *>(ptr->data);
const uint8_t *bone_ptr = bcolor_ptr - offsetof(Bone, color);
const Bone *bone = reinterpret_cast<const Bone *>(bone_ptr);
# ifndef NDEBUG
/* Sanity check that the above pointer magic actually worked. */
BLI_assert(GS(ptr->owner_id->name) == ID_AR);
const bArmature *arm = reinterpret_cast<const bArmature *>(ptr->owner_id);
bool found = false;
blender::animrig::ANIM_armature_foreach_bone(&arm->bonebase, [&](const Bone *checkBone) {
if (&checkBone->color == ptr->data) {
BLI_assert_msg(checkBone == bone,
"pointer magic to find the pose bone failed (found the wrong bone)");
found = true;
}
});
BLI_assert_msg(found, "pointer magic to find the pose bone failed (did not find the bone)");
# endif
char name_esc[sizeof(bone->name) * 2];
BLI_str_escape(name_esc, bone->name, sizeof(name_esc));
return BLI_sprintfN("bones[\"%s\"].color", name_esc);
}
static char *rna_BoneColor_path_editbone(const PointerRNA *ptr)
{
/* Find the Bone that owns this BoneColor. */
const uint8_t *bcolor_ptr = static_cast<const uint8_t *>(ptr->data);
const uint8_t *bone_ptr = bcolor_ptr - offsetof(EditBone, color);
const EditBone *bone = reinterpret_cast<const EditBone *>(bone_ptr);
# ifndef NDEBUG
/* Sanity check that the above pointer magic actually worked. */
BLI_assert(GS(ptr->owner_id->name) == ID_AR);
const bArmature *arm = reinterpret_cast<const bArmature *>(ptr->owner_id);
bool found = false;
LISTBASE_FOREACH (const EditBone *, checkBone, arm->edbo) {
if (&checkBone->color == ptr->data) {
BLI_assert_msg(checkBone == bone,
"pointer magic to find the pose bone failed (found the wrong bone)");
found = true;
break;
}
}
BLI_assert_msg(found, "pointer magic to find the pose bone failed (did not find the bone)");
# endif
char name_esc[sizeof(bone->name) * 2];
BLI_str_escape(name_esc, bone->name, sizeof(name_esc));
return BLI_sprintfN("bones[\"%s\"].color", name_esc);
}
static char *rna_BoneColor_path(const PointerRNA *ptr)
{
const ID *owner = ptr->owner_id;
BLI_assert_msg(owner, "expecting all bone colors to have an owner");
switch (GS(owner->name)) {
case ID_OB:
return rna_BoneColor_path_posebone(ptr);
case ID_AR: {
const bArmature *arm = reinterpret_cast<const bArmature *>(owner);
if (arm->edbo == nullptr) {
return rna_BoneColor_path_bone(ptr);
}
return rna_BoneColor_path_editbone(ptr);
}
default:
BLI_assert_msg(false, "expected object or armature");
return nullptr;
}
}
void rna_BoneColor_palette_index_set(PointerRNA *ptr, const int new_palette_index)
{
if (new_palette_index < -1 || new_palette_index > COLOR_SETS_MAX_THEMED_INDEX) {
BKE_reportf(nullptr, RPT_ERROR, "Invalid color palette index: %d", new_palette_index);
return;
}
BoneColor *bcolor = static_cast<BoneColor *>(ptr->data);
bcolor->palette_index = new_palette_index;
ID *id = ptr->owner_id;
DEG_id_tag_update(id, ID_RECALC_COPY_ON_WRITE);
WM_main_add_notifier(NC_GEOM | ND_DATA, id);
}
bool rna_BoneColor_is_custom_get(PointerRNA *ptr)
{
BoneColor *bcolor = static_cast<BoneColor *>(ptr->data);
return bcolor->palette_index < 0;
}
static void rna_BoneColor_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr)
{
/* Ugly hack to trigger the setting of the SACTION_RUNTIME_FLAG_NEED_CHAN_SYNC flag on the
* animation editors, which in turn calls ANIM_sync_animchannels_to_data(C) with the right
* context.
*
* Without this, changes to the bone colors are not reflected on the bActionGroup colors.
*/
WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, ptr->data);
}
static void rna_Armature_redraw_data(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr)
@ -259,40 +501,6 @@ static IDProperty **rna_EditBone_idprops(PointerRNA *ptr)
return &ebone->prop;
}
static void rna_bone_layer_set(int *layer, const bool *values)
{
int i, tot = 0;
/* ensure we always have some layer selected */
for (i = 0; i < 32; i++) {
if (values[i]) {
tot++;
}
}
if (tot == 0) {
return;
}
for (i = 0; i < 32; i++) {
if (values[i]) {
*layer |= (1u << i);
}
else {
*layer &= ~(1u << i);
}
}
}
static void rna_Bone_layer_set(PointerRNA *ptr, const bool *values)
{
bArmature *arm = (bArmature *)ptr->owner_id;
Bone *bone = (Bone *)ptr->data;
rna_bone_layer_set(&bone->layer, values);
BKE_armature_refresh_layer_used(nullptr, arm);
}
/* TODO: remove the deprecation stubs. */
static bool rna_use_inherit_scale_get(char inherit_scale_mode)
{
@ -327,32 +535,6 @@ static void rna_Bone_use_inherit_scale_set(PointerRNA *ptr, bool value)
rna_use_inherit_scale_set(&((Bone *)ptr->data)->inherit_scale_mode, value);
}
static void rna_Armature_layer_set(PointerRNA *ptr, const bool *values)
{
bArmature *arm = (bArmature *)ptr->data;
int i, tot = 0;
/* ensure we always have some layer selected */
for (i = 0; i < 32; i++) {
if (values[i]) {
tot++;
}
}
if (tot == 0) {
return;
}
for (i = 0; i < 32; i++) {
if (values[i]) {
arm->layer |= (1u << i);
}
else {
arm->layer &= ~(1u << i);
}
}
}
static void rna_EditBone_name_set(PointerRNA *ptr, const char *value)
{
bArmature *arm = (bArmature *)ptr->owner_id;
@ -381,12 +563,6 @@ static void rna_Bone_name_set(PointerRNA *ptr, const char *value)
ED_armature_bone_rename(G_MAIN, arm, oldname, newname);
}
static void rna_EditBone_layer_set(PointerRNA *ptr, const bool values[])
{
EditBone *data = (EditBone *)(ptr->data);
rna_bone_layer_set(&data->layer, values);
}
static void rna_EditBone_connected_check(EditBone *ebone)
{
if (ebone->parent) {
@ -574,6 +750,12 @@ static void rna_Bone_bbone_next_set(PointerRNA *ptr, PointerRNA value, ReportLis
}
}
static PointerRNA rna_EditBone_color_get(PointerRNA *ptr)
{
EditBone *data = (EditBone *)(ptr->data);
return rna_pointer_inherit_refine(ptr, &RNA_BoneColor, &data->color);
}
static void rna_Armature_editbone_transform_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
bArmature *arm = (bArmature *)ptr->owner_id;
@ -676,6 +858,41 @@ static void rna_Armature_relation_line_position_set(PointerRNA *ptr, const int v
#else
static void rna_def_bonecolor(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "BoneColor", nullptr);
RNA_def_struct_ui_text(srna, "BoneColor", "Theme color or custom color of a bone");
RNA_def_struct_ui_icon(srna, ICON_BONE_DATA);
RNA_def_struct_path_func(srna, "rna_BoneColor_path");
prop = RNA_def_property(srna, "palette", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "palette_index");
RNA_def_property_enum_items(prop, rna_enum_color_palettes_items);
RNA_def_property_enum_funcs(prop, NULL, "rna_BoneColor_palette_index_set", NULL);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Color Set", "Color palette to use");
RNA_def_property_update(prop, 0, "rna_BoneColor_update");
prop = RNA_def_property(srna, "is_custom", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop, "rna_BoneColor_is_custom_get", NULL);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop,
"Use Custom Color",
"A color palette is user-defined, instead of using a theme-defined one");
prop = RNA_def_property(srna, "custom", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_struct_type(prop, "ThemeBoneColorSet");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(
prop, "Custom", "The custom bone colors, used when palette is 'CUSTOM'");
RNA_def_property_update(prop, 0, "rna_BoneColor_update");
}
void rna_def_bone_curved_common(StructRNA *srna, bool is_posebone, bool is_editbone)
{
/* NOTE: The pose-mode values get applied over the top of the edit-mode ones. */
@ -873,19 +1090,14 @@ static void rna_def_bone_common(StructRNA *srna, int editbone)
RNA_define_lib_overridable(true);
/* flags */
prop = RNA_def_property(srna, "layers", PROP_BOOLEAN, PROP_LAYER_MEMBER);
RNA_def_property_boolean_sdna(prop, nullptr, "layer", 1);
RNA_def_property_array(prop, 32);
prop = RNA_def_property(srna, "color", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "BoneColor");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
if (editbone) {
RNA_def_property_boolean_funcs(prop, nullptr, "rna_EditBone_layer_set");
RNA_def_property_pointer_funcs(prop, "rna_EditBone_color_get", nullptr, nullptr, nullptr);
}
else {
RNA_def_property_boolean_funcs(prop, nullptr, "rna_Bone_layer_set");
}
RNA_def_property_ui_text(prop, "Layers", "Layers bone exists in");
RNA_def_property_update(prop, 0, "rna_Armature_redraw_data");
/* flags */
prop = RNA_def_property(srna, "use_connect", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", BONE_CONNECTED);
if (editbone) {
@ -1178,6 +1390,21 @@ static void rna_def_bone(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_PTR_NO_OWNERSHIP);
RNA_def_property_ui_text(prop, "Children", "Bones which are children of this bone");
prop = RNA_def_property(srna, "collections", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "BoneCollection");
RNA_def_property_collection_funcs(prop,
"rna_Bone_collections_begin",
"rna_iterator_listbase_next",
"rna_iterator_listbase_end",
"rna_Bone_collections_get",
nullptr,
nullptr,
nullptr,
nullptr);
RNA_def_property_flag(prop, PROP_PTR_NO_OWNERSHIP);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Collections", "Bone Collections that contain this bone");
rna_def_bone_common(srna, 0);
rna_def_bone_curved_common(srna, false, false);
@ -1284,6 +1511,21 @@ static void rna_def_edit_bone(BlenderRNA *brna)
RNA_def_struct_ui_text(srna, "Edit Bone", "Edit mode bone in an armature data-block");
RNA_def_struct_ui_icon(srna, ICON_BONE_DATA);
prop = RNA_def_property(srna, "collections", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "BoneCollection");
RNA_def_property_collection_funcs(prop,
"rna_EditBone_collections_begin",
"rna_iterator_listbase_next",
"rna_iterator_listbase_end",
"rna_Bone_collections_get",
nullptr,
nullptr,
nullptr,
nullptr);
RNA_def_property_flag(prop, PROP_PTR_NO_OWNERSHIP);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Collections", "Bone Collections that contain this bone");
RNA_define_verify_sdna(false); /* not in sdna */
prop = RNA_def_property(srna, "parent", PROP_POINTER, PROP_NONE);
@ -1450,6 +1692,66 @@ static void rna_def_armature_edit_bones(BlenderRNA *brna, PropertyRNA *cprop)
RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, ParameterFlag(0));
}
/** Armature.collections collection-of-bonecollections interface. */
static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
PropertyRNA *prop;
FunctionRNA *func;
PropertyRNA *parm;
RNA_def_property_srna(cprop, "BoneCollections");
srna = RNA_def_struct(brna, "BoneCollections", NULL);
RNA_def_struct_sdna(srna, "bArmature");
RNA_def_struct_ui_text(
srna, "Armature Bone Collections", "The Bone Collections of this Armature");
prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "BoneCollection");
RNA_def_property_pointer_sdna(prop, NULL, "active_collection");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Active Collection", "Armature's active bone collection");
prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "runtime.active_collection_index");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop,
"Active Collection Index",
"The index of the Armature's active bone collection; -1 when there "
"is no active collection");
RNA_def_property_int_funcs(prop,
"rna_BoneCollections_active_index_get",
"rna_BoneCollections_active_index_set",
"rna_BoneCollections_active_index_range");
/* Armature.collections.new(...) */
func = RNA_def_function(srna, "new", "ANIM_armature_bonecoll_new");
RNA_def_function_ui_description(func, "Add a new empty bone collection to the armature");
parm = RNA_def_string(func,
"name",
NULL,
0,
"Name",
"Name of the new collection. Blender will ensure it is unique within the "
"collections of the Armature");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
/* Return value. */
parm = RNA_def_pointer(
func, "bonecollection", "BoneCollection", "", "Newly created bone collection");
RNA_def_function_return(func, parm);
/* Armature.collections.remove(...) */
func = RNA_def_function(srna, "remove", "ANIM_armature_bonecoll_remove");
RNA_def_function_ui_description(func, "Remove the bone collection from the armature");
parm = RNA_def_pointer(func,
"bone_collection",
"BoneCollection",
"Bone Collection",
"The bone collection to remove");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
}
static void rna_def_armature(BlenderRNA *brna)
{
StructRNA *srna;
@ -1533,6 +1835,12 @@ static void rna_def_armature(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Edit Bones", "");
rna_def_armature_edit_bones(brna, prop);
prop = RNA_def_property(srna, "collections", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, NULL, "collections", NULL);
RNA_def_property_struct_type(prop, "BoneCollection");
RNA_def_property_ui_text(prop, "Bone Collections", "");
rna_def_armature_collections(brna, prop);
/* Enum values */
prop = RNA_def_property(srna, "pose_position", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, nullptr, "flag");
@ -1549,26 +1857,6 @@ static void rna_def_armature(BlenderRNA *brna)
RNA_def_property_update(prop, 0, "rna_Armature_redraw_data");
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);
/* Boolean values */
/* layer */
prop = RNA_def_property(srna, "layers", PROP_BOOLEAN, PROP_LAYER_MEMBER);
RNA_def_property_boolean_sdna(prop, nullptr, "layer", 1);
RNA_def_property_array(prop, 32);
RNA_def_property_ui_text(prop, "Visible Layers", "Armature layer visibility");
RNA_def_property_boolean_funcs(prop, nullptr, "rna_Armature_layer_set");
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Armature_update_layers");
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);
/* layer protection */
prop = RNA_def_property(srna, "layers_protected", PROP_BOOLEAN, PROP_LAYER);
RNA_def_property_boolean_sdna(prop, nullptr, "layer_protected", 1);
RNA_def_property_array(prop, 32);
RNA_def_property_ui_text(prop,
"Layer Override Protection",
"Protected layers in overridden instances are restored to "
"their original settings on file reload and undo");
RNA_def_property_update(prop, 0, "rna_Armature_redraw_data");
/* flag */
prop = RNA_def_property(srna, "show_axes", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", ARM_DRAWAXES);
@ -1632,8 +1920,37 @@ static void rna_def_armature(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
static void rna_def_bonecollection(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "BoneCollection", NULL);
RNA_def_struct_ui_text(srna, "BoneCollection", "Bone collection in an Armature data-block");
RNA_def_struct_path_func(srna, "rna_BoneCollection_path");
RNA_def_struct_idprops_func(srna, "rna_BoneCollection_idprops");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "name");
RNA_def_property_ui_text(prop, "Name", "Unique within the Armature");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_BoneCollection_name_set");
// RNA_def_property_update(prop, 0, "rna_Bone_update_renamed");
prop = RNA_def_property(srna, "is_visible", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flags", BONE_COLLECTION_VISIBLE);
RNA_def_property_ui_text(
prop, "Visible", "Bones in this collection will be visible in pose/object mode");
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, nullptr);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_api_bonecollection(srna);
}
void RNA_def_armature(BlenderRNA *brna)
{
rna_def_bonecolor(brna);
rna_def_bonecollection(brna);
rna_def_armature(brna);
rna_def_bone(brna);
rna_def_edit_bone(brna);

View File

@ -96,6 +96,81 @@ static void rna_Bone_AxisRollFromMatrix(const float matrix[9],
mat3_to_vec_roll(mat, r_axis, r_roll);
}
}
using bonecoll_assign_func_bone = bool (*)(BoneCollection *, Bone *);
using bonecoll_assign_func_ebone = bool (*)(BoneCollection *, EditBone *);
static bool rna_BoneCollection_assign_abstract(BoneCollection *bcoll,
bContext *C,
ReportList *reports,
PointerRNA *bone_ptr,
bonecoll_assign_func_bone assign_bone,
bonecoll_assign_func_ebone assign_ebone)
{
if (RNA_pointer_is_null(bone_ptr)) {
return false;
}
if (RNA_struct_is_a(bone_ptr->type, &RNA_PoseBone)) {
bPoseChannel *pchan = static_cast<bPoseChannel *>(bone_ptr->data);
const bool made_any_change = assign_bone(bcoll, pchan->bone);
if (made_any_change) {
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr);
}
return made_any_change;
}
if (RNA_struct_is_a(bone_ptr->type, &RNA_Bone)) {
Bone *bone = static_cast<Bone *>(bone_ptr->data);
const bool made_any_change = assign_bone(bcoll, bone);
if (made_any_change) {
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr);
}
return made_any_change;
}
if (RNA_struct_is_a(bone_ptr->type, &RNA_EditBone)) {
EditBone *ebone = static_cast<EditBone *>(bone_ptr->data);
const bool made_any_change = assign_ebone(bcoll, ebone);
if (made_any_change) {
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, nullptr);
}
return made_any_change;
}
BKE_reportf(reports,
RPT_ERROR,
"%s is not supported, pass a Bone, PoseBone, or EditBone",
RNA_struct_identifier(bone_ptr->type));
return false;
}
static bool rna_BoneCollection_assign(BoneCollection *bcoll,
bContext *C,
ReportList *reports,
PointerRNA *bone_ptr)
{
return rna_BoneCollection_assign_abstract(bcoll,
C,
reports,
bone_ptr,
ANIM_armature_bonecoll_assign,
ANIM_armature_bonecoll_assign_editbone);
}
static bool rna_BoneCollection_unassign(BoneCollection *bcoll,
bContext *C,
ReportList *reports,
PointerRNA *bone_ptr)
{
return rna_BoneCollection_assign_abstract(bcoll,
C,
reports,
bone_ptr,
ANIM_armature_bonecoll_unassign,
ANIM_armature_bonecoll_unassign_editbone);
}
#else
void RNA_api_armature_edit_bone(StructRNA *srna)
@ -203,4 +278,48 @@ void RNA_api_bone(StructRNA *srna)
RNA_def_function_output(func, parm);
}
void RNA_api_bonecollection(StructRNA *srna)
{
PropertyRNA *parm;
FunctionRNA *func;
func = RNA_def_function(srna, "assign", "rna_BoneCollection_assign");
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
RNA_def_function_ui_description(func, "Assign the given bone to this collection");
parm = RNA_def_pointer(
func,
"bone",
"AnyType",
"",
"Bone to assign to this collection. This must be a Bone, PoseBone, or EditBone");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED | PARM_RNAPTR);
/* return value */
parm = RNA_def_boolean(func,
"assigned",
false,
"Assigned",
"Whether the bone was actually assigned; will be false if the bone was "
"already member of the collection");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "unassign", "rna_BoneCollection_unassign");
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
RNA_def_function_ui_description(func, "Remove the given bone from this collection");
parm = RNA_def_pointer(
func,
"bone",
"AnyType",
"",
"Bone to remove from this collection. This must be a Bone, PoseBone, or EditBone");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED | PARM_RNAPTR);
/* return value */
parm = RNA_def_boolean(func,
"assigned",
false,
"Unassigned",
"Whether the bone was actually removed; will be false if the bone was "
"not a member of the collection to begin with");
RNA_def_function_return(func, parm);
}
#endif

View File

@ -425,6 +425,7 @@ void RNA_api_action(StructRNA *srna);
void RNA_api_animdata(struct StructRNA *srna);
void RNA_api_armature_edit_bone(StructRNA *srna);
void RNA_api_bone(StructRNA *srna);
void RNA_api_bonecollection(StructRNA *srna);
void RNA_api_camera(StructRNA *srna);
void RNA_api_curve(StructRNA *srna);
void RNA_api_curve_nurb(StructRNA *srna);

View File

@ -1407,6 +1407,10 @@ static void rna_def_pose_channel(BlenderRNA *brna)
RNA_def_property_editable_func(prop, "rna_PoseChannel_proxy_editable");
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update");
prop = RNA_def_property(srna, "color", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "BoneColor");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
/* transform locks */
prop = RNA_def_property(srna, "lock_location", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "protectflag", OB_LOCK_LOCX);