Code Cleanup: Move GPencil Interpolation operators into their own file
authorJoshua Leung <aligorith@gmail.com>
Tue, 3 Jan 2017 10:29:21 +0000 (23:29 +1300)
committerJoshua Leung <aligorith@gmail.com>
Wed, 18 Jan 2017 06:41:57 +0000 (19:41 +1300)
The interpolation operators (and their associated code) occupied a significant
portion of gpencil_edit.c (which was getting a bit heavy). So, it's best to split
these out into a separate file to make things easier to handle, in preparation
for some further dev work.

source/blender/editors/gpencil/CMakeLists.txt
source/blender/editors/gpencil/gpencil_edit.c
source/blender/editors/gpencil/gpencil_interpolate.c [new file with mode: 0644]

index 6604d5955738fc58a4d51fdf8d44539d52a61c08..3d5317b2ebdba1a3a0c502ac84b0499262da24b9 100644 (file)
@@ -44,6 +44,7 @@ set(SRC
        gpencil_convert.c
        gpencil_data.c
        gpencil_edit.c
+       gpencil_interpolate.c
        gpencil_ops.c
        gpencil_paint.c
        gpencil_select.c
index 15f65b394a97e8124f4d4cf51ea12e5b61e681d2..f01dfee494acab0ee018349e9c70f5795a296579 100644 (file)
@@ -2098,673 +2098,3 @@ void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot)
        RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 
 }
-
-/* =========  Interpolation operators ========================== */
-/* Helper: Update point with interpolation */
-static void gp_interpolate_update_points(bGPDstroke *gps_from, bGPDstroke *gps_to, bGPDstroke *new_stroke, float factor)
-{
-       bGPDspoint *prev, *pt, *next;
-
-       /* update points */
-       for (int i = 0; i < new_stroke->totpoints; i++) {
-               prev = &gps_from->points[i];
-               pt = &new_stroke->points[i];
-               next = &gps_to->points[i];
-
-               /* Interpolate all values */
-               interp_v3_v3v3(&pt->x, &prev->x, &next->x, factor);
-               pt->pressure = interpf(prev->pressure, next->pressure, factor);
-               pt->strength = interpf(prev->strength, next->strength, factor);
-               CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
-       }
-}
-
-/* Helper: Update all strokes interpolated */
-static void gp_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgpi)
-{
-       tGPDinterpolate_layer *tgpil;
-       bGPDstroke *new_stroke, *gps_from, *gps_to;
-       int cStroke;
-       float factor;
-       float shift = tgpi->shift;
-
-       for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
-               factor = tgpil->factor + shift;
-               for (new_stroke = tgpil->interFrame->strokes.first; new_stroke; new_stroke = new_stroke->next) {
-                       if (new_stroke->totpoints == 0) {
-                               continue;
-                       }
-                       /* get strokes to interpolate */
-                       cStroke = BLI_findindex(&tgpil->interFrame->strokes, new_stroke);
-                       gps_from = BLI_findlink(&tgpil->prevFrame->strokes, cStroke);
-                       gps_to = BLI_findlink(&tgpil->nextFrame->strokes, cStroke);
-                       /* update points position */
-                       if ((gps_from) && (gps_to)) {
-                               gp_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
-                       }
-               }
-       }
-
-       WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
-}
-
-/* Helper: Verify valid strokes for interpolation */
-static bool gp_interpolate_check_todo(bContext *C, bGPdata *gpd)
-{
-       ToolSettings *ts = CTX_data_tool_settings(C);
-       int flag = ts->gp_sculpt.flag;
-
-       bGPDlayer *gpl;
-       bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
-       bGPDstroke *gps_from, *gps_to;
-       int fFrame;
-
-       /* get layers */
-       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
-               /* all layers or only active */
-               if (((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
-                       continue;
-               }
-               /* only editable and visible layers are considered */
-               if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
-                       continue;
-               }
-               /* read strokes */
-               for (gps_from = gpl->actframe->strokes.first; gps_from; gps_from = gps_from->next) {
-                       /* only selected */
-                       if ((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
-                               continue;
-                       }
-                       /* skip strokes that are invalid for current view */
-                       if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
-                               continue;
-                       }
-                       /* check if the color is editable */
-                       if (ED_gpencil_stroke_color_use(gpl, gps_from) == false) {
-                               continue;
-                       }
-                       /* get final stroke to interpolate */
-                       fFrame = BLI_findindex(&gpl->actframe->strokes, gps_from);
-                       gps_to = BLI_findlink(&gpl->actframe->next->strokes, fFrame);
-                       if (gps_to == NULL) {
-                               continue;
-                       }
-                       return 1;
-               }
-       }
-       return 0;
-}
-
-/* Helper: Create internal strokes interpolated */
-static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi)
-{
-       bGPDlayer *gpl;
-       bGPdata *gpd = tgpi->gpd;
-       tGPDinterpolate_layer *tgpil;
-       bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
-       bGPDstroke *gps_from, *gps_to, *new_stroke;
-       int fFrame;
-
-       /* save initial factor for active layer to define shift limits */
-       tgpi->init_factor = (float)(tgpi->cframe - active_gpl->actframe->framenum) / (active_gpl->actframe->next->framenum - active_gpl->actframe->framenum + 1);
-       /* limits are 100% below 0 and 100% over the 100% */
-       tgpi->low_limit = -1.0f - tgpi->init_factor;
-       tgpi->high_limit = 2.0f - tgpi->init_factor;
-
-       /* set layers */
-       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
-               /* all layers or only active */
-               if (((tgpi->flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
-                       continue;
-               }
-               /* only editable and visible layers are considered */
-               if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
-                       continue;
-               }
-               /* create temp data for each layer */
-               tgpil = NULL;
-               tgpil = MEM_callocN(sizeof(tGPDinterpolate_layer), "GPencil Interpolate Layer");
-
-               tgpil->gpl = gpl;
-               tgpil->prevFrame = gpl->actframe;
-               tgpil->nextFrame = gpl->actframe->next;
-
-               BLI_addtail(&tgpi->ilayers, tgpil);
-               /* create a new temporary frame */
-               tgpil->interFrame = MEM_callocN(sizeof(bGPDframe), "bGPDframe");
-               tgpil->interFrame->framenum = tgpi->cframe;
-
-               /* get interpolation factor by layer (usually must be equal for all layers, but not sure) */
-               tgpil->factor = (float)(tgpi->cframe - tgpil->prevFrame->framenum) / (tgpil->nextFrame->framenum - tgpil->prevFrame->framenum + 1);
-               /* create new strokes data with interpolated points reading original stroke */
-               for (gps_from = tgpil->prevFrame->strokes.first; gps_from; gps_from = gps_from->next) {
-                       bool valid = true;
-                       /* only selected */
-                       if ((tgpi->flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
-                               valid = false;
-                       }
-
-                       /* skip strokes that are invalid for current view */
-                       if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
-                               valid = false;
-                       }
-                       /* check if the color is editable */
-                       if (ED_gpencil_stroke_color_use(tgpil->gpl, gps_from) == false) {
-                               valid = false;
-                       }
-                       /* get final stroke to interpolate */
-                       fFrame = BLI_findindex(&tgpil->prevFrame->strokes, gps_from);
-                       gps_to = BLI_findlink(&tgpil->nextFrame->strokes, fFrame);
-                       if (gps_to == NULL) {
-                               valid = false;
-                       }
-                       /* create new stroke */
-                       new_stroke = MEM_dupallocN(gps_from);
-                       new_stroke->points = MEM_dupallocN(gps_from->points);
-                       new_stroke->triangles = MEM_dupallocN(gps_from->triangles);
-                       if (valid) {
-                               /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */
-                               if (gps_from->totpoints > gps_to->totpoints) {
-                                       new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points) * gps_to->totpoints);
-                                       new_stroke->totpoints = gps_to->totpoints;
-                                       new_stroke->tot_triangles = 0;
-                                       new_stroke->flag |= GP_STROKE_RECALC_CACHES;
-                               }
-                               /* update points position */
-                               gp_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor);
-                       }
-                       else {
-                               /* need an empty stroke to keep index correct for lookup, but resize to smallest size */
-                               new_stroke->totpoints = 0;
-                               new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points));
-                               new_stroke->tot_triangles = 0;
-                               new_stroke->triangles = MEM_recallocN(new_stroke->triangles, sizeof(*new_stroke->triangles));
-                       }
-                       /* add to strokes */
-                       BLI_addtail(&tgpil->interFrame->strokes, new_stroke);
-               }
-       }
-}
-
-/* Helper: calculate shift based on position of mouse (we only use x-axis for now.
-* since this is more convenient for users to do), and store new shift value
-*/
-static void gpencil_mouse_update_shift(tGPDinterpolate *tgpi, wmOperator *op, const wmEvent *event)
-{
-       float mid = (float)(tgpi->ar->winx - tgpi->ar->winrct.xmin) / 2.0f;
-       float mpos = event->x - tgpi->ar->winrct.xmin;
-       if (mpos >= mid) {
-               tgpi->shift = ((mpos - mid) * tgpi->high_limit) / mid;
-       }
-       else {
-               tgpi->shift = tgpi->low_limit - ((mpos * tgpi->low_limit) / mid);
-       }
-
-       CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
-       RNA_float_set(op->ptr, "shift", tgpi->shift);
-}
-
-/* Helper: Draw status message while the user is running the operator */
-static void gpencil_interpolate_status_indicators(tGPDinterpolate *p)
-{
-       Scene *scene = p->scene;
-       char status_str[UI_MAX_DRAW_STR];
-       char msg_str[UI_MAX_DRAW_STR];
-       BLI_strncpy(msg_str, IFACE_("GPencil Interpolation: ESC/RMB to cancel, Enter/LMB to confirm, WHEEL/MOVE to adjust, Factor"), UI_MAX_DRAW_STR);
-
-       if (hasNumInput(&p->num)) {
-               char str_offs[NUM_STR_REP_LEN];
-
-               outputNumInput(&p->num, str_offs, &scene->unit);
-
-               BLI_snprintf(status_str, sizeof(status_str), "%s: %s", msg_str, str_offs);
-       }
-       else {
-               BLI_snprintf(status_str, sizeof(status_str), "%s: %d %%", msg_str, (int)((p->init_factor + p->shift)  * 100.0f));
-       }
-
-       ED_area_headerprint(p->sa, status_str);
-}
-
-/* Helper: Update screen and stroke */
-static void gpencil_interpolate_update(bContext *C, wmOperator *op, tGPDinterpolate *tgpi)
-{
-       /* update shift indicator in header */
-       gpencil_interpolate_status_indicators(tgpi);
-       /* apply... */
-       tgpi->shift = RNA_float_get(op->ptr, "shift");
-       /* update points position */
-       gp_interpolate_update_strokes(C, tgpi);
-}
-
-/* init new temporary interpolation data */
-static bool gp_interpolate_set_init_values(bContext *C, wmOperator *op, tGPDinterpolate *tgpi)
-{
-       ToolSettings *ts = CTX_data_tool_settings(C);
-       bGPdata *gpd = CTX_data_gpencil_data(C);
-
-       /* set current scene and window */
-       tgpi->scene = CTX_data_scene(C);
-       tgpi->sa = CTX_wm_area(C);
-       tgpi->ar = CTX_wm_region(C);
-       tgpi->flag = ts->gp_sculpt.flag;
-
-       /* set current frame number */
-       tgpi->cframe = tgpi->scene->r.cfra;
-
-       /* set GP datablock */
-       tgpi->gpd = gpd;
-
-       /* set interpolation weight */
-       tgpi->shift = RNA_float_get(op->ptr, "shift");
-       /* set layers */
-       gp_interpolate_set_points(C, tgpi);
-
-       return 1;
-}
-
-/* Poll handler: check if context is suitable for interpolation */
-static int gpencil_interpolate_poll(bContext *C)
-{
-       bGPdata * gpd = CTX_data_gpencil_data(C);
-       bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
-       /* only 3D view */
-       if (CTX_wm_area(C)->spacetype != SPACE_VIEW3D) {
-               return 0;
-       }
-       /* need data to interpolate */
-       if (ELEM(NULL, gpd, gpl)) {
-               return 0;
-       }
-
-       return 1;
-}
-
-/* Allocate memory and initialize values */
-static tGPDinterpolate *gp_session_init_interpolation(bContext *C, wmOperator *op)
-{
-       tGPDinterpolate *tgpi = NULL;
-
-       /* create new context data */
-       tgpi = MEM_callocN(sizeof(tGPDinterpolate), "GPencil Interpolate Data");
-
-       /* define initial values */
-       gp_interpolate_set_init_values(C, op, tgpi);
-
-       /* return context data for running operator */
-       return tgpi;
-}
-
-/* Exit and free memory */
-static void gpencil_interpolate_exit(bContext *C, wmOperator *op)
-{
-       tGPDinterpolate *tgpi = op->customdata;
-       tGPDinterpolate_layer *tgpil;
-
-       /* don't assume that operator data exists at all */
-       if (tgpi) {
-               /* remove drawing handler */
-               if (tgpi->draw_handle_screen) {
-                       ED_region_draw_cb_exit(tgpi->ar->type, tgpi->draw_handle_screen);
-               }
-               if (tgpi->draw_handle_3d) {
-                       ED_region_draw_cb_exit(tgpi->ar->type, tgpi->draw_handle_3d);
-               }
-               /* clear status message area */
-               ED_area_headerprint(tgpi->sa, NULL);
-               /* finally, free memory used by temp data */
-               for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
-                       BKE_gpencil_free_strokes(tgpil->interFrame);
-                       MEM_freeN(tgpil->interFrame);
-               }
-
-               BLI_freelistN(&tgpi->ilayers);
-               MEM_freeN(tgpi);
-       }
-       WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
-
-       /* clear pointer */
-       op->customdata = NULL;
-}
-
-/* Cancel handler */
-static void gpencil_interpolate_cancel(bContext *C, wmOperator *op)
-{
-       /* this is just a wrapper around exit() */
-       gpencil_interpolate_exit(C, op);
-}
-
-/* Init interpolation: Allocate memory and set init values         */
-static int gpencil_interpolate_init(bContext *C, wmOperator *op)
-{
-       tGPDinterpolate *tgpi;
-       /* check context */
-       tgpi = op->customdata = gp_session_init_interpolation(C, op);
-       if (tgpi == NULL) {
-               /* something wasn't set correctly in context */
-               gpencil_interpolate_exit(C, op);
-               return 0;
-       }
-
-       /* everything is now setup ok */
-       return 1;
-}
-
-/* ********************** custom drawcall api ***************** */
-/* Helper: drawing callback for modal operator in screen mode */
-static void gpencil_interpolate_draw_screen(const struct bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
-{
-       wmOperator *op = arg;
-       struct tGPDinterpolate *tgpi = op->customdata;
-       ED_gp_draw_interpolation(tgpi, REGION_DRAW_POST_PIXEL);
-}
-
-/* Helper: drawing callback for modal operator in 3d mode */
-static void gpencil_interpolate_draw_3d(const struct bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
-{
-       wmOperator *op = arg;
-       struct tGPDinterpolate *tgpi = op->customdata;
-       ED_gp_draw_interpolation(tgpi, REGION_DRAW_POST_VIEW);
-}
-
-/* Invoke handler: Initialize the operator */
-static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
-{
-       wmWindow *win = CTX_wm_window(C);
-       Scene *scene = CTX_data_scene(C);
-       bGPdata * gpd = CTX_data_gpencil_data(C);
-       bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
-       tGPDinterpolate *tgpi = NULL;
-
-       /* cannot interpolate if not between 2 frames */
-       if ((gpl->actframe == NULL) || (gpl->actframe->next == NULL)) {
-               BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames in active layer");
-               return OPERATOR_CANCELLED;
-       }
-
-       /* cannot interpolate in extremes */
-       if ((gpl->actframe->framenum == scene->r.cfra) || (gpl->actframe->next->framenum == scene->r.cfra)) {
-               BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames in active layer");
-               return OPERATOR_CANCELLED;
-       }
-
-       /* need editable strokes */
-       if (!gp_interpolate_check_todo(C, gpd)) {
-               BKE_report(op->reports, RPT_ERROR, "Interpolation requires some editable stroke");
-               return OPERATOR_CANCELLED;
-       }
-
-       /* try to initialize context data needed */
-       if (!gpencil_interpolate_init(C, op)) {
-               if (op->customdata)
-                       MEM_freeN(op->customdata);
-               return OPERATOR_CANCELLED;
-       }
-       else
-               tgpi = op->customdata;
-
-       /* enable custom drawing handlers. It needs 2 handlers because can be strokes in 3d space and screen space and each handler use different
-          coord system */
-       tgpi->draw_handle_screen = ED_region_draw_cb_activate(tgpi->ar->type, gpencil_interpolate_draw_screen, op, REGION_DRAW_POST_PIXEL);
-       tgpi->draw_handle_3d = ED_region_draw_cb_activate(tgpi->ar->type, gpencil_interpolate_draw_3d, op, REGION_DRAW_POST_VIEW);
-       /* set cursor to indicate modal */
-       WM_cursor_modal_set(win, BC_EW_SCROLLCURSOR);
-       /* update shift indicator in header */
-       gpencil_interpolate_status_indicators(tgpi);
-       WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
-
-       /* add a modal handler for this operator */
-       WM_event_add_modal_handler(C, op);
-
-       return OPERATOR_RUNNING_MODAL;
-}
-
-/* Modal handler: Events handling during interactive part */
-static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent *event)
-{
-       tGPDinterpolate *tgpi = op->customdata;
-       wmWindow *win = CTX_wm_window(C);
-       bGPDframe *gpf_dst;
-       bGPDstroke *gps_src, *gps_dst;
-       tGPDinterpolate_layer *tgpil;
-       const bool has_numinput = hasNumInput(&tgpi->num);
-
-       switch (event->type) {
-               case LEFTMOUSE: /* confirm */
-               case RETKEY:
-               {
-                       /* return to normal cursor and header status */
-                       ED_area_headerprint(tgpi->sa, NULL);
-                       WM_cursor_modal_restore(win);
-
-                       /* insert keyframes as required... */
-                       for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
-                               gpf_dst = BKE_gpencil_layer_getframe(tgpil->gpl, tgpi->cframe, GP_GETFRAME_ADD_NEW);
-                               gpf_dst->key_type = BEZT_KEYTYPE_BREAKDOWN;
-                               
-                               /* copy strokes */
-                               BLI_listbase_clear(&gpf_dst->strokes);
-                               for (gps_src = tgpil->interFrame->strokes.first; gps_src; gps_src = gps_src->next) {
-                                       if (gps_src->totpoints == 0) {
-                                               continue;
-                                       }
-                                       /* make copy of source stroke, then adjust pointer to points too */
-                                       gps_dst = MEM_dupallocN(gps_src);
-                                       gps_dst->points = MEM_dupallocN(gps_src->points);
-                                       gps_dst->triangles = MEM_dupallocN(gps_src->triangles);
-                                       gps_dst->flag |= GP_STROKE_RECALC_CACHES;
-                                       BLI_addtail(&gpf_dst->strokes, gps_dst);
-                               }
-                       }
-                       /* clean up temp data */
-                       gpencil_interpolate_exit(C, op);
-
-                       /* done! */
-                       return OPERATOR_FINISHED;
-               }
-
-               case ESCKEY:    /* cancel */
-               case RIGHTMOUSE:
-               {
-                       /* return to normal cursor and header status */
-                       ED_area_headerprint(tgpi->sa, NULL);
-                       WM_cursor_modal_restore(win);
-
-                       /* clean up temp data */
-                       gpencil_interpolate_exit(C, op);
-
-                       /* canceled! */
-                       return OPERATOR_CANCELLED;
-               }
-               case WHEELUPMOUSE:
-               {
-                       tgpi->shift = tgpi->shift + 0.01f;
-                       CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
-                       RNA_float_set(op->ptr, "shift", tgpi->shift);
-                       /* update screen */
-                       gpencil_interpolate_update(C, op, tgpi);
-                       break;
-               }
-               case WHEELDOWNMOUSE:
-               {
-                       tgpi->shift = tgpi->shift - 0.01f;
-                       CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
-                       RNA_float_set(op->ptr, "shift", tgpi->shift);
-                       /* update screen */
-                       gpencil_interpolate_update(C, op, tgpi);
-                       break;
-               }
-               case MOUSEMOVE: /* calculate new position */
-               {
-                       /* only handle mousemove if not doing numinput */
-                       if (has_numinput == false) {
-                               /* update shift based on position of mouse */
-                               gpencil_mouse_update_shift(tgpi, op, event);
-                               /* update screen */
-                               gpencil_interpolate_update(C, op, tgpi);
-                       }
-                       break;
-               }
-               default:
-                       if ((event->val == KM_PRESS) && handleNumInput(C, &tgpi->num, event)) {
-                               float value;
-                               float factor = tgpi->init_factor;
-
-                               /* Grab shift from numeric input, and store this new value (the user see an int) */
-                               value = (factor + tgpi->shift) * 100.0f;
-                               applyNumInput(&tgpi->num, &value);
-                               tgpi->shift = value / 100.0f;
-                               /* recalculate the shift to get the right value in the frame scale */
-                               tgpi->shift = tgpi->shift - factor;
-
-                               CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
-                               RNA_float_set(op->ptr, "shift", tgpi->shift);
-
-                               /* update screen */
-                               gpencil_interpolate_update(C, op, tgpi);
-
-                               break;
-                       }
-                       else {
-                               /* unhandled event - allow to pass through */
-                               return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
-                       }
-       }
-
-       /* still running... */
-       return OPERATOR_RUNNING_MODAL;
-}
-
-/* Define modal operator for interpolation */
-void GPENCIL_OT_interpolate(wmOperatorType *ot)
-{
-       /* identifiers */
-       ot->name = "Grease Pencil Interpolation";
-       ot->idname = "GPENCIL_OT_interpolate";
-       ot->description = "Interpolate grease pencil strokes between frames";
-
-       /* api callbacks */
-       ot->invoke = gpencil_interpolate_invoke;
-       ot->modal = gpencil_interpolate_modal;
-       ot->cancel = gpencil_interpolate_cancel;
-       ot->poll = gpencil_interpolate_poll;
-
-       /* flags */
-       ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
-
-       RNA_def_float_percentage(ot->srna, "shift", 0.0f, -1.0f, 1.0f, "Shift", "Displacement factor for the interpolate operation", -0.9f, 0.9f);
-}
-
-/* =============== Interpolate sequence ===============*/
-/* Create Sequence Interpolation */
-static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op)
-{
-       Scene *scene = CTX_data_scene(C);
-       ToolSettings *ts = CTX_data_tool_settings(C);
-       bGPdata * gpd = CTX_data_gpencil_data(C);
-       bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
-       bGPDlayer *gpl;
-       bGPDframe *prevFrame, *nextFrame, *interFrame;
-       bGPDstroke *gps_from, *gps_to, *new_stroke;
-       float factor;
-       int cframe, fFrame;
-       int flag = ts->gp_sculpt.flag;
-
-       /* cannot interpolate if not between 2 frames */
-       if ((active_gpl->actframe == NULL) || (active_gpl->actframe->next == NULL)) {
-               BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames");
-               return OPERATOR_CANCELLED;
-       }
-       /* cannot interpolate in extremes */
-       if ((active_gpl->actframe->framenum == scene->r.cfra) || (active_gpl->actframe->next->framenum == scene->r.cfra)) {
-               BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames");
-               return OPERATOR_CANCELLED;
-       }
-       
-       /* loop all layer to check if need interpolation */
-       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
-               /* all layers or only active */
-               if (((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
-                       continue;
-               }
-               /* only editable and visible layers are considered */
-               if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
-                       continue;
-               }
-               /* store extremes */
-               prevFrame = gpl->actframe;
-               nextFrame = gpl->actframe->next;
-               /* Loop over intermediary frames and create the interpolation */
-               for (cframe = prevFrame->framenum + 1; cframe < nextFrame->framenum; cframe++) {
-                       interFrame = NULL;
-
-                       /* get interpolation factor */
-                       factor = (float)(cframe - prevFrame->framenum) / (nextFrame->framenum - prevFrame->framenum + 1);
-
-                       /* create new strokes data with interpolated points reading original stroke */
-                       for (gps_from = prevFrame->strokes.first; gps_from; gps_from = gps_from->next) {
-                               /* only selected */
-                               if ((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
-                                       continue;
-                               }
-                               /* skip strokes that are invalid for current view */
-                               if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
-                                       continue;
-                               }
-                               /* check if the color is editable */
-                               if (ED_gpencil_stroke_color_use(gpl, gps_from) == false) {
-                                       continue;
-                               }
-                               /* get final stroke to interpolate */
-                               fFrame = BLI_findindex(&prevFrame->strokes, gps_from);
-                               gps_to = BLI_findlink(&nextFrame->strokes, fFrame);
-                               if (gps_to == NULL) {
-                                       continue;
-                               }
-                               /* create a new frame if needed */
-                               if (interFrame == NULL) {
-                                       interFrame = BKE_gpencil_layer_getframe(gpl, cframe, GP_GETFRAME_ADD_NEW);
-                                       interFrame->key_type = BEZT_KEYTYPE_BREAKDOWN;
-                               }
-                               /* create new stroke */
-                               new_stroke = MEM_dupallocN(gps_from);
-                               new_stroke->points = MEM_dupallocN(gps_from->points);
-                               new_stroke->triangles = MEM_dupallocN(gps_from->triangles);
-                               /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */
-                               if (gps_from->totpoints > gps_to->totpoints) {
-                                       new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points) * gps_to->totpoints);
-                                       new_stroke->totpoints = gps_to->totpoints;
-                                       new_stroke->tot_triangles = 0;
-                                       new_stroke->flag |= GP_STROKE_RECALC_CACHES;
-                               }
-                               /* update points position */
-                               gp_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
-
-                               /* add to strokes */
-                               BLI_addtail(&interFrame->strokes, new_stroke);
-                       }
-               }
-       }
-
-       /* notifiers */
-       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
-
-       return OPERATOR_FINISHED;
-}
-
-/* Define sequence interpolation               */
-void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot)
-{
-       /* identifiers */
-       ot->name = "Grease Pencil Sequence Interpolation";
-       ot->idname = "GPENCIL_OT_interpolate_sequence";
-       ot->description = "Interpolate full grease pencil strokes sequence between frames";
-
-       /* api callbacks */
-       ot->exec = gpencil_interpolate_seq_exec;
-       ot->poll = gpencil_interpolate_poll;
-
-       /* flags */
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-}
-/* =========  End Interpolation operators ========================== */
diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c
new file mode 100644 (file)
index 0000000..154776d
--- /dev/null
@@ -0,0 +1,753 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2016, Blender Foundation
+ * This is a new part of Blender
+ *
+ * Contributor(s): Antonio Vazquez, Joshua Leung
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ * Operators for interpolating new Grease Pencil frames from existing strokes
+ */
+
+/** \file blender/editors/gpencil/gpencil_interpolate.c
+ *  \ingroup edgpencil
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <math.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_blenlib.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+#include "DNA_view3d_types.h"
+#include "DNA_gpencil_types.h"
+
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_gpencil.h"
+#include "BKE_library.h"
+#include "BKE_report.h"
+#include "BKE_screen.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "UI_view2d.h"
+
+#include "ED_gpencil.h"
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_view3d.h"
+#include "ED_screen.h"
+#include "ED_space_api.h"
+
+#include "gpencil_intern.h"
+
+/* ************************************************ */
+
+/* =========  Interpolation operators ========================== */
+/* Helper: Update point with interpolation */
+static void gp_interpolate_update_points(bGPDstroke *gps_from, bGPDstroke *gps_to, bGPDstroke *new_stroke, float factor)
+{
+       bGPDspoint *prev, *pt, *next;
+
+       /* update points */
+       for (int i = 0; i < new_stroke->totpoints; i++) {
+               prev = &gps_from->points[i];
+               pt = &new_stroke->points[i];
+               next = &gps_to->points[i];
+
+               /* Interpolate all values */
+               interp_v3_v3v3(&pt->x, &prev->x, &next->x, factor);
+               pt->pressure = interpf(prev->pressure, next->pressure, factor);
+               pt->strength = interpf(prev->strength, next->strength, factor);
+               CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
+       }
+}
+
+/* Helper: Update all strokes interpolated */
+static void gp_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgpi)
+{
+       tGPDinterpolate_layer *tgpil;
+       bGPDstroke *new_stroke, *gps_from, *gps_to;
+       int cStroke;
+       float factor;
+       float shift = tgpi->shift;
+
+       for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
+               factor = tgpil->factor + shift;
+               for (new_stroke = tgpil->interFrame->strokes.first; new_stroke; new_stroke = new_stroke->next) {
+                       if (new_stroke->totpoints == 0) {
+                               continue;
+                       }
+                       /* get strokes to interpolate */
+                       cStroke = BLI_findindex(&tgpil->interFrame->strokes, new_stroke);
+                       gps_from = BLI_findlink(&tgpil->prevFrame->strokes, cStroke);
+                       gps_to = BLI_findlink(&tgpil->nextFrame->strokes, cStroke);
+                       /* update points position */
+                       if ((gps_from) && (gps_to)) {
+                               gp_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
+                       }
+               }
+       }
+
+       WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
+}
+
+/* Helper: Verify valid strokes for interpolation */
+static bool gp_interpolate_check_todo(bContext *C, bGPdata *gpd)
+{
+       ToolSettings *ts = CTX_data_tool_settings(C);
+       int flag = ts->gp_sculpt.flag;
+
+       bGPDlayer *gpl;
+       bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
+       bGPDstroke *gps_from, *gps_to;
+       int fFrame;
+
+       /* get layers */
+       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+               /* all layers or only active */
+               if (((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
+                       continue;
+               }
+               /* only editable and visible layers are considered */
+               if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
+                       continue;
+               }
+               /* read strokes */
+               for (gps_from = gpl->actframe->strokes.first; gps_from; gps_from = gps_from->next) {
+                       /* only selected */
+                       if ((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
+                               continue;
+                       }
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
+                               continue;
+                       }
+                       /* check if the color is editable */
+                       if (ED_gpencil_stroke_color_use(gpl, gps_from) == false) {
+                               continue;
+                       }
+                       /* get final stroke to interpolate */
+                       fFrame = BLI_findindex(&gpl->actframe->strokes, gps_from);
+                       gps_to = BLI_findlink(&gpl->actframe->next->strokes, fFrame);
+                       if (gps_to == NULL) {
+                               continue;
+                       }
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+/* Helper: Create internal strokes interpolated */
+static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi)
+{
+       bGPDlayer *gpl;
+       bGPdata *gpd = tgpi->gpd;
+       tGPDinterpolate_layer *tgpil;
+       bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
+       bGPDstroke *gps_from, *gps_to, *new_stroke;
+       int fFrame;
+
+       /* save initial factor for active layer to define shift limits */
+       tgpi->init_factor = (float)(tgpi->cframe - active_gpl->actframe->framenum) / (active_gpl->actframe->next->framenum - active_gpl->actframe->framenum + 1);
+       /* limits are 100% below 0 and 100% over the 100% */
+       tgpi->low_limit = -1.0f - tgpi->init_factor;
+       tgpi->high_limit = 2.0f - tgpi->init_factor;
+
+       /* set layers */
+       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+               /* all layers or only active */
+               if (((tgpi->flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
+                       continue;
+               }
+               /* only editable and visible layers are considered */
+               if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
+                       continue;
+               }
+               /* create temp data for each layer */
+               tgpil = NULL;
+               tgpil = MEM_callocN(sizeof(tGPDinterpolate_layer), "GPencil Interpolate Layer");
+
+               tgpil->gpl = gpl;
+               tgpil->prevFrame = gpl->actframe;
+               tgpil->nextFrame = gpl->actframe->next;
+
+               BLI_addtail(&tgpi->ilayers, tgpil);
+               /* create a new temporary frame */
+               tgpil->interFrame = MEM_callocN(sizeof(bGPDframe), "bGPDframe");
+               tgpil->interFrame->framenum = tgpi->cframe;
+
+               /* get interpolation factor by layer (usually must be equal for all layers, but not sure) */
+               tgpil->factor = (float)(tgpi->cframe - tgpil->prevFrame->framenum) / (tgpil->nextFrame->framenum - tgpil->prevFrame->framenum + 1);
+               /* create new strokes data with interpolated points reading original stroke */
+               for (gps_from = tgpil->prevFrame->strokes.first; gps_from; gps_from = gps_from->next) {
+                       bool valid = true;
+                       /* only selected */
+                       if ((tgpi->flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
+                               valid = false;
+                       }
+
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
+                               valid = false;
+                       }
+                       /* check if the color is editable */
+                       if (ED_gpencil_stroke_color_use(tgpil->gpl, gps_from) == false) {
+                               valid = false;
+                       }
+                       /* get final stroke to interpolate */
+                       fFrame = BLI_findindex(&tgpil->prevFrame->strokes, gps_from);
+                       gps_to = BLI_findlink(&tgpil->nextFrame->strokes, fFrame);
+                       if (gps_to == NULL) {
+                               valid = false;
+                       }
+                       /* create new stroke */
+                       new_stroke = MEM_dupallocN(gps_from);
+                       new_stroke->points = MEM_dupallocN(gps_from->points);
+                       new_stroke->triangles = MEM_dupallocN(gps_from->triangles);
+                       if (valid) {
+                               /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */
+                               if (gps_from->totpoints > gps_to->totpoints) {
+                                       new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points) * gps_to->totpoints);
+                                       new_stroke->totpoints = gps_to->totpoints;
+                                       new_stroke->tot_triangles = 0;
+                                       new_stroke->flag |= GP_STROKE_RECALC_CACHES;
+                               }
+                               /* update points position */
+                               gp_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor);
+                       }
+                       else {
+                               /* need an empty stroke to keep index correct for lookup, but resize to smallest size */
+                               new_stroke->totpoints = 0;
+                               new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points));
+                               new_stroke->tot_triangles = 0;
+                               new_stroke->triangles = MEM_recallocN(new_stroke->triangles, sizeof(*new_stroke->triangles));
+                       }
+                       /* add to strokes */
+                       BLI_addtail(&tgpil->interFrame->strokes, new_stroke);
+               }
+       }
+}
+
+/* Helper: calculate shift based on position of mouse (we only use x-axis for now.
+* since this is more convenient for users to do), and store new shift value
+*/
+static void gpencil_mouse_update_shift(tGPDinterpolate *tgpi, wmOperator *op, const wmEvent *event)
+{
+       float mid = (float)(tgpi->ar->winx - tgpi->ar->winrct.xmin) / 2.0f;
+       float mpos = event->x - tgpi->ar->winrct.xmin;
+       if (mpos >= mid) {
+               tgpi->shift = ((mpos - mid) * tgpi->high_limit) / mid;
+       }
+       else {
+               tgpi->shift = tgpi->low_limit - ((mpos * tgpi->low_limit) / mid);
+       }
+
+       CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
+       RNA_float_set(op->ptr, "shift", tgpi->shift);
+}
+
+/* Helper: Draw status message while the user is running the operator */
+static void gpencil_interpolate_status_indicators(tGPDinterpolate *p)
+{
+       Scene *scene = p->scene;
+       char status_str[UI_MAX_DRAW_STR];
+       char msg_str[UI_MAX_DRAW_STR];
+       BLI_strncpy(msg_str, IFACE_("GPencil Interpolation: ESC/RMB to cancel, Enter/LMB to confirm, WHEEL/MOVE to adjust, Factor"), UI_MAX_DRAW_STR);
+
+       if (hasNumInput(&p->num)) {
+               char str_offs[NUM_STR_REP_LEN];
+
+               outputNumInput(&p->num, str_offs, &scene->unit);
+
+               BLI_snprintf(status_str, sizeof(status_str), "%s: %s", msg_str, str_offs);
+       }
+       else {
+               BLI_snprintf(status_str, sizeof(status_str), "%s: %d %%", msg_str, (int)((p->init_factor + p->shift)  * 100.0f));
+       }
+
+       ED_area_headerprint(p->sa, status_str);
+}
+
+/* Helper: Update screen and stroke */
+static void gpencil_interpolate_update(bContext *C, wmOperator *op, tGPDinterpolate *tgpi)
+{
+       /* update shift indicator in header */
+       gpencil_interpolate_status_indicators(tgpi);
+       /* apply... */
+       tgpi->shift = RNA_float_get(op->ptr, "shift");
+       /* update points position */
+       gp_interpolate_update_strokes(C, tgpi);
+}
+
+/* init new temporary interpolation data */
+static bool gp_interpolate_set_init_values(bContext *C, wmOperator *op, tGPDinterpolate *tgpi)
+{
+       ToolSettings *ts = CTX_data_tool_settings(C);
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+
+       /* set current scene and window */
+       tgpi->scene = CTX_data_scene(C);
+       tgpi->sa = CTX_wm_area(C);
+       tgpi->ar = CTX_wm_region(C);
+       tgpi->flag = ts->gp_sculpt.flag;
+
+       /* set current frame number */
+       tgpi->cframe = tgpi->scene->r.cfra;
+
+       /* set GP datablock */
+       tgpi->gpd = gpd;
+
+       /* set interpolation weight */
+       tgpi->shift = RNA_float_get(op->ptr, "shift");
+       /* set layers */
+       gp_interpolate_set_points(C, tgpi);
+
+       return 1;
+}
+
+/* Poll handler: check if context is suitable for interpolation */
+static int gpencil_interpolate_poll(bContext *C)
+{
+       bGPdata * gpd = CTX_data_gpencil_data(C);
+       bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
+       /* only 3D view */
+       if (CTX_wm_area(C)->spacetype != SPACE_VIEW3D) {
+               return 0;
+       }
+       /* need data to interpolate */
+       if (ELEM(NULL, gpd, gpl)) {
+               return 0;
+       }
+
+       return 1;
+}
+
+/* Allocate memory and initialize values */
+static tGPDinterpolate *gp_session_init_interpolation(bContext *C, wmOperator *op)
+{
+       tGPDinterpolate *tgpi = NULL;
+
+       /* create new context data */
+       tgpi = MEM_callocN(sizeof(tGPDinterpolate), "GPencil Interpolate Data");
+
+       /* define initial values */
+       gp_interpolate_set_init_values(C, op, tgpi);
+
+       /* return context data for running operator */
+       return tgpi;
+}
+
+/* Exit and free memory */
+static void gpencil_interpolate_exit(bContext *C, wmOperator *op)
+{
+       tGPDinterpolate *tgpi = op->customdata;
+       tGPDinterpolate_layer *tgpil;
+
+       /* don't assume that operator data exists at all */
+       if (tgpi) {
+               /* remove drawing handler */
+               if (tgpi->draw_handle_screen) {
+                       ED_region_draw_cb_exit(tgpi->ar->type, tgpi->draw_handle_screen);
+               }
+               if (tgpi->draw_handle_3d) {
+                       ED_region_draw_cb_exit(tgpi->ar->type, tgpi->draw_handle_3d);
+               }
+               /* clear status message area */
+               ED_area_headerprint(tgpi->sa, NULL);
+               /* finally, free memory used by temp data */
+               for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
+                       BKE_gpencil_free_strokes(tgpil->interFrame);
+                       MEM_freeN(tgpil->interFrame);
+               }
+
+               BLI_freelistN(&tgpi->ilayers);
+               MEM_freeN(tgpi);
+       }
+       WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
+
+       /* clear pointer */
+       op->customdata = NULL;
+}
+
+/* Cancel handler */
+static void gpencil_interpolate_cancel(bContext *C, wmOperator *op)
+{
+       /* this is just a wrapper around exit() */
+       gpencil_interpolate_exit(C, op);
+}
+
+/* Init interpolation: Allocate memory and set init values         */
+static int gpencil_interpolate_init(bContext *C, wmOperator *op)
+{
+       tGPDinterpolate *tgpi;
+       /* check context */
+       tgpi = op->customdata = gp_session_init_interpolation(C, op);
+       if (tgpi == NULL) {
+               /* something wasn't set correctly in context */
+               gpencil_interpolate_exit(C, op);
+               return 0;
+       }
+
+       /* everything is now setup ok */
+       return 1;
+}
+
+/* ********************** custom drawcall api ***************** */
+/* Helper: drawing callback for modal operator in screen mode */
+static void gpencil_interpolate_draw_screen(const struct bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
+{
+       wmOperator *op = arg;
+       struct tGPDinterpolate *tgpi = op->customdata;
+       ED_gp_draw_interpolation(tgpi, REGION_DRAW_POST_PIXEL);
+}
+
+/* Helper: drawing callback for modal operator in 3d mode */
+static void gpencil_interpolate_draw_3d(const struct bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
+{
+       wmOperator *op = arg;
+       struct tGPDinterpolate *tgpi = op->customdata;
+       ED_gp_draw_interpolation(tgpi, REGION_DRAW_POST_VIEW);
+}
+
+/* Invoke handler: Initialize the operator */
+static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+       wmWindow *win = CTX_wm_window(C);
+       Scene *scene = CTX_data_scene(C);
+       bGPdata * gpd = CTX_data_gpencil_data(C);
+       bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
+       tGPDinterpolate *tgpi = NULL;
+
+       /* cannot interpolate if not between 2 frames */
+       if ((gpl->actframe == NULL) || (gpl->actframe->next == NULL)) {
+               BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames in active layer");
+               return OPERATOR_CANCELLED;
+       }
+
+       /* cannot interpolate in extremes */
+       if ((gpl->actframe->framenum == scene->r.cfra) || (gpl->actframe->next->framenum == scene->r.cfra)) {
+               BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames in active layer");
+               return OPERATOR_CANCELLED;
+       }
+
+       /* need editable strokes */
+       if (!gp_interpolate_check_todo(C, gpd)) {
+               BKE_report(op->reports, RPT_ERROR, "Interpolation requires some editable stroke");
+               return OPERATOR_CANCELLED;
+       }
+
+       /* try to initialize context data needed */
+       if (!gpencil_interpolate_init(C, op)) {
+               if (op->customdata)
+                       MEM_freeN(op->customdata);
+               return OPERATOR_CANCELLED;
+       }
+       else
+               tgpi = op->customdata;
+
+       /* enable custom drawing handlers. It needs 2 handlers because can be strokes in 3d space and screen space and each handler use different
+          coord system */
+       tgpi->draw_handle_screen = ED_region_draw_cb_activate(tgpi->ar->type, gpencil_interpolate_draw_screen, op, REGION_DRAW_POST_PIXEL);
+       tgpi->draw_handle_3d = ED_region_draw_cb_activate(tgpi->ar->type, gpencil_interpolate_draw_3d, op, REGION_DRAW_POST_VIEW);
+       /* set cursor to indicate modal */
+       WM_cursor_modal_set(win, BC_EW_SCROLLCURSOR);
+       /* update shift indicator in header */
+       gpencil_interpolate_status_indicators(tgpi);
+       WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
+
+       /* add a modal handler for this operator */
+       WM_event_add_modal_handler(C, op);
+
+       return OPERATOR_RUNNING_MODAL;
+}
+
+/* Modal handler: Events handling during interactive part */
+static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+       tGPDinterpolate *tgpi = op->customdata;
+       wmWindow *win = CTX_wm_window(C);
+       bGPDframe *gpf_dst;
+       bGPDstroke *gps_src, *gps_dst;
+       tGPDinterpolate_layer *tgpil;
+       const bool has_numinput = hasNumInput(&tgpi->num);
+
+       switch (event->type) {
+               case LEFTMOUSE: /* confirm */
+               case RETKEY:
+               {
+                       /* return to normal cursor and header status */
+                       ED_area_headerprint(tgpi->sa, NULL);
+                       WM_cursor_modal_restore(win);
+
+                       /* insert keyframes as required... */
+                       for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
+                               gpf_dst = BKE_gpencil_layer_getframe(tgpil->gpl, tgpi->cframe, GP_GETFRAME_ADD_NEW);
+                               gpf_dst->key_type = BEZT_KEYTYPE_BREAKDOWN;
+                               
+                               /* copy strokes */
+                               BLI_listbase_clear(&gpf_dst->strokes);
+                               for (gps_src = tgpil->interFrame->strokes.first; gps_src; gps_src = gps_src->next) {
+                                       if (gps_src->totpoints == 0) {
+                                               continue;
+                                       }
+                                       /* make copy of source stroke, then adjust pointer to points too */
+                                       gps_dst = MEM_dupallocN(gps_src);
+                                       gps_dst->points = MEM_dupallocN(gps_src->points);
+                                       gps_dst->triangles = MEM_dupallocN(gps_src->triangles);
+                                       gps_dst->flag |= GP_STROKE_RECALC_CACHES;
+                                       BLI_addtail(&gpf_dst->strokes, gps_dst);
+                               }
+                       }
+                       /* clean up temp data */
+                       gpencil_interpolate_exit(C, op);
+
+                       /* done! */
+                       return OPERATOR_FINISHED;
+               }
+
+               case ESCKEY:    /* cancel */
+               case RIGHTMOUSE:
+               {
+                       /* return to normal cursor and header status */
+                       ED_area_headerprint(tgpi->sa, NULL);
+                       WM_cursor_modal_restore(win);
+
+                       /* clean up temp data */
+                       gpencil_interpolate_exit(C, op);
+
+                       /* canceled! */
+                       return OPERATOR_CANCELLED;
+               }
+               case WHEELUPMOUSE:
+               {
+                       tgpi->shift = tgpi->shift + 0.01f;
+                       CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
+                       RNA_float_set(op->ptr, "shift", tgpi->shift);
+                       /* update screen */
+                       gpencil_interpolate_update(C, op, tgpi);
+                       break;
+               }
+               case WHEELDOWNMOUSE:
+               {
+                       tgpi->shift = tgpi->shift - 0.01f;
+                       CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
+                       RNA_float_set(op->ptr, "shift", tgpi->shift);
+                       /* update screen */
+                       gpencil_interpolate_update(C, op, tgpi);
+                       break;
+               }
+               case MOUSEMOVE: /* calculate new position */
+               {
+                       /* only handle mousemove if not doing numinput */
+                       if (has_numinput == false) {
+                               /* update shift based on position of mouse */
+                               gpencil_mouse_update_shift(tgpi, op, event);
+                               /* update screen */
+                               gpencil_interpolate_update(C, op, tgpi);
+                       }
+                       break;
+               }
+               default:
+                       if ((event->val == KM_PRESS) && handleNumInput(C, &tgpi->num, event)) {
+                               float value;
+                               float factor = tgpi->init_factor;
+
+                               /* Grab shift from numeric input, and store this new value (the user see an int) */
+                               value = (factor + tgpi->shift) * 100.0f;
+                               applyNumInput(&tgpi->num, &value);
+                               tgpi->shift = value / 100.0f;
+                               /* recalculate the shift to get the right value in the frame scale */
+                               tgpi->shift = tgpi->shift - factor;
+
+                               CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
+                               RNA_float_set(op->ptr, "shift", tgpi->shift);
+
+                               /* update screen */
+                               gpencil_interpolate_update(C, op, tgpi);
+
+                               break;
+                       }
+                       else {
+                               /* unhandled event - allow to pass through */
+                               return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
+                       }
+       }
+
+       /* still running... */
+       return OPERATOR_RUNNING_MODAL;
+}
+
+/* Define modal operator for interpolation */
+void GPENCIL_OT_interpolate(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Grease Pencil Interpolation";
+       ot->idname = "GPENCIL_OT_interpolate";
+       ot->description = "Interpolate grease pencil strokes between frames";
+
+       /* api callbacks */
+       ot->invoke = gpencil_interpolate_invoke;
+       ot->modal = gpencil_interpolate_modal;
+       ot->cancel = gpencil_interpolate_cancel;
+       ot->poll = gpencil_interpolate_poll;
+
+       /* flags */
+       ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
+
+       RNA_def_float_percentage(ot->srna, "shift", 0.0f, -1.0f, 1.0f, "Shift", "Displacement factor for the interpolate operation", -0.9f, 0.9f);
+}
+
+/* =============== Interpolate sequence ===============*/
+/* Create Sequence Interpolation */
+static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op)
+{
+       Scene *scene = CTX_data_scene(C);
+       ToolSettings *ts = CTX_data_tool_settings(C);
+       bGPdata * gpd = CTX_data_gpencil_data(C);
+       bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
+       bGPDlayer *gpl;
+       bGPDframe *prevFrame, *nextFrame, *interFrame;
+       bGPDstroke *gps_from, *gps_to, *new_stroke;
+       float factor;
+       int cframe, fFrame;
+       int flag = ts->gp_sculpt.flag;
+
+       /* cannot interpolate if not between 2 frames */
+       if ((active_gpl->actframe == NULL) || (active_gpl->actframe->next == NULL)) {
+               BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames");
+               return OPERATOR_CANCELLED;
+       }
+       /* cannot interpolate in extremes */
+       if ((active_gpl->actframe->framenum == scene->r.cfra) || (active_gpl->actframe->next->framenum == scene->r.cfra)) {
+               BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* loop all layer to check if need interpolation */
+       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+               /* all layers or only active */
+               if (((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
+                       continue;
+               }
+               /* only editable and visible layers are considered */
+               if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
+                       continue;
+               }
+               /* store extremes */
+               prevFrame = gpl->actframe;
+               nextFrame = gpl->actframe->next;
+               /* Loop over intermediary frames and create the interpolation */
+               for (cframe = prevFrame->framenum + 1; cframe < nextFrame->framenum; cframe++) {
+                       interFrame = NULL;
+
+                       /* get interpolation factor */
+                       factor = (float)(cframe - prevFrame->framenum) / (nextFrame->framenum - prevFrame->framenum + 1);
+
+                       /* create new strokes data with interpolated points reading original stroke */
+                       for (gps_from = prevFrame->strokes.first; gps_from; gps_from = gps_from->next) {
+                               /* only selected */
+                               if ((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
+                                       continue;
+                               }
+                               /* skip strokes that are invalid for current view */
+                               if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
+                                       continue;
+                               }
+                               /* check if the color is editable */
+                               if (ED_gpencil_stroke_color_use(gpl, gps_from) == false) {
+                                       continue;
+                               }
+                               /* get final stroke to interpolate */
+                               fFrame = BLI_findindex(&prevFrame->strokes, gps_from);
+                               gps_to = BLI_findlink(&nextFrame->strokes, fFrame);
+                               if (gps_to == NULL) {
+                                       continue;
+                               }
+                               /* create a new frame if needed */
+                               if (interFrame == NULL) {
+                                       interFrame = BKE_gpencil_layer_getframe(gpl, cframe, GP_GETFRAME_ADD_NEW);
+                                       interFrame->key_type = BEZT_KEYTYPE_BREAKDOWN;
+                               }
+                               /* create new stroke */
+                               new_stroke = MEM_dupallocN(gps_from);
+                               new_stroke->points = MEM_dupallocN(gps_from->points);
+                               new_stroke->triangles = MEM_dupallocN(gps_from->triangles);
+                               /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */
+                               if (gps_from->totpoints > gps_to->totpoints) {
+                                       new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points) * gps_to->totpoints);
+                                       new_stroke->totpoints = gps_to->totpoints;
+                                       new_stroke->tot_triangles = 0;
+                                       new_stroke->flag |= GP_STROKE_RECALC_CACHES;
+                               }
+                               /* update points position */
+                               gp_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
+
+                               /* add to strokes */
+                               BLI_addtail(&interFrame->strokes, new_stroke);
+                       }
+               }
+       }
+
+       /* notifiers */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+
+       return OPERATOR_FINISHED;
+}
+
+/* Define sequence interpolation               */
+void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Grease Pencil Sequence Interpolation";
+       ot->idname = "GPENCIL_OT_interpolate_sequence";
+       ot->description = "Interpolate full grease pencil strokes sequence between frames";
+
+       /* api callbacks */
+       ot->exec = gpencil_interpolate_seq_exec;
+       ot->poll = gpencil_interpolate_poll;
+
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* =========  End Interpolation operators ========================== */