GP: box/lasso support for select-operations

This adds support for more adbvanced select operations
matching 3D view select operators.

Also de-duplicate box/lasso select code.
This commit is contained in:
Campbell Barton 2018-11-13 14:04:00 +11:00
parent ed4f204d86
commit b323167600
6 changed files with 137 additions and 170 deletions

View File

@ -2727,18 +2727,18 @@ def _grease_pencil_selection(params):
("gpencil.select_box", {"type": 'B', "value": 'PRESS'}, None),
# Lasso select
("gpencil.select_lasso", {"type": 'EVT_TWEAK_A', "value": 'ANY', "ctrl": True},
{"properties": [("deselect", False)]}),
{"properties": [("mode", 'ADD')]}),
("gpencil.select_lasso", {"type": 'EVT_TWEAK_A', "value": 'ANY', "shift": True, "ctrl": True},
{"properties": [("deselect", True)]}),
{"properties": [("mode", 'SUB')]}),
# In the Node Editor, lasso select needs ALT modifier too
# (as somehow CTRL+LMB drag gets taken for "cut" quite early).
# There probably isn't too much harm adding this for other editors too
# as part of standard GP editing keymap. This hotkey combo doesn't seem
# to see much use under standard scenarios?
("gpencil.select_lasso", {"type": 'EVT_TWEAK_A', "value": 'ANY', "ctrl": True, "alt": True},
{"properties": [("deselect", False)]}),
{"properties": [("mode", 'ADD')]}),
("gpencil.select_lasso", {"type": 'EVT_TWEAK_A', "value": 'ANY', "shift": True, "ctrl": True, "alt": True},
{"properties": [("deselect", True)]}),
{"properties": [("mode", 'SUB')]}),
("gpencil.select", {"type": params.select_mouse, "value": 'PRESS', "shift": True},
{"properties": [("extend", True), ("toggle", True)]}),
# Whole stroke select
@ -2918,8 +2918,7 @@ def km_grease_pencil_stroke_paint_draw_brush(_params):
# Box select
("gpencil.select_box", {"type": 'B', "value": 'PRESS'}, None),
# Lasso select
("gpencil.select_lasso", {"type": 'EVT_TWEAK_A', "value": 'ANY', "ctrl": True, "alt": True},
{"properties": [("deselect", False)]}),
("gpencil.select_lasso", {"type": 'EVT_TWEAK_A', "value": 'ANY', "ctrl": True, "alt": True}, None),
])
return keymap
@ -2942,8 +2941,7 @@ def km_grease_pencil_stroke_paint_erase(_params):
# Box select (used by eraser)
("gpencil.select_box", {"type": 'B', "value": 'PRESS'}, None),
# Lasso select
("gpencil.select_lasso", {"type": 'EVT_TWEAK_A', "value": 'ANY', "ctrl": True, "alt": True},
{"properties": [("deselect", False)]}),
("gpencil.select_lasso", {"type": 'EVT_TWEAK_A', "value": 'ANY', "ctrl": True, "alt": True}, None),
])
return keymap

View File

@ -149,6 +149,8 @@ bool BKE_gpencil_data_minmax(
bool BKE_gpencil_stroke_minmax(
const struct bGPDstroke *gps, const bool use_select,
float r_min[3], float r_max[3]);
bool BKE_gpencil_stroke_select_check(
const struct bGPDstroke *gps);
struct BoundBox *BKE_gpencil_boundbox_get(struct Object *ob);
void BKE_gpencil_centroid_3D(struct bGPdata *gpd, float r_centroid[3]);

View File

@ -1133,6 +1133,19 @@ bool BKE_gpencil_data_minmax(Object *ob, const bGPdata *gpd, float r_min[3], flo
return changed;
}
bool BKE_gpencil_stroke_select_check(
const bGPDstroke *gps)
{
const bGPDspoint *pt;
int i;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (pt->flag & GP_SPOINT_SELECT) {
return true;
}
}
return false;
}
/* compute center of bounding box */
void BKE_gpencil_centroid_3D(bGPdata *gpd, float r_centroid[3])
{

View File

@ -199,14 +199,14 @@ bool gp_stroke_inside_circle(
void gp_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc);
void gp_point_to_xy(
GP_SpaceConversion *settings, struct bGPDstroke *gps, struct bGPDspoint *pt,
const GP_SpaceConversion *gsc, const struct bGPDstroke *gps, const struct bGPDspoint *pt,
int *r_x, int *r_y);
void gp_point_to_xy_fl(
GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
const GP_SpaceConversion *gsc, const bGPDstroke *gps, const bGPDspoint *pt,
float *r_x, float *r_y);
void gp_point_to_parent_space(bGPDspoint *pt, float diff_mat[4][4], bGPDspoint *r_pt);
void gp_point_to_parent_space(const bGPDspoint *pt, const float diff_mat[4][4], bGPDspoint *r_pt);
/**
* Change points position relative to parent object
*/

View File

@ -1014,21 +1014,25 @@ void GPENCIL_OT_select_circle(wmOperatorType *ot)
WM_operator_properties_gesture_circle_select(ot);
}
/* ********************************************** */
/* Box Selection */
static int gpencil_box_select_exec(bContext *C, wmOperator *op)
typedef bool (*GPencilTestFn)(
bGPDstroke *gps, bGPDspoint *pt,
const GP_SpaceConversion *gsc, const float diff_mat[4][4], void *user_data);
static int gpencil_generic_select_exec(
bContext *C, wmOperator *op,
GPencilTestFn is_inside_fn, void *user_data)
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
ToolSettings *ts = CTX_data_tool_settings(C);
ScrArea *sa = CTX_wm_area(C);
const bool strokemode = (
(ts->gpencil_selectmode == GP_SELECTMODE_STROKE) &&
((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0));
const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode");
const bool select = !RNA_boolean_get(op->ptr, "deselect");
bool extend = RNA_boolean_get(op->ptr, "extend") && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0);
const bool strokemode = (ts->gpencil_selectmode == GP_SELECTMODE_STROKE) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0);
GP_SpaceConversion gsc = {NULL};
rcti rect = {0};
bool changed = false;
@ -1038,15 +1042,12 @@ static int gpencil_box_select_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (strokemode) {
extend = false;
}
/* init space conversion stuff */
gp_point_conversion_init(C, &gsc);
/* deselect all strokes first? */
if (select && !extend) {
if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
{
bGPDspoint *pt;
@ -1061,9 +1062,6 @@ static int gpencil_box_select_exec(bContext *C, wmOperator *op)
CTX_DATA_END;
}
/* get settings from operator */
WM_operator_properties_border_to_rcti(op, &rect);
/* select/deselect points */
GP_EDITABLE_STROKES_BEGIN(C, gpl, gps)
{
@ -1072,40 +1070,40 @@ static int gpencil_box_select_exec(bContext *C, wmOperator *op)
int i;
bool hit = false;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
int x0, y0;
/* convert point coords to screenspace */
bGPDspoint pt2;
gp_point_to_parent_space(pt, diff_mat, &pt2);
gp_point_to_xy(&gsc, gps, &pt2, &x0, &y0);
const bool is_inside = is_inside_fn(gps, pt, &gsc, diff_mat, user_data);
/* test if in selection rect */
if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0)) {
hit = true;
if (select) {
pt->flag |= GP_SPOINT_SELECT;
if (strokemode == false) {
const bool is_select = (pt->flag & GP_SPOINT_SELECT) != 0;
const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
if (sel_op_result != -1) {
SET_FLAG_FROM_TEST(pt->flag, sel_op_result, GP_SPOINT_SELECT);
changed = true;
}
else {
pt->flag &= ~GP_SPOINT_SELECT;
}
changed = true;
/* if stroke mode, don't check more points */
if ((hit) && (strokemode)) {
}
else {
if (is_inside) {
hit = true;
break;
}
}
}
/* if stroke mode expand selection */
if ((hit) && (strokemode)) {
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (select) {
pt->flag |= GP_SPOINT_SELECT;
}
else {
pt->flag &= ~GP_SPOINT_SELECT;
if (strokemode) {
const bool is_select = BKE_gpencil_stroke_select_check(gps);
const bool is_inside = hit;
const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
if (sel_op_result != -1) {
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (sel_op_result) {
pt->flag |= GP_SPOINT_SELECT;
}
else {
pt->flag &= ~GP_SPOINT_SELECT;
}
}
changed = true;
}
}
@ -1134,6 +1132,35 @@ static int gpencil_box_select_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
/* ********************************************** */
/* Box Selection */
struct GP_SelectBoxUserData {
rcti rect;
};
static bool gpencil_test_box(
bGPDstroke *gps, bGPDspoint *pt,
const GP_SpaceConversion *gsc, const float diff_mat[4][4], void *user_data)
{
const struct GP_SelectBoxUserData *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);
return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) &&
BLI_rcti_isect_pt(&data->rect, x0, y0));
}
static int gpencil_box_select_exec(bContext *C, wmOperator *op)
{
struct GP_SelectBoxUserData data = {0};
WM_operator_properties_border_to_rcti(op, &data.rect);
return gpencil_generic_select_exec(
C, op,
gpencil_test_box, &data);
}
void GPENCIL_OT_select_box(wmOperatorType *ot)
{
/* identifiers */
@ -1153,129 +1180,55 @@ void GPENCIL_OT_select_box(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
/* rna */
WM_operator_properties_gesture_box_select(ot);
WM_operator_properties_select_operation(ot);
WM_operator_properties_gesture_box(ot);
}
/* ********************************************** */
/* Lasso */
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 boundbox + within the lasso noose */
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));
}
static int gpencil_lasso_select_exec(bContext *C, wmOperator *op)
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
ToolSettings *ts = CTX_data_tool_settings(C);
GP_SpaceConversion gsc = {NULL};
rcti rect = {0};
struct GP_SelectLassoUserData data = {0};
data.mcords = WM_gesture_lasso_path_to_array(C, op, &data.mcords_len);
bool extend = RNA_boolean_get(op->ptr, "extend") && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0);
const bool select = !RNA_boolean_get(op->ptr, "deselect");
const bool strokemode = (ts->gpencil_selectmode == GP_SELECTMODE_STROKE) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0);
int mcords_tot;
const int (*mcords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcords_tot);
bool changed = false;
/* sanity check */
if (mcords == NULL)
/* Sanity check. */
if (data.mcords == NULL) {
return OPERATOR_PASS_THROUGH;
if (strokemode) {
extend = false;
}
/* compute boundbox of lasso (for faster testing later) */
BLI_lasso_boundbox(&rect, mcords, mcords_tot);
/* Compute boundbox of lasso (for faster testing later). */
BLI_lasso_boundbox(&data.rect, data.mcords, data.mcords_len);
/* init space conversion stuff */
gp_point_conversion_init(C, &gsc);
int ret = gpencil_generic_select_exec(
C, op,
gpencil_test_lasso, &data);
/* deselect all strokes first? */
if (select && !extend) {
CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
{
bGPDspoint *pt;
int i;
MEM_freeN((void *)data.mcords);
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/deselect points */
GP_EDITABLE_STROKES_BEGIN(C, gpl, gps)
{
bGPDspoint *pt;
int i;
bool hit = false;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
int x0, y0;
/* convert point coords to screenspace */
bGPDspoint pt2;
gp_point_to_parent_space(pt, diff_mat, &pt2);
gp_point_to_xy(&gsc, gps, &pt2, &x0, &y0);
/* test if in lasso boundbox + within the lasso noose */
if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0) &&
BLI_lasso_is_point_inside(mcords, mcords_tot, x0, y0, INT_MAX))
{
hit = true;
if (select) {
pt->flag |= GP_SPOINT_SELECT;
}
else {
pt->flag &= ~GP_SPOINT_SELECT;
}
changed = true;
/* if stroke mode, don't check more points */
if ((hit) && (strokemode)) {
break;
}
}
}
/* if stroke mode expand selection */
if ((hit) && (strokemode)) {
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (select) {
pt->flag |= GP_SPOINT_SELECT;
}
else {
pt->flag &= ~GP_SPOINT_SELECT;
}
}
}
/* Ensure that stroke selection is in sync with its points */
BKE_gpencil_stroke_sync_selection(gps);
}
GP_EDITABLE_STROKES_END;
/* cleanup */
MEM_freeN((void *)mcords);
/* if paint mode,delete selected points */
if (gpd->flag & GP_DATA_STROKE_PAINTMODE) {
gp_delete_selected_point_wrap(C);
changed = true;
DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA);
}
/* updates */
if (changed) {
DEG_id_tag_update(&gpd->id, OB_RECALC_DATA);
/* copy on write tag is needed, or else no refresh happens */
DEG_id_tag_update(&gpd->id, DEG_TAG_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
}
return OPERATOR_FINISHED;
return ret;
}
void GPENCIL_OT_select_lasso(wmOperatorType *ot)
@ -1294,7 +1247,8 @@ void GPENCIL_OT_select_lasso(wmOperatorType *ot)
ot->flag = OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
/* properties */
WM_operator_properties_gesture_lasso_select(ot);
WM_operator_properties_select_operation(ot);
WM_operator_properties_gesture_lasso(ot);
}
/* ********************************************** */

View File

@ -555,7 +555,7 @@ void gp_point_conversion_init(bContext *C, GP_SpaceConversion *r_gsc)
* \param diff_mat: Matrix with the difference between original parent matrix
* \param[out] r_pt: Pointer to new point after apply matrix
*/
void gp_point_to_parent_space(bGPDspoint *pt, float diff_mat[4][4], bGPDspoint *r_pt)
void gp_point_to_parent_space(const bGPDspoint *pt, const float diff_mat[4][4], bGPDspoint *r_pt)
{
float fpt[3];
@ -612,12 +612,12 @@ void gp_apply_parent_point(Depsgraph *depsgraph, Object *obact, bGPdata *gpd, bG
* \warning This assumes that the caller has already checked whether the stroke in question can be drawn.
*/
void gp_point_to_xy(
GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
const GP_SpaceConversion *gsc, const bGPDstroke *gps, const bGPDspoint *pt,
int *r_x, int *r_y)
{
ARegion *ar = gsc->ar;
View2D *v2d = gsc->v2d;
rctf *subrect = gsc->subrect;
const ARegion *ar = gsc->ar;
const View2D *v2d = gsc->v2d;
const rctf *subrect = gsc->subrect;
int xyval[2];
/* sanity checks */
@ -666,12 +666,12 @@ void gp_point_to_xy(
* \warning This assumes that the caller has already checked whether the stroke in question can be drawn
*/
void gp_point_to_xy_fl(
GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
const GP_SpaceConversion *gsc, const bGPDstroke *gps, const bGPDspoint *pt,
float *r_x, float *r_y)
{
ARegion *ar = gsc->ar;
View2D *v2d = gsc->v2d;
rctf *subrect = gsc->subrect;
const ARegion *ar = gsc->ar;
const View2D *v2d = gsc->v2d;
const rctf *subrect = gsc->subrect;
float xyval[2];
/* sanity checks */