GPencil: New option to lock strokes to axis

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.
This commit is contained in:
Antonio Vazquez 2016-10-22 16:44:11 +02:00
parent 9d0ac94d52
commit 5765deecd4
4 changed files with 134 additions and 23 deletions

View 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

View 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;
}

View 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;

View 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");