1120 lines
29 KiB
C
1120 lines
29 KiB
C
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* 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.
|
|
*
|
|
* Contributor(s): Blender Foundation, 2002-2009 full recode.
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*
|
|
* API's and Operators for selecting armature bones in EditMode
|
|
*/
|
|
|
|
/** \file blender/editors/armature/armature_select.c
|
|
* \ingroup edarmature
|
|
*/
|
|
|
|
#include "DNA_armature_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_math.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_deform.h"
|
|
#include "BKE_report.h"
|
|
|
|
#include "BIF_gl.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "ED_armature.h"
|
|
#include "ED_screen.h"
|
|
#include "ED_view3d.h"
|
|
|
|
#include "armature_intern.h"
|
|
|
|
/* utility macros fro storing a temp int in the bone (selection flag) */
|
|
#define EBONE_PREV_FLAG_GET(ebone) ((void)0, (GET_INT_FROM_POINTER((ebone)->temp)))
|
|
#define EBONE_PREV_FLAG_SET(ebone, val) ((ebone)->temp = SET_INT_IN_POINTER(val))
|
|
|
|
/* **************** PoseMode & EditMode Selection Buffer Queries *************************** */
|
|
|
|
/* only for opengl selection indices */
|
|
Bone *get_indexed_bone(Object *ob, int index)
|
|
{
|
|
bPoseChannel *pchan;
|
|
if (ob->pose == NULL) return NULL;
|
|
index >>= 16; // bone selection codes use left 2 bytes
|
|
|
|
pchan = BLI_findlink(&ob->pose->chanbase, index);
|
|
return pchan ? pchan->bone : NULL;
|
|
}
|
|
|
|
/* See if there are any selected bones in this buffer */
|
|
/* only bones from base are checked on */
|
|
void *get_bone_from_selectbuffer(Scene *scene, Base *base, unsigned int *buffer, short hits, short findunsel)
|
|
{
|
|
Object *obedit = scene->obedit; // XXX get from context
|
|
Bone *bone;
|
|
EditBone *ebone;
|
|
void *firstunSel = NULL, *firstSel = NULL, *data;
|
|
unsigned int hitresult;
|
|
short i, takeNext = 0, sel;
|
|
|
|
for (i = 0; i < hits; i++) {
|
|
hitresult = buffer[3 + (i * 4)];
|
|
|
|
if (!(hitresult & BONESEL_NOSEL)) { // -1
|
|
if (hitresult & BONESEL_ANY) { // to avoid including objects in selection
|
|
|
|
hitresult &= ~(BONESEL_ANY);
|
|
/* Determine what the current bone is */
|
|
if (obedit == NULL || base->object != obedit) {
|
|
/* no singular posemode, so check for correct object */
|
|
if (base->selcol == (hitresult & 0xFFFF)) {
|
|
bone = get_indexed_bone(base->object, hitresult);
|
|
|
|
if (findunsel)
|
|
sel = (bone->flag & BONE_SELECTED);
|
|
else
|
|
sel = !(bone->flag & BONE_SELECTED);
|
|
|
|
data = bone;
|
|
}
|
|
else {
|
|
data = NULL;
|
|
sel = 0;
|
|
}
|
|
}
|
|
else {
|
|
bArmature *arm = obedit->data;
|
|
|
|
ebone = BLI_findlink(arm->edbo, hitresult);
|
|
if (findunsel)
|
|
sel = (ebone->flag & BONE_SELECTED);
|
|
else
|
|
sel = !(ebone->flag & BONE_SELECTED);
|
|
|
|
data = ebone;
|
|
}
|
|
|
|
if (data) {
|
|
if (sel) {
|
|
if (!firstSel) firstSel = data;
|
|
takeNext = 1;
|
|
}
|
|
else {
|
|
if (!firstunSel)
|
|
firstunSel = data;
|
|
if (takeNext)
|
|
return data;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (firstunSel)
|
|
return firstunSel;
|
|
else
|
|
return firstSel;
|
|
}
|
|
|
|
/* used by posemode as well editmode */
|
|
/* only checks scene->basact! */
|
|
/* x and y are mouse coords (area space) */
|
|
void *get_nearest_bone(bContext *C, short findunsel, int x, int y)
|
|
{
|
|
ViewContext vc;
|
|
rcti rect;
|
|
unsigned int buffer[MAXPICKBUF];
|
|
short hits;
|
|
|
|
view3d_set_viewcontext(C, &vc);
|
|
|
|
// rect.xmin = ... mouseco!
|
|
rect.xmin = rect.xmax = x;
|
|
rect.ymin = rect.ymax = y;
|
|
|
|
glInitNames();
|
|
hits = view3d_opengl_select(&vc, buffer, MAXPICKBUF, &rect);
|
|
|
|
if (hits > 0)
|
|
return get_bone_from_selectbuffer(vc.scene, vc.scene->basact, buffer, hits, findunsel);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* **************** EditMode stuff ********************** */
|
|
|
|
/* called in space.c */
|
|
/* previously "selectconnected_armature" */
|
|
static int armature_select_linked_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
bArmature *arm;
|
|
EditBone *bone, *curBone, *next;
|
|
int extend = RNA_boolean_get(op->ptr, "extend");
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
arm = obedit->data;
|
|
|
|
view3d_operator_needs_opengl(C);
|
|
|
|
if (extend)
|
|
bone = get_nearest_bone(C, 0, event->mval[0], event->mval[1]);
|
|
else
|
|
bone = get_nearest_bone(C, 1, event->mval[0], event->mval[1]);
|
|
|
|
if (!bone)
|
|
return OPERATOR_CANCELLED;
|
|
|
|
/* Select parents */
|
|
for (curBone = bone; curBone; curBone = next) {
|
|
if ((curBone->flag & BONE_UNSELECTABLE) == 0) {
|
|
if (extend) {
|
|
curBone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
else {
|
|
curBone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
}
|
|
|
|
if (curBone->flag & BONE_CONNECTED)
|
|
next = curBone->parent;
|
|
else
|
|
next = NULL;
|
|
}
|
|
|
|
/* Select children */
|
|
while (bone) {
|
|
for (curBone = arm->edbo->first; curBone; curBone = next) {
|
|
next = curBone->next;
|
|
if ((curBone->parent == bone) && (curBone->flag & BONE_UNSELECTABLE) == 0) {
|
|
if (curBone->flag & BONE_CONNECTED) {
|
|
if (extend)
|
|
curBone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
else
|
|
curBone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
bone = curBone;
|
|
break;
|
|
}
|
|
else {
|
|
bone = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!curBone)
|
|
bone = NULL;
|
|
}
|
|
|
|
ED_armature_sync_selection(arm->edbo);
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int armature_select_linked_poll(bContext *C)
|
|
{
|
|
return (ED_operator_view3d_active(C) && ED_operator_editarmature(C) );
|
|
}
|
|
|
|
void ARMATURE_OT_select_linked(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Select Connected";
|
|
ot->idname = "ARMATURE_OT_select_linked";
|
|
ot->description = "Select bones related to selected ones by parent/child relationships";
|
|
|
|
/* api callbacks */
|
|
/* leave 'exec' unset */
|
|
ot->invoke = armature_select_linked_invoke;
|
|
ot->poll = armature_select_linked_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* properties */
|
|
RNA_def_boolean(ot->srna, "extend", FALSE, "Extend", "Extend selection instead of deselecting everything first");
|
|
}
|
|
|
|
/* does bones and points */
|
|
/* note that BONE ROOT only gets drawn for root bones (or without IK) */
|
|
static EditBone *get_nearest_editbonepoint(ViewContext *vc, const int mval[2],
|
|
ListBase *edbo, int findunsel, int *selmask)
|
|
{
|
|
bArmature *arm = (bArmature *)vc->obedit->data;
|
|
EditBone *ebone_next_act = arm->act_edbone;
|
|
|
|
EditBone *ebone;
|
|
rcti rect;
|
|
unsigned int buffer[MAXPICKBUF];
|
|
unsigned int hitresult, besthitresult = BONESEL_NOSEL;
|
|
int i, mindep = 4;
|
|
short hits;
|
|
|
|
glInitNames();
|
|
|
|
/* 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. */
|
|
if (ebone_next_act &&
|
|
EBONE_VISIBLE(arm, ebone_next_act) &&
|
|
ebone_next_act->flag & (BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL))
|
|
{
|
|
ebone_next_act = ebone_next_act->next ? ebone_next_act->next : arm->edbo->first;
|
|
}
|
|
else {
|
|
ebone_next_act = NULL;
|
|
}
|
|
|
|
rect.xmin = mval[0] - 5;
|
|
rect.xmax = mval[0] + 5;
|
|
rect.ymin = mval[1] - 5;
|
|
rect.ymax = mval[1] + 5;
|
|
|
|
hits = view3d_opengl_select(vc, buffer, MAXPICKBUF, &rect);
|
|
if (hits == 0) {
|
|
rect.xmin = mval[0] - 12;
|
|
rect.xmax = mval[0] + 12;
|
|
rect.ymin = mval[1] - 12;
|
|
rect.ymax = mval[1] + 12;
|
|
hits = view3d_opengl_select(vc, buffer, MAXPICKBUF, &rect);
|
|
}
|
|
/* See if there are any selected bones in this group */
|
|
if (hits > 0) {
|
|
|
|
if (hits == 1) {
|
|
if (!(buffer[3] & BONESEL_NOSEL))
|
|
besthitresult = buffer[3];
|
|
}
|
|
else {
|
|
for (i = 0; i < hits; i++) {
|
|
hitresult = buffer[3 + (i * 4)];
|
|
if (!(hitresult & BONESEL_NOSEL)) {
|
|
int dep;
|
|
|
|
ebone = BLI_findlink(edbo, hitresult & ~BONESEL_ANY);
|
|
|
|
/* 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)
|
|
dep = 1;
|
|
else if ( (hitresult & BONESEL_TIP) && (ebone->flag & BONE_TIPSEL) == 0)
|
|
dep = 1;
|
|
else
|
|
dep = 2;
|
|
}
|
|
else {
|
|
dep = 2;
|
|
}
|
|
}
|
|
else {
|
|
/* bone found */
|
|
if (findunsel) {
|
|
if ((ebone->flag & BONE_SELECTED) == 0)
|
|
dep = 2;
|
|
else
|
|
dep = 3;
|
|
}
|
|
else {
|
|
dep = 3;
|
|
}
|
|
}
|
|
|
|
if (ebone == ebone_next_act) {
|
|
dep -= 1;
|
|
}
|
|
|
|
if (dep < mindep) {
|
|
mindep = dep;
|
|
besthitresult = hitresult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(besthitresult & BONESEL_NOSEL)) {
|
|
|
|
ebone = BLI_findlink(edbo, besthitresult & ~BONESEL_ANY);
|
|
|
|
*selmask = 0;
|
|
if (besthitresult & BONESEL_ROOT)
|
|
*selmask |= BONE_ROOTSEL;
|
|
if (besthitresult & BONESEL_TIP)
|
|
*selmask |= BONE_TIPSEL;
|
|
if (besthitresult & BONESEL_BONE)
|
|
*selmask |= BONE_SELECTED;
|
|
return ebone;
|
|
}
|
|
}
|
|
*selmask = 0;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/* toggle==0: deselect
|
|
* toggle==1: swap (based on test)
|
|
* toggle==2: swap (no test), CURRENTLY UNUSED
|
|
*/
|
|
void ED_armature_deselect_all(Object *obedit, int toggle)
|
|
{
|
|
bArmature *arm = obedit->data;
|
|
EditBone *eBone;
|
|
int sel = 1;
|
|
|
|
if (toggle == 1) {
|
|
/* Determine if there are any selected bones
|
|
* and therefore whether we are selecting or deselecting */
|
|
for (eBone = arm->edbo->first; eBone; eBone = eBone->next) {
|
|
// if (arm->layer & eBone->layer) {
|
|
if (eBone->flag & (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL)) {
|
|
sel = 0;
|
|
break;
|
|
}
|
|
// }
|
|
}
|
|
}
|
|
else {
|
|
sel = toggle;
|
|
}
|
|
|
|
/* Set the flags */
|
|
for (eBone = arm->edbo->first; eBone; eBone = eBone->next) {
|
|
if (sel == 2) {
|
|
/* invert selection of bone */
|
|
if (EBONE_VISIBLE(arm, eBone)) {
|
|
eBone->flag ^= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
if (arm->act_edbone == eBone)
|
|
arm->act_edbone = NULL;
|
|
}
|
|
}
|
|
else if (sel == 1) {
|
|
/* select bone */
|
|
if (EBONE_VISIBLE(arm, eBone)) {
|
|
eBone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
if (eBone->parent)
|
|
eBone->parent->flag |= (BONE_TIPSEL);
|
|
}
|
|
}
|
|
else {
|
|
/* deselect bone */
|
|
eBone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
if (arm->act_edbone == eBone)
|
|
arm->act_edbone = NULL;
|
|
}
|
|
}
|
|
|
|
ED_armature_sync_selection(arm->edbo);
|
|
}
|
|
|
|
void ED_armature_deselect_all_visible(Object *obedit)
|
|
{
|
|
bArmature *arm = obedit->data;
|
|
EditBone *ebone;
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
/* first and foremost, bone must be visible and selected */
|
|
if (EBONE_VISIBLE(arm, ebone)) {
|
|
ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
}
|
|
|
|
ED_armature_sync_selection(arm->edbo);
|
|
}
|
|
|
|
/* accounts for connected parents */
|
|
static int ebone_select_flag(EditBone *ebone)
|
|
{
|
|
if (ebone->parent && (ebone->flag & BONE_CONNECTED)) {
|
|
return ((ebone->parent->flag & BONE_TIPSEL) ? BONE_ROOTSEL : 0) | (ebone->flag & (BONE_SELECTED | BONE_TIPSEL));
|
|
}
|
|
else {
|
|
return ebone->flag & (BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL);
|
|
}
|
|
}
|
|
|
|
/* context: editmode armature in view3d */
|
|
bool mouse_armature(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
|
|
{
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
bArmature *arm = obedit->data;
|
|
ViewContext vc;
|
|
EditBone *nearBone = NULL;
|
|
int selmask;
|
|
|
|
view3d_set_viewcontext(C, &vc);
|
|
|
|
BIF_sk_selectStroke(C, mval, extend);
|
|
|
|
nearBone = get_nearest_editbonepoint(&vc, mval, arm->edbo, 1, &selmask);
|
|
if (nearBone) {
|
|
|
|
if (!extend && !deselect && !toggle)
|
|
ED_armature_deselect_all(obedit, 0);
|
|
|
|
/* 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 (nearBone->parent && (nearBone->flag & BONE_CONNECTED)) {
|
|
/* click in a chain */
|
|
if (extend) {
|
|
/* select this bone */
|
|
nearBone->flag |= BONE_TIPSEL;
|
|
nearBone->parent->flag |= BONE_TIPSEL;
|
|
}
|
|
else if (deselect) {
|
|
/* deselect this bone */
|
|
nearBone->flag &= ~(BONE_TIPSEL | BONE_SELECTED);
|
|
/* only deselect parent tip if it is not selected */
|
|
if (!(nearBone->parent->flag & BONE_SELECTED))
|
|
nearBone->parent->flag &= ~BONE_TIPSEL;
|
|
}
|
|
else if (toggle) {
|
|
/* hold shift inverts this bone's selection */
|
|
if (nearBone->flag & BONE_SELECTED) {
|
|
/* deselect this bone */
|
|
nearBone->flag &= ~(BONE_TIPSEL | BONE_SELECTED);
|
|
/* only deselect parent tip if it is not selected */
|
|
if (!(nearBone->parent->flag & BONE_SELECTED))
|
|
nearBone->parent->flag &= ~BONE_TIPSEL;
|
|
}
|
|
else {
|
|
/* select this bone */
|
|
nearBone->flag |= BONE_TIPSEL;
|
|
nearBone->parent->flag |= BONE_TIPSEL;
|
|
}
|
|
}
|
|
else {
|
|
/* select this bone */
|
|
nearBone->flag |= BONE_TIPSEL;
|
|
nearBone->parent->flag |= BONE_TIPSEL;
|
|
}
|
|
}
|
|
else {
|
|
if (extend) {
|
|
nearBone->flag |= (BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
else if (deselect) {
|
|
nearBone->flag &= ~(BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
else if (toggle) {
|
|
/* hold shift inverts this bone's selection */
|
|
if (nearBone->flag & BONE_SELECTED)
|
|
nearBone->flag &= ~(BONE_TIPSEL | BONE_ROOTSEL);
|
|
else
|
|
nearBone->flag |= (BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
else
|
|
nearBone->flag |= (BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
}
|
|
else {
|
|
if (extend)
|
|
nearBone->flag |= selmask;
|
|
else if (deselect)
|
|
nearBone->flag &= ~selmask;
|
|
else if (toggle && (nearBone->flag & selmask))
|
|
nearBone->flag &= ~selmask;
|
|
else
|
|
nearBone->flag |= selmask;
|
|
}
|
|
|
|
ED_armature_sync_selection(arm->edbo);
|
|
|
|
if (nearBone) {
|
|
/* then now check for active status */
|
|
if (ebone_select_flag(nearBone)) {
|
|
arm->act_edbone = nearBone;
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, vc.obedit);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* **************** Selections ******************/
|
|
|
|
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;
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, NULL);
|
|
|
|
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);
|
|
}
|
|
|
|
/**************** Select more/less **************/
|
|
|
|
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_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 = NULL;
|
|
}
|
|
|
|
ED_armature_sync_selection(arm->edbo);
|
|
}
|
|
|
|
static int armature_de_select_more_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
armature_select_more_less(obedit, true);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
|
|
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;
|
|
}
|
|
|
|
static int armature_de_select_less_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
armature_select_more_less(obedit, false);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
|
|
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;
|
|
}
|
|
|
|
enum {
|
|
SIMEDBONE_LENGTH = 1,
|
|
SIMEDBONE_DIRECTION,
|
|
SIMEDBONE_PREFIX,
|
|
SIMEDBONE_SUFFIX,
|
|
SIMEDBONE_LAYER
|
|
};
|
|
|
|
static EnumPropertyItem prop_similar_types[] = {
|
|
{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", ""},
|
|
{0, NULL, 0, NULL, NULL}
|
|
};
|
|
|
|
|
|
static void select_similar_length(bArmature *arm, EditBone *ebone_act, const float thresh)
|
|
{
|
|
EditBone *ebone;
|
|
|
|
/* thresh is always relative to current length */
|
|
const float len_min = ebone_act->length / (1.0f + thresh);
|
|
const float len_max = ebone_act->length * (1.0f + thresh);
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (EBONE_SELECTABLE(arm, ebone)) {
|
|
if ((ebone->length >= len_min) &&
|
|
(ebone->length <= len_max))
|
|
{
|
|
ED_armature_ebone_select_set(ebone, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void select_similar_direction(bArmature *arm, EditBone *ebone_act, const float thresh)
|
|
{
|
|
EditBone *ebone;
|
|
float dir_act[3];
|
|
sub_v3_v3v3(dir_act, ebone_act->head, ebone_act->tail);
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (EBONE_SELECTABLE(arm, ebone)) {
|
|
float dir[3];
|
|
sub_v3_v3v3(dir, ebone->head, ebone->tail);
|
|
|
|
if (angle_v3v3(dir_act, dir) / (float)M_PI < thresh) {
|
|
ED_armature_ebone_select_set(ebone, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void select_similar_layer(bArmature *arm, EditBone *ebone_act)
|
|
{
|
|
EditBone *ebone;
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (EBONE_SELECTABLE(arm, ebone)) {
|
|
if (ebone->layer & ebone_act->layer) {
|
|
ED_armature_ebone_select_set(ebone, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void select_similar_prefix(bArmature *arm, EditBone *ebone_act)
|
|
{
|
|
EditBone *ebone;
|
|
|
|
char body_tmp[MAX_VGROUP_NAME];
|
|
char prefix_act[MAX_VGROUP_NAME];
|
|
|
|
BKE_deform_split_prefix(ebone_act->name, prefix_act, body_tmp);
|
|
|
|
if (prefix_act[0] == '\0')
|
|
return;
|
|
|
|
/* Find matches */
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (EBONE_SELECTABLE(arm, ebone)) {
|
|
char prefix_other[MAX_VGROUP_NAME];
|
|
BKE_deform_split_prefix(ebone->name, prefix_other, body_tmp);
|
|
if (STREQ(prefix_act, prefix_other)) {
|
|
ED_armature_ebone_select_set(ebone, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void select_similar_suffix(bArmature *arm, EditBone *ebone_act)
|
|
{
|
|
EditBone *ebone;
|
|
|
|
char body_tmp[MAX_VGROUP_NAME];
|
|
char suffix_act[MAX_VGROUP_NAME];
|
|
|
|
BKE_deform_split_suffix(ebone_act->name, body_tmp, suffix_act);
|
|
|
|
if (suffix_act[0] == '\0')
|
|
return;
|
|
|
|
/* Find matches */
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (EBONE_SELECTABLE(arm, ebone)) {
|
|
char suffix_other[MAX_VGROUP_NAME];
|
|
BKE_deform_split_suffix(ebone->name, body_tmp, suffix_other);
|
|
if (STREQ(suffix_act, suffix_other)) {
|
|
ED_armature_ebone_select_set(ebone, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int armature_select_similar_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
bArmature *arm = obedit->data;
|
|
EditBone *ebone_act = CTX_data_active_bone(C);
|
|
|
|
/* Get props */
|
|
int type = RNA_enum_get(op->ptr, "type");
|
|
float thresh = RNA_float_get(op->ptr, "threshold");
|
|
|
|
/* Check for active bone */
|
|
if (ebone_act == NULL) {
|
|
BKE_report(op->reports, RPT_ERROR, "Operation requires an active bone");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
switch (type) {
|
|
case SIMEDBONE_LENGTH:
|
|
select_similar_length(arm, ebone_act, thresh);
|
|
break;
|
|
case SIMEDBONE_DIRECTION:
|
|
select_similar_direction(arm, ebone_act, thresh);
|
|
break;
|
|
case SIMEDBONE_PREFIX:
|
|
select_similar_prefix(arm, ebone_act);
|
|
break;
|
|
case SIMEDBONE_SUFFIX:
|
|
select_similar_suffix(arm, ebone_act);
|
|
break;
|
|
case SIMEDBONE_LAYER:
|
|
select_similar_layer(arm, ebone_act);
|
|
break;
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
|
|
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, 0, "Type", "");
|
|
RNA_def_float(ot->srna, "threshold", 0.1f, 0.0f, 1.0f, "Threshold", "", 0.0f, 1.0f);
|
|
}
|
|
|
|
/* ********************* select hierarchy operator ************** */
|
|
|
|
/* Get the first available child of an editbone */
|
|
static EditBone *editbone_get_child(bArmature *arm, EditBone *pabone, short use_visibility)
|
|
{
|
|
EditBone *curbone, *chbone = NULL;
|
|
|
|
for (curbone = arm->edbo->first; curbone; curbone = curbone->next) {
|
|
if (curbone->parent == pabone) {
|
|
if (use_visibility) {
|
|
if ((arm->layer & curbone->layer) && !(pabone->flag & BONE_HIDDEN_A)) {
|
|
chbone = curbone;
|
|
}
|
|
}
|
|
else
|
|
chbone = curbone;
|
|
}
|
|
}
|
|
|
|
return chbone;
|
|
}
|
|
|
|
static int armature_select_hierarchy_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
Object *ob;
|
|
bArmature *arm;
|
|
EditBone *curbone, *pabone, *chbone;
|
|
int direction = RNA_enum_get(op->ptr, "direction");
|
|
int add_to_sel = RNA_boolean_get(op->ptr, "extend");
|
|
|
|
ob = obedit;
|
|
arm = (bArmature *)ob->data;
|
|
|
|
for (curbone = arm->edbo->first; curbone; curbone = curbone->next) {
|
|
/* only work on bone if it is visible and its selection can change */
|
|
if (EBONE_SELECTABLE(arm, curbone)) {
|
|
if (curbone == arm->act_edbone) {
|
|
if (direction == BONE_SELECT_PARENT) {
|
|
if (curbone->parent == NULL) continue;
|
|
else pabone = curbone->parent;
|
|
|
|
if (EBONE_VISIBLE(arm, pabone)) {
|
|
pabone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
arm->act_edbone = pabone;
|
|
if (pabone->parent) pabone->parent->flag |= BONE_TIPSEL;
|
|
|
|
if (!add_to_sel) curbone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
break;
|
|
}
|
|
|
|
}
|
|
else { // BONE_SELECT_CHILD
|
|
chbone = editbone_get_child(arm, curbone, 1);
|
|
if (chbone == NULL) continue;
|
|
|
|
if (EBONE_SELECTABLE(arm, chbone)) {
|
|
chbone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
arm->act_edbone = chbone;
|
|
|
|
if (!add_to_sel) {
|
|
curbone->flag &= ~(BONE_SELECTED | BONE_ROOTSEL);
|
|
if (curbone->parent) curbone->parent->flag &= ~BONE_TIPSEL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ED_armature_sync_selection(arm->edbo);
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_select_hierarchy(wmOperatorType *ot)
|
|
{
|
|
static 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");
|
|
}
|
|
|
|
/****************** Mirror Select ****************/
|
|
|
|
static int armature_select_mirror_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
bArmature *arm = obedit->data;
|
|
EditBone *ebone, *ebone_mirror_act = NULL;
|
|
const bool active_only = RNA_boolean_get(op->ptr, "only_active");
|
|
const bool extend = RNA_boolean_get(op->ptr, "extend");
|
|
|
|
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_bone_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_armature_sync_selection(arm->edbo);
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_select_mirror(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Mirror Select";
|
|
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");
|
|
}
|