GPencil: New interpolate strokes operators
authorAntonioya <blendergit@gmail.com>
Wed, 7 Sep 2016 07:54:50 +0000 (09:54 +0200)
committerAntonioya <blendergit@gmail.com>
Wed, 7 Sep 2016 08:02:52 +0000 (10:02 +0200)
Two new modal operators to create a grease pencil interpolate drawing
for one frame or a complete sequence between two frames.  For drawing
the temporary strokes in the viewport, two drawing handlers have been
added to manage 3D and 2D stuff.

Video: https://youtu.be/qxYwO5sSg5Y

The operator shortcuts are Ctrl+E and Ctrl+Shift+E. During the modal
operator, the interpolation can be adjusted using the mouse (moving
left/right) or the wheel mouse.

release/scripts/startup/bl_ui/properties_grease_pencil_common.py
source/blender/editors/gpencil/drawgpencil.c
source/blender/editors/gpencil/gpencil_edit.c
source/blender/editors/gpencil/gpencil_intern.h
source/blender/editors/gpencil/gpencil_ops.c
source/blender/editors/include/ED_gpencil.h
source/blender/makesdna/DNA_gpencil_types.h
source/blender/makesdna/DNA_scene_types.h
source/blender/makesrna/intern/rna_sculpt_paint.c

index e42f7263218f1715899167b697a17346bcd6d707..b1e9f52b8a687e49f5c69ede84d43dd85dbc1e1c 100644 (file)
@@ -216,6 +216,15 @@ class GreasePencilStrokeEditPanel:
         col.operator_menu_enum("gpencil.stroke_arrange", text="Arrange Strokes...", property="direction")
         col.operator("gpencil.stroke_change_color", text="Move to Color")
 
+        if is_3d_view:
+            layout.separator()
+            col = layout.column(align=True)
+            col.operator("gpencil.interpolate", text="Interpolate")
+            col.operator("gpencil.interpolate_sequence", text="Sequence")
+            settings = context.tool_settings.gpencil_sculpt
+            col.prop(settings, "interpolate_all_layers")
+            col.prop(settings, "interpolate_selected_only")
+
         layout.separator()
         col = layout.column(align=True)
         col.operator("gpencil.stroke_join", text="Join").type = 'JOIN'
index 4ef76f5ee25db1ecce50013ec44ccaf7e5b32ce3..48786e08f85d66ea3c387ea6e109eed304968919 100644 (file)
@@ -66,6 +66,7 @@
 #include "ED_gpencil.h"
 #include "ED_screen.h"
 #include "ED_view3d.h"
+#include "ED_space_api.h"
 
 #include "UI_interface_icons.h"
 #include "UI_resources.h"
@@ -74,7 +75,6 @@
 /* GREASE PENCIL DRAWING */
 
 /* ----- General Defines ------ */
-
 /* flags for sflag */
 typedef enum eDrawStrokeFlags {
        GP_DRAWDATA_NOSTATUS    = (1 << 0),   /* don't draw status info */
@@ -1338,6 +1338,39 @@ static void gp_draw_onionskins(
        
 }
 
+/* draw interpolate strokes (used only while operator is running) */
+void ED_gp_draw_interpolation(tGPDinterpolate *tgpi, const int type)
+{
+       tGPDinterpolate_layer *tgpil;
+       float diff_mat[4][4];
+       float color[4];
+
+       int offsx = 0;
+       int offsy = 0;
+       int winx = tgpi->ar->winx;
+       int winy = tgpi->ar->winy;
+
+       UI_GetThemeColor3fv(TH_GP_VERTEX_SELECT, color);
+       color[3] = 0.6f;
+       int dflag = 0; 
+       /* if 3d stuff, enable flags */
+       if (type == REGION_DRAW_POST_VIEW) {
+               dflag |= (GP_DRAWDATA_ONLY3D | GP_DRAWDATA_NOSTATUS);
+       }
+
+       /* turn on alpha-blending */
+       glEnable(GL_BLEND);
+       for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
+               /* calculate parent position */
+               ED_gpencil_parent_location(tgpil->gpl, diff_mat);
+               if (tgpil->interFrame) {
+                       gp_draw_strokes(tgpi->gpd, tgpil->interFrame, offsx, offsy, winx, winy, dflag, false,
+                               tgpil->gpl->thickness, 1.0f, color, true, true, diff_mat);
+               }
+       }
+       glDisable(GL_BLEND);
+}
+
 /* loop over gpencil data layers, drawing them */
 static void gp_draw_data_layers(
         bGPDbrush *brush, float alpha, bGPdata *gpd,
index 9f700e8716c39f91d48693dbc24c6db15cf9d6e4..c3b318a4d68816fa7510e386cf493ab2249d768d 100644 (file)
@@ -18,7 +18,7 @@
  * The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung
  * This is a new part of Blender
  *
- * Contributor(s): Joshua Leung
+ * Contributor(s): Joshua Leung, Antonio Vazquez
  *
  * ***** END GPL LICENSE BLOCK *****
  *
@@ -74,6 +74,8 @@
 #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"
 
@@ -1968,4 +1970,661 @@ void GPENCIL_OT_reproject(wmOperatorType *ot)
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
-/* ************************************************ */
+/* =========  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;
+
+       /* 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 */
+               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) / mid;
+       }
+       else {
+               tgpi->shift = -1.0f * (1.0f - (mpos / mid));
+       }
+
+       CLAMP(tgpi->shift, -1.0f, 1.0f);
+       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->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;
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       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 *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");
+               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");
+               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);
+                               /* 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, -1.0f, 1.0f);
+                       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, -1.0f, 1.0f);
+                       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;
+
+                               /* Grab shift from numeric input, and store this new value (the user see an int) */
+                               value = tgpi->shift * 100.0f;
+                               applyNumInput(&tgpi->num, &value);
+
+                               tgpi->shift = value / 100.0f;
+                               CLAMP(tgpi->shift, -1.0f, 1.0f);
+                               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);
+                               }
+                               /* 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 ========================== */
index 4178d49d6527b70c5953e7dbc27859bd2fa1508b..c1ed603273add9c90d682320479f925607b896ea 100644 (file)
@@ -335,6 +335,11 @@ void gpencil_undo_init(struct bGPdata *gpd);
 void gpencil_undo_push(struct bGPdata *gpd);
 void gpencil_undo_finish(void);
 
+/* interpolation ---------- */
+
+void GPENCIL_OT_interpolate(struct wmOperatorType *ot);
+void GPENCIL_OT_interpolate_sequence(struct wmOperatorType *ot);
+
 /* ****************************************************** */
 /* FILTERED ACTION DATA - TYPES  ---> XXX DEPRECEATED OLD ANIM SYSTEM CODE! */
 
index ae1c5554521881bfe66f747c95e47e8bd2aed738..e5d5bdbc831ad1b5d80b82cf366fee593a554cd8 100644 (file)
@@ -79,7 +79,6 @@ static void ed_keymap_gpencil_general(wmKeyConfig *keyconf)
        RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_ERASER);
        RNA_boolean_set(kmi->ptr, "wait_for_input", false);
        
-       
        /* Tablet Mappings for Drawing ------------------ */
        /* For now, only support direct drawing using the eraser, as most users using a tablet
         * may still want to use that as their primary pointing device!
@@ -144,7 +143,10 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
        kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0);
        RNA_string_set(kmi->ptr, "data_path_primary", "user_preferences.edit.grease_pencil_eraser_radius");
        
-       
+       /* Interpolation */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_interpolate", EKEY, KM_PRESS, KM_CTRL, 0);
+       WM_keymap_add_item(keymap, "GPENCIL_OT_interpolate_sequence", EKEY, KM_PRESS, KM_SHIFT | KM_CTRL, 0);
+
        /* Sculpting ------------------------------------- */
        
        /* Brush-Based Editing:
@@ -433,6 +435,10 @@ void ED_operatortypes_gpencil(void)
        WM_operatortype_append(GPENCIL_OT_brush_select);
 
        /* Editing (Time) --------------- */
+       
+       /* Interpolation */
+       WM_operatortype_append(GPENCIL_OT_interpolate);
+       WM_operatortype_append(GPENCIL_OT_interpolate_sequence);
 }
 
 void ED_operatormacros_gpencil(void)
index d526b0841cc3379862edba22ee6f53a9a4940e88..e53f29ef6b72ac1a15ae6b7a11cb070dc8923fd3 100644 (file)
@@ -30,6 +30,8 @@
 #ifndef __ED_GPENCIL_H__
 #define __ED_GPENCIL_H__
 
+#include "ED_numinput.h"
+
 struct ID;
 struct ListBase;
 struct bContext;
@@ -51,6 +53,33 @@ struct wmKeyConfig;
 
 
 /* ------------- Grease-Pencil Helpers ---------------- */
+typedef struct tGPDinterpolate_layer {
+       struct tGPDinterpolate_layer *next, *prev;
+
+       struct bGPDlayer *gpl;            /* layer */
+       struct bGPDframe *prevFrame;      /* frame before current frame (interpolate-from) */
+       struct bGPDframe *nextFrame;      /* frame after current frame (interpolate-to) */
+       struct bGPDframe *interFrame;     /* interpolated frame */
+       float factor;                     /* interpolate factor */
+
+} tGPDinterpolate_layer;
+
+/* Temporary interpolate operation data */
+typedef struct tGPDinterpolate {
+       struct Scene *scene;       /* current scene from context */
+       struct ScrArea *sa;        /* area where painting originated */
+       struct ARegion *ar;        /* region where painting originated */
+       struct bGPdata *gpd;       /* current GP datablock */
+
+       int cframe;                /* current frame number */
+       ListBase ilayers;   /* (tGPDinterpolate_layer) layers to be interpolated */
+       float shift;        /* -1/1 value for determining the displacement influence */
+       int flag;           /* flag from toolsettings */
+
+       NumInput num;       /* numeric input */
+       void *draw_handle_3d; /* handle for drawing strokes while operator is running 3d stuff */
+       void *draw_handle_screen; /* handle for drawing strokes while operator is running screen stuff */
+} tGPDinterpolate;
 
 /* Temporary 'Stroke Point' data 
  *
@@ -118,6 +147,7 @@ void ED_gpencil_draw_view2d(const struct bContext *C, bool onlyv2d);
 void ED_gpencil_draw_view3d(struct wmWindowManager *wm, struct Scene *scene, struct View3D *v3d, struct ARegion *ar, bool only3d);
 void ED_gpencil_draw_ex(struct Scene *scene, struct bGPdata *gpd, int winx, int winy,
                         const int cfra, const char spacetype);
+void ED_gp_draw_interpolation(struct tGPDinterpolate *tgpi, const int type);
 
 /* ----------- Grease-Pencil AnimEdit API ------------------ */
 bool  ED_gplayer_frames_looper(struct bGPDlayer *gpl, struct Scene *scene,
index 773d203bdb3e2bc943257d68d10746295b5df792..23b73424da53a28bb10dae5a98334de9ba3e00d1 100644 (file)
@@ -336,7 +336,7 @@ typedef enum eGPdata_Flag {
        /* Convenience/cache flag to make it easier to quickly toggle onion skinning on/off */
        GP_DATA_SHOW_ONIONSKINS = (1 << 9),
        /* Draw a green and red point to indicate start and end of the stroke */
-       GP_DATA_SHOW_DIRECTION = (1 << 10)   
+       GP_DATA_SHOW_DIRECTION = (1 << 10)
 } eGPdata_Flag;
 
 #endif /*  __DNA_GPENCIL_TYPES_H__ */
index 9f18a702aefdaec2b5ec7ed4243eea7743b2d8ea..5c5264afcba77ed72761ee80d7cd03077da5c152 100644 (file)
@@ -1167,7 +1167,11 @@ typedef enum eGP_BrushEdit_SettingsFlag {
        /* apply brush to strength */
        GP_BRUSHEDIT_FLAG_APPLY_STRENGTH = (1 << 2),
        /* apply brush to thickness */
-       GP_BRUSHEDIT_FLAG_APPLY_THICKNESS = (1 << 3)
+       GP_BRUSHEDIT_FLAG_APPLY_THICKNESS = (1 << 3),
+       /* apply interpolation to all layers */
+       GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS = (1 << 4),
+       /* apply interpolation to only selected */
+       GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED = (1 << 5)
 
 } eGP_BrushEdit_SettingsFlag;
 
index a8e80dbc4a35d5a3ab38e7ca9999b0c315ef50a4..7e1d0164eb4d6e5ae39058d06013fca8776c59a1 100644 (file)
@@ -1037,6 +1037,16 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna)
        RNA_def_property_ui_text(prop, "Affect Thickness", "The brush affects the thickness of the point");
        RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
 
+       prop = RNA_def_property(srna, "interpolate_all_layers", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS);
+       RNA_def_property_ui_text(prop, "Interpolate All Layers", "Interpolate all layers, not only active");
+       RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
+
+       prop = RNA_def_property(srna, "interpolate_selected_only", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED);
+       RNA_def_property_ui_text(prop, "Interpolate Selected Strokes", "Interpolate only selected strokes in the original frame");
+       RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
+
        prop = RNA_def_property(srna, "selection_alpha", PROP_FLOAT, PROP_NONE);
        RNA_def_property_float_sdna(prop, NULL, "alpha");
        RNA_def_property_range(prop, 0.0f, 1.0f);