diff options
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_edit.c')
-rw-r--r-- | source/blender/editors/gpencil/gpencil_edit.c | 793 |
1 files changed, 791 insertions, 2 deletions
diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 9f700e8716c..7175ec3e83d 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -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,791 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/* ************************************************ */ +/* ******************* Stroke subdivide ************************** */ +/* helper: Count how many points need to be inserted */ +static int gp_count_subdivision_cuts(bGPDstroke *gps) +{ + bGPDspoint *pt; + int i; + int totnewpoints = 0; + for (i = 0, pt = gps->points; i < gps->totpoints && pt; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + if (i + 1 < gps->totpoints){ + if (gps->points[i + 1].flag & GP_SPOINT_SELECT) { + ++totnewpoints; + }; + } + } + } + + return totnewpoints; +} +static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDspoint *temp_points; + const int cuts = RNA_int_get(op->ptr, "number_cuts"); + + int totnewpoints, oldtotpoints; + int i2; + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN(C, gpl, gps) + { + if (gps->flag & GP_STROKE_SELECT) { + /* loop as many times as cuts */ + for (int s = 0; s < cuts; s++) { + totnewpoints = gp_count_subdivision_cuts(gps); + if (totnewpoints == 0) { + continue; + } + /* duplicate points in a temp area */ + temp_points = MEM_dupallocN(gps->points); + oldtotpoints = gps->totpoints; + + /* resize the points arrys */ + gps->totpoints += totnewpoints; + gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); + gps->flag |= GP_STROKE_RECALC_CACHES; + + /* loop and interpolate */ + i2 = 0; + for (int i = 0; i < oldtotpoints; i++) { + bGPDspoint *pt = &temp_points[i]; + bGPDspoint *pt_final = &gps->points[i2]; + + /* copy current point */ + copy_v3_v3(&pt_final->x, &pt->x); + pt_final->pressure = pt->pressure; + pt_final->strength = pt->strength; + pt_final->time = pt->time; + pt_final->flag = pt->flag; + ++i2; + + /* if next point is selected add a half way point */ + if (pt->flag & GP_SPOINT_SELECT) { + if (i + 1 < oldtotpoints){ + if (temp_points[i + 1].flag & GP_SPOINT_SELECT) { + pt_final = &gps->points[i2]; + /* Interpolate all values */ + bGPDspoint *next = &temp_points[i + 1]; + interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); + pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); + pt_final->strength = interpf(pt->strength, next->strength, 0.5f); + CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); + pt_final->time = interpf(pt->time, next->time, 0.5f); + pt_final->flag |= GP_SPOINT_SELECT; + ++i2; + }; + } + } + } + /* free temp memory */ + MEM_freeN(temp_points); + } + } + } + GP_EDITABLE_STROKES_END; + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Subdivide Stroke"; + ot->idname = "GPENCIL_OT_stroke_subdivide"; + ot->description = "Subdivide between continuous selected points of the stroke adding a point half way between them"; + + /* api callbacks */ + ot->exec = gp_stroke_subdivide_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 10, "Number of Cuts", "", 1, 5); + /* avoid re-using last var because it can cause _very_ high value and annoy users */ + 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 ========================== */ |