Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_interpolate.c')
-rw-r--r--source/blender/editors/gpencil/gpencil_interpolate.c1011
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;
}
-
-/* *************************************************************** */