diff options
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_interpolate.c')
-rw-r--r-- | source/blender/editors/gpencil/gpencil_interpolate.c | 1011 |
1 files changed, 772 insertions, 239 deletions
diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index ecd243ed595..6a2eae934a2 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -32,6 +32,7 @@ #include "BLI_blenlib.h" #include "BLI_easing.h" +#include "BLI_ghash.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -53,6 +54,7 @@ #include "BKE_report.h" #include "UI_interface.h" +#include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" @@ -67,6 +69,78 @@ #include "gpencil_intern.h" +/* Temporary interpolate operation data */ +typedef struct tGPDinterpolate_layer { + struct tGPDinterpolate_layer *next, *prev; + + /** layer */ + struct bGPDlayer *gpl; + /** frame before current frame (interpolate-from) */ + struct bGPDframe *prevFrame; + /** frame after current frame (interpolate-to) */ + struct bGPDframe *nextFrame; + /** interpolated frame */ + struct bGPDframe *interFrame; + /** interpolate factor */ + float factor; + + /* Hash tablets to create temp relationship between strokes. */ + struct GHash *used_strokes; + struct GHash *pair_strokes; + +} tGPDinterpolate_layer; + +typedef struct tGPDinterpolate { + /** Current depsgraph from context */ + struct Depsgraph *depsgraph; + /** current scene from context */ + struct Scene *scene; + /** area where painting originated */ + struct ScrArea *area; + /** region where painting originated */ + struct ARegion *region; + /** current object */ + struct Object *ob; + /** current GP datablock */ + struct bGPdata *gpd; + /** current material */ + struct Material *mat; + /* Space Conversion Data */ + struct GP_SpaceConversion gsc; + + /** current frame number */ + int cframe; + /** (tGPDinterpolate_layer) layers to be interpolated */ + ListBase ilayers; + /** value for determining the displacement influence */ + float shift; + /** initial interpolation factor for active layer */ + float init_factor; + /** shift low limit (-100%) */ + float low_limit; + /** shift upper limit (200%) */ + float high_limit; + /** flag from toolsettings */ + int flag; + /** Flip mode. */ + int flipmode; + /** smooth factor */ + float smooth_factor; + /** smooth iterations */ + int smooth_steps; + + NumInput num; /* numeric input */ +} tGPDinterpolate; + +typedef enum eGP_InterpolateFlipMode { + /* No flip. */ + GP_INTERPOLATE_NOFLIP = 0, + /* Flip always. */ + GP_INTERPOLATE_FLIP = 1, + /* Flip if needed. */ + GP_INTERPOLATE_FLIPAUTO = 2, +} eGP_InterpolateFlipMode; + /* ************************************************ */ /* Core/Shared Utilities */ @@ -79,17 +153,150 @@ static bool gpencil_view3d_poll(bContext *C) /* only 3D view */ ScrArea *area = CTX_wm_area(C); if (area && area->spacetype != SPACE_VIEW3D) { - return 0; + return false; } /* need data to interpolate */ if (ELEM(NULL, gpd, gpl)) { - return 0; + return false; } - return 1; + return true; +} + +/* Return if the stroke must be flipped or not. The logic of the calculation + * is to check if the lines from extremes crossed. All is done in 2D. */ +static bool gpencil_stroke_need_flip(Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + GP_SpaceConversion *gsc, + bGPDstroke *gps_from, + bGPDstroke *gps_to) +{ + float diff_mat[4][4]; + /* calculate parent matrix */ + BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat); + bGPDspoint *pt, pt_dummy_ps; + float v1a[2], v1b[2], v2a[2], v2b[2]; + + /* Line from start of strokes. */ + pt = &gps_from->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v1a[0], &v1a[1]); + + pt = &gps_to->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v1b[0], &v1b[1]); + + /* Line from end of strokes. */ + pt = &gps_from->points[gps_from->totpoints - 1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v2a[0], &v2a[1]); + + pt = &gps_to->points[gps_to->totpoints - 1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v2b[0], &v2b[1]); + + if (isect_seg_seg_v2(v1a, v1b, v2a, v2b) == ISECT_LINE_LINE_CROSS) { + return true; + } + + return false; +} + +/* Return the stroke related to the selection index, returning the stroke with + * the smallest selection index greater than reference index. */ +static bGPDstroke *gpencil_stroke_get_related(GHash *used_strokes, + bGPDframe *gpf, + const int reference_index) +{ + bGPDstroke *gps_found = NULL; + int lower_index = INT_MAX; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->select_index > reference_index) { + if (!BLI_ghash_haskey(used_strokes, gps)) { + if (gps->select_index < lower_index) { + lower_index = gps->select_index; + gps_found = gps; + } + } + } + } + + /* Set as used. */ + if (gps_found) { + BLI_ghash_insert(used_strokes, gps_found, gps_found); + } + + return gps_found; +} + +/* Load a Hash with the relationship between strokes. */ +static void gpencil_stroke_pair_table(bContext *C, + tGPDinterpolate *tgpi, + tGPDinterpolate_layer *tgpil) +{ + bGPdata *gpd = tgpi->gpd; + const bool only_selected = ((GPENCIL_EDIT_MODE(gpd)) && + ((tgpi->flag & GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED) != 0)); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + + /* Create hash tablets with relationship between strokes. */ + tgpil->used_strokes = BLI_ghash_ptr_new(__func__); + tgpil->pair_strokes = BLI_ghash_ptr_new(__func__); + + /* Create a table with source and target pair of strokes. */ + LISTBASE_FOREACH (bGPDstroke *, gps_from, &tgpil->prevFrame->strokes) { + bGPDstroke *gps_to = NULL; + /* only selected */ + if ((GPENCIL_EDIT_MODE(gpd)) && (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 material is editable. */ + if (ED_gpencil_stroke_material_editable(tgpi->ob, tgpil->gpl, gps_from) == false) { + continue; + } + /* Try to get the related stroke. */ + if ((is_multiedit) && (gps_from->select_index > 0)) { + gps_to = gpencil_stroke_get_related( + tgpil->used_strokes, tgpil->nextFrame, gps_from->select_index); + } + /* If not found, get final stroke to interpolate using position in the array. */ + if (gps_to == NULL) { + int fFrame = BLI_findindex(&tgpil->prevFrame->strokes, gps_from); + gps_to = BLI_findlink(&tgpil->nextFrame->strokes, fFrame); + } + + if (ELEM(NULL, gps_from, gps_to)) { + continue; + } + /* Insert the pair entry in the hash table. */ + BLI_ghash_insert(tgpil->pair_strokes, gps_from, gps_to); + } } +static void gpencil_interpolate_smooth_stroke(bGPDstroke *gps, + float smooth_factor, + int smooth_steps) +{ + if (smooth_factor == 0.0f) { + return; + } + + float reduce = 0.0f; + for (int r = 0; r < smooth_steps; r++) { + for (int i = 0; i < gps->totpoints - 1; i++) { + BKE_gpencil_stroke_smooth(gps, i, smooth_factor - reduce); + BKE_gpencil_stroke_smooth_strength(gps, i, smooth_factor); + } + reduce += 0.25f; /* reduce the factor */ + } +} /* Perform interpolation */ static void gpencil_interpolate_update_points(const bGPDstroke *gps_from, const bGPDstroke *gps_to, @@ -112,7 +319,7 @@ static void gpencil_interpolate_update_points(const bGPDstroke *gps_from, /* ****************** Interpolate Interactive *********************** */ /* Helper: free all temp strokes for display. */ -static void gpencil_interpolate_free_temp_strokes(bGPDframe *gpf) +static void gpencil_interpolate_free_tagged_strokes(bGPDframe *gpf) { if (gpf == NULL) { return; @@ -152,33 +359,34 @@ static void gpencil_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgp const float factor = tgpil->factor + shift; bGPDframe *gpf = tgpil->gpl->actframe; - /* Free temp strokes. */ - gpencil_interpolate_free_temp_strokes(gpf); + /* Free temp strokes used for display. */ + gpencil_interpolate_free_tagged_strokes(gpf); - LISTBASE_FOREACH (bGPDstroke *, new_stroke, &tgpil->interFrame->strokes) { - bGPDstroke *gps_from, *gps_to; - int stroke_idx; + /* Clear previous interpolations. */ + gpencil_interpolate_free_tagged_strokes(tgpil->interFrame); - if (new_stroke->totpoints == 0) { - continue; - } + GHashIterator gh_iter; + GHASH_ITER (gh_iter, tgpil->pair_strokes) { + bGPDstroke *gps_from = (bGPDstroke *)BLI_ghashIterator_getKey(&gh_iter); + bGPDstroke *gps_to = (bGPDstroke *)BLI_ghashIterator_getValue(&gh_iter); + /* Create new stroke. */ + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); + new_stroke->flag |= GP_STROKE_TAG; + new_stroke->select_index = 0; - /* get strokes to interpolate */ - stroke_idx = BLI_findindex(&tgpil->interFrame->strokes, new_stroke); + /* Update points position. */ + gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); - gps_from = BLI_findlink(&tgpil->prevFrame->strokes, stroke_idx); - gps_to = BLI_findlink(&tgpil->nextFrame->strokes, stroke_idx); - - /* update points position */ - if ((gps_from) && (gps_to)) { - gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + /* Add to strokes. */ + BLI_addtail(&tgpil->interFrame->strokes, new_stroke); - /* Add temp strokes. */ - if (gpf) { - bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true, true); - gps_eval->flag |= GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_eval); - } + /* Add temp strokes to display. */ + if (gpf) { + bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true, true); + gps_eval->flag |= GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_eval); } } } @@ -187,65 +395,50 @@ static void gpencil_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgp WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); } -/* Helper: Verify valid strokes for interpolation */ -static bool gpencil_interpolate_check_todo(bContext *C, bGPdata *gpd) +/* Helper: Get previous keyframe. */ +static bGPDframe *gpencil_get_previous_keyframe(bGPDlayer *gpl, int cfra) { - Object *ob = CTX_data_active_object(C); - ToolSettings *ts = CTX_data_tool_settings(C); - eGP_Interpolate_SettingsFlag flag = ts->gp_interpolate.flag; + if (gpl->actframe != NULL && gpl->actframe->framenum < cfra && + gpl->actframe->key_type == BEZT_KEYTYPE_KEYFRAME) { + return gpl->actframe; + } - /* get layers */ - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - /* all layers or only active */ - if (!(flag & GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS) && !(gpl->flag & GP_LAYER_ACTIVE)) { + LISTBASE_FOREACH_BACKWARD (bGPDframe *, gpf, &gpl->frames) { + if (gpf->key_type != BEZT_KEYTYPE_KEYFRAME) { continue; } - /* only editable and visible layers are considered */ - if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { + if (gpf->framenum >= cfra) { continue; } + return gpf; + } - /* read strokes */ - LISTBASE_FOREACH (bGPDstroke *, gps_from, &gpl->actframe->strokes) { - bGPDstroke *gps_to; - int fFrame; - - /* only selected */ - if ((GPENCIL_EDIT_MODE(gpd)) && (flag & GP_TOOLFLAG_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_material_editable(ob, gpl, gps_from) == false) { - continue; - } - - /* get final stroke to interpolate */ - fFrame = BLI_findindex(&gpl->actframe->strokes, gps_from); - gps_to = (gpl->actframe->next != NULL) ? - BLI_findlink(&gpl->actframe->next->strokes, fFrame) : - NULL; - if (gps_to == NULL) { - continue; - } + return NULL; +} - return true; +/* Helper: Get next keyframe. */ +static bGPDframe *gpencil_get_next_keyframe(bGPDlayer *gpl, int cfra) +{ + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + if (gpf->key_type != BEZT_KEYTYPE_KEYFRAME) { + continue; + } + if (gpf->framenum <= cfra) { + continue; } + return gpf; } - return false; + + return NULL; } /* Helper: Create internal strokes interpolated */ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) { + Scene *scene = tgpi->scene; bGPdata *gpd = tgpi->gpd; bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C); bGPDframe *actframe = active_gpl->actframe; - Object *ob = CTX_data_active_object(C); /* save initial factor for active layer to define shift limits */ tgpi->init_factor = (float)(tgpi->cframe - actframe->framenum) / @@ -274,12 +467,15 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) tgpil = MEM_callocN(sizeof(tGPDinterpolate_layer), "GPencil Interpolate Layer"); tgpil->gpl = gpl; - tgpil->prevFrame = BKE_gpencil_frame_duplicate(gpl->actframe, true); - tgpil->nextFrame = BKE_gpencil_frame_duplicate(gpl->actframe->next, true); + bGPDframe *gpf = gpencil_get_previous_keyframe(gpl, CFRA); + tgpil->prevFrame = BKE_gpencil_frame_duplicate(gpf, true); + + gpf = gpencil_get_next_keyframe(gpl, CFRA); + tgpil->nextFrame = BKE_gpencil_frame_duplicate(gpf, true); BLI_addtail(&tgpi->ilayers, tgpil); - /* create a new temporary frame */ + /* Create a new temporary frame. */ tgpil->interFrame = MEM_callocN(sizeof(bGPDframe), "bGPDframe"); tgpil->interFrame->framenum = tgpi->cframe; @@ -287,62 +483,42 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) 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 */ - LISTBASE_FOREACH (bGPDstroke *, gps_from, &tgpil->prevFrame->strokes) { - bGPDstroke *gps_to; - int fFrame; + /* Load the relationship between frames. */ + gpencil_stroke_pair_table(C, tgpi, tgpil); - bGPDstroke *new_stroke = NULL; - bool valid = true; + /* Create new strokes data with interpolated points reading original stroke. */ + GHashIterator gh_iter; + GHASH_ITER (gh_iter, tgpil->pair_strokes) { + bGPDstroke *gps_from = (bGPDstroke *)BLI_ghashIterator_getKey(&gh_iter); + bGPDstroke *gps_to = (bGPDstroke *)BLI_ghashIterator_getValue(&gh_iter); - /* only selected */ - if ((GPENCIL_EDIT_MODE(gpd)) && (tgpi->flag & GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED) && - ((gps_from->flag & GP_STROKE_SELECT) == 0)) { - valid = false; + /* If destination stroke is smaller, resize new_stroke to size of gps_to stroke. */ + if (gps_from->totpoints > gps_to->totpoints) { + BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true); } - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps_from) == false) { - valid = false; + if (gps_to->totpoints > gps_from->totpoints) { + BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true); } - /* check if the color is editable */ - if (ED_gpencil_stroke_material_editable(ob, tgpil->gpl, gps_from) == false) { - valid = false; + /* Flip stroke. */ + if (tgpi->flipmode == GP_INTERPOLATE_FLIP) { + BKE_gpencil_stroke_flip(gps_to); } - - /* 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; - } - - if (valid) { - /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ - if (gps_from->totpoints > gps_to->totpoints) { - BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true); + else if (tgpi->flipmode == GP_INTERPOLATE_FLIPAUTO) { + if (gpencil_stroke_need_flip( + tgpi->depsgraph, tgpi->ob, gpl, &tgpi->gsc, gps_from, gps_to)) { + BKE_gpencil_stroke_flip(gps_to); } - if (gps_to->totpoints > gps_from->totpoints) { - BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true); - } - - /* Create new stroke. */ - new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); - - /* Update points position. */ - gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor); } - else { - /* Create new stroke. */ - new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); - /* 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)); - if (new_stroke->dvert != NULL) { - new_stroke->dvert = MEM_recallocN(new_stroke->dvert, sizeof(*new_stroke->dvert)); - } - } + /* Create new stroke. */ + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); + new_stroke->flag |= GP_STROKE_TAG; + new_stroke->select_index = 0; + + /* Update points position. */ + gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor); + gpencil_interpolate_smooth_stroke(new_stroke, tgpi->smooth_factor, tgpi->smooth_steps); /* Calc geometry data. */ BKE_gpencil_stroke_geometry_update(gpd, new_stroke); @@ -429,7 +605,7 @@ static void gpencil_interpolate_exit(bContext *C, wmOperator *op) /* Clear any temp stroke. */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - gpencil_interpolate_free_temp_strokes(gpf); + gpencil_interpolate_free_tagged_strokes(gpf); } } @@ -441,9 +617,18 @@ static void gpencil_interpolate_exit(bContext *C, wmOperator *op) MEM_SAFE_FREE(tgpil->prevFrame); MEM_SAFE_FREE(tgpil->nextFrame); MEM_SAFE_FREE(tgpil->interFrame); + + /* Free Hash tablets. */ + if (tgpil->used_strokes != NULL) { + BLI_ghash_free(tgpil->used_strokes, NULL, NULL); + } + if (tgpil->pair_strokes != NULL) { + BLI_ghash_free(tgpil->pair_strokes, NULL, NULL); + } } BLI_freelistN(&tgpi->ilayers); + MEM_SAFE_FREE(tgpi); } DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -456,24 +641,33 @@ static void gpencil_interpolate_exit(bContext *C, wmOperator *op) /* Init new temporary interpolation data */ static bool gpencil_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->depsgraph = CTX_data_ensure_evaluated_depsgraph(C); tgpi->scene = CTX_data_scene(C); tgpi->area = CTX_wm_area(C); tgpi->region = CTX_wm_region(C); - tgpi->flag = ts->gp_interpolate.flag; + tgpi->ob = CTX_data_active_object(C); + /* Setup space conversions. */ + gpencil_point_conversion_init(C, &tgpi->gsc); /* set current frame number */ tgpi->cframe = tgpi->scene->r.cfra; /* set GP datablock */ - tgpi->gpd = gpd; - + tgpi->gpd = tgpi->ob->data; /* set interpolation weight */ tgpi->shift = RNA_float_get(op->ptr, "shift"); + SET_FLAG_FROM_TEST( + tgpi->flag, (RNA_enum_get(op->ptr, "layers") == 1), GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS); + SET_FLAG_FROM_TEST( + tgpi->flag, + ((GPENCIL_EDIT_MODE(tgpi->gpd)) && (RNA_boolean_get(op->ptr, "interpolate_selected_only"))), + GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED); + + tgpi->flipmode = RNA_enum_get(op->ptr, "flip"); + + tgpi->smooth_factor = RNA_float_get(op->ptr, "smooth_factor"); + tgpi->smooth_steps = RNA_int_get(op->ptr, "smooth_steps"); /* Untag strokes to be sure nothing is pending due any canceled process. */ LISTBASE_FOREACH (bGPDlayer *, gpl, &tgpi->gpd->layers) { @@ -524,12 +718,13 @@ static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent bGPdata *gpd = CTX_data_gpencil_data(C); bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); Scene *scene = CTX_data_scene(C); - int cfra = CFRA; - bGPDframe *actframe = gpl->actframe; tGPDinterpolate *tgpi = NULL; /* cannot interpolate if not between 2 frames */ - if (ELEM(NULL, actframe, actframe->next)) { + int cfra = CFRA; + bGPDframe *gpf_prv = gpencil_get_previous_keyframe(gpl, cfra); + bGPDframe *gpf_next = gpencil_get_next_keyframe(gpl, cfra); + if (ELEM(NULL, gpf_prv, gpf_next)) { BKE_report( op->reports, RPT_ERROR, @@ -537,25 +732,10 @@ static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent return OPERATOR_CANCELLED; } - /* cannot interpolate in extremes */ - if (ELEM(cfra, actframe->framenum, actframe->next->framenum)) { - BKE_report(op->reports, - RPT_ERROR, - "Cannot interpolate as current frame already has existing grease pencil frames"); - return OPERATOR_CANCELLED; - } - if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { BKE_report(op->reports, RPT_ERROR, "Cannot interpolate in curve edit mode"); return OPERATOR_CANCELLED; } - - /* need editable strokes */ - if (!gpencil_interpolate_check_todo(C, gpd)) { - BKE_report(op->reports, RPT_ERROR, "Interpolation requires some editable strokes"); - return OPERATOR_CANCELLED; - } - /* try to initialize context data needed */ if (!gpencil_interpolate_init(C, op)) { if (op->customdata) { @@ -602,8 +782,7 @@ static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent gpf_dst = BKE_gpencil_layer_frame_get(tgpil->gpl, tgpi->cframe, GP_GETFRAME_ADD_NEW); gpf_dst->key_type = BEZT_KEYTYPE_BREAKDOWN; - /* copy strokes */ - BLI_listbase_clear(&gpf_dst->strokes); + /* Copy strokes. */ LISTBASE_FOREACH (bGPDstroke *, gps_src, &tgpil->interFrame->strokes) { if (gps_src->totpoints == 0) { continue; @@ -710,6 +889,15 @@ static void gpencil_interpolate_cancel(bContext *C, wmOperator *op) void GPENCIL_OT_interpolate(wmOperatorType *ot) { + static const EnumPropertyItem flip_modes[] = { + {GP_INTERPOLATE_NOFLIP, "NOFLIP", 0, "No Flip", ""}, + {GP_INTERPOLATE_FLIP, "FLIP", 0, "Flip", ""}, + {GP_INTERPOLATE_FLIPAUTO, "AUTO", 0, "Automatic", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + /* identifiers */ ot->name = "Grease Pencil Interpolation"; ot->idname = "GPENCIL_OT_interpolate"; @@ -724,6 +912,12 @@ void GPENCIL_OT_interpolate(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; + static const EnumPropertyItem gpencil_interpolation_layer_items[] = { + {0, "ACTIVE", 0, "Active", ""}, + {1, "ALL", 0, "All Layers", ""}, + {0, NULL, 0, NULL, NULL}, + }; + /* properties */ RNA_def_float_factor( ot->srna, @@ -735,25 +929,68 @@ void GPENCIL_OT_interpolate(wmOperatorType *ot) "Bias factor for which frame has more influence on the interpolated strokes", -0.9f, 0.9f); + + RNA_def_enum(ot->srna, + "layers", + gpencil_interpolation_layer_items, + 0, + "Layer", + "Layers included in the interpolation"); + + RNA_def_boolean(ot->srna, + "interpolate_selected_only", + 0, + "Only Selected", + "Interpolate only selected strokes"); + + RNA_def_enum(ot->srna, + "flip", + flip_modes, + GP_INTERPOLATE_FLIPAUTO, + "Flip Mode", + "Invert destination stroke to match start and end with source stroke"); + + RNA_def_int(ot->srna, + "smooth_steps", + 1, + 1, + 3, + "Iterations", + "Number of times to smooth newly created strokes", + 1, + 3); + + RNA_def_float(ot->srna, + "smooth_factor", + 0.0f, + 0.0f, + 2.0f, + "Smooth", + "Amount of smoothing to apply to interpolated strokes, to reduce jitter/noise", + 0.0f, + 2.0f); + + prop = RNA_def_boolean(ot->srna, "release_confirm", 0, "Confirm on Release", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /* ****************** Interpolate Sequence *********************** */ /* Helper: Perform easing equation calculations for GP interpolation operator */ -static float gpencil_interpolate_seq_easing_calc(GP_Interpolate_Settings *ipo_settings, float time) +static float gpencil_interpolate_seq_easing_calc(wmOperator *op, float time) { const float begin = 0.0f; const float change = 1.0f; const float duration = 1.0f; - const float back = ipo_settings->back; - const float amplitude = ipo_settings->amplitude; - const float period = ipo_settings->period; - - eBezTriple_Easing easing = ipo_settings->easing; + const float back = RNA_float_get(op->ptr, "back"); + const float amplitude = RNA_float_get(op->ptr, "amplitude"); + const float period = RNA_float_get(op->ptr, "period"); + const eBezTriple_Easing easing = RNA_enum_get(op->ptr, "easing"); + const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type"); float result = time; - switch (ipo_settings->type) { + switch (type) { case GP_IPO_BACK: switch (easing) { case BEZT_IPO_EASE_IN: @@ -936,7 +1173,7 @@ static float gpencil_interpolate_seq_easing_calc(GP_Interpolate_Settings *ipo_se break; default: - printf("%s: Unknown interpolation type - %d\n", __func__, ipo_settings->type); + printf("%s: Unknown interpolation type\n", __func__); break; } @@ -945,34 +1182,47 @@ static float gpencil_interpolate_seq_easing_calc(GP_Interpolate_Settings *ipo_se static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) { - bGPdata *gpd = CTX_data_gpencil_data(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C); - bGPDframe *actframe = active_gpl->actframe; + /* Setup space conversions. */ + GP_SpaceConversion gsc; + gpencil_point_conversion_init(C, &gsc); - Object *ob = CTX_data_active_object(C); - ToolSettings *ts = CTX_data_tool_settings(C); - Scene *scene = CTX_data_scene(C); int cfra = CFRA; GP_Interpolate_Settings *ipo_settings = &ts->gp_interpolate; - eGP_Interpolate_SettingsFlag flag = ipo_settings->flag; - const int step = ipo_settings->step; + const int step = RNA_int_get(op->ptr, "step"); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool all_layers = (bool)(RNA_enum_get(op->ptr, "layers") == 1); + const bool only_selected = ((GPENCIL_EDIT_MODE(gpd)) && + (RNA_boolean_get(op->ptr, "interpolate_selected_only") != 0)); - /* cannot interpolate if not between 2 frames */ - if (ELEM(NULL, actframe, actframe->next)) { + eGP_InterpolateFlipMode flipmode = RNA_enum_get(op->ptr, "flip"); + + const float smooth_factor = RNA_float_get(op->ptr, "smooth_factor"); + const int smooth_steps = RNA_int_get(op->ptr, "smooth_steps"); + + const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type"); + + if (ipo_settings->custom_ipo == NULL) { + ipo_settings->custom_ipo = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + } + BKE_curvemapping_init(ipo_settings->custom_ipo); + + /* Cannot interpolate if not between 2 frames. */ + bGPDframe *gpf_prv = gpencil_get_previous_keyframe(active_gpl, cfra); + bGPDframe *gpf_next = gpencil_get_next_keyframe(active_gpl, cfra); + if (ELEM(NULL, gpf_prv, gpf_next)) { BKE_report( op->reports, RPT_ERROR, "Cannot find a pair of grease pencil frames to interpolate between in active layer"); return OPERATOR_CANCELLED; } - /* cannot interpolate in extremes */ - if (ELEM(cfra, actframe->framenum, actframe->next->framenum)) { - BKE_report(op->reports, - RPT_ERROR, - "Cannot interpolate as current frame already has existing grease pencil frames"); - return OPERATOR_CANCELLED; - } if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { BKE_report(op->reports, RPT_ERROR, "Cannot interpolate in curve edit mode"); @@ -981,103 +1231,137 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) /* loop all layer to check if need interpolation */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - bGPDframe *prevFrame, *nextFrame; - bGPDstroke *gps_from, *gps_to; - int cframe, fFrame; - - /* Need a set of frames to interpolate. */ - if ((gpl->actframe == NULL) || (gpl->actframe->next == NULL)) { - continue; - } /* all layers or only active */ - if (((flag & GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) { + if ((!all_layers) && (gpl != active_gpl)) { continue; } /* only editable and visible layers are considered */ if (!BKE_gpencil_layer_is_editable(gpl)) { continue; } + gpf_prv = gpencil_get_previous_keyframe(gpl, cfra); + gpf_next = gpencil_get_next_keyframe(gpl, cfra); + + /* Need a set of frames to interpolate. */ + if ((gpf_prv == NULL) || (gpf_next == NULL)) { + continue; + } + + /* Store extremes. */ + bGPDframe *prevFrame = BKE_gpencil_frame_duplicate(gpf_prv, true); + bGPDframe *nextFrame = BKE_gpencil_frame_duplicate(gpf_next, true); + + /* Create a table with source and target pair of strokes. */ + GHash *used_strokes = BLI_ghash_ptr_new(__func__); + GHash *pair_strokes = BLI_ghash_ptr_new(__func__); + + LISTBASE_FOREACH (bGPDstroke *, gps_from, &prevFrame->strokes) { + bGPDstroke *gps_to = NULL; + /* Only selected. */ + if ((GPENCIL_EDIT_MODE(gpd)) && (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 material is editable. */ + if (ED_gpencil_stroke_material_editable(ob, gpl, gps_from) == false) { + continue; + } + /* Try to get the related stroke. */ + if ((is_multiedit) && (gps_from->select_index > 0)) { + gps_to = gpencil_stroke_get_related(used_strokes, nextFrame, gps_from->select_index); + } + /* If not found, get final stroke to interpolate using position in the array. */ + if (gps_to == NULL) { + int fFrame = BLI_findindex(&prevFrame->strokes, gps_from); + gps_to = BLI_findlink(&nextFrame->strokes, fFrame); + } + + if (ELEM(NULL, gps_from, gps_to)) { + continue; + } + + /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ + if (gps_from->totpoints > gps_to->totpoints) { + BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true); + } + if (gps_to->totpoints > gps_from->totpoints) { + BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true); + } - /* store extremes */ - prevFrame = BKE_gpencil_frame_duplicate(gpl->actframe, true); - nextFrame = BKE_gpencil_frame_duplicate(gpl->actframe->next, true); + /* Flip stroke. */ + if (flipmode == GP_INTERPOLATE_FLIP) { + BKE_gpencil_stroke_flip(gps_to); + } + else if (flipmode == GP_INTERPOLATE_FLIPAUTO) { + if (gpencil_stroke_need_flip(depsgraph, ob, gpl, &gsc, gps_from, gps_to)) { + BKE_gpencil_stroke_flip(gps_to); + } + } - /* Loop over intermediary frames and create the interpolation */ - for (cframe = prevFrame->framenum + step; cframe < nextFrame->framenum; cframe += step) { - bGPDframe *interFrame = NULL; - float factor; + /* Insert the pair entry in the hash table. */ + BLI_ghash_insert(pair_strokes, gps_from, gps_to); + } - /* get interpolation factor */ + /* Loop over intermediary frames and create the interpolation. */ + for (int cframe = prevFrame->framenum + step; cframe < nextFrame->framenum; cframe += step) { + /* Get interpolation factor. */ float framerange = nextFrame->framenum - prevFrame->framenum; CLAMP_MIN(framerange, 1.0f); - factor = (float)(cframe - prevFrame->framenum) / framerange; + float factor = (float)(cframe - prevFrame->framenum) / framerange; - if (ipo_settings->type == GP_IPO_CURVEMAP) { + if (type == GP_IPO_CURVEMAP) { /* custom curvemap */ if (ipo_settings->custom_ipo) { factor = BKE_curvemapping_evaluateF(ipo_settings->custom_ipo, 0, factor); } else { BKE_report(op->reports, RPT_ERROR, "Custom interpolation curve does not exist"); + continue; } } - else if (ipo_settings->type >= GP_IPO_BACK) { + else if (type >= GP_IPO_BACK) { /* easing equation... */ - factor = gpencil_interpolate_seq_easing_calc(ipo_settings, factor); + factor = gpencil_interpolate_seq_easing_calc(op, factor); } - /* 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 ((GPENCIL_EDIT_MODE(gpd)) && (flag & GP_TOOLFLAG_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_material_editable(ob, 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_frame_get(gpl, cframe, GP_GETFRAME_ADD_NEW); - interFrame->key_type = BEZT_KEYTYPE_BREAKDOWN; - } - - /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ - if (gps_from->totpoints > gps_to->totpoints) { - BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true); - } - if (gps_to->totpoints > gps_from->totpoints) { - BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true); - } + /* Apply the factor to all pair of strokes. */ + GHashIterator gh_iter; + GHASH_ITER (gh_iter, pair_strokes) { + bGPDstroke *gps_from = (bGPDstroke *)BLI_ghashIterator_getKey(&gh_iter); + bGPDstroke *gps_to = (bGPDstroke *)BLI_ghashIterator_getValue(&gh_iter); - /* create new stroke */ + /* Create new stroke. */ bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); + new_stroke->flag |= GP_STROKE_TAG; + new_stroke->select_index = 0; - /* update points position */ + /* Update points position. */ gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); + gpencil_interpolate_smooth_stroke(new_stroke, smooth_factor, smooth_steps); /* Calc geometry data. */ BKE_gpencil_stroke_geometry_update(gpd, new_stroke); - /* add to strokes */ + /* Add strokes to frame. */ + bGPDframe *interFrame = BKE_gpencil_layer_frame_get(gpl, cframe, GP_GETFRAME_ADD_NEW); + interFrame->key_type = BEZT_KEYTYPE_BREAKDOWN; + BLI_addtail(&interFrame->strokes, new_stroke); } } + /* Free Hash tablets. */ + if (used_strokes != NULL) { + BLI_ghash_free(used_strokes, NULL, NULL); + } + if (pair_strokes != NULL) { + BLI_ghash_free(pair_strokes, NULL, NULL); + } + BKE_gpencil_free_strokes(prevFrame); BKE_gpencil_free_strokes(nextFrame); MEM_SAFE_FREE(prevFrame); @@ -1091,8 +1375,148 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void gpencil_interpolate_seq_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + uiLayout *col, *row; + PointerRNA ptr; + + RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr); + + const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + col = uiLayoutColumn(layout, true); + uiItemR(col, &ptr, "step", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "layers", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "interpolate_selected_only", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "flip", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "smooth_factor", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "smooth_steps", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "type", 0, NULL, ICON_NONE); + + if (type == GP_IPO_CURVEMAP) { + /* Get an RNA pointer to ToolSettings to give to the custom curve. */ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + PointerRNA gpsettings_ptr; + RNA_pointer_create( + &scene->id, &RNA_GPencilInterpolateSettings, &ts->gp_interpolate, &gpsettings_ptr); + uiTemplateCurveMapping( + layout, &gpsettings_ptr, "interpolation_curve", 0, false, true, true, false); + } + else if (type != GP_IPO_LINEAR) { + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "easing", 0, NULL, ICON_NONE); + if (type == GP_IPO_BACK) { + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "back", 0, NULL, ICON_NONE); + } + else if (type == GP_IPO_ELASTIC) { + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "amplitude", 0, NULL, ICON_NONE); + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "period", 0, NULL, ICON_NONE); + } + } +} + void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) { + static const EnumPropertyItem gpencil_interpolation_layer_items[] = { + {0, "ACTIVE", 0, "Active", ""}, + {1, "ALL", 0, "All Layers", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem gpencil_interpolation_type_items[] = { + /* interpolation */ + {0, "", 0, N_("Interpolation"), "Standard transitions between keyframes"}, + {GP_IPO_LINEAR, + "LINEAR", + ICON_IPO_LINEAR, + "Linear", + "Straight-line interpolation between A and B (i.e. no ease in/out)"}, + {GP_IPO_CURVEMAP, + "CUSTOM", + ICON_IPO_BEZIER, + "Custom", + "Custom interpolation defined using a curve map"}, + + /* easing */ + {0, + "", + 0, + N_("Easing (by strength)"), + "Predefined inertial transitions, useful for motion graphics (from least to most " + "''dramatic'')"}, + {GP_IPO_SINE, + "SINE", + ICON_IPO_SINE, + "Sinusoidal", + "Sinusoidal easing (weakest, almost linear but with a slight curvature)"}, + {GP_IPO_QUAD, "QUAD", ICON_IPO_QUAD, "Quadratic", "Quadratic easing"}, + {GP_IPO_CUBIC, "CUBIC", ICON_IPO_CUBIC, "Cubic", "Cubic easing"}, + {GP_IPO_QUART, "QUART", ICON_IPO_QUART, "Quartic", "Quartic easing"}, + {GP_IPO_QUINT, "QUINT", ICON_IPO_QUINT, "Quintic", "Quintic easing"}, + {GP_IPO_EXPO, "EXPO", ICON_IPO_EXPO, "Exponential", "Exponential easing (dramatic)"}, + {GP_IPO_CIRC, + "CIRC", + ICON_IPO_CIRC, + "Circular", + "Circular easing (strongest and most dynamic)"}, + + {0, "", 0, N_("Dynamic Effects"), "Simple physics-inspired easing effects"}, + {GP_IPO_BACK, "BACK", ICON_IPO_BACK, "Back", "Cubic easing with overshoot and settle"}, + {GP_IPO_BOUNCE, + "BOUNCE", + ICON_IPO_BOUNCE, + "Bounce", + "Exponentially decaying parabolic bounce, like when objects collide"}, + {GP_IPO_ELASTIC, + "ELASTIC", + ICON_IPO_ELASTIC, + "Elastic", + "Exponentially decaying sine wave, like an elastic band"}, + + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem gpencil_interpolation_easing_items[] = { + {BEZT_IPO_EASE_AUTO, + "AUTO", + ICON_IPO_EASE_IN_OUT, + "Automatic Easing", + "Easing type is chosen automatically based on what the type of interpolation used " + "(e.g. 'Ease In' for transitional types, and 'Ease Out' for dynamic effects)"}, + + {BEZT_IPO_EASE_IN, + "EASE_IN", + ICON_IPO_EASE_IN, + "Ease In", + "Only on the end closest to the next keyframe"}, + {BEZT_IPO_EASE_OUT, + "EASE_OUT", + ICON_IPO_EASE_OUT, + "Ease Out", + "Only on the end closest to the first keyframe"}, + {BEZT_IPO_EASE_IN_OUT, + "EASE_IN_OUT", + ICON_IPO_EASE_IN_OUT, + "Ease In and Out", + "Segment between both keyframes"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem flip_modes[] = { + {GP_INTERPOLATE_NOFLIP, "NOFLIP", 0, "No Flip", ""}, + {GP_INTERPOLATE_FLIP, "FLIP", 0, "Flip", ""}, + {GP_INTERPOLATE_FLIPAUTO, "AUTO", 0, "Automatic", ""}, + {0, NULL, 0, NULL, NULL}, + }; + /* identifiers */ ot->name = "Interpolate Sequence"; ot->idname = "GPENCIL_OT_interpolate_sequence"; @@ -1101,6 +1525,103 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_interpolate_seq_exec; ot->poll = gpencil_view3d_poll; + ot->ui = gpencil_interpolate_seq_ui; + + RNA_def_int(ot->srna, + "step", + 1, + 1, + MAXFRAME, + "Step", + "Number of frames between generated interpolated frames", + 1, + MAXFRAME); + + RNA_def_enum(ot->srna, + "layers", + gpencil_interpolation_layer_items, + 0, + "Layer", + "Layers included in the interpolation"); + + RNA_def_boolean(ot->srna, + "interpolate_selected_only", + 0, + "Only Selected", + "Interpolate only selected strokes"); + + RNA_def_enum(ot->srna, + "flip", + flip_modes, + GP_INTERPOLATE_FLIPAUTO, + "Flip Mode", + "Invert destination stroke to match start and end with source stroke"); + + RNA_def_int(ot->srna, + "smooth_steps", + 1, + 1, + 3, + "Iterations", + "Number of times to smooth newly created strokes", + 1, + 3); + + RNA_def_float(ot->srna, + "smooth_factor", + 0.0f, + 0.0f, + 2.0f, + "Smooth", + "Amount of smoothing to apply to interpolated strokes, to reduce jitter/noise", + 0.0f, + 2.0f); + + RNA_def_enum(ot->srna, + "type", + gpencil_interpolation_type_items, + 0, + "Type", + "Interpolation method to use the next time 'Interpolate Sequence' is run"); + + RNA_def_enum( + ot->srna, + "easing", + gpencil_interpolation_easing_items, + 0, + "Easing", + "Which ends of the segment between the preceding and following grease pencil frames " + "easing interpolation is applied to"); + + RNA_def_float(ot->srna, + "back", + 1.702f, + 0.0f, + FLT_MAX, + "Back", + "Amount of overshoot for 'back' easing", + 0.0f, + FLT_MAX); + + RNA_def_float(ot->srna, + "amplitude", + 0.15f, + 0.0f, + FLT_MAX, + "Amplitude", + "Amount to boost elastic bounces for 'elastic' easing", + 0.0f, + FLT_MAX); + + RNA_def_float(ot->srna, + "period", + 0.15f, + -FLT_MAX, + FLT_MAX, + "Period", + "Time between bounces for elastic easing", + -FLT_MAX, + FLT_MAX); /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -1110,19 +1631,30 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) static bool gpencil_interpolate_reverse_poll(bContext *C) { - if (!gpencil_view3d_poll(C)) { - return 0; + ScrArea *area = CTX_wm_area(C); + if (area == NULL) { + return false; + } + if ((area->spacetype != SPACE_VIEW3D) && (area->spacetype != SPACE_ACTION)) { + return false; } - bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + if (gpd == NULL) { + return false; + } + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + if (gpl == NULL) { + return false; + } /* need to be on a breakdown frame */ if ((gpl->actframe == NULL) || (gpl->actframe->key_type != BEZT_KEYTYPE_BREAKDOWN)) { CTX_wm_operator_poll_msg_set(C, "Expected current frame to be a breakdown"); - return 0; + return false; } - return 1; + return true; } static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op)) @@ -1132,7 +1664,11 @@ static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op)) /* Go through each layer, deleting the breakdowns around the current frame, * but only if there is a keyframe nearby to stop at */ - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* only editable and visible layers are considered */ + if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { + continue; + } bGPDframe *start_key = NULL; bGPDframe *end_key = NULL; bGPDframe *gpf, *gpfn; @@ -1193,7 +1729,6 @@ static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op)) BLI_freelinkN(&gpl->frames, end_key); } } - CTX_DATA_END; /* notifiers */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -1205,7 +1740,7 @@ static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op)) void GPENCIL_OT_interpolate_reverse(wmOperatorType *ot) { /* identifiers */ - ot->name = "Remove Breakdowns"; + ot->name = "Delete Breakdowns"; ot->idname = "GPENCIL_OT_interpolate_reverse"; ot->description = "Remove breakdown frames generated by interpolating between two Grease Pencil frames"; @@ -1217,5 +1752,3 @@ void GPENCIL_OT_interpolate_reverse(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } - -/* *************************************************************** */ |