Annotations: Add different arrow styles for line tool

This patch adds different kind of shapes/styles for the line extremes while using the annotation line tool.

Current Styles: (following @mendio mockup)
  - Arrow (closed arrow)
  - Open Arrow
  - Segment
  - Square
For future it would be great to have icons, it would be more intuitive (and less space) with previews of what each end / start of line does, like the google slides one as reference:

{F8511116}

Reviewed By: #grease_pencil, antoniov, HooglyBoogly

Differential Revision: https://developer.blender.org/D7608
This commit is contained in:
Juanfran Matheu 2020-05-06 11:38:32 +02:00 committed by Antonio Vazquez
parent 969d6b157e
commit 668dd146f6
4 changed files with 419 additions and 23 deletions

View File

@ -148,6 +148,8 @@ class _defs_annotate:
def draw_settings_common(context, layout, tool):
gpd = context.annotation_data
region_type = context.region.type
if gpd is not None:
if gpd.layers.active_note is not None:
text = gpd.layers.active_note
@ -160,17 +162,24 @@ class _defs_annotate:
gpl = context.active_annotation_layer
if gpl is not None:
layout.label(text="Annotation:")
sub = layout.row(align=True)
sub.ui_units_x = 8
if context.space_data.type == 'VIEW_3D':
if region_type == 'TOOL_HEADER':
sub = layout.split(align=True, factor=0.5)
sub.ui_units_x = 6.5
sub.prop(gpl, "color", text="")
else:
sub = layout.row(align=True)
sub.prop(gpl, "color", text="")
sub.popover(
panel="TOPBAR_PT_annotation_layers",
text=text,
)
else:
layout.prop(gpl, "color", text="")
sub.prop(gpl, "color", text="")
sub.popover(
panel="TOPBAR_PT_annotation_layers",
text=text,
)
tool_settings = context.tool_settings
space_type = tool.space_type
tool_settings = context.tool_settings
if space_type == 'VIEW_3D':
layout.separator()
@ -181,6 +190,21 @@ class _defs_annotate:
elif tool_settings.gpencil_stroke_placement_view3d in {'SURFACE', 'STROKE'}:
row.prop(tool_settings, "use_gpencil_stroke_endpoints")
if tool.idname == "builtin.annotate_line":
layout.separator()
props = tool.operator_properties("gpencil.annotate")
if region_type == 'TOOL_HEADER':
row = layout.row()
row.ui_units_x = 15
row.prop(props, "arrowstyle_start", text="Start")
row.separator()
row.prop(props, "arrowstyle_end", text="End")
else:
col = layout.row().column(align=True)
col.prop(props, "arrowstyle_start", text="Style Start")
col.prop(props, "arrowstyle_end", text="End")
@ToolDef.from_fn.with_args(draw_settings=draw_settings_common)
def scribble(*, draw_settings):
return dict(

View File

@ -90,14 +90,54 @@ typedef enum eDrawStrokeFlags {
/* ----- Tool Buffer Drawing ------ */
static void annotation_draw_stroke_arrow_buffer(uint pos,
const float *corner_point,
const float *arrow_coords,
const int arrow_style)
{
immBeginAtMost(GPU_PRIM_LINE_STRIP, arrow_style);
switch (arrow_style) {
case GP_STROKE_ARROWSTYLE_SEGMENT:
immVertex2f(pos, arrow_coords[0], arrow_coords[1]);
immVertex2f(pos, arrow_coords[2], arrow_coords[3]);
break;
case GP_STROKE_ARROWSTYLE_CLOSED:
immVertex2f(pos, arrow_coords[0], arrow_coords[1]);
immVertex2f(pos, arrow_coords[2], arrow_coords[3]);
immVertex2f(pos, arrow_coords[4], arrow_coords[5]);
immVertex2f(pos, arrow_coords[0], arrow_coords[1]);
break;
case GP_STROKE_ARROWSTYLE_OPEN:
immVertex2f(pos, arrow_coords[0], arrow_coords[1]);
immVertex2f(pos, corner_point[0], corner_point[1]);
immVertex2f(pos, arrow_coords[2], arrow_coords[3]);
break;
case GP_STROKE_ARROWSTYLE_SQUARE:
immVertex2f(pos, corner_point[0], corner_point[1]);
immVertex2f(pos, arrow_coords[0], arrow_coords[1]);
immVertex2f(pos, arrow_coords[4], arrow_coords[5]);
immVertex2f(pos, arrow_coords[6], arrow_coords[7]);
immVertex2f(pos, arrow_coords[2], arrow_coords[3]);
immVertex2f(pos, corner_point[0], corner_point[1]);
break;
default:
break;
}
immEnd();
}
/* draw stroke defined in buffer (simple ogl lines/points for now, as dotted lines) */
static void annotation_draw_stroke_buffer(const tGPspoint *points,
int totpoints,
static void annotation_draw_stroke_buffer(bGPdata *gps,
short thickness,
short dflag,
short sflag,
const float ink[4])
{
bGPdata_Runtime runtime = gps->runtime;
const tGPspoint *points = runtime.sbuffer;
int totpoints = runtime.sbuffer_used;
short sflag = runtime.sbuffer_sflag;
int draw_points = 0;
/* error checking */
@ -176,6 +216,26 @@ static void annotation_draw_stroke_buffer(const tGPspoint *points,
}
immEnd();
/* Draw arrow stroke. */
if (totpoints > 1) {
/* Draw ending arrow stroke. */
if ((sflag & GP_STROKE_USE_ARROW_END) &&
(runtime.arrow_end_style != GP_STROKE_ARROWSTYLE_NONE)) {
float end[2];
copy_v2_fl2(end, points[1].x, points[1].y);
annotation_draw_stroke_arrow_buffer(pos, end, runtime.arrow_end, runtime.arrow_end_style);
}
/* Draw starting arrow stroke. */
if ((sflag & GP_STROKE_USE_ARROW_START) &&
(runtime.arrow_start_style != GP_STROKE_ARROWSTYLE_NONE)) {
float start[2];
copy_v2_fl2(start, points[0].x, points[0].y);
annotation_draw_stroke_arrow_buffer(
pos, start, runtime.arrow_start, runtime.arrow_start_style);
}
}
immUnbindProgram();
}
@ -653,12 +713,7 @@ static void annotation_draw_data_layers(
* It should also be noted that sbuffer contains temporary point types
* i.e. tGPspoints NOT bGPDspoints
*/
annotation_draw_stroke_buffer(gpd->runtime.sbuffer,
gpd->runtime.sbuffer_used,
lthick,
dflag,
gpd->runtime.sbuffer_sflag,
ink);
annotation_draw_stroke_buffer(gpd, lthick, dflag, ink);
}
}
}

View File

@ -418,6 +418,85 @@ static void gp_smooth_buffer(tGPsdata *p, float inf, int idx)
copy_v2_v2(&ptc->x, c);
}
static void gp_stroke_arrow_calc_points_segment(float stroke_points[8],
const float ref_point[2],
const float dir_cw[2],
const float dir_ccw[2],
const float lenght,
const float sign)
{
stroke_points[0] = ref_point[0] + dir_cw[0] * lenght * sign;
stroke_points[1] = ref_point[1] + dir_cw[1] * lenght * sign;
stroke_points[2] = ref_point[0] + dir_ccw[0] * lenght * sign;
stroke_points[3] = ref_point[1] + dir_ccw[1] * lenght * sign;
}
static void gp_stroke_arrow_calc_points(tGPspoint *point,
const float stroke_dir[2],
float corner[2],
float stroke_points[8],
const int arrow_style)
{
const int arrow_lenght = 8;
float norm_dir[2];
copy_v2_v2(norm_dir, stroke_dir);
normalize_v2(norm_dir);
const float inv_norm_dir_clockwise[2] = {norm_dir[1], -norm_dir[0]};
const float inv_norm_dir_counterclockwise[2] = {-norm_dir[1], norm_dir[0]};
switch (arrow_style) {
case GP_STROKE_ARROWSTYLE_OPEN:
mul_v2_fl(norm_dir, arrow_lenght);
stroke_points[0] = corner[0] + inv_norm_dir_clockwise[0] * arrow_lenght + norm_dir[0];
stroke_points[1] = corner[1] + inv_norm_dir_clockwise[1] * arrow_lenght + norm_dir[1];
stroke_points[2] = corner[0] + inv_norm_dir_counterclockwise[0] * arrow_lenght + norm_dir[0];
stroke_points[3] = corner[1] + inv_norm_dir_counterclockwise[1] * arrow_lenght + norm_dir[1];
break;
case GP_STROKE_ARROWSTYLE_SEGMENT:
gp_stroke_arrow_calc_points_segment(stroke_points,
corner,
inv_norm_dir_clockwise,
inv_norm_dir_counterclockwise,
arrow_lenght,
1.0f);
break;
case GP_STROKE_ARROWSTYLE_CLOSED:
mul_v2_fl(norm_dir, arrow_lenght);
if (point != NULL) {
add_v2_v2(&point->x, norm_dir);
copy_v2_v2(corner, &point->x);
}
gp_stroke_arrow_calc_points_segment(stroke_points,
corner,
inv_norm_dir_clockwise,
inv_norm_dir_counterclockwise,
arrow_lenght,
-1.0f);
stroke_points[4] = corner[0] - norm_dir[0];
stroke_points[5] = corner[1] - norm_dir[1];
break;
case GP_STROKE_ARROWSTYLE_SQUARE:
mul_v2_fl(norm_dir, arrow_lenght * 1.5f);
if (point != NULL) {
add_v2_v2(&point->x, norm_dir);
copy_v2_v2(corner, &point->x);
}
gp_stroke_arrow_calc_points_segment(stroke_points,
corner,
inv_norm_dir_clockwise,
inv_norm_dir_counterclockwise,
arrow_lenght * 0.75f,
-1.0f);
stroke_points[4] = stroke_points[0] - norm_dir[0];
stroke_points[5] = stroke_points[1] - norm_dir[1];
stroke_points[6] = stroke_points[2] - norm_dir[0];
stroke_points[7] = stroke_points[3] - norm_dir[1];
break;
default:
break;
}
}
/* add current stroke-point to buffer (returns whether point was successfully added) */
static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure, double curtime)
{
@ -457,6 +536,32 @@ static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure
/* now the buffer has 2 points (and shouldn't be allowed to get any larger) */
gpd->runtime.sbuffer_used = 2;
/* Arrows. */
if (gpd->runtime.sbuffer_sflag & (GP_STROKE_USE_ARROW_START | GP_STROKE_USE_ARROW_END)) {
/* Store start and end point coords for arrows. */
float end[2];
copy_v2_v2(end, &pt->x);
pt = ((tGPspoint *)(gpd->runtime.sbuffer));
float start[2];
copy_v2_v2(start, &pt->x);
/* Arrow end corner. */
if (gpd->runtime.sbuffer_sflag & GP_STROKE_USE_ARROW_END) {
pt++;
float e_heading[2] = {start[0] - end[0], start[1] - end[1]};
/* Calculate points for ending arrow. */
gp_stroke_arrow_calc_points(
pt, e_heading, end, gpd->runtime.arrow_end, gpd->runtime.arrow_end_style);
}
/* Arrow start corner. */
if (gpd->runtime.sbuffer_sflag & GP_STROKE_USE_ARROW_START) {
float s_heading[2] = {end[0] - start[0], end[1] - start[1]};
/* Calculate points for starting arrow. */
gp_stroke_arrow_calc_points(
NULL, s_heading, start, gpd->runtime.arrow_start, gpd->runtime.arrow_start_style);
}
}
}
/* can keep carrying on this way :) */
@ -490,7 +595,7 @@ static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure
}
else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) {
/* get pointer to destination point */
pt = (tGPspoint *)(gpd->runtime.sbuffer);
pt = (tGPspoint *)gpd->runtime.sbuffer;
/* store settings */
copy_v2_v2(&pt->x, mval);
@ -552,6 +657,123 @@ static short gp_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure
return GP_STROKEADD_INVALID;
}
static void gp_stroke_arrow_init_point_default(bGPDspoint *pt)
{
pt->pressure = 1.0f;
pt->strength = 1.0f;
pt->time = 1.0f;
}
static void gp_stroke_arrow_init_conv_point(bGPDspoint *pt, const float point[3])
{
copy_v3_v3(&pt->x, point);
gp_stroke_arrow_init_point_default(pt);
}
static void gp_stroke_arrow_init_point(
tGPsdata *p, tGPspoint *ptc, bGPDspoint *pt, const float co[8], const int co_idx)
{
/* Note: provided co_idx should be always pair number as it's [x1, y1, x2, y2, x3, y3]. */
float real_co[2] = {co[co_idx], co[co_idx + 1]};
copy_v2_v2(&ptc->x, real_co);
gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
gp_stroke_arrow_init_point_default(pt);
}
static void gp_stroke_arrow_allocate(bGPDstroke *gps, const int totpoints)
{
/* Copy appropriate settings for stroke. */
gps->totpoints = totpoints;
/* Allocate enough memory for a continuous array for storage points. */
gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
}
static void gp_arrow_create_open(tGPsdata *p,
tGPspoint *ptc,
bGPDspoint *pt,
const float corner_point[3],
const float arrow_points[8])
{
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
pt++;
gp_stroke_arrow_init_conv_point(pt, corner_point);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 2);
}
static void gp_arrow_create_segm(tGPsdata *p,
tGPspoint *ptc,
bGPDspoint *pt,
const float arrow_points[8])
{
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 2);
}
static void gp_arrow_create_closed(tGPsdata *p,
tGPspoint *ptc,
bGPDspoint *pt,
const float arrow_points[8])
{
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 2);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 4);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
}
static void gp_arrow_create_square(tGPsdata *p,
tGPspoint *ptc,
bGPDspoint *pt,
const float corner_point[3],
const float arrow_points[8])
{
gp_stroke_arrow_init_conv_point(pt, corner_point);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 4);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 6);
pt++;
gp_stroke_arrow_init_point(p, ptc, pt, arrow_points, 2);
pt++;
gp_stroke_arrow_init_conv_point(pt, corner_point);
}
static void gp_arrow_create(tGPsdata *p,
tGPspoint *ptc,
bGPDspoint *pt,
bGPDstroke *arrow_stroke,
const float arrow_points[8],
const int style)
{
float corner_conv[3];
copy_v3_v3(corner_conv, &pt->x);
switch (style) {
case GP_STROKE_ARROWSTYLE_SEGMENT:
gp_arrow_create_segm(p, ptc, pt, arrow_points);
break;
case GP_STROKE_ARROWSTYLE_CLOSED:
gp_arrow_create_closed(p, ptc, pt, arrow_points);
break;
case GP_STROKE_ARROWSTYLE_OPEN:
gp_arrow_create_open(p, ptc, pt, corner_conv, arrow_points);
break;
case GP_STROKE_ARROWSTYLE_SQUARE:
gp_arrow_create_square(p, ptc, pt, corner_conv, arrow_points);
break;
default:
break;
}
/* Link stroke to frame. */
BLI_addtail(&p->gpf->strokes, arrow_stroke);
}
/* make a new stroke from the buffer data */
static void gp_stroke_newfrombuffer(tGPsdata *p)
{
@ -637,17 +859,61 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
}
if (totelem == 2) {
/* last point if applicable */
ptc = ((tGPspoint *)gpd->runtime.sbuffer) + (gpd->runtime.sbuffer_used - 1);
bGPdata_Runtime runtime = gpd->runtime;
/* convert screen-coordinates to appropriate coordinates (and store them) */
/* Last point if applicable. */
ptc = ((tGPspoint *)runtime.sbuffer) + (runtime.sbuffer_used - 1);
/* Convert screen-coordinates to appropriate coordinates (and store them). */
gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
/* copy pressure and time */
/* Copy pressure and time. */
pt->pressure = ptc->pressure;
pt->strength = ptc->strength;
CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
pt->time = ptc->time;
/** Create arrow strokes. **/
/* End arrow stroke. */
if ((runtime.sbuffer_sflag & GP_STROKE_USE_ARROW_END) &&
(runtime.arrow_end_style != GP_STROKE_ARROWSTYLE_NONE)) {
int totarrowpoints = runtime.arrow_end_style;
/* Setting up arrow stroke. */
bGPDstroke *e_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false);
gp_stroke_arrow_allocate(e_arrow_gps, totarrowpoints);
/* Set pointer to first non-initialized point. */
pt = e_arrow_gps->points + (e_arrow_gps->totpoints - totarrowpoints);
/* End point. */
ptc = ((tGPspoint *)runtime.sbuffer) + (runtime.sbuffer_used - 1);
gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
gp_stroke_arrow_init_point_default(pt);
/* Fill and convert arrow points to create arrow shape. */
gp_arrow_create(p, ptc, pt, e_arrow_gps, runtime.arrow_end, runtime.arrow_end_style);
}
/* Start arrow stroke. */
if ((runtime.sbuffer_sflag & GP_STROKE_USE_ARROW_START) &&
(runtime.arrow_start_style != GP_STROKE_ARROWSTYLE_NONE)) {
int totarrowpoints = runtime.arrow_start_style;
/* Setting up arrow stroke. */
bGPDstroke *s_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false);
gp_stroke_arrow_allocate(s_arrow_gps, totarrowpoints);
/* Set pointer to first non-initialized point. */
pt = s_arrow_gps->points + (s_arrow_gps->totpoints - totarrowpoints);
/* Start point. */
ptc = runtime.sbuffer;
gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL);
gp_stroke_arrow_init_point_default(pt);
/* Fill and convert arrow points to create arrow shape. */
gp_arrow_create(p, ptc, pt, s_arrow_gps, runtime.arrow_start, runtime.arrow_start_style);
}
}
}
else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) {
@ -1902,6 +2168,16 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event
if (p->paintmode == GP_PAINTMODE_ERASER) {
gpencil_draw_toggle_eraser_cursor(C, p, true);
}
else if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) {
if (RNA_enum_get(op->ptr, "arrowstyle_start") != GP_STROKE_ARROWSTYLE_NONE) {
p->gpd->runtime.sbuffer_sflag |= GP_STROKE_USE_ARROW_START;
p->gpd->runtime.arrow_start_style = RNA_enum_get(op->ptr, "arrowstyle_start");
}
if (RNA_enum_get(op->ptr, "arrowstyle_end") != GP_STROKE_ARROWSTYLE_NONE) {
p->gpd->runtime.sbuffer_sflag |= GP_STROKE_USE_ARROW_END;
p->gpd->runtime.arrow_end_style = RNA_enum_get(op->ptr, "arrowstyle_end");
}
}
/* set cursor
* NOTE: This may change later (i.e. intentionally via brush toggle,
* or unintentionally if the user scrolls outside the area)...
@ -2370,6 +2646,19 @@ static const EnumPropertyItem prop_gpencil_drawmodes[] = {
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem arrow_types[] = {
{GP_STROKE_ARROWSTYLE_NONE, "NONE", 0, "None", "Don't use any arrow/style in corner"},
{GP_STROKE_ARROWSTYLE_CLOSED, "ARROW", 0, "Arrow", "Use closed arrow style"},
{GP_STROKE_ARROWSTYLE_OPEN, "ARROW_OPEN", 0, "Open Arrow", "Use open arrow style"},
{GP_STROKE_ARROWSTYLE_SEGMENT,
"ARROW_OPEN_INVERTED",
0,
"Segment",
"Use perpendicular segment style"},
{GP_STROKE_ARROWSTYLE_SQUARE, "DIAMOND", 0, "Square", "Use square style"},
{0, NULL, 0, NULL, NULL},
};
void GPENCIL_OT_annotate(wmOperatorType *ot)
{
PropertyRNA *prop;
@ -2393,6 +2682,12 @@ void GPENCIL_OT_annotate(wmOperatorType *ot)
ot->prop = RNA_def_enum(
ot->srna, "mode", prop_gpencil_drawmodes, 0, "Mode", "Way to interpret mouse movements");
/* properties */
prop = RNA_def_enum(
ot->srna, "arrowstyle_start", arrow_types, 0, "Start Arrow Style", "Stroke start style");
prop = RNA_def_enum(
ot->srna, "arrowstyle_end", arrow_types, 0, "End Arrow Style", "Stroke end style");
prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);

View File

@ -264,6 +264,10 @@ typedef enum eGPDstroke_Flag {
/* Flag used to indicate that stroke is used for fill close and must use
* fill color for stroke and no fill area */
GP_STROKE_NOFILL = (1 << 8),
/* only for use with stroke-buffer (while drawing arrows) */
GP_STROKE_USE_ARROW_START = (1 << 12),
/* only for use with stroke-buffer (while drawing arrows) */
GP_STROKE_USE_ARROW_END = (1 << 13),
/* Tag for update geometry */
GP_STROKE_TAG = (1 << 14),
/* only for use with stroke-buffer (while drawing eraser) */
@ -280,6 +284,17 @@ typedef enum eGPDstroke_Caps {
GP_STROKE_CAP_MAX,
} GPDstroke_Caps;
/* Arrows ----------------------- */
/* bGPDataRuntime.arrowstyle */
typedef enum eGPDstroke_Arrowstyle {
GP_STROKE_ARROWSTYLE_NONE = 0,
GP_STROKE_ARROWSTYLE_SEGMENT = 2,
GP_STROKE_ARROWSTYLE_OPEN = 3,
GP_STROKE_ARROWSTYLE_CLOSED = 4,
GP_STROKE_ARROWSTYLE_SQUARE = 6,
} eGPDstroke_Arrowstyle;
/* ***************************************** */
/* GP Frame */
@ -511,6 +526,13 @@ typedef struct bGPdata_Runtime {
/** Vertex Color applied to Fill (while drawing). */
float vert_color_fill[4];
/** Arrow points for stroke corners **/
float arrow_start[8];
float arrow_end[8];
/* Arrow style for each corner */
int arrow_start_style;
int arrow_end_style;
/** Number of control-points for stroke. */
int tot_cp_points;
char _pad2[4];