diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 3c71a23cf40..c7fbf5bed49 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -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), diff --git a/scripts/startup/bl_ui/properties_data_armature.py b/scripts/startup/bl_ui/properties_data_armature.py index 5466d4ade81..ca854d975ac 100644 --- a/scripts/startup/bl_ui/properties_data_armature.py +++ b/scripts/startup/bl_ui/properties_data_armature.py @@ -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,23 +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') - - -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): @@ -115,44 +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' + 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") 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") class DATA_PT_iksolver_itasc(ArmatureButtonsPanel, Panel): @@ -258,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, diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 96a9530fb4a..4a389dc291a 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -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): @@ -8438,7 +8447,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, diff --git a/source/blender/animrig/ANIM_bone_collections.h b/source/blender/animrig/ANIM_bone_collections.h index 038650cb167..db864374180 100644 --- a/source/blender/animrig/ANIM_bone_collections.h +++ b/source/blender/animrig/ANIM_bone_collections.h @@ -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 diff --git a/source/blender/animrig/CMakeLists.txt b/source/blender/animrig/CMakeLists.txt index 87ed8ff89f2..280207e02cf 100644 --- a/source/blender/animrig/CMakeLists.txt +++ b/source/blender/animrig/CMakeLists.txt @@ -25,18 +25,21 @@ 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() diff --git a/source/blender/animrig/intern/bone_collections.cc b/source/blender/animrig/intern/bone_collections.cc index 242cceef4d4..84f2193d61b 100644 --- a/source/blender/animrig/intern/bone_collections.cc +++ b/source/blender/animrig/intern/bone_collections.cc @@ -6,57 +6,416 @@ * \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 "ANIM_armature_iter.hh" #include "ANIM_bone_collections.h" +#include +#include + +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(alloc_name.c_str()); + + BLI_strncpy(bcoll->name, name, sizeof(bcoll->name)); + bcoll->flags = default_flags; + + 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(__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(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) +{ + BLI_strncpy(bcoll->name, name, sizeof(bcoll->name)); + bonecoll_ensure_name_unique(armature, bcoll); +} + +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(__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(__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(__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( + 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( + 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); } diff --git a/source/blender/animrig/intern/bone_collections_test.cc b/source/blender/animrig/intern/bone_collections_test.cc new file mode 100644 index 00000000000..031ec922652 --- /dev/null +++ b/source/blender/animrig/intern/bone_collections_test.cc @@ -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 + { + STRNCPY(bone1.name, "bone1"); + STRNCPY(bone2.name, "bone2"); + STRNCPY(bone3.name, "bone3"); + + memset(&arm, 0, sizeof(arm)); + 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. */ + } +}; + +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(BLI_findlink(&bcoll->bones, 0))->bone); + EXPECT_EQ(&bone2, static_cast(BLI_findlink(&bcoll->bones, 1))->bone); + + EXPECT_EQ(bcoll, static_cast(bone1.runtime.collections.first)->bcoll) + << "expecting back-reference to collection in bone1 runtime data"; + EXPECT_EQ(bcoll, static_cast(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 diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h index 1a044b438f5..a56a39b0720 100644 --- a/source/blender/blenkernel/BKE_armature.h +++ b/source/blender/blenkernel/BKE_armature.h @@ -104,6 +104,7 @@ typedef struct EditBone { 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 { @@ -204,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`. */ @@ -569,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)) diff --git a/source/blender/blenkernel/intern/armature.cc b/source/blender/blenkernel/intern/armature.cc index e0b4cc4ffa8..1314b66443e 100644 --- a/source/blender/blenkernel/intern/armature.cc +++ b/source/blender/blenkernel/intern/armature.cc @@ -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,32 @@ 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) { + 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) { + BLI_freelistN(&bcoll->bones); + } + BLI_freelistN(&armature->collections); BKE_armature_bone_hash_free(armature); BKE_armature_bonelist_free(&armature->bonebase, false); @@ -190,8 +216,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 +234,19 @@ 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); + BLO_write_struct_list(writer, BoneCollectionMember, &bcoll->bones); + + // /* 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); + // } +} + static void armature_blend_write(BlendWriter *writer, ID *id, const void *id_address) { bArmature *arm = (bArmature *)id; @@ -216,13 +258,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 +291,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_list(reader, &bcoll->bones); + LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) { + BLO_read_data_address(reader, &member->bone); + } + + // BLO_read_data_address(reader, &bcoll->prop); + // IDP_BlendDataRead(reader, &bcoll->prop); } static void armature_blend_read_data(BlendDataReader *reader, ID *id) @@ -256,10 +319,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 +401,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 +435,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 +704,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( diff --git a/source/blender/blenkernel/intern/armature_selection.cc b/source/blender/blenkernel/intern/armature_selection.cc index 795da8d00fa..b5373e7ecdf 100644 --- a/source/blender/blenkernel/intern/armature_selection.cc +++ b/source/blender/blenkernel/intern/armature_selection.cc @@ -13,6 +13,8 @@ #include "DNA_action_types.h" #include "DNA_armature_types.h" +#include "ANIM_bone_collections.h" + namespace blender::bke { namespace { diff --git a/source/blender/blenkernel/intern/armature_test.cc b/source/blender/blenkernel/intern/armature_test.cc index da44e8e1c27..a6dbcef7279 100644 --- a/source/blender/blenkernel/intern/armature_test.cc +++ b/source/blender/blenkernel/intern/armature_test.cc @@ -11,6 +11,8 @@ #include "DNA_armature_types.h" +#include "ANIM_bone_collections.h" + #include "testing/testing.h" namespace blender::bke::tests { @@ -369,9 +371,6 @@ class BKE_armature_find_selected_bones_test : public testing::Test { 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; } }; diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index d90fddb60da..1c94741825d 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -32,6 +32,7 @@ #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" @@ -42,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.hh" @@ -108,6 +114,110 @@ static void version_bonegroup_migrate_color(Main *bmain) } } +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(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> 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(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)) { @@ -183,6 +293,11 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain) 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); + } } } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 73deafa55dd..8b3d5f5670a 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -1792,14 +1792,14 @@ 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); } diff --git a/source/blender/editors/armature/CMakeLists.txt b/source/blender/editors/armature/CMakeLists.txt index 04b9ec24cd6..7fcbd4ceb70 100644 --- a/source/blender/editors/armature/CMakeLists.txt +++ b/source/blender/editors/armature/CMakeLists.txt @@ -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 diff --git a/source/blender/editors/armature/armature_add.cc b/source/blender/editors/armature/armature_add.cc index b9265427551..63156c1928a 100644 --- a/source/blender/editors/armature/armature_add.cc +++ b/source/blender/editors/armature/armature_add.cc @@ -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(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(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); diff --git a/source/blender/editors/armature/armature_edit.cc b/source/blender/editors/armature/armature_edit.cc index bafafa5126a..223c454170a 100644 --- a/source/blender/editors/armature/armature_edit.cc +++ b/source/blender/editors/armature/armature_edit.cc @@ -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); diff --git a/source/blender/editors/armature/armature_intern.h b/source/blender/editors/armature/armature_intern.h index 7c603bea370..32e4c57e75a 100644 --- a/source/blender/editors/armature/armature_intern.h +++ b/source/blender/editors/armature/armature_intern.h @@ -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); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/armature/armature_naming.cc b/source/blender/editors/armature/armature_naming.cc index 9c0d0319022..1b941dce672 100644 --- a/source/blender/editors/armature/armature_naming.cc +++ b/source/blender/editors/armature/armature_naming.cc @@ -49,6 +49,8 @@ #include "ED_armature.hh" #include "ED_screen.hh" +#include "ANIM_bone_collections.h" + #include "armature_intern.h" /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/armature/armature_ops.cc b/source/blender/editors/armature/armature_ops.cc index b2b9f1bda37..13d668dfc24 100644 --- a/source/blender/editors/armature/armature_ops.cc +++ b/source/blender/editors/armature/armature_ops.cc @@ -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); diff --git a/source/blender/editors/armature/armature_relations.cc b/source/blender/editors/armature/armature_relations.cc index d5b76d0ad13..01f5868272c 100644 --- a/source/blender/editors/armature/armature_relations.cc +++ b/source/blender/editors/armature/armature_relations.cc @@ -50,6 +50,8 @@ #include "UI_interface.hh" #include "UI_resources.hh" +#include "ANIM_bone_collections.h" + #include "armature_intern.h" /* -------------------------------------------------------------------- */ @@ -676,7 +678,6 @@ static int separate_armature_exec(bContext *C, wmOperator *op) /* 5) restore original conditions */ ED_armature_to_edit(static_cast(ob_old->data)); - ED_armature_edit_refresh_layer_used(static_cast(ob_old->data)); /* parents tips remain selected when connected children are removed. */ ED_armature_edit_deselect_all(ob_old); diff --git a/source/blender/editors/armature/armature_select.cc b/source/blender/editors/armature/armature_select.cc index 6ecd3de2b24..5203820c031 100644 --- a/source/blender/editors/armature/armature_select.cc +++ b/source/blender/editors/armature/armature_select.cc @@ -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) */ diff --git a/source/blender/editors/armature/armature_utils.cc b/source/blender/editors/armature/armature_utils.cc index 191e212be31..7fac273845c 100644 --- a/source/blender/editors/armature/armature_utils.cc +++ b/source/blender/editors/armature/armature_utils.cc @@ -33,6 +33,8 @@ #include "armature_intern.h" +#include + /* -------------------------------------------------------------------- */ /** \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, @@ -518,6 +522,7 @@ static EditBone *make_boneList_recursive(ListBase *edbo, 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); @@ -732,6 +737,12 @@ void ED_armature_from_edit(Main *bmain, bArmature *arm) newBone->color = eBone->color; + LISTBASE_FOREACH (BoneCollectionReference *, ref, &eBone->bone_collections) { + BoneCollectionReference *newBoneRef = MEM_cnew( + "ED_armature_from_edit", *ref); + BLI_addtail(&newBone->runtime.collections, newBoneRef); + } + if (eBone->prop) { newBone->prop = IDP_CopyProperty(eBone->prop); } @@ -763,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); @@ -787,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); @@ -852,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; } } diff --git a/source/blender/editors/armature/bone_collections.cc b/source/blender/editors/armature/bone_collections.cc new file mode 100644 index 00000000000..5cc9ccacb30 --- /dev/null +++ b/source/blender/editors/armature/bone_collections.cc @@ -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 + +#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(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(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(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(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(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(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(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(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(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( + 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(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; +} + +/* ********************************************** */ diff --git a/source/blender/editors/armature/pose_edit.cc b/source/blender/editors/armature/pose_edit.cc index 2b08f4246fd..d800732f8f9 100644 --- a/source/blender/editors/armature/pose_edit.cc +++ b/source/blender/editors/armature/pose_edit.cc @@ -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(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) diff --git a/source/blender/editors/armature/pose_group.cc b/source/blender/editors/armature/pose_group.cc index f282d92fccf..fa0c39795b3 100644 --- a/source/blender/editors/armature/pose_group.cc +++ b/source/blender/editors/armature/pose_group.cc @@ -35,6 +35,8 @@ #include "UI_interface.hh" #include "UI_resources.hh" +#include "ANIM_bone_collections.h" + #include "armature_intern.h" /* ********************************************** */ diff --git a/source/blender/editors/armature/pose_lib_2.cc b/source/blender/editors/armature/pose_lib_2.cc index 6e42221f093..82ff7ba7601 100644 --- a/source/blender/editors/armature/pose_lib_2.cc +++ b/source/blender/editors/armature/pose_lib_2.cc @@ -43,6 +43,8 @@ #include "ED_screen.hh" #include "ED_util.hh" +#include "ANIM_bone_collections.h" + #include "armature_intern.h" enum ePoseBlendState { diff --git a/source/blender/editors/armature/pose_select.cc b/source/blender/editors/armature/pose_select.cc index 07514c5d05d..4b34ebdcbc6 100644 --- a/source/blender/editors/armature/pose_select.cc +++ b/source/blender/editors/armature/pose_select.cc @@ -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) */ diff --git a/source/blender/editors/armature/pose_transform.cc b/source/blender/editors/armature/pose_transform.cc index f2dc22ebdfc..0b4cb38e952 100644 --- a/source/blender/editors/armature/pose_transform.cc +++ b/source/blender/editors/armature/pose_transform.cc @@ -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); diff --git a/source/blender/editors/include/ED_armature.hh b/source/blender/editors/include/ED_armature.hh index 6fa0b6e8369..3a5cce136a5 100644 --- a/source/blender/editors/include/ED_armature.hh +++ b/source/blender/editors/include/ED_armature.hh @@ -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. */ diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 5dd4424d379..700f5872c1d 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -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(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); diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index c9b0737e487..96a1bfb6846 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -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(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("edbo armature"); diff --git a/source/blender/editors/object/object_select.cc b/source/blender/editors/object/object_select.cc index 07432fa6a1d..8d14683f40c 100644 --- a/source/blender/editors/object/object_select.cc +++ b/source/blender/editors/object/object_select.cc @@ -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. */ diff --git a/source/blender/editors/screen/CMakeLists.txt b/source/blender/editors/screen/CMakeLists.txt index 402c85c8eac..607f5138d18 100644 --- a/source/blender/editors/screen/CMakeLists.txt +++ b/source/blender/editors/screen/CMakeLists.txt @@ -44,6 +44,7 @@ set(SRC ) set(LIB + PRIVATE bf::animrig PRIVATE bf::blenlib PRIVATE bf::dna bf_editor_datafiles diff --git a/source/blender/editors/screen/screen_context.cc b/source/blender/editors/screen/screen_context.cc index 2874b09ea23..5adab1f3dd4 100644 --- a/source/blender/editors/screen/screen_context.cc +++ b/source/blender/editors/screen/screen_context.cc @@ -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[] = { diff --git a/source/blender/editors/space_outliner/CMakeLists.txt b/source/blender/editors/space_outliner/CMakeLists.txt index 9fc84d78a4e..3e4bcb6e247 100644 --- a/source/blender/editors/space_outliner/CMakeLists.txt +++ b/source/blender/editors/space_outliner/CMakeLists.txt @@ -131,6 +131,7 @@ set(SRC ) set(LIB + PRIVATE bf::animrig bf_blenkernel PRIVATE bf::blenlib PRIVATE bf::dna diff --git a/source/blender/editors/space_outliner/outliner_select.cc b/source/blender/editors/space_outliner/outliner_select.cc index 186f494ea58..53ce10c4630 100644 --- a/source/blender/editors/space_outliner/outliner_select.cc +++ b/source/blender/editors/space_outliner/outliner_select.cc @@ -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" diff --git a/source/blender/editors/space_outliner/outliner_sync.cc b/source/blender/editors/space_outliner/outliner_sync.cc index 3b4ab763909..da356e77cb1 100644 --- a/source/blender/editors/space_outliner/outliner_sync.cc +++ b/source/blender/editors/space_outliner/outliner_sync.cc @@ -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" diff --git a/source/blender/editors/space_view3d/view3d_iterators.cc b/source/blender/editors/space_view3d/view3d_iterators.cc index 17da7d4133e..62a4a0a3772 100644 --- a/source/blender/editors/space_view3d/view3d_iterators.cc +++ b/source/blender/editors/space_view3d/view3d_iterators.cc @@ -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" diff --git a/source/blender/editors/space_view3d/view3d_select.cc b/source/blender/editors/space_view3d/view3d_select.cc index 98c3199c3b8..030e2bbda3c 100644 --- a/source/blender/editors/space_view3d/view3d_select.cc +++ b/source/blender/editors/space_view3d/view3d_select.cc @@ -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" diff --git a/source/blender/editors/transform/transform_convert_armature.cc b/source/blender/editors/transform/transform_convert_armature.cc index 019559de30f..235aa6593fd 100644 --- a/source/blender/editors/transform/transform_convert_armature.cc +++ b/source/blender/editors/transform/transform_convert_armature.cc @@ -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" diff --git a/source/blender/editors/transform/transform_gizmo_3d.cc b/source/blender/editors/transform/transform_gizmo_3d.cc index 0740e2dfbcc..b0eab61c9ae 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.cc +++ b/source/blender/editors/transform/transform_gizmo_3d.cc @@ -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" diff --git a/source/blender/io/collada/ArmatureImporter.cpp b/source/blender/io/collada/ArmatureImporter.cpp index 168aa996a65..5117f7d31dd 100644 --- a/source/blender/io/collada/ArmatureImporter.cpp +++ b/source/blender/io/collada/ArmatureImporter.cpp @@ -491,7 +491,7 @@ void ArmatureImporter::create_armature_bones(Main *bmain, std::vector 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); diff --git a/source/blender/makesdna/DNA_armature_types.h b/source/blender/makesdna/DNA_armature_types.h index 6e7a9db6b56..e02f2743f54 100644 --- a/source/blender/makesdna/DNA_armature_types.h +++ b/source/blender/makesdna/DNA_armature_types.h @@ -15,6 +15,8 @@ #include "BLI_utildefines.h" +#include "BLI_utildefines.h" + #ifdef __cplusplus namespace blender::animrig { class BoneColor; @@ -24,6 +26,7 @@ extern "C" { #endif struct AnimData; +struct BoneCollection; /* this system works on different transformation space levels; * @@ -49,6 +52,11 @@ typedef struct BoneColor { #endif } BoneColor; +typedef struct Bone_Runtime { + /* #BoneCollectionReference */ + ListBase collections; +} Bone_Runtime; + typedef struct Bone { /** Next/previous elements within this list. */ struct Bone *next, *prev; @@ -127,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; @@ -163,15 +185,67 @@ 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]; + + // TODO: add IDProperties. +} 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 { @@ -342,6 +416,13 @@ 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() diff --git a/source/blender/makesrna/intern/rna_armature.cc b/source/blender/makesrna/intern/rna_armature.cc index 486335b90fe..832f8773f78 100644 --- a/source/blender/makesrna/intern/rna_armature.cc +++ b/source/blender/makesrna/intern/rna_armature.cc @@ -22,6 +22,8 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "ED_anim_api.hh" + #include "WM_api.hh" #include "WM_types.hh" @@ -68,6 +70,8 @@ constexpr int COLOR_SETS_MAX_THEMED_INDEX = 20; # include "BKE_armature.h" # include "ED_armature.hh" +# include "ANIM_bone_collections.h" + # include "DEG_depsgraph.h" # include "DEG_depsgraph_build.h" @@ -181,12 +185,63 @@ 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. +} + +/* 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) @@ -431,40 +486,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) { @@ -499,32 +520,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; @@ -553,12 +548,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) { @@ -1094,18 +1083,6 @@ static void rna_def_bone_common(StructRNA *srna, int editbone) } /* 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); - if (editbone) { - RNA_def_property_boolean_funcs(prop, nullptr, "rna_EditBone_layer_set"); - } - 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"); - prop = RNA_def_property(srna, "use_connect", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flag", BONE_CONNECTED); if (editbone) { @@ -1398,6 +1375,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); @@ -1504,6 +1496,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); @@ -1670,6 +1677,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; @@ -1753,6 +1820,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"); @@ -1769,26 +1842,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); @@ -1852,9 +1905,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); diff --git a/source/blender/makesrna/intern/rna_armature_api.cc b/source/blender/makesrna/intern/rna_armature_api.cc index 09995922362..4d32c87f94c 100644 --- a/source/blender/makesrna/intern/rna_armature_api.cc +++ b/source/blender/makesrna/intern/rna_armature_api.cc @@ -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(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_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(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 diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index b28c411bd08..a8bd44a913a 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -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);