This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/armature/armature_select.c
Aaron Carlisle 1c357a3c5f UI: Use Consistent name for select mirror
This commit cleans up the RNA names of select mirror operators so that 
they are all "Select mirror".

This makes the select menu in edit/pose mode consistent regardless of 
object type.

Differential Revision: https://developer.blender.org/D7356
2021-04-12 14:52:29 -04:00

2292 lines
68 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
* All rights reserved.
* API's and Operators for selecting armature bones in EditMode
*/
/** \file
* \ingroup edarmature
*/
#include "MEM_guardedalloc.h"
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLI_rect.h"
#include "BLI_string_utils.h"
#include "BKE_action.h"
#include "BKE_armature.h"
#include "BKE_context.h"
#include "BKE_layer.h"
#include "BKE_object.h"
#include "BKE_report.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_armature.h"
#include "ED_object.h"
#include "ED_outliner.h"
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "ED_view3d.h"
#include "DEG_depsgraph.h"
#include "armature_intern.h"
/* utility macros for storing a temp int in the bone (selection flag) */
#define EBONE_PREV_FLAG_GET(ebone) ((void)0, (ebone)->temp.i)
#define EBONE_PREV_FLAG_SET(ebone, val) ((ebone)->temp.i = val)
/* -------------------------------------------------------------------- */
/** \name Select Buffer Queries for PoseMode & EditMode
* \{ */
Base *ED_armature_base_and_ebone_from_select_buffer(Base **bases,
uint bases_len,
int hit,
EditBone **r_ebone)
{
const uint hit_object = hit & 0xFFFF;
Base *base = NULL;
EditBone *ebone = NULL;
/* TODO(campbell): optimize, eg: sort & binary search. */
for (uint base_index = 0; base_index < bases_len; base_index++) {
if (bases[base_index]->object->runtime.select_id == hit_object) {
base = bases[base_index];
break;
}
}
if (base != NULL) {
const uint hit_bone = (hit & ~BONESEL_ANY) >> 16;
bArmature *arm = base->object->data;
ebone = BLI_findlink(arm->edbo, hit_bone);
}
*r_ebone = ebone;
return base;
}
Object *ED_armature_object_and_ebone_from_select_buffer(Object **objects,
uint objects_len,
int hit,
EditBone **r_ebone)
{
const uint hit_object = hit & 0xFFFF;
Object *ob = NULL;
EditBone *ebone = NULL;
/* TODO(campbell): optimize, eg: sort & binary search. */
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
if (objects[ob_index]->runtime.select_id == hit_object) {
ob = objects[ob_index];
break;
}
}
if (ob != NULL) {
const uint hit_bone = (hit & ~BONESEL_ANY) >> 16;
bArmature *arm = ob->data;
ebone = BLI_findlink(arm->edbo, hit_bone);
}
*r_ebone = ebone;
return ob;
}
Base *ED_armature_base_and_pchan_from_select_buffer(Base **bases,
uint bases_len,
int hit,
bPoseChannel **r_pchan)
{
const uint hit_object = hit & 0xFFFF;
Base *base = NULL;
bPoseChannel *pchan = NULL;
/* TODO(campbell): optimize, eg: sort & binary search. */
for (uint base_index = 0; base_index < bases_len; base_index++) {
if (bases[base_index]->object->runtime.select_id == hit_object) {
base = bases[base_index];
break;
}
}
if (base != NULL) {
if (base->object->pose != NULL) {
const uint hit_bone = (hit & ~BONESEL_ANY) >> 16;
/* pchan may be NULL. */
pchan = BLI_findlink(&base->object->pose->chanbase, hit_bone);
}
}
*r_pchan = pchan;
return base;
}
/* For callers that don't need the pose channel. */
Base *ED_armature_base_and_bone_from_select_buffer(Base **bases,
uint bases_len,
int hit,
Bone **r_bone)
{
bPoseChannel *pchan = NULL;
Base *base = ED_armature_base_and_pchan_from_select_buffer(bases, bases_len, hit, &pchan);
*r_bone = pchan ? pchan->bone : NULL;
return base;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cursor Pick from Select Buffer API
*
* Internal #ed_armature_pick_bone_from_selectbuffer_impl is exposed as:
* - #ED_armature_pick_ebone_from_selectbuffer
* - #ED_armature_pick_pchan_from_selectbuffer
* - #ED_armature_pick_bone_from_selectbuffer
* \{ */
/* See if there are any selected bones in this buffer */
/* only bones from base are checked on */
static void *ed_armature_pick_bone_from_selectbuffer_impl(const bool is_editmode,
Base **bases,
uint bases_len,
const uint *buffer,
short hits,
bool findunsel,
bool do_nearest,
Base **r_base)
{
bPoseChannel *pchan;
EditBone *ebone;
void *firstunSel = NULL, *firstSel = NULL, *data;
Base *firstunSel_base = NULL, *firstSel_base = NULL;
uint hitresult;
bool takeNext = false;
int minsel = 0xffffffff, minunsel = 0xffffffff;
for (short i = 0; i < hits; i++) {
hitresult = buffer[3 + (i * 4)];
if (hitresult & BONESEL_ANY) { /* to avoid including objects in selection */
Base *base = NULL;
bool sel;
hitresult &= ~BONESEL_ANY;
/* Determine what the current bone is */
if (is_editmode == false) {
base = ED_armature_base_and_pchan_from_select_buffer(bases, bases_len, hitresult, &pchan);
if (pchan != NULL) {
if (findunsel) {
sel = (pchan->bone->flag & BONE_SELECTED);
}
else {
sel = !(pchan->bone->flag & BONE_SELECTED);
}
data = pchan;
}
else {
data = NULL;
sel = 0;
}
}
else {
base = ED_armature_base_and_ebone_from_select_buffer(bases, bases_len, hitresult, &ebone);
if (findunsel) {
sel = (ebone->flag & BONE_SELECTED);
}
else {
sel = !(ebone->flag & BONE_SELECTED);
}
data = ebone;
}
if (data) {
if (sel) {
if (do_nearest) {
if (minsel > buffer[4 * i + 1]) {
firstSel = data;
firstSel_base = base;
minsel = buffer[4 * i + 1];
}
}
else {
if (!firstSel) {
firstSel = data;
firstSel_base = base;
}
takeNext = 1;
}
}
else {
if (do_nearest) {
if (minunsel > buffer[4 * i + 1]) {
firstunSel = data;
firstunSel_base = base;
minunsel = buffer[4 * i + 1];
}
}
else {
if (!firstunSel) {
firstunSel = data;
firstunSel_base = base;
}
if (takeNext) {
*r_base = base;
return data;
}
}
}
}
}
}
if (firstunSel) {
*r_base = firstunSel_base;
return firstunSel;
}
*r_base = firstSel_base;
return firstSel;
}
EditBone *ED_armature_pick_ebone_from_selectbuffer(Base **bases,
uint bases_len,
const uint *buffer,
short hits,
bool findunsel,
bool do_nearest,
Base **r_base)
{
const bool is_editmode = true;
return ed_armature_pick_bone_from_selectbuffer_impl(
is_editmode, bases, bases_len, buffer, hits, findunsel, do_nearest, r_base);
}
bPoseChannel *ED_armature_pick_pchan_from_selectbuffer(Base **bases,
uint bases_len,
const uint *buffer,
short hits,
bool findunsel,
bool do_nearest,
Base **r_base)
{
const bool is_editmode = false;
return ed_armature_pick_bone_from_selectbuffer_impl(
is_editmode, bases, bases_len, buffer, hits, findunsel, do_nearest, r_base);
}
Bone *ED_armature_pick_bone_from_selectbuffer(Base **bases,
uint bases_len,
const uint *buffer,
short hits,
bool findunsel,
bool do_nearest,
Base **r_base)
{
bPoseChannel *pchan = ED_armature_pick_pchan_from_selectbuffer(
bases, bases_len, buffer, hits, findunsel, do_nearest, r_base);
return pchan ? pchan->bone : NULL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cursor Pick API
*
* Internal #ed_armature_pick_bone_impl is exposed as:
* - #ED_armature_pick_ebone
* - #ED_armature_pick_pchan
* - #ED_armature_pick_bone
* \{ */
/**
* \param xy: Cursor coordinates (area space).
* \return An #EditBone when is_editmode, otherwise a #bPoseChannel.
* \note Only checks objects in the current mode (edit-mode or pose-mode).
*/
static void *ed_armature_pick_bone_impl(
const bool is_editmode, bContext *C, const int xy[2], bool findunsel, Base **r_base)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc;
rcti rect;
uint buffer[MAXPICKBUF];
short hits;
ED_view3d_viewcontext_init(C, &vc, depsgraph);
BLI_assert((vc.obedit != NULL) == is_editmode);
BLI_rcti_init_pt_radius(&rect, xy, 0);
/* Don't use hits with this ID, (armature drawing uses this). */
const int select_id_ignore = -1;
hits = view3d_opengl_select_with_id_filter(&vc,
buffer,
MAXPICKBUF,
&rect,
VIEW3D_SELECT_PICK_NEAREST,
VIEW3D_SELECT_FILTER_NOP,
select_id_ignore);
*r_base = NULL;
if (hits > 0) {
uint bases_len = 0;
Base **bases;
if (vc.obedit != NULL) {
bases = BKE_view_layer_array_from_bases_in_mode(vc.view_layer,
vc.v3d,
&bases_len,
{
.object_mode = OB_MODE_EDIT,
});
}
else {
bases = BKE_object_pose_base_array_get(vc.view_layer, vc.v3d, &bases_len);
}
void *bone = ed_armature_pick_bone_from_selectbuffer_impl(
is_editmode, bases, bases_len, buffer, hits, findunsel, true, r_base);
MEM_freeN(bases);
return bone;
}
return NULL;
}
EditBone *ED_armature_pick_ebone(bContext *C, const int xy[2], bool findunsel, Base **r_base)
{
const bool is_editmode = true;
return ed_armature_pick_bone_impl(is_editmode, C, xy, findunsel, r_base);
}
bPoseChannel *ED_armature_pick_pchan(bContext *C, const int xy[2], bool findunsel, Base **r_base)
{
const bool is_editmode = false;
return ed_armature_pick_bone_impl(is_editmode, C, xy, findunsel, r_base);
}
Bone *ED_armature_pick_bone(bContext *C, const int xy[2], bool findunsel, Base **r_base)
{
bPoseChannel *pchan = ED_armature_pick_pchan(C, xy, findunsel, r_base);
return pchan ? pchan->bone : NULL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Linked Implementation
*
* Shared logic for select linked all/pick.
*
* Use #BONE_DONE flag to select linked.
* \{ */
/**
* \param all_forks: Control how chains are stepped over.
* true: select all connected bones traveling up & down forks.
* false: select all parents and all children, but not the children of the root bone.
*/
static bool armature_select_linked_impl(Object *ob, const bool select, const bool all_forks)
{
bool changed = false;
bArmature *arm = ob->data;
/* Implementation note, this flood-fills selected bones with the 'TOUCH' flag,
* even though this is a loop-within a loop, walking up the parent chain only touches new bones.
* Bones that have been touched are skipped, so the complexity is OK. */
enum {
/* Bone has been walked over, its LINK value can be read. */
TOUCH = (1 << 0),
/* When TOUCH has been set, this flag can be checked to see if the bone is connected. */
LINK = (1 << 1),
};
#define CHECK_PARENT(ebone) \
(((ebone)->flag & BONE_CONNECTED) && \
((ebone)->parent ? EBONE_SELECTABLE(arm, (ebone)->parent) : false))
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
ebone->temp.i = 0;
}
/* Select parents. */
LISTBASE_FOREACH (EditBone *, ebone_iter, arm->edbo) {
if (ebone_iter->temp.i & TOUCH) {
continue;
}
if ((ebone_iter->flag & BONE_DONE) == 0) {
continue;
}
ebone_iter->temp.i |= TOUCH | LINK;
/* We have an un-touched link. */
for (EditBone *ebone = ebone_iter; ebone; ebone = CHECK_PARENT(ebone) ? ebone->parent : NULL) {
ED_armature_ebone_select_set(ebone, select);
changed = true;
if (all_forks) {
ebone->temp.i |= (TOUCH | LINK);
}
else {
ebone->temp.i |= TOUCH;
}
/* Don't walk onto links (messes up 'all_forks' logic). */
if (ebone->parent && ebone->parent->temp.i & LINK) {
break;
}
}
}
/* Select children. */
LISTBASE_FOREACH (EditBone *, ebone_iter, arm->edbo) {
/* No need to 'touch' this bone as it won't be walked over when scanning up the chain. */
if (!CHECK_PARENT(ebone_iter)) {
continue;
}
if (ebone_iter->temp.i & TOUCH) {
continue;
}
/* First check if we're marked. */
EditBone *ebone_touched_parent = NULL;
for (EditBone *ebone = ebone_iter; ebone; ebone = CHECK_PARENT(ebone) ? ebone->parent : NULL) {
if (ebone->temp.i & TOUCH) {
ebone_touched_parent = ebone;
break;
}
ebone->temp.i |= TOUCH;
}
if ((ebone_touched_parent != NULL) && (ebone_touched_parent->temp.i & LINK)) {
for (EditBone *ebone = ebone_iter; ebone != ebone_touched_parent; ebone = ebone->parent) {
if ((ebone->temp.i & LINK) == 0) {
ebone->temp.i |= LINK;
ED_armature_ebone_select_set(ebone, select);
changed = true;
}
}
}
}
#undef CHECK_PARENT
if (changed) {
ED_armature_edit_sync_selection(arm->edbo);
DEG_id_tag_update(&arm->id, ID_RECALC_COPY_ON_WRITE);
WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, ob);
}
return changed;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Linked Operator
* \{ */
static int armature_select_linked_exec(bContext *C, wmOperator *op)
{
const bool all_forks = RNA_boolean_get(op->ptr, "all_forks");
bool changed_multi = false;
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
bArmature *arm = ob->data;
bool found = false;
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (EBONE_VISIBLE(arm, ebone) &&
(ebone->flag & (BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL))) {
ebone->flag |= BONE_DONE;
found = true;
}
else {
ebone->flag &= ~BONE_DONE;
}
}
if (found) {
if (armature_select_linked_impl(ob, true, all_forks)) {
changed_multi = true;
}
}
}
MEM_freeN(objects);
if (changed_multi) {
ED_outliner_select_sync_from_edit_bone_tag(C);
}
return OPERATOR_FINISHED;
}
void ARMATURE_OT_select_linked(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Linked All";
ot->idname = "ARMATURE_OT_select_linked";
ot->description = "Select all bones linked by parent/child connections to the current selection";
/* api callbacks */
ot->exec = armature_select_linked_exec;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Leave disabled by default as this matches pose mode. */
RNA_def_boolean(ot->srna, "all_forks", 0, "All Forks", "Follow forks in the parents chain");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Linked (Cursor Pick) Operator
* \{ */
static int armature_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const bool select = !RNA_boolean_get(op->ptr, "deselect");
const bool all_forks = RNA_boolean_get(op->ptr, "all_forks");
view3d_operator_needs_opengl(C);
BKE_object_update_select_id(CTX_data_main(C));
Base *base = NULL;
EditBone *ebone_active = ED_armature_pick_ebone(C, event->mval, true, &base);
if (ebone_active == NULL) {
return OPERATOR_CANCELLED;
}
bArmature *arm = base->object->data;
if (!EBONE_SELECTABLE(arm, ebone_active)) {
return OPERATOR_CANCELLED;
}
/* Initialize flags. */
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
ebone->flag &= ~BONE_DONE;
}
ebone_active->flag |= BONE_DONE;
if (armature_select_linked_impl(base->object, select, all_forks)) {
ED_outliner_select_sync_from_edit_bone_tag(C);
}
return OPERATOR_FINISHED;
}
static bool armature_select_linked_pick_poll(bContext *C)
{
return (ED_operator_view3d_active(C) && ED_operator_editarmature(C));
}
void ARMATURE_OT_select_linked_pick(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Linked";
ot->idname = "ARMATURE_OT_select_linked_pick";
ot->description = "(De)select bones linked by parent/child connections under the mouse cursor";
/* api callbacks */
/* leave 'exec' unset */
ot->invoke = armature_select_linked_pick_invoke;
ot->poll = armature_select_linked_pick_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "");
/* Leave disabled by default as this matches pose mode. */
RNA_def_boolean(ot->srna, "all_forks", 0, "All Forks", "Follow forks in the parents chain");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Buffer Queries EditMode
* \{ */
/* utility function for get_nearest_editbonepoint */
static int selectbuffer_ret_hits_12(uint *UNUSED(buffer), const int hits12)
{
return hits12;
}
static int selectbuffer_ret_hits_5(uint *buffer, const int hits12, const int hits5)
{
const int ofs = 4 * hits12;
memcpy(buffer, buffer + ofs, 4 * hits5 * sizeof(uint));
return hits5;
}
/* does bones and points */
/* note that BONE ROOT only gets drawn for root bones (or without IK) */
static EditBone *get_nearest_editbonepoint(
ViewContext *vc, bool findunsel, bool use_cycle, Base **r_base, int *r_selmask)
{
uint buffer[MAXPICKBUF];
struct {
uint hitresult;
Base *base;
EditBone *ebone;
} *result = NULL,
result_cycle = {.hitresult = -1, .base = NULL, .ebone = NULL},
result_bias = {.hitresult = -1, .base = NULL, .ebone = NULL};
/* find the bone after the current active bone, so as to bump up its chances in selection.
* this way overlapping bones will cycle selection state as with objects. */
Object *obedit_orig = vc->obedit;
EditBone *ebone_active_orig = ((bArmature *)obedit_orig->data)->act_edbone;
if (ebone_active_orig == NULL) {
use_cycle = false;
}
if (use_cycle) {
static int last_mval[2] = {-100, -100};
if ((len_manhattan_v2v2_int(vc->mval, last_mval) <= WM_EVENT_CURSOR_MOTION_THRESHOLD) == 0) {
use_cycle = false;
}
copy_v2_v2_int(last_mval, vc->mval);
}
const bool do_nearest = !(XRAY_ACTIVE(vc->v3d) || use_cycle);
/* matching logic from 'mixed_bones_object_selectbuffer' */
int hits = 0;
/* Don't use hits with this ID, (armature drawing uses this). */
const int select_id_ignore = -1;
/* we _must_ end cache before return, use 'goto cache_end' */
view3d_opengl_select_cache_begin();
{
const int select_mode = (do_nearest ? VIEW3D_SELECT_PICK_NEAREST : VIEW3D_SELECT_PICK_ALL);
const eV3DSelectObjectFilter select_filter = VIEW3D_SELECT_FILTER_NOP;
rcti rect;
BLI_rcti_init_pt_radius(&rect, vc->mval, 12);
const int hits12 = view3d_opengl_select_with_id_filter(
vc, buffer, MAXPICKBUF, &rect, select_mode, select_filter, select_id_ignore);
if (hits12 == 1) {
hits = selectbuffer_ret_hits_12(buffer, hits12);
goto cache_end;
}
else if (hits12 > 0) {
int ofs;
ofs = 4 * hits12;
BLI_rcti_init_pt_radius(&rect, vc->mval, 5);
const int hits5 = view3d_opengl_select_with_id_filter(
vc, buffer + ofs, MAXPICKBUF - ofs, &rect, select_mode, select_filter, select_id_ignore);
if (hits5 == 1) {
hits = selectbuffer_ret_hits_5(buffer, hits12, hits5);
goto cache_end;
}
if (hits5 > 0) {
hits = selectbuffer_ret_hits_5(buffer, hits12, hits5);
goto cache_end;
}
else {
hits = selectbuffer_ret_hits_12(buffer, hits12);
goto cache_end;
}
}
}
cache_end:
view3d_opengl_select_cache_end();
uint bases_len;
Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data(
vc->view_layer, vc->v3d, &bases_len);
/* See if there are any selected bones in this group */
if (hits > 0) {
if (hits == 1) {
result_bias.hitresult = buffer[3];
result_bias.base = ED_armature_base_and_ebone_from_select_buffer(
bases, bases_len, result_bias.hitresult, &result_bias.ebone);
}
else {
int bias_max = INT_MIN;
/* Track Cycle Variables
* - Offset is always set to the active bone.
* - The object & bone indices subtracted by the 'offset.as_u32' value.
* Unsigned subtraction wrapping means we always select the next bone in the cycle.
*/
struct {
union {
uint32_t as_u32;
struct {
#ifdef __BIG_ENDIAN__
uint16_t ob;
uint16_t bone;
#else
uint16_t bone;
uint16_t ob;
#endif
};
} offset, test, best;
} cycle_order;
if (use_cycle) {
bArmature *arm = obedit_orig->data;
int ob_index = obedit_orig->runtime.select_id & 0xFFFF;
int bone_index = BLI_findindex(arm->edbo, ebone_active_orig);
/* Offset from the current active bone, so we cycle onto the next. */
cycle_order.offset.ob = ob_index;
cycle_order.offset.bone = bone_index;
/* The value of the active bone (with offset subtracted, a signal to always overwrite). */
cycle_order.best.as_u32 = 0;
}
for (int i = 0; i < hits; i++) {
const uint hitresult = buffer[3 + (i * 4)];
Base *base = NULL;
EditBone *ebone;
base = ED_armature_base_and_ebone_from_select_buffer(bases, bases_len, hitresult, &ebone);
/* If this fails, selection code is setting the selection ID's incorrectly. */
BLI_assert(base && ebone);
/* Prioritized selection. */
{
int bias;
/* clicks on bone points get advantage */
if (hitresult & (BONESEL_ROOT | BONESEL_TIP)) {
/* but also the unselected one */
if (findunsel) {
if ((hitresult & BONESEL_ROOT) && (ebone->flag & BONE_ROOTSEL) == 0) {
bias = 4;
}
else if ((hitresult & BONESEL_TIP) && (ebone->flag & BONE_TIPSEL) == 0) {
bias = 4;
}
else {
bias = 3;
}
}
else {
bias = 4;
}
}
else {
/* bone found */
if (findunsel) {
if ((ebone->flag & BONE_SELECTED) == 0) {
bias = 2;
}
else {
bias = 1;
}
}
else {
bias = 2;
}
}
if (bias > bias_max) {
bias_max = bias;
result_bias.hitresult = hitresult;
result_bias.base = base;
result_bias.ebone = ebone;
}
}
/* Cycle selected items (objects & bones). */
if (use_cycle) {
cycle_order.test.ob = hitresult & 0xFFFF;
cycle_order.test.bone = (hitresult & ~BONESEL_ANY) >> 16;
if (ebone == ebone_active_orig) {
BLI_assert(cycle_order.test.ob == cycle_order.offset.ob);
BLI_assert(cycle_order.test.bone == cycle_order.offset.bone);
}
/* Subtraction as a single value is needed to support cycling through bones
* from multiple objects. So once the last bone is selected,
* the bits for the bone index wrap into the object,
* causing the next object to be stepped onto. */
cycle_order.test.as_u32 -= cycle_order.offset.as_u32;
/* Even though this logic avoids stepping onto the active bone,
* always set the 'best' value for the first time.
* Otherwise ensure the value is the smallest it can be,
* relative to the active bone, as long as it's not the active bone. */
if ((cycle_order.best.as_u32 == 0) ||
(cycle_order.test.as_u32 && (cycle_order.test.as_u32 < cycle_order.best.as_u32))) {
cycle_order.best = cycle_order.test;
result_cycle.hitresult = hitresult;
result_cycle.base = base;
result_cycle.ebone = ebone;
}
}
}
}
result = (use_cycle && result_cycle.ebone) ? &result_cycle : &result_bias;
if (result->hitresult != -1) {
*r_base = result->base;
*r_selmask = 0;
if (result->hitresult & BONESEL_ROOT) {
*r_selmask |= BONE_ROOTSEL;
}
if (result->hitresult & BONESEL_TIP) {
*r_selmask |= BONE_TIPSEL;
}
if (result->hitresult & BONESEL_BONE) {
*r_selmask |= BONE_SELECTED;
}
MEM_freeN(bases);
return result->ebone;
}
}
*r_selmask = 0;
*r_base = NULL;
MEM_freeN(bases);
return NULL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Utility Functions
* \{ */
bool ED_armature_edit_deselect_all(Object *obedit)
{
bArmature *arm = obedit->data;
bool changed = false;
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (ebone->flag & (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL)) {
ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
changed = true;
}
}
return changed;
}
bool ED_armature_edit_deselect_all_visible(Object *obedit)
{
bArmature *arm = obedit->data;
bool changed = false;
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
/* first and foremost, bone must be visible and selected */
if (EBONE_VISIBLE(arm, ebone)) {
if (ebone->flag & (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL)) {
ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
changed = true;
}
}
}
if (changed) {
ED_armature_edit_sync_selection(arm->edbo);
}
return changed;
}
bool ED_armature_edit_deselect_all_multi_ex(struct Base **bases, uint bases_len)
{
bool changed_multi = false;
for (uint base_index = 0; base_index < bases_len; base_index++) {
Object *obedit = bases[base_index]->object;
changed_multi |= ED_armature_edit_deselect_all(obedit);
}
return changed_multi;
}
bool ED_armature_edit_deselect_all_visible_multi_ex(struct Base **bases, uint bases_len)
{
bool changed_multi = false;
for (uint base_index = 0; base_index < bases_len; base_index++) {
Object *obedit = bases[base_index]->object;
changed_multi |= ED_armature_edit_deselect_all_visible(obedit);
}
return changed_multi;
}
bool ED_armature_edit_deselect_all_visible_multi(bContext *C)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc;
ED_view3d_viewcontext_init(C, &vc, depsgraph);
uint bases_len = 0;
Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data(
vc.view_layer, vc.v3d, &bases_len);
bool changed_multi = ED_armature_edit_deselect_all_multi_ex(bases, bases_len);
MEM_freeN(bases);
return changed_multi;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Cursor Pick API
* \{ */
bool ED_armature_edit_select_pick_bone(bContext *C,
Base *basact,
EditBone *ebone,
const int selmask,
const bool extend,
const bool deselect,
const bool toggle)
{
if (!ebone) {
return false;
}
ViewLayer *view_layer = CTX_data_view_layer(C);
View3D *v3d = CTX_wm_view3d(C);
BLI_assert(BKE_object_is_in_editmode(basact->object));
bArmature *arm = basact->object->data;
if (!EBONE_SELECTABLE(arm, ebone)) {
return false;
}
if (!extend && !deselect && !toggle) {
uint bases_len = 0;
Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data(
view_layer, v3d, &bases_len);
ED_armature_edit_deselect_all_multi_ex(bases, bases_len);
MEM_freeN(bases);
}
/* By definition the non-root connected bones have no root point drawn,
* so a root selection needs to be delivered to the parent tip. */
if (selmask & BONE_SELECTED) {
if (ebone->parent && (ebone->flag & BONE_CONNECTED)) {
/* Bone is in a chain. */
if (extend) {
/* Select this bone. */
ebone->flag |= BONE_TIPSEL;
ebone->parent->flag |= BONE_TIPSEL;
}
else if (deselect) {
/* Deselect this bone. */
ebone->flag &= ~(BONE_TIPSEL | BONE_SELECTED);
/* Only deselect parent tip if it is not selected. */
if (!(ebone->parent->flag & BONE_SELECTED)) {
ebone->parent->flag &= ~BONE_TIPSEL;
}
}
else if (toggle) {
/* Toggle inverts this bone's selection. */
if (ebone->flag & BONE_SELECTED) {
/* Deselect this bone. */
ebone->flag &= ~(BONE_TIPSEL | BONE_SELECTED);
/* Only deselect parent tip if it is not selected. */
if (!(ebone->parent->flag & BONE_SELECTED)) {
ebone->parent->flag &= ~BONE_TIPSEL;
}
}
else {
/* Select this bone. */
ebone->flag |= BONE_TIPSEL;
ebone->parent->flag |= BONE_TIPSEL;
}
}
else {
/* Select this bone. */
ebone->flag |= BONE_TIPSEL;
ebone->parent->flag |= BONE_TIPSEL;
}
}
else {
if (extend) {
ebone->flag |= (BONE_TIPSEL | BONE_ROOTSEL);
}
else if (deselect) {
ebone->flag &= ~(BONE_TIPSEL | BONE_ROOTSEL);
}
else if (toggle) {
/* Toggle inverts this bone's selection. */
if (ebone->flag & BONE_SELECTED) {
ebone->flag &= ~(BONE_TIPSEL | BONE_ROOTSEL);
}
else {
ebone->flag |= (BONE_TIPSEL | BONE_ROOTSEL);
}
}
else {
ebone->flag |= (BONE_TIPSEL | BONE_ROOTSEL);
}
}
}
else {
if (extend) {
ebone->flag |= selmask;
}
else if (deselect) {
ebone->flag &= ~selmask;
}
else if (toggle && (ebone->flag & selmask)) {
ebone->flag &= ~selmask;
}
else {
ebone->flag |= selmask;
}
}
ED_armature_edit_sync_selection(arm->edbo);
/* Then now check for active status. */
if (ED_armature_ebone_selectflag_get(ebone)) {
arm->act_edbone = ebone;
}
if (view_layer->basact != basact) {
ED_object_base_activate(C, basact);
}
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, basact->object);
DEG_id_tag_update(&arm->id, ID_RECALC_COPY_ON_WRITE);
return true;
}
/* context: editmode armature in view3d */
bool ED_armature_edit_select_pick(
bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc;
EditBone *nearBone = NULL;
int selmask;
Base *basact = NULL;
ED_view3d_viewcontext_init(C, &vc, depsgraph);
vc.mval[0] = mval[0];
vc.mval[1] = mval[1];
nearBone = get_nearest_editbonepoint(&vc, true, true, &basact, &selmask);
return ED_armature_edit_select_pick_bone(C, basact, nearBone, selmask, extend, deselect, toggle);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Op From Tagged
*
* Implements #ED_armature_edit_select_op_from_tagged
* \{ */
static bool armature_edit_select_op_apply(bArmature *arm,
EditBone *ebone,
const eSelectOp sel_op,
int is_ignore_flag,
int is_inside_flag)
{
BLI_assert(!(is_ignore_flag & ~(BONESEL_ROOT | BONESEL_TIP)));
BLI_assert(!(is_inside_flag & ~(BONESEL_ROOT | BONESEL_TIP | BONESEL_BONE)));
BLI_assert(EBONE_VISIBLE(arm, ebone));
bool changed = false;
bool is_point_done = false;
int points_proj_tot = 0;
BLI_assert(ebone->flag == ebone->temp.i);
const int ebone_flag_prev = ebone->flag;
if ((is_ignore_flag & BONE_ROOTSEL) == 0) {
points_proj_tot++;
const bool is_select = ebone->flag & BONE_ROOTSEL;
const bool is_inside = is_inside_flag & BONESEL_ROOT;
const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
if (sel_op_result != -1) {
if (sel_op_result == 0 || EBONE_SELECTABLE(arm, ebone)) {
SET_FLAG_FROM_TEST(ebone->flag, sel_op_result, BONE_ROOTSEL);
}
}
is_point_done |= is_inside;
}
if ((is_ignore_flag & BONE_TIPSEL) == 0) {
points_proj_tot++;
const bool is_select = ebone->flag & BONE_TIPSEL;
const bool is_inside = is_inside_flag & BONESEL_TIP;
const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
if (sel_op_result != -1) {
if (sel_op_result == 0 || EBONE_SELECTABLE(arm, ebone)) {
SET_FLAG_FROM_TEST(ebone->flag, sel_op_result, BONE_TIPSEL);
}
}
is_point_done |= is_inside;
}
/* if one of points selected, we skip the bone itself */
if ((is_point_done == false) && (points_proj_tot == 2)) {
const bool is_select = ebone->flag & BONE_SELECTED;
{
const bool is_inside = is_inside_flag & BONESEL_BONE;
const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
if (sel_op_result != -1) {
if (sel_op_result == 0 || EBONE_SELECTABLE(arm, ebone)) {
SET_FLAG_FROM_TEST(
ebone->flag, sel_op_result, BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL);
}
}
}
changed = true;
}
changed |= is_point_done;
if (ebone_flag_prev != ebone->flag) {
ebone->temp.i = ebone->flag;
ebone->flag = ebone_flag_prev;
ebone->flag = ebone_flag_prev | BONE_DONE;
changed = true;
}
return changed;
}
/**
* Perform a selection operation on elements which have been 'touched',
* use for lasso & border select but can be used elsewhere too.
*
* Tagging is done via #EditBone.temp.i using: #BONESEL_ROOT, #BONESEL_TIP, #BONESEL_BONE
* And optionally ignoring end-points using the #BONESEL_ROOT, #BONESEL_TIP right shifted 16 bits.
* (used when the values are clipped outside the view).
*
* \param sel_op: #eSelectOp type.
*
* \note Visibility checks must be done by the caller.
*/
bool ED_armature_edit_select_op_from_tagged(bArmature *arm, const int sel_op)
{
bool changed = false;
/* Initialize flags. */
{
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
/* Flush the parent flag to this bone
* so we don't need to check the parent when adjusting the selection. */
if ((ebone->flag & BONE_CONNECTED) && ebone->parent) {
if (ebone->parent->flag & BONE_TIPSEL) {
ebone->flag |= BONE_ROOTSEL;
}
else {
ebone->flag &= ~BONE_ROOTSEL;
}
/* Flush the 'temp.i' flag. */
if (ebone->parent->temp.i & BONESEL_TIP) {
ebone->temp.i |= BONESEL_ROOT;
}
}
ebone->flag &= ~BONE_DONE;
}
}
/* Apply selection from bone selection flags. */
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (ebone->temp.i != 0) {
int is_ignore_flag = ((ebone->temp.i << 16) & (BONESEL_ROOT | BONESEL_TIP));
int is_inside_flag = (ebone->temp.i & (BONESEL_ROOT | BONESEL_TIP | BONESEL_BONE));
/* Use as previous bone flag from now on. */
ebone->temp.i = ebone->flag;
/* When there is a partial selection without both endpoints, only select an endpoint. */
if ((is_inside_flag & BONESEL_BONE) &&
ELEM(is_inside_flag & (BONESEL_ROOT | BONESEL_TIP), BONESEL_ROOT, BONESEL_TIP)) {
is_inside_flag &= ~BONESEL_BONE;
}
changed |= armature_edit_select_op_apply(arm, ebone, sel_op, is_ignore_flag, is_inside_flag);
}
}
if (changed) {
/* Cleanup flags. */
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (ebone->flag & BONE_DONE) {
SWAP(int, ebone->temp.i, ebone->flag);
ebone->flag |= BONE_DONE;
if ((ebone->flag & BONE_CONNECTED) && ebone->parent) {
if ((ebone->parent->flag & BONE_DONE) == 0) {
/* Checked below. */
ebone->parent->temp.i = ebone->parent->flag;
}
}
}
}
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (ebone->flag & BONE_DONE) {
if ((ebone->flag & BONE_CONNECTED) && ebone->parent) {
bool is_parent_tip_changed = (ebone->parent->flag & BONE_TIPSEL) !=
(ebone->parent->temp.i & BONE_TIPSEL);
if ((ebone->temp.i & BONE_ROOTSEL) == 0) {
if ((ebone->flag & BONE_ROOTSEL) != 0) {
ebone->parent->flag |= BONE_TIPSEL;
}
}
else {
if ((ebone->flag & BONE_ROOTSEL) == 0) {
ebone->parent->flag &= ~BONE_TIPSEL;
}
}
if (is_parent_tip_changed == false) {
/* Keep tip selected if the parent remains selected. */
if (ebone->parent->flag & BONE_SELECTED) {
ebone->parent->flag |= BONE_TIPSEL;
}
}
}
ebone->flag &= ~BONE_DONE;
}
}
ED_armature_edit_sync_selection(arm->edbo);
ED_armature_edit_validate_active(arm);
}
return changed;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name (De)Select All Operator
* \{ */
static int armature_de_select_all_exec(bContext *C, wmOperator *op)
{
int action = RNA_enum_get(op->ptr, "action");
if (action == SEL_TOGGLE) {
/* Determine if there are any selected bones
* And therefore whether we are selecting or deselecting */
action = SEL_SELECT;
CTX_DATA_BEGIN (C, EditBone *, ebone, visible_bones) {
if (ebone->flag & (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL)) {
action = SEL_DESELECT;
break;
}
}
CTX_DATA_END;
}
/* Set the flags. */
CTX_DATA_BEGIN (C, EditBone *, ebone, visible_bones) {
/* ignore bone if selection can't change */
switch (action) {
case SEL_SELECT:
if ((ebone->flag & BONE_UNSELECTABLE) == 0) {
ebone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
if (ebone->parent) {
ebone->parent->flag |= BONE_TIPSEL;
}
}
break;
case SEL_DESELECT:
ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
break;
case SEL_INVERT:
if (ebone->flag & BONE_SELECTED) {
ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
}
else {
if ((ebone->flag & BONE_UNSELECTABLE) == 0) {
ebone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
if (ebone->parent) {
ebone->parent->flag |= BONE_TIPSEL;
}
}
}
break;
}
}
CTX_DATA_END;
ED_outliner_select_sync_from_edit_bone_tag(C);
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, NULL);
/* Tagging only one object to refresh drawing. */
Object *obedit = CTX_data_edit_object(C);
DEG_id_tag_update(&obedit->id, ID_RECALC_SELECT);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_select_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "(De)select All";
ot->idname = "ARMATURE_OT_select_all";
ot->description = "Toggle selection status of all bones";
/* api callbacks */
ot->exec = armature_de_select_all_exec;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
WM_operator_properties_select_all(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select More/Less Implementation
* \{ */
static void armature_select_more(bArmature *arm, EditBone *ebone)
{
if ((EBONE_PREV_FLAG_GET(ebone) & (BONE_ROOTSEL | BONE_TIPSEL)) != 0) {
if (EBONE_SELECTABLE(arm, ebone)) {
ED_armature_ebone_select_set(ebone, true);
}
}
if (ebone->parent && (ebone->flag & BONE_CONNECTED)) {
/* to parent */
if ((EBONE_PREV_FLAG_GET(ebone) & BONE_ROOTSEL) != 0) {
if (EBONE_SELECTABLE(arm, ebone->parent)) {
ED_armature_ebone_selectflag_enable(ebone->parent,
(BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL));
}
}
/* from parent (difference from select less) */
if ((EBONE_PREV_FLAG_GET(ebone->parent) & BONE_TIPSEL) != 0) {
if (EBONE_SELECTABLE(arm, ebone)) {
ED_armature_ebone_selectflag_enable(ebone, (BONE_SELECTED | BONE_ROOTSEL));
}
}
}
}
static void armature_select_less(bArmature *UNUSED(arm), EditBone *ebone)
{
if ((EBONE_PREV_FLAG_GET(ebone) & (BONE_ROOTSEL | BONE_TIPSEL)) !=
(BONE_ROOTSEL | BONE_TIPSEL)) {
ED_armature_ebone_select_set(ebone, false);
}
if (ebone->parent && (ebone->flag & BONE_CONNECTED)) {
/* to parent */
if ((EBONE_PREV_FLAG_GET(ebone) & BONE_SELECTED) == 0) {
ED_armature_ebone_selectflag_disable(ebone->parent, (BONE_SELECTED | BONE_TIPSEL));
}
/* from parent (difference from select more) */
if ((EBONE_PREV_FLAG_GET(ebone->parent) & BONE_SELECTED) == 0) {
ED_armature_ebone_selectflag_disable(ebone, (BONE_SELECTED | BONE_ROOTSEL));
}
}
}
static void armature_select_more_less(Object *ob, bool more)
{
bArmature *arm = (bArmature *)ob->data;
EditBone *ebone;
/* XXX, eventually we shouldn't need this - campbell */
ED_armature_edit_sync_selection(arm->edbo);
/* count bones & store selection state */
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
EBONE_PREV_FLAG_SET(ebone, ED_armature_ebone_selectflag_get(ebone));
}
/* do selection */
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
if (EBONE_VISIBLE(arm, ebone)) {
if (more) {
armature_select_more(arm, ebone);
}
else {
armature_select_less(arm, ebone);
}
}
}
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
if (EBONE_VISIBLE(arm, ebone)) {
if (more == false) {
if (ebone->flag & BONE_SELECTED) {
ED_armature_ebone_select_set(ebone, true);
}
}
}
ebone->temp.p = NULL;
}
ED_armature_edit_sync_selection(arm->edbo);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select More Operator
* \{ */
static int armature_de_select_more_exec(bContext *C, wmOperator *UNUSED(op))
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
armature_select_more_less(ob, true);
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
MEM_freeN(objects);
ED_outliner_select_sync_from_edit_bone_tag(C);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_select_more(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select More";
ot->idname = "ARMATURE_OT_select_more";
ot->description = "Select those bones connected to the initial selection";
/* api callbacks */
ot->exec = armature_de_select_more_exec;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Less Operator
* \{ */
static int armature_de_select_less_exec(bContext *C, wmOperator *UNUSED(op))
{
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
armature_select_more_less(ob, false);
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
MEM_freeN(objects);
ED_outliner_select_sync_from_edit_bone_tag(C);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_select_less(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Less";
ot->idname = "ARMATURE_OT_select_less";
ot->description = "Deselect those bones at the boundary of each selection region";
/* api callbacks */
ot->exec = armature_de_select_less_exec;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Similar
* \{ */
enum {
SIMEDBONE_CHILDREN = 1,
SIMEDBONE_CHILDREN_IMMEDIATE,
SIMEDBONE_SIBLINGS,
SIMEDBONE_LENGTH,
SIMEDBONE_DIRECTION,
SIMEDBONE_PREFIX,
SIMEDBONE_SUFFIX,
SIMEDBONE_LAYER,
SIMEDBONE_GROUP,
SIMEDBONE_SHAPE,
};
static const EnumPropertyItem prop_similar_types[] = {
{SIMEDBONE_CHILDREN, "CHILDREN", 0, "Children", ""},
{SIMEDBONE_CHILDREN_IMMEDIATE, "CHILDREN_IMMEDIATE", 0, "Immediate Children", ""},
{SIMEDBONE_SIBLINGS, "SIBLINGS", 0, "Siblings", ""},
{SIMEDBONE_LENGTH, "LENGTH", 0, "Length", ""},
{SIMEDBONE_DIRECTION, "DIRECTION", 0, "Direction (Y Axis)", ""},
{SIMEDBONE_PREFIX, "PREFIX", 0, "Prefix", ""},
{SIMEDBONE_SUFFIX, "SUFFIX", 0, "Suffix", ""},
{SIMEDBONE_LAYER, "LAYER", 0, "Layer", ""},
{SIMEDBONE_GROUP, "GROUP", 0, "Group", ""},
{SIMEDBONE_SHAPE, "SHAPE", 0, "Shape", ""},
{0, NULL, 0, NULL, NULL},
};
static float bone_length_squared_worldspace_get(Object *ob, EditBone *ebone)
{
float v1[3], v2[3];
mul_v3_mat3_m4v3(v1, ob->obmat, ebone->head);
mul_v3_mat3_m4v3(v2, ob->obmat, ebone->tail);
return len_squared_v3v3(v1, v2);
}
static void select_similar_length(bContext *C, const float thresh)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
Object *ob_act = CTX_data_edit_object(C);
EditBone *ebone_act = CTX_data_active_bone(C);
/* Thresh is always relative to current length. */
const float len = bone_length_squared_worldspace_get(ob_act, ebone_act);
const float len_min = len / (1.0f + (thresh - FLT_EPSILON));
const float len_max = len * (1.0f + (thresh + FLT_EPSILON));
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
bArmature *arm = ob->data;
bool changed = false;
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (EBONE_SELECTABLE(arm, ebone)) {
const float len_iter = bone_length_squared_worldspace_get(ob, ebone);
if ((len_iter > len_min) && (len_iter < len_max)) {
ED_armature_ebone_select_set(ebone, true);
changed = true;
}
}
}
if (changed) {
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
}
MEM_freeN(objects);
}
static void bone_direction_worldspace_get(Object *ob, EditBone *ebone, float *r_dir)
{
float v1[3], v2[3];
copy_v3_v3(v1, ebone->head);
copy_v3_v3(v2, ebone->tail);
mul_m4_v3(ob->obmat, v1);
mul_m4_v3(ob->obmat, v2);
sub_v3_v3v3(r_dir, v1, v2);
normalize_v3(r_dir);
}
static void select_similar_direction(bContext *C, const float thresh)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
Object *ob_act = CTX_data_edit_object(C);
EditBone *ebone_act = CTX_data_active_bone(C);
float dir_act[3];
bone_direction_worldspace_get(ob_act, ebone_act, dir_act);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
bArmature *arm = ob->data;
bool changed = false;
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (EBONE_SELECTABLE(arm, ebone)) {
float dir[3];
bone_direction_worldspace_get(ob, ebone, dir);
if (angle_v3v3(dir_act, dir) / (float)M_PI < (thresh + FLT_EPSILON)) {
ED_armature_ebone_select_set(ebone, true);
changed = true;
}
}
}
if (changed) {
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
}
MEM_freeN(objects);
}
static void select_similar_layer(bContext *C)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
EditBone *ebone_act = CTX_data_active_bone(C);
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
bArmature *arm = ob->data;
bool changed = false;
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (EBONE_SELECTABLE(arm, ebone)) {
if (ebone->layer & ebone_act->layer) {
ED_armature_ebone_select_set(ebone, true);
changed = true;
}
}
}
if (changed) {
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
}
MEM_freeN(objects);
}
static void select_similar_prefix(bContext *C)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
EditBone *ebone_act = CTX_data_active_bone(C);
char body_tmp[MAXBONENAME];
char prefix_act[MAXBONENAME];
BLI_string_split_prefix(ebone_act->name, prefix_act, body_tmp, sizeof(ebone_act->name));
if (prefix_act[0] == '\0') {
return;
}
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
bArmature *arm = ob->data;
bool changed = false;
/* Find matches */
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (EBONE_SELECTABLE(arm, ebone)) {
char prefix_other[MAXBONENAME];
BLI_string_split_prefix(ebone->name, prefix_other, body_tmp, sizeof(ebone->name));
if (STREQ(prefix_act, prefix_other)) {
ED_armature_ebone_select_set(ebone, true);
changed = true;
}
}
}
if (changed) {
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
}
MEM_freeN(objects);
}
static void select_similar_suffix(bContext *C)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
EditBone *ebone_act = CTX_data_active_bone(C);
char body_tmp[MAXBONENAME];
char suffix_act[MAXBONENAME];
BLI_string_split_suffix(ebone_act->name, body_tmp, suffix_act, sizeof(ebone_act->name));
if (suffix_act[0] == '\0') {
return;
}
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
bArmature *arm = ob->data;
bool changed = false;
/* Find matches */
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (EBONE_SELECTABLE(arm, ebone)) {
char suffix_other[MAXBONENAME];
BLI_string_split_suffix(ebone->name, body_tmp, suffix_other, sizeof(ebone->name));
if (STREQ(suffix_act, suffix_other)) {
ED_armature_ebone_select_set(ebone, true);
changed = true;
}
}
}
if (changed) {
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
}
MEM_freeN(objects);
}
/** Use for matching any pose channel data. */
static void select_similar_data_pchan(bContext *C, const size_t bytes_size, const int offset)
{
Object *obedit = CTX_data_edit_object(C);
bArmature *arm = obedit->data;
EditBone *ebone_act = CTX_data_active_bone(C);
const bPoseChannel *pchan_active = BKE_pose_channel_find_name(obedit->pose, ebone_act->name);
/* This will mostly happen for corner cases where the user tried to access this
* before having any valid pose data for the armature. */
if (pchan_active == NULL) {
return;
}
const char *data_active = (const char *)POINTER_OFFSET(pchan_active, offset);
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
if (EBONE_SELECTABLE(arm, ebone)) {
const bPoseChannel *pchan = BKE_pose_channel_find_name(obedit->pose, ebone->name);
if (pchan) {
const char *data_test = (const char *)POINTER_OFFSET(pchan, offset);
if (memcmp(data_active, data_test, bytes_size) == 0) {
ED_armature_ebone_select_set(ebone, true);
}
}
}
}
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
DEG_id_tag_update(&obedit->id, ID_RECALC_COPY_ON_WRITE);
}
static void is_ancestor(EditBone *bone, EditBone *ancestor)
{
if (ELEM(bone->temp.ebone, ancestor, NULL)) {
return;
}
if (!ELEM(bone->temp.ebone->temp.ebone, NULL, ancestor)) {
is_ancestor(bone->temp.ebone, ancestor);
}
bone->temp.ebone = bone->temp.ebone->temp.ebone;
}
static void select_similar_children(bContext *C)
{
Object *obedit = CTX_data_edit_object(C);
bArmature *arm = obedit->data;
EditBone *ebone_act = CTX_data_active_bone(C);
LISTBASE_FOREACH (EditBone *, ebone_iter, arm->edbo) {
ebone_iter->temp.ebone = ebone_iter->parent;
}
LISTBASE_FOREACH (EditBone *, ebone_iter, arm->edbo) {
is_ancestor(ebone_iter, ebone_act);
if (ebone_iter->temp.ebone == ebone_act && EBONE_SELECTABLE(arm, ebone_iter)) {
ED_armature_ebone_select_set(ebone_iter, true);
}
}
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
DEG_id_tag_update(&obedit->id, ID_RECALC_COPY_ON_WRITE);
}
static void select_similar_children_immediate(bContext *C)
{
Object *obedit = CTX_data_edit_object(C);
bArmature *arm = obedit->data;
EditBone *ebone_act = CTX_data_active_bone(C);
LISTBASE_FOREACH (EditBone *, ebone_iter, arm->edbo) {
if (ebone_iter->parent == ebone_act && EBONE_SELECTABLE(arm, ebone_iter)) {
ED_armature_ebone_select_set(ebone_iter, true);
}
}
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
DEG_id_tag_update(&obedit->id, ID_RECALC_COPY_ON_WRITE);
}
static void select_similar_siblings(bContext *C)
{
Object *obedit = CTX_data_edit_object(C);
bArmature *arm = obedit->data;
EditBone *ebone_act = CTX_data_active_bone(C);
if (ebone_act->parent == NULL) {
return;
}
LISTBASE_FOREACH (EditBone *, ebone_iter, arm->edbo) {
if (ebone_iter->parent == ebone_act->parent && EBONE_SELECTABLE(arm, ebone_iter)) {
ED_armature_ebone_select_set(ebone_iter, true);
}
}
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
DEG_id_tag_update(&obedit->id, ID_RECALC_COPY_ON_WRITE);
}
static int armature_select_similar_exec(bContext *C, wmOperator *op)
{
/* Get props */
int type = RNA_enum_get(op->ptr, "type");
float thresh = RNA_float_get(op->ptr, "threshold");
/* Check for active bone */
if (CTX_data_active_bone(C) == NULL) {
BKE_report(op->reports, RPT_ERROR, "Operation requires an active bone");
return OPERATOR_CANCELLED;
}
#define STRUCT_SIZE_AND_OFFSET(_struct, _member) \
sizeof(((_struct *)NULL)->_member), offsetof(_struct, _member)
switch (type) {
case SIMEDBONE_CHILDREN:
select_similar_children(C);
break;
case SIMEDBONE_CHILDREN_IMMEDIATE:
select_similar_children_immediate(C);
break;
case SIMEDBONE_SIBLINGS:
select_similar_siblings(C);
break;
case SIMEDBONE_LENGTH:
select_similar_length(C, thresh);
break;
case SIMEDBONE_DIRECTION:
select_similar_direction(C, thresh);
break;
case SIMEDBONE_PREFIX:
select_similar_prefix(C);
break;
case SIMEDBONE_SUFFIX:
select_similar_suffix(C);
break;
case SIMEDBONE_LAYER:
select_similar_layer(C);
break;
case SIMEDBONE_GROUP:
select_similar_data_pchan(C, STRUCT_SIZE_AND_OFFSET(bPoseChannel, agrp_index));
break;
case SIMEDBONE_SHAPE:
select_similar_data_pchan(C, STRUCT_SIZE_AND_OFFSET(bPoseChannel, custom));
break;
}
#undef STRUCT_SIZE_AND_OFFSET
ED_outliner_select_sync_from_edit_bone_tag(C);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_select_similar(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Similar";
ot->idname = "ARMATURE_OT_select_similar";
/* callback functions */
ot->invoke = WM_menu_invoke;
ot->exec = armature_select_similar_exec;
ot->poll = ED_operator_editarmature;
ot->description = "Select similar bones by property types";
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_enum(ot->srna, "type", prop_similar_types, SIMEDBONE_LENGTH, "Type", "");
RNA_def_float(ot->srna, "threshold", 0.1f, 0.0f, 1.0f, "Threshold", "", 0.0f, 1.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Hierarchy Operator
* \{ */
/* No need to convert to multi-objects. Just like we keep the non-active bones
* selected we then keep the non-active objects untouched (selected/unselected). */
static int armature_select_hierarchy_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_edit_object(C);
EditBone *ebone_active;
int direction = RNA_enum_get(op->ptr, "direction");
const bool add_to_sel = RNA_boolean_get(op->ptr, "extend");
bool changed = false;
bArmature *arm = (bArmature *)ob->data;
ebone_active = arm->act_edbone;
if (ebone_active == NULL) {
return OPERATOR_CANCELLED;
}
if (direction == BONE_SELECT_PARENT) {
if (ebone_active->parent) {
EditBone *ebone_parent;
ebone_parent = ebone_active->parent;
if (EBONE_SELECTABLE(arm, ebone_parent)) {
arm->act_edbone = ebone_parent;
if (!add_to_sel) {
ED_armature_ebone_select_set(ebone_active, false);
}
ED_armature_ebone_select_set(ebone_parent, true);
changed = true;
}
}
}
else { /* BONE_SELECT_CHILD */
EditBone *ebone_iter, *ebone_child = NULL;
int pass;
/* first pass, only connected bones (the logical direct child) */
for (pass = 0; pass < 2 && (ebone_child == NULL); pass++) {
for (ebone_iter = arm->edbo->first; ebone_iter; ebone_iter = ebone_iter->next) {
/* possible we have multiple children, some invisible */
if (EBONE_SELECTABLE(arm, ebone_iter)) {
if (ebone_iter->parent == ebone_active) {
if ((pass == 1) || (ebone_iter->flag & BONE_CONNECTED)) {
ebone_child = ebone_iter;
break;
}
}
}
}
}
if (ebone_child) {
arm->act_edbone = ebone_child;
if (!add_to_sel) {
ED_armature_ebone_select_set(ebone_active, false);
}
ED_armature_ebone_select_set(ebone_child, true);
changed = true;
}
}
if (changed == false) {
return OPERATOR_CANCELLED;
}
ED_outliner_select_sync_from_edit_bone_tag(C);
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);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_select_hierarchy(wmOperatorType *ot)
{
static const EnumPropertyItem direction_items[] = {
{BONE_SELECT_PARENT, "PARENT", 0, "Select Parent", ""},
{BONE_SELECT_CHILD, "CHILD", 0, "Select Child", ""},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Select Hierarchy";
ot->idname = "ARMATURE_OT_select_hierarchy";
ot->description = "Select immediate parent/children of selected bones";
/* api callbacks */
ot->exec = armature_select_hierarchy_exec;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_enum(ot->srna, "direction", direction_items, BONE_SELECT_PARENT, "Direction", "");
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Mirror Operator
* \{ */
/**
* \note clone of #pose_select_mirror_exec keep in sync
*/
static int armature_select_mirror_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
const bool active_only = RNA_boolean_get(op->ptr, "only_active");
const bool extend = RNA_boolean_get(op->ptr, "extend");
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
view_layer, CTX_wm_view3d(C), &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob = objects[ob_index];
bArmature *arm = ob->data;
EditBone *ebone, *ebone_mirror_act = NULL;
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
const int flag = ED_armature_ebone_selectflag_get(ebone);
EBONE_PREV_FLAG_SET(ebone, flag);
}
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
if (EBONE_SELECTABLE(arm, ebone)) {
EditBone *ebone_mirror;
int flag_new = extend ? EBONE_PREV_FLAG_GET(ebone) : 0;
if ((ebone_mirror = ED_armature_ebone_get_mirrored(arm->edbo, ebone)) &&
(EBONE_VISIBLE(arm, ebone_mirror))) {
const int flag_mirror = EBONE_PREV_FLAG_GET(ebone_mirror);
flag_new |= flag_mirror;
if (ebone == arm->act_edbone) {
ebone_mirror_act = ebone_mirror;
}
/* skip all but the active or its mirror */
if (active_only && !ELEM(arm->act_edbone, ebone, ebone_mirror)) {
continue;
}
}
ED_armature_ebone_selectflag_set(ebone, flag_new);
}
}
if (ebone_mirror_act) {
arm->act_edbone = ebone_mirror_act;
}
ED_outliner_select_sync_from_edit_bone_tag(C);
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);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
}
void ARMATURE_OT_select_mirror(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Mirror";
ot->idname = "ARMATURE_OT_select_mirror";
ot->description = "Mirror the bone selection";
/* api callbacks */
ot->exec = armature_select_mirror_exec;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(
ot->srna, "only_active", false, "Active Only", "Only operate on the active bone");
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Path Operator
* \{ */
static bool armature_shortest_path_select(
bArmature *arm, EditBone *ebone_parent, EditBone *ebone_child, bool use_parent, bool is_test)
{
do {
if (!use_parent && (ebone_child == ebone_parent)) {
break;
}
if (is_test) {
if (!EBONE_SELECTABLE(arm, ebone_child)) {
return false;
}
}
else {
ED_armature_ebone_selectflag_set(ebone_child, (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL));
}
if (ebone_child == ebone_parent) {
break;
}
ebone_child = ebone_child->parent;
} while (true);
return true;
}
static int armature_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *obedit = CTX_data_edit_object(C);
bArmature *arm = obedit->data;
EditBone *ebone_src, *ebone_dst;
EditBone *ebone_isect_parent = NULL;
EditBone *ebone_isect_child[2];
bool changed;
Base *base_dst = NULL;
view3d_operator_needs_opengl(C);
BKE_object_update_select_id(CTX_data_main(C));
ebone_src = arm->act_edbone;
ebone_dst = ED_armature_pick_ebone(C, event->mval, false, &base_dst);
/* fallback to object selection */
if (ELEM(NULL, ebone_src, ebone_dst) || (ebone_src == ebone_dst)) {
return OPERATOR_PASS_THROUGH;
}
if (base_dst && base_dst->object != obedit) {
/* Disconnected, ignore. */
return OPERATOR_CANCELLED;
}
ebone_isect_child[0] = ebone_src;
ebone_isect_child[1] = ebone_dst;
/* ensure 'ebone_src' is the parent of 'ebone_dst', or set 'ebone_isect_parent' */
if (ED_armature_ebone_is_child_recursive(ebone_src, ebone_dst)) {
/* pass */
}
else if (ED_armature_ebone_is_child_recursive(ebone_dst, ebone_src)) {
SWAP(EditBone *, ebone_src, ebone_dst);
}
else if ((ebone_isect_parent = ED_armature_ebone_find_shared_parent(ebone_isect_child, 2))) {
/* pass */
}
else {
/* disconnected bones */
return OPERATOR_CANCELLED;
}
if (ebone_isect_parent) {
if (armature_shortest_path_select(arm, ebone_isect_parent, ebone_src, false, true) &&
armature_shortest_path_select(arm, ebone_isect_parent, ebone_dst, false, true)) {
armature_shortest_path_select(arm, ebone_isect_parent, ebone_src, false, false);
armature_shortest_path_select(arm, ebone_isect_parent, ebone_dst, false, false);
changed = true;
}
else {
/* unselectable */
changed = false;
}
}
else {
if (armature_shortest_path_select(arm, ebone_src, ebone_dst, true, true)) {
armature_shortest_path_select(arm, ebone_src, ebone_dst, true, false);
changed = true;
}
else {
/* unselectable */
changed = false;
}
}
if (changed) {
arm->act_edbone = ebone_dst;
ED_outliner_select_sync_from_edit_bone_tag(C);
ED_armature_edit_sync_selection(arm->edbo);
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
DEG_id_tag_update(&obedit->id, ID_RECALC_COPY_ON_WRITE);
return OPERATOR_FINISHED;
}
BKE_report(op->reports, RPT_WARNING, "Unselectable bone in chain");
return OPERATOR_CANCELLED;
}
void ARMATURE_OT_shortest_path_pick(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Pick Shortest Path";
ot->idname = "ARMATURE_OT_shortest_path_pick";
ot->description = "Select shortest path between two bones";
/* api callbacks */
ot->invoke = armature_shortest_path_pick_invoke;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */