GPencil: "Reproject Strokes" operator

A common problem encountered by artists was that they would accidentally move
the 3D cursor while drawing, causing their strokes to end up in weird places in
3D space when viewing the drawing again from other perspectives.

This operator helps fix up this mess by taking the selected strokes, projecting them
to screenspace, and then back to 3D space again. As a result, it should be as if
you had directly drawn the whole thing again, but from the current viewpoint instead.
Unfortunately, if there was originally some depth information present (i.e. you already
started reshaping the sketch in 3D), then that will get lost during this process.
But so far, my tests indicate that this seems to work well enough.
This commit is contained in:
Joshua Leung 2016-08-29 03:03:09 +12:00
parent dec7145032
commit 299bb019b5
5 changed files with 156 additions and 0 deletions

View File

@ -225,6 +225,10 @@ class GreasePencilStrokeEditPanel:
if gpd:
col.prop(gpd, "show_stroke_direction", text="Show Directions")
if is_3d_view:
layout.separator()
layout.operator("gpencil.reproject")
class GreasePencilBrushPanel:
# subclass must set

View File

@ -72,6 +72,7 @@
#include "ED_gpencil.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_view3d.h"
#include "gpencil_intern.h"
@ -1881,4 +1882,83 @@ void GPENCIL_OT_stroke_flip(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ***************** Reproject Strokes ********************** */
static int gp_strokes_reproject_poll(bContext *C)
{
/* 2 Requirements:
* - 1) Editable GP data
* - 2) 3D View only (2D editors don't have projection issues)
*/
return (gp_stroke_edit_poll(C) && ED_operator_view3d_active(C));
}
static int gp_strokes_reproject_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
GP_SpaceConversion gsc = {NULL};
/* init space conversion stuff */
gp_point_conversion_init(C, &gsc);
/* Go through each editable + selected stroke, adjusting each of its points one by one... */
GP_EDITABLE_STROKES_BEGIN(C, gpl, gps)
{
if (gps->flag & GP_STROKE_SELECT) {
bGPDspoint *pt;
int i;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
float xy[2];
/* 3D to Screenspace */
/* Note: We can't use gp_point_to_xy() here because that uses ints for the screenspace
* coordinates, resulting in lost precision, which in turn causes stairstepping
* artifacts in the final points.
*/
if (gpl->parent == NULL) {
gp_point_to_xy_fl(&gsc, gps, pt, &xy[0], &xy[1]);
}
else {
bGPDspoint pt2;
gp_point_to_parent_space(pt, diff_mat, &pt2);
gp_point_to_xy_fl(&gsc, gps, &pt2, &xy[0], &xy[1]);
}
/* Project screenspace back to 3D space (from current perspective)
* so that all points have been treated the same way
*/
gp_point_xy_to_3d(&gsc, scene, xy, &pt->x);
/* Unapply parent corrections */
if (gpl->parent) {
float inverse_diff_mat[4][4];
invert_m4_m4(inverse_diff_mat, diff_mat);
mul_m4_v3(inverse_diff_mat, &pt->x);
}
}
}
}
GP_EDITABLE_STROKES_END;
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_reproject(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reproject Strokes";
ot->idname = "GPENCIL_OT_reproject";
ot->description = "Reproject the selected strokes from the current viewpoint to get all points on the same plane again "
"(e.g. to fix problems from accidental 3D cursor movement, or viewport changes)";
/* callbacks */
ot->exec = gp_strokes_reproject_exec;
ot->poll = gp_strokes_reproject_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ************************************************ */

View File

@ -100,6 +100,18 @@ void gp_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc);
void gp_point_to_xy(GP_SpaceConversion *settings, struct bGPDstroke *gps, struct bGPDspoint *pt,
int *r_x, int *r_y);
/**
* Convert a Grease Pencil coordinate (i.e. can be 2D or 3D) to screenspace (2D)
*
* Just like gp_point_to_xy(), except the resulting coordinates are floats not ints.
* Use this version to solve "stair-step" artifacts which may arise when roundtripping the calculations.
*
* \param[out] r_x The screen-space x-coordinate of the point
* \param[out] r_y The screen-space y-coordinate of the point
*/
void gp_point_to_xy_fl(GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
float *r_x, float *r_y);
/**
* Convert point to parent space
*
@ -250,6 +262,7 @@ void GPENCIL_OT_snap_to_cursor(struct wmOperatorType *ot);
void GPENCIL_OT_snap_cursor_to_selected(struct wmOperatorType *ot);
void GPENCIL_OT_snap_cursor_to_center(struct wmOperatorType *ot);
void GPENCIL_OT_reproject(struct wmOperatorType *ot);
/* stroke sculpting -- */

View File

@ -375,6 +375,8 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_snap_to_cursor);
WM_operatortype_append(GPENCIL_OT_snap_cursor_to_selected);
WM_operatortype_append(GPENCIL_OT_reproject);
WM_operatortype_append(GPENCIL_OT_brush_paint);
/* Editing (Buttons) ------------ */

View File

@ -628,6 +628,63 @@ void gp_point_to_xy(GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
}
}
/* Convert Grease Pencil points to screen-space values (as floats)
* WARNING: This assumes that the caller has already checked whether the stroke in question can be drawn
*/
void gp_point_to_xy_fl(GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
float *r_x, float *r_y)
{
ARegion *ar = gsc->ar;
View2D *v2d = gsc->v2d;
rctf *subrect = gsc->subrect;
float xyval[2];
/* sanity checks */
BLI_assert(!(gps->flag & GP_STROKE_3DSPACE) || (gsc->sa->spacetype == SPACE_VIEW3D));
BLI_assert(!(gps->flag & GP_STROKE_2DSPACE) || (gsc->sa->spacetype != SPACE_VIEW3D));
if (gps->flag & GP_STROKE_3DSPACE) {
if (ED_view3d_project_float_global(ar, &pt->x, xyval, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
*r_x = xyval[0];
*r_y = xyval[1];
}
else {
*r_x = 0.0f;
*r_y = 0.0f;
}
}
else if (gps->flag & GP_STROKE_2DSPACE) {
float vec[3] = {pt->x, pt->y, 0.0f};
int t_x, t_y;
mul_m4_v3(gsc->mat, vec);
UI_view2d_view_to_region_clip(v2d, vec[0], vec[1], &t_x, &t_y);
if ((t_x == t_y) && (t_x == V2D_IS_CLIPPED)) {
/* XXX: Or should we just always use the values as-is? */
*r_x = 0.0f;
*r_y = 0.0f;
}
else {
*r_x = (float)t_x;
*r_y = (float)t_y;
}
}
else {
if (subrect == NULL) {
/* normal 3D view (or view space) */
*r_x = (pt->x / 100.0f * ar->winx);
*r_y = (pt->y / 100.0f * ar->winy);
}
else {
/* camera view, use subrect */
*r_x = ((pt->x / 100.0f) * BLI_rctf_size_x(subrect)) + subrect->xmin;
*r_y = ((pt->y / 100.0f) * BLI_rctf_size_y(subrect)) + subrect->ymin;
}
}
}
/**
* Project screenspace coordinates to 3D-space
*