Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntonioya <blendergit@gmail.com>2019-01-11 21:15:23 +0300
committerAntonioya <blendergit@gmail.com>2019-01-11 21:21:56 +0300
commitbb9c9d0eaaab836b8f20ab7b2228795f607b823a (patch)
tree8fd6ebad4084c66e6d1a49849ed6b9dbbc126d22 /source/blender/editors
parent6dbfd7f6d6bc9bea9556861eba682a3126b5ed40 (diff)
GP: New Cutter, Constraints and Segment selection
This commit groups a set of new tools that were tested in grease pencil object branch before moving to master. We decide to do all the development in a separated branch because it could break master during days or weeks before the new tools were ready to deploy. The commit includes: - New Cutter tool to trim strokes and help cleaning up drawings. - New set of constraints and guides to draw different types of shapes. All the credits for this development goes to Charlie Jolly (@charlie), thanks for your help! - Segment selection mode to select strokes between intersections. - New operator to change strokes cap mode. - New option to display only keyframed frames. This option is very important when fill strokes with color. - Multiple small fixes and tweaks. Thanks to @pepeland and @mendio for their ideas, tests, reviews and support. Note: Still pending the final icons for Cutter in Toolbar and Segment Selection in Topbar. @billreynish could help us here?
Diffstat (limited to 'source/blender/editors')
-rw-r--r--source/blender/editors/gpencil/annotate_paint.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_edit.c364
-rw-r--r--source/blender/editors/gpencil/gpencil_intern.h17
-rw-r--r--source/blender/editors/gpencil/gpencil_merge.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_ops.c6
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c545
-rw-r--r--source/blender/editors/gpencil/gpencil_primitive.c61
-rw-r--r--source/blender/editors/gpencil/gpencil_select.c46
-rw-r--r--source/blender/editors/gpencil/gpencil_utils.c404
-rw-r--r--source/blender/editors/include/ED_gpencil.h13
10 files changed, 1351 insertions, 109 deletions
diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c
index c6f88287e3e..7991c317468 100644
--- a/source/blender/editors/gpencil/annotate_paint.c
+++ b/source/blender/editors/gpencil/annotate_paint.c
@@ -907,7 +907,7 @@ static void gp_stroke_eraser_dostroke(
/* Second Pass: Remove any points that are tagged */
if (do_cull) {
- gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false);
+ gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0);
}
}
}
diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c
index a6ebc81c178..45653ac69df 100644
--- a/source/blender/editors/gpencil/gpencil_edit.c
+++ b/source/blender/editors/gpencil/gpencil_edit.c
@@ -40,6 +40,7 @@
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
+#include "BLI_lasso_2d.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_string_utils.h"
@@ -86,6 +87,7 @@
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_view3d.h"
+#include "ED_select_utils.h"
#include "ED_space_api.h"
#include "DEG_depsgraph.h"
@@ -260,7 +262,7 @@ void GPENCIL_OT_selectmode_toggle(wmOperatorType *ot)
ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
/* properties */
- prop = RNA_def_int(ot->srna, "mode", 0, 0, 1, "Select mode", "Select mode", 0, 1);
+ prop = RNA_def_int(ot->srna, "mode", 0, 0, 2, "Select mode", "Select mode", 0, 2);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
@@ -1829,9 +1831,10 @@ typedef struct tGPDeleteIsland {
* - Once we start having larger islands than that, the number required
* becomes much less
* 2) Each island gets converted to a new stroke
+ * If the number of points is <= limit, the stroke is deleted
*/
void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke,
- int tag_flags, bool select)
+ int tag_flags, bool select, int limit)
{
tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
bool in_island = false;
@@ -1929,12 +1932,17 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke
}
}
- /* Add new stroke to the frame */
- if (next_stroke) {
- BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
+ /* Add new stroke to the frame or delete if below limit */
+ if ((limit > 0) && (new_stroke->totpoints <= limit)) {
+ BKE_gpencil_free_stroke(new_stroke);
}
else {
- BLI_addtail(&gpf->strokes, new_stroke);
+ if (next_stroke) {
+ BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
+ }
+ else {
+ BLI_addtail(&gpf->strokes, new_stroke);
+ }
}
}
}
@@ -1995,7 +2003,7 @@ static int gp_delete_selected_points(bContext *C)
gps->flag &= ~GP_STROKE_SELECT;
/* delete unwanted points by splitting stroke into several smaller ones */
- gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false);
+ gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0);
changed = true;
}
@@ -2507,6 +2515,102 @@ void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot)
ot->prop = RNA_def_enum(ot->srna, "type", cyclic_type, GP_STROKE_CYCLIC_TOGGLE, "Type", "");
}
+/* ******************* Flat Stroke Caps ************************** */
+
+enum {
+ GP_STROKE_CAPS_TOGGLE_BOTH = 0,
+ GP_STROKE_CAPS_TOGGLE_START = 1,
+ GP_STROKE_CAPS_TOGGLE_END = 2,
+ GP_STROKE_CAPS_TOGGLE_DEFAULT = 3
+};
+
+static int gp_stroke_caps_set_exec(bContext *C, wmOperator *op)
+{
+ bGPdata *gpd = ED_gpencil_data_get_active(C);
+ Object *ob = CTX_data_active_object(C);
+
+ const int type = RNA_enum_get(op->ptr, "type");
+
+ /* sanity checks */
+ if (ELEM(NULL, gpd))
+ return OPERATOR_CANCELLED;
+
+ /* loop all selected strokes */
+ CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+ {
+ if (gpl->actframe == NULL)
+ continue;
+
+ for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) {
+ MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1);
+
+ /* skip strokes that are not selected or invalid for current view */
+ if (((gps->flag & GP_STROKE_SELECT) == 0) || ED_gpencil_stroke_can_use(C, gps) == false)
+ continue;
+ /* skip hidden or locked colors */
+ if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || (gp_style->flag & GP_STYLE_COLOR_LOCKED))
+ continue;
+
+ if ((type == GP_STROKE_CAPS_TOGGLE_BOTH) ||
+ (type == GP_STROKE_CAPS_TOGGLE_START))
+ {
+ ++gps->caps[0];
+ if (gps->caps[0] >= GP_STROKE_CAP_MAX) {
+ gps->caps[0] = GP_STROKE_CAP_ROUND;
+ }
+ }
+ if ((type == GP_STROKE_CAPS_TOGGLE_BOTH) ||
+ (type == GP_STROKE_CAPS_TOGGLE_END))
+ {
+ ++gps->caps[1];
+ if (gps->caps[1] >= GP_STROKE_CAP_MAX) {
+ gps->caps[1] = GP_STROKE_CAP_ROUND;
+ }
+ }
+ if (type == GP_STROKE_CAPS_TOGGLE_DEFAULT) {
+ gps->caps[0] = GP_STROKE_CAP_ROUND;
+ gps->caps[1] = GP_STROKE_CAP_ROUND;
+ }
+ }
+ }
+ CTX_DATA_END;
+
+ /* notifiers */
+ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+/**
+ * Change Stroke caps mode Rounded or Flat
+ */
+void GPENCIL_OT_stroke_caps_set(wmOperatorType *ot)
+{
+ static const EnumPropertyItem toggle_type[] = {
+ {GP_STROKE_CAPS_TOGGLE_BOTH, "TOGGLE", 0, "Both", ""},
+ {GP_STROKE_CAPS_TOGGLE_START, "START", 0, "Start", ""},
+ {GP_STROKE_CAPS_TOGGLE_END, "END", 0, "End", ""},
+ {GP_STROKE_CAPS_TOGGLE_DEFAULT, "TOGGLE", 0, "Default", "Set as default rounded"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ /* identifiers */
+ ot->name = "Set Caps Mode";
+ ot->idname = "GPENCIL_OT_stroke_caps_set";
+ ot->description = "Change Stroke caps mode (rounded or flat)";
+
+ /* api callbacks */
+ ot->exec = gp_stroke_caps_set_exec;
+ ot->poll = gp_active_layer_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ ot->prop = RNA_def_enum(ot->srna, "type", toggle_type, GP_STROKE_CAPS_TOGGLE_BOTH, "Type", "");
+}
+
/* ******************* Stroke join ************************** */
/* Helper: flip stroke */
@@ -3472,10 +3576,10 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op)
}
/* delete selected points from destination stroke */
- gp_stroke_delete_tagged_points(gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false);
+ gp_stroke_delete_tagged_points(gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0);
/* delete selected points from origin stroke */
- gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false);
+ gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0);
}
/* selected strokes mode */
else if (mode == GP_SEPARATE_STROKE) {
@@ -3609,10 +3713,10 @@ static int gp_stroke_split_exec(bContext *C, wmOperator *UNUSED(op))
}
/* delete selected points from destination stroke */
- gp_stroke_delete_tagged_points(gpf, gps_dst, NULL, GP_SPOINT_SELECT, true);
+ gp_stroke_delete_tagged_points(gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0);
/* delete selected points from origin stroke */
- gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false);
+ gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0);
}
}
/* select again tagged points */
@@ -3702,3 +3806,241 @@ void GPENCIL_OT_stroke_smooth(wmOperatorType *ot)
RNA_def_boolean(ot->srna, "smooth_strength", false, "Strength", "");
RNA_def_boolean(ot->srna, "smooth_uv", false, "UV", "");
}
+
+/* smart stroke cutter for trimming stroke ends */
+struct GP_SelectLassoUserData {
+ rcti rect;
+ const int(*mcords)[2];
+ int mcords_len;
+};
+
+static bool gpencil_test_lasso(
+ bGPDstroke *gps, bGPDspoint *pt,
+ const GP_SpaceConversion *gsc, const float diff_mat[4][4],
+ void *user_data)
+{
+ const struct GP_SelectLassoUserData *data = user_data;
+ bGPDspoint pt2;
+ int x0, y0;
+ gp_point_to_parent_space(pt, diff_mat, &pt2);
+ gp_point_to_xy(gsc, gps, &pt2, &x0, &y0);
+ /* test if in lasso */
+ return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) &&
+ BLI_rcti_isect_pt(&data->rect, x0, y0) &&
+ BLI_lasso_is_point_inside(data->mcords, data->mcords_len, x0, y0, INT_MAX));
+}
+
+typedef bool(*GPencilTestFn)(
+ bGPDstroke *gps, bGPDspoint *pt,
+ const GP_SpaceConversion *gsc, const float diff_mat[4][4], void *user_data);
+
+static void gpencil_cutter_dissolve(bGPDlayer *hit_layer, bGPDstroke *hit_stroke)
+{
+ bGPDspoint *pt = NULL;
+ bGPDspoint *pt1 = NULL;
+ int i;
+
+ bGPDstroke *gpsn = hit_stroke->next;
+
+ int totselect = 0;
+ for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) {
+ if (pt->flag & GP_SPOINT_SELECT) {
+ totselect++;
+ }
+ }
+
+ /* if all points selected delete or only 2 points and 1 selected */
+ if ((totselect == 1) && (hit_stroke->totpoints == 2) ||
+ (hit_stroke->totpoints == totselect))
+ {
+ BLI_remlink(&hit_layer->actframe->strokes, hit_stroke);
+ BKE_gpencil_free_stroke(hit_stroke);
+ hit_stroke = NULL;
+ }
+
+ /* if very small distance delete */
+ if ((hit_stroke) && (hit_stroke->totpoints == 2)) {
+ pt = &hit_stroke->points[0];
+ pt1 = &hit_stroke->points[1];
+ if (len_v3v3(&pt->x, &pt1->x) < 0.001f) {
+ BLI_remlink(&hit_layer->actframe->strokes, hit_stroke);
+ BKE_gpencil_free_stroke(hit_stroke);
+ hit_stroke = NULL;
+ }
+ }
+
+ if (hit_stroke) {
+ /* tag and dissolve (untag new points) */
+ for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) {
+ if (pt->flag & GP_SPOINT_SELECT) {
+ pt->flag &= ~GP_SPOINT_SELECT;
+ pt->flag |= GP_SPOINT_TAG;
+ }
+ else if (pt->flag & GP_SPOINT_TAG) {
+ pt->flag &= ~GP_SPOINT_TAG;
+ }
+ }
+ gp_stroke_delete_tagged_points(
+ hit_layer->actframe, hit_stroke, gpsn, GP_SPOINT_TAG, false, 1);
+ }
+}
+
+static int gpencil_cutter_lasso_select(
+ bContext *C, wmOperator *op,
+ GPencilTestFn is_inside_fn, void *user_data)
+{
+ bGPdata *gpd = ED_gpencil_data_get_active(C);
+ ScrArea *sa = CTX_wm_area(C);
+ ToolSettings *ts = CTX_data_tool_settings(C);
+ const float scale = ts->gp_sculpt.isect_threshold;
+
+ bGPDspoint *pt;
+ int i;
+ GP_SpaceConversion gsc = { NULL };
+
+ bool changed = false;
+
+ /* sanity checks */
+ if (sa == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "No active area");
+ return OPERATOR_CANCELLED;
+ }
+
+ /* init space conversion stuff */
+ gp_point_conversion_init(C, &gsc);
+
+ /* deselect all strokes first */
+ CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+ {
+ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+ pt->flag &= ~GP_SPOINT_SELECT;
+ }
+
+ gps->flag &= ~GP_STROKE_SELECT;
+ }
+ CTX_DATA_END;
+
+ /* select points */
+ GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps)
+ {
+ int tot_inside = 0;
+ const int oldtot = gps->totpoints;
+ for (i = 0; i < gps->totpoints; i++) {
+ pt = &gps->points[i];
+ if ((pt->flag & GP_SPOINT_SELECT) || (pt->flag & GP_SPOINT_TAG)) {
+ continue;
+ }
+ /* convert point coords to screenspace */
+ const bool is_inside = is_inside_fn(gps, pt, &gsc, gpstroke_iter.diff_mat, user_data);
+ if (is_inside) {
+ tot_inside++;
+ changed = true;
+ pt->flag |= GP_SPOINT_SELECT;
+ gps->flag |= GP_STROKE_SELECT;
+ float r_hita[3], r_hitb[3];
+ if (gps->totpoints > 1) {
+ ED_gpencil_select_stroke_segment(
+ gpl, gps, pt, true, true, scale, r_hita, r_hitb);
+ }
+ /* avoid infinite loops */
+ if (gps->totpoints > oldtot) {
+ break;
+ }
+ }
+ }
+ /* if mark all points inside lasso set to remove all stroke */
+ if ((tot_inside == oldtot) ||
+ ((tot_inside == 1) && (oldtot == 2)))
+ {
+ for (i = 0; i < gps->totpoints; i++) {
+ pt = &gps->points[i];
+ pt->flag |= GP_SPOINT_SELECT;
+ }
+ }
+ }
+ GP_EDITABLE_STROKES_END(gpstroke_iter);
+
+ /* dissolve selected points */
+ bGPDstroke *gpsn;
+ for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+ bGPDframe *gpf = gpl->actframe;
+ if (gpf == NULL) {
+ continue;
+ }
+ for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gpsn) {
+ gpsn = gps->next;
+ if (gps->flag & GP_STROKE_SELECT) {
+ gpencil_cutter_dissolve(gpl, gps);
+ }
+ }
+ }
+
+ /* updates */
+ if (changed) {
+ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
+ WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
+ WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static bool gpencil_cutter_poll(bContext *C)
+{
+ bGPdata *gpd = ED_gpencil_data_get_active(C);
+
+ if (GPENCIL_PAINT_MODE(gpd)) {
+ if (gpd->layers.first)
+ return true;
+ }
+
+ return false;
+}
+
+static int gpencil_cutter_exec(bContext *C, wmOperator *op)
+{
+ ScrArea *sa = CTX_wm_area(C);
+ /* sanity checks */
+ if (sa == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "No active area");
+ return OPERATOR_CANCELLED;
+ }
+
+ struct GP_SelectLassoUserData data = { 0 };
+ data.mcords = WM_gesture_lasso_path_to_array(C, op, &data.mcords_len);
+
+ /* Sanity check. */
+ if (data.mcords == NULL) {
+ return OPERATOR_PASS_THROUGH;
+ }
+
+ /* Compute boundbox of lasso (for faster testing later). */
+ BLI_lasso_boundbox(&data.rect, data.mcords, data.mcords_len);
+
+ gpencil_cutter_lasso_select(C, op, gpencil_test_lasso, &data);
+
+ MEM_freeN((void *)data.mcords);
+
+ return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_stroke_cutter(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Stroke Cutter";
+ ot->description = "Select section and cut";
+ ot->idname = "GPENCIL_OT_stroke_cutter";
+
+ /* callbacks */
+ ot->invoke = WM_gesture_lasso_invoke;
+ ot->modal = WM_gesture_lasso_modal;
+ ot->exec = gpencil_cutter_exec;
+ ot->poll = gpencil_cutter_poll;
+ ot->cancel = WM_gesture_lasso_cancel;
+
+ /* flag */
+ ot->flag = OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
+
+ /* properties */
+ WM_operator_properties_gesture_lasso(ot);
+}
diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h
index e6ef70009c8..56ddf81f357 100644
--- a/source/blender/editors/gpencil/gpencil_intern.h
+++ b/source/blender/editors/gpencil/gpencil_intern.h
@@ -234,7 +234,9 @@ void gp_apply_parent(struct Depsgraph *depsgraph, struct Object *obact, bGPdata
*/
void gp_apply_parent_point(struct Depsgraph *depsgraph, struct Object *obact, bGPdata *gpd, bGPDlayer *gpl, bGPDspoint *pt);
-bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, struct Scene *scene, const float screen_co[2], float r_out[3]);
+void gp_point_3d_to_xy(const GP_SpaceConversion *gsc, const short flag, const float pt[3], float xy[2]);
+
+bool gp_point_xy_to_3d(const GP_SpaceConversion *gsc, struct Scene *scene, const float screen_co[2], float r_out[3]);
/* helper to convert 2d to 3d */
void gp_stroke_convertcoords_tpoint(
@@ -263,7 +265,7 @@ struct GHash *gp_copybuf_validate_colormap(struct bContext *C);
void gp_stroke_delete_tagged_points(
bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke,
- int tag_flags, bool select);
+ int tag_flags, bool select, int limit);
int gp_delete_selected_point_wrap(bContext *C);
void gp_subdivide_stroke(bGPDstroke *gps, const int subdivide);
@@ -291,12 +293,17 @@ void GPENCIL_OT_annotate(struct wmOperatorType *ot);
void GPENCIL_OT_draw(struct wmOperatorType *ot);
void GPENCIL_OT_fill(struct wmOperatorType *ot);
+/* Guides ----------------------- */
+
+void GPENCIL_OT_guide_rotate(struct wmOperatorType *ot);
+
/* Paint Modes for operator */
typedef enum eGPencil_PaintModes {
GP_PAINTMODE_DRAW = 0,
GP_PAINTMODE_ERASER,
GP_PAINTMODE_DRAW_STRAIGHT,
- GP_PAINTMODE_DRAW_POLY
+ GP_PAINTMODE_DRAW_POLY,
+ GP_PAINTMODE_SET_CP
} eGPencil_PaintModes;
/* maximum sizes of gp-session buffer */
@@ -384,7 +391,7 @@ enum {
GP_STROKE_LINE = 1,
GP_STROKE_CIRCLE = 2,
GP_STROKE_ARC = 3,
- GP_STROKE_CURVE = 4
+ GP_STROKE_CURVE = 4,
};
enum {
@@ -397,6 +404,7 @@ void GPENCIL_OT_stroke_change_color(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_lock_color(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_apply_thickness(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_cyclical_set(struct wmOperatorType *ot);
+void GPENCIL_OT_stroke_caps_set(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_join(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_flip(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_subdivide(struct wmOperatorType *ot);
@@ -406,6 +414,7 @@ void GPENCIL_OT_stroke_separate(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_split(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_smooth(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_merge(struct wmOperatorType *ot);
+void GPENCIL_OT_stroke_cutter(struct wmOperatorType *ot);
void GPENCIL_OT_brush_presets_create(struct wmOperatorType *ot);
diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c
index 3641308ae17..830b4a1738f 100644
--- a/source/blender/editors/gpencil/gpencil_merge.c
+++ b/source/blender/editors/gpencil/gpencil_merge.c
@@ -194,7 +194,7 @@ static void gpencil_dissolve_points(bContext *C)
for (gps = gpf->strokes.first; gps; gps = gpsn) {
gpsn = gps->next;
- gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_TAG, false);
+ gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_TAG, false, 0);
}
}
CTX_DATA_END;
diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c
index 7814aa963ba..8493244998d 100644
--- a/source/blender/editors/gpencil/gpencil_ops.c
+++ b/source/blender/editors/gpencil/gpencil_ops.c
@@ -228,6 +228,10 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_draw);
WM_operatortype_append(GPENCIL_OT_fill);
+ /* Guides ----------------------- */
+
+ WM_operatortype_append(GPENCIL_OT_guide_rotate);
+
/* Editing (Strokes) ------------ */
WM_operatortype_append(GPENCIL_OT_editmode_toggle);
@@ -301,6 +305,7 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_stroke_lock_color);
WM_operatortype_append(GPENCIL_OT_stroke_apply_thickness);
WM_operatortype_append(GPENCIL_OT_stroke_cyclical_set);
+ WM_operatortype_append(GPENCIL_OT_stroke_caps_set);
WM_operatortype_append(GPENCIL_OT_stroke_join);
WM_operatortype_append(GPENCIL_OT_stroke_flip);
WM_operatortype_append(GPENCIL_OT_stroke_subdivide);
@@ -310,6 +315,7 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_stroke_split);
WM_operatortype_append(GPENCIL_OT_stroke_smooth);
WM_operatortype_append(GPENCIL_OT_stroke_merge);
+ WM_operatortype_append(GPENCIL_OT_stroke_cutter);
WM_operatortype_append(GPENCIL_OT_brush_presets_create);
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index 6543a65f67f..16e154b0289 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -18,7 +18,7 @@
* The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung
* This is a new part of Blender
*
- * Contributor(s): Joshua Leung, Antonio Vazquez
+ * Contributor(s): Joshua Leung, Antonio Vazquez, Charlie Jolly
*
* ***** END GPL LICENSE BLOCK *****
*/
@@ -119,9 +119,9 @@ typedef enum eGPencil_PaintFlags {
GP_PAINTFLAG_SELECTMASK = (1 << 3),
GP_PAINTFLAG_HARD_ERASER = (1 << 4),
GP_PAINTFLAG_STROKE_ERASER = (1 << 5),
+ GP_PAINTFLAG_REQ_VECTOR = (1 << 6),
} eGPencil_PaintFlags;
-
/* Temporary 'Stroke' Operation data
* "p" = op->customdata
*/
@@ -177,6 +177,8 @@ typedef struct tGPsdata {
float mval[2];
/** previous recorded mouse-position. */
float mvalo[2];
+ /** initial recorded mouse-position */
+ float mvali[2];
/** current stylus pressure. */
float pressure;
@@ -206,35 +208,45 @@ typedef struct tGPsdata {
void *erasercursor;
/* mat settings are only used for 3D view */
- /** current material. */
+ /** current material */
Material *material;
-
- /** current drawing brush. */
+ /** current drawing brush */
Brush *brush;
- /** default eraser brush. */
+ /** default eraser brush */
Brush *eraser;
- /** 1: line horizontal, 2: line vertical, other: not defined, second element position. */
- short straight[2];
- /** lock drawing to one axis. */
+
+ /** 1: line horizontal, 2: line vertical, other: not defined */
+ short straight;
+ /** lock drawing to one axis */
int lock_axis;
- /** the stroke is no fill mode. */
+ /** the stroke is no fill mode */
bool disable_fill;
RNG *rng;
- /** key used for invoking the operator. */
+ /** key used for invoking the operator */
short keymodifier;
- /** shift modifier flag. */
+ /** shift modifier flag */
short shift;
-
- /** size in pixels for uv calculation. */
+ /** size in pixels for uv calculation */
float totpixlen;
+ /* guide */
+ /** guide spacing */
+ float guide_spacing;
+ /** half guide spacing */
+ float half_spacing;
+ /** origin */
+ float origin[2];
+
ReportList *reports;
} tGPsdata;
/* ------ */
+#define STROKE_HORIZONTAL 1
+#define STROKE_VERTICAL 2
+
/* Macros for accessing sensitivity thresholds... */
/* minimum number of pixels mouse should move before new point created */
#define MIN_MANHATTEN_PX (U.gp_manhattendist)
@@ -330,11 +342,11 @@ static void gp_get_3d_reference(tGPsdata *p, float vec[3])
/* Stroke Editing ---------------------------- */
/* check if the current mouse position is suitable for adding a new point */
-static bool gp_stroke_filtermval(tGPsdata *p, const float mval[2], float pmval[2])
+static bool gp_stroke_filtermval(tGPsdata *p, const float mval[2], float mvalo[2])
{
Brush *brush = p->brush;
- int dx = (int)fabsf(mval[0] - pmval[0]);
- int dy = (int)fabsf(mval[1] - pmval[1]);
+ int dx = (int)fabsf(mval[0] - mvalo[0]);
+ int dy = (int)fabsf(mval[1] - mvalo[1]);
brush->gpencil_settings->flag &= ~GP_BRUSH_STABILIZE_MOUSE_TEMP;
/* if buffer is empty, just let this go through (i.e. so that dots will work) */
@@ -422,7 +434,7 @@ static void gp_stroke_convertcoords(tGPsdata *p, const float mval[2], float out[
* - nothing more needs to be done here, since view_autodist_simple() has already done it
*/
- /* verify valid zdepth, if it's wrong, the default darwing mode is used
+ /* verify valid zdepth, if it's wrong, the default drawing mode is used
* and the function doesn't return now */
if ((depth == NULL) || (*depth <= 1.0f)) {
return;
@@ -1607,7 +1619,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p,
gp_stroke_soft_refine(gps, cull_thresh);
}
- gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false);
+ gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0);
}
gp_update_cache(p->gpd);
}
@@ -2019,7 +2031,7 @@ static void gp_session_cleanup(tGPsdata *p)
/* free stroke buffer */
if (gpd->runtime.sbuffer) {
/* printf("\t\tGP - free sbuffer\n"); */
- MEM_freeN(gpd->runtime.sbuffer);
+ MEM_SAFE_FREE(gpd->runtime.sbuffer);
gpd->runtime.sbuffer = NULL;
}
@@ -2034,10 +2046,10 @@ static void gp_session_free(tGPsdata *p)
if (p->rng != NULL) {
BLI_rng_free(p->rng);
}
+
MEM_freeN(p);
}
-
/* init new stroke */
static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Depsgraph *depsgraph)
{
@@ -2472,8 +2484,19 @@ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p)
ED_workspace_status_text(C, IFACE_("Grease Pencil Line Session: Hold and drag LMB to draw | "
"ESC/Enter to end (or click outside this area)"));
break;
+ case GP_PAINTMODE_SET_CP:
+ ED_workspace_status_text(C, IFACE_("Grease Pencil Guides: LMB click and release to place reference point | "
+ "Esc/RMB to cancel"));
+ break;
case GP_PAINTMODE_DRAW:
- ED_workspace_status_text(C, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw"));
+ GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
+ if (guide->use_guide) {
+ ED_workspace_status_text(C, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw | "
+ "M key to flip guide | O key to move reference point"));
+ }
+ else {
+ ED_workspace_status_text(C, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw"));
+ }
break;
case GP_PAINTMODE_DRAW_POLY:
ED_workspace_status_text(C, IFACE_("Grease Pencil Poly Session: LMB click to place next stroke vertex | "
@@ -2510,8 +2533,7 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra
gp_stroke_doeraser(p);
/* store used values */
- p->mvalo[0] = p->mval[0];
- p->mvalo[1] = p->mval[1];
+ copy_v2_v2(p->mvalo, p->mval);
p->opressure = p->pressure;
}
/* only add current point to buffer if mouse moved (even though we got an event, it might be just noise) */
@@ -2560,8 +2582,7 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra
}
/* store used values */
- p->mvalo[0] = p->mval[0];
- p->mvalo[1] = p->mval[1];
+ copy_v2_v2(p->mvalo, p->mval);
p->opressure = p->pressure;
p->ocurtime = p->curtime;
@@ -2580,45 +2601,99 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra
}
}
+/* Helper to rotate point around origin */
+static void gp_rotate_v2_v2v2fl(float v[2], const float p[2], const float origin[2], const float angle)
+{
+ float pt[2];
+ float r[2];
+ sub_v2_v2v2(pt, p, origin);
+ rotate_v2_v2fl(r, pt, angle);
+ add_v2_v2v2(v, r, origin);
+}
+
+/* Helper to snap value to grid */
+static float gp_snap_to_grid_fl(float v, const float offset, const float spacing)
+{
+ if (spacing > 0.0f)
+ return roundf(v / spacing) * spacing + fmodf(offset, spacing);
+ else
+ return v;
+}
+
+static void gp_snap_to_grid_v2(float v[2], const float offset[2], const float spacing)
+{
+ v[0] = gp_snap_to_grid_fl(v[0], offset[0], spacing);
+ v[1] = gp_snap_to_grid_fl(v[1], offset[1], spacing);
+}
+
+/* get reference point - screen coords to buffer coords */
+static void gp_origin_set(wmOperator *op, const int mval[2])
+{
+ tGPsdata *p = op->customdata;
+ GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
+ float origin[2];
+ float point[3];
+ copy_v2fl_v2i(origin, mval);
+ gp_stroke_convertcoords(p, origin, point, NULL);
+ if (guide->reference_point == GP_GUIDE_REF_CUSTOM) {
+ copy_v3_v3(guide->location, point);
+ }
+ else if (guide->reference_point == GP_GUIDE_REF_CURSOR) {
+ copy_v3_v3(p->scene->cursor.location, point);
+ }
+}
+
+/* get reference point - buffer coords to screen coords */
+static void gp_origin_get(tGPsdata *p, float origin[2])
+{
+ GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
+ float location[3];
+ if (guide->reference_point == GP_GUIDE_REF_CUSTOM) {
+ copy_v3_v3(location, guide->location);
+ }
+ else if (guide->reference_point == GP_GUIDE_REF_OBJECT &&
+ guide->reference_object != NULL) {
+ copy_v3_v3(location, guide->reference_object->loc);
+ }
+ else {
+ copy_v3_v3(location, p->scene->cursor.location);
+ }
+ GP_SpaceConversion *gsc = &p->gsc;
+ gp_point_3d_to_xy(gsc, p->gpd->runtime.sbuffer_sflag, location, origin);
+}
+
/* handle draw event */
static void gpencil_draw_apply_event(bContext *C, wmOperator *op, const wmEvent *event, Depsgraph *depsgraph, float x, float y)
{
tGPsdata *p = op->customdata;
+ GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
PointerRNA itemptr;
float mousef[2];
int tablet = 0;
/* convert from window-space to area-space mouse coordinates
* add any x,y override position for fake events
- * NOTE: float to ints conversions, +1 factor is probably used to ensure a bit more accurate rounding...
*/
- p->mval[0] = event->mval[0] + 1.0f - x;
- p->mval[1] = event->mval[1] + 1.0f - y;
+ p->mval[0] = (float)event->mval[0] - x;
+ p->mval[1] = (float)event->mval[1] - y;
p->shift = event->shift;
- /* verify key status for straight lines */
- if ((event->alt > 0) && (RNA_boolean_get(op->ptr, "disable_straight") == false)) {
- if (p->straight[0] == 0) {
- int dx = (int)fabsf(p->mval[0] - p->mvalo[0]);
- int dy = (int)fabsf(p->mval[1] - p->mvalo[1]);
+ /* verify direction for straight lines */
+ if ((guide->use_guide) || ((event->alt > 0) && (RNA_boolean_get(op->ptr, "disable_straight") == false))) {
+ if (p->straight == 0) {
+ int dx = (int)fabsf(p->mval[0] - p->mvali[0]);
+ int dy = (int)fabsf(p->mval[1] - p->mvali[1]);
if ((dx > 0) || (dy > 0)) {
- /* check mouse direction to replace the other coordinate with previous values */
- if (dx >= dy) {
- /* horizontal */
- p->straight[0] = 1;
- p->straight[1] = (short)p->mval[1]; /* save y */
+ /* store mouse direction */
+ if (dx > dy) {
+ p->straight = STROKE_HORIZONTAL;
}
- else {
- /* vertical */
- p->straight[0] = 2;
- p->straight[1] = (short)p->mval[0]; /* save x */
+ else if (dx < dy) {
+ p->straight = STROKE_VERTICAL;
}
}
}
}
- else {
- p->straight[0] = 0;
- }
p->curtime = PIL_check_seconds_timer();
@@ -2666,29 +2741,164 @@ static void gpencil_draw_apply_event(bContext *C, wmOperator *op, const wmEvent
if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
p->flags &= ~GP_PAINTFLAG_FIRSTRUN;
- p->mvalo[0] = p->mval[0];
- p->mvalo[1] = p->mval[1];
+ /* set values */
+ copy_v2_v2(p->mvalo, p->mval);
p->opressure = p->pressure;
p->inittime = p->ocurtime = p->curtime;
- p->straight[0] = 0;
- p->straight[1] = 0;
+ p->straight = 0;
+
+ /* save initial mouse */
+ copy_v2_v2(p->mvali, p->mval);
+
+ /* calculate once and store snapping distance and origin */
+ RegionView3D * rv3d = p->ar->regiondata;
+ float scale = 1.0f;
+ if (rv3d->is_persp) {
+ float vec[3];
+ gp_get_3d_reference(p, vec);
+ mul_m4_v3(rv3d->persmat, vec);
+ scale = vec[2] * rv3d->pixsize;
+ }
+ else {
+ scale = rv3d->pixsize;
+ }
+ p->guide_spacing = guide->spacing / scale;
+ p->half_spacing = p->guide_spacing * 0.5f;
+ gp_origin_get(p, p->origin);
/* special exception here for too high pressure values on first touch in
* windows for some tablets, then we just skip first touch...
*/
- if (tablet && (p->pressure >= 0.99f))
+ if (tablet && (p->pressure >= 0.99f)) {
return;
+ }
+
+ /* special exception for grid snapping
+ * it requires direction which needs at least two points
+ */
+ if (!ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP) &&
+ guide->use_guide &&
+ guide->use_snapping &&
+ (guide->type == GP_GUIDE_GRID)) {
+ p->flags |= GP_PAINTFLAG_REQ_VECTOR;
+ }
}
- /* check if alt key is pressed and limit to straight lines */
- if ((p->paintmode != GP_PAINTMODE_ERASER) && (p->straight[0] != 0)) {
- if (p->straight[0] == 1) {
- /* horizontal */
- p->mval[1] = p->straight[1]; /* replace y */
+ /* wait for vector then add initial point */
+ if (p->flags & GP_PAINTFLAG_REQ_VECTOR) {
+ if (p->straight == 0) {
+ return;
+ }
+
+ p->flags &= ~GP_PAINTFLAG_REQ_VECTOR;
+
+ /* create fake events */
+ float tmp[2];
+ float pt[2];
+ copy_v2_v2(tmp, p->mval);
+ sub_v2_v2v2(pt, p->mval, p->mvali);
+ gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), pt[0], pt[1]);
+ if (len_v2v2(p->mval, p->mvalo)) {
+ sub_v2_v2v2(pt, p->mval, p->mvalo);
+ gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), pt[0], pt[1]);
+ }
+ copy_v2_v2(p->mval, tmp);
+ }
+
+ /* check if stroke is straight or guided */
+ if ((p->paintmode != GP_PAINTMODE_ERASER)
+ && ((p->straight) || (guide->use_guide))) {
+ /* guided stroke */
+ if (guide->use_guide) {
+ switch (guide->type) {
+ default:
+ case GP_GUIDE_CIRCULAR:
+ {
+ float distance;
+ distance = len_v2v2(p->mvali, p->origin);
+
+ if (guide->use_snapping && (guide->spacing > 0.0f)) {
+ distance = gp_snap_to_grid_fl(distance, 0.0f, p->guide_spacing);
+ }
+
+ dist_ensure_v2_v2fl(p->mval, p->origin, distance);
+ }
+ break;
+ case GP_GUIDE_RADIAL:
+ {
+ if (guide->use_snapping &&
+ (guide->angle_snap > 0.0f)) {
+ float point[2];
+ float xy[2];
+ float angle;
+ float half_angle = guide->angle_snap * 0.5f;
+ sub_v2_v2v2(xy, p->mvali, p->origin);
+ angle = atan2f(xy[1], xy[0]);
+ angle += (M_PI * 2.0f);
+ angle = fmodf(angle + half_angle, guide->angle_snap);
+ angle -= half_angle;
+ gp_rotate_v2_v2v2fl(point, p->mvali, p->origin, -angle);
+ closest_to_line_v2(p->mval, p->mval, point, p->origin);
+ }
+ else {
+ closest_to_line_v2(p->mval, p->mval, p->mvali, p->origin);
+ }
+ }
+ break;
+ case GP_GUIDE_PARALLEL:
+ {
+ float point[2];
+ float unit[2];
+ copy_v2_v2(unit, p->mvali);
+ unit[0] += 1.0f; /* start from horizontal */
+ gp_rotate_v2_v2v2fl(point, unit, p->mvali, guide->angle);
+ closest_to_line_v2(p->mval, p->mval, p->mvali, point);
+
+ if (guide->use_snapping &&
+ (guide->spacing > 0.0f)) {
+ gp_rotate_v2_v2v2fl(p->mval, p->mval, p->origin, -guide->angle);
+ p->mval[1] = gp_snap_to_grid_fl(p->mval[1] - p->half_spacing, p->origin[1], p->guide_spacing);
+ gp_rotate_v2_v2v2fl(p->mval, p->mval, p->origin, guide->angle);
+ }
+
+ }
+ break;
+ case GP_GUIDE_GRID:
+ {
+ if (guide->use_snapping &&
+ (guide->spacing > 0.0f)) {
+
+ float point[2];
+ float unit[2];
+ float angle;
+ copy_v2_v2(unit, p->mvali);
+ unit[0] += 1.0f; /* start from horizontal */
+ angle = (p->straight == STROKE_VERTICAL) ? M_PI_2 : 0.0f;
+ gp_rotate_v2_v2v2fl(point, unit, p->mvali, angle);
+ closest_to_line_v2(p->mval, p->mval, p->mvali, point);
+
+ if (p->straight == STROKE_HORIZONTAL) {
+ p->mval[1] = gp_snap_to_grid_fl(p->mval[1] - p->half_spacing, p->origin[1], p->guide_spacing);
+ }
+ else {
+ p->mval[0] = gp_snap_to_grid_fl(p->mval[0] - p->half_spacing, p->origin[0], p->guide_spacing);
+ }
+ }
+ else if (p->straight == STROKE_HORIZONTAL) {
+ p->mval[1] = p->mvali[1]; /* replace y */
+ }
+ else {
+ p->mval[0] = p->mvali[0]; /* replace x */
+ }
+ }
+ break;
+ }
+ }
+ else if (p->straight == STROKE_HORIZONTAL) {
+ p->mval[1] = p->mvali[1]; /* replace y */
}
else {
- /* vertical */
- p->mval[0] = p->straight[1]; /* replace x */
+ p->mval[0] = p->mvali[0]; /* replace x */
}
}
@@ -2742,8 +2952,8 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op)
/* get relevant data for this point from stroke */
RNA_float_get_array(&itemptr, "mouse", mousef);
- p->mval[0] = (int)mousef[0];
- p->mval[1] = (int)mousef[1];
+ p->mval[0] = mousef[0];
+ p->mval[1] = mousef[1];
p->pressure = RNA_float_get(&itemptr, "pressure");
p->curtime = (double)RNA_float_get(&itemptr, "time") + p->inittime;
@@ -2787,6 +2997,93 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op)
/* ------------------------------- */
+/* handle events for guides */
+static void gpencil_guide_event_handling(bContext *C, wmOperator *op, const wmEvent *event, tGPsdata *p)
+{
+ bool add_notifier = false;
+ GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
+
+ /* Enter or exit set center point mode */
+ if ((event->type == OKEY) && (event->val == KM_RELEASE)) {
+ if (p->paintmode == GP_PAINTMODE_DRAW && guide->reference_point != GP_GUIDE_REF_OBJECT) {
+ add_notifier = true;
+ p->paintmode = GP_PAINTMODE_SET_CP;
+ ED_gpencil_toggle_brush_cursor(C, false, NULL);
+ }
+ }
+ /* Freehand mode, turn off speed guide */
+ else if ((event->type == VKEY) && (event->val == KM_RELEASE)) {
+ guide->use_guide = false;
+ add_notifier = true;
+ }
+ /* Alternate or flip direction */
+ else if ((event->type == MKEY) && (event->val == KM_RELEASE)) {
+ if (guide->type == GP_GUIDE_CIRCULAR) {
+ add_notifier = true;
+ guide->type = GP_GUIDE_RADIAL;
+ }
+ else if (guide->type == GP_GUIDE_RADIAL) {
+ add_notifier = true;
+ guide->type = GP_GUIDE_CIRCULAR;
+ }
+ else if (guide->type == GP_GUIDE_PARALLEL) {
+ add_notifier = true;
+ guide->angle += M_PI_2;
+ guide->angle = angle_compat_rad(guide->angle, M_PI);
+ }
+ else {
+ add_notifier = false;
+ }
+ }
+ /* Line guides */
+ else if ((event->type == LKEY) && (event->val == KM_RELEASE)) {
+ add_notifier = true;
+ guide->use_guide = true;
+ if (event->ctrl) {
+ guide->angle = 0.0f;
+ guide->type = GP_GUIDE_PARALLEL;
+ }
+ else if (event->alt) {
+ guide->type = GP_GUIDE_PARALLEL;
+ guide->angle = RNA_float_get(op->ptr, "guide_last_angle");
+ }
+ else {
+ guide->type = GP_GUIDE_PARALLEL;
+ }
+ }
+ /* Point guide */
+ else if ((event->type == CKEY) && (event->val == KM_RELEASE)) {
+ add_notifier = true;
+ guide->use_guide = true;
+ if (guide->type == GP_GUIDE_CIRCULAR) {
+ guide->type = GP_GUIDE_RADIAL;
+ }
+ else if (guide->type == GP_GUIDE_RADIAL) {
+ guide->type = GP_GUIDE_CIRCULAR;
+ }
+ else {
+ guide->type = GP_GUIDE_CIRCULAR;
+ }
+ }
+ /* Change line angle */
+ else if (ELEM(event->type, JKEY, KKEY) && (event->val == KM_RELEASE)) {
+ add_notifier = true;
+ float angle = guide->angle;
+ float adjust = (float)M_PI / 180.0f;
+ if (event->alt)
+ adjust *= 45.0f;
+ else if (!event->shift)
+ adjust *= 15.0f;
+ angle += (event->type == JKEY) ? adjust : -adjust;
+ angle = angle_compat_rad(angle, M_PI);
+ guide->angle = angle;
+ }
+
+ if (add_notifier) {
+ WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS | NC_GPENCIL | NA_EDITED, NULL);
+ }
+}
+
/* start of interactive drawing part of operator */
static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
@@ -2836,7 +3133,7 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event
p->status = GP_STATUS_PAINTING;
/* handle the initial drawing - i.e. for just doing a simple dot */
- gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), 0, 0);
+ gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), 0.0f, 0.0f);
op->flag |= OP_IS_MODAL_CURSOR_REGION;
}
else {
@@ -2847,6 +3144,12 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event
/* enable paint mode */
if (p->sa->spacetype == SPACE_VIEW3D) {
+
+ /* handle speed guide events before drawing inside view3d */
+ if (!ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP)) {
+ gpencil_guide_event_handling(C, op, event, p);
+ }
+
Object *ob = CTX_data_active_object(C);
if (ob && (ob->type == OB_GPENCIL) && ((p->gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)) {
/* FIXME: use the mode switching operator, this misses notifiers, messages. */
@@ -2952,12 +3255,21 @@ static void gpencil_move_last_stroke_to_back(bContext *C)
static void gpencil_add_missing_events(bContext *C, wmOperator *op, const wmEvent *event, tGPsdata *p)
{
Brush *brush = p->brush;
- if (brush->gpencil_settings->input_samples == 0) {
+ GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
+ int input_samples = brush->gpencil_settings->input_samples;
+
+ /* ensure sampling when using circular guide */
+ if (guide->use_guide && (guide->type == GP_GUIDE_CIRCULAR)) {
+ input_samples = GP_MAX_INPUT_SAMPLES;
+ }
+
+ if (input_samples == 0) {
return;
}
+
RegionView3D *rv3d = p->ar->regiondata;
float defaultpixsize = rv3d->pixsize * 1000.0f;
- int samples = (GP_MAX_INPUT_SAMPLES - brush->gpencil_settings->input_samples + 1);
+ int samples = (GP_MAX_INPUT_SAMPLES - input_samples + 1);
float thickness = (float)brush->size;
float pt[2], a[2], b[2];
@@ -2991,8 +3303,8 @@ static void gpencil_add_missing_events(bContext *C, wmOperator *op, const wmEven
float factor = ((thickness * dot_factor) / scale) * samples;
copy_v2_v2(a, p->mvalo);
- b[0] = event->mval[0] + 1;
- b[1] = event->mval[1] + 1;
+ b[0] = (float)event->mval[0] + 1.0f;
+ b[1] = (float)event->mval[1] + 1.0f;
/* get distance in pixels */
float dist = len_v2v2(a, b);
@@ -3003,7 +3315,7 @@ static void gpencil_add_missing_events(bContext *C, wmOperator *op, const wmEven
sub_v2_v2v2(pt, b, pt);
/* create fake event */
gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C),
- (int)pt[0], (int)pt[1]);
+ pt[0], pt[1]);
}
else if (dist >= factor) {
int slices = 2 + (int)((dist - 1.0) / factor);
@@ -3013,7 +3325,7 @@ static void gpencil_add_missing_events(bContext *C, wmOperator *op, const wmEven
sub_v2_v2v2(pt, b, pt);
/* create fake event */
gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C),
- (int)pt[0], (int)pt[1]);
+ pt[0], pt[1]);
}
}
}
@@ -3023,6 +3335,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
tGPsdata *p = op->customdata;
ToolSettings *ts = CTX_data_tool_settings(C);
+ GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide;
/* default exit state - pass through to support MMB view nav, etc. */
int estate = OPERATOR_PASS_THROUGH;
@@ -3044,6 +3357,43 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
p->ar = ar;
}
+ /* special mode for editing control points */
+ if (p->paintmode == GP_PAINTMODE_SET_CP) {
+ wmWindow *win = p->win;
+ WM_cursor_modal_set(win, BC_NSEW_SCROLLCURSOR);
+ bool drawmode = false;
+
+ switch (event->type) {
+ /* cancel */
+ case ESCKEY:
+ case RIGHTMOUSE:
+ {
+ if (ELEM(event->val, KM_RELEASE)) {
+ drawmode = true;
+ }
+ }
+ break;
+ /* set */
+ case LEFTMOUSE:
+ {
+ if (ELEM(event->val, KM_RELEASE)) {
+ gp_origin_set(op, event->mval);
+ drawmode = true;
+ }
+ }
+ break;
+ }
+ if (drawmode) {
+ p->status = GP_STATUS_IDLING;
+ p->paintmode = GP_PAINTMODE_DRAW;
+ ED_gpencil_toggle_brush_cursor(C, true, NULL);
+ DEG_id_tag_update(&p->scene->id, ID_RECALC_COPY_ON_WRITE);
+ }
+ else {
+ return OPERATOR_RUNNING_MODAL;
+ }
+ }
+
/* we don't pass on key events, GP is used with key-modifiers - prevents Dkey to insert drivers */
if (ISKEYBOARD(event->type)) {
if (ELEM(event->type, LEFTARROWKEY, DOWNARROWKEY, RIGHTARROWKEY, UPARROWKEY, ZKEY)) {
@@ -3067,6 +3417,10 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
WM_operator_name_call(C, "GPENCIL_OT_blank_frame_add", WM_OP_EXEC_DEFAULT, NULL);
estate = OPERATOR_RUNNING_MODAL;
}
+ else if ((!ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP))) {
+ gpencil_guide_event_handling(C, op, event, p);
+ estate = OPERATOR_RUNNING_MODAL;
+ }
else {
estate = OPERATOR_RUNNING_MODAL;
}
@@ -3091,6 +3445,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
}
}
}
+
p->status = GP_STATUS_DONE;
estate = OPERATOR_FINISHED;
}
@@ -3251,6 +3606,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
else if (event->val == KM_RELEASE) {
p->status = GP_STATUS_IDLING;
op->flag |= OP_IS_MODAL_CURSOR_REGION;
+ ED_region_tag_redraw(p->ar);
}
}
@@ -3260,9 +3616,10 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) {
/* handle drawing event */
/* printf("\t\tGP - add point\n"); */
+
gpencil_add_missing_events(C, op, event, p);
- gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), 0, 0);
+ gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), 0.0f, 0.0f);
/* finish painting operation if anything went wrong just now */
if (p->status == GP_STATUS_ERROR) {
@@ -3326,6 +3683,13 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
/* process last operations before exiting */
switch (estate) {
case OPERATOR_FINISHED:
+ /* store stroke angle for parallel guide */
+ if ((p->straight == 0) || (guide->use_guide && (guide->type == GP_GUIDE_CIRCULAR))){
+ float xy[2];
+ sub_v2_v2v2(xy, p->mval, p->mvali);
+ float angle = atan2f(xy[1], xy[0]);
+ RNA_float_set(op->ptr, "guide_last_angle", angle);
+ }
/* one last flush before we're done */
gpencil_draw_exit(C, op);
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
@@ -3392,4 +3756,49 @@ void GPENCIL_OT_draw(wmOperatorType *ot)
prop = RNA_def_boolean(ot->srna, "disable_fill", false, "No Fill Areas", "Disable fill to use stroke as fill boundary");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+
+ /* guides */
+ prop = RNA_def_float(ot->srna, "guide_last_angle", 0.0f, -10000.0f, 10000.0f, "Angle", "Speed guide angle", -10000.0f, 10000.0f);
+ prop = RNA_def_float_vector(ot->srna, "guide_origin", 3, NULL, -10000.0f, 10000.0f, "Origin", "Speed guide origin", -10000.0f, 10000.0f);
+}
+
+/* additional OPs */
+
+static int gpencil_guide_rotate(bContext *C, wmOperator *op)
+{
+ ToolSettings *ts = CTX_data_tool_settings(C);
+ GP_Sculpt_Guide *guide = &ts->gp_sculpt.guide;
+ float angle = RNA_float_get(op->ptr, "angle");
+ bool increment = RNA_boolean_get(op->ptr, "increment");
+ if (increment) {
+ float oldangle = guide->angle;
+ oldangle += angle;
+ guide->angle = angle_compat_rad(oldangle, M_PI);
+ }
+ else {
+ guide->angle = angle_compat_rad(angle, M_PI);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_guide_rotate(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Rotate Guide Angle";
+ ot->idname = "GPENCIL_OT_guide_rotate";
+ ot->description = "Rotate guide angle";
+
+ /* api callbacks */
+ ot->exec = gpencil_guide_rotate;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ PropertyRNA *prop;
+
+ prop = RNA_def_boolean(ot->srna, "increment", true, "Increment", "Increment angle");
+ RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+ prop = RNA_def_float(ot->srna, "angle", 0.0f, -10000.0f, 10000.0f, "Angle", "Guide angle", -10000.0f, 10000.0f);
+ RNA_def_property_flag(prop, PROP_HIDDEN);
}
diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c
index 6849e876bf5..ba49afd0680 100644
--- a/source/blender/editors/gpencil/gpencil_primitive.c
+++ b/source/blender/editors/gpencil/gpencil_primitive.c
@@ -18,7 +18,7 @@
* The Original Code is Copyright (C) 2017, Blender Foundation
* This is a new part of Blender
*
- * Contributor(s): Antonio Vazquez
+ * Contributor(s): Antonio Vazquez, Charlie Jolly
*
* ***** END GPL LICENSE BLOCK *****
*
@@ -248,6 +248,15 @@ static void gp_primitive_update_cps(tGPDprimitive *tgpi)
}
}
+/* Helper to reflect point */
+static void gp_reflect_point_v2_v2v2v2(float va[2], const float p[2], const float a[2], const float b[2])
+{
+ float point[2];
+ closest_to_line_v2(point, p, a, b);
+ va[0] = point[0] - (p[0] - point[0]);
+ va[1] = point[1] - (p[1] - point[1]);
+}
+
/* Poll callback for primitive operators */
static bool gpencil_primitive_add_poll(bContext *C)
{
@@ -677,7 +686,8 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi)
/* compute screen-space coordinates for points */
tGPspoint *points2D = tgpi->points;
- switch (tgpi->type) {
+ if (tgpi->tot_edges > 1) {
+ switch (tgpi->type) {
case GP_STROKE_BOX:
gp_primitive_rectangle(tgpi, points2D);
break;
@@ -694,6 +704,7 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi)
gp_primitive_bezier(tgpi, points2D);
default:
break;
+ }
}
/* convert screen-coordinates to 3D coordinates */
@@ -1321,7 +1332,7 @@ static void gpencil_primitive_edit_event_handling(bContext *C, wmOperator *op, w
{
if ((event->val == KM_PRESS) &&
(tgpi->curve) &&
- (tgpi->orign_type == GP_STROKE_ARC))
+ (ELEM(tgpi->orign_type, GP_STROKE_ARC) ))
{
tgpi->flip ^= 1;
gp_primitive_update_cps(tgpi);
@@ -1432,22 +1443,22 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e
if (tgpi->flag == IN_MOVE) {
switch (event->type) {
- case MOUSEMOVE:
- gpencil_primitive_move(tgpi, false);
- gpencil_primitive_update(C, op, tgpi);
- break;
- case ESCKEY:
- case LEFTMOUSE:
- zero_v2(tgpi->move);
+ case MOUSEMOVE:
+ gpencil_primitive_move(tgpi, false);
+ gpencil_primitive_update(C, op, tgpi);
+ break;
+ case ESCKEY:
+ case LEFTMOUSE:
+ zero_v2(tgpi->move);
+ tgpi->flag = IN_CURVE_EDIT;
+ break;
+ case RIGHTMOUSE:
+ if (event->val == KM_RELEASE) {
tgpi->flag = IN_CURVE_EDIT;
- break;
- case RIGHTMOUSE:
- if (event->val == KM_RELEASE) {
- tgpi->flag = IN_CURVE_EDIT;
- gpencil_primitive_move(tgpi, true);
- gpencil_primitive_update(C, op, tgpi);
- }
- break;
+ gpencil_primitive_move(tgpi, true);
+ gpencil_primitive_update(C, op, tgpi);
+ }
+ break;
}
copy_v2_v2(tgpi->mvalo, tgpi->mval);
return OPERATOR_RUNNING_MODAL;
@@ -1543,7 +1554,19 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e
/* done! */
return OPERATOR_FINISHED;
}
- case RIGHTMOUSE: /* cancel */
+ case RIGHTMOUSE:
+ {
+ /* exception to cancel current stroke when we have previous strokes in buffer */
+ if (tgpi->tot_stored_edges > 0) {
+ tgpi->flag = IDLE;
+ tgpi->tot_edges = 0;
+ gp_primitive_update_strokes(C, tgpi);
+ gpencil_primitive_interaction_end(C, op, win, tgpi);
+ /* done! */
+ return OPERATOR_FINISHED;
+ }
+ ATTR_FALLTHROUGH;
+ }
case ESCKEY:
{
/* return to normal cursor and header status */
diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c
index fff753cf3a9..1deeab641f4 100644
--- a/source/blender/editors/gpencil/gpencil_select.c
+++ b/source/blender/editors/gpencil/gpencil_select.c
@@ -861,11 +861,14 @@ void GPENCIL_OT_select_less(wmOperatorType *ot)
* It would be great to de-duplicate the logic here sometime, but that can wait...
*/
static bool gp_stroke_do_circle_sel(
+ bGPDlayer *gpl,
bGPDstroke *gps, GP_SpaceConversion *gsc,
const int mx, const int my, const int radius,
- const bool select, rcti *rect, float diff_mat[4][4], const int selectmode)
+ const bool select, rcti *rect, float diff_mat[4][4], const int selectmode,
+ const float scale)
{
- bGPDspoint *pt1, *pt2;
+ bGPDspoint *pt1 = NULL;
+ bGPDspoint *pt2 = NULL;
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
int i;
bool changed = false;
@@ -958,6 +961,14 @@ static bool gp_stroke_do_circle_sel(
}
}
+ /* expand selection to segment */
+ if ((hit) && (selectmode == GP_SELECTMODE_SEGMENT) && (select)) {
+ float r_hita[3], r_hitb[3];
+ bool hit_select = (bool)(pt1->flag & GP_SPOINT_SELECT);
+ ED_gpencil_select_stroke_segment(
+ gpl, gps, pt1, hit_select, false, scale, r_hita, r_hitb);
+ }
+
/* Ensure that stroke selection is in sync with its points */
BKE_gpencil_stroke_sync_selection(gps);
}
@@ -971,6 +982,7 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op)
bGPdata *gpd = ED_gpencil_data_get_active(C);
ToolSettings *ts = CTX_data_tool_settings(C);
const int selectmode = ts->gpencil_selectmode;
+ const float scale = ts->gp_sculpt.isect_threshold;
/* if not edit/sculpt mode, the event is catched but not processed */
if (GPENCIL_NONE_EDIT_MODE(gpd)) {
@@ -1012,7 +1024,8 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op)
GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps)
{
changed |= gp_stroke_do_circle_sel(
- gps, &gsc, mx, my, radius, select, &rect, gpstroke_iter.diff_mat, selectmode);
+ gpl, gps, &gsc, mx, my, radius, select, &rect,
+ gpstroke_iter.diff_mat, selectmode, scale);
}
GP_EDITABLE_STROKES_END(gpstroke_iter);
@@ -1074,7 +1087,11 @@ static int gpencil_generic_select_exec(
const bool strokemode = (
(ts->gpencil_selectmode == GP_SELECTMODE_STROKE) &&
((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0));
+ const bool segmentmode = (
+ (ts->gpencil_selectmode == GP_SELECTMODE_SEGMENT) &&
+ ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0));
const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode");
+ const float scale = ts->gp_sculpt.isect_threshold;
GP_SpaceConversion gsc = {NULL};
@@ -1124,6 +1141,17 @@ static int gpencil_generic_select_exec(
if (sel_op_result != -1) {
SET_FLAG_FROM_TEST(pt->flag, sel_op_result, GP_SPOINT_SELECT);
changed = true;
+
+ /* expand selection to segment */
+ if ((sel_op_result != -1) &&
+ (segmentmode))
+ {
+ bool hit_select = (bool)(pt->flag & GP_SPOINT_SELECT);
+ float r_hita[3], r_hitb[3];
+ ED_gpencil_select_stroke_segment(
+ gpl, gps, pt, hit_select, false, scale, r_hita, r_hitb);
+ }
+
}
}
else {
@@ -1336,6 +1364,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op)
ScrArea *sa = CTX_wm_area(C);
bGPdata *gpd = ED_gpencil_data_get_active(C);
ToolSettings *ts = CTX_data_tool_settings(C);
+ const float scale = ts->gp_sculpt.isect_threshold;
/* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */
const float radius = 0.50f * U.widget_unit;
@@ -1350,6 +1379,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op)
GP_SpaceConversion gsc = {NULL};
+ bGPDlayer *hit_layer = NULL;
bGPDstroke *hit_stroke = NULL;
bGPDspoint *hit_point = NULL;
int hit_distance = radius_squared;
@@ -1394,6 +1424,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op)
if (pt_distance <= radius_squared) {
/* only use this point if it is a better match than the current hit - T44685 */
if (pt_distance < hit_distance) {
+ hit_layer = gpl;
hit_stroke = gps;
hit_point = pt;
hit_distance = pt_distance;
@@ -1454,6 +1485,15 @@ static int gpencil_select_exec(bContext *C, wmOperator *op)
/* we're adding selection, so selection must be true */
hit_point->flag |= GP_SPOINT_SELECT;
hit_stroke->flag |= GP_STROKE_SELECT;
+
+ /* expand selection to segment */
+ if (ts->gpencil_selectmode == GP_SELECTMODE_SEGMENT) {
+ float r_hita[3], r_hitb[3];
+ bool hit_select = (bool)(hit_point->flag & GP_SPOINT_SELECT);
+ ED_gpencil_select_stroke_segment(
+ hit_layer, hit_stroke, hit_point, hit_select,
+ false, scale, r_hita, r_hitb);
+ }
}
else {
/* deselect point */
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c
index 607341add86..232f3b8f606 100644
--- a/source/blender/editors/gpencil/gpencil_utils.c
+++ b/source/blender/editors/gpencil/gpencil_utils.c
@@ -36,6 +36,7 @@
#include "BLI_math.h"
#include "BLI_blenlib.h"
+#include "BLI_ghash.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "BLI_rand.h"
@@ -723,6 +724,62 @@ void gp_point_to_xy_fl(
}
}
+
+/**
+* generic based on gp_point_to_xy_fl
+*/
+void gp_point_3d_to_xy(const GP_SpaceConversion *gsc, const short flag, const float pt[3], float xy[2])
+{
+ const ARegion *ar = gsc->ar;
+ const View2D *v2d = gsc->v2d;
+ const rctf *subrect = gsc->subrect;
+ float xyval[2];
+
+ /* sanity checks */
+ BLI_assert((gsc->sa->spacetype == SPACE_VIEW3D));
+
+ if (flag & GP_STROKE_3DSPACE) {
+ if (ED_view3d_project_float_global(ar, pt, xyval, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
+ xy[0] = xyval[0];
+ xy[1] = xyval[1];
+ }
+ else {
+ xy[0] = 0.0f;
+ xy[1] = 0.0f;
+ }
+ }
+ else if (flag & GP_STROKE_2DSPACE) {
+ float vec[3] = { pt[0], pt[1], 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? */
+ xy[0] = 0.0f;
+ xy[1] = 0.0f;
+ }
+ else {
+ xy[0] = (float)t_x;
+ xy[1] = (float)t_y;
+ }
+ }
+ else {
+ if (subrect == NULL) {
+ /* normal 3D view (or view space) */
+ xy[0] = (pt[0] / 100.0f * ar->winx);
+ xy[1] = (pt[1] / 100.0f * ar->winy);
+ }
+ else {
+ /* camera view, use subrect */
+ xy[0] = ((pt[0] / 100.0f) * BLI_rctf_size_x(subrect)) + subrect->xmin;
+ xy[1] = ((pt[1] / 100.0f) * BLI_rctf_size_y(subrect)) + subrect->ymin;
+ }
+ }
+}
+
+
/**
* Project screenspace coordinates to 3D-space
*
@@ -738,7 +795,7 @@ void gp_point_to_xy_fl(
*
* \warning Assumes that it is getting called in a 3D view only.
*/
-bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, Scene *scene, const float screen_co[2], float r_out[3])
+bool gp_point_xy_to_3d(const GP_SpaceConversion *gsc, Scene *scene, const float screen_co[2], float r_out[3])
{
const RegionView3D *rv3d = gsc->ar->regiondata;
float rvec[3];
@@ -1946,4 +2003,347 @@ void ED_gpencil_update_color_uv(Main *bmain, Material *mat)
}
}
}
-/* ******************************************************** */
+
+static bool gpencil_check_collision(
+ bGPDstroke *gps, bGPDstroke **gps_array, GHash *all_2d,
+ int totstrokes, float p2d_a1[2], float p2d_a2[2], float r_hit[2])
+{
+ bool hit = false;
+ /* check segment with all segments of all strokes */
+ for (int s = 0; s < totstrokes; s++) {
+ bGPDstroke *gps_iter = gps_array[s];
+ if (gps_iter->totpoints < 2) {
+ continue;
+ }
+ /* get stroke 2d version */
+ float(*points2d)[2] = BLI_ghash_lookup(all_2d, gps_iter);
+
+ for (int i2 = 0; i2 < gps_iter->totpoints - 1; i2++) {
+ float p2d_b1[2], p2d_b2[2];
+ copy_v2_v2(p2d_b1, points2d[i2]);
+ copy_v2_v2(p2d_b2, points2d[i2 + 1]);
+
+ /* don't self check */
+ if (gps == gps_iter) {
+ if (equals_v2v2(p2d_a1, p2d_b1) || equals_v2v2(p2d_a1, p2d_b2)) {
+ continue;
+ }
+ if (equals_v2v2(p2d_a2, p2d_b1) || equals_v2v2(p2d_a2, p2d_b2)) {
+ continue;
+ }
+ }
+ /* check collision */
+ int check = isect_seg_seg_v2_point(p2d_a1, p2d_a2, p2d_b1, p2d_b2, r_hit);
+ if (check > 0) {
+ hit = true;
+ break;
+ }
+ }
+
+ if (hit) {
+ break;
+ }
+ }
+
+ if (!hit) {
+ zero_v2(r_hit);
+ }
+
+ return hit;
+}
+
+void static gp_copy_points(
+ bGPDstroke *gps, bGPDspoint *pt, bGPDspoint *pt_final, int i, int i2)
+{
+ copy_v3_v3(&pt_final->x, &pt->x);
+ pt_final->pressure = pt->pressure;
+ pt_final->strength = pt->strength;
+ pt_final->time = pt->time;
+ pt_final->flag = pt->flag;
+ pt_final->uv_fac = pt->uv_fac;
+ pt_final->uv_rot = pt->uv_rot;
+
+ if (gps->dvert != NULL) {
+ MDeformVert *dvert = &gps->dvert[i];
+ MDeformVert *dvert_final = &gps->dvert[i2];
+
+ dvert_final->totweight = dvert->totweight;
+ dvert_final->dw = dvert->dw;
+ }
+
+}
+
+void static gp_insert_point(
+ bGPDstroke *gps,
+ bGPDspoint *a_pt, bGPDspoint *b_pt,
+ float co_a[3], float co_b[3])
+{
+ bGPDspoint *temp_points;
+ int totnewpoints, oldtotpoints;
+
+ totnewpoints = gps->totpoints;
+ if (a_pt) {
+ totnewpoints++;
+ }
+ if (b_pt) {
+ totnewpoints++;
+ }
+
+ /* duplicate points in a temp area */
+ temp_points = MEM_dupallocN(gps->points);
+ oldtotpoints = gps->totpoints;
+
+ /* look index of base points because memory is changed when resize points array */
+ int a_idx = -1;
+ int b_idx = -1;
+ for (int i = 0; i < oldtotpoints; i++) {
+ bGPDspoint *pt = &gps->points[i];
+ if (pt == a_pt) {
+ a_idx = i;
+ }
+ if (pt == b_pt) {
+ b_idx = i;
+ }
+ }
+
+ /* resize the points arrays */
+ gps->totpoints = totnewpoints;
+ gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
+ if (gps->dvert != NULL) {
+ gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
+ }
+ gps->flag |= GP_STROKE_RECALC_GEOMETRY;
+
+ /* copy all points */
+ int i2 = 0;
+ for (int i = 0; i < oldtotpoints; i++) {
+ bGPDspoint *pt = &temp_points[i];
+ bGPDspoint *pt_final = &gps->points[i2];
+ gp_copy_points(gps, pt, pt_final, i, i2);
+
+ /* create new point duplicating point and copy location */
+ if ((i == a_idx) || (i == b_idx)) {
+ i2++;
+ pt_final = &gps->points[i2];
+ gp_copy_points(gps, pt, pt_final, i, i2);
+ copy_v3_v3(&pt_final->x, (i == a_idx) ? co_a : co_b);
+
+ /* unselect */
+ pt_final->flag &= ~GP_SPOINT_SELECT;
+ /* tag to avoid more checking with this point */
+ pt_final->flag |= GP_SPOINT_TAG;
+ }
+
+ i2++;
+ }
+
+ MEM_SAFE_FREE(temp_points);
+}
+
+static float gp_calc_factor(float p2d_a1[2], float p2d_a2[2], float r_hit2d[2])
+{
+ float dist1 = len_squared_v2v2(p2d_a1, p2d_a2);
+ float dist2 = len_squared_v2v2(p2d_a1, r_hit2d);
+ float f = dist1 > 0.0f ? dist2 / dist1 : 0.0f;
+
+ /* apply a correction factor */
+ float v1[2];
+ interp_v2_v2v2(v1, p2d_a1, p2d_a2, f);
+ float dist3 = len_squared_v2v2(p2d_a1, v1);
+ float f1 = dist1 > 0.0f ? dist3 / dist1 : 0.0f;
+ f = f + (f - f1);
+
+ return f;
+}
+
+/* extend selection to stroke intersections */
+int ED_gpencil_select_stroke_segment(
+ bGPDlayer *gpl, bGPDstroke *gps, bGPDspoint *pt,
+ bool select, bool insert, const float scale,
+ float r_hita[3], float r_hitb[3])
+{
+ const float min_factor = 0.0015f;
+ bGPDspoint *pta1 = NULL;
+ bGPDspoint *pta2 = NULL;
+ float f = 0.0f;
+ int i2 = 0;
+
+ bGPDframe *gpf = gpl->actframe;
+ if (gpf == NULL) {
+ return 0;
+ }
+
+ int memsize = BLI_listbase_count(&gpf->strokes);
+ bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * memsize, __func__);
+
+ /* save points */
+ bGPDspoint *oldpoints = MEM_dupallocN(gps->points);
+
+ /* Save list of strokes to check */
+ int totstrokes = 0;
+ for (bGPDstroke *gps_iter = gpf->strokes.first; gps_iter; gps_iter = gps_iter->next) {
+
+ if (gps_iter->totpoints < 2) {
+ continue;
+ }
+ gps_array[totstrokes] = gps_iter;
+ totstrokes++;
+ }
+
+ if (totstrokes == 0) {
+ return 0;
+ }
+
+ /* look for index of the current point */
+ int cur_idx = -1;
+ for (int i = 0; i < gps->totpoints; i++) {
+ pta1 = &gps->points[i];
+ if (pta1 == pt) {
+ cur_idx = i;
+ break;
+ }
+ }
+ if (cur_idx < 0) {
+ return 0;
+ }
+
+ /* convert all gps points to 2d and save in a hash to avoid recalculation */
+ int direction = 0;
+ float(*points2d)[2] = MEM_mallocN(sizeof(*points2d) * gps->totpoints, "GP Stroke temp 2d points");
+ BKE_gpencil_stroke_2d_flat_ref(
+ gps->points, gps->totpoints,
+ gps->points, gps->totpoints, points2d, scale, &direction);
+
+ GHash *all_2d = BLI_ghash_ptr_new(__func__);
+
+ for (int s = 0; s < totstrokes; s++) {
+ bGPDstroke *gps_iter = gps_array[s];
+ float(*points2d_iter)[2] = MEM_mallocN(sizeof(*points2d_iter) * gps_iter->totpoints, __func__);
+
+ /* the extremes of the stroke are scaled to improve collision detection
+ * for near lines */
+ BKE_gpencil_stroke_2d_flat_ref(
+ gps->points, gps->totpoints,
+ gps_iter->points, gps_iter->totpoints, points2d_iter,
+ scale, &direction);
+ BLI_ghash_insert(all_2d, gps_iter, points2d_iter);
+ }
+
+ bool hit_a = false;
+ bool hit_b = false;
+ float p2d_a1[2] = {0.0f, 0.0f};
+ float p2d_a2[2] = {0.0f, 0.0f};
+ float r_hit2d[2];
+ bGPDspoint *hit_pointa = NULL;
+ bGPDspoint *hit_pointb = NULL;
+
+ /* analyze points before current */
+ if (cur_idx > 0) {
+ for (int i = cur_idx; i >= 0; i--) {
+ pta1 = &gps->points[i];
+ copy_v2_v2(p2d_a1, points2d[i]);
+
+ i2 = i - 1;
+ CLAMP_MIN(i2, 0);
+ pta2 = &gps->points[i2];
+ copy_v2_v2(p2d_a2, points2d[i2]);
+
+ hit_a = gpencil_check_collision(
+ gps, gps_array, all_2d, totstrokes, p2d_a1, p2d_a2, r_hit2d);
+
+ if (select) {
+ pta1->flag |= GP_SPOINT_SELECT;
+ }
+ else {
+ pta1->flag &= ~GP_SPOINT_SELECT;
+ }
+
+ if (hit_a) {
+ f = gp_calc_factor(p2d_a1, p2d_a2, r_hit2d);
+ interp_v3_v3v3(r_hita, &pta1->x, &pta2->x, f);
+ if (f > min_factor) {
+ hit_pointa = pta2; /* first point is second (inverted loop) */
+ }
+ else {
+ pta1->flag &= ~GP_SPOINT_SELECT;
+ }
+ break;
+ }
+ }
+ }
+
+ /* analyze points after current */
+ for (int i = cur_idx; i < gps->totpoints; i++) {
+ pta1 = &gps->points[i];
+ copy_v2_v2(p2d_a1, points2d[i]);
+
+ i2 = i + 1;
+ CLAMP_MAX(i2, gps->totpoints - 1);
+ pta2 = &gps->points[i2];
+ copy_v2_v2(p2d_a2, points2d[i2]);
+
+ hit_b = gpencil_check_collision(
+ gps, gps_array, all_2d, totstrokes, p2d_a1, p2d_a2, r_hit2d);
+
+ if (select) {
+ pta1->flag |= GP_SPOINT_SELECT;
+ }
+ else {
+ pta1->flag &= ~GP_SPOINT_SELECT;
+ }
+
+ if (hit_b) {
+ f = gp_calc_factor(p2d_a1, p2d_a2, r_hit2d);
+ interp_v3_v3v3(r_hitb, &pta1->x, &pta2->x, f);
+ if (f > min_factor) {
+ hit_pointb = pta1;
+ }
+ else {
+ pta1->flag &= ~GP_SPOINT_SELECT;
+ }
+ break;
+ }
+ }
+
+ /* insert new point in the collision points */
+ if (insert) {
+ gp_insert_point(gps, hit_pointa, hit_pointb, r_hita, r_hitb);
+ }
+
+ /* free memory */
+ if (all_2d) {
+ GHashIterator gh_iter;
+ GHASH_ITER(gh_iter, all_2d) {
+ float(*p2d)[2] = BLI_ghashIterator_getValue(&gh_iter);
+ MEM_SAFE_FREE(p2d);
+ }
+ BLI_ghash_free(all_2d, NULL, NULL);
+ }
+
+ /* if no hit, reset selection flag */
+ if ((!hit_a) && (!hit_b)) {
+ for (int i = 0; i < gps->totpoints; i++) {
+ pta1 = &gps->points[i];
+ pta2 = &oldpoints[i];
+ pta1->flag = pta2->flag;
+ }
+ }
+
+ MEM_SAFE_FREE(points2d);
+ MEM_SAFE_FREE(gps_array);
+ MEM_SAFE_FREE(oldpoints);
+
+ /* return type of hit */
+ if ((hit_a) && (hit_b)) {
+ return 3;
+ }
+ else if (hit_a) {
+ return 1;
+ }
+ else if (hit_b) {
+ return 2;
+ }
+ else {
+ return 0;
+ }
+}
diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h
index c231d0dc355..808e8461471 100644
--- a/source/blender/editors/include/ED_gpencil.h
+++ b/source/blender/editors/include/ED_gpencil.h
@@ -259,4 +259,17 @@ void ED_gpencil_tpoint_to_point(struct ARegion *ar, float origin[3], const struc
void ED_gpencil_calc_stroke_uv(struct Object *ob, struct bGPDstroke *gps);
void ED_gpencil_update_color_uv(struct Main *bmain, struct Material *mat);
+/* extend selection to stroke intersections
+ * returns:
+ * 0 - No hit
+ * 1 - Hit in point A
+ * 2 - Hit in point B
+ * 3 - Hit in point A and B
+*/
+int ED_gpencil_select_stroke_segment(
+ struct bGPDlayer *gpl,
+ struct bGPDstroke *gps, struct bGPDspoint *pt,
+ bool select, bool insert, const float scale,
+ float r_hita[3], float r_hitb[3]);
+
#endif /* __ED_GPENCIL_H__ */