diff options
Diffstat (limited to 'source')
19 files changed, 445 insertions, 119 deletions
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index 2e68c88c5f7..c959504f84f 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -480,6 +480,7 @@ void BKE_brush_gpencil_presets(bContext *C) brush->gpencil_settings->fill_leak = 3; brush->gpencil_settings->fill_threshold = 0.1f; brush->gpencil_settings->fill_simplylvl = 1; + brush->gpencil_settings->fill_factor = 1; brush->gpencil_settings->icon_id = GP_BRUSH_ICON_FILL; brush->gpencil_tool = GPAINT_TOOL_FILL; diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c b/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c index 1f3874e70ba..b72a6ebebbd 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c @@ -270,7 +270,7 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness) for (int i = 0; i < totpoints; i++, tpt++) { ED_gpencil_tpoint_to_point(ar, origin, tpt, &pt); - ED_gp_project_point_to_plane(ob, rv3d, origin, ts->gp_sculpt.lock_axis - 1, &pt); + ED_gp_project_point_to_plane(scene, ob, rv3d, origin, ts->gp_sculpt.lock_axis - 1, &pt); /* first point for adjacency (not drawn) */ if (i == 0) { @@ -361,7 +361,7 @@ GPUBatch *DRW_gpencil_get_buffer_point_geom(bGPdata *gpd, short thickness) for (int i = 0; i < totpoints; i++, tpt++) { ED_gpencil_tpoint_to_point(ar, origin, tpt, &pt); - ED_gp_project_point_to_plane(ob, rv3d, origin, ts->gp_sculpt.lock_axis - 1, &pt); + ED_gp_project_point_to_plane(scene, ob, rv3d, origin, ts->gp_sculpt.lock_axis - 1, &pt); /* set point */ gpencil_set_stroke_point( diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_utils.c b/source/blender/draw/engines/gpencil/gpencil_draw_utils.c index a50b4fac15c..2710ecc5e5b 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_utils.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_utils.c @@ -506,7 +506,7 @@ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create( DRW_shgroup_uniform_float(grp, "pixsize", stl->storage->pixsize, 1); /* avoid wrong values */ - if ((gpd) && (gpd->pixfactor == 0)) { + if ((gpd) && (gpd->pixfactor == 0.0f)) { gpd->pixfactor = GP_DEFAULT_PIX_FACTOR; } @@ -627,7 +627,7 @@ static DRWShadingGroup *DRW_gpencil_shgroup_point_create( DRW_shgroup_uniform_float(grp, "pixsize", stl->storage->pixsize, 1); /* avoid wrong values */ - if ((gpd) && (gpd->pixfactor == 0)) { + if ((gpd) && (gpd->pixfactor == 0.0f)) { gpd->pixfactor = GP_DEFAULT_PIX_FACTOR; } diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.c b/source/blender/draw/engines/gpencil/gpencil_engine.c index 5f2652b0bfb..7fbae5e98ea 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.c +++ b/source/blender/draw/engines/gpencil/gpencil_engine.c @@ -582,6 +582,7 @@ void GPENCIL_cache_populate(void *vedata, Object *ob) Scene *scene = draw_ctx->scene; ToolSettings *ts = scene->toolsettings; View3D *v3d = draw_ctx->v3d; + const View3DCursor *cursor = &scene->cursor; if (ob->type == OB_GPENCIL && ob->data) { bGPdata *gpd = (bGPdata *)ob->data; @@ -626,7 +627,9 @@ void GPENCIL_cache_populate(void *vedata, Object *ob) if ((v3d) && ((v3d->flag2 & V3D_HIDE_OVERLAYS) == 0) && (v3d->gp_flag & V3D_GP_SHOW_GRID) && - (ob->type == OB_GPENCIL) && (ob == draw_ctx->obact)) + (ob->type == OB_GPENCIL) && (ob == draw_ctx->obact) && + ((ts->gpencil_v3d_align & GP_PROJECT_DEPTH_VIEW) == 0) && + ((ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) == 0)) { GPU_BATCH_DISCARD_SAFE(e_data.batch_grid); MEM_SAFE_FREE(e_data.batch_grid); @@ -634,13 +637,36 @@ void GPENCIL_cache_populate(void *vedata, Object *ob) e_data.batch_grid = DRW_gpencil_get_grid(ob); /* define grid orientation */ - if (ts->gp_sculpt.lock_axis != GP_LOCKAXIS_VIEW) { - copy_m4_m4(stl->storage->grid_matrix, ob->obmat); + switch (ts->gp_sculpt.lock_axis) { + case GP_LOCKAXIS_VIEW: + { + /* align always to view */ + invert_m4_m4(stl->storage->grid_matrix, draw_ctx->rv3d->viewmat); + /* copy ob location */ + copy_v3_v3(stl->storage->grid_matrix[3], ob->obmat[3]); + break; + } + case GP_LOCKAXIS_CURSOR: + { + float scale[3] = { 1.0f, 1.0f, 1.0f }; + loc_eul_size_to_mat4(stl->storage->grid_matrix, + cursor->location, + cursor->rotation_euler, + scale); + break; + } + default: + { + copy_m4_m4(stl->storage->grid_matrix, ob->obmat); + break; + } + } + + /* Move the origin to Object or Cursor */ + if (ts->gpencil_v3d_align & GP_PROJECT_CURSOR) { + copy_v3_v3(stl->storage->grid_matrix[3], cursor->location); } else { - /* align always to view */ - invert_m4_m4(stl->storage->grid_matrix, draw_ctx->rv3d->viewmat); - /* copy ob location */ copy_v3_v3(stl->storage->grid_matrix[3], ob->obmat[3]); } diff --git a/source/blender/editors/gpencil/drawgpencil.c b/source/blender/editors/gpencil/drawgpencil.c index 1ae3176d393..ebe0fa61b21 100644 --- a/source/blender/editors/gpencil/drawgpencil.c +++ b/source/blender/editors/gpencil/drawgpencil.c @@ -720,9 +720,12 @@ static void gp_draw_stroke_3d(tGPDdraw *tgpw, short thickness, const float ink[4 immUniform1f("objscale", obj_scale); int keep_size = (int)((tgpw->gpd) && (tgpw->gpd->flag & GP_DATA_STROKE_KEEPTHICKNESS)); immUniform1i("keep_size", keep_size); - immUniform1i("pixfactor", tgpw->gpd->pixfactor); + immUniform1f("pixfactor", tgpw->gpd->pixfactor); /* xray mode always to 3D space to avoid wrong zdepth calculation (T60051) */ immUniform1i("xraymode", GP_XRAY_3DSPACE); + immUniform1i("caps_start", (int)tgpw->gps->caps[0]); + immUniform1i("caps_end", (int)tgpw->gps->caps[1]); + immUniform1i("fill_stroke", (int)tgpw->is_fill_stroke); /* draw stroke curve */ GPU_line_width(max_ff(curpressure * thickness, 1.0f)); @@ -733,23 +736,22 @@ static void gp_draw_stroke_3d(tGPDdraw *tgpw, short thickness, const float ink[4 /* first point for adjacency (not drawn) */ if (i == 0) { gp_set_point_varying_color(points, ink, attr_id.color); - immAttr1f(attr_id.thickness, max_ff(curpressure * thickness, 1.0f)); + if ((cyclic) && (totpoints > 2)) { + immAttr1f(attr_id.thickness, max_ff((points + totpoints - 1)->pressure * thickness, 1.0f)); mul_v3_m4v3(fpt, tgpw->diff_mat, &(points + totpoints - 1)->x); } else { + immAttr1f(attr_id.thickness, max_ff((points + 1)->pressure * thickness, 1.0f)); mul_v3_m4v3(fpt, tgpw->diff_mat, &(points + 1)->x); } - mul_v3_fl(fpt, -1.0f); immVertex3fv(attr_id.pos, fpt); } /* set point */ gp_set_point_varying_color(pt, ink, attr_id.color); - immAttr1f(attr_id.thickness, max_ff(curpressure * thickness, 1.0f)); + immAttr1f(attr_id.thickness, max_ff(pt->pressure * thickness, 1.0f)); mul_v3_m4v3(fpt, tgpw->diff_mat, &pt->x); immVertex3fv(attr_id.pos, fpt); - - curpressure = pt->pressure; } if (cyclic && totpoints > 2) { @@ -765,10 +767,9 @@ static void gp_draw_stroke_3d(tGPDdraw *tgpw, short thickness, const float ink[4 } /* last adjacency point (not drawn) */ else { - gp_set_point_varying_color(points + totpoints - 1, ink, attr_id.color); - immAttr1f(attr_id.thickness, max_ff(curpressure * thickness, 1.0f)); + gp_set_point_varying_color(points + totpoints - 2, ink, attr_id.color); + immAttr1f(attr_id.thickness, max_ff((points + totpoints - 2)->pressure * thickness, 1.0f)); mul_v3_m4v3(fpt, tgpw->diff_mat, &(points + totpoints - 2)->x); - mul_v3_fl(fpt, -1.0f); immVertex3fv(attr_id.pos, fpt); } @@ -1029,6 +1030,10 @@ static void gp_draw_strokes(tGPDdraw *tgpw) /* calculate thickness */ sthickness = gps->thickness + tgpw->lthick; + if (tgpw->is_fill_stroke) { + sthickness = (short)max_ii(1, sthickness / 2); + } + if (sthickness <= 0) { continue; } @@ -1427,6 +1432,7 @@ static void gp_draw_data_layers(RegionView3D *rv3d, tgpw.winx = winx; tgpw.winy = winy; tgpw.dflag = dflag; + tgpw.is_fill_stroke = false; for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { /* calculate parent position */ diff --git a/source/blender/editors/gpencil/gpencil_brush.c b/source/blender/editors/gpencil/gpencil_brush.c index 0403a42a2c9..2df0edd3bf7 100644 --- a/source/blender/editors/gpencil/gpencil_brush.c +++ b/source/blender/editors/gpencil/gpencil_brush.c @@ -176,18 +176,54 @@ static void gpsculpt_compute_lock_axis(tGP_BrushEditData *gso, bGPDspoint *pt, c return; } - ToolSettings *ts = gso->scene->toolsettings; - int axis = ts->gp_sculpt.lock_axis; + const ToolSettings *ts = gso->scene->toolsettings; + const View3DCursor *cursor = &gso->scene->cursor; + const int axis = ts->gp_sculpt.lock_axis; /* lock axis control */ - if (axis == 1) { - pt->x = save_pt[0]; - } - if (axis == 2) { - pt->y = save_pt[1]; - } - if (axis == 3) { - pt->z = save_pt[2]; + switch (axis) { + case GP_LOCKAXIS_X: + { + pt->x = save_pt[0]; + break; + } + case GP_LOCKAXIS_Y: + { + pt->y = save_pt[1]; + break; + } + case GP_LOCKAXIS_Z: + { + pt->z = save_pt[2]; + break; + } + case GP_LOCKAXIS_CURSOR: + { + /* compute a plane with cursor normal and position of the point + before do the sculpt */ + const float scale[3] = { 1.0f, 1.0f, 1.0f }; + float plane_normal[3] = { 0.0f, 0.0f, 1.0f }; + float plane[4]; + float mat[4][4]; + float r_close[3]; + + loc_eul_size_to_mat4(mat, + cursor->location, + cursor->rotation_euler, + scale); + + mul_mat3_m4_v3(mat, plane_normal); + plane_from_point_normal_v3(plane, save_pt, plane_normal); + + /* find closest point to the plane with the new position */ + closest_to_plane_v3(r_close, plane, &pt->x); + copy_v3_v3(&pt->x, r_close); + break; + } + default: + { + break; + } } } diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 1388beb0b20..f14ba3b4f27 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -3280,6 +3280,8 @@ typedef enum eGP_ReprojectModes { GP_REPROJECT_VIEW, /* Reprojected on to the scene geometry */ GP_REPROJECT_SURFACE, + /* Reprojected on 3D cursor orientation */ + GP_REPROJECT_CURSOR, } eGP_ReprojectModes; static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) @@ -3334,10 +3336,17 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) gp_point_to_xy_fl(&gsc, gps, &pt2, &xy[0], &xy[1]); /* Project stroke in one axis */ - if (ELEM(mode, GP_REPROJECT_FRONT, GP_REPROJECT_SIDE, GP_REPROJECT_TOP)) { - ED_gp_get_drawing_reference( - scene, ob, gpl, - ts->gpencil_v3d_align, origin); + if (ELEM(mode, GP_REPROJECT_FRONT, GP_REPROJECT_SIDE, + GP_REPROJECT_TOP, GP_REPROJECT_CURSOR)) + { + if (mode != GP_REPROJECT_CURSOR) { + ED_gp_get_drawing_reference( + scene, ob, gpl, + ts->gpencil_v3d_align, origin); + } + else { + copy_v3_v3(origin, scene->cursor.location); + } int axis = 0; switch (mode) { @@ -3356,6 +3365,11 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) axis = 2; break; } + case GP_REPROJECT_CURSOR: + { + axis = 3; + break; + } default: { axis = 1; @@ -3364,8 +3378,8 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) } ED_gp_project_point_to_plane( - ob, rv3d, origin, - axis, &pt2); + scene, ob, rv3d, origin, + axis, &pt2); copy_v3_v3(&pt->x, &pt2.x); @@ -3427,6 +3441,8 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) "using 'Cursor' Stroke Placement"}, {GP_REPROJECT_SURFACE, "SURFACE", 0, "Surface", "Reproject the strokes on to the scene geometry, as if drawn using 'Surface' placement"}, + {GP_REPROJECT_CURSOR, "CURSOR", 0, "Cursor", + "Reproject the strokes using the orienation of 3D cursor"}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 7668b7cf5fd..36202bdac0a 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -132,6 +132,8 @@ typedef struct tGPDfill { int fill_simplylvl; /** boundary limits drawing mode */ int fill_draw_mode; + /* scaling factor */ + short fill_factor; /** number of elements currently in cache */ short sbuffer_size; @@ -146,6 +148,13 @@ typedef struct tGPDfill { BLI_Stack *stack; /** handle for drawing strokes while operator is running 3d stuff */ void *draw_handle_3d; + + /* tmp size x */ + int bwinx; + /* tmp size y */ + int bwiny; + rcti brect; + } tGPDfill; @@ -231,6 +240,7 @@ static void gp_draw_datablock(tGPDfill *tgpf, const float ink[4]) tgpw.dflag = 0; tgpw.disable_fill = 1; tgpw.dflag |= (GP_DRAWFILLS_ONLY3D | GP_DRAWFILLS_NOSTATUS); + glEnable(GL_BLEND); @@ -271,23 +281,27 @@ static void gp_draw_datablock(tGPDfill *tgpf, const float ink[4]) tgpw.t_gpf = gpf; /* reduce thickness to avoid gaps */ - tgpw.lthick = gpl->line_change - 4; + tgpw.is_fill_stroke = (tgpf->fill_draw_mode == GP_FILL_DMODE_CONTROL) ? false : true ; + tgpw.lthick = gpl->line_change; tgpw.opacity = 1.0; copy_v4_v4(tgpw.tintcolor, ink); tgpw.onion = true; tgpw.custonion = true; + bool textured_stroke = (gp_style->stroke_style == GP_STYLE_STROKE_STYLE_TEXTURE); + /* normal strokes */ - if ((tgpf->fill_draw_mode == GP_FILL_DMODE_STROKE) || - (tgpf->fill_draw_mode == GP_FILL_DMODE_BOTH)) + if (((tgpf->fill_draw_mode == GP_FILL_DMODE_STROKE) || + (tgpf->fill_draw_mode == GP_FILL_DMODE_BOTH)) && + !textured_stroke) { ED_gp_draw_fill(&tgpw); - } /* 3D Lines with basic shapes and invisible lines */ if ((tgpf->fill_draw_mode == GP_FILL_DMODE_CONTROL) || - (tgpf->fill_draw_mode == GP_FILL_DMODE_BOTH)) + (tgpf->fill_draw_mode == GP_FILL_DMODE_BOTH) || + textured_stroke) { gp_draw_basic_stroke( tgpf, gps, tgpw.diff_mat, gps->flag & GP_STROKE_CYCLIC, ink, @@ -300,17 +314,45 @@ static void gp_draw_datablock(tGPDfill *tgpf, const float ink[4]) } /* draw strokes in offscreen buffer */ -static void gp_render_offscreen(tGPDfill *tgpf) +static bool gp_render_offscreen(tGPDfill *tgpf) { bool is_ortho = false; float winmat[4][4]; if (!tgpf->gpd) { - return; + return false; } + + /* set temporary new size */ + tgpf->bwinx = tgpf->ar->winx; + tgpf->bwiny = tgpf->ar->winy; + tgpf->brect = tgpf->ar->winrct; + + /* resize ar */ + tgpf->ar->winrct.xmin = 0; + tgpf->ar->winrct.ymin = 0; + tgpf->ar->winrct.xmax = (int)tgpf->ar->winx * tgpf->fill_factor; + tgpf->ar->winrct.ymax = (int)tgpf->ar->winy * tgpf->fill_factor; + tgpf->ar->winx = (short)abs(tgpf->ar->winrct.xmax - tgpf->ar->winrct.xmin); + tgpf->ar->winy = (short)abs(tgpf->ar->winrct.ymax - tgpf->ar->winrct.ymin); + + /* save new size */ + tgpf->sizex = (int)tgpf->ar->winx; + tgpf->sizey = (int)tgpf->ar->winy; + + /* adjust center */ + float center[2]; + center[0] = (float)tgpf->center[0] * ((float)tgpf->ar->winx / (float)tgpf->bwinx); + center[1] = (float)tgpf->center[1] * ((float)tgpf->ar->winy / (float)tgpf->bwiny); + round_v2i_v2fl(tgpf->center, center); char err_out[256] = "unknown"; GPUOffScreen *offscreen = GPU_offscreen_create(tgpf->sizex, tgpf->sizey, 0, true, false, err_out); + if (offscreen == NULL) { + printf("GPencil - Fill - Unable to create fill buffer\n"); + return false; + } + GPU_offscreen_bind(offscreen, true); uint flag = IB_rect | IB_rectfloat; ImBuf *ibuf = IMB_allocImBuf(tgpf->sizex, tgpf->sizey, 32, flag); @@ -328,18 +370,6 @@ static void gp_render_offscreen(tGPDfill *tgpf) perspective_m4(winmat, viewplane.xmin, viewplane.xmax, viewplane.ymin, viewplane.ymax, clip_start, clip_end); } - /* set temporary new size */ - int bwinx = tgpf->ar->winx; - int bwiny = tgpf->ar->winy; - rcti brect = tgpf->ar->winrct; - - tgpf->ar->winx = (short)tgpf->sizex; - tgpf->ar->winy = (short)tgpf->sizey; - tgpf->ar->winrct.xmin = 0; - tgpf->ar->winrct.ymin = 0; - tgpf->ar->winrct.xmax = tgpf->sizex; - tgpf->ar->winrct.ymax = tgpf->sizey; - GPU_matrix_push_projection(); GPU_matrix_identity_set(); GPU_matrix_push(); @@ -359,11 +389,6 @@ static void gp_render_offscreen(tGPDfill *tgpf) float ink[4] = {1.0f, 0.0f, 0.0f, 1.0f}; gp_draw_datablock(tgpf, ink); - /* restore size */ - tgpf->ar->winx = (short)bwinx; - tgpf->ar->winy = (short)bwiny; - tgpf->ar->winrct = brect; - GPU_matrix_pop_projection(); GPU_matrix_pop(); @@ -386,6 +411,8 @@ static void gp_render_offscreen(tGPDfill *tgpf) /* switch back to window-system-provided framebuffer */ GPU_offscreen_unbind(offscreen, true); GPU_offscreen_free(offscreen); + + return true; } /* return pixel data (rgba) at index */ @@ -459,7 +486,8 @@ static bool is_leak_narrow(ImBuf *ibuf, const int maxpixel, int limit, int index } } else { - t_a = true; /* edge of image*/ + /* edge of image*/ + t_a = true; break; } } @@ -474,7 +502,8 @@ static bool is_leak_narrow(ImBuf *ibuf, const int maxpixel, int limit, int index } } else { - t_b = true; /* edge of image*/ + /* edge of image*/ + t_b = true; break; } } @@ -543,7 +572,7 @@ static void gpencil_boundaryfill_area(tGPDfill *tgpf) /* calculate index of the seed point using the position of the mouse */ int index = (tgpf->sizex * tgpf->center[1]) + tgpf->center[0]; - if ((index >= 0) && (index < maxpixel)) { + if ((index >= 0) && (index <= maxpixel)) { BLI_stack_push(stack, &index); } @@ -561,6 +590,7 @@ static void gpencil_boundaryfill_area(tGPDfill *tgpf) */ while (!BLI_stack_is_empty(stack)) { int v; + BLI_stack_pop(stack, &v); get_pixel(ibuf, v, rgba); @@ -568,7 +598,7 @@ static void gpencil_boundaryfill_area(tGPDfill *tgpf) if (true) { /* Was: 'rgba' */ /* check if no border(red) or already filled color(green) */ if ((rgba[0] != 1.0f) && (rgba[1] != 1.0f)) { - /* fill current pixel */ + /* fill current pixel with green */ set_pixel(ibuf, v, fill_col); /* add contact pixels */ @@ -580,22 +610,22 @@ static void gpencil_boundaryfill_area(tGPDfill *tgpf) } } /* pixel right */ - if (v + 1 < maxpixel) { + if (v + 1 <= maxpixel) { index = v + 1; if (!is_leak_narrow(ibuf, maxpixel, tgpf->fill_leak, v, LEAK_HORZ)) { BLI_stack_push(stack, &index); } } /* pixel top */ - if (v + tgpf->sizex < maxpixel) { - index = v + tgpf->sizex; + if (v + ibuf->x <= maxpixel) { + index = v + ibuf->x; if (!is_leak_narrow(ibuf, maxpixel, tgpf->fill_leak, v, LEAK_VERT)) { BLI_stack_push(stack, &index); } } /* pixel bottom */ - if (v - tgpf->sizex >= 0) { - index = v - tgpf->sizex; + if (v - ibuf->x >= 0) { + index = v - ibuf->x; if (!is_leak_narrow(ibuf, maxpixel, tgpf->fill_leak, v, LEAK_VERT)) { BLI_stack_push(stack, &index); } @@ -625,7 +655,7 @@ static void gpencil_clean_borders(tGPDfill *tgpf) int pixel = 0; /* horizontal lines */ - for (idx = 0; idx < ibuf->x - 1; idx++) { + for (idx = 0; idx < ibuf->x; idx++) { /* bottom line */ set_pixel(ibuf, idx, fill_col); /* top line */ @@ -649,12 +679,117 @@ static void gpencil_clean_borders(tGPDfill *tgpf) tgpf->ima->id.tag |= LIB_TAG_DOIT; } +/* Naive dilate + * + * Expand green areas into enclosing red areas. + * Using stack prevents creep when replacing colors directly. + * ----------- + * XXXXXXX + * XoooooX + * XXooXXX + * XXXX + * ----------- + */ +static void dilate(ImBuf *ibuf) +{ + BLI_Stack *stack = BLI_stack_new(sizeof(int), __func__); + const float green[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; + const int maxpixel = (ibuf->x * ibuf->y) - 1; + /* detect pixels and expand into red areas */ + for (int v = maxpixel; v != 0; v--) { + float color[4]; + int index; + int tp = 0; + int bm = 0; + int lt = 0; + int rt = 0; + get_pixel(ibuf, v, color); + if (color[1] == 1.0f) { + /* pixel left */ + if (v - 1 >= 0) { + index = v - 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + lt = index; + } + } + /* pixel right */ + if (v + 1 <= maxpixel) { + index = v + 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + rt = index; + } + } + /* pixel top */ + if (v + ibuf->x <= maxpixel) { + index = v + ibuf->x; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + tp = index; + } + } + /* pixel bottom */ + if (v - ibuf->x >= 0) { + index = v - ibuf->x; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + bm = index; + } + } + /* pixel top-left */ + if (tp && lt) { + index = tp - 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + } + } + /* pixel top-right */ + if (tp && rt) { + index = tp + 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + } + } + /* pixel bottom-left */ + if (bm && lt) { + index = bm - 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + } + } + /* pixel bottom-right */ + if (bm && rt) { + index = bm + 1; + get_pixel(ibuf, index, color); + if (color[0] == 1.0f) { + BLI_stack_push(stack, &index); + } + } + } + } + /* set dilated pixels */ + while (!BLI_stack_is_empty(stack)) { + int v; + BLI_stack_pop(stack, &v); + set_pixel(ibuf, v, green); + } + BLI_stack_free(stack); +} + /* Get the outline points of a shape using Moore Neighborhood algorithm * * This is a Blender customized version of the general algorithm described * in https://en.wikipedia.org/wiki/Moore_neighborhood */ -static void gpencil_get_outline_points(tGPDfill *tgpf) +static void gpencil_get_outline_points(tGPDfill *tgpf) { ImBuf *ibuf; float rgba[4]; @@ -686,6 +821,9 @@ static void gpencil_get_outline_points(tGPDfill *tgpf) ibuf = BKE_image_acquire_ibuf(tgpf->ima, NULL, &lock); int imagesize = ibuf->x * ibuf->y; + /* dilate */ + dilate(ibuf); + /* find the initial point to start outline analysis */ for (int idx = imagesize - 1; idx != 0; idx--) { get_pixel(ibuf, idx, rgba); @@ -835,9 +973,9 @@ static void gpencil_points_from_stack(tGPDfill *tgpf) while (!BLI_stack_is_empty(tgpf->stack)) { int v[2]; BLI_stack_pop(tgpf->stack, &v); - point2D->x = v[0]; - point2D->y = v[1]; - + copy_v2fl_v2i(&point2D->x, v); + /* shift points to center of pixel */ + add_v2_fl(&point2D->x, 0.5f); point2D->pressure = 1.0f; point2D->strength = 1.0f; point2D->time = 0.0f; @@ -962,7 +1100,7 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) tgpf->scene, tgpf->ob, tgpf->gpl, ts->gpencil_v3d_align, origin); ED_gp_project_stroke_to_plane( - tgpf->ob, tgpf->rv3d, gps, origin, + tgpf->scene, tgpf->ob, tgpf->rv3d, gps, origin, tgpf->lock_axis - 1); } @@ -1076,7 +1214,8 @@ static tGPDfill *gp_session_init_fill(bContext *C, wmOperator *UNUSED(op)) tgpf->fill_threshold = brush->gpencil_settings->fill_threshold; tgpf->fill_simplylvl = brush->gpencil_settings->fill_simplylvl; tgpf->fill_draw_mode = brush->gpencil_settings->fill_draw_mode; - + tgpf->fill_factor = (short)max_ii(1, min_ii((int)brush->gpencil_settings->fill_factor,8)); + /* get color info */ Material *ma = BKE_gpencil_get_material_from_brush(brush); /* if no brush defaults, get material and color info */ @@ -1238,34 +1377,38 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) in_bounds = BLI_rcti_isect_pt(&ar->winrct, event->x, event->y); if ((in_bounds) && (ar->regiontype == RGN_TYPE_WINDOW)) { + /* TODO GPXX: Verify the mouse click is right for any window size */ tgpf->center[0] = event->mval[0]; tgpf->center[1] = event->mval[1]; - /* save size */ - tgpf->sizex = ar->winx; - tgpf->sizey = ar->winy; - /* render screen to temp image */ - gp_render_offscreen(tgpf); + if ( gp_render_offscreen(tgpf) ) { - /* apply boundary fill */ - gpencil_boundaryfill_area(tgpf); + /* apply boundary fill */ + gpencil_boundaryfill_area(tgpf); - /* clean borders to avoid infinite loops */ - gpencil_clean_borders(tgpf); + /* clean borders to avoid infinite loops */ + gpencil_clean_borders(tgpf); - /* analyze outline */ - gpencil_get_outline_points(tgpf); + /* analyze outline */ + gpencil_get_outline_points(tgpf); - /* create array of points from stack */ - gpencil_points_from_stack(tgpf); + /* create array of points from stack */ + gpencil_points_from_stack(tgpf); - /* create z-depth array for reproject */ - gpencil_get_depth_array(tgpf); + /* create z-depth array for reproject */ + gpencil_get_depth_array(tgpf); + + /* create stroke and reproject */ + gpencil_stroke_from_buffer(tgpf); + + } - /* create stroke and reproject */ - gpencil_stroke_from_buffer(tgpf); + /* restore size */ + tgpf->ar->winx = (short)tgpf->bwinx; + tgpf->ar->winy = (short)tgpf->bwiny; + tgpf->ar->winrct = tgpf->brect; /* free temp stack data */ if (tgpf->stack) { diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 4fba83a5f02..aa47319e3d9 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -92,6 +92,7 @@ typedef struct tGPDdraw { float tintcolor[4]; /* tint color */ bool onion; /* onion flag */ bool custonion; /* use custom onion colors */ + bool is_fill_stroke; /* use fill tool */ float diff_mat[4][4]; /* matrix */ } tGPDdraw; diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 99b38098138..40f0c0e7b84 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -398,7 +398,9 @@ static void gp_reproject_toplane(tGPsdata *p, bGPDstroke *gps) /* get drawing origin */ gp_get_3d_reference(p, origin); - ED_gp_project_stroke_to_plane(obact, rv3d, gps, origin, p->lock_axis - 1); + ED_gp_project_stroke_to_plane( + p->scene, obact, rv3d, gps, + origin, p->lock_axis - 1); } /* convert screen-coordinates to buffer-coordinates */ @@ -752,11 +754,11 @@ static short gp_stroke_addpoint( gp_get_3d_reference(p, origin); /* reproject current */ ED_gpencil_tpoint_to_point(p->ar, origin, pt, &spt); - ED_gp_project_point_to_plane(obact, rv3d, origin, p->lock_axis - 1, &spt); + ED_gp_project_point_to_plane(p->scene, obact, rv3d, origin, p->lock_axis - 1, &spt); /* reproject previous */ ED_gpencil_tpoint_to_point(p->ar, origin, ptb, &spt2); - ED_gp_project_point_to_plane(obact, rv3d, origin, p->lock_axis - 1, &spt2); + ED_gp_project_point_to_plane(p->scene, obact, rv3d, origin, p->lock_axis - 1, &spt2); p->totpixlen += len_v3v3(&spt.x, &spt2.x) / pixsize; pt->uv_fac = p->totpixlen; if ((gp_style) && (gp_style->sima)) { diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 88906488e1a..962442824f9 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -930,11 +930,13 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) ts->gpencil_v3d_align, origin); /* reproject current */ ED_gpencil_tpoint_to_point(tgpi->ar, origin, tpt, &spt); - ED_gp_project_point_to_plane(tgpi->ob, tgpi->rv3d, origin, tgpi->lock_axis - 1, &spt); + ED_gp_project_point_to_plane(tgpi->scene, tgpi->ob, tgpi->rv3d, + origin, tgpi->lock_axis - 1, &spt); /* reproject previous */ ED_gpencil_tpoint_to_point(tgpi->ar, origin, tptb, &spt2); - ED_gp_project_point_to_plane(tgpi->ob, tgpi->rv3d, origin, tgpi->lock_axis - 1, &spt2); + ED_gp_project_point_to_plane(tgpi->scene, tgpi->ob, tgpi->rv3d, + origin, tgpi->lock_axis - 1, &spt2); tgpi->totpixlen += len_v3v3(&spt.x, &spt2.x) / pixsize; tpt->uv_fac = tgpi->totpixlen; if ((gp_style) && (gp_style->sima)) { @@ -993,7 +995,8 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) tgpi->scene, tgpi->ob, tgpi->gpl, ts->gpencil_v3d_align, origin); ED_gp_project_stroke_to_plane( - tgpi->ob, tgpi->rv3d, gps, origin, ts->gp_sculpt.lock_axis - 1); + tgpi->scene, tgpi->ob, tgpi->rv3d, gps, + origin, ts->gp_sculpt.lock_axis - 1); } /* if parented change position relative to parent object */ diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 84bfd709b10..1605353a156 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -937,8 +937,12 @@ void ED_gpencil_project_stroke_to_view(bContext *C, bGPDlayer *gpl, bGPDstroke * * Reproject all points of the stroke to a plane locked to axis to avoid stroke offset */ void ED_gp_project_stroke_to_plane( - const Object *ob, const RegionView3D *rv3d, bGPDstroke *gps, const float origin[3], const int axis) + const Scene *scene, const Object *ob, + const RegionView3D *rv3d, bGPDstroke *gps, + const float origin[3], const int axis) { + const ToolSettings *ts = scene->toolsettings; + const View3DCursor *cursor = &scene->cursor; float plane_normal[3]; float vn[3]; @@ -953,13 +957,37 @@ void ED_gp_project_stroke_to_plane( */ ED_view3d_global_to_vector(rv3d, origin, plane_normal); } - else { + else if (axis < 3) { plane_normal[axis] = 1.0f; /* if object, apply object rotation */ if (ob && (ob->type == OB_GPENCIL)) { - mul_mat3_m4_v3(ob->obmat, plane_normal); + float mat[4][4]; + copy_m4_m4(mat, ob->obmat); + + /* move origin to cursor */ + if (ts->gpencil_v3d_align & GP_PROJECT_CURSOR) { + copy_v3_v3(mat[3], cursor->location); + } + + mul_mat3_m4_v3(mat, plane_normal); } } + else { + float scale[3] = { 1.0f, 1.0f, 1.0f }; + plane_normal[2] = 1.0f; + float mat[4][4]; + loc_eul_size_to_mat4(mat, + cursor->location, + cursor->rotation_euler, + scale); + + /* move origin to object */ + if ((ts->gpencil_v3d_align & GP_PROJECT_CURSOR) == 0) { + copy_v3_v3(mat[3], ob->obmat[3]); + } + + mul_mat3_m4_v3(mat, plane_normal); + } /* Reproject the points in the plane */ for (int i = 0; i < gps->totpoints; i++) { @@ -984,8 +1012,12 @@ void ED_gp_project_stroke_to_plane( * \param[in, out] pt : Point to affect */ void ED_gp_project_point_to_plane( - const Object *ob, const RegionView3D *rv3d, const float origin[3], const int axis, bGPDspoint *pt) + const Scene *scene, const Object *ob, + const RegionView3D *rv3d, const float origin[3], + const int axis, bGPDspoint *pt) { + const ToolSettings *ts = scene->toolsettings; + const View3DCursor *cursor = &scene->cursor; float plane_normal[3]; float vn[3]; @@ -1000,14 +1032,37 @@ void ED_gp_project_point_to_plane( */ ED_view3d_global_to_vector(rv3d, origin, plane_normal); } - else { + else if (axis < 3) { plane_normal[axis] = 1.0f; /* if object, apply object rotation */ if (ob && (ob->type == OB_GPENCIL)) { - mul_mat3_m4_v3(ob->obmat, plane_normal); + float mat[4][4]; + copy_m4_m4(mat, ob->obmat); + + /* move origin to cursor */ + if (ts->gpencil_v3d_align & GP_PROJECT_CURSOR) { + copy_v3_v3(mat[3], cursor->location); + } + + mul_mat3_m4_v3(mat, plane_normal); } } + else { + float scale[3] = { 1.0f, 1.0f, 1.0f }; + plane_normal[2] = 1.0f; + float mat[4][4]; + loc_eul_size_to_mat4(mat, + cursor->location, + cursor->rotation_euler, + scale); + + /* move origin to object */ + if ((ts->gpencil_v3d_align & GP_PROJECT_CURSOR) == 0) { + copy_v3_v3(mat[3], ob->obmat[3]); + } + mul_mat3_m4_v3(mat, plane_normal); + } /* Reproject the points in the plane */ /* get a vector from the point with the current view direction of the viewport */ diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index e331032bd6e..fdbb8556891 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -225,9 +225,11 @@ void ED_gpencil_add_defaults(struct bContext *C, struct Object *ob); void ED_gpencil_setup_modes(struct bContext *C, struct bGPdata *gpd, int newmode); void ED_gp_project_stroke_to_plane( + const struct Scene *scene, const struct Object *ob, const struct RegionView3D *rv3d, struct bGPDstroke *gps, const float origin[3], const int axis); void ED_gp_project_point_to_plane( + const struct Scene *scene, const struct Object *ob, const struct RegionView3D *rv3d, const float origin[3], const int axis, struct bGPDspoint *pt); void ED_gp_get_drawing_reference( diff --git a/source/blender/gpu/shaders/gpu_shader_gpencil_stroke_geom.glsl b/source/blender/gpu/shaders/gpu_shader_gpencil_stroke_geom.glsl index 3de1bd838b3..6c7e2d17e06 100644 --- a/source/blender/gpu/shaders/gpu_shader_gpencil_stroke_geom.glsl +++ b/source/blender/gpu/shaders/gpu_shader_gpencil_stroke_geom.glsl @@ -1,6 +1,9 @@ uniform mat4 ModelViewProjectionMatrix; uniform vec2 Viewport; uniform int xraymode; +uniform int caps_start; +uniform int caps_end; +uniform int fill_stroke; layout(lines_adjacency) in; layout(triangle_strip, max_vertices = 13) out; @@ -15,6 +18,8 @@ out vec2 mTexCoord; #define GP_XRAY_3DSPACE 1 #define GP_XRAY_BACK 2 +#define GPENCIL_FLATCAP 1 + /* project 3d point to 2d on screen space */ vec2 toScreenSpace(vec4 vertex) { @@ -37,6 +42,22 @@ float getZdepth(vec4 point) /* in front by default */ return 0.0; } + +/* check equality but with a small tolerance */ +bool is_equal(vec4 p1, vec4 p2) +{ + float limit = 0.0001; + float x = abs(p1.x - p2.x); + float y = abs(p1.y - p2.y); + float z = abs(p1.z - p2.z); + + if ((x < limit) && (y < limit) && (z < limit)) { + return true; + } + + return false; +} + void main(void) { float MiterLimit = 0.75; @@ -134,10 +155,11 @@ void main(void) } /* generate the start endcap (alpha < 0 used as endcap flag)*/ - if (P0 == P2) { + float extend = (fill_stroke > 0) ? 2 : 1 ; + if ((caps_start != GPENCIL_FLATCAP) && is_equal(P0,P2)) { mTexCoord = vec2(1, 0.5); mColor = vec4(finalColor[1].rgb, finalColor[1].a * -1.0) ; - vec2 svn1 = normalize(sp1 - sp2) * length_a * 4.0; + vec2 svn1 = normalize(sp1 - sp2) * length_a * 4.0 * extend; gl_Position = vec4((sp1 + svn1) / Viewport, getZdepth(P1), 1.0); EmitVertex(); @@ -174,7 +196,7 @@ void main(void) EmitVertex(); /* generate the end endcap (alpha < 0 used as endcap flag)*/ - if (P1 == P3) { + if ((caps_end != GPENCIL_FLATCAP) && is_equal(P1,P3)) { mTexCoord = vec2(0, 1); mColor = vec4(finalColor[2].rgb, finalColor[2].a * -1.0) ; gl_Position = vec4((sp2 + (length_b * 2.0) * miter_b) / Viewport, getZdepth(P2), 1.0); @@ -187,7 +209,7 @@ void main(void) mTexCoord = vec2(1, 0.5); mColor = vec4(finalColor[2].rgb, finalColor[2].a * -1.0) ; - vec2 svn2 = normalize(sp2 - sp1) * length_b * 4.0; + vec2 svn2 = normalize(sp2 - sp1) * length_b * 4.0 * extend; gl_Position = vec4((sp2 + svn2) / Viewport, getZdepth(P2), 1.0); EmitVertex(); } diff --git a/source/blender/gpu/shaders/gpu_shader_gpencil_stroke_vert.glsl b/source/blender/gpu/shaders/gpu_shader_gpencil_stroke_vert.glsl index 5cbe2f60ebd..968f913d4e4 100644 --- a/source/blender/gpu/shaders/gpu_shader_gpencil_stroke_vert.glsl +++ b/source/blender/gpu/shaders/gpu_shader_gpencil_stroke_vert.glsl @@ -4,7 +4,7 @@ uniform mat4 ProjectionMatrix; uniform float pixsize; /* rv3d->pixsize */ uniform int keep_size; uniform float objscale; -uniform int pixfactor; +uniform float pixfactor; in vec3 pos; in vec4 color; diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 5fec1528e37..4bfbb3655af 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -83,23 +83,25 @@ typedef struct BrushGpencilSettings { float fill_threshold; /** Number of pixel to consider the leak is too small (x 2). */ short fill_leak; - char _pad1[6]; + /** Fill zoom factor */ + short fill_factor; + char _pad_1[4]; /** Number of simplify steps. */ - int fill_simplylvl; + int fill_simplylvl; /** Type of control lines drawing mode. */ - int fill_draw_mode; + int fill_draw_mode; /** Icon identifier. */ - int icon_id; + int icon_id; /** Maximum distance before generate new point for very fast mouse movements. */ - int input_samples; + int input_samples; /** Random factor for UV rotation. */ float uv_random; /** Moved to 'Brush.gpencil_tool'. */ - int brush_type DNA_DEPRECATED; + int brush_type DNA_DEPRECATED; /** Soft, hard or stroke. */ - int eraser_mode; + int eraser_mode; /** Smooth while drawing factor. */ float active_smooth; /** Factor to apply to strength for soft eraser. */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 711ac8c4bb1..428eb8fbc57 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1053,6 +1053,7 @@ typedef enum eGP_Lockaxis_Types { GP_LOCKAXIS_X = 1, GP_LOCKAXIS_Y = 2, GP_LOCKAXIS_Z = 3, + GP_LOCKAXIS_CURSOR = 4 } eGP_Lockaxis_Types; /* Settings for a GPencil Stroke Sculpting Brush */ diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index b14e97b8656..86ac8d3bae6 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -126,10 +126,10 @@ static EnumPropertyItem rna_enum_gpencil_brush_eraser_modes_items[] = { }; static EnumPropertyItem rna_enum_gpencil_fill_draw_modes_items[] = { + {GP_FILL_DMODE_BOTH, "BOTH", 0, "Default", "Use both visible strokes and edit lines as fill boundary limits"}, {GP_FILL_DMODE_STROKE, "STROKE", 0, "Strokes", "Use visible strokes as fill boundary limits"}, - {GP_FILL_DMODE_CONTROL, "CONTROL", 0, "Control", "Use internal control lines as fill boundary limits"}, - {GP_FILL_DMODE_BOTH, "BOTH", 0, "Both", "Use visible strokes and control lines as fill boundary limits"}, - {0, NULL, 0, NULL, NULL}, + {GP_FILL_DMODE_CONTROL, "CONTROL", 0, "Edit Lines", "Use edit lines as fill boundary limits"}, + {0, NULL, 0, NULL, NULL} }; static EnumPropertyItem rna_enum_gpencil_brush_icons_items[] = { @@ -1160,6 +1160,15 @@ static void rna_def_gpencil_options(BlenderRNA *brna) RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + /* fill factor size */ + prop = RNA_def_property(srna, "fill_factor", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "fill_factor"); + RNA_def_property_range(prop, 1, 8); + RNA_def_property_ui_text(prop, "Resolution", + "Multiplier for fill resolution, higher resolution is more accurate but slower"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + /* fill simplify steps */ prop = RNA_def_property(srna, "fill_simplify_level", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "fill_simplylvl"); diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 1393a9d9888..b66363f45ae 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -82,6 +82,7 @@ static const EnumPropertyItem rna_enum_gpencil_lock_axis_items[] = { {GP_LOCKAXIS_Y, "AXIS_Y", ICON_AXIS_FRONT, "Front (X-Z)", "Project strokes to plane locked to Y"}, {GP_LOCKAXIS_X, "AXIS_X", ICON_AXIS_SIDE, "Side (Y-Z)", "Project strokes to plane locked to X"}, {GP_LOCKAXIS_Z, "AXIS_Z", ICON_AXIS_TOP, "Top (X-Y)", "Project strokes to plane locked to Z"}, + {GP_LOCKAXIS_CURSOR, "CURSOR", ICON_PIVOT_CURSOR, "Cursor", "Align strokes to current 3D cursor orientation"}, {0, NULL, 0, NULL, NULL}, }; #endif |