|
|
|
@ -18,7 +18,7 @@
|
|
|
|
|
* The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung
|
|
|
|
|
* This is a new part of Blender
|
|
|
|
|
*
|
|
|
|
|
* Contributor(s): Joshua Leung
|
|
|
|
|
* Contributor(s): Joshua Leung, Antonio Vazquez
|
|
|
|
|
*
|
|
|
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
|
|
|
*
|
|
|
|
@ -74,6 +74,8 @@
|
|
|
|
|
#include "ED_object.h"
|
|
|
|
|
#include "ED_screen.h"
|
|
|
|
|
#include "ED_view3d.h"
|
|
|
|
|
#include "ED_screen.h"
|
|
|
|
|
#include "ED_space_api.h"
|
|
|
|
|
|
|
|
|
|
#include "gpencil_intern.h"
|
|
|
|
|
|
|
|
|
@ -1968,4 +1970,661 @@ void GPENCIL_OT_reproject(wmOperatorType *ot)
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ************************************************ */
|
|
|
|
|
/* ========= Interpolation operators ========================== */
|
|
|
|
|
/* Helper: Update point with interpolation */
|
|
|
|
|
static void gp_interpolate_update_points(bGPDstroke *gps_from, bGPDstroke *gps_to, bGPDstroke *new_stroke, float factor)
|
|
|
|
|
{
|
|
|
|
|
bGPDspoint *prev, *pt, *next;
|
|
|
|
|
|
|
|
|
|
/* update points */
|
|
|
|
|
for (int i = 0; i < new_stroke->totpoints; i++) {
|
|
|
|
|
prev = &gps_from->points[i];
|
|
|
|
|
pt = &new_stroke->points[i];
|
|
|
|
|
next = &gps_to->points[i];
|
|
|
|
|
|
|
|
|
|
/* Interpolate all values */
|
|
|
|
|
interp_v3_v3v3(&pt->x, &prev->x, &next->x, factor);
|
|
|
|
|
pt->pressure = interpf(prev->pressure, next->pressure, factor);
|
|
|
|
|
pt->strength = interpf(prev->strength, next->strength, factor);
|
|
|
|
|
CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Helper: Update all strokes interpolated */
|
|
|
|
|
static void gp_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgpi)
|
|
|
|
|
{
|
|
|
|
|
tGPDinterpolate_layer *tgpil;
|
|
|
|
|
bGPDstroke *new_stroke, *gps_from, *gps_to;
|
|
|
|
|
int cStroke;
|
|
|
|
|
float factor;
|
|
|
|
|
float shift = tgpi->shift;
|
|
|
|
|
|
|
|
|
|
for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
|
|
|
|
|
factor = tgpil->factor + shift;
|
|
|
|
|
for (new_stroke = tgpil->interFrame->strokes.first; new_stroke; new_stroke = new_stroke->next) {
|
|
|
|
|
if (new_stroke->totpoints == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* get strokes to interpolate */
|
|
|
|
|
cStroke = BLI_findindex(&tgpil->interFrame->strokes, new_stroke);
|
|
|
|
|
gps_from = BLI_findlink(&tgpil->prevFrame->strokes, cStroke);
|
|
|
|
|
gps_to = BLI_findlink(&tgpil->nextFrame->strokes, cStroke);
|
|
|
|
|
/* update points position */
|
|
|
|
|
if ((gps_from) && (gps_to)) {
|
|
|
|
|
gp_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Helper: Verify valid strokes for interpolation */
|
|
|
|
|
static bool gp_interpolate_check_todo(bContext *C, bGPdata *gpd)
|
|
|
|
|
{
|
|
|
|
|
ToolSettings *ts = CTX_data_tool_settings(C);
|
|
|
|
|
int flag = ts->gp_sculpt.flag;
|
|
|
|
|
|
|
|
|
|
bGPDlayer *gpl;
|
|
|
|
|
bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
|
|
|
|
|
bGPDstroke *gps_from, *gps_to;
|
|
|
|
|
int fFrame;
|
|
|
|
|
|
|
|
|
|
/* get layers */
|
|
|
|
|
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
|
|
|
/* all layers or only active */
|
|
|
|
|
if (((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* only editable and visible layers are considered */
|
|
|
|
|
if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* read strokes */
|
|
|
|
|
for (gps_from = gpl->actframe->strokes.first; gps_from; gps_from = gps_from->next) {
|
|
|
|
|
/* only selected */
|
|
|
|
|
if ((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* skip strokes that are invalid for current view */
|
|
|
|
|
if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* check if the color is editable */
|
|
|
|
|
if (ED_gpencil_stroke_color_use(gpl, gps_from) == false) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* get final stroke to interpolate */
|
|
|
|
|
fFrame = BLI_findindex(&gpl->actframe->strokes, gps_from);
|
|
|
|
|
gps_to = BLI_findlink(&gpl->actframe->next->strokes, fFrame);
|
|
|
|
|
if (gps_to == NULL) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Helper: Create internal strokes interpolated */
|
|
|
|
|
static void gp_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi)
|
|
|
|
|
{
|
|
|
|
|
bGPDlayer *gpl;
|
|
|
|
|
bGPdata *gpd = tgpi->gpd;
|
|
|
|
|
tGPDinterpolate_layer *tgpil;
|
|
|
|
|
bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
|
|
|
|
|
bGPDstroke *gps_from, *gps_to, *new_stroke;
|
|
|
|
|
int fFrame;
|
|
|
|
|
|
|
|
|
|
/* set layers */
|
|
|
|
|
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
|
|
|
/* all layers or only active */
|
|
|
|
|
if (((tgpi->flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* only editable and visible layers are considered */
|
|
|
|
|
if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* create temp data for each layer */
|
|
|
|
|
tgpil = NULL;
|
|
|
|
|
tgpil = MEM_callocN(sizeof(tGPDinterpolate_layer), "GPencil Interpolate Layer");
|
|
|
|
|
|
|
|
|
|
tgpil->gpl = gpl;
|
|
|
|
|
tgpil->prevFrame = gpl->actframe;
|
|
|
|
|
tgpil->nextFrame = gpl->actframe->next;
|
|
|
|
|
|
|
|
|
|
BLI_addtail(&tgpi->ilayers, tgpil);
|
|
|
|
|
/* create a new temporary frame */
|
|
|
|
|
tgpil->interFrame = MEM_callocN(sizeof(bGPDframe), "bGPDframe");
|
|
|
|
|
tgpil->interFrame->framenum = tgpi->cframe;
|
|
|
|
|
|
|
|
|
|
/* get interpolation factor */
|
|
|
|
|
tgpil->factor = (float)(tgpi->cframe - tgpil->prevFrame->framenum) / (tgpil->nextFrame->framenum - tgpil->prevFrame->framenum + 1);
|
|
|
|
|
/* create new strokes data with interpolated points reading original stroke */
|
|
|
|
|
for (gps_from = tgpil->prevFrame->strokes.first; gps_from; gps_from = gps_from->next) {
|
|
|
|
|
bool valid = true;
|
|
|
|
|
/* only selected */
|
|
|
|
|
if ((tgpi->flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* skip strokes that are invalid for current view */
|
|
|
|
|
if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
/* check if the color is editable */
|
|
|
|
|
if (ED_gpencil_stroke_color_use(tgpil->gpl, gps_from) == false) {
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
/* get final stroke to interpolate */
|
|
|
|
|
fFrame = BLI_findindex(&tgpil->prevFrame->strokes, gps_from);
|
|
|
|
|
gps_to = BLI_findlink(&tgpil->nextFrame->strokes, fFrame);
|
|
|
|
|
if (gps_to == NULL) {
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
/* create new stroke */
|
|
|
|
|
new_stroke = MEM_dupallocN(gps_from);
|
|
|
|
|
new_stroke->points = MEM_dupallocN(gps_from->points);
|
|
|
|
|
new_stroke->triangles = MEM_dupallocN(gps_from->triangles);
|
|
|
|
|
if (valid) {
|
|
|
|
|
/* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */
|
|
|
|
|
if (gps_from->totpoints > gps_to->totpoints) {
|
|
|
|
|
new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points) * gps_to->totpoints);
|
|
|
|
|
new_stroke->totpoints = gps_to->totpoints;
|
|
|
|
|
new_stroke->tot_triangles = 0;
|
|
|
|
|
new_stroke->flag |= GP_STROKE_RECALC_CACHES;
|
|
|
|
|
}
|
|
|
|
|
/* update points position */
|
|
|
|
|
gp_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* need an empty stroke to keep index correct for lookup, but resize to smallest size */
|
|
|
|
|
new_stroke->totpoints = 0;
|
|
|
|
|
new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points));
|
|
|
|
|
new_stroke->tot_triangles = 0;
|
|
|
|
|
new_stroke->triangles = MEM_recallocN(new_stroke->triangles, sizeof(*new_stroke->triangles));
|
|
|
|
|
}
|
|
|
|
|
/* add to strokes */
|
|
|
|
|
BLI_addtail(&tgpil->interFrame->strokes, new_stroke);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Helper: calculate shift based on position of mouse (we only use x-axis for now.
|
|
|
|
|
* since this is more convenient for users to do), and store new shift value
|
|
|
|
|
*/
|
|
|
|
|
static void gpencil_mouse_update_shift(tGPDinterpolate *tgpi, wmOperator *op, const wmEvent *event)
|
|
|
|
|
{
|
|
|
|
|
float mid = (float)(tgpi->ar->winx - tgpi->ar->winrct.xmin) / 2.0f;
|
|
|
|
|
float mpos = event->x - tgpi->ar->winrct.xmin;
|
|
|
|
|
if (mpos >= mid) {
|
|
|
|
|
tgpi->shift = (mpos - mid) / mid;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
tgpi->shift = -1.0f * (1.0f - (mpos / mid));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CLAMP(tgpi->shift, -1.0f, 1.0f);
|
|
|
|
|
RNA_float_set(op->ptr, "shift", tgpi->shift);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Helper: Draw status message while the user is running the operator */
|
|
|
|
|
static void gpencil_interpolate_status_indicators(tGPDinterpolate *p)
|
|
|
|
|
{
|
|
|
|
|
Scene *scene = p->scene;
|
|
|
|
|
char status_str[UI_MAX_DRAW_STR];
|
|
|
|
|
char msg_str[UI_MAX_DRAW_STR];
|
|
|
|
|
BLI_strncpy(msg_str, IFACE_("GPencil Interpolation: ESC/RMB to cancel, Enter/LMB to confirm, WHEEL/MOVE to adjust, Factor"), UI_MAX_DRAW_STR);
|
|
|
|
|
|
|
|
|
|
if (hasNumInput(&p->num)) {
|
|
|
|
|
char str_offs[NUM_STR_REP_LEN];
|
|
|
|
|
|
|
|
|
|
outputNumInput(&p->num, str_offs, &scene->unit);
|
|
|
|
|
|
|
|
|
|
BLI_snprintf(status_str, sizeof(status_str), "%s: %s", msg_str, str_offs);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_snprintf(status_str, sizeof(status_str), "%s: %d", msg_str, (int)(p->shift * 100.0f));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ED_area_headerprint(p->sa, status_str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Helper: Update screen and stroke */
|
|
|
|
|
static void gpencil_interpolate_update(bContext *C, wmOperator *op, tGPDinterpolate *tgpi)
|
|
|
|
|
{
|
|
|
|
|
/* update shift indicator in header */
|
|
|
|
|
gpencil_interpolate_status_indicators(tgpi);
|
|
|
|
|
/* apply... */
|
|
|
|
|
tgpi->shift = RNA_float_get(op->ptr, "shift");
|
|
|
|
|
/* update points position */
|
|
|
|
|
gp_interpolate_update_strokes(C, tgpi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* init new temporary interpolation data */
|
|
|
|
|
static bool gp_interpolate_set_init_values(bContext *C, wmOperator *op, tGPDinterpolate *tgpi)
|
|
|
|
|
{
|
|
|
|
|
ToolSettings *ts = CTX_data_tool_settings(C);
|
|
|
|
|
bGPdata *gpd = CTX_data_gpencil_data(C);
|
|
|
|
|
|
|
|
|
|
/* set current scene and window */
|
|
|
|
|
tgpi->scene = CTX_data_scene(C);
|
|
|
|
|
tgpi->sa = CTX_wm_area(C);
|
|
|
|
|
tgpi->ar = CTX_wm_region(C);
|
|
|
|
|
tgpi->flag = ts->gp_sculpt.flag;
|
|
|
|
|
|
|
|
|
|
/* set current frame number */
|
|
|
|
|
tgpi->cframe = tgpi->scene->r.cfra;
|
|
|
|
|
|
|
|
|
|
/* set GP datablock */
|
|
|
|
|
tgpi->gpd = gpd;
|
|
|
|
|
|
|
|
|
|
/* set interpolation weight */
|
|
|
|
|
tgpi->shift = RNA_float_get(op->ptr, "shift");
|
|
|
|
|
/* set layers */
|
|
|
|
|
gp_interpolate_set_points(C, tgpi);
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Poll handler: check if context is suitable for interpolation */
|
|
|
|
|
static int gpencil_interpolate_poll(bContext *C)
|
|
|
|
|
{
|
|
|
|
|
bGPdata * gpd = CTX_data_gpencil_data(C);
|
|
|
|
|
bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
|
|
|
|
|
/* only 3D view */
|
|
|
|
|
if (CTX_wm_area(C)->spacetype != SPACE_VIEW3D) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
/* need data to interpolate */
|
|
|
|
|
if (ELEM(NULL, gpd, gpl)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Allocate memory and initialize values */
|
|
|
|
|
static tGPDinterpolate *gp_session_init_interpolation(bContext *C, wmOperator *op)
|
|
|
|
|
{
|
|
|
|
|
tGPDinterpolate *tgpi = NULL;
|
|
|
|
|
|
|
|
|
|
/* create new context data */
|
|
|
|
|
tgpi = MEM_callocN(sizeof(tGPDinterpolate), "GPencil Interpolate Data");
|
|
|
|
|
|
|
|
|
|
/* define initial values */
|
|
|
|
|
gp_interpolate_set_init_values(C, op, tgpi);
|
|
|
|
|
|
|
|
|
|
/* return context data for running operator */
|
|
|
|
|
return tgpi;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Exit and free memory */
|
|
|
|
|
static void gpencil_interpolate_exit(bContext *C, wmOperator *op)
|
|
|
|
|
{
|
|
|
|
|
tGPDinterpolate *tgpi = op->customdata;
|
|
|
|
|
bGPdata *gpd = CTX_data_gpencil_data(C);
|
|
|
|
|
tGPDinterpolate_layer *tgpil;
|
|
|
|
|
|
|
|
|
|
/* don't assume that operator data exists at all */
|
|
|
|
|
if (tgpi) {
|
|
|
|
|
/* remove drawing handler */
|
|
|
|
|
if (tgpi->draw_handle_screen) {
|
|
|
|
|
ED_region_draw_cb_exit(tgpi->ar->type, tgpi->draw_handle_screen);
|
|
|
|
|
}
|
|
|
|
|
if (tgpi->draw_handle_3d) {
|
|
|
|
|
ED_region_draw_cb_exit(tgpi->ar->type, tgpi->draw_handle_3d);
|
|
|
|
|
}
|
|
|
|
|
/* clear status message area */
|
|
|
|
|
ED_area_headerprint(tgpi->sa, NULL);
|
|
|
|
|
/* finally, free memory used by temp data */
|
|
|
|
|
for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
|
|
|
|
|
BKE_gpencil_free_strokes(tgpil->interFrame);
|
|
|
|
|
MEM_freeN(tgpil->interFrame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BLI_freelistN(&tgpi->ilayers);
|
|
|
|
|
MEM_freeN(tgpi);
|
|
|
|
|
}
|
|
|
|
|
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
|
|
|
|
|
|
|
|
|
|
/* clear pointer */
|
|
|
|
|
op->customdata = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Cancel handler */
|
|
|
|
|
static void gpencil_interpolate_cancel(bContext *C, wmOperator *op)
|
|
|
|
|
{
|
|
|
|
|
/* this is just a wrapper around exit() */
|
|
|
|
|
gpencil_interpolate_exit(C, op);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Init interpolation: Allocate memory and set init values */
|
|
|
|
|
static int gpencil_interpolate_init(bContext *C, wmOperator *op)
|
|
|
|
|
{
|
|
|
|
|
tGPDinterpolate *tgpi;
|
|
|
|
|
/* check context */
|
|
|
|
|
tgpi = op->customdata = gp_session_init_interpolation(C, op);
|
|
|
|
|
if (tgpi == NULL) {
|
|
|
|
|
/* something wasn't set correctly in context */
|
|
|
|
|
gpencil_interpolate_exit(C, op);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* everything is now setup ok */
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ********************** custom drawcall api ***************** */
|
|
|
|
|
/* Helper: drawing callback for modal operator in screen mode */
|
|
|
|
|
static void gpencil_interpolate_draw_screen(const struct bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
|
|
|
|
|
{
|
|
|
|
|
wmOperator *op = arg;
|
|
|
|
|
struct tGPDinterpolate *tgpi = op->customdata;
|
|
|
|
|
ED_gp_draw_interpolation(tgpi, REGION_DRAW_POST_PIXEL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Helper: drawing callback for modal operator in 3d mode */
|
|
|
|
|
static void gpencil_interpolate_draw_3d(const struct bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
|
|
|
|
|
{
|
|
|
|
|
wmOperator *op = arg;
|
|
|
|
|
struct tGPDinterpolate *tgpi = op->customdata;
|
|
|
|
|
ED_gp_draw_interpolation(tgpi, REGION_DRAW_POST_VIEW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Invoke handler: Initialize the operator */
|
|
|
|
|
static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
|
|
|
{
|
|
|
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
|
bGPdata * gpd = CTX_data_gpencil_data(C);
|
|
|
|
|
bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
|
|
|
|
|
tGPDinterpolate *tgpi = NULL;
|
|
|
|
|
|
|
|
|
|
/* cannot interpolate if not between 2 frames */
|
|
|
|
|
if ((gpl->actframe == NULL) || (gpl->actframe->next == NULL)) {
|
|
|
|
|
BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames");
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* cannot interpolate in extremes */
|
|
|
|
|
if ((gpl->actframe->framenum == scene->r.cfra) || (gpl->actframe->next->framenum == scene->r.cfra)) {
|
|
|
|
|
BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames");
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* need editable strokes */
|
|
|
|
|
if (!gp_interpolate_check_todo(C, gpd)) {
|
|
|
|
|
BKE_report(op->reports, RPT_ERROR, "Interpolation requires some editable stroke");
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* try to initialize context data needed */
|
|
|
|
|
if (!gpencil_interpolate_init(C, op)) {
|
|
|
|
|
if (op->customdata)
|
|
|
|
|
MEM_freeN(op->customdata);
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
tgpi = op->customdata;
|
|
|
|
|
|
|
|
|
|
/* enable custom drawing handlers. It needs 2 handlers because can be strokes in 3d space and screen space and each handler use different
|
|
|
|
|
coord system */
|
|
|
|
|
tgpi->draw_handle_screen = ED_region_draw_cb_activate(tgpi->ar->type, gpencil_interpolate_draw_screen, op, REGION_DRAW_POST_PIXEL);
|
|
|
|
|
tgpi->draw_handle_3d = ED_region_draw_cb_activate(tgpi->ar->type, gpencil_interpolate_draw_3d, op, REGION_DRAW_POST_VIEW);
|
|
|
|
|
/* set cursor to indicate modal */
|
|
|
|
|
WM_cursor_modal_set(win, BC_EW_SCROLLCURSOR);
|
|
|
|
|
/* update shift indicator in header */
|
|
|
|
|
gpencil_interpolate_status_indicators(tgpi);
|
|
|
|
|
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
|
|
|
|
|
|
|
|
|
|
/* add a modal handler for this operator */
|
|
|
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
|
|
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Modal handler: Events handling during interactive part */
|
|
|
|
|
static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
|
|
|
{
|
|
|
|
|
tGPDinterpolate *tgpi = op->customdata;
|
|
|
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
|
bGPDframe *gpf_dst;
|
|
|
|
|
bGPDstroke *gps_src, *gps_dst;
|
|
|
|
|
tGPDinterpolate_layer *tgpil;
|
|
|
|
|
const bool has_numinput = hasNumInput(&tgpi->num);
|
|
|
|
|
|
|
|
|
|
switch (event->type) {
|
|
|
|
|
case LEFTMOUSE: /* confirm */
|
|
|
|
|
case RETKEY:
|
|
|
|
|
{
|
|
|
|
|
/* return to normal cursor and header status */
|
|
|
|
|
ED_area_headerprint(tgpi->sa, NULL);
|
|
|
|
|
WM_cursor_modal_restore(win);
|
|
|
|
|
|
|
|
|
|
/* insert keyframes as required... */
|
|
|
|
|
for (tgpil = tgpi->ilayers.first; tgpil; tgpil = tgpil->next) {
|
|
|
|
|
gpf_dst = BKE_gpencil_layer_getframe(tgpil->gpl, tgpi->cframe, GP_GETFRAME_ADD_NEW);
|
|
|
|
|
/* copy strokes */
|
|
|
|
|
BLI_listbase_clear(&gpf_dst->strokes);
|
|
|
|
|
for (gps_src = tgpil->interFrame->strokes.first; gps_src; gps_src = gps_src->next) {
|
|
|
|
|
if (gps_src->totpoints == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* make copy of source stroke, then adjust pointer to points too */
|
|
|
|
|
gps_dst = MEM_dupallocN(gps_src);
|
|
|
|
|
gps_dst->points = MEM_dupallocN(gps_src->points);
|
|
|
|
|
gps_dst->triangles = MEM_dupallocN(gps_src->triangles);
|
|
|
|
|
gps_dst->flag |= GP_STROKE_RECALC_CACHES;
|
|
|
|
|
BLI_addtail(&gpf_dst->strokes, gps_dst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* clean up temp data */
|
|
|
|
|
gpencil_interpolate_exit(C, op);
|
|
|
|
|
|
|
|
|
|
/* done! */
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case ESCKEY: /* cancel */
|
|
|
|
|
case RIGHTMOUSE:
|
|
|
|
|
{
|
|
|
|
|
/* return to normal cursor and header status */
|
|
|
|
|
ED_area_headerprint(tgpi->sa, NULL);
|
|
|
|
|
WM_cursor_modal_restore(win);
|
|
|
|
|
|
|
|
|
|
/* clean up temp data */
|
|
|
|
|
gpencil_interpolate_exit(C, op);
|
|
|
|
|
|
|
|
|
|
/* canceled! */
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
case WHEELUPMOUSE:
|
|
|
|
|
{
|
|
|
|
|
tgpi->shift = tgpi->shift + 0.01f;
|
|
|
|
|
CLAMP(tgpi->shift, -1.0f, 1.0f);
|
|
|
|
|
RNA_float_set(op->ptr, "shift", tgpi->shift);
|
|
|
|
|
/* update screen */
|
|
|
|
|
gpencil_interpolate_update(C, op, tgpi);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case WHEELDOWNMOUSE:
|
|
|
|
|
{
|
|
|
|
|
tgpi->shift = tgpi->shift - 0.01f;
|
|
|
|
|
CLAMP(tgpi->shift, -1.0f, 1.0f);
|
|
|
|
|
RNA_float_set(op->ptr, "shift", tgpi->shift);
|
|
|
|
|
/* update screen */
|
|
|
|
|
gpencil_interpolate_update(C, op, tgpi);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case MOUSEMOVE: /* calculate new position */
|
|
|
|
|
{
|
|
|
|
|
/* only handle mousemove if not doing numinput */
|
|
|
|
|
if (has_numinput == false) {
|
|
|
|
|
/* update shift based on position of mouse */
|
|
|
|
|
gpencil_mouse_update_shift(tgpi, op, event);
|
|
|
|
|
/* update screen */
|
|
|
|
|
gpencil_interpolate_update(C, op, tgpi);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
if ((event->val == KM_PRESS) && handleNumInput(C, &tgpi->num, event)) {
|
|
|
|
|
float value;
|
|
|
|
|
|
|
|
|
|
/* Grab shift from numeric input, and store this new value (the user see an int) */
|
|
|
|
|
value = tgpi->shift * 100.0f;
|
|
|
|
|
applyNumInput(&tgpi->num, &value);
|
|
|
|
|
|
|
|
|
|
tgpi->shift = value / 100.0f;
|
|
|
|
|
CLAMP(tgpi->shift, -1.0f, 1.0f);
|
|
|
|
|
RNA_float_set(op->ptr, "shift", tgpi->shift);
|
|
|
|
|
|
|
|
|
|
/* update screen */
|
|
|
|
|
gpencil_interpolate_update(C, op, tgpi);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* unhandled event - allow to pass through */
|
|
|
|
|
return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* still running... */
|
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Define modal operator for interpolation */
|
|
|
|
|
void GPENCIL_OT_interpolate(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Grease Pencil Interpolation";
|
|
|
|
|
ot->idname = "GPENCIL_OT_interpolate";
|
|
|
|
|
ot->description = "Interpolate grease pencil strokes between frames";
|
|
|
|
|
|
|
|
|
|
/* api callbacks */
|
|
|
|
|
ot->invoke = gpencil_interpolate_invoke;
|
|
|
|
|
ot->modal = gpencil_interpolate_modal;
|
|
|
|
|
ot->cancel = gpencil_interpolate_cancel;
|
|
|
|
|
ot->poll = gpencil_interpolate_poll;
|
|
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
|
|
|
|
|
|
|
|
|
|
RNA_def_float_percentage(ot->srna, "shift", 0.0f, -1.0f, 1.0f, "Shift", "Displacement factor for the interpolate operation", -0.9f, 0.9f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* =============== Interpolate sequence ===============*/
|
|
|
|
|
/* Create Sequence Interpolation */
|
|
|
|
|
static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op)
|
|
|
|
|
{
|
|
|
|
|
Scene *scene = CTX_data_scene(C);
|
|
|
|
|
ToolSettings *ts = CTX_data_tool_settings(C);
|
|
|
|
|
bGPdata * gpd = CTX_data_gpencil_data(C);
|
|
|
|
|
bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
|
|
|
|
|
bGPDlayer *gpl;
|
|
|
|
|
bGPDframe *prevFrame, *nextFrame, *interFrame;
|
|
|
|
|
bGPDstroke *gps_from, *gps_to, *new_stroke;
|
|
|
|
|
float factor;
|
|
|
|
|
int cframe, fFrame;
|
|
|
|
|
int flag = ts->gp_sculpt.flag;
|
|
|
|
|
|
|
|
|
|
/* cannot interpolate if not between 2 frames */
|
|
|
|
|
if ((active_gpl->actframe == NULL) || (active_gpl->actframe->next == NULL)) {
|
|
|
|
|
BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames");
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
/* cannot interpolate in extremes */
|
|
|
|
|
if ((active_gpl->actframe->framenum == scene->r.cfra) || (active_gpl->actframe->next->framenum == scene->r.cfra)) {
|
|
|
|
|
BKE_report(op->reports, RPT_ERROR, "Interpolation requires to be between two grease pencil frames");
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* loop all layer to check if need interpolation */
|
|
|
|
|
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
|
|
|
/* all layers or only active */
|
|
|
|
|
if (((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* only editable and visible layers are considered */
|
|
|
|
|
if (!gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* store extremes */
|
|
|
|
|
prevFrame = gpl->actframe;
|
|
|
|
|
nextFrame = gpl->actframe->next;
|
|
|
|
|
/* Loop over intermediary frames and create the interpolation */
|
|
|
|
|
for (cframe = prevFrame->framenum + 1; cframe < nextFrame->framenum; cframe++) {
|
|
|
|
|
interFrame = NULL;
|
|
|
|
|
|
|
|
|
|
/* get interpolation factor */
|
|
|
|
|
factor = (float)(cframe - prevFrame->framenum) / (nextFrame->framenum - prevFrame->framenum + 1);
|
|
|
|
|
|
|
|
|
|
/* create new strokes data with interpolated points reading original stroke */
|
|
|
|
|
for (gps_from = prevFrame->strokes.first; gps_from; gps_from = gps_from->next) {
|
|
|
|
|
/* only selected */
|
|
|
|
|
if ((flag & GP_BRUSHEDIT_FLAG_INTERPOLATE_ONLY_SELECTED) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* skip strokes that are invalid for current view */
|
|
|
|
|
if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* check if the color is editable */
|
|
|
|
|
if (ED_gpencil_stroke_color_use(gpl, gps_from) == false) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* get final stroke to interpolate */
|
|
|
|
|
fFrame = BLI_findindex(&prevFrame->strokes, gps_from);
|
|
|
|
|
gps_to = BLI_findlink(&nextFrame->strokes, fFrame);
|
|
|
|
|
if (gps_to == NULL) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* create a new frame if needed */
|
|
|
|
|
if (interFrame == NULL) {
|
|
|
|
|
interFrame = BKE_gpencil_layer_getframe(gpl, cframe, GP_GETFRAME_ADD_NEW);
|
|
|
|
|
}
|
|
|
|
|
/* create new stroke */
|
|
|
|
|
new_stroke = MEM_dupallocN(gps_from);
|
|
|
|
|
new_stroke->points = MEM_dupallocN(gps_from->points);
|
|
|
|
|
new_stroke->triangles = MEM_dupallocN(gps_from->triangles);
|
|
|
|
|
/* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */
|
|
|
|
|
if (gps_from->totpoints > gps_to->totpoints) {
|
|
|
|
|
new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points) * gps_to->totpoints);
|
|
|
|
|
new_stroke->totpoints = gps_to->totpoints;
|
|
|
|
|
new_stroke->tot_triangles = 0;
|
|
|
|
|
new_stroke->flag |= GP_STROKE_RECALC_CACHES;
|
|
|
|
|
}
|
|
|
|
|
/* update points position */
|
|
|
|
|
gp_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
|
|
|
|
|
|
|
|
|
|
/* add to strokes */
|
|
|
|
|
BLI_addtail(&interFrame->strokes, new_stroke);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* notifiers */
|
|
|
|
|
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
|
|
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Define sequence interpolation */
|
|
|
|
|
void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Grease Pencil Sequence Interpolation";
|
|
|
|
|
ot->idname = "GPENCIL_OT_interpolate_sequence";
|
|
|
|
|
ot->description = "Interpolate full grease pencil strokes sequence between frames";
|
|
|
|
|
|
|
|
|
|
/* api callbacks */
|
|
|
|
|
ot->exec = gpencil_interpolate_seq_exec;
|
|
|
|
|
ot->poll = gpencil_interpolate_poll;
|
|
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
}
|
|
|
|
|
/* ========= End Interpolation operators ========================== */
|
|
|
|
|