GPencil: "Reproject Strokes" operator
authorJoshua Leung <aligorith@gmail.com>
Sun, 28 Aug 2016 15:03:09 +0000 (03:03 +1200)
committerJoshua Leung <aligorith@gmail.com>
Mon, 29 Aug 2016 02:51:30 +0000 (14:51 +1200)
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.

release/scripts/startup/bl_ui/properties_grease_pencil_common.py
source/blender/editors/gpencil/gpencil_edit.c
source/blender/editors/gpencil/gpencil_intern.h
source/blender/editors/gpencil/gpencil_ops.c
source/blender/editors/gpencil/gpencil_utils.c

index 80ab4e4d14b62095255a37dae5396398d0ef3938..ba7006da45e241e580c8f38ca91654a2092d9445 100644 (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
index 3fe53e3d0ab2ffc5bb5e5ec53efa7e6476877e54..d9b6c8046cd7766d43ff3c378767df2ba0ba7712 100644 (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;
+}
+
 /* ************************************************ */
index e8d007c6c192aec7bacc2239e6d6881800c4154e..4178d49d6527b70c5953e7dbc27859bd2fa1508b 100644 (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 -- */
 
index 50f4e795d70971ddd0b33b84435b60c4ee84c66c..ae1c5554521881bfe66f747c95e47e8bd2aed738 100644 (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) ------------ */
index a61c83be54f206da02580fefa053a9edaa3801c3..564ba639983904cc7f14f60611e3ca3a7c39f799 100644 (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
  *