Allow select range in animation editor #104565

Merged
Pratik Borhade merged 19 commits from PratikPB2123/blender:T103855-anim-select-extend into main 2023-05-05 17:46:14 +02:00
7 changed files with 223 additions and 29 deletions

View File

@ -3463,7 +3463,9 @@ def km_animation_channels(params):
items.extend([
# Click select.
("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True},
{"properties": [("extend_range", True)]}),
("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True},
{"properties": [("extend", True)]}),
("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True, "ctrl": True},
{"properties": [("children_only", True)]}),

View File

@ -214,7 +214,64 @@ void ANIM_set_active_channel(bAnimContext *ac,
ANIM_animdata_freelist(&anim_data);
}
static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp, bAnimListElem *ale)
bool ANIM_is_active_channel(bAnimListElem *ale)

Document what the change_active parameter does. Booleans are notoriously tricky to understand.

Document what the `change_active` parameter does. Booleans are notoriously tricky to understand.
{
switch (ale->type) {
dr.sybren marked this conversation as resolved

You don't need this bool variable. Since it's only set once, never changes, and then is used to return a value, you can replace all the is_active_found = ... with return ....

You don't need this `bool` variable. Since it's only set once, never changes, and then is used to return a value, you can replace all the `is_active_found = ...` with `return ...`.
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
case ANIMTYPE_DSCACHEFILE:
case ANIMTYPE_DSCUR:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR:
case ANIMTYPE_DSPART:
case ANIMTYPE_DSMBALL:
case ANIMTYPE_DSARM:
case ANIMTYPE_DSMESH:
case ANIMTYPE_DSNTREE:
case ANIMTYPE_DSTEX:
case ANIMTYPE_DSLAT:
case ANIMTYPE_DSLINESTYLE:
case ANIMTYPE_DSSPK:
case ANIMTYPE_DSGPENCIL:
case ANIMTYPE_DSMCLIP:
case ANIMTYPE_DSHAIR:
case ANIMTYPE_DSPOINTCLOUD:
case ANIMTYPE_DSVOLUME:
case ANIMTYPE_NLAACTION:
case ANIMTYPE_DSSIMULATION: {
if (ale->adt) {
return ale->adt->flag & ADT_UI_ACTIVE;
}
}
case ANIMTYPE_GROUP: {
bActionGroup *argp = (bActionGroup *)ale->data;
return argp->flag & AGRP_ACTIVE;
}
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE: {
FCurve *fcu = (FCurve *)ale->data;
return fcu->flag & FCURVE_ACTIVE;
}
case ANIMTYPE_GPLAYER: {
bGPDlayer *gpl = (bGPDlayer *)ale->data;
return gpl->flag & GP_LAYER_ACTIVE;
}
/* These channel types do not have active flags. */

This return is still in the wrong place...

This `return` is still in the wrong place...

👍 now it's instantly clear that these were not forgotten.

👍 now it's instantly clear that these were not forgotten.
case ANIMTYPE_MASKLAYER:
case ANIMTYPE_SHAPEKEY:
break;
}
return false;

This line is never reached, and the function has no return statement for when there is no matching case.

Just end the function with return false;

This line is never reached, and the function has no `return` statement for when there is no matching `case`. Just end the function with `return false;`
}
/* change_active determines whether to change the active bone of the armature when selecting pose
* channels. It is false during range selection otherwise true. */
static void select_pchan_for_action_group(bAnimContext *ac,
bActionGroup *agrp,
bAnimListElem *ale,
const bool change_active)
{
/* Armatures-Specific Feature:
* See mouse_anim_channels() -> ANIMTYPE_GROUP case for more details (#38737)
@ -230,11 +287,12 @@ static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp,
* TODO: check the first F-Curve or so to be sure...
*/
bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, agrp->name);
if (agrp->flag & AGRP_SELECTED) {
ED_pose_bone_select(ob, pchan, true);
ED_pose_bone_select(ob, pchan, true, change_active);
}
else {
ED_pose_bone_select(ob, pchan, false);
ED_pose_bone_select(ob, pchan, false, change_active);
}
}
}
@ -342,10 +400,15 @@ static void anim_channels_select_set(bAnimContext *ac,
eAnimChannels_SetFlag sel)
{
bAnimListElem *ale;
/* Boolean to keep active channel status during range selection. */
const bool change_active = (sel != ACHANNEL_SETFLAG_EXTEND_RANGE);
for (ale = anim_data.first; ale; ale = ale->next) {
switch (ale->type) {
case ANIMTYPE_SCENE: {
if (change_active) {
break;
}
Scene *scene = (Scene *)ale->data;
ACHANNEL_SET_FLAG(scene, sel, SCE_DS_SELECTED);
@ -372,8 +435,10 @@ static void anim_channels_select_set(bAnimContext *ac,
case ANIMTYPE_GROUP: {
bActionGroup *agrp = (bActionGroup *)ale->data;
ACHANNEL_SET_FLAG(agrp, sel, AGRP_SELECTED);
select_pchan_for_action_group(ac, agrp, ale);
agrp->flag &= ~AGRP_ACTIVE;
select_pchan_for_action_group(ac, agrp, ale, change_active);
if (change_active) {
agrp->flag &= ~AGRP_ACTIVE;
}
break;
}
case ANIMTYPE_FCURVE:
@ -381,7 +446,7 @@ static void anim_channels_select_set(bAnimContext *ac,
FCurve *fcu = (FCurve *)ale->data;
ACHANNEL_SET_FLAG(fcu, sel, FCURVE_SELECTED);
if ((fcu->flag & FCURVE_SELECTED) == 0) {
if (!(fcu->flag & FCURVE_SELECTED) && change_active) {
/* Only erase the ACTIVE flag when deselecting. This ensures that "select all curves"
* retains the currently active curve. */
fcu->flag &= ~FCURVE_ACTIVE;
@ -428,7 +493,9 @@ static void anim_channels_select_set(bAnimContext *ac,
/* need to verify that this data is valid for now */
if (ale->adt) {
ACHANNEL_SET_FLAG(ale->adt, sel, ADT_UI_SELECTED);
ale->adt->flag &= ~ADT_UI_ACTIVE;
if (change_active) {
ale->adt->flag &= ~ADT_UI_ACTIVE;
}
}
break;
}
@ -2747,7 +2814,7 @@ static void box_select_anim_channels(bAnimContext *ac, rcti *rect, short selectm
switch (ale->type) {
case ANIMTYPE_GROUP: {
bActionGroup *agrp = (bActionGroup *)ale->data;
select_pchan_for_action_group(ac, agrp, ale);
select_pchan_for_action_group(ac, agrp, ale, true);
/* always clear active flag after doing this */
agrp->flag &= ~AGRP_ACTIVE;
break;
@ -3027,6 +3094,70 @@ static int click_select_channel_scene(bAnimListElem *ale,
return (ND_ANIMCHAN | NA_SELECTED);
}
/* Return whether active channel of given type is present. */
static bool animchannel_has_active_of_type(bAnimContext *ac, const eAnim_ChannelType type)
{
ListBase anim_data = anim_channels_for_selection(ac);
bool is_active_found = false;
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
if (ale->type != type) {
continue;
}
is_active_found = ANIM_is_active_channel(ale);
if (is_active_found) {
break;

This function is now very similar to anim_channels_select_set(), except that that one also handles some other types, for example ANIMTYPE_SCENE and ANIMTYPE_NLATRACK.

If there is still need for this function, document well what the differences are, and why these differences are relevant.

Also the switch should have a case for all items in eAnim_ChannelType. Without that, a compiler can complain that there are missing items (which is a good mechanic to detect issues when a new enum item is added, so silencing the warning with a default: case is usually not recommended).

This function is now very similar to `anim_channels_select_set()`, except that that one also handles some other types, for example `ANIMTYPE_SCENE` and `ANIMTYPE_NLATRACK`. If there is still need for this function, document well what the differences are, and why these differences are relevant. Also the `switch` should have a `case` for all items in `eAnim_ChannelType`. Without that, a compiler can complain that there are missing items (which is a good mechanic to detect issues when a new enum item is added, so silencing the warning with a `default:` case is usually not recommended).

This function is now very similar to anim_channels_select_set(), except that that one also handles some other types, for example ANIMTYPE_SCENE and ANIMTYPE_NLATRACK

Right. I mentioned this earlier about replacing animchannel_clear_selection with the existing function: #104565 (comment) :)

> This function is now very similar to anim_channels_select_set(), except that that one also handles some other types, for example ANIMTYPE_SCENE and ANIMTYPE_NLATRACK Right. I mentioned this earlier about replacing `animchannel_clear_selection` with the existing function: https://projects.blender.org/blender/blender/pulls/104565#issuecomment-886697 :)

Also the switch should have a case for all items in eAnim_ChannelType

make sense to add switch case for other channels when clearing selection flag.
But do you agree about replacing clear_selection function with anim_channels_select_set?

> Also the switch should have a case for all items in eAnim_ChannelType make sense to add switch case for other channels when clearing selection flag. But do you agree about replacing clear_selection function with `anim_channels_select_set`?

Yeah, for sure! 👍

Yeah, for sure! :+1:

Thanks, replaced it with the existing function (with minor changes).

Thanks, replaced it with the existing function (with minor changes).
}
}
ANIM_animdata_freelist(&anim_data);
return is_active_found;
}
/* Select channels that lies between active channel and cursor_elem. */
static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_elem)
{
ListBase anim_data = anim_channels_for_selection(ac);
bool in_selection_range = false;
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
/* Allow selection when active channel and `cursor_elem` are of same type. */
if (ale->type != cursor_elem->type) {
continue;
}
const bool is_cursor_elem = (ale->data == cursor_elem->data);
const bool is_active_elem = ANIM_is_active_channel(ale);
/* Restrict selection when active element is not found and group-channels are excluded from the
* selection. */
if (is_active_elem || is_cursor_elem) {
/* Select first and last element from the range. Reverse selection status on extremes. */
ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, ACHANNEL_SETFLAG_ADD);
in_selection_range = !in_selection_range;
if (ale->type == ANIMTYPE_GROUP) {
select_pchan_for_action_group(ac, (bActionGroup *)ale->data, ale, false);
}
}
else if (in_selection_range) {
/* Select elements between the range. */
ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, ACHANNEL_SETFLAG_ADD);
if (ale->type == ANIMTYPE_GROUP) {
select_pchan_for_action_group(ac, (bActionGroup *)ale->data, ale, false);
}
}
if (is_active_elem && is_cursor_elem) {
/* Selection range is only one element when active channel and clicked channel are same. So
* exit out of the loop when this condition is hit. */
break;
}
}
dr.sybren marked this conversation as resolved

I think the "last clicked channel" is actually the cursor_elem parameter? If so, don't reference UI interaction here, as it's irrelevant to the functionality of this function how that cursor_elem was passed.

As an illustration of possible use it's fine to mention this, but then it should be in a second sentence and not the primary definition of what the code does.

I think the "last clicked channel" is actually the `cursor_elem` parameter? If so, don't reference UI interaction here, as it's irrelevant to the functionality of this function how that `cursor_elem` was passed. As an illustration of possible use it's fine to mention this, but then it should be in a second sentence and not the primary definition of what the code does.
ANIM_animdata_freelist(&anim_data);
}
static int click_select_channel_object(bContext *C,
bAnimContext *ac,
bAnimListElem *ale,
@ -3050,8 +3181,13 @@ static int click_select_channel_object(bContext *C,
adt->flag ^= ADT_UI_SELECTED;
}

is_active_elem isn't used before this point, so just declare it as const bool is_active_elem = ANIM_is_active_channel(ale);

`is_active_elem` isn't used before this point, so just declare it as `const bool is_active_elem = ANIM_is_active_channel(ale);`
}
else if (selectmode == SELECT_EXTEND_RANGE) {
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE);
animchannel_select_range(ac, ale);
}
else {
/* deselect all */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
BKE_view_layer_synced_ensure(scene, view_layer);
/* TODO: should this deselect all other types of channels too? */
LISTBASE_FOREACH (Base *, b, BKE_view_layer_object_bases_get(view_layer)) {
@ -3074,7 +3210,8 @@ static int click_select_channel_object(bContext *C,
* to avoid getting stuck there, see: #48747. */
ED_object_base_activate_with_mode_exit_if_needed(C, base); /* adds notifier */
dr.sybren marked this conversation as resolved Outdated

I think selected is a bit of a confusing name. The fact that something was selected or not is secondary; this variable indicates whether the loop is inside the to-be-selected range. in_selection_range would be better.

I think `selected` is a bit of a confusing name. The fact that something was selected or not is secondary; this variable indicates whether the loop is inside the to-be-selected range. `in_selection_range` would be better.
if ((adt) && (adt->flag & ADT_UI_SELECTED)) {
/* Similar to outliner, do not change active element when selecting elements in range.*/
if ((adt) && (adt->flag & ADT_UI_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) {
adt->flag |= ADT_UI_ACTIVE;
dr.sybren marked this conversation as resolved Outdated

Same question as above: why only operate on FCurves?

Same question as above: why only operate on FCurves?
}
@ -3094,14 +3231,18 @@ static int click_select_channel_dummy(bAnimContext *ac,
/* inverse selection status of this AnimData block only */
ale->adt->flag ^= ADT_UI_SELECTED;
}
else if (selectmode == SELECT_EXTEND_RANGE) {
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE);
animchannel_select_range(ac, ale);
}
else {
/* select AnimData block by itself */
PratikPB2123 marked this conversation as resolved Outdated

No break outside the braces, see the style guide.

No `break` outside the braces, see [the style guide](https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Switch_Statement).
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
ale->adt->flag |= ADT_UI_SELECTED;
}
/* set active? */
if (ale->adt->flag & ADT_UI_SELECTED) {
/* Similar to outliner, do not change active element when selecting elements in range. */
if ((ale->adt->flag & ADT_UI_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) {
ale->adt->flag |= ADT_UI_ACTIVE;
}
@ -3147,6 +3288,10 @@ static int click_select_channel_group(bAnimContext *ac,
/* inverse selection status of this group only */
agrp->flag ^= AGRP_SELECTED;
}
else if (selectmode == SELECT_EXTEND_RANGE) {
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE);
animchannel_select_range(ac, ale);
}
else if (selectmode == -1) {
/* select all in group (and deselect everything else) */
FCurve *fcu;
@ -3173,17 +3318,22 @@ static int click_select_channel_group(bAnimContext *ac,
agrp->flag |= AGRP_SELECTED;
}
/* if group is selected now, make group the 'active' one in the visible list */
/* if group is selected now, make group the 'active' one in the visible list.
* Similar to outliner, do not change active element when selecting elements in range. */
if (agrp->flag & AGRP_SELECTED) {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, agrp, ANIMTYPE_GROUP);
if (pchan) {
ED_pose_bone_select(ob, pchan, true);
if (selectmode != SELECT_EXTEND_RANGE) {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, agrp, ANIMTYPE_GROUP);
if (pchan) {
ED_pose_bone_select(ob, pchan, true, true);
}
}
}
else {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, NULL, ANIMTYPE_GROUP);
if (pchan) {
ED_pose_bone_select(ob, pchan, false);
if (selectmode != SELECT_EXTEND_RANGE) {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, NULL, ANIMTYPE_GROUP);
if (pchan) {
ED_pose_bone_select(ob, pchan, false, true);
}
}
}
@ -3202,14 +3352,19 @@ static int click_select_channel_fcurve(bAnimContext *ac,
/* inverse selection status of this F-Curve only */
fcu->flag ^= FCURVE_SELECTED;
}
else if (selectmode == SELECT_EXTEND_RANGE) {
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE);
animchannel_select_range(ac, ale);
}
else {
/* select F-Curve by itself */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
fcu->flag |= FCURVE_SELECTED;
}

Same questions/remarks as above.

Same questions/remarks as above.
/* if F-Curve is selected now, make F-Curve the 'active' one in the visible list */
if (fcu->flag & FCURVE_SELECTED) {
/* if F-Curve is selected now, make F-Curve the 'active' one in the visible list.
* Similar to outliner, do not change active element when selecting elements in range. */
if ((fcu->flag & FCURVE_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, fcu, ale->type);
}
@ -3277,14 +3432,19 @@ static int click_select_channel_gplayer(bContext *C,
/* invert selection status of this layer only */
gpl->flag ^= GP_LAYER_SELECT;
}
else if (selectmode == SELECT_EXTEND_RANGE) {
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE);
animchannel_select_range(ac, ale);
}
else {
/* select layer by itself */
ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR);
gpl->flag |= GP_LAYER_SELECT;
}
/* change active layer, if this is selected (since we must always have an active layer) */
if (gpl->flag & GP_LAYER_SELECT) {
/* change active layer, if this is selected (since we must always have an active layer).
* Similar to outliner, do not change active element when selecting elements in range. */
if ((gpl->flag & GP_LAYER_SELECT) && (selectmode != SELECT_EXTEND_RANGE)) {
ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, gpl, ANIMTYPE_GPLAYER);
/* update other layer status */
BKE_gpencil_layer_active_set(gpd, gpl);
@ -3333,7 +3493,7 @@ static int click_select_channel_masklayer(bAnimContext *ac,
static int mouse_anim_channels(bContext *C,
bAnimContext *ac,
const int channel_index,
const short /* eEditKeyframes_Select or -1 */ selectmode)
short /* eEditKeyframes_Select or -1 */ selectmode)
{
ListBase anim_data = {NULL, NULL};
bAnimListElem *ale;
@ -3371,6 +3531,11 @@ static int mouse_anim_channels(bContext *C,
return 0;
}
/* Change selection mode to single when no active element is found. */
if ((selectmode == SELECT_EXTEND_RANGE) && !animchannel_has_active_of_type(ac, ale->type)) {
selectmode = SELECT_INVERT;
}
/* action to take depends on what channel we've got */
/* WARNING: must keep this in sync with the equivalent function in nla_channels.c */
switch (ale->type) {
@ -3474,6 +3639,9 @@ static int animchannels_mouseclick_invoke(bContext *C, wmOperator *op, const wmE
if (RNA_boolean_get(op->ptr, "extend")) {
selectmode = SELECT_INVERT;
}
else if (RNA_boolean_get(op->ptr, "extend_range")) {
selectmode = SELECT_EXTEND_RANGE;
}
else if (RNA_boolean_get(op->ptr, "children_only")) {
/* this is a bit of a special case for ActionGroups only...
* should it be removed or extended to all instead? */
@ -3527,6 +3695,13 @@ static void ANIM_OT_channels_click(wmOperatorType *ot)
prop = RNA_def_boolean(ot->srna, "extend", false, "Extend Select", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"extend_range",
false,
"Extend Range",
"Selection of active channel to clicked channel");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
/* Key-map: Enable with `Ctrl-Shift`. */
prop = RNA_def_boolean(ot->srna, "children_only", false, "Select Children Only", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);

View File

@ -92,7 +92,7 @@ void ED_pose_bone_select_tag_update(Object *ob)
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
}
void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select)
void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select, bool change_active)
{
bArmature *arm;
@ -109,11 +109,15 @@ void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select)
/* change selection state - activate too if selected */
if (select) {
pchan->bone->flag |= BONE_SELECTED;
arm->act_bone = pchan->bone;
if (change_active) {

I think a no-op assignment when change_active == false is error-prone. Better to use a bit more code and write:

if (change_active) {
  arm->act_bone = pchan->bone;
}

Same below.

I think a no-op assignment when `change_active == false` is error-prone. Better to use a bit more code and write: ```c if (change_active) { arm->act_bone = pchan->bone; } ``` Same below.
arm->act_bone = pchan->bone;
}
}
else {
pchan->bone->flag &= ~BONE_SELECTED;
arm->act_bone = NULL;
if (change_active) {
arm->act_bone = NULL;
}
}
/* TODO: select and activate corresponding vgroup? */

View File

@ -530,6 +530,8 @@ typedef enum eAnimChannels_SetFlag {
ACHANNEL_SETFLAG_INVERT = 2,
/** some on -> all off / all on */
ACHANNEL_SETFLAG_TOGGLE = 3,
/** turn off, keep active flag **/
ACHANNEL_SETFLAG_EXTEND_RANGE = 4,
} eAnimChannels_SetFlag;
/* types of settings for AnimChannels */
@ -696,6 +698,11 @@ void ANIM_set_active_channel(bAnimContext *ac,
void *channel_data,
eAnim_ChannelType channel_type);
/**
* Return whether channel is active.
*/
bool ANIM_is_active_channel(bAnimListElem *ale);
/**
* Delete the F-Curve from the given AnimData block (if possible),
* as appropriate according to animation context.

View File

@ -356,8 +356,13 @@ bool ED_pose_deselect_all(struct Object *ob, int select_mode, bool ignore_visibi
void ED_pose_bone_select_tag_update(struct Object *ob);
/**
* Utility method for changing the selection status of a bone.
* change_active determines whether to change the active bone of the armature when selecting pose

Document what change_active means.

Document what `change_active` means.
* channels. It is false during range selection otherwise true.

Remove const, it has no meaning in declarations of pass-by-value parameters.

Remove `const`, it has no meaning in declarations of pass-by-value parameters.
*/
void ED_pose_bone_select(struct Object *ob, struct bPoseChannel *pchan, bool select);
void ED_pose_bone_select(struct Object *ob,
struct bPoseChannel *pchan,
bool select,
bool change_active);
/* meshlaplacian.cc */

View File

@ -58,6 +58,7 @@ typedef enum eEditKeyframes_Select {
SELECT_SUBTRACT = (1 << 2),
/* flip ok status of keyframes based on key status */
SELECT_INVERT = (1 << 3),
SELECT_EXTEND_RANGE = (1 << 4),
} eEditKeyframes_Select;
/* "selection map" building modes */

View File

@ -345,7 +345,7 @@ bool ED_object_jump_to_bone(bContext *C,
/* Select it. */
ED_pose_deselect_all(ob, SEL_DESELECT, true);
ED_pose_bone_select(ob, pchan, true);
ED_pose_bone_select(ob, pchan, true, true);
arm->act_bone = pchan->bone;