Dopesheet: Lasso and Circle Select tools work for selecting keyframes
authorJoshua Leung <aligorith@gmail.com>
Thu, 23 Jun 2016 11:16:14 +0000 (23:16 +1200)
committerJoshua Leung <aligorith@gmail.com>
Thu, 23 Jun 2016 15:18:35 +0000 (03:18 +1200)
This only works in the Action and Dopesheet modes (which operate on FCurve keyframes).
Support for Grease Pencil and Mask Keyframes though is still pending.

release/scripts/startup/bl_ui/space_dopesheet.py
source/blender/editors/animation/keyframes_edit.c
source/blender/editors/include/ED_keyframes_edit.h
source/blender/editors/space_action/action_intern.h
source/blender/editors/space_action/action_ops.c
source/blender/editors/space_action/action_select.c
source/blender/editors/space_graph/graph_select.c
source/blender/windowmanager/intern/wm_operators.c

index 779303c0f7a34a4be0f4d1949e176bffffc3165e..546c9c2808df6a36fa79d96614c5510bc870b1a2 100644 (file)
@@ -250,6 +250,8 @@ class DOPESHEET_MT_select(Menu):
         layout.operator("action.select_border").axis_range = False
         layout.operator("action.select_border", text="Border Axis Range").axis_range = True
 
+        layout.operator("action.select_circle")
+
         layout.separator()
         layout.operator("action.select_column", text="Columns on Selected Keys").mode = 'KEYS'
         layout.operator("action.select_column", text="Column on Current Frame").mode = 'CFRA'
index cd00b963482026d6ba5b05d754cc1b8c791e2f13..f909b39cca31536d7ba717b3cbe0cabcfd47ccd2 100644 (file)
@@ -539,7 +539,7 @@ static short ok_bezier_region(KeyframeEditData *ked, BezTriple *bezt)
 }
 
 /**
- * only called from #ok_bezier_region_lasso
+ * Called from #ok_bezier_region_lasso and #ok_bezier_channel_lasso
  */
 static bool bezier_region_lasso_test(
         const KeyframeEdit_LassoData *data_lasso,
@@ -575,8 +575,35 @@ static short ok_bezier_region_lasso(KeyframeEditData *ked, BezTriple *bezt)
                return 0;
 }
 
+static short ok_bezier_channel_lasso(KeyframeEditData *ked, BezTriple *bezt)
+{
+       /* check for lasso customdata (KeyframeEdit_LassoData) */
+       if (ked->data) {
+               KeyframeEdit_LassoData *data = ked->data;
+               float pt[2];
+               
+               /* late-binding remap of the x values (for summary channels) */
+               /* XXX: Ideally we reset, but it should be fine just leaving it as-is
+                * as the next channel will reset it properly, while the next summary-channel
+                * curve will also reset by itself...
+                */
+               if (ked->iterflags & (KED_F1_NLA_UNMAP | KED_F2_NLA_UNMAP)) {
+                       data->rectf_scaled->xmin = ked->f1;
+                       data->rectf_scaled->xmax = ked->f2;
+               }
+               
+               /* only use the x-coordinate of the point; the y is the channel range... */
+               pt[0] = bezt->vec[1][0];
+               pt[1] = ked->channel_y;
+               
+               if (bezier_region_lasso_test(data, pt))
+                       return KEYFRAME_OK_KEY;
+       }
+       return 0;
+}
+
 /**
- * only called from #ok_bezier_region_circle
+ * Called from #ok_bezier_region_circle and #ok_bezier_channel_circle
  */
 static bool bezier_region_circle_test(
         const KeyframeEdit_CircleData *data_circle,
@@ -613,6 +640,33 @@ static short ok_bezier_region_circle(KeyframeEditData *ked, BezTriple *bezt)
                return 0;
 }
 
+static short ok_bezier_channel_circle(KeyframeEditData *ked, BezTriple *bezt)
+{
+       /* check for circle select customdata (KeyframeEdit_CircleData) */
+       if (ked->data) {
+               KeyframeEdit_CircleData *data = ked->data;
+               float pt[2];
+               
+               /* late-binding remap of the x values (for summary channels) */
+               /* XXX: Ideally we reset, but it should be fine just leaving it as-is
+                * as the next channel will reset it properly, while the next summary-channel
+                * curve will also reset by itself...
+                */
+               if (ked->iterflags & (KED_F1_NLA_UNMAP | KED_F2_NLA_UNMAP)) {
+                       data->rectf_scaled->xmin = ked->f1;
+                       data->rectf_scaled->xmax = ked->f2;
+               }
+               
+               /* only use the x-coordinate of the point; the y is the channel range... */
+               pt[0] = bezt->vec[1][0];
+               pt[1] = ked->channel_y;
+               
+               if (bezier_region_circle_test(data, pt))
+                       return KEYFRAME_OK_KEY;
+       }
+       return 0;
+}
+
 
 KeyframeEditFunc ANIM_editkeyframes_ok(short mode)
 {
@@ -634,6 +688,10 @@ KeyframeEditFunc ANIM_editkeyframes_ok(short mode)
                        return ok_bezier_region_lasso;
                case BEZT_OK_REGION_CIRCLE: /* only if the point falls within KeyframeEdit_CircleData defined data */
                        return ok_bezier_region_circle;
+               case BEZT_OK_CHANNEL_LASSO: /* same as BEZT_OK_REGION_LASSO, but we're only using the x-value of the points */
+                       return ok_bezier_channel_lasso;
+               case BEZT_OK_CHANNEL_CIRCLE: /* same as BEZT_OK_REGION_CIRCLE, but we're only using the x-value of the points */
+                       return ok_bezier_channel_circle;
                default: /* nothing was ok */
                        return NULL;
        }
index ab51298eb6c47ec3f2b7d89b427294e268e612c0..0d352ab5eadeb18b4fa4d672523aa4526bd3c987 100644 (file)
@@ -45,14 +45,21 @@ struct Scene;
 
 /* bezt validation */
 typedef enum eEditKeyframes_Validate {
+       /* Frame range */
        BEZT_OK_FRAME = 1,
        BEZT_OK_FRAMERANGE,
+       /* Selection status */
        BEZT_OK_SELECTED,
+       /* Values (y-val) only */
        BEZT_OK_VALUE,
        BEZT_OK_VALUERANGE,
+       /* For graph editor keyframes (2D tests) */
        BEZT_OK_REGION,
        BEZT_OK_REGION_LASSO,
        BEZT_OK_REGION_CIRCLE,
+       /* Only for keyframes a certain Dopesheet channel */
+       BEZT_OK_CHANNEL_LASSO,
+       BEZT_OK_CHANNEL_CIRCLE,
 } eEditKeyframes_Validate;
 
 /* ------------ */
@@ -98,7 +105,7 @@ typedef enum eEditKeyframes_Mirror {
 
 /* use with BEZT_OK_REGION_LASSO */
 typedef struct KeyframeEdit_LassoData {
-       const rctf *rectf_scaled;
+       rctf *rectf_scaled;
        const rctf *rectf_view;
        const int (*mcords)[2];
        int mcords_tot;
@@ -106,7 +113,7 @@ typedef struct KeyframeEdit_LassoData {
 
 /* use with BEZT_OK_REGION_CIRCLE */
 typedef struct KeyframeEdit_CircleData {
-       const rctf *rectf_scaled;
+       rctf *rectf_scaled;
        const rctf *rectf_view;
        float mval[2];
        float radius_squared;
@@ -157,7 +164,8 @@ typedef struct KeyframeEditData {
        /* current iteration data */
        struct FCurve *fcu;         /* F-Curve that is being iterated over */
        int curIndex;               /* index of current keyframe being iterated over */
-
+       float channel_y;            /* y-position of midpoint of the channel (for the dopesheet) */
+       
        /* flags */
        eKeyframeVertOk curflags;        /* current flags for the keyframe we're reached in the iteration process */
        eKeyframeIterFlags iterflags;    /* settings for iteration process */
index 50e10e7e154e88a1e54500cf4c66660452e71098..408eb38d386ded424b79e64e58f7170d2d60c708 100644 (file)
@@ -59,6 +59,8 @@ void draw_channel_strips(struct bAnimContext *ac, struct SpaceAction *saction, s
 
 void ACTION_OT_select_all_toggle(struct wmOperatorType *ot);
 void ACTION_OT_select_border(struct wmOperatorType *ot);
+void ACTION_OT_select_lasso(struct wmOperatorType *ot);
+void ACTION_OT_select_circle(struct wmOperatorType *ot);
 void ACTION_OT_select_column(struct wmOperatorType *ot);
 void ACTION_OT_select_linked(struct wmOperatorType *ot);
 void ACTION_OT_select_more(struct wmOperatorType *ot);
index f69f9944f8a320dee6cb638ee0f045175e5bc1e4..a261202b69045d5f9089c7f6034aeca62eaabaaf 100644 (file)
@@ -59,6 +59,8 @@ void action_operatortypes(void)
        WM_operatortype_append(ACTION_OT_clickselect);
        WM_operatortype_append(ACTION_OT_select_all_toggle);
        WM_operatortype_append(ACTION_OT_select_border);
+       WM_operatortype_append(ACTION_OT_select_lasso);
+       WM_operatortype_append(ACTION_OT_select_circle);
        WM_operatortype_append(ACTION_OT_select_column);
        WM_operatortype_append(ACTION_OT_select_linked);
        WM_operatortype_append(ACTION_OT_select_more);
@@ -178,6 +180,14 @@ static void action_keymap_keyframes(wmKeyConfig *keyconf, wmKeyMap *keymap)
        kmi = WM_keymap_add_item(keymap, "ACTION_OT_select_border", BKEY, KM_PRESS, KM_ALT, 0);
        RNA_boolean_set(kmi->ptr, "axis_range", true);
        
+       /* region select */
+       kmi = WM_keymap_add_item(keymap, "ACTION_OT_select_lasso", EVT_TWEAK_A, KM_ANY, KM_CTRL, 0);
+       RNA_boolean_set(kmi->ptr, "deselect", false);
+       kmi = WM_keymap_add_item(keymap, "ACTION_OT_select_lasso", EVT_TWEAK_A, KM_ANY, KM_CTRL | KM_SHIFT, 0);
+       RNA_boolean_set(kmi->ptr, "deselect", true);
+       
+       WM_keymap_add_item(keymap, "ACTION_OT_select_circle", CKEY, KM_PRESS, 0, 0);
+       
        /* column select */
        RNA_enum_set(WM_keymap_add_item(keymap, "ACTION_OT_select_column", KKEY, KM_PRESS, 0, 0)->ptr, "mode", ACTKEYS_COLUMNSEL_KEYS);
        RNA_enum_set(WM_keymap_add_item(keymap, "ACTION_OT_select_column", KKEY, KM_PRESS, KM_CTRL, 0)->ptr, "mode", ACTKEYS_COLUMNSEL_CFRA);
index f2813b2a8d308359bab4bc49c1d80c0bf6e6bee7..c9a4922eb0b41be76183cc2e0694f8292326ab97 100644 (file)
@@ -36,6 +36,7 @@
 
 #include "BLI_blenlib.h"
 #include "BLI_dlrbTree.h"
+#include "BLI_lasso.h"
 #include "BLI_utildefines.h"
 
 #include "DNA_anim_types.h"
@@ -375,6 +376,264 @@ void ACTION_OT_select_border(wmOperatorType *ot)
        ot->prop = RNA_def_boolean(ot->srna, "axis_range", 0, "Axis Range", "");
 }
 
+/* ******************** Region Select Operators ***************************** */
+/* "Region Select" operators include the Lasso and Circle Select operators.
+ * These two ended up being lumped together, as it was easier in the 
+ * original Graph Editor implmentation of these to do it this way.
+ */
+
+static void region_select_action_keys(bAnimContext *ac, const rctf *rectf_view, short mode, short selectmode, void *data)
+{
+       ListBase anim_data = {NULL, NULL};
+       bAnimListElem *ale;
+       int filter;
+       
+       KeyframeEditData ked;
+       KeyframeEditFunc ok_cb, select_cb;
+       View2D *v2d = &ac->ar->v2d;
+       rctf rectf, scaled_rectf;
+       float ymin = 0, ymax = (float)(-ACHANNEL_HEIGHT_HALF);
+       
+       /* convert mouse coordinates to frame ranges and channel coordinates corrected for view pan/zoom */
+       UI_view2d_region_to_view_rctf(v2d, rectf_view, &rectf);
+       
+       /* filter data */
+       filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | ANIMFILTER_NODUPLIS);
+       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+       
+       /* get beztriple editing/validation funcs  */
+       select_cb = ANIM_editkeyframes_select(selectmode);
+       ok_cb = ANIM_editkeyframes_ok(mode);
+       
+       /* init editing data */
+       memset(&ked, 0, sizeof(KeyframeEditData));
+       if (mode == BEZT_OK_CHANNEL_LASSO) {
+               KeyframeEdit_LassoData *data_lasso = data;
+               data_lasso->rectf_scaled = &scaled_rectf;
+               ked.data = data_lasso;
+       }
+       else if (mode == BEZT_OK_CHANNEL_CIRCLE) {
+               KeyframeEdit_CircleData *data_circle = data;
+               data_circle->rectf_scaled = &scaled_rectf;
+               ked.data = data;
+       }
+       else {
+               ked.data = &scaled_rectf;
+       }
+       
+       /* loop over data, doing region select */
+       for (ale = anim_data.first; ale; ale = ale->next) {
+               AnimData *adt = ANIM_nla_mapping_get(ac, ale);
+               
+               /* get new vertical minimum extent of channel */
+               ymin = ymax - ACHANNEL_STEP;
+               
+               /* compute midpoint of channel (used for testing if the key is in the region or not) */
+               ked.channel_y = ymin + ACHANNEL_HEIGHT_HALF;
+               
+               /* if channel is mapped in NLA, apply correction
+                * - Apply to the bounds being checked, not all the keyframe points,
+                *   to avoid having scaling everything
+                * - Save result to the scaled_rect, which is all that these operators
+                *   will read from
+                */
+               if (adt) {
+                       ked.iterflags &= ~(KED_F1_NLA_UNMAP | KED_F2_NLA_UNMAP);
+                       ked.f1 = BKE_nla_tweakedit_remap(adt, rectf.xmin, NLATIME_CONVERT_UNMAP);
+                       ked.f2 = BKE_nla_tweakedit_remap(adt, rectf.xmax, NLATIME_CONVERT_UNMAP);
+               }
+               else {
+                       ked.iterflags |= (KED_F1_NLA_UNMAP | KED_F2_NLA_UNMAP); /* for summary tracks */
+                       ked.f1 = rectf.xmin;
+                       ked.f2 = rectf.xmax;
+               }
+               
+               /* Update values for scaled_rectf - which is used to compute the mapping in the callbacks
+                * NOTE: Since summary tracks need late-binding remapping, the callbacks may overwrite these 
+                *       with the properly remapped ked.f1/f2 values, when needed
+                */
+               scaled_rectf.xmin = ked.f1;
+               scaled_rectf.xmax = ked.f2;
+               scaled_rectf.ymin = ymin;
+               scaled_rectf.ymax = ymax;
+               
+               /* perform vertical suitability check (if applicable) */
+               if ((mode == ACTKEYS_BORDERSEL_FRAMERANGE) ||
+                   !((ymax < rectf.ymin) || (ymin > rectf.ymax)))
+               {
+                       /* loop over data selecting */
+                       switch (ale->type) {
+                               case ANIMTYPE_GPDATABLOCK:
+                               {
+                                       bGPdata *gpd = ale->data;
+                                       bGPDlayer *gpl;
+                                       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+                                               //ED_gplayer_frames_select_border(gpl, rectf.xmin, rectf.xmax, selectmode);
+                                       }
+                                       break;
+                               }
+                               case ANIMTYPE_GPLAYER:
+                               {
+                                       //ED_gplayer_frames_select_border(ale->data, rectf.xmin, rectf.xmax, selectmode);
+                                       break;
+                               }
+                               case ANIMTYPE_MASKDATABLOCK:
+                               {
+                                       Mask *mask = ale->data;
+                                       MaskLayer *masklay;
+                                       for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
+                                               //ED_masklayer_frames_select_border(masklay, rectf.xmin, rectf.xmax, selectmode);
+                                       }
+                                       break;
+                               }
+                               case ANIMTYPE_MASKLAYER:
+                               {
+                                       //ED_masklayer_frames_select_border(ale->data, rectf.xmin, rectf.xmax, selectmode);
+                                       break;
+                               }
+                               default:
+                                       ANIM_animchannel_keyframes_loop(&ked, ac->ads, ale, ok_cb, select_cb, NULL);
+                                       break;
+                       }
+               }
+               
+               /* set minimum extent to be the maximum of the next channel */
+               ymax = ymin;
+       }
+       
+       /* cleanup */
+       ANIM_animdata_freelist(&anim_data);
+}
+/* ----------------------------------- */
+static int actkeys_lassoselect_exec(bContext *C, wmOperator *op)
+{
+       bAnimContext ac;
+       
+       KeyframeEdit_LassoData data_lasso;
+       rcti rect;
+       rctf rect_fl;
+       
+       short selectmode;
+       bool extend;
+       
+       /* get editor data */
+       if (ANIM_animdata_get_context(C, &ac) == 0)
+               return OPERATOR_CANCELLED;
+       
+       data_lasso.rectf_view = &rect_fl;
+       data_lasso.mcords = WM_gesture_lasso_path_to_array(C, op, &data_lasso.mcords_tot);
+       if (data_lasso.mcords == NULL)
+               return OPERATOR_CANCELLED;
+       
+       /* clear all selection if not extending selection */
+       extend = RNA_boolean_get(op->ptr, "extend");
+       if (!extend)
+               deselect_action_keys(&ac, 1, SELECT_SUBTRACT);
+       
+       if (!RNA_boolean_get(op->ptr, "deselect"))
+               selectmode = SELECT_ADD;
+       else
+               selectmode = SELECT_SUBTRACT;
+       
+       /* get settings from operator */
+       BLI_lasso_boundbox(&rect, data_lasso.mcords, data_lasso.mcords_tot);
+       BLI_rctf_rcti_copy(&rect_fl, &rect);
+       
+       /* apply borderselect action */
+       region_select_action_keys(&ac, &rect_fl, BEZT_OK_CHANNEL_LASSO, selectmode, &data_lasso);
+       
+       MEM_freeN((void *)data_lasso.mcords);
+       
+       /* send notifier that keyframe selection has changed */
+       WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void ACTION_OT_select_lasso(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Lasso Select";
+       ot->description = "Select keyframe points using lasso selection";
+       ot->idname = "ACTION_OT_select_lasso";
+       
+       /* api callbacks */
+       ot->invoke = WM_gesture_lasso_invoke;
+       ot->modal = WM_gesture_lasso_modal;
+       ot->exec = actkeys_lassoselect_exec;
+       ot->poll = ED_operator_action_active;
+       ot->cancel = WM_gesture_lasso_cancel;
+       
+       /* flags */
+       ot->flag = OPTYPE_UNDO;
+       
+       /* properties */
+       RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
+       RNA_def_boolean(ot->srna, "deselect", false, "Deselect", "Deselect rather than select items");
+       RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend selection instead of deselecting everything first");
+}
+
+/* ------------------- */
+
+static int action_circle_select_exec(bContext *C, wmOperator *op)
+{
+       bAnimContext ac;
+       const int gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
+       const short selectmode = (gesture_mode == GESTURE_MODAL_SELECT) ? SELECT_ADD : SELECT_SUBTRACT;
+       
+       KeyframeEdit_CircleData data = {0};
+       rctf rect_fl;
+       
+       float x = RNA_int_get(op->ptr, "x");
+       float y = RNA_int_get(op->ptr, "y");
+       float radius = RNA_int_get(op->ptr, "radius");
+
+       /* get editor data */
+       if (ANIM_animdata_get_context(C, &ac) == 0)
+               return OPERATOR_CANCELLED;
+       
+       data.mval[0] = x;
+       data.mval[1] = y;
+       data.radius_squared = radius * radius;
+       data.rectf_view = &rect_fl;
+       
+       rect_fl.xmin = x - radius;
+       rect_fl.xmax = x + radius;
+       rect_fl.ymin = y - radius;
+       rect_fl.ymax = y + radius;
+       
+       /* apply region select action */
+       region_select_action_keys(&ac, &rect_fl, BEZT_OK_CHANNEL_CIRCLE, selectmode, &data);
+       
+       /* send notifier that keyframe selection has changed */
+       WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void ACTION_OT_select_circle(wmOperatorType *ot)
+{
+       ot->name = "Circle Select";
+       ot->description = "Select keyframe points using circle selection";
+       ot->idname = "ACTION_OT_select_circle";
+       
+       ot->invoke = WM_gesture_circle_invoke;
+       ot->modal = WM_gesture_circle_modal;
+       ot->exec = action_circle_select_exec;
+       ot->poll = ED_operator_action_active;
+       ot->cancel = WM_gesture_circle_cancel;
+       
+       /* flags */
+       ot->flag = OPTYPE_UNDO;
+       
+       RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
+       RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
+       RNA_def_int(ot->srna, "radius", 1, 1, INT_MAX, "Radius", "", 1, INT_MAX);
+       RNA_def_int(ot->srna, "gesture_mode", 0, INT_MIN, INT_MAX, "Event Type", "", INT_MIN, INT_MAX);
+}
+
 /* ******************** Column Select Operator **************************** */
 /* This operator works in one of four ways:
  *     - 1) select all keyframes in the same frame as a selected one  (KKEY)
index 8c058d23cdec460a4c1dfebd399d1f1c0b781a02..67b960bfa534f77a92e3d74c120b73dc48793e36 100644 (file)
@@ -415,7 +415,7 @@ static int graphkeys_lassoselect_exec(bContext *C, wmOperator *op)
 {
        bAnimContext ac;
        
-       KeyframeEdit_LassoData data_lasso;
+       KeyframeEdit_LassoData data_lasso = {0};
        rcti rect;
        rctf rect_fl;
        
@@ -423,7 +423,6 @@ static int graphkeys_lassoselect_exec(bContext *C, wmOperator *op)
        bool incl_handles;
        bool extend;
        
-       
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
                return OPERATOR_CANCELLED;
@@ -501,7 +500,7 @@ static int graph_circle_select_exec(bContext *C, wmOperator *op)
        const short selectmode = (gesture_mode == GESTURE_MODAL_SELECT) ? SELECT_ADD : SELECT_SUBTRACT;
        bool incl_handles = false;
        
-       KeyframeEdit_CircleData data;
+       KeyframeEdit_CircleData data = {0};
        rctf rect_fl;
        
        float x = RNA_int_get(op->ptr, "x");
index a51648290db5586fb4e4bcc7ceee3248a0d29093..8968c2a4543c54c2d8c949aeb9ce66e5088b71a9 100644 (file)
@@ -4208,7 +4208,8 @@ static void gesture_circle_modal_keymap(wmKeyConfig *keyconf)
        WM_modalkeymap_assign(keymap, "MASK_OT_select_circle");
        WM_modalkeymap_assign(keymap, "NODE_OT_select_circle");
        WM_modalkeymap_assign(keymap, "GPENCIL_OT_select_circle");
-       WM_modalkeymap_assign(keymap, "GRAPH_OT_select_circle");        
+       WM_modalkeymap_assign(keymap, "GRAPH_OT_select_circle");
+       WM_modalkeymap_assign(keymap, "ACTION_OT_select_circle");
 
 }