GPencil: New option to lock strokes to axis
authorAntonioya <blendergit@gmail.com>
Sat, 22 Oct 2016 14:44:11 +0000 (16:44 +0200)
committerAntonioya <blendergit@gmail.com>
Sat, 22 Oct 2016 14:44:39 +0000 (16:44 +0200)
Now, the strokes can be locked to a plane set in the cursor location.
This option allow the artist to rotate the view and draw keeping the
strokes flat over the surface. This option is similar to surface option
but doesn't need a object.

The option is only valid for 3D view and strokes in CURSOR mode.

release/scripts/startup/bl_ui/properties_grease_pencil_common.py
source/blender/editors/gpencil/gpencil_paint.c
source/blender/makesdna/DNA_scene_types.h
source/blender/makesrna/intern/rna_sculpt_paint.c

index 84442f9b2d99b477c5850c7ba00d0b8427cbc40c..bc40932018d17dde70628705715d88b5a95589b3 100644 (file)
@@ -52,6 +52,12 @@ def gpencil_stroke_placement_settings(context, layout):
         row.active = getattr(ts, propname) in {'SURFACE', 'STROKE'}
         row.prop(ts, "use_gpencil_stroke_endpoints")
 
+        if context.scene.tool_settings.gpencil_stroke_placement_view3d == 'CURSOR':
+            row = col.row(align=True)
+            row.label("Lock axis:")
+            row = col.row(align=True)
+            row.prop(ts.gpencil_sculpt, "lockaxis", expand=True)
+
 
 def gpencil_active_brush_settings_simple(context, layout):
     brush = context.active_gpencil_brush
index cc45cbd82af15fb853dc1b4f578d853586e7577b..c23bfb1ff607443a50362cbd2443ce7541ec26c2 100644 (file)
@@ -40,6 +40,7 @@
 #include "BLI_math.h"
 #include "BLI_utildefines.h"
 #include "BLI_rand.h"
+#include "BLI_math_geom.h"
 
 #include "BLT_translation.h"
 
@@ -160,6 +161,7 @@ typedef struct tGPsdata {
        bGPDpalettecolor *palettecolor; /* current palette color */
        bGPDbrush *brush; /* current drawing brush */
        short straight[2];   /* 1: line horizontal, 2: line vertical, other: not defined, second element position */
+       int lock_axis;       /* lock drawing to one axis */
 } tGPsdata;
 
 /* ------ */
@@ -278,6 +280,64 @@ static bool gp_stroke_filtermval(tGPsdata *p, const int mval[2], int pmval[2])
                return false;
 }
 
+/* reproject the points of the stroke to a plane locked to axis to avoid stroke offset */
+static void gp_project_points_to_plane(RegionView3D *rv3d, bGPDstroke *gps, const float origin[3], const int axis)
+{
+       float plane_normal[3];
+       float vn[3];
+
+       float ray[3];
+       float rpoint[3];
+
+       /* normal vector for a plane locked to axis */
+       zero_v3(plane_normal);
+       plane_normal[axis] = 1.0f;
+
+       /* Reproject the points in the plane */
+       for (int i = 0; i < gps->totpoints; i++) {
+               bGPDspoint *pt = &gps->points[i];
+
+               /* get a vector from the point with the current view direction of the viewport */
+               ED_view3d_global_to_vector(rv3d, &pt->x, vn);
+
+               /* calculate line extrem point to create a ray that cross the plane */
+               mul_v3_fl(vn, -50.0f);
+               add_v3_v3v3(ray, &pt->x, vn);
+
+               /* if the line never intersect, the point is not changed */
+               if (isect_line_plane_v3(rpoint, &pt->x, ray, origin, plane_normal)) {
+                       copy_v3_v3(&pt->x, rpoint);
+               }
+       }
+}
+
+/* reproject stroke to plane locked to axis in 3d cursor location */
+static void gp_reproject_toplane(tGPsdata *p, bGPDstroke *gps)
+{
+       bGPdata *gpd = p->gpd;
+       float origin[3];
+       float cursor[3];
+       RegionView3D *rv3d = p->ar->regiondata;
+
+       /* verify the stroke mode is CURSOR 3d space mode */
+       if ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) == 0) {
+               return;
+       }
+       if ((*p->align_flag & GP_PROJECT_VIEWSPACE) == 0) {
+               return;
+       }
+       if ((*p->align_flag & GP_PROJECT_DEPTH_VIEW) || (*p->align_flag & GP_PROJECT_DEPTH_STROKE)) {
+               return;
+       }
+
+       /* get 3d cursor and set origin for locked axis only. Uses axis-1 because the enum for XYZ start with 1 */
+       gp_get_3d_reference(p, cursor);
+       zero_v3(origin);
+       origin[p->lock_axis - 1] = cursor[p->lock_axis - 1];
+
+       gp_project_points_to_plane(rv3d, gps, origin, p->lock_axis - 1);
+}
+
 /* convert screen-coordinates to buffer-coordinates */
 /* XXX this method needs a total overhaul! */
 static void gp_stroke_convertcoords(tGPsdata *p, const int mval[2], float out[3], float *depth)
@@ -581,6 +641,10 @@ static short gp_stroke_addpoint(tGPsdata *p, const int mval[2], float pressure,
                        
                        /* convert screen-coordinates to appropriate coordinates (and store them) */
                        gp_stroke_convertcoords(p, &pt->x, &pts->x, NULL);
+                       /* if axis locked, reproject to plane locked (only in 3d space) */
+                       if (p->lock_axis > GP_LOCKAXIS_NONE) {
+                               gp_reproject_toplane(p, gps);
+                       }
                        /* if parented change position relative to parent object */
                        if (gpl->parent != NULL) {
                                gp_apply_parent_point(gpl, pts);
@@ -679,7 +743,6 @@ static void gp_stroke_simplify(tGPsdata *p)
        MEM_freeN(old_points);
 }
 
-
 /* make a new stroke from the buffer data */
 static void gp_stroke_newfrombuffer(tGPsdata *p)
 {
@@ -756,6 +819,10 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
                        
                        /* convert screen-coordinates to appropriate coordinates (and store them) */
                        gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
+                       /* if axis locked, reproject to plane locked (only in 3d space) */
+                       if (p->lock_axis > GP_LOCKAXIS_NONE) {
+                               gp_reproject_toplane(p, gps);
+                       }
                        /* if parented change position relative to parent object */
                        if (gpl->parent != NULL) {
                                gp_apply_parent_point(gpl, pt);
@@ -775,6 +842,10 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
                        
                        /* convert screen-coordinates to appropriate coordinates (and store them) */
                        gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
+                       /* if axis locked, reproject to plane locked (only in 3d space) */
+                       if (p->lock_axis > GP_LOCKAXIS_NONE) {
+                               gp_reproject_toplane(p, gps);
+                       }
                        /* if parented change position relative to parent object */
                        if (gpl->parent != NULL) {
                                gp_apply_parent_point(gpl, pt);
@@ -793,6 +864,10 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
                
                /* convert screen-coordinates to appropriate coordinates (and store them) */
                gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
+               /* if axis locked, reproject to plane locked (only in 3d space) */
+               if (p->lock_axis > GP_LOCKAXIS_NONE) {
+                       gp_reproject_toplane(p, gps);
+               }
                /* if parented change position relative to parent object */
                if (gpl->parent != NULL) {
                        gp_apply_parent_point(gpl, pt);
@@ -805,30 +880,30 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
        }
        else {
                float *depth_arr = NULL;
-               
+
                /* get an array of depths, far depths are blended */
                if (gpencil_project_check(p)) {
-                       int mval[2], mval_prev[2] = {0};
+                       int mval[2], mval_prev[2] = { 0 };
                        int interp_depth = 0;
                        int found_depth = 0;
-                       
+
                        depth_arr = MEM_mallocN(sizeof(float) * gpd->sbuffer_size, "depth_points");
-                       
+
                        for (i = 0, ptc = gpd->sbuffer; i < gpd->sbuffer_size; i++, ptc++, pt++) {
                                copy_v2_v2_int(mval, &ptc->x);
-                               
+
                                if ((ED_view3d_autodist_depth(p->ar, mval, depth_margin, depth_arr + i) == 0) &&
-                                   (i && (ED_view3d_autodist_depth_seg(p->ar, mval, mval_prev, depth_margin + 1, depth_arr + i) == 0)))
+                                       (i && (ED_view3d_autodist_depth_seg(p->ar, mval, mval_prev, depth_margin + 1, depth_arr + i) == 0)))
                                {
                                        interp_depth = true;
                                }
                                else {
                                        found_depth = true;
                                }
-                               
+
                                copy_v2_v2_int(mval_prev, mval);
                        }
-                       
+
                        if (found_depth == false) {
                                /* eeh... not much we can do.. :/, ignore depth in this case, use the 3D cursor */
                                for (i = gpd->sbuffer_size - 1; i >= 0; i--)
@@ -839,54 +914,54 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
                                        /* remove all info between the valid endpoints */
                                        int first_valid = 0;
                                        int last_valid = 0;
-                                       
+
                                        for (i = 0; i < gpd->sbuffer_size; i++) {
                                                if (depth_arr[i] != FLT_MAX)
                                                        break;
                                        }
                                        first_valid = i;
-                                       
+
                                        for (i = gpd->sbuffer_size - 1; i >= 0; i--) {
                                                if (depth_arr[i] != FLT_MAX)
                                                        break;
                                        }
                                        last_valid = i;
-                                       
+
                                        /* invalidate non-endpoints, so only blend between first and last */
                                        for (i = first_valid + 1; i < last_valid; i++)
                                                depth_arr[i] = FLT_MAX;
-                                       
+
                                        interp_depth = true;
                                }
-                               
+
                                if (interp_depth) {
                                        interp_sparse_array(depth_arr, gpd->sbuffer_size, FLT_MAX);
                                }
                        }
                }
-               
-               
+
+
                pt = gps->points;
-               
+
                /* convert all points (normal behavior) */
                for (i = 0, ptc = gpd->sbuffer; i < gpd->sbuffer_size && ptc; i++, ptc++, pt++) {
                        /* convert screen-coordinates to appropriate coordinates (and store them) */
                        gp_stroke_convertcoords(p, &ptc->x, &pt->x, depth_arr ? depth_arr + i : NULL);
-                       
+
                        /* copy pressure and time */
                        pt->pressure = ptc->pressure;
                        pt->strength = ptc->strength;
                        CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
                        pt->time = ptc->time;
                }
-               
+
                /* subdivide the stroke */
                if (sublevel > 0) {
                        int totpoints = gps->totpoints;
                        for (i = 0; i < sublevel; i++) {
                                /* we're adding one new point between each pair of verts on each step */
                                totpoints += totpoints - 1;
-                               
+
                                gp_subdivide_stroke(gps, totpoints);
                        }
                }
@@ -895,8 +970,8 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
                        gp_randomize_stroke(gps, brush);
                }
 
-               /* smooth stroke after subdiv - only if there's something to do 
-                * for each iteration, the factor is reduced to get a better smoothing without changing too much 
+               /* smooth stroke after subdiv - only if there's something to do
+                * for each iteration, the factor is reduced to get a better smoothing without changing too much
                 * the original stroke
                 */
                if (brush->draw_smoothfac > 0.0f) {
@@ -909,6 +984,11 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
                                reduce += 0.25f;  // reduce the factor
                        }
                }
+
+               /* if axis locked, reproject to plane locked (only in 3d space) */
+               if (p->lock_axis > GP_LOCKAXIS_NONE) {
+                       gp_reproject_toplane(p, gps);
+               }
                /* if parented change position relative to parent object */
                if (gpl->parent != NULL) {
                        gp_apply_parent(gpl, gps);
@@ -1467,6 +1547,8 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
        bGPdata *pdata = p->gpd;
        copy_v4_v4(pdata->scolor, palcolor->color);
        pdata->sflag = palcolor->flag;
+       /* lock axis */
+       p->lock_axis = ts->gp_sculpt.lock_axis;
 
        return 1;
 }
index 94f231972936643ae3397f86dc9dff0abdb9315a..9b0781ebe708f347cb92783e160bab44fdcf584e 100644 (file)
@@ -1160,6 +1160,14 @@ typedef enum eGP_EditBrush_Types {
        TOT_GP_EDITBRUSH_TYPES
 } eGP_EditBrush_Types;
 
+/* Lock axis options */
+typedef enum eGP_Lockaxis_Types {
+       GP_LOCKAXIS_NONE = 0,
+       GP_LOCKAXIS_X = 1,
+       GP_LOCKAXIS_Y = 2,
+       GP_LOCKAXIS_Z = 3
+} eGP_Lockaxis_Types;
+
 /* Settings for a GPencil Stroke Sculpting Brush */
 typedef struct GP_EditBrush_Data {
        short size;             /* radius of brush */
@@ -1190,7 +1198,7 @@ typedef struct GP_BrushEdit_Settings {
        
        int brushtype;                /* eGP_EditBrush_Types */
        int flag;                     /* eGP_BrushEdit_SettingsFlag */
-       char pad[4];
+       int lock_axis;                /* lock drawing to one axis */
        float alpha;                  /* alpha factor for selection color */
 } GP_BrushEdit_Settings;
 
index 7e1d0164eb4d6e5ae39058d06013fca8776c59a1..e169ef0d822964ac47d8b02abad28889750d8bc7 100644 (file)
@@ -75,6 +75,14 @@ EnumPropertyItem rna_enum_gpencil_sculpt_brush_items[] = {
        { 0, NULL, 0, NULL, NULL }
 };
 
+EnumPropertyItem rna_enum_gpencil_lockaxis_items[] = {
+       { GP_LOCKAXIS_NONE, "GP_LOCKAXIS_NONE", 0, "None", "" },
+       { GP_LOCKAXIS_X, "GP_LOCKAXIS_X", 0, "X", "Project strokes to plane locked to X" },
+       { GP_LOCKAXIS_Y, "GP_LOCKAXIS_Y", 0, "Y", "Project strokes to plane locked to Y" },
+       { GP_LOCKAXIS_Z, "GP_LOCKAXIS_Z", 0, "Z", "Project strokes to plane locked to Z" },
+       { 0, NULL, 0, NULL, NULL }
+};
+
 EnumPropertyItem rna_enum_symmetrize_direction_items[] = {
        {BMO_SYMMETRIZE_NEGATIVE_X, "NEGATIVE_X", 0, "-X to +X", ""},
        {BMO_SYMMETRIZE_POSITIVE_X, "POSITIVE_X", 0, "+X to -X", ""},
@@ -1053,6 +1061,13 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna)
        RNA_def_property_ui_text(prop, "Alpha", "Alpha value for selected vertices");
        RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, "rna_GPencil_update");
 
+       /* lock axis */
+       prop = RNA_def_property(srna, "lockaxis", PROP_ENUM, PROP_NONE);
+       RNA_def_property_enum_sdna(prop, NULL, "lock_axis");
+       RNA_def_property_enum_items(prop, rna_enum_gpencil_lockaxis_items);
+       RNA_def_property_ui_text(prop, "Lock", "");
+       RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
+
        /* brush */
        srna = RNA_def_struct(brna, "GPencilSculptBrush", NULL);
        RNA_def_struct_sdna(srna, "GP_EditBrush_Data");