1257 lines
41 KiB
C++
1257 lines
41 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup edarmature
|
|
* Implementation of Bone Collection operators and editing API's.
|
|
*/
|
|
|
|
#include <cstring>
|
|
|
|
#include "ANIM_bone_collections.hh"
|
|
|
|
#include "DNA_ID.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BKE_action.h"
|
|
#include "BKE_context.hh"
|
|
#include "BKE_lib_override.hh"
|
|
#include "BKE_report.hh"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_define.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "ED_armature.hh"
|
|
#include "ED_object.hh"
|
|
#include "ED_outliner.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
|
|
#include "armature_intern.hh"
|
|
|
|
struct wmOperator;
|
|
|
|
/* ********************************************** */
|
|
/* Bone collections */
|
|
|
|
static bool bone_collection_add_poll(bContext *C)
|
|
{
|
|
bArmature *armature = ED_armature_context(C);
|
|
if (armature == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (ID_IS_LINKED(&armature->id)) {
|
|
CTX_wm_operator_poll_msg_set(
|
|
C, "Cannot add bone collections to a linked Armature without an override");
|
|
return false;
|
|
}
|
|
|
|
if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
|
|
CTX_wm_operator_poll_msg_set(C,
|
|
"Cannot add bone collections to a linked Armature with a system "
|
|
"override; explicitly create an override on the Armature");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Allow edits of local bone collection only (full local or local override). */
|
|
static bool active_bone_collection_poll(bContext *C)
|
|
{
|
|
bArmature *armature = ED_armature_context(C);
|
|
if (armature == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
|
|
CTX_wm_operator_poll_msg_set(C,
|
|
"Cannot update a linked Armature with a system override; "
|
|
"explicitly create an override on the Armature");
|
|
return false;
|
|
}
|
|
|
|
BoneCollection *bcoll = armature->runtime.active_collection;
|
|
if (bcoll == nullptr) {
|
|
CTX_wm_operator_poll_msg_set(C, "Armature has no active bone collection, select one first");
|
|
return false;
|
|
}
|
|
|
|
if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
|
|
CTX_wm_operator_poll_msg_set(
|
|
C, "Cannot edit bone collections that are linked from another blend file");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int bone_collection_add_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
using namespace blender::animrig;
|
|
|
|
bArmature *armature = ED_armature_context(C);
|
|
|
|
/* If there is an active bone collection, create the new one as a sibling. */
|
|
const int parent_index = armature_bonecoll_find_parent_index(
|
|
armature, armature->runtime.active_collection_index);
|
|
|
|
BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, nullptr, parent_index);
|
|
|
|
if (armature->runtime.active_collection) {
|
|
const int active_child_index = armature_bonecoll_child_number_find(
|
|
armature, armature->runtime.active_collection);
|
|
armature_bonecoll_child_number_set(armature, bcoll, active_child_index + 1);
|
|
}
|
|
|
|
ANIM_armature_bonecoll_active_set(armature, bcoll);
|
|
/* TODO: ensure the ancestors of the new bone collection are all expanded. */
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr);
|
|
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_add_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
static int bone_collection_remove_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
/* The poll function ensures armature->active_collection is not NULL. */
|
|
bArmature *armature = ED_armature_context(C);
|
|
ANIM_armature_bonecoll_remove(armature, armature->runtime.active_collection);
|
|
|
|
/* notifiers for updates */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr);
|
|
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)
|
|
{
|
|
const int direction = RNA_enum_get(op->ptr, "direction");
|
|
|
|
/* Poll function makes sure this is valid. */
|
|
bArmature *armature = ED_armature_context(C);
|
|
|
|
const bool ok = ANIM_armature_bonecoll_move(
|
|
armature, armature->runtime.active_collection, direction);
|
|
if (!ok) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
ANIM_armature_bonecoll_active_runtime_refresh(armature);
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_COLLECTION, nullptr);
|
|
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, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
/* 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");
|
|
}
|
|
|
|
static BoneCollection *get_bonecoll_named_or_active(bContext * /*C*/, wmOperator *op, Object *ob)
|
|
{
|
|
bArmature *armature = static_cast<bArmature *>(ob->data);
|
|
|
|
char bcoll_name[MAX_NAME];
|
|
RNA_string_get(op->ptr, "name", bcoll_name);
|
|
|
|
if (bcoll_name[0] == '\0') {
|
|
return armature->runtime.active_collection;
|
|
}
|
|
|
|
BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(armature, bcoll_name);
|
|
if (!bcoll) {
|
|
WM_reportf(RPT_ERROR, "No bone collection named '%s'", bcoll_name);
|
|
return nullptr;
|
|
}
|
|
|
|
return bcoll;
|
|
}
|
|
|
|
using assign_bone_func = bool (*)(BoneCollection *bcoll, Bone *bone);
|
|
using assign_ebone_func = bool (*)(BoneCollection *bcoll, EditBone *ebone);
|
|
|
|
/* The following 3 functions either assign or unassign, depending on the
|
|
* 'assign_bone_func'/'assign_ebone_func' they get passed. */
|
|
|
|
static void bone_collection_assign_pchans(bContext *C,
|
|
Object *ob,
|
|
BoneCollection *bcoll,
|
|
assign_bone_func assign_func,
|
|
bool *made_any_changes,
|
|
bool *had_bones_to_assign)
|
|
{
|
|
/* TODO: support multi-object pose mode. */
|
|
FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (ob, pchan) {
|
|
*made_any_changes |= assign_func(bcoll, pchan->bone);
|
|
*had_bones_to_assign = true;
|
|
}
|
|
FOREACH_PCHAN_SELECTED_IN_OBJECT_END;
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
|
|
|
|
bArmature *arm = static_cast<bArmature *>(ob->data);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
|
|
}
|
|
|
|
static void bone_collection_assign_editbones(bContext *C,
|
|
Object *ob,
|
|
BoneCollection *bcoll,
|
|
assign_ebone_func assign_func,
|
|
bool *made_any_changes,
|
|
bool *had_bones_to_assign)
|
|
{
|
|
bArmature *arm = static_cast<bArmature *>(ob->data);
|
|
ED_armature_edit_sync_selection(arm->edbo);
|
|
|
|
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
|
|
if (!EBONE_EDITABLE(ebone)) {
|
|
continue;
|
|
}
|
|
*made_any_changes |= assign_func(bcoll, ebone);
|
|
*had_bones_to_assign = true;
|
|
}
|
|
|
|
ED_armature_edit_sync_selection(arm->edbo);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_COLLECTION, ob);
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
|
|
}
|
|
|
|
/**
|
|
* Assign or unassign all selected bones to/from the given bone collection.
|
|
*
|
|
* \return 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: {
|
|
bone_collection_assign_editbones(
|
|
C, ob, bcoll, assign_ebone_func, made_any_changes, had_bones_to_assign);
|
|
|
|
ED_outliner_select_sync_from_edit_bone_tag(C);
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assign or unassign the named bone to/from the given bone collection.
|
|
*
|
|
* \return whether the current mode is actually supported.
|
|
*/
|
|
static bool bone_collection_assign_named_mode_specific(bContext *C,
|
|
Object *ob,
|
|
BoneCollection *bcoll,
|
|
const char *bone_name,
|
|
assign_bone_func assign_bone_func,
|
|
assign_ebone_func assign_ebone_func,
|
|
bool *made_any_changes,
|
|
bool *had_bones_to_assign)
|
|
{
|
|
bArmature *arm = static_cast<bArmature *>(ob->data);
|
|
|
|
switch (CTX_data_mode_enum(C)) {
|
|
case CTX_MODE_POSE: {
|
|
bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
|
|
if (!pchan) {
|
|
return true;
|
|
}
|
|
|
|
*had_bones_to_assign = true;
|
|
*made_any_changes |= assign_bone_func(bcoll, pchan->bone);
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_COLLECTION, ob);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
|
|
return true;
|
|
}
|
|
|
|
case CTX_MODE_EDIT_ARMATURE: {
|
|
EditBone *ebone = ED_armature_ebone_find_name(arm->edbo, bone_name);
|
|
if (!ebone) {
|
|
return true;
|
|
}
|
|
|
|
*had_bones_to_assign = true;
|
|
*made_any_changes |= assign_ebone_func(bcoll, ebone);
|
|
|
|
ED_armature_edit_sync_selection(arm->edbo);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_COLLECTION, ob);
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool bone_collection_assign_poll(bContext *C)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
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;
|
|
}
|
|
|
|
bArmature *armature = static_cast<bArmature *>(ob->data);
|
|
if (ID_IS_LINKED(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
|
|
CTX_wm_operator_poll_msg_set(
|
|
C, "Cannot edit bone collections on linked Armatures without override");
|
|
return false;
|
|
}
|
|
if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
|
|
CTX_wm_operator_poll_msg_set(C,
|
|
"Cannot edit bone collections on a linked Armature with a system "
|
|
"override; explicitly create an override on the Armature");
|
|
return false;
|
|
}
|
|
|
|
CTX_wm_operator_poll_msg_set(C, "Linked bone collections are not editable");
|
|
|
|
/* The target bone collection can be specified by name in an operator property, but that's not
|
|
* available here. So just allow in the poll function, and do the final check in the execute. */
|
|
return true;
|
|
}
|
|
|
|
/* Assign selected pchans to the bone collection that the user selects */
|
|
static int bone_collection_assign_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
BoneCollection *bcoll = get_bonecoll_named_or_active(C, op, ob);
|
|
if (bcoll == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
bArmature *armature = static_cast<bArmature *>(ob->data);
|
|
if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
|
|
WM_reportf(RPT_ERROR, "Cannot assign to linked bone collection %s", bcoll->name);
|
|
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;
|
|
}
|
|
|
|
WM_main_add_notifier(NC_OBJECT | ND_BONE_COLLECTION, &ob->id);
|
|
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 */
|
|
ot->exec = bone_collection_assign_exec;
|
|
ot->poll = bone_collection_assign_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_UNDO;
|
|
|
|
/* properties */
|
|
RNA_def_string(ot->srna,
|
|
"name",
|
|
nullptr,
|
|
MAX_NAME,
|
|
"Bone Collection",
|
|
"Name of the bone collection to assign this bone to; empty to assign to the "
|
|
"active bone collection");
|
|
}
|
|
|
|
static bool bone_collection_create_and_assign_poll(bContext *C)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
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;
|
|
}
|
|
|
|
bArmature *armature = static_cast<bArmature *>(ob->data);
|
|
if (ID_IS_LINKED(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
|
|
CTX_wm_operator_poll_msg_set(
|
|
C, "Cannot edit bone collections on linked Armatures without override");
|
|
return false;
|
|
}
|
|
if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
|
|
CTX_wm_operator_poll_msg_set(C,
|
|
"Cannot edit bone collections on a linked Armature with a system "
|
|
"override; explicitly create an override on the Armature");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Assign selected pchans to the bone collection that the user selects */
|
|
static int bone_collection_create_and_assign_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
bArmature *armature = static_cast<bArmature *>(ob->data);
|
|
|
|
char bcoll_name[MAX_NAME];
|
|
RNA_string_get(op->ptr, "name", bcoll_name);
|
|
|
|
/* Note that this bone collection can be removed later on, if the assignment part of this
|
|
* operation failed. */
|
|
BoneCollection *bcoll = ANIM_armature_bonecoll_new(armature, bcoll_name);
|
|
|
|
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");
|
|
ANIM_armature_bonecoll_remove(armature, bcoll);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (!had_bones_to_assign) {
|
|
WM_report(RPT_WARNING, "No bones selected, nothing to assign to bone collection");
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
/* Not checking for `made_any_changes`, as if there were any bones to assign, they never could
|
|
* have already been assigned to this brand new bone collection. */
|
|
|
|
ANIM_armature_bonecoll_active_set(armature, bcoll);
|
|
WM_main_add_notifier(NC_OBJECT | ND_BONE_COLLECTION, &ob->id);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_collection_create_and_assign(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Add Selected Bones to New Collection";
|
|
ot->idname = "ARMATURE_OT_collection_create_and_assign";
|
|
ot->description = "Create a new bone collection and assign all selected bones";
|
|
|
|
/* api callbacks */
|
|
ot->exec = bone_collection_create_and_assign_exec;
|
|
ot->poll = bone_collection_create_and_assign_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* properties */
|
|
RNA_def_string(ot->srna,
|
|
"name",
|
|
nullptr,
|
|
MAX_NAME,
|
|
"Bone Collection",
|
|
"Name of the bone collection to create");
|
|
}
|
|
|
|
static int bone_collection_unassign_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
BoneCollection *bcoll = get_bonecoll_named_or_active(C, op, ob);
|
|
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_assign_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_UNDO;
|
|
|
|
RNA_def_string(ot->srna,
|
|
"name",
|
|
nullptr,
|
|
MAX_NAME,
|
|
"Bone Collection",
|
|
"Name of the bone collection to unassign this bone from; empty to unassign from "
|
|
"the active bone collection");
|
|
}
|
|
|
|
static int bone_collection_unassign_named_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
BoneCollection *bcoll = get_bonecoll_named_or_active(C, op, ob);
|
|
if (bcoll == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
char bone_name[MAX_NAME];
|
|
RNA_string_get(op->ptr, "bone_name", bone_name);
|
|
if (!bone_name[0]) {
|
|
WM_report(RPT_ERROR, "Missing bone name");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
bool made_any_changes = false;
|
|
bool had_bones_to_unassign = false;
|
|
const bool mode_is_supported = bone_collection_assign_named_mode_specific(
|
|
C,
|
|
ob,
|
|
bcoll,
|
|
bone_name,
|
|
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_reportf(RPT_WARNING, "Could not find bone '%s'", bone_name);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (!made_any_changes) {
|
|
WM_reportf(
|
|
RPT_WARNING, "Bone '%s' was not assigned to collection '%s'", bone_name, bcoll->name);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_collection_unassign_named(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Remove Bone from Bone Collection";
|
|
ot->idname = "ARMATURE_OT_collection_unassign_named";
|
|
ot->description = "Unassign the named bone from this bone collection";
|
|
|
|
/* api callbacks */
|
|
ot->exec = bone_collection_unassign_named_exec;
|
|
ot->poll = bone_collection_assign_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_UNDO;
|
|
|
|
RNA_def_string(ot->srna,
|
|
"name",
|
|
nullptr,
|
|
MAX_NAME,
|
|
"Bone Collection",
|
|
"Name of the bone collection to unassign this bone from; empty to unassign from "
|
|
"the active bone collection");
|
|
RNA_def_string(ot->srna,
|
|
"bone_name",
|
|
nullptr,
|
|
MAX_NAME,
|
|
"Bone Name",
|
|
"Name of the bone to unassign from the collection; empty to use the active bone");
|
|
}
|
|
|
|
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 bool armature_bone_select_poll(bContext *C)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr || ob->type != OB_ARMATURE) {
|
|
return false;
|
|
}
|
|
|
|
/* For bone selection, at least the pose should be editable to actually store
|
|
* the selection state. */
|
|
if (ID_IS_LINKED(ob) && !ID_IS_OVERRIDE_LIBRARY(ob)) {
|
|
CTX_wm_operator_poll_msg_set(
|
|
C, "Cannot (de)select bones on linked object, that would need an override");
|
|
return false;
|
|
}
|
|
|
|
const bArmature *armature = reinterpret_cast<bArmature *>(ob->data);
|
|
if (armature->runtime.active_collection == nullptr) {
|
|
CTX_wm_operator_poll_msg_set(C, "No active bone collection");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void bone_collection_select(bContext *C,
|
|
Object *ob,
|
|
BoneCollection *bcoll,
|
|
const bool select)
|
|
{
|
|
bArmature *armature = static_cast<bArmature *>(ob->data);
|
|
const bool is_editmode = armature->edbo != nullptr;
|
|
|
|
if (is_editmode) {
|
|
LISTBASE_FOREACH (EditBone *, ebone, armature->edbo) {
|
|
if (!EBONE_SELECTABLE(armature, ebone)) {
|
|
continue;
|
|
}
|
|
if (!editbone_is_member(ebone, bcoll)) {
|
|
continue;
|
|
}
|
|
ED_armature_ebone_select_set(ebone, select);
|
|
}
|
|
}
|
|
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_BONE_COLLECTION, 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 = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
bArmature *armature = reinterpret_cast<bArmature *>(ob->data);
|
|
BoneCollection *bcoll = armature->runtime.active_collection;
|
|
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 = armature_bone_select_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
static int bone_collection_deselect_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
bArmature *armature = reinterpret_cast<bArmature *>(ob->data);
|
|
BoneCollection *bcoll = armature->runtime.active_collection;
|
|
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 = armature_bone_select_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/* -------------------------- */
|
|
|
|
static BoneCollection *add_or_move_to_collection_bcoll(wmOperator *op, bArmature *arm)
|
|
{
|
|
const int collection_index = RNA_int_get(op->ptr, "collection_index");
|
|
BoneCollection *target_bcoll;
|
|
|
|
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "new_collection_name");
|
|
if (RNA_property_is_set(op->ptr, prop)) {
|
|
/* TODO: check this with linked, non-overridden armatures. */
|
|
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, collection_index);
|
|
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 {
|
|
if (collection_index >= arm->collection_array_num) {
|
|
BKE_reportf(op->reports,
|
|
RPT_ERROR,
|
|
"Bone collection with index %d not found on Armature %s",
|
|
collection_index,
|
|
arm->id.name + 2);
|
|
return nullptr;
|
|
}
|
|
target_bcoll = arm->collection_array[collection_index];
|
|
}
|
|
|
|
if (!ANIM_armature_bonecoll_is_editable(arm, target_bcoll)) {
|
|
BKE_reportf(op->reports,
|
|
RPT_ERROR,
|
|
"Bone collection %s is not editable, maybe add an override on the armature?",
|
|
target_bcoll->name);
|
|
return nullptr;
|
|
}
|
|
|
|
return target_bcoll;
|
|
}
|
|
|
|
static int add_or_move_to_collection_exec(bContext *C,
|
|
wmOperator *op,
|
|
const assign_bone_func assign_func_bone,
|
|
const assign_ebone_func assign_func_ebone)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob->mode == OB_MODE_POSE) {
|
|
ob = ED_pose_object_from_context(C);
|
|
}
|
|
if (!ob) {
|
|
BKE_reportf(op->reports, RPT_ERROR, "No object found to operate on");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
bArmature *arm = static_cast<bArmature *>(ob->data);
|
|
BoneCollection *target_bcoll = add_or_move_to_collection_bcoll(op, arm);
|
|
if (!target_bcoll) {
|
|
/* add_or_move_to_collection_bcoll() already reported the reason. */
|
|
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,
|
|
target_bcoll,
|
|
assign_func_bone,
|
|
assign_func_ebone,
|
|
&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;
|
|
}
|
|
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); /* Recreate the draw buffers. */
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DATA, ob);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
|
|
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,
|
|
ANIM_armature_bonecoll_assign_and_move_editbone);
|
|
}
|
|
|
|
static int assign_to_collection_exec(bContext *C, wmOperator *op)
|
|
{
|
|
return add_or_move_to_collection_exec(
|
|
C, op, ANIM_armature_bonecoll_assign, ANIM_armature_bonecoll_assign_editbone);
|
|
}
|
|
|
|
static bool move_to_collection_poll(bContext *C)
|
|
{
|
|
Object *ob = blender::ed::object::context_object(C);
|
|
if (ob == nullptr) {
|
|
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;
|
|
}
|
|
|
|
const bArmature *armature = static_cast<bArmature *>(ob->data);
|
|
if (ID_IS_LINKED(armature) && !ID_IS_OVERRIDE_LIBRARY(armature)) {
|
|
CTX_wm_operator_poll_msg_set(C, "This needs a local Armature or an override");
|
|
return false;
|
|
}
|
|
|
|
if (BKE_lib_override_library_is_system_defined(nullptr, &armature->id)) {
|
|
CTX_wm_operator_poll_msg_set(C,
|
|
"Cannot update a linked Armature with a system override; "
|
|
"explicitly create an override on the Armature");
|
|
return false;
|
|
}
|
|
|
|
CTX_wm_operator_poll_msg_set(C, "Linked bone collections are not editable");
|
|
|
|
/* Ideally this would also check the target bone collection to move/assign to.
|
|
* However, that requires access to the operator properties, and those are not
|
|
* available in the poll function. */
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Encode the parameters into an integer, and return as void*.
|
|
*
|
|
* NOTE(@sybren): This makes it possible to use these values and pass them directly as
|
|
* 'custom data' pointer to `uiItemMenuF()`. This makes it possible to give every menu a unique
|
|
* bone collection index for which it should show the child collections, without having to allocate
|
|
* memory or use static variables. See `move_to_collection_invoke()` in `object_edit.cc`
|
|
* for the alternative that I wanted to avoid.
|
|
*/
|
|
static void *menu_custom_data_encode(const int bcoll_index, const bool is_move_operation)
|
|
{
|
|
/* Add 1 to the index, so that it's never negative (it can be -1 to indicate 'all roots'). */
|
|
const uintptr_t index_and_move_bit = ((bcoll_index + 1) << 1) | (is_move_operation << 0);
|
|
return reinterpret_cast<void *>(index_and_move_bit);
|
|
}
|
|
|
|
/**
|
|
* Decode the `void*` back into a bone collection index and a boolean `is_move_operation`.
|
|
*
|
|
* \see menu_custom_data_encode for rationale.
|
|
*/
|
|
static std::pair<int, bool> menu_custom_data_decode(void *menu_custom_data)
|
|
{
|
|
const uintptr_t index_and_move_bit = reinterpret_cast<intptr_t>(menu_custom_data);
|
|
const bool is_move_operation = (index_and_move_bit & 1) == 1;
|
|
const int bcoll_index = int(index_and_move_bit >> 1) - 1;
|
|
return std::make_pair(bcoll_index, is_move_operation);
|
|
}
|
|
|
|
static int icon_for_bone_collection(const bool collection_contains_active_bone)
|
|
{
|
|
return collection_contains_active_bone ? ICON_REMOVE : ICON_ADD;
|
|
}
|
|
|
|
static void menu_add_item_for_move_assign_unassign(uiLayout *layout,
|
|
const bArmature *arm,
|
|
const BoneCollection *bcoll,
|
|
const int bcoll_index,
|
|
const bool is_move_operation)
|
|
{
|
|
if (is_move_operation) {
|
|
uiItemIntO(layout,
|
|
bcoll->name,
|
|
ICON_NONE,
|
|
"ARMATURE_OT_move_to_collection",
|
|
"collection_index",
|
|
bcoll_index);
|
|
return;
|
|
}
|
|
|
|
const bool contains_active_bone = ANIM_armature_bonecoll_contains_active_bone(arm, bcoll);
|
|
const int icon = icon_for_bone_collection(contains_active_bone);
|
|
|
|
if (contains_active_bone) {
|
|
uiItemStringO(
|
|
layout, bcoll->name, icon, "ARMATURE_OT_collection_unassign", "name", bcoll->name);
|
|
}
|
|
else {
|
|
uiItemStringO(layout, bcoll->name, icon, "ARMATURE_OT_collection_assign", "name", bcoll->name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add menu items to the layout, for a set of bone collections.
|
|
*
|
|
* \param menu_custom_data: Contains two values, encoded as void* to match the signature required
|
|
* by `uiItemMenuF`. It contains the parent bone collection index (either -1 to show all roots, or
|
|
* another value to show the children of that collection), as well as a boolean that indicates
|
|
* whether the menu is created for the "move to collection" or "assign to collection" operator.
|
|
*
|
|
* \see menu_custom_data_encode
|
|
*/
|
|
static void move_to_collection_menu_create(bContext *C, uiLayout *layout, void *menu_custom_data)
|
|
{
|
|
int parent_bcoll_index;
|
|
bool is_move_operation;
|
|
std::tie(parent_bcoll_index, is_move_operation) = menu_custom_data_decode(menu_custom_data);
|
|
|
|
const Object *ob = blender::ed::object::context_object(C);
|
|
const bArmature *arm = static_cast<bArmature *>(ob->data);
|
|
|
|
/* The "Create a new collection" mode of this operator has its own menu, and should thus be
|
|
* invoked. */
|
|
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
|
|
uiItemIntO(layout,
|
|
"New Bone Collection",
|
|
ICON_NONE,
|
|
is_move_operation ? "ARMATURE_OT_move_to_collection" :
|
|
"ARMATURE_OT_assign_to_collection",
|
|
"collection_index",
|
|
parent_bcoll_index);
|
|
|
|
/* The remaining operators in this menu should be executed on click. Invoking
|
|
* them would show this same menu again. */
|
|
uiLayoutSetOperatorContext(layout, WM_OP_EXEC_DEFAULT);
|
|
|
|
int child_index, child_count;
|
|
if (parent_bcoll_index == -1) {
|
|
child_index = 0;
|
|
child_count = arm->collection_root_count;
|
|
}
|
|
else {
|
|
/* Add a menu item to assign to the parent first, before listing the children.
|
|
* The parent is assumed to be editable, because otherwise the menu would
|
|
* have been disabled already one recursion level higher. */
|
|
const BoneCollection *parent = arm->collection_array[parent_bcoll_index];
|
|
menu_add_item_for_move_assign_unassign(
|
|
layout, arm, parent, parent_bcoll_index, is_move_operation);
|
|
uiItemS(layout);
|
|
|
|
child_index = parent->child_index;
|
|
child_count = parent->child_count;
|
|
}
|
|
|
|
/* Loop over the children. There should be at least one, otherwise this parent
|
|
* bone collection wouldn't have been drawn as a menu. */
|
|
for (int index = child_index; index < child_index + child_count; index++) {
|
|
const BoneCollection *bcoll = arm->collection_array[index];
|
|
|
|
/* Avoid assigning/moving to a linked bone collection. */
|
|
if (!ANIM_armature_bonecoll_is_editable(arm, bcoll)) {
|
|
uiLayout *sub = uiLayoutRow(layout, false);
|
|
uiLayoutSetEnabled(sub, false);
|
|
|
|
menu_add_item_for_move_assign_unassign(sub, arm, bcoll, index, is_move_operation);
|
|
continue;
|
|
}
|
|
|
|
if (blender::animrig::bonecoll_has_children(bcoll)) {
|
|
uiItemMenuF(layout,
|
|
bcoll->name,
|
|
ICON_NONE,
|
|
move_to_collection_menu_create,
|
|
menu_custom_data_encode(index, is_move_operation));
|
|
}
|
|
else {
|
|
menu_add_item_for_move_assign_unassign(layout, arm, bcoll, index, is_move_operation);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int move_to_collection_regular_invoke(bContext *C, wmOperator *op)
|
|
{
|
|
const char *title = CTX_IFACE_(op->type->translation_context, op->type->name);
|
|
uiPopupMenu *pup = UI_popup_menu_begin(C, title, ICON_NONE);
|
|
uiLayout *layout = UI_popup_menu_layout(pup);
|
|
|
|
const bool is_move_operation = STREQ(op->type->idname, "ARMATURE_OT_move_to_collection");
|
|
move_to_collection_menu_create(C, layout, menu_custom_data_encode(-1, is_move_operation));
|
|
|
|
UI_popup_menu_end(C, pup);
|
|
|
|
return OPERATOR_INTERFACE;
|
|
}
|
|
|
|
static int move_to_new_collection_invoke(bContext *C, wmOperator *op)
|
|
{
|
|
RNA_string_set(op->ptr, "new_collection_name", IFACE_("Bones"));
|
|
return WM_operator_props_dialog_popup(
|
|
C, op, 200, IFACE_("Move to New Bone Collection"), IFACE_("Move"));
|
|
}
|
|
|
|
static int move_to_collection_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
|
|
{
|
|
/* Invoking with `collection_index` set has a special meaning: show the menu to create a new bone
|
|
* collection as the child of this one. */
|
|
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "collection_index");
|
|
if (RNA_property_is_set(op->ptr, prop)) {
|
|
return move_to_new_collection_invoke(C, op);
|
|
}
|
|
|
|
return move_to_collection_regular_invoke(C, op);
|
|
}
|
|
|
|
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_int(
|
|
ot->srna,
|
|
"collection_index",
|
|
-1,
|
|
-1,
|
|
INT_MAX,
|
|
"Collection Index",
|
|
"Index of the collection to move selected bones to. When the operator should create a new "
|
|
"bone collection, do not include this parameter and pass new_collection_name",
|
|
-1,
|
|
INT_MAX);
|
|
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 a to-be-added bone collection. Only pass this if you want to create a new bone "
|
|
"collection and move the selected bones to it. To move to an existing collection, do not "
|
|
"include this parameter and use collection_index");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
ot->prop = prop;
|
|
}
|
|
|
|
void ARMATURE_OT_assign_to_collection(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
/* identifiers */
|
|
ot->name = "Assign to Collection";
|
|
ot->description =
|
|
"Assign all selected bones to a collection, or unassign them, depending on whether the "
|
|
"active bone is already assigned or not";
|
|
ot->idname = "ARMATURE_OT_assign_to_collection";
|
|
|
|
/* 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_int(
|
|
ot->srna,
|
|
"collection_index",
|
|
-1,
|
|
-1,
|
|
INT_MAX,
|
|
"Collection Index",
|
|
"Index of the collection to assign selected bones to. When the operator should create a new "
|
|
"bone collection, use new_collection_name to define the collection name, and set this "
|
|
"parameter to the parent index of the new bone collection",
|
|
-1,
|
|
INT_MAX);
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
|
|
|
|
prop = RNA_def_string(
|
|
ot->srna,
|
|
"new_collection_name",
|
|
nullptr,
|
|
MAX_NAME,
|
|
"Name",
|
|
"Name of a to-be-added bone collection. Only pass this if you want to create a new bone "
|
|
"collection and assign the selected bones to it. To assign to an existing collection, do "
|
|
"not include this parameter and use collection_index");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
ot->prop = prop;
|
|
}
|
|
|
|
/* ********************************************** */
|