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
Member

This patch will add support to select all channels between active channel
and last-clicked channel without deselecting previous selection. Shift key is
now assigned for the range selection and ctrl key to extend the selection. This
changes will make multi-selection similar as outliner tree-elements. New function is
created animchannel_select_range to handle the range selection of channels.

Old Differential revision: https://archive.blender.org/developer/D17079

This patch will add support to select all channels between active channel and last-clicked channel without deselecting previous selection. `Shift` key is now assigned for the range selection and `ctrl` key to extend the selection. This changes will make multi-selection similar as outliner tree-elements. New function is created `animchannel_select_range` to handle the range selection of channels. Old Differential revision: https://archive.blender.org/developer/D17079
Pratik Borhade added 1 commit 2023-02-10 13:05:47 +01:00
b02bfb716f Allow select range in animation editor
Patch will allow range selection similar to outliner.
New function ANIM_animchannel_select_range created for range selection.
Similar to outliner, active fcurve remains unchanged when this operation is called.
New keymap is created to separate extend and extend range functionality.

Old Differential revision: https://archive.blender.org/developer/D17079
Pratik Borhade requested review from Sybren A. Stüvel 2023-02-10 13:06:47 +01:00
Pratik Borhade requested review from Christoph Lendenfeld 2023-02-10 13:06:47 +01:00
Christoph Lendenfeld requested changes 2023-02-10 17:15:13 +01:00
Christoph Lendenfeld left a comment
Member

One thing I noticed in the code
But I like the functionality :)

One thing I noticed in the code But I like the functionality :)
@ -3192,0 +3220,4 @@
if (cursor->flag & FCURVE_ACTIVE) {
cursor->flag |= FCURVE_SELECTED;
return;

This early return statement would mean that ANIM_animdata_freelist(&anim_data); never gets called.
It's probably better to use break instead

This early return statement would mean that `ANIM_animdata_freelist(&anim_data);` never gets called. It's probably better to use `break` instead
ChrisLend marked this conversation as resolved
Pratik Borhade added 1 commit 2023-02-11 07:23:05 +01:00
Author
Member

Done, replaced it with break

Done, replaced it with `break`
Christoph Lendenfeld approved these changes 2023-02-11 17:12:05 +01:00
Christoph Lendenfeld left a comment
Member

looks good to me now :)

looks good to me now :)
Brecht Van Lommel added this to the Animation & Rigging project 2023-02-13 09:11:32 +01:00
Sybren A. Stüvel requested changes 2023-02-13 17:38:09 +01:00
Sybren A. Stüvel left a comment
Member

Patch will allow range selection for F-curves similar to outliner.

Please edit the description to explain what this means in practice. "similar to outliner" is a bit too vague for me.

It's a nice patch, but can do with a few clarifications.

> Patch will allow range selection for F-curves similar to outliner. Please edit the description to explain what this means in practice. "similar to outliner" is a bit too vague for me. It's a nice patch, but can do with a few clarifications.
@ -3192,0 +3194,4 @@
ListBase anim_data = anim_channels_for_selection(ac);
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
if (ale->type != ANIMTYPE_FCURVE) {

What's the reason to exclude non-FCurve channels here (like GreasePencil)?

What's the reason to exclude non-FCurve channels here (like GreasePencil)?
dr.sybren marked this conversation as resolved
@ -3192,0 +3198,4 @@
continue;
}
FCurve *fcu = (FCurve *)ale->data;

This should probably be extracted into a separate function, like animchannel_select(bAnimListElem *channel), that can then handle the per-channel-type specification of how selection is supposed to work.

This should probably be extracted into a separate function, like `animchannel_select(bAnimListElem *channel)`, that can then handle the per-channel-type specification of how selection is supposed to work.
Author
Member

Instead we could use anim_channels_select_set function to reset the flag as done in this diff: (check below messages)
What do you think?

Instead we could use `anim_channels_select_set` function to reset the flag as done in this diff: (check below messages) What do you think?

Please don't rely on external websites for code reviews. If you want to propose an alternative approach, either use a separate PR or just paste the diff in a comment here.

Please don't rely on external websites for code reviews. If you want to propose an alternative approach, either use a separate PR or just paste the diff in a comment here.
Author
Member

Sure. I thought that would make conversation less readable :/

index 995ad861ec8..d98f99ae838 100644
--- a/source/blender/editors/animation/anim_channels_edit.c
+++ b/source/blender/editors/animation/anim_channels_edit.c
@@ -380,7 +380,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) == 0) && (sel != ACHANNEL_SETFLAG_EXTEND_RANGE)) {
           /* Only erase the ACTIVE flag when deselecting. This ensures that "select all curves"
            * retains the currently active curve. */
           fcu->flag &= ~FCURVE_ACTIVE;
@@ -3251,7 +3251,7 @@ static int click_select_channel_fcurve(bAnimContext *ac,
     fcu->flag ^= FCURVE_SELECTED;
   }
   else if (selectmode == SELECT_EXTEND_RANGE) {
-    animchannel_clear_selection(ac);
+    ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE);
     animchannel_select_range(ac, fcu);
   }
   else {
diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h
index a436fa43989..09b85d0f4ef 100644
--- a/source/blender/editors/include/ED_anim_api.h
+++ b/source/blender/editors/include/ED_anim_api.h
@@ -530,6 +530,7 @@ typedef enum eAnimChannels_SetFlag {
   ACHANNEL_SETFLAG_INVERT = 2,
   /** some on -> all off / all on */
   ACHANNEL_SETFLAG_TOGGLE = 3,
+  ACHANNEL_SETFLAG_EXTEND_RANGE = 4,
 } eAnimChannels_SetFlag;
 
 /* types of settings for AnimChannels */```
Sure. I thought that would make conversation less readable :/ ```diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c index 995ad861ec8..d98f99ae838 100644 --- a/source/blender/editors/animation/anim_channels_edit.c +++ b/source/blender/editors/animation/anim_channels_edit.c @@ -380,7 +380,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) == 0) && (sel != ACHANNEL_SETFLAG_EXTEND_RANGE)) { /* Only erase the ACTIVE flag when deselecting. This ensures that "select all curves" * retains the currently active curve. */ fcu->flag &= ~FCURVE_ACTIVE; @@ -3251,7 +3251,7 @@ static int click_select_channel_fcurve(bAnimContext *ac, fcu->flag ^= FCURVE_SELECTED; } else if (selectmode == SELECT_EXTEND_RANGE) { - animchannel_clear_selection(ac); + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE); animchannel_select_range(ac, fcu); } else { diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h index a436fa43989..09b85d0f4ef 100644 --- a/source/blender/editors/include/ED_anim_api.h +++ b/source/blender/editors/include/ED_anim_api.h @@ -530,6 +530,7 @@ typedef enum eAnimChannels_SetFlag { ACHANNEL_SETFLAG_INVERT = 2, /** some on -> all off / all on */ ACHANNEL_SETFLAG_TOGGLE = 3, + ACHANNEL_SETFLAG_EXTEND_RANGE = 4, } eAnimChannels_SetFlag; /* types of settings for AnimChannels */```
dr.sybren marked this conversation as resolved
@ -3192,0 +3205,4 @@
ANIM_animdata_freelist(&anim_data);
}
static void animchannel_select_range(bAnimContext *ac, FCurve *cursor)

This should get a comment to explain what it does, as it's not directly clear which range it selects.

This should get a comment to explain what it does, as it's not directly clear which range it selects.
dr.sybren marked this conversation as resolved
@ -3192,0 +3208,4 @@
static void animchannel_select_range(bAnimContext *ac, FCurve *cursor)
{
ListBase anim_data = anim_channels_for_selection(ac);
bool selected = false;

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.
dr.sybren marked this conversation as resolved
@ -3192,0 +3212,4 @@
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
if (ale->type != ANIMTYPE_FCURVE) {

Same question as above: why only operate on FCurves?

Same question as above: why only operate on FCurves?
dr.sybren marked this conversation as resolved
@ -3192,0 +3218,4 @@
FCurve *fcu = (FCurve *)ale->data;
if (cursor->flag & FCURVE_ACTIVE) {

This condition will not change within the for-loop, so there is no need to keep checking it here. As a matter of fact, you can even do this before the call to anim_channels_for_selection(ac) and then immediately return.

This condition will not change within the for-loop, so there is no need to keep checking it here. As a matter of fact, you can even do this before the call to `anim_channels_for_selection(ac)` and then immediately return.
@ -3526,6 +3583,9 @@ 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", "");

This should get a tooltip, to explain that it selects from the active channel to the current/clicked/howeveryouwannacallit channel.

This should get a tooltip, to explain that it selects from the active channel to the current/clicked/howeveryouwannacallit channel.
PratikPB2123 marked this conversation as resolved
Author
Member

Hi @dr.sybren , thanks for the review. I'll update the patch in a day or two.

What's the reason to exclude non-FCurve channels here (like GreasePencil)?

I had asked about this in design task: #103855-comment
Will include other channel types too. Which other channel types are expected to do range selection?

Hi @dr.sybren , thanks for the review. I'll update the patch in a day or two. > What's the reason to exclude non-FCurve channels here (like GreasePencil)? I had asked about this in design task: [#103855-comment](https://projects.blender.org/blender/blender/issues/103855#issuecomment-80459) `Will include other channel types too. Which other channel types are expected to do range selection?`

Hi @dr.sybren , thanks for the review. I'll update the patch in a day or two.

What's the reason to exclude non-FCurve channels here (like GreasePencil)?

I had asked about this in design task: #103855-comment
Will include other channel types too. Which other channel types are expected to do range selection?

I think this should work for any channel type.

> Hi @dr.sybren , thanks for the review. I'll update the patch in a day or two. > > > What's the reason to exclude non-FCurve channels here (like GreasePencil)? > > I had asked about this in design task: [#103855-comment](https://projects.blender.org/blender/blender/issues/103855#issuecomment-80459) > `Will include other channel types too. Which other channel types are expected to do range selection?` I think this should work for any channel type.
Author
Member

What's the reason to exclude non-FCurve channels here (like GreasePencil)?

@dr.sybren , do you expect use of switch case for the selection logic of individual channel types in animchannel_select_range?

> What's the reason to exclude non-FCurve channels here (like GreasePencil)? @dr.sybren , do you expect use of switch case for the selection logic of individual channel types in `animchannel_select_range`?

Yes, as that's more readable than a chain of if/else if/else.

Yes, as that's more readable than a chain of `if`/`else if`/`else`.
Pratik Borhade added 1 commit 2023-03-05 06:12:06 +01:00
Pratik Borhade added 1 commit 2023-03-05 12:01:28 +01:00
Pratik Borhade added 1 commit 2023-03-05 12:55:04 +01:00
0cdeaed781 Changes suggested by reviewer
- Ran clang format
- Add code comments
- Rename `selected` to `in_selection_range`
- Move condition of "active flag check" outside of for loop (now added
  in `click_select_channel` functions of individual channels)
- Tooltip for `extend_range` property
Author
Member

@dr.sybren hi, did changes to PR description and updated the code too.
Also included the selection of gpencil channels.
BTW, some channels don't have active flags (example: mask, shape-key). Supporting range selection of these channels will be different than the Fcurve/Gpencil channel types.

@dr.sybren hi, did changes to PR description and updated the code too. Also included the selection of gpencil channels. BTW, some channels don't have `active` flags (example: mask, shape-key). Supporting range selection of these channels will be different than the Fcurve/Gpencil channel types.

I can't seem to get this PR to work. I select a channel, then shift-click on another channel, but it just keeps those two selected. There's no range select. I tested in the dopesheet and the graph editor, with F-Curve channels.

I can't seem to get this PR to work. I select a channel, then shift-click on another channel, but it just keeps those two selected. There's no range select. I tested in the dopesheet and the graph editor, with F-Curve channels.
Sybren A. Stüvel requested changes 2023-03-07 16:41:35 +01:00
Sybren A. Stüvel left a comment
Member

I can't seem to get this PR to work. I select a channel, then shift-click on another channel, but it just keeps those two selected. There's no range select. I tested in the dopesheet and the graph editor, with F-Curve channels.

Never mind, that was an issue on my end.

Just some minor remarks on the code, the rest looks good!

~~I can't seem to get this PR to work. I select a channel, then shift-click on another channel, but it just keeps those two selected. There's no range select. I tested in the dopesheet and the graph editor, with F-Curve channels.~~ Never mind, that was an issue on my end. Just some minor remarks on the code, the rest looks good!
@ -3205,0 +3273,4 @@
else if (selectmode == SELECT_EXTEND_RANGE) {
animchannel_clear_selection(ac);
/* When active channel is being clicked again for range selection, only select the
* clicked/active channel. Otherwise call range selection function. */

Is there any reason to have a special case for this? I think it would be better to have animchannel_select_range() simply work well when the start and end of the range are the same channel.

Is there any reason to have a special case for this? I think it would be better to have `animchannel_select_range()` simply work well when the start and end of the range are the same channel.
Author
Member

AFAICT this check is needed. Suppose only one channel is selected and then we shift-select the same channel, select_range will select all channels below to that.
To avoid this, I had added this condition in animchannel_select_range (now moved the if condition here)

AFAICT this check is needed. Suppose only one channel is selected and then we `shift-select` the same channel, `select_range` will select all channels below to that. To avoid this, I had added this condition in `animchannel_select_range` (now moved the `if` condition here)
@ -3205,0 +3274,4 @@
animchannel_clear_selection(ac);
/* When active channel is being clicked again for range selection, only select the
* clicked/active channel. Otherwise call range selection function. */
if ((fcu->flag & FCURVE_ACTIVE) == 0) {

If this code needs to stick around, flip the condition, and swap the if and else blocks. That way you can do

if (fcu->flag & FCURVE_ACTIVE) {
    // ...
}
else {
    // ...
}

This avoids the extra negation of 'is this FCurve active-flag NOT set?' in combination with an else.

If this code needs to stick around, flip the condition, and swap the `if` and `else` blocks. That way you can do ```c if (fcu->flag & FCURVE_ACTIVE) { // ... } else { // ... } ``` This avoids the extra negation of 'is this FCurve active-flag NOT set?' in combination with an `else`.
dr.sybren marked this conversation as resolved
@ -3280,0 +3361,4 @@
animchannel_clear_selection(ac);
/* When active channel is being clicked again for range selection, only select the
* clicked/active channel. Otherwise call range selection function. */
if ((gpl->flag & FCURVE_ACTIVE) == 0) {

Same questions/remarks as above.

Same questions/remarks as above.
@ -3285,3 +3376,3 @@
/* change active layer, if this is selected (since we must always have an active layer) */
if (gpl->flag & GP_LAYER_SELECT) {
if ((gpl->flag & GP_LAYER_SELECT) && (selectmode != SELECT_EXTEND_RANGE)) {

Add the same note here as above, that SELECT_EXTEND_RANGE is excluded here to give the same behaviour as the Outliner.

Add the same note here as above, that `SELECT_EXTEND_RANGE` is excluded here to give the same behaviour as the Outliner.
PratikPB2123 marked this conversation as resolved
Pratik Borhade added 1 commit 2023-03-09 04:20:09 +01:00
72dc98fb14 Minor changes
- Swap if-else code block
- Add comment
Sybren A. Stüvel requested changes 2023-03-14 11:19:58 +01:00
@ -3193,0 +3224,4 @@
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE: {
FCurve *fcu = (FCurve *)ale->data;
FCurve *cursor = (FCurve *)cursor_elem->data;

There is no guarantee that cursor_elem is actually an FCurve. In the Dopesheet you can have a mixture of different channel types. Same for the cast below -- ale->type dose not say anything about cursor_elem.

There is no guarantee that `cursor_elem` is actually an `FCurve`. In the Dopesheet you can have a mixture of different channel types. Same for the cast below -- `ale->type` dose not say anything about `cursor_elem`.
@ -3193,0 +3236,4 @@
if (in_selection_range) {
fcu->flag |= FCURVE_SELECTED;
}
} break;

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).
PratikPB2123 marked this conversation as resolved
@ -3205,0 +3273,4 @@
else if (selectmode == SELECT_EXTEND_RANGE) {
animchannel_clear_selection(ac);
/* When active channel is being clicked again for range selection, only select the
* clicked/active channel. Otherwise call range selection function. */

This behaviour should be part of animchannel_select_range(). Otherwise the caller has to know about this particular behaviour, which is basically stems from an implementation detail of animchannel_select_range(). It creates strong coupling, which is undesired.

This behaviour should be part of `animchannel_select_range()`. Otherwise the caller has to know about this particular behaviour, which is basically stems from an implementation detail of `animchannel_select_range()`. It creates strong coupling, which is undesired.
Pratik Borhade reviewed 2023-03-15 16:56:02 +01:00
@ -3193,0 +3224,4 @@
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE: {
FCurve *fcu = (FCurve *)ale->data;
FCurve *cursor = (FCurve *)cursor_elem->data;
Author
Member

As far as I understand, cursor_elem will always be of type Fcurve.
We're passing cursor_elem/AnimListElem to animchannel_select_range from the selection function of individual channel type (for Fcurves: click_select_channel_fcurve). In that function AnimListElem is Fcurve.

I hope my explanation is clear.

As far as I understand, `cursor_elem` will always be of type `Fcurve`. We're passing `cursor_elem/AnimListElem` to `animchannel_select_range` from the selection function of individual channel type (for Fcurves: `click_select_channel_fcurve`). In that function `AnimListElem` is Fcurve. I hope my explanation is clear.
PratikPB2123 marked this conversation as resolved
@ -3205,0 +3273,4 @@
else if (selectmode == SELECT_EXTEND_RANGE) {
animchannel_clear_selection(ac);
/* When active channel is being clicked again for range selection, only select the
* clicked/active channel. Otherwise call range selection function. */
Author
Member

This change was part of animchannel_select_range but you recommended to move it outside :)

This change was part of `animchannel_select_range` but you recommended to move it outside :)
Sybren A. Stüvel requested changes 2023-03-21 16:06:00 +01:00
Sybren A. Stüvel left a comment
Member

To take the discussion into the main conversation:

As far as I understand, cursor_elem will always be of type Fcurve.

It's not. A few lines further down you have:

bGPDlayer *cursor = (bGPDlayer *)cursor_elem->data;

so there it is assumed to be of type bGPDlayer *.

My point is that you make this decision based on ale->type and not cursor_elem->type.

Try adding BLI_assert(ale->type == cursor_elem->type); in the loop, and test on the attached file. Click on the X Location channel, then shift-click on the Fills channel. This should select the entire range, which combines FCurves and Grease Pencil channels.

To take the discussion into the main conversation: > As far as I understand, `cursor_elem` will always be of type `Fcurve`. It's not. A few lines further down you have: ```c bGPDlayer *cursor = (bGPDlayer *)cursor_elem->data; ``` so there it is assumed to be of type `bGPDlayer *`. My point is that you make this decision based on `ale->type` and not `cursor_elem->type`. Try adding `BLI_assert(ale->type == cursor_elem->type);` in the loop, and test on the attached file. Click on the `X Location` channel, then shift-click on the `Fills` channel. This should select the entire range, which combines FCurves and Grease Pencil channels.
Pratik Borhade added 1 commit 2023-03-24 16:43:27 +01:00
c95aca3571 Cleanup and use of is_cursor_elem
- Add `break` within braces
- Use `is_cursor_elem` to check whether clicked channel and current
  `ale` is same
Sybren A. Stüvel requested changes 2023-03-27 16:07:52 +02:00
Sybren A. Stüvel left a comment
Member

Something like this could avoid the need to check for the 'clicked on the active element' before calling animchannel_select_range():

diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c
index 72a62642fed..c86aa9b8dd6 100644
--- a/source/blender/editors/animation/anim_channels_edit.c
+++ b/source/blender/editors/animation/anim_channels_edit.c
@@ -3322,6 +3322,7 @@ static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_ele
 
   LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
     bool is_cursor_elem = (ale->data == cursor_elem->data);
+    bool is_active_elem = false;
 
     switch (ale->type) {
 
@@ -3330,7 +3331,8 @@ static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_ele
         FCurve *fcu = (FCurve *)ale->data;
 
         /* Select first and last element from the range. Reverse selection status on extremes. */
-        if ((fcu->flag & FCURVE_ACTIVE) || is_cursor_elem) {
+        is_active_elem = (fcu->flag & FCURVE_ACTIVE);
+        if (is_active_elem || is_cursor_elem) {
           fcu->flag |= FCURVE_SELECTED;
           in_selection_range = !in_selection_range;
         }
@@ -3344,7 +3346,8 @@ static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_ele
       case ANIMTYPE_GPLAYER: {
         bGPDlayer *gpl = (bGPDlayer *)ale->data;
 
-        if ((gpl->flag & GP_LAYER_ACTIVE) || is_cursor_elem) {
+        is_active_elem = (gpl->flag & GP_LAYER_ACTIVE);
+        if (is_active_elem || is_cursor_elem) {
           gpl->flag |= GP_LAYER_SELECT;
           in_selection_range = !in_selection_range;
         }
@@ -3357,6 +3360,13 @@ static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_ele
       default:
         break;
     }
+
+    if (is_active_elem && is_cursor_elem) {
+      /* The selection range is just this element. The above code assumes there are two crossings
+       * into and out of the selection range, so we should stop looping now or everything below the
+       * active element will be selected. */
+      break;
+    }
   }
 
   ANIM_animdata_freelist(&anim_data);
Something like this could avoid the need to check for the 'clicked on the active element' before calling `animchannel_select_range()`: ```diff diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c index 72a62642fed..c86aa9b8dd6 100644 --- a/source/blender/editors/animation/anim_channels_edit.c +++ b/source/blender/editors/animation/anim_channels_edit.c @@ -3322,6 +3322,7 @@ static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_ele LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { bool is_cursor_elem = (ale->data == cursor_elem->data); + bool is_active_elem = false; switch (ale->type) { @@ -3330,7 +3331,8 @@ static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_ele FCurve *fcu = (FCurve *)ale->data; /* Select first and last element from the range. Reverse selection status on extremes. */ - if ((fcu->flag & FCURVE_ACTIVE) || is_cursor_elem) { + is_active_elem = (fcu->flag & FCURVE_ACTIVE); + if (is_active_elem || is_cursor_elem) { fcu->flag |= FCURVE_SELECTED; in_selection_range = !in_selection_range; } @@ -3344,7 +3346,8 @@ static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_ele case ANIMTYPE_GPLAYER: { bGPDlayer *gpl = (bGPDlayer *)ale->data; - if ((gpl->flag & GP_LAYER_ACTIVE) || is_cursor_elem) { + is_active_elem = (gpl->flag & GP_LAYER_ACTIVE); + if (is_active_elem || is_cursor_elem) { gpl->flag |= GP_LAYER_SELECT; in_selection_range = !in_selection_range; } @@ -3357,6 +3360,13 @@ static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_ele default: break; } + + if (is_active_elem && is_cursor_elem) { + /* The selection range is just this element. The above code assumes there are two crossings + * into and out of the selection range, so we should stop looping now or everything below the + * active element will be selected. */ + break; + } } ANIM_animdata_freelist(&anim_data); ```
@ -3193,0 +3221,4 @@
bool in_selection_range = false;
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
bool is_cursor_elem = (ale->data == cursor_elem->data);

This can be const

This can be `const`
PratikPB2123 marked this conversation as resolved
Pratik Borhade added 1 commit 2023-03-28 12:50:26 +02:00
Author
Member

Thanks for the alternative solution. Tested and this change works well

Thanks for the alternative solution. Tested and this change works well
Sybren A. Stüvel requested changes 2023-03-28 14:42:20 +02:00
Sybren A. Stüvel left a comment
Member

I've been digging some more into the animation channel API, and I think the code in this PR can be simplified if we use that instead. Not as far as I would like, because that API also has its shortcomings, but at least we can improve things :)

The select-range loop can be written like this:

  LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
    /* Unfortunately, the ANIM API does not have an easy way to check whether a
     * channel is the active one. */
    bool is_active_elem = false;
    switch (ale->type) {
      case ANIMTYPE_FCURVE:
      case ANIMTYPE_NLACURVE: {
        FCurve *fcu = (FCurve *)ale->data;
        is_active_elem = fcu->flag & FCURVE_ACTIVE;
        break;
      }
      case ANIMTYPE_GPLAYER: {
        bGPDlayer *gpl = (bGPDlayer *)ale->data;
        is_active_elem = gpl->flag & GP_LAYER_ACTIVE;
        break;
      }
      default:
        break;
    }

    const bool is_cursor_elem = (ale->data == cursor_elem->data);
    if (is_active_elem || is_cursor_elem) {
      /* Always select the crossover points. */
      ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, ACHANNEL_SETFLAG_ADD);
      in_selection_range = !in_selection_range;
    }
    else if (in_selection_range) {
      ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, ACHANNEL_SETFLAG_ADD);
    }

    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;
    }
  }

That way the whole "select the channel" code is using ANIM_channel_setting_set(), and the switch is only used to get the active channel. Getting the active channel isn't possible with a simple API call (yet) so that's why I left it in the switch.

I've been digging some more into the animation channel API, and I think the code in this PR can be simplified if we use that instead. Not as far as I would like, because that API also has its shortcomings, but at least we can improve things :) The select-range loop can be written like this: ```c LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { /* Unfortunately, the ANIM API does not have an easy way to check whether a * channel is the active one. */ bool is_active_elem = false; switch (ale->type) { case ANIMTYPE_FCURVE: case ANIMTYPE_NLACURVE: { FCurve *fcu = (FCurve *)ale->data; is_active_elem = fcu->flag & FCURVE_ACTIVE; break; } case ANIMTYPE_GPLAYER: { bGPDlayer *gpl = (bGPDlayer *)ale->data; is_active_elem = gpl->flag & GP_LAYER_ACTIVE; break; } default: break; } const bool is_cursor_elem = (ale->data == cursor_elem->data); if (is_active_elem || is_cursor_elem) { /* Always select the crossover points. */ ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, ACHANNEL_SETFLAG_ADD); in_selection_range = !in_selection_range; } else if (in_selection_range) { ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, ACHANNEL_SETFLAG_ADD); } 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; } } ``` That way the whole "select the channel" code is using `ANIM_channel_setting_set()`, and the `switch` is only used to get the active channel. Getting the active channel isn't possible with a simple API call (yet) so that's why I left it in the switch.
Author
Member

Thanks, this solution is more clean and works good.
But it is also selecting anim-elements of type group, object, etc. that lies within the range. Is this expected? I mean if someone wants to select and then delete channels from different groups with "range select", they will not be able to delete particular channels, they might end up with all channels deleted from the group.

New: 2nd Group not preserved Old: 2nd Group preserved
104565-new.mp4 104565-old.mp4

Either we can keep the old logic
Or we'd need to exclude group/object elements from being selected with new logic.

Thanks, this solution is more clean and works good. But it is also selecting anim-elements of type group, object, etc. that lies within the range. Is this expected? I mean if someone wants to select and then delete channels from different groups with "range select", they will not be able to delete particular channels, they might end up with all channels deleted from the group. | New: 2nd Group not preserved | Old: 2nd Group preserved | | -------- | -------- | | [104565-new.mp4](/attachments/8d976c88-593e-4c9f-8302-e268775da7c1) | [104565-old.mp4](/attachments/6bca5551-b033-499b-9e96-dc6fb607ffea) | Either we can keep the old logic Or we'd need to exclude group/object elements from being selected with new logic.

But it is also selecting anim-elements of type group, object, etc. that lies within the range. Is this expected? I mean if someone wants to select and then delete channels from different groups with "range select", they will not be able to delete particular channels, they might end up with all channels deleted from the group.

That's a good point, and thanks for the clear demo videos. I'll discuss this in today's module meeting.

If the decision is to keep the old behaviour, I think it's nicer to explicitly test for this, and then still use the generic API to actually select things.

> But it is also selecting anim-elements of type group, object, etc. that lies within the range. Is this expected? I mean if someone wants to select and then delete channels from different groups with "range select", they will not be able to delete particular channels, they might end up with all channels deleted from the group. That's a good point, and thanks for the clear demo videos. I'll discuss this in today's module meeting. If the decision is to keep the old behaviour, I think it's nicer to explicitly test for this, and then still use the generic API to actually select things.
Author
Member

I'll discuss this in module meeting.

Hi, any updates on this?

> I'll discuss this in module meeting. Hi, any updates on this?

Thanks for the poke. Yes, this was discussed in last week's module meeting.

The consensus was:

  • Great to have this feature.
  • Whether group-like channels (the collapsible ones, like the object, bone groups, etc) are included in the range should be determined by what's being shift-clicked on.
  • Clicking on a 'data channel' (fcurve, grease pencil, etc) should select only data channels, and skip the groups (so like your original patch).
  • Clicking on a group-like channel should select other group-like channels as well, so that multiple objects can be selected as a whole by clicking on the first, then shift-clicking on the last.

Is this something you could implement?

Thanks for the poke. Yes, this was discussed [in last week's module meeting](https://devtalk.blender.org/t/2023-03-30-animation-rigging-module-meeting/28530). The consensus was: - Great to have this feature. - Whether group-like channels (the collapsible ones, like the object, bone groups, etc) are included in the range should be determined by what's being shift-clicked on. - Clicking on a 'data channel' (fcurve, grease pencil, etc) should select only data channels, and skip the groups (so like your original patch). - Clicking on a group-like channel should select other group-like channels as well, so that multiple objects can be selected as a whole by clicking on the first, then shift-clicking on the last. Is this something you could implement?
Author
Member

Sure, will do.

Sure, will do.
Pratik Borhade added 2 commits 2023-04-11 10:25:16 +02:00
b0e3eacaad Include group-like channels when selecting range
Group-channels are only included in selection when the channel type
of clicked element matches. That is, groups are selected along with the
general channels when clicked on group.

- `is_selection_allowed` decides whether to include group channels or
  not
 - Clear general channels when object-channel is selected
 (`ANIM_anim_channels_select_set` added). Otherwise there would be two
 active channels.

Thanks for the changes, the FCurve channel selection works fine.
I discovered two issues though.
When selecting groups, e.g. this image
it also selects the FCurve channels inbetween. The idea was that the selection would stay on the level it is on and not go down or up.

Second, when you have two objects with animation (turn off "only show selected") it will also select all channels from the other object. In fact it seems to just not work properly with channel selection that are not bones

Thanks for the changes, the FCurve channel selection works fine. I discovered two issues though. When selecting groups, e.g. this ![image](/attachments/bdd32341-70e0-42a2-b19e-07624feb6948) it also selects the FCurve channels inbetween. The idea was that the selection would stay on the level it is on and not go down or up. Second, when you have two objects with animation (turn off "only show selected") it will also select all channels from the other object. In fact it seems to just not work properly with channel selection that are not bones
6.2 KiB
Christoph Lendenfeld requested changes 2023-04-14 10:00:35 +02:00
Christoph Lendenfeld left a comment
Member

see the described issues in the previous comment

see the described issues in the previous comment
Author
Member

when selecting groups it also selects the FCurve channels inbetween.

ok, so in above case, only selection of groups within range is expected? I was not in the meeting so I misinterpreted Sybren's message (I consider to include all channels+groups when group is shift clicked). In short, which elements to select should depend on the shift-clicked element, right?

Second,... it will also select all channels from the other object. In fact it seems to just not work properly with channel selection that are not bones

I've not understood this problem really. A short video might help.

> when selecting groups it also selects the FCurve channels inbetween. ok, so in above case, only selection of groups within range is expected? I was not in the meeting so I misinterpreted Sybren's message (I consider to include all channels+groups when group is shift clicked). In short, which elements to select should depend on the shift-clicked element, right? > Second,... it will also select all channels from the other object. In fact it seems to just not work properly with channel selection that are not bones I've not understood this problem really. A short video might help.

ok, so in above case, only selection of groups within range is expected? I was not in the meeting so I misinterpreted Sybren's message (I consider to include all channels+groups when group is shift clicked). In short, which elements to select should depend on the shift-clicked element, right?

Yeah that's the idea

Second,... it will also select all channels from the other object. In fact it seems to just not work properly with channel selection that are not bones

I've not understood this problem really. A short video might help.

So it took me a while to replicate this on second go. Quite obscure.
But video is there

> ok, so in above case, only selection of groups within range is expected? I was not in the meeting so I misinterpreted Sybren's message (I consider to include all channels+groups when group is shift clicked). In short, which elements to select should depend on the shift-clicked element, right? > Yeah that's the idea > > Second,... it will also select all channels from the other object. In fact it seems to just not work properly with channel selection that are not bones > > I've not understood this problem really. A short video might help. So it took me a while to replicate this on second go. Quite obscure. But video is there
Pratik Borhade added 1 commit 2023-04-15 12:59:11 +02:00
488fbf4f1b Following fixes:
- Restrict channel selection to similar types. Channels that matches with the type of
  shift-clicked channel are selected (Example: Fcurve only). This prevents selection
  of Gpencil and fcurve channel at once. But for now its better to not
  allow both at once, see: #106116
- Change selection mode when no active element is found. Otherwise all channels below
  the clicked channel would get selected.
Pratik Borhade added 1 commit 2023-04-16 09:20:42 +02:00
Pratik Borhade added 1 commit 2023-04-16 12:50:00 +02:00
Pratik Borhade requested review from Christoph Lendenfeld 2023-04-16 12:50:45 +02:00

So it took me a while to replicate this on second go. Quite obscure.
But video is there

Is this an issue with this PR? Just asking, because there are already quite a few issues with selection syncing between channels, and if it's not specific to this PR, I'd rather land this one and solve the rest some other time ;-)

> So it took me a while to replicate this on second go. Quite obscure. > But video is there Is this an issue with this PR? Just asking, because there are already [quite a few issues with selection syncing](https://wiki.blender.org/wiki/Modules/Animation-Rigging/Weak_Areas#Selection_synchronisation_between_pose_bones_and_animation_channels) between channels, and if it's not specific to this PR, I'd rather land this one and solve the rest some other time ;-)
Author
Member

Is this an issue with this PR?

Not sure, never encountered this issue.
I found one problem ( [i] ) while trying to replicate the above issue. Included the fix for it.

[i] calling range selection when no active element is present

> Is this an issue with this PR? Not sure, never encountered this issue. I found one problem ( [i] ) while trying to replicate the above issue. Included the fix for it. [i] calling range selection when no active element is present
Christoph Lendenfeld reviewed 2023-04-20 17:27:31 +02:00
Christoph Lendenfeld left a comment
Member

I cannot reproduce the previous issue anymore, so from my side this is good to go

I cannot reproduce the previous issue anymore, so from my side this is good to go
Christoph Lendenfeld approved these changes 2023-04-20 17:28:33 +02:00
Christoph Lendenfeld left a comment
Member

oops ctrl-enter makes a comment
officially green ticked from me now

oops ctrl-enter makes a comment officially green ticked from me now
Sybren A. Stüvel requested changes 2023-05-01 16:51:21 +02:00
Sybren A. Stüvel left a comment
Member

Some minor things.

Some minor things.
@ -215,3 +215,3 @@
}
static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp, bAnimListElem *ale)
static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp, bAnimListElem *ale, const bool change_active)

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.
@ -3027,6 +3028,224 @@ static int click_select_channel_scene(bAnimListElem *ale,
return (ND_ANIMCHAN | NA_SELECTED);
}
/* Before selecting the range, check if any active element is present. */

Document what a function does, not when it should be called. The latter can be included, but should be secondary, and not the first thing documented.

"check" → "return whether". In #104949 the word "check" was used as "ensure", but here it seems to have another meaning.

Probably a better function name would be animchannel_has_active_of_type.

Document what a function does, not when it should be called. The latter can be included, but should be secondary, and not the first thing documented. "check" → "return whether". In #104949 the word "check" was used as "ensure", but here it seems to have another meaning. Probably a better function name would be `animchannel_has_active_of_type`.
@ -3028,2 +3029,4 @@
}
/* Before selecting the range, check if any active element is present. */
static bool animchannel_active_check(bAnimContext *ac, eAnim_ChannelType type)

const eAnim_ChannelType type

`const eAnim_ChannelType type`
@ -3030,0 +3085,4 @@
is_active_found |= gpl->flag & GP_LAYER_ACTIVE;
break;
}
default:

Remove the default clause (also in the other switches) so that the compiler can warn if there are any enum elements that don't have a corresponding case.

Remove the `default` clause (also in the other `switch`es) so that the compiler can warn if there are any enum elements that don't have a corresponding `case`.
@ -3030,0 +3088,4 @@
default:
break;
}
}

Add this here:

if (is_active_found) {
  break;
}

as once the active element is found, the loop can stop.

Add this here: ``` if (is_active_found) { break; } ``` as once the active element is found, the loop can stop.
dr.sybren marked this conversation as resolved
@ -3030,0 +3153,4 @@
ANIM_animdata_freelist(&anim_data);
}
/* Select channels that lies between active channel and last clicked channel. */

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.
dr.sybren marked this conversation as resolved
@ -3030,0 +3161,4 @@
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
bool is_active_elem = false;
bool is_selection_allowed = ale->type == cursor_elem->type;

const

`const`
@ -3030,0 +3162,4 @@
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
bool is_active_elem = false;
bool is_selection_allowed = ale->type == cursor_elem->type;
const bool is_cursor_elem = (ale->data == cursor_elem->data);

Consistency: either use parentheses aroung the A == B expressions, or don't. But don't use different styles for neighbouring lines.

Consistency: either use parentheses aroung the `A == B` expressions, or don't. But don't use different styles for neighbouring lines.
@ -3030,0 +3169,4 @@
continue;
}
switch (ale->type) {

This switch is a copy of the logic above in the animchannel_active_check() function. Both basically ask 'is ale the active one?'

Move the duplicated code into a function of its own, and use that from both places. Since it's so generic, it should probably be something like bool ANIM_is_active_channel(bAnimListElem *ale) and be declared in ED_anim_api.h.

This switch is a copy of the logic above in the `animchannel_active_check()` function. Both basically ask 'is `ale` the active one?' Move the duplicated code into a function of its own, and use that from both places. Since it's so generic, it should probably be something like `bool ANIM_is_active_channel(bAnimListElem *ale)` and be declared in `ED_anim_api.h`.
@ -357,7 +357,7 @@ void ED_pose_bone_select_tag_update(struct Object *ob);
/**
* Utility method for changing the selection status of a bone.
*/

Document what change_active means.

Document what `change_active` means.
@ -358,3 +358,3 @@
* Utility method for changing the selection status of a bone.
*/
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, const bool change_active);

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.
Pratik Borhade added 2 commits 2023-05-02 12:55:35 +02:00
c31fed5b2b Code cleanup
- Document `change_active` parameter
- Fix code comments
- Remove default clauses
- Rename `animchannel_active_check` to `animchannel_has_active_of_type`
b3c299277e New function to find out the given channel is active
ANIM_is_active_channel() returns whether given channel is active
Pratik Borhade requested review from Sybren A. Stüvel 2023-05-04 12:32:07 +02:00
Sybren A. Stüvel requested changes 2023-05-04 16:34:13 +02:00
@ -217,1 +217,3 @@
static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp, bAnimListElem *ale)
bool ANIM_is_active_channel(bAnimListElem *ale)
{
bool is_active_found = false;

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 ...`.
dr.sybren marked this conversation as resolved
@ -218,0 +263,4 @@
is_active_found = gpl->flag & GP_LAYER_ACTIVE;
break;
}
return is_active_found;

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;`
@ -3030,0 +3106,4 @@
return is_active_found;
}
static void animchannel_clear_selection(bAnimContext *ac)

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).
Author
Member

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 :)
Author
Member

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:
Author
Member

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

Thanks, replaced it with the existing function (with minor changes).
@ -3030,0 +3172,4 @@
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
bool is_active_elem = false;
const bool is_selection_allowed = (ale->type == cursor_elem->type);
const bool is_cursor_elem = (ale->data == cursor_elem->data);

This can be moved down below the if (!is_selection_allowed) block, because it's only used after that block.

This can be moved down below the `if (!is_selection_allowed)` block, because it's only used after that block.
@ -3030,0 +3179,4 @@
continue;
}
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);

`is_active_elem` isn't used before this point, so just declare it as `const bool is_active_elem = ANIM_is_active_channel(ale);`
@ -110,3 +110,3 @@
if (select) {
pchan->bone->flag |= BONE_SELECTED;
arm->act_bone = pchan->bone;
arm->act_bone = change_active ? pchan->bone : arm->act_bone;

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.
Pratik Borhade added 1 commit 2023-05-05 12:36:51 +02:00
981cb0a3de Cleanup
- `is_active_found` removed
- Replace ternary operator with `if` block
Sybren A. Stüvel requested changes 2023-05-05 12:53:37 +02:00
@ -218,0 +258,4 @@
bGPDlayer *gpl = (bGPDlayer *)ale->data;
return gpl->flag & GP_LAYER_ACTIVE;
}
return false;

This return is still in the wrong place...

This `return` is still in the wrong place...
Pratik Borhade added 1 commit 2023-05-05 12:56:53 +02:00
Pratik Borhade added 1 commit 2023-05-05 14:12:33 +02:00
Pratik Borhade added 1 commit 2023-05-05 14:29:23 +02:00
Pratik Borhade requested review from Sybren A. Stüvel 2023-05-05 14:31:34 +02:00
Sybren A. Stüvel approved these changes 2023-05-05 16:20:55 +02:00
Sybren A. Stüvel left a comment
Member

\o/ woohoo!

\o/ woohoo!
@ -218,0 +258,4 @@
bGPDlayer *gpl = (bGPDlayer *)ale->data;
return gpl->flag & GP_LAYER_ACTIVE;
}
/* These channel types do not have active flags. */

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

👍 now it's instantly clear that these were not forgotten.
Pratik Borhade merged commit 80feb13665 into main 2023-05-05 17:46:14 +02:00
Pratik Borhade deleted branch T103855-anim-select-extend 2023-05-05 17:46:15 +02:00
Author
Member

Merged, thanks for reviewing :)

Merged, thanks for reviewing :)
Howard Trickey referenced this issue from a commit 2023-05-29 02:51:42 +02:00
Demeter Dzadik removed this from the Animation & Rigging project 2023-08-28 20:01:45 +02:00
Member

I believe this was already documented here.

I believe this was already documented [here](https://docs.blender.org/manual/en/latest/editors/graph_editor/channels.html#selecting).
Author
Member

Yes, @ChrisLend had updated the manual: 36fdeffef4

Yes, @ChrisLend had updated the manual: https://projects.blender.org/blender/blender-manual/commit/36fdeffef49cf41da3558fb2dc9f3fe4d93d4a90
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
4 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#104565
No description provided.