Design Documents ---------------- * https://wiki.blender.org/index.php/Dev:2.8/Source/Layers * https://wiki.blender.org/index.php/Dev:2.8/Source/DataDesignRevised User Commit Log --------------- * New Layer and Collection system to replace render layers and viewport layers. * A layer is a set of collections of objects (and their drawing options) required for specific tasks. * A collection is a set of objects, equivalent of the old layers in Blender. A collection can be shared across multiple layers. * All Scenes have a master collection that all other collections are children of. * New collection "context" tab (in Properties Editor) * New temporary viewport "collections" panel to control per-collection visibility Missing User Features --------------------- * Collection "Filter" Option to add objects based on their names * Collection Manager operators The existing buttons are placeholders * Collection Manager drawing The editor main region is empty * Collection Override * Per-Collection engine settings This will come as a separate commit, as part of the clay-engine branch Dev Commit Log -------------- * New DNA file (DNA_layer_types.h) with the new structs We are replacing Base by a new extended Base while keeping it backward compatible with some legacy settings (i.e., lay, flag_legacy). Renamed all Base to BaseLegacy to make it clear the areas of code that still need to be converted Note: manual changes were required on - deg_builder_nodes.h, rna_object.c, KX_Light.cpp * Unittesting for main syncronization requirements - read, write, add/copy/remove objects, copy scene, collection link/unlinking, context) * New Editor: Collection Manager Based on patch by Julian Eisel This is extracted from the layer-manager branch. With the following changes: - Renamed references of layer manager to collections manager - I doesn't include the editors/space_collections/ draw and util files - The drawing code itself will be implemented separately by Julian * Base / Object: A little note about them. Original Blender code would try to keep them in sync through the code, juggling flags back and forth. This will now be handled by Depsgraph, keeping Object and Bases more separated throughout the non-rendering code. Scene.base is being cleared in doversion, and the old viewport drawing code was poorly converted to use the new bases while the new viewport code doesn't get merged and replace the old one. Python API Changes ------------------ ``` - scene.layers + # no longer exists - scene.objects + scene.scene_layers.active.objects - scene.objects.active + scene.render_layers.active.objects.active - bpy.context.scene.objects.link() + bpy.context.scene_collection.objects.link() - bpy_extras.object_utils.object_data_add(context, obdata, operator=None, use_active_layer=True, name=None) + bpy_extras.object_utils.object_data_add(context, obdata, operator=None, name=None) - bpy.context.object.select + bpy.context.object.select = True + bpy.context.object.select = False + bpy.context.object.select_get() + bpy.context.object.select_set(action='SELECT') + bpy.context.object.select_set(action='DESELECT') -AddObjectHelper.layers + # no longer exists ```
954 lines
26 KiB
C
954 lines
26 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 *****
|
|
*/
|
|
|
|
/** \file blender/editors/armature/pose_select.c
|
|
* \ingroup edarmature
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_armature_types.h"
|
|
#include "DNA_constraint_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
|
|
#include "BKE_action.h"
|
|
#include "BKE_armature.h"
|
|
#include "BKE_constraint.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_depsgraph.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_keyframing.h"
|
|
#include "ED_mesh.h"
|
|
#include "ED_object.h"
|
|
#include "ED_screen.h"
|
|
#include "ED_view3d.h"
|
|
|
|
#include "armature_intern.h"
|
|
|
|
/* utility macros for storing a temp int in the bone (selection flag) */
|
|
#define PBONE_PREV_FLAG_GET(pchan) ((void)0, (GET_INT_FROM_POINTER((pchan)->temp)))
|
|
#define PBONE_PREV_FLAG_SET(pchan, val) ((pchan)->temp = SET_INT_IN_POINTER(val))
|
|
|
|
|
|
/* ***************** Pose Select Utilities ********************* */
|
|
|
|
/* Note: SEL_TOGGLE is assumed to have already been handled! */
|
|
static void pose_do_bone_select(bPoseChannel *pchan, const int select_mode)
|
|
{
|
|
/* select pchan only if selectable, but deselect works always */
|
|
switch (select_mode) {
|
|
case SEL_SELECT:
|
|
if (!(pchan->bone->flag & BONE_UNSELECTABLE))
|
|
pchan->bone->flag |= BONE_SELECTED;
|
|
break;
|
|
case SEL_DESELECT:
|
|
pchan->bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
break;
|
|
case SEL_INVERT:
|
|
if (pchan->bone->flag & BONE_SELECTED) {
|
|
pchan->bone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
else if (!(pchan->bone->flag & BONE_UNSELECTABLE)) {
|
|
pchan->bone->flag |= BONE_SELECTED;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Utility method for changing the selection status of a bone */
|
|
void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select)
|
|
{
|
|
bArmature *arm;
|
|
|
|
/* sanity checks */
|
|
// XXX: actually, we can probably still get away with no object - at most we have no updates
|
|
if (ELEM(NULL, ob, ob->pose, pchan, pchan->bone))
|
|
return;
|
|
|
|
arm = ob->data;
|
|
|
|
/* can only change selection state if bone can be modified */
|
|
if (PBONE_SELECTABLE(arm, pchan->bone)) {
|
|
/* change selection state - activate too if selected */
|
|
if (select) {
|
|
pchan->bone->flag |= BONE_SELECTED;
|
|
arm->act_bone = pchan->bone;
|
|
}
|
|
else {
|
|
pchan->bone->flag &= ~BONE_SELECTED;
|
|
arm->act_bone = NULL;
|
|
}
|
|
|
|
// TODO: select and activate corresponding vgroup?
|
|
|
|
/* tag necessary depsgraph updates
|
|
* (see rna_Bone_select_update() in rna_armature.c for details)
|
|
*/
|
|
if (arm->flag & ARM_HAS_VIZ_DEPS) {
|
|
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
|
|
}
|
|
|
|
/* send necessary notifiers */
|
|
WM_main_add_notifier(NC_GEOM | ND_DATA, ob);
|
|
}
|
|
}
|
|
|
|
/* called from editview.c, for mode-less pose selection */
|
|
/* assumes scene obact and basact is still on old situation */
|
|
int ED_do_pose_selectbuffer(Scene *scene, BaseLegacy *base, unsigned int *buffer, short hits,
|
|
bool extend, bool deselect, bool toggle, bool do_nearest)
|
|
{
|
|
Object *ob = base->object;
|
|
Bone *nearBone;
|
|
|
|
if (!ob || !ob->pose) return 0;
|
|
|
|
nearBone = get_bone_from_selectbuffer(scene, base, buffer, hits, 1, do_nearest);
|
|
|
|
/* if the bone cannot be affected, don't do anything */
|
|
if ((nearBone) && !(nearBone->flag & BONE_UNSELECTABLE)) {
|
|
Object *ob_act = OBACT;
|
|
bArmature *arm = ob->data;
|
|
|
|
/* since we do unified select, we don't shift+select a bone if the
|
|
* armature object was not active yet.
|
|
* note, special exception for armature mode so we can do multi-select
|
|
* we could check for multi-select explicitly but think its fine to
|
|
* always give predictable behavior in weight paint mode - campbell */
|
|
if ((ob_act == NULL) || ((ob_act != ob) && (ob_act->mode & OB_MODE_WEIGHT_PAINT) == 0)) {
|
|
/* when we are entering into posemode via toggle-select,
|
|
* frop another active object - always select the bone. */
|
|
if (!extend && !deselect && toggle) {
|
|
/* re-select below */
|
|
nearBone->flag &= ~BONE_SELECTED;
|
|
}
|
|
}
|
|
|
|
if (!extend && !deselect && !toggle) {
|
|
ED_pose_de_selectall(ob, SEL_DESELECT, true);
|
|
nearBone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
arm->act_bone = nearBone;
|
|
}
|
|
else {
|
|
if (extend) {
|
|
nearBone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
arm->act_bone = nearBone;
|
|
}
|
|
else if (deselect) {
|
|
nearBone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
else if (toggle) {
|
|
if (nearBone->flag & BONE_SELECTED) {
|
|
/* if not active, we make it active */
|
|
if (nearBone != arm->act_bone) {
|
|
arm->act_bone = nearBone;
|
|
}
|
|
else {
|
|
nearBone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
}
|
|
}
|
|
else {
|
|
nearBone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
|
|
arm->act_bone = nearBone;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ob_act) {
|
|
/* in weightpaint we select the associated vertex group too */
|
|
if (ob_act->mode & OB_MODE_WEIGHT_PAINT) {
|
|
if (nearBone == arm->act_bone) {
|
|
ED_vgroup_select_by_name(ob_act, nearBone->name);
|
|
DAG_id_tag_update(&ob_act->id, OB_RECALC_DATA);
|
|
}
|
|
}
|
|
/* if there are some dependencies for visualizing armature state
|
|
* (e.g. Mask Modifier in 'Armature' mode), force update
|
|
*/
|
|
else if (arm->flag & ARM_HAS_VIZ_DEPS) {
|
|
/* NOTE: ob not ob_act here is intentional - it's the source of the
|
|
* bones being selected [T37247]
|
|
*/
|
|
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nearBone != NULL;
|
|
}
|
|
|
|
/* 'select_mode' is usual SEL_SELECT/SEL_DESELECT/SEL_TOGGLE/SEL_INVERT.
|
|
* When true, 'ignore_visibility' makes this func also affect invisible bones (hidden or on hidden layers). */
|
|
void ED_pose_de_selectall(Object *ob, int select_mode, const bool ignore_visibility)
|
|
{
|
|
bArmature *arm = ob->data;
|
|
bPoseChannel *pchan;
|
|
|
|
/* we call this from outliner too */
|
|
if (ob->pose == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Determine if we're selecting or deselecting */
|
|
if (select_mode == SEL_TOGGLE) {
|
|
select_mode = SEL_SELECT;
|
|
for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
|
|
if (ignore_visibility || PBONE_VISIBLE(arm, pchan->bone)) {
|
|
if (pchan->bone->flag & BONE_SELECTED) {
|
|
select_mode = SEL_DESELECT;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set the flags accordingly */
|
|
for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
|
|
/* ignore the pchan if it isn't visible or if its selection cannot be changed */
|
|
if (ignore_visibility || PBONE_VISIBLE(arm, pchan->bone)) {
|
|
pose_do_bone_select(pchan, select_mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ***************** Selections ********************** */
|
|
|
|
static void selectconnected_posebonechildren(Object *ob, Bone *bone, int extend)
|
|
{
|
|
Bone *curBone;
|
|
|
|
/* stop when unconnected child is encountered, or when unselectable bone is encountered */
|
|
if (!(bone->flag & BONE_CONNECTED) || (bone->flag & BONE_UNSELECTABLE))
|
|
return;
|
|
|
|
/* XXX old cruft! use notifiers instead */
|
|
//select_actionchannel_by_name (ob->action, bone->name, !(shift));
|
|
|
|
if (extend)
|
|
bone->flag &= ~BONE_SELECTED;
|
|
else
|
|
bone->flag |= BONE_SELECTED;
|
|
|
|
for (curBone = bone->childbase.first; curBone; curBone = curBone->next)
|
|
selectconnected_posebonechildren(ob, curBone, extend);
|
|
}
|
|
|
|
/* within active object context */
|
|
/* previously known as "selectconnected_posearmature" */
|
|
static int pose_select_connected_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
|
|
bArmature *arm = (bArmature *)ob->data;
|
|
Bone *bone, *curBone, *next = NULL;
|
|
const bool extend = RNA_boolean_get(op->ptr, "extend");
|
|
|
|
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) {
|
|
/* ignore bone if cannot be selected */
|
|
if ((curBone->flag & BONE_UNSELECTABLE) == 0) {
|
|
if (extend)
|
|
curBone->flag &= ~BONE_SELECTED;
|
|
else
|
|
curBone->flag |= BONE_SELECTED;
|
|
|
|
if (curBone->flag & BONE_CONNECTED)
|
|
next = curBone->parent;
|
|
else
|
|
next = NULL;
|
|
}
|
|
else
|
|
next = NULL;
|
|
}
|
|
|
|
/* Select children */
|
|
for (curBone = bone->childbase.first; curBone; curBone = next)
|
|
selectconnected_posebonechildren(ob, curBone, extend);
|
|
|
|
/* updates */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
|
|
if (arm->flag & ARM_HAS_VIZ_DEPS) {
|
|
/* mask modifier ('armature' mode), etc. */
|
|
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int pose_select_linked_poll(bContext *C)
|
|
{
|
|
return (ED_operator_view3d_active(C) && ED_operator_posemode(C));
|
|
}
|
|
|
|
void POSE_OT_select_linked(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Select Connected";
|
|
ot->idname = "POSE_OT_select_linked";
|
|
ot->description = "Select bones related to selected ones by parent/child relationships";
|
|
|
|
/* api callbacks */
|
|
/* leave 'exec' unset */
|
|
ot->invoke = pose_select_connected_invoke;
|
|
ot->poll = pose_select_linked_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* props */
|
|
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection instead of deselecting everything first");
|
|
}
|
|
|
|
/* -------------------------------------- */
|
|
|
|
static int pose_de_select_all_exec(bContext *C, wmOperator *op)
|
|
{
|
|
int action = RNA_enum_get(op->ptr, "action");
|
|
|
|
Scene *scene = CTX_data_scene(C);
|
|
Object *ob = ED_object_context(C);
|
|
bArmature *arm = ob->data;
|
|
int multipaint = scene->toolsettings->multipaint;
|
|
|
|
if (action == SEL_TOGGLE) {
|
|
action = CTX_DATA_COUNT(C, selected_pose_bones) ? SEL_DESELECT : SEL_SELECT;
|
|
}
|
|
|
|
/* Set the flags */
|
|
CTX_DATA_BEGIN(C, bPoseChannel *, pchan, visible_pose_bones)
|
|
{
|
|
pose_do_bone_select(pchan, action);
|
|
}
|
|
CTX_DATA_END;
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, NULL);
|
|
|
|
/* weightpaint or mask modifiers need depsgraph updates */
|
|
if (multipaint || (arm->flag & ARM_HAS_VIZ_DEPS)) {
|
|
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void POSE_OT_select_all(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "(De)select All";
|
|
ot->idname = "POSE_OT_select_all";
|
|
ot->description = "Toggle selection status of all bones";
|
|
|
|
/* api callbacks */
|
|
ot->exec = pose_de_select_all_exec;
|
|
ot->poll = ED_operator_posemode;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
WM_operator_properties_select_all(ot);
|
|
}
|
|
|
|
/* -------------------------------------- */
|
|
|
|
static int pose_select_parent_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
|
|
bArmature *arm = (bArmature *)ob->data;
|
|
bPoseChannel *pchan, *parent;
|
|
|
|
/* Determine if there is an active bone */
|
|
pchan = CTX_data_active_pose_bone(C);
|
|
if (pchan) {
|
|
parent = pchan->parent;
|
|
if ((parent) && !(parent->bone->flag & (BONE_HIDDEN_P | BONE_UNSELECTABLE))) {
|
|
parent->bone->flag |= BONE_SELECTED;
|
|
arm->act_bone = parent->bone;
|
|
}
|
|
else {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
else {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* updates */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
|
|
if (arm->flag & ARM_HAS_VIZ_DEPS) {
|
|
/* mask modifier ('armature' mode), etc. */
|
|
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void POSE_OT_select_parent(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Select Parent Bone";
|
|
ot->idname = "POSE_OT_select_parent";
|
|
ot->description = "Select bones that are parents of the currently selected bones";
|
|
|
|
/* api callbacks */
|
|
ot->exec = pose_select_parent_exec;
|
|
ot->poll = ED_operator_posemode;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/* -------------------------------------- */
|
|
|
|
static int pose_select_constraint_target_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
|
|
bArmature *arm = (bArmature *)ob->data;
|
|
bConstraint *con;
|
|
int found = 0;
|
|
|
|
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones)
|
|
{
|
|
if (pchan->bone->flag & BONE_SELECTED) {
|
|
for (con = pchan->constraints.first; con; con = con->next) {
|
|
const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
|
|
ListBase targets = {NULL, NULL};
|
|
bConstraintTarget *ct;
|
|
|
|
if (cti && cti->get_constraint_targets) {
|
|
cti->get_constraint_targets(con, &targets);
|
|
|
|
for (ct = targets.first; ct; ct = ct->next) {
|
|
if ((ct->tar == ob) && (ct->subtarget[0])) {
|
|
bPoseChannel *pchanc = BKE_pose_channel_find_name(ob->pose, ct->subtarget);
|
|
if ((pchanc) && !(pchanc->bone->flag & BONE_UNSELECTABLE)) {
|
|
pchanc->bone->flag |= BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL;
|
|
found = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cti->flush_constraint_targets)
|
|
cti->flush_constraint_targets(con, &targets, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CTX_DATA_END;
|
|
|
|
if (!found)
|
|
return OPERATOR_CANCELLED;
|
|
|
|
/* updates */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
|
|
if (arm->flag & ARM_HAS_VIZ_DEPS) {
|
|
/* mask modifier ('armature' mode), etc. */
|
|
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void POSE_OT_select_constraint_target(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Select Constraint Target";
|
|
ot->idname = "POSE_OT_select_constraint_target";
|
|
ot->description = "Select bones used as targets for the currently selected bones";
|
|
|
|
/* api callbacks */
|
|
ot->exec = pose_select_constraint_target_exec;
|
|
ot->poll = ED_operator_posemode;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/* -------------------------------------- */
|
|
|
|
static int pose_select_hierarchy_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
|
|
bArmature *arm = ob->data;
|
|
bPoseChannel *pchan_act;
|
|
int direction = RNA_enum_get(op->ptr, "direction");
|
|
const bool add_to_sel = RNA_boolean_get(op->ptr, "extend");
|
|
bool changed = false;
|
|
|
|
pchan_act = BKE_pose_channel_active(ob);
|
|
if (pchan_act == NULL) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (direction == BONE_SELECT_PARENT) {
|
|
if (pchan_act->parent) {
|
|
Bone *bone_parent;
|
|
bone_parent = pchan_act->parent->bone;
|
|
|
|
if (PBONE_SELECTABLE(arm, bone_parent)) {
|
|
if (!add_to_sel) {
|
|
pchan_act->bone->flag &= ~BONE_SELECTED;
|
|
}
|
|
bone_parent->flag |= BONE_SELECTED;
|
|
arm->act_bone = bone_parent;
|
|
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
else { /* direction == BONE_SELECT_CHILD */
|
|
bPoseChannel *pchan_iter;
|
|
Bone *bone_child = NULL;
|
|
int pass;
|
|
|
|
/* first pass, only connected bones (the logical direct child) */
|
|
for (pass = 0; pass < 2 && (bone_child == NULL); pass++) {
|
|
for (pchan_iter = ob->pose->chanbase.first; pchan_iter; pchan_iter = pchan_iter->next) {
|
|
/* possible we have multiple children, some invisible */
|
|
if (PBONE_SELECTABLE(arm, pchan_iter->bone)) {
|
|
if (pchan_iter->parent == pchan_act) {
|
|
if ((pass == 1) || (pchan_iter->bone->flag & BONE_CONNECTED)) {
|
|
bone_child = pchan_iter->bone;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bone_child) {
|
|
arm->act_bone = bone_child;
|
|
|
|
if (!add_to_sel) {
|
|
pchan_act->bone->flag &= ~BONE_SELECTED;
|
|
}
|
|
bone_child->flag |= BONE_SELECTED;
|
|
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed == false) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* updates */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
|
|
if (arm->flag & ARM_HAS_VIZ_DEPS) {
|
|
/* mask modifier ('armature' mode), etc. */
|
|
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void POSE_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 = "POSE_OT_select_hierarchy";
|
|
ot->description = "Select immediate parent/children of selected bones";
|
|
|
|
/* api callbacks */
|
|
ot->exec = pose_select_hierarchy_exec;
|
|
ot->poll = ED_operator_posemode;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* props */
|
|
ot->prop = RNA_def_enum(ot->srna, "direction", direction_items, BONE_SELECT_PARENT, "Direction", "");
|
|
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
|
|
}
|
|
|
|
/* -------------------------------------- */
|
|
|
|
/* modes for select same */
|
|
typedef enum ePose_SelectSame_Mode {
|
|
POSE_SEL_SAME_LAYER = 0,
|
|
POSE_SEL_SAME_GROUP = 1,
|
|
POSE_SEL_SAME_KEYINGSET = 2,
|
|
} ePose_SelectSame_Mode;
|
|
|
|
static bool pose_select_same_group(bContext *C, Object *ob, bool extend)
|
|
{
|
|
bArmature *arm = (ob) ? ob->data : NULL;
|
|
bPose *pose = (ob) ? ob->pose : NULL;
|
|
char *group_flags;
|
|
int numGroups = 0;
|
|
bool changed = false, tagged = false;
|
|
|
|
/* sanity checks */
|
|
if (ELEM(NULL, ob, pose, arm))
|
|
return 0;
|
|
|
|
/* count the number of groups */
|
|
numGroups = BLI_listbase_count(&pose->agroups);
|
|
if (numGroups == 0)
|
|
return 0;
|
|
|
|
/* alloc a small array to keep track of the groups to use
|
|
* - each cell stores on/off state for whether group should be used
|
|
* - size is (numGroups + 1), since (index = 0) is used for no-group
|
|
*/
|
|
group_flags = MEM_callocN(numGroups + 1, "pose_select_same_group");
|
|
|
|
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones)
|
|
{
|
|
/* keep track of group as group to use later? */
|
|
if (pchan->bone->flag & BONE_SELECTED) {
|
|
group_flags[pchan->agrp_index] = 1;
|
|
tagged = true;
|
|
}
|
|
|
|
/* deselect all bones before selecting new ones? */
|
|
if ((extend == false) && (pchan->bone->flag & BONE_UNSELECTABLE) == 0)
|
|
pchan->bone->flag &= ~BONE_SELECTED;
|
|
}
|
|
CTX_DATA_END;
|
|
|
|
/* small optimization: only loop through bones a second time if there are any groups tagged */
|
|
if (tagged) {
|
|
/* only if group matches (and is not selected or current bone) */
|
|
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones)
|
|
{
|
|
if ((pchan->bone->flag & BONE_UNSELECTABLE) == 0) {
|
|
/* check if the group used by this bone is counted */
|
|
if (group_flags[pchan->agrp_index]) {
|
|
pchan->bone->flag |= BONE_SELECTED;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
CTX_DATA_END;
|
|
}
|
|
|
|
/* free temp info */
|
|
MEM_freeN(group_flags);
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool pose_select_same_layer(bContext *C, Object *ob, bool extend)
|
|
{
|
|
bPose *pose = (ob) ? ob->pose : NULL;
|
|
bArmature *arm = (ob) ? ob->data : NULL;
|
|
bool changed = false;
|
|
int layers = 0;
|
|
|
|
if (ELEM(NULL, ob, pose, arm))
|
|
return 0;
|
|
|
|
/* figure out what bones are selected */
|
|
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones)
|
|
{
|
|
/* keep track of layers to use later? */
|
|
if (pchan->bone->flag & BONE_SELECTED)
|
|
layers |= pchan->bone->layer;
|
|
|
|
/* deselect all bones before selecting new ones? */
|
|
if ((extend == false) && (pchan->bone->flag & BONE_UNSELECTABLE) == 0)
|
|
pchan->bone->flag &= ~BONE_SELECTED;
|
|
}
|
|
CTX_DATA_END;
|
|
if (layers == 0)
|
|
return 0;
|
|
|
|
/* select bones that are on same layers as layers flag */
|
|
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones)
|
|
{
|
|
/* if bone is on a suitable layer, and the bone can have its selection changed, select it */
|
|
if ((layers & pchan->bone->layer) && (pchan->bone->flag & BONE_UNSELECTABLE) == 0) {
|
|
pchan->bone->flag |= BONE_SELECTED;
|
|
changed = true;
|
|
}
|
|
}
|
|
CTX_DATA_END;
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool pose_select_same_keyingset(bContext *C, ReportList *reports, Object *ob, bool extend)
|
|
{
|
|
KeyingSet *ks = ANIM_scene_get_active_keyingset(CTX_data_scene(C));
|
|
KS_Path *ksp;
|
|
|
|
bArmature *arm = (ob) ? ob->data : NULL;
|
|
bPose *pose = (ob) ? ob->pose : NULL;
|
|
bool changed = false;
|
|
|
|
/* sanity checks: validate Keying Set and object */
|
|
if (ks == NULL) {
|
|
BKE_report(reports, RPT_ERROR, "No active Keying Set to use");
|
|
return false;
|
|
}
|
|
else if (ANIM_validate_keyingset(C, NULL, ks) != 0) {
|
|
if (ks->paths.first == NULL) {
|
|
if ((ks->flag & KEYINGSET_ABSOLUTE) == 0) {
|
|
BKE_report(reports, RPT_ERROR,
|
|
"Use another Keying Set, as the active one depends on the currently "
|
|
"selected items or cannot find any targets due to unsuitable context");
|
|
}
|
|
else {
|
|
BKE_report(reports, RPT_ERROR, "Keying Set does not contain any paths");
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (ELEM(NULL, ob, pose, arm))
|
|
return false;
|
|
|
|
/* if not extending selection, deselect all selected first */
|
|
if (extend == false) {
|
|
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, visible_pose_bones)
|
|
{
|
|
if ((pchan->bone->flag & BONE_UNSELECTABLE) == 0)
|
|
pchan->bone->flag &= ~BONE_SELECTED;
|
|
}
|
|
CTX_DATA_END;
|
|
}
|
|
|
|
/* iterate over elements in the Keying Set, setting selection depending on whether
|
|
* that bone is visible or not...
|
|
*/
|
|
for (ksp = ks->paths.first; ksp; ksp = ksp->next) {
|
|
/* only items related to this object will be relevant */
|
|
if ((ksp->id == &ob->id) && (ksp->rna_path != NULL)) {
|
|
if (strstr(ksp->rna_path, "bones")) {
|
|
char *boneName = BLI_str_quoted_substrN(ksp->rna_path, "bones[");
|
|
|
|
if (boneName) {
|
|
bPoseChannel *pchan = BKE_pose_channel_find_name(pose, boneName);
|
|
|
|
if (pchan) {
|
|
/* select if bone is visible and can be affected */
|
|
if (PBONE_SELECTABLE(arm, pchan->bone)) {
|
|
pchan->bone->flag |= BONE_SELECTED;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
/* free temp memory */
|
|
MEM_freeN(boneName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static int pose_select_grouped_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
|
|
bArmature *arm = (bArmature *)ob->data;
|
|
const ePose_SelectSame_Mode type = RNA_enum_get(op->ptr, "type");
|
|
const bool extend = RNA_boolean_get(op->ptr, "extend");
|
|
bool changed = false;
|
|
|
|
/* sanity check */
|
|
if (ob->pose == NULL)
|
|
return OPERATOR_CANCELLED;
|
|
|
|
/* selection types */
|
|
switch (type) {
|
|
case POSE_SEL_SAME_LAYER: /* layer */
|
|
changed = pose_select_same_layer(C, ob, extend);
|
|
break;
|
|
|
|
case POSE_SEL_SAME_GROUP: /* group */
|
|
changed = pose_select_same_group(C, ob, extend);
|
|
break;
|
|
|
|
case POSE_SEL_SAME_KEYINGSET: /* Keying Set */
|
|
changed = pose_select_same_keyingset(C, op->reports, ob, extend);
|
|
break;
|
|
|
|
default:
|
|
printf("pose_select_grouped() - Unknown selection type %u\n", type);
|
|
break;
|
|
}
|
|
|
|
/* notifiers for updates */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
|
|
|
|
if (arm->flag & ARM_HAS_VIZ_DEPS) {
|
|
/* mask modifier ('armature' mode), etc. */
|
|
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
|
|
}
|
|
|
|
/* report done status */
|
|
if (changed)
|
|
return OPERATOR_FINISHED;
|
|
else
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void POSE_OT_select_grouped(wmOperatorType *ot)
|
|
{
|
|
static EnumPropertyItem prop_select_grouped_types[] = {
|
|
{POSE_SEL_SAME_LAYER, "LAYER", 0, "Layer", "Shared layers"},
|
|
{POSE_SEL_SAME_GROUP, "GROUP", 0, "Group", "Shared group"},
|
|
{POSE_SEL_SAME_KEYINGSET, "KEYINGSET", 0, "Keying Set", "All bones affected by active Keying Set"},
|
|
{0, NULL, 0, NULL, NULL}
|
|
};
|
|
|
|
/* identifiers */
|
|
ot->name = "Select Grouped";
|
|
ot->description = "Select all visible bones grouped by similar properties";
|
|
ot->idname = "POSE_OT_select_grouped";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = WM_menu_invoke;
|
|
ot->exec = pose_select_grouped_exec;
|
|
ot->poll = ED_operator_posemode;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* properties */
|
|
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection instead of deselecting everything first");
|
|
ot->prop = RNA_def_enum(ot->srna, "type", prop_select_grouped_types, 0, "Type", "");
|
|
}
|
|
|
|
/* -------------------------------------- */
|
|
|
|
/**
|
|
* \note clone of #armature_select_mirror_exec keep in sync
|
|
*/
|
|
static int pose_select_mirror_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob_act = CTX_data_active_object(C);
|
|
Object *ob = BKE_object_pose_armature_get(ob_act);
|
|
bArmature *arm;
|
|
bPoseChannel *pchan, *pchan_mirror_act = NULL;
|
|
const bool active_only = RNA_boolean_get(op->ptr, "only_active");
|
|
const bool extend = RNA_boolean_get(op->ptr, "extend");
|
|
|
|
if ((ob && (ob->mode & OB_MODE_POSE)) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
arm = ob->data;
|
|
|
|
for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
|
|
const int flag = (pchan->bone->flag & BONE_SELECTED);
|
|
PBONE_PREV_FLAG_SET(pchan, flag);
|
|
}
|
|
|
|
for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
|
|
if (PBONE_SELECTABLE(arm, pchan->bone)) {
|
|
bPoseChannel *pchan_mirror;
|
|
int flag_new = extend ? PBONE_PREV_FLAG_GET(pchan) : 0;
|
|
|
|
if ((pchan_mirror = BKE_pose_channel_get_mirrored(ob->pose, pchan->name)) &&
|
|
(PBONE_VISIBLE(arm, pchan_mirror->bone)))
|
|
{
|
|
const int flag_mirror = PBONE_PREV_FLAG_GET(pchan_mirror);
|
|
flag_new |= flag_mirror;
|
|
|
|
if (pchan->bone == arm->act_bone) {
|
|
pchan_mirror_act = pchan_mirror;
|
|
}
|
|
|
|
/* skip all but the active or its mirror */
|
|
if (active_only && !ELEM(arm->act_bone, pchan->bone, pchan_mirror->bone)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pchan->bone->flag = (pchan->bone->flag & ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL)) | flag_new;
|
|
}
|
|
}
|
|
|
|
if (pchan_mirror_act) {
|
|
arm->act_bone = pchan_mirror_act->bone;
|
|
|
|
/* in weightpaint we select the associated vertex group too */
|
|
if (ob_act->mode & OB_MODE_WEIGHT_PAINT) {
|
|
ED_vgroup_select_by_name(ob_act, pchan_mirror_act->name);
|
|
DAG_id_tag_update(&ob_act->id, OB_RECALC_DATA);
|
|
}
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void POSE_OT_select_mirror(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Flip Active/Selected Bone";
|
|
ot->idname = "POSE_OT_select_mirror";
|
|
ot->description = "Mirror the bone selection";
|
|
|
|
/* api callbacks */
|
|
ot->exec = pose_select_mirror_exec;
|
|
ot->poll = ED_operator_posemode;
|
|
|
|
/* 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");
|
|
}
|
|
|