diff options
Diffstat (limited to 'source/blender/editors')
29 files changed, 3322 insertions, 955 deletions
diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index b2c618073f4..1e91edbb3c0 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -42,6 +42,7 @@ set(SRC gpencil_convert.c gpencil_data.c gpencil_edit.c + gpencil_edit_curve.c gpencil_fill.c gpencil_interpolate.c gpencil_merge.c diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index a9f9625db7a..a73bf8154d9 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -910,7 +910,7 @@ static void annotation_stroke_newfrombuffer(tGPsdata *p) int totarrowpoints = runtime.arrow_end_style; /* Setting up arrow stroke. */ - bGPDstroke *e_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false); + bGPDstroke *e_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false, false); annotation_stroke_arrow_allocate(e_arrow_gps, totarrowpoints); /* Set pointer to first non-initialized point. */ @@ -931,7 +931,7 @@ static void annotation_stroke_newfrombuffer(tGPsdata *p) int totarrowpoints = runtime.arrow_start_style; /* Setting up arrow stroke. */ - bGPDstroke *s_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false); + bGPDstroke *s_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false, false); annotation_stroke_arrow_allocate(s_arrow_gps, totarrowpoints); /* Set pointer to first non-initialized point. */ @@ -1198,7 +1198,7 @@ static void annotation_stroke_eraser_dostroke(tGPsdata *p, /* Second Pass: Remove any points that are tagged */ if (do_cull) { - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + gpencil_stroke_delete_tagged_points(p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } } } diff --git a/source/blender/editors/gpencil/editaction_gpencil.c b/source/blender/editors/gpencil/editaction_gpencil.c index d4f3c4d7f5e..aae31b11025 100644 --- a/source/blender/editors/gpencil/editaction_gpencil.c +++ b/source/blender/editors/gpencil/editaction_gpencil.c @@ -486,7 +486,7 @@ bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) */ for (gps = gpfs->strokes.first; gps; gps = gps->next) { /* make a copy of stroke, then of its points array */ - gpsn = BKE_gpencil_stroke_duplicate(gps, true); + gpsn = BKE_gpencil_stroke_duplicate(gps, true, true); /* append stroke to frame */ BLI_addtail(&gpf->strokes, gpsn); diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index 23ca5241866..315b3c281da 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -861,115 +861,115 @@ void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4]) /* generate strokes */ gps = BKE_gpencil_stroke_add(frameFills, color_Skin, 270, 75, false); BKE_gpencil_stroke_add_points(gps, data0, 270, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data1, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 60, false); BKE_gpencil_stroke_add_points(gps, data2, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data3, 64, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data4, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data5, 64, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data6, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data7, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Eyes, 49, 60, false); BKE_gpencil_stroke_add_points(gps, data8, 49, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data9, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Eyes, 49, 60, false); BKE_gpencil_stroke_add_points(gps, data10, 49, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data11, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data12, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data13, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data14, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 65, 60, false); BKE_gpencil_stroke_add_points(gps, data15, 65, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 34, 60, false); BKE_gpencil_stroke_add_points(gps, data16, 34, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data17, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 40, false); BKE_gpencil_stroke_add_points(gps, data18, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 34, 40, false); BKE_gpencil_stroke_add_points(gps, data19, 34, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data20, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data21, 64, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Pupils, 26, 60, false); BKE_gpencil_stroke_add_points(gps, data22, 26, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Pupils, 26, 60, false); BKE_gpencil_stroke_add_points(gps, data23, 26, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data24, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data25, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data26, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data27, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* update depsgraph */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c index 39a2d594c13..f26fd936d40 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -249,7 +249,7 @@ void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4]) /* generate stroke */ gps = BKE_gpencil_stroke_add(frame_lines, color_black, 175, 75, false); BKE_gpencil_stroke_add_points(gps, data0, 175, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* update depsgraph */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 237b5839c42..293ce370a0b 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -1855,12 +1855,12 @@ static int image_to_gpencil_exec(bContext *C, wmOperator *op) bGPdata *gpd = (bGPdata *)ob->data; bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true); bGPDframe *gpf = BKE_gpencil_frame_addnew(gpl, CFRA); - done = BKE_gpencil_from_image(sima, gpf, size, is_mask); + done = BKE_gpencil_from_image(sima, gpd, gpf, size, is_mask); if (done) { /* Delete any selected point. */ LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } BKE_reportf(op->reports, RPT_INFO, "Object created"); diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index 2cee06c36ad..a963632a01b 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -580,7 +580,7 @@ static int gpencil_layer_duplicate_object_exec(bContext *C, wmOperator *op) LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { /* Make copy of source stroke. */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); /* Check if material is in destination object, * otherwise add the slot with the material. */ @@ -1531,6 +1531,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) const int direction = RNA_enum_get(op->ptr, "direction"); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { /* temp listbase to store selected strokes */ ListBase selected = {NULL}; @@ -1589,7 +1590,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) break; /* Bring Forward */ case GP_STROKE_MOVE_UP: - for (LinkData *link = selected.last; link; link = link->prev) { + LISTBASE_FOREACH_BACKWARD (LinkData *, link, &selected) { gps = link->data; BLI_listbase_link_move(&gpf->strokes, gps, 1); } @@ -1603,7 +1604,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) break; /* Send to Back */ case GP_STROKE_MOVE_BOTTOM: - for (LinkData *link = selected.last; link; link = link->prev) { + LISTBASE_FOREACH_BACKWARD (LinkData *, link, &selected) { gps = link->data; BLI_remlink(&gpf->strokes, gps); BLI_addhead(&gpf->strokes, gps); @@ -1612,6 +1613,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) default: BLI_assert(0); break; + changed = true; } } BLI_freelistN(&selected); @@ -1625,9 +1627,11 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) } 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); + if (changed) { + /* 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; } @@ -1693,6 +1697,7 @@ static int gpencil_stroke_change_color_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; /* loop all strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -1717,6 +1722,8 @@ static int gpencil_stroke_change_color_exec(bContext *C, wmOperator *op) /* assign new color */ gps->mat_nr = idx; + + changed = true; } } } @@ -1728,9 +1735,11 @@ static int gpencil_stroke_change_color_exec(bContext *C, wmOperator *op) } 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); + if (changed) { + /* 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; } @@ -1757,9 +1766,7 @@ void GPENCIL_OT_stroke_change_color(wmOperatorType *ot) static int gpencil_material_lock_unsused_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); - Object *ob = CTX_data_active_object(C); - short *totcol = BKE_object_material_len_p(ob); /* sanity checks */ @@ -1776,6 +1783,7 @@ static int gpencil_material_lock_unsused_exec(bContext *C, wmOperator *UNUSED(op } } + bool changed = false; /* loop all selected strokes and unlock any color */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ @@ -1793,19 +1801,24 @@ static int gpencil_material_lock_unsused_exec(bContext *C, wmOperator *UNUSED(op tmp_ma->gp_style->flag &= ~GP_MATERIAL_LOCKED; DEG_id_tag_update(&tmp_ma->id, ID_RECALC_COPY_ON_WRITE); } + + changed = true; } } } } - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* 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); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + + /* 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; } @@ -3476,7 +3489,6 @@ static int gpencil_set_active_material_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); - bool changed = false; /* Sanity checks. */ if (gpd == NULL) { @@ -3484,6 +3496,7 @@ static int gpencil_set_active_material_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; /* Loop all selected strokes. */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 4ba75bcd604..f2c3804a067 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -52,6 +52,7 @@ #include "BKE_context.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "BKE_layer.h" #include "BKE_lib_id.h" @@ -145,6 +146,18 @@ static bool gpencil_editmode_toggle_poll(bContext *C) return ED_gpencil_data_get_active(C) != NULL; } +static bool gpencil_stroke_not_in_curve_edit_mode(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + bGPdata *gpd = (bGPdata *)ob->data; + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + + return (gpl != NULL && !GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -195,6 +208,22 @@ static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *op) ob->mode = mode; } + /* Recalculate editcurves for strokes where the geometry/vertex colors have changed */ + if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_NEEDS_STROKE_UPDATE) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + /* Update the selection from the stroke to the curve. */ + BKE_gpencil_editcurve_stroke_sync_selection(gps, gps->editcurve); + + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + GP_EDITABLE_CURVES_END(gps_iter); + } + /* setup other modes */ ED_gpencil_setup_modes(C, gpd, mode); /* set cache as dirty */ @@ -813,7 +842,8 @@ void GPENCIL_OT_selection_opacity_toggle(wmOperatorType *ot) * \{ */ /* Make copies of selected point segments in a selected stroke */ -static void gpencil_duplicate_points(const bGPDstroke *gps, +static void gpencil_duplicate_points(bGPdata *gpd, + const bGPDstroke *gps, ListBase *new_strokes, const char *layername) { @@ -853,7 +883,7 @@ static void gpencil_duplicate_points(const bGPDstroke *gps, bGPDstroke *gpsd; /* make a stupid copy first of the entire stroke (to get the flags too) */ - gpsd = BKE_gpencil_stroke_duplicate((bGPDstroke *)gps, false); + gpsd = BKE_gpencil_stroke_duplicate((bGPDstroke *)gps, false, true); /* saves original layer name */ BLI_strncpy(gpsd->runtime.tmp_layerinfo, layername, sizeof(gpsd->runtime.tmp_layerinfo)); @@ -877,7 +907,7 @@ static void gpencil_duplicate_points(const bGPDstroke *gps, } } - BKE_gpencil_stroke_geometry_update(gpsd); + BKE_gpencil_stroke_geometry_update(gpd, gpsd); /* add to temp buffer */ gpsd->next = gpsd->prev = NULL; @@ -894,6 +924,7 @@ static void gpencil_duplicate_points(const bGPDstroke *gps, static int gpencil_duplicate_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -905,69 +936,80 @@ static int gpencil_duplicate_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* for each visible (and editable) layer's selected strokes, - * copy the strokes into a temporary buffer, then append - * once all done - */ - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - ListBase new_strokes = {NULL, NULL}; - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps; - - if (gpf == NULL) { - continue; - } + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* for each visible (and editable) layer's selected strokes, + * copy the strokes into a temporary buffer, then append + * once all done + */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + ListBase new_strokes = {NULL, NULL}; + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps; - /* make copies of selected strokes, and deselect these once we're done */ - for (gps = gpf->strokes.first; gps; gps = gps->next) { - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { + if (gpf == NULL) { continue; } - if (gps->flag & GP_STROKE_SELECT) { - if (gps->totpoints == 1) { - /* Special Case: If there's just a single point in this stroke... */ - bGPDstroke *gpsd; + /* make copies of selected strokes, and deselect these once we're done */ + for (gps = gpf->strokes.first; gps; gps = gps->next) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } - /* make direct copies of the stroke and its points */ - gpsd = BKE_gpencil_stroke_duplicate(gps, true); + if (gps->flag & GP_STROKE_SELECT) { + if (gps->totpoints == 1) { + /* Special Case: If there's just a single point in this stroke... */ + bGPDstroke *gpsd; - BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); + /* make direct copies of the stroke and its points */ + gpsd = BKE_gpencil_stroke_duplicate(gps, true, true); - /* Initialize triangle information. */ - BKE_gpencil_stroke_geometry_update(gpsd); + BLI_strncpy( + gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); - /* add to temp buffer */ - gpsd->next = gpsd->prev = NULL; - BLI_addtail(&new_strokes, gpsd); - } - else { - /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ - gpencil_duplicate_points(gps, &new_strokes, gpl->info); - } + /* Initialize triangle information. */ + BKE_gpencil_stroke_geometry_update(gpd, gpsd); - /* deselect original stroke, or else the originals get moved too - * (when using the copy + move macro) - */ - bGPDspoint *pt; - int i; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + /* add to temp buffer */ + gpsd->next = gpsd->prev = NULL; + BLI_addtail(&new_strokes, gpsd); + } + else { + /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ + gpencil_duplicate_points(gpd, gps, &new_strokes, gpl->info); + } + + /* deselect original stroke, or else the originals get moved too + * (when using the copy + move macro) + */ + bGPDspoint *pt; + int i; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } + gps->flag &= ~GP_STROKE_SELECT; + + changed = true; } - gps->flag &= ~GP_STROKE_SELECT; } - } - /* add all new strokes in temp buffer to the frame (preventing double-copies) */ - BLI_movelisttolist(&gpf->strokes, &new_strokes); - BLI_assert(new_strokes.first == NULL); + /* add all new strokes in temp buffer to the frame (preventing double-copies) */ + BLI_movelisttolist(&gpf->strokes, &new_strokes); + BLI_assert(new_strokes.first == NULL); + } + CTX_DATA_END; } - CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + /* updates */ + 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; } @@ -1028,7 +1070,7 @@ static void gpencil_copy_move_point(bGPDstroke *gps, } } -static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) +static void gpencil_add_move_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps) { bGPDspoint *temp_points = NULL; MDeformVert *temp_dverts = NULL; @@ -1049,7 +1091,7 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) pt = &gps->points[i]; if (pt->flag == GP_SPOINT_SELECT) { /* duplicate original stroke data */ - bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, false); + bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, false, true); gps_new->prev = gps_new->next = NULL; /* add new points array */ @@ -1067,8 +1109,8 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) gpencil_copy_move_point(gps_new, gps->points, gps->dvert, i, 0, true); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); - BKE_gpencil_stroke_geometry_update(gps_new); + BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps_new); /* deselect orinal point */ pt->flag &= ~GP_SPOINT_SELECT; @@ -1143,7 +1185,7 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); MEM_SAFE_FREE(temp_points); MEM_SAFE_FREE(temp_dverts); @@ -1155,10 +1197,100 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) } } +static void gpencil_curve_extrude_points(bGPdata *gpd, + bGPDframe *gpf, + bGPDstroke *gps, + bGPDcurve *gpc) +{ + const int old_num_points = gpc->tot_curve_points; + const bool first_select = gpc->curve_points[0].flag & GP_CURVE_POINT_SELECT; + bool last_select = gpc->curve_points[old_num_points - 1].flag & GP_CURVE_POINT_SELECT; + + /* iterate over middle points */ + for (int i = 1; i < gpc->tot_curve_points - 1; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + + /* Create new stroke if selected point */ + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, false, false); + gps_new->points = NULL; + gps_new->flag &= ~GP_STROKE_CYCLIC; + gps_new->prev = gps_new->next = NULL; + + gps_new->editcurve = BKE_gpencil_stroke_editcurve_new(2); + bGPDcurve *new_gpc = gps_new->editcurve; + for (int j = 0; j < new_gpc->tot_curve_points; j++) { + bGPDcurve_point *gpc_pt_new = &new_gpc->curve_points[j]; + memcpy(gpc_pt_new, gpc_pt, sizeof(bGPDcurve_point)); + gpc_pt_new->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt_new->bezt); + } + + /* select last point */ + bGPDcurve_point *gpc_pt_last = &new_gpc->curve_points[1]; + gpc_pt_last->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(&gpc_pt_last->bezt, 1); + gps_new->editcurve->flag |= GP_CURVE_SELECT; + + BLI_insertlinkafter(&gpf->strokes, gps, gps_new); + + gps_new->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps_new); + + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + } + + /* Edgcase for single curve point. */ + if (gpc->tot_curve_points == 1) { + last_select = false; + } + + if (first_select || last_select) { + int new_num_points = old_num_points; + + if (first_select) { + new_num_points++; + } + if (last_select) { + new_num_points++; + } + + /* Grow the array */ + gpc->tot_curve_points = new_num_points; + gpc->curve_points = MEM_recallocN(gpc->curve_points, sizeof(bGPDcurve_point) * new_num_points); + + if (first_select) { + /* shift points by one */ + memmove( + &gpc->curve_points[1], &gpc->curve_points[0], sizeof(bGPDcurve_point) * old_num_points); + + bGPDcurve_point *old_first = &gpc->curve_points[1]; + + old_first->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&old_first->bezt); + } + + if (last_select) { + bGPDcurve_point *old_last = &gpc->curve_points[gpc->tot_curve_points - 2]; + bGPDcurve_point *new_last = &gpc->curve_points[gpc->tot_curve_points - 1]; + memcpy(new_last, old_last, sizeof(bGPDcurve_point)); + + old_last->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&old_last->bezt); + } + + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } +} + static int gpencil_extrude_exec(bContext *C, wmOperator *op) { Object *obact = CTX_data_active_object(C); bGPdata *gpd = (bGPdata *)obact->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); bGPDstroke *gps = NULL; @@ -1167,6 +1299,7 @@ static int gpencil_extrude_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -1182,9 +1315,22 @@ static int gpencil_extrude_exec(bContext *C, wmOperator *op) continue; } - if (gps->flag & GP_STROKE_SELECT) { - gpencil_add_move_points(gpf, gps); + if (is_curve_edit) { + if (gps->editcurve == NULL) { + continue; + } + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + gpencil_curve_extrude_points(gpd, gpf, gps, gpc); + } + } + else { + if (gps->flag & GP_STROKE_SELECT) { + gpencil_add_move_points(gpd, gpf, gps); + } } + + changed = true; } /* if not multiedit, exit loop*/ if (!is_multiedit) { @@ -1195,10 +1341,13 @@ static int gpencil_extrude_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); - DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, + ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } return OPERATOR_FINISHED; } @@ -1352,6 +1501,7 @@ static int gpencil_strokes_copy_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -1366,56 +1516,62 @@ static int gpencil_strokes_copy_exec(bContext *C, wmOperator *op) /* clear the buffer first */ ED_gpencil_strokes_copybuf_free(); - /* for each visible (and editable) layer's selected strokes, - * copy the strokes into a temporary buffer, then append - * once all done - */ - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps; - - if (gpf == NULL) { - continue; - } + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* for each visible (and editable) layer's selected strokes, + * copy the strokes into a temporary buffer, then append + * once all done + */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps; - /* make copies of selected strokes, and deselect these once we're done */ - for (gps = gpf->strokes.first; gps; gps = gps->next) { - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { + if (gpf == NULL) { continue; } - if (gps->flag & GP_STROKE_SELECT) { - if (gps->totpoints == 1) { - /* Special Case: If there's just a single point in this stroke... */ - bGPDstroke *gpsd; - - /* make direct copies of the stroke and its points */ - gpsd = BKE_gpencil_stroke_duplicate(gps, false); - - /* saves original layer name */ - BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); - gpsd->points = MEM_dupallocN(gps->points); - if (gps->dvert != NULL) { - gpsd->dvert = MEM_dupallocN(gps->dvert); - BKE_gpencil_stroke_weights_duplicate(gps, gpsd); - } + /* make copies of selected strokes, and deselect these once we're done */ + for (gps = gpf->strokes.first; gps; gps = gps->next) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpsd); + if (gps->flag & GP_STROKE_SELECT) { + if (gps->totpoints == 1) { + /* Special Case: If there's just a single point in this stroke... */ + bGPDstroke *gpsd; + + /* make direct copies of the stroke and its points */ + gpsd = BKE_gpencil_stroke_duplicate(gps, false, true); + + /* saves original layer name */ + BLI_strncpy( + gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); + gpsd->points = MEM_dupallocN(gps->points); + if (gps->dvert != NULL) { + gpsd->dvert = MEM_dupallocN(gps->dvert); + BKE_gpencil_stroke_weights_duplicate(gps, gpsd); + } - /* add to temp buffer */ - gpsd->next = gpsd->prev = NULL; - BLI_addtail(&gpencil_strokes_copypastebuf, gpsd); - } - else { - /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ - gpencil_duplicate_points(gps, &gpencil_strokes_copypastebuf, gpl->info); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gpsd); + + /* add to temp buffer */ + gpsd->next = gpsd->prev = NULL; + BLI_addtail(&gpencil_strokes_copypastebuf, gpsd); + } + else { + /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ + gpencil_duplicate_points(gpd, gps, &gpencil_strokes_copypastebuf, gpl->info); + } } } } + CTX_DATA_END; } - CTX_DATA_END; /* Build up hash of material colors used in these strokes */ if (gpencil_strokes_copypastebuf.first) { @@ -1493,6 +1649,7 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = (bGPdata *)ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); /* only use active for copy merge */ Scene *scene = CTX_data_scene(C); bGPDframe *gpf; @@ -1556,46 +1713,52 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) /* Ensure that all the necessary colors exist */ new_colors = gpencil_copybuf_validate_colormap(C); - /* Copy over the strokes from the buffer (and adjust the colors) */ - bGPDstroke *gps_init = (!on_back) ? gpencil_strokes_copypastebuf.first : - gpencil_strokes_copypastebuf.last; - for (bGPDstroke *gps = gps_init; gps; gps = (!on_back) ? gps->next : gps->prev) { - if (ED_gpencil_stroke_can_use(C, gps)) { - /* Need to verify if layer exists */ - if (type != GP_COPY_TO_ACTIVE) { - gpl = BLI_findstring(&gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info)); - if (gpl == NULL) { - /* no layer - use active (only if layer deleted before paste) */ - gpl = BKE_gpencil_layer_active_get(gpd); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* Copy over the strokes from the buffer (and adjust the colors) */ + bGPDstroke *gps_init = (!on_back) ? gpencil_strokes_copypastebuf.first : + gpencil_strokes_copypastebuf.last; + for (bGPDstroke *gps = gps_init; gps; gps = (!on_back) ? gps->next : gps->prev) { + if (ED_gpencil_stroke_can_use(C, gps)) { + /* Need to verify if layer exists */ + if (type != GP_COPY_TO_ACTIVE) { + gpl = BLI_findstring( + &gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info)); + if (gpl == NULL) { + /* no layer - use active (only if layer deleted before paste) */ + gpl = BKE_gpencil_layer_active_get(gpd); + } } - } - /* Ensure we have a frame to draw into - * NOTE: Since this is an op which creates strokes, - * we are obliged to add a new frame if one - * doesn't exist already - */ - gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); - if (gpf) { - /* Create new stroke */ - bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true); - new_stroke->runtime.tmp_layerinfo[0] = '\0'; - new_stroke->next = new_stroke->prev = NULL; + /* Ensure we have a frame to draw into + * NOTE: Since this is an op which creates strokes, + * we are obliged to add a new frame if one + * doesn't exist already + */ + gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); + if (gpf) { + /* Create new stroke */ + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true, true); + new_stroke->runtime.tmp_layerinfo[0] = '\0'; + new_stroke->next = new_stroke->prev = NULL; - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(new_stroke); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); - if (on_back) { - BLI_addhead(&gpf->strokes, new_stroke); - } - else { - BLI_addtail(&gpf->strokes, new_stroke); - } + if (on_back) { + BLI_addhead(&gpf->strokes, new_stroke); + } + else { + BLI_addtail(&gpf->strokes, new_stroke); + } - /* Remap material */ - Material *ma = BLI_ghash_lookup(new_colors, POINTER_FROM_INT(new_stroke->mat_nr)); - new_stroke->mat_nr = BKE_gpencil_object_material_index_get(ob, ma); - CLAMP_MIN(new_stroke->mat_nr, 0); + /* Remap material */ + Material *ma = BLI_ghash_lookup(new_colors, POINTER_FROM_INT(new_stroke->mat_nr)); + new_stroke->mat_nr = BKE_gpencil_object_material_index_get(ob, ma); + CLAMP_MIN(new_stroke->mat_nr, 0); + } } } } @@ -2018,10 +2181,10 @@ typedef enum eGP_DissolveMode { /* Delete selected strokes */ static int gpencil_delete_selected_strokes(bContext *C) { - bool changed = false; bGPdata *gpd = ED_gpencil_data_get_active(C); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -2042,16 +2205,9 @@ static int gpencil_delete_selected_strokes(bContext *C) /* free stroke if selected */ if (gps->flag & GP_STROKE_SELECT) { + BLI_remlink(&gpf->strokes, gps); /* free stroke memory arrays, then stroke itself */ - if (gps->points) { - MEM_freeN(gps->points); - } - if (gps->dvert) { - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->dvert); - } - MEM_SAFE_FREE(gps->triangles); - BLI_freelinkN(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); changed = true; } @@ -2071,11 +2227,144 @@ static int gpencil_delete_selected_strokes(bContext *C) /* ----------------------------------- */ -/* Delete selected points but keep the stroke */ -static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) +static bool gpencil_dissolve_selected_curve_points(bContext *C, + bGPdata *gpd, + eGP_DissolveMode mode) +{ + bool changed = false; + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_SELECT) { + int first = 0, last = 0; + int num_points_remaining = gpc->tot_curve_points; + + switch (mode) { + case GP_DISSOLVE_POINTS: + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + num_points_remaining--; + } + } + break; + case GP_DISSOLVE_BETWEEN: + first = -1; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + if (first < 0) { + first = i; + } + last = i; + } + } + + for (int i = first + 1; i < last; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + num_points_remaining--; + } + } + break; + case GP_DISSOLVE_UNSELECT: + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + num_points_remaining--; + } + } + break; + default: + return false; + break; + } + + if (num_points_remaining < 1) { + /* Delete stroke */ + BLI_remlink(&gpf_->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + else { + bGPDcurve_point *new_points = MEM_callocN(sizeof(bGPDcurve_point) * num_points_remaining, + __func__); + + int idx = 0; + switch (mode) { + case GP_DISSOLVE_POINTS: + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + *new_cpt = *cpt; + idx++; + } + } + break; + case GP_DISSOLVE_BETWEEN: + for (int i = 0; i < first; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + + *new_cpt = *cpt; + idx++; + } + + for (int i = first; i < last; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + *new_cpt = *cpt; + idx++; + } + } + + for (int i = last; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + + *new_cpt = *cpt; + idx++; + } + break; + case GP_DISSOLVE_UNSELECT: + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + *new_cpt = *cpt; + idx++; + } + } + break; + default: + return false; + break; + } + + if (gpc->curve_points != NULL) { + MEM_freeN(gpc->curve_points); + } + + gpc->curve_points = new_points; + gpc->tot_curve_points = num_points_remaining; + + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + changed = true; + } + } + GP_EDITABLE_CURVES_END(gps_iter); + + return changed; +} + +static bool gpencil_dissolve_selected_stroke_points(bContext *C, + bGPdata *gpd, + eGP_DissolveMode mode) { - Object *ob = CTX_data_active_object(C); - bGPdata *gpd = (bGPdata *)ob->data; bool changed = false; int first = 0; int last = 0; @@ -2135,18 +2424,8 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) /* if no points are left, we simply delete the entire stroke */ if (tot <= 0) { /* remove the entire stroke */ - if (gps->points) { - MEM_freeN(gps->points); - } - if (gps->dvert) { - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->dvert); - } - if (gps->triangles) { - MEM_freeN(gps->triangles); - } - BLI_freelinkN(&gpf_->strokes, gps); - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + BLI_remlink(&gpf_->strokes, gps); + BKE_gpencil_free_stroke(gps); } else { /* just copy all points to keep into a smaller buffer */ @@ -2263,7 +2542,7 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) gps->totpoints = tot; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* deselect the stroke, since none of its selected points will still be selected */ gps->flag &= ~GP_STROKE_SELECT; @@ -2277,6 +2556,24 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) } GP_EDITABLE_STROKES_END(gpstroke_iter); + return changed; +} + +/* Delete selected points but keep the stroke */ +static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; + + if (is_curve_edit) { + changed = gpencil_dissolve_selected_curve_points(C, gpd, mode); + } + else { + changed = gpencil_dissolve_selected_stroke_points(C, gpd, mode); + } + if (changed) { DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -2289,14 +2586,15 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) /* Temp data for storing information about an "island" of points * that should be kept when splitting up a stroke. Used in: - * gp_stroke_delete_tagged_points() + * gpencil_stroke_delete_tagged_points() */ typedef struct tGPDeleteIsland { int start_idx; int end_idx; } tGPDeleteIsland; -static void gpencil_stroke_join_islands(bGPDframe *gpf, +static void gpencil_stroke_join_islands(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps_first, bGPDstroke *gps_last) { @@ -2305,7 +2603,7 @@ static void gpencil_stroke_join_islands(bGPDframe *gpf, const int totpoints = gps_first->totpoints + gps_last->totpoints; /* create new stroke */ - bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false); + bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false, true); join_stroke->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__); join_stroke->totpoints = totpoints; @@ -2373,7 +2671,7 @@ static void gpencil_stroke_join_islands(bGPDframe *gpf, /* add new stroke at head */ BLI_addhead(&gpf->strokes, join_stroke); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(join_stroke); + BKE_gpencil_stroke_geometry_update(gpd, join_stroke); /* remove first stroke */ BLI_remlink(&gpf->strokes, gps_first); @@ -2398,7 +2696,8 @@ static void gpencil_stroke_join_islands(bGPDframe *gpf, * 2) Each island gets converted to a new stroke * If the number of points is <= limit, the stroke is deleted */ -void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, +void gpencil_stroke_delete_tagged_points(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags, @@ -2450,7 +2749,7 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, /* Create each new stroke... */ for (idx = 0; idx < num_islands; idx++) { tGPDeleteIsland *island = &islands[idx]; - new_stroke = BKE_gpencil_stroke_duplicate(gps, false); + new_stroke = BKE_gpencil_stroke_duplicate(gps, false, true); /* if cyclic and first stroke, save to join later */ if ((is_cyclic) && (gps_first == NULL)) { @@ -2521,7 +2820,7 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, } else { /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); if (next_stroke) { BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); @@ -2533,7 +2832,7 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, } /* if cyclic, need to join last stroke with first stroke */ if ((is_cyclic) && (gps_first != NULL) && (gps_first != new_stroke)) { - gpencil_stroke_join_islands(gpf, gps_first, new_stroke); + gpencil_stroke_join_islands(gpd, gpf, gps_first, new_stroke); } } @@ -2545,11 +2844,118 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, BKE_gpencil_free_stroke(gps); } +static void gpencil_curve_delete_tagged_points(bGPdata *gpd, + bGPDframe *gpf, + bGPDstroke *gps, + bGPDstroke *next_stroke, + bGPDcurve *gpc, + int tag_flags) +{ + if (gpc == NULL) { + return; + } + const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + const int idx_last = gpc->tot_curve_points - 1; + bGPDstroke *gps_first = NULL; + bGPDstroke *gps_last = NULL; + + int idx_start = 0; + int idx_end = 0; + bool prev_selected = gpc->curve_points[0].flag & tag_flags; + for (int i = 1; i < gpc->tot_curve_points; i++) { + bool selected = gpc->curve_points[i].flag & tag_flags; + if (prev_selected == true && selected == false) { + idx_start = i; + } + /* Island ends if the current point is selected or if we reached the end of the stroke */ + if ((prev_selected == false && selected == true) || (selected == false && i == idx_last)) { + + idx_end = selected ? i - 1 : i; + int island_length = idx_end - idx_start + 1; + + /* If an island has only a single curve point, there is no curve segment, so skip island */ + if (island_length == 1) { + if (is_cyclic) { + if (idx_start > 0 && idx_end < idx_last) { + prev_selected = selected; + continue; + } + } + else { + prev_selected = selected; + continue; + } + } + + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, false, false); + new_stroke->points = NULL; + new_stroke->flag &= ~GP_STROKE_CYCLIC; + new_stroke->editcurve = BKE_gpencil_stroke_editcurve_new(island_length); + + if (gps_first == NULL) { + gps_first = new_stroke; + } + + bGPDcurve *new_gpc = new_stroke->editcurve; + memcpy(new_gpc->curve_points, + gpc->curve_points + idx_start, + sizeof(bGPDcurve_point) * island_length); + + BKE_gpencil_editcurve_recalculate_handles(new_stroke); + new_stroke->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + + if (next_stroke) { + BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); + } + else { + BLI_addtail(&gpf->strokes, new_stroke); + } + + gps_last = new_stroke; + } + prev_selected = selected; + } + + /* join first and last stroke if cyclic */ + if (is_cyclic && gps_first != NULL && gps_last != NULL && gps_first != gps_last) { + bGPDcurve *gpc_first = gps_first->editcurve; + bGPDcurve *gpc_last = gps_last->editcurve; + int first_tot_points = gpc_first->tot_curve_points; + int old_tot_points = gpc_last->tot_curve_points; + + gpc_last->tot_curve_points = first_tot_points + old_tot_points; + gpc_last->curve_points = MEM_recallocN(gpc_last->curve_points, + sizeof(bGPDcurve_point) * gpc_last->tot_curve_points); + /* copy data from first to last */ + memcpy(gpc_last->curve_points + old_tot_points, + gpc_first->curve_points, + sizeof(bGPDcurve_point) * first_tot_points); + + BKE_gpencil_editcurve_recalculate_handles(gps_last); + gps_last->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps_last); + + /* remove first one */ + BLI_remlink(&gpf->strokes, gps_first); + BKE_gpencil_free_stroke(gps_first); + } + + /* Delete the old stroke */ + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); +} + /* Split selected strokes into segments, splitting on selected points */ static int gpencil_delete_selected_points(bContext *C) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); bool changed = false; @@ -2579,8 +2985,16 @@ static int gpencil_delete_selected_points(bContext *C) /* deselect old stroke, since it will be used as template for the new strokes */ gps->flag &= ~GP_STROKE_SELECT; - /* delete unwanted points by splitting stroke into several smaller ones */ - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + if (is_curve_edit) { + bGPDcurve *gpc = gps->editcurve; + gpencil_curve_delete_tagged_points( + gpd, gpf, gps, gps->next, gpc, GP_CURVE_POINT_SELECT); + } + else { + /* delete unwanted points by splitting stroke into several smaller ones */ + gpencil_stroke_delete_tagged_points( + gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + } changed = true; } @@ -2743,7 +3157,9 @@ static int gpencil_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *obact = CTX_data_active_object(C); const float gridf = ED_view3d_grid_view_scale(scene, v3d, region, NULL); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { @@ -2754,9 +3170,6 @@ static int gpencil_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - bGPDspoint *pt; - int i; - /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; @@ -2766,30 +3179,82 @@ static int gpencil_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) continue; } - /* TODO: if entire stroke is selected, offset entire stroke by same amount? */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - /* only if point is selected */ - if (pt->flag & GP_SPOINT_SELECT) { - /* apply parent transformations */ - float fpt[3]; - mul_v3_m4v3(fpt, diff_mat, &pt->x); + if (is_curve_edit) { + if (gps->editcurve == NULL) { + continue; + } + float inv_diff_mat[4][4]; + invert_m4_m4_safe(inv_diff_mat, diff_mat); + + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + float tmp0[3], tmp1[3], tmp2[3], offset[3]; + mul_v3_m4v3(tmp0, diff_mat, bezt->vec[0]); + mul_v3_m4v3(tmp1, diff_mat, bezt->vec[1]); + mul_v3_m4v3(tmp2, diff_mat, bezt->vec[2]); + + /* calculate the offset vector */ + offset[0] = gridf * floorf(0.5f + tmp1[0] / gridf) - tmp1[0]; + offset[1] = gridf * floorf(0.5f + tmp1[1] / gridf) - tmp1[1]; + offset[2] = gridf * floorf(0.5f + tmp1[2] / gridf) - tmp1[2]; + + /* shift bezTriple */ + add_v3_v3(bezt->vec[0], offset); + add_v3_v3(bezt->vec[1], offset); + add_v3_v3(bezt->vec[2], offset); + + mul_v3_m4v3(tmp0, inv_diff_mat, bezt->vec[0]); + mul_v3_m4v3(tmp1, inv_diff_mat, bezt->vec[1]); + mul_v3_m4v3(tmp2, inv_diff_mat, bezt->vec[2]); + copy_v3_v3(bezt->vec[0], tmp0); + copy_v3_v3(bezt->vec[1], tmp1); + copy_v3_v3(bezt->vec[2], tmp2); + + changed = true; + } + } + + if (changed) { + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + else { + /* TODO: if entire stroke is selected, offset entire stroke by same amount? */ + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + /* only if point is selected */ + if (pt->flag & GP_SPOINT_SELECT) { + /* apply parent transformations */ + float fpt[3]; + mul_v3_m4v3(fpt, diff_mat, &pt->x); + + fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); + fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); + fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); - fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); - fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); - fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); + /* return data */ + copy_v3_v3(&pt->x, fpt); + gpencil_apply_parent_point(depsgraph, obact, gpl, pt); - /* return data */ - copy_v3_v3(&pt->x, fpt); - gpencil_apply_parent_point(depsgraph, obact, gpl, pt); + changed = true; + } } } } } } - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + return OPERATOR_FINISHED; } @@ -2817,7 +3282,7 @@ void GPENCIL_OT_snap_to_grid(wmOperatorType *ot) static int gpencil_snap_to_cursor(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); - + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *obact = CTX_data_active_object(C); @@ -2825,50 +3290,60 @@ static int gpencil_snap_to_cursor(bContext *C, wmOperator *op) const bool use_offset = RNA_boolean_get(op->ptr, "use_offset"); const float *cursor_global = scene->cursor.location; - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - /* only editable and visible layers are considered */ - if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { - bGPDframe *gpf = gpl->actframe; - float diff_mat[4][4]; + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* only editable and visible layers are considered */ + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + bGPDframe *gpf = gpl->actframe; + float diff_mat[4][4]; - /* calculate difference matrix */ - BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); + /* calculate difference matrix */ + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - bGPDspoint *pt; - int i; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + bGPDspoint *pt; + int i; - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { - continue; - } - /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { - continue; - } - /* only continue if this stroke is selected (editable doesn't guarantee this)... */ - if ((gps->flag & GP_STROKE_SELECT) == 0) { - continue; - } + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { + continue; + } + /* only continue if this stroke is selected (editable doesn't guarantee this)... */ + if ((gps->flag & GP_STROKE_SELECT) == 0) { + continue; + } - if (use_offset) { - float offset[3]; + if (use_offset) { + float offset[3]; - /* compute offset from first point of stroke to cursor */ - /* TODO: Allow using midpoint instead? */ - sub_v3_v3v3(offset, cursor_global, &gps->points->x); + /* compute offset from first point of stroke to cursor */ + /* TODO: Allow using midpoint instead? */ + sub_v3_v3v3(offset, cursor_global, &gps->points->x); - /* apply offset to all points in the stroke */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - add_v3_v3(&pt->x, offset); + /* apply offset to all points in the stroke */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + add_v3_v3(&pt->x, offset); + } + + changed = true; } - } - else { - /* affect each selected point */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - copy_v3_v3(&pt->x, cursor_global); - gpencil_apply_parent_point(depsgraph, obact, gpl, pt); + else { + /* affect each selected point */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + copy_v3_v3(&pt->x, cursor_global); + gpencil_apply_parent_point(depsgraph, obact, gpl, pt); + + changed = true; + } } } } @@ -2876,9 +3351,12 @@ static int gpencil_snap_to_cursor(bContext *C, wmOperator *op) } } - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + return OPERATOR_FINISHED; } @@ -2910,23 +3388,16 @@ void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot) /** \name Snapping Cursor to Selection Operator * \{ */ -static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) +static bool gpencil_stroke_points_centroid(Depsgraph *depsgraph, + bContext *C, + Object *obact, + bGPdata *gpd, + float r_centroid[3], + float r_min[3], + float r_max[3], + size_t *count) { - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Object *obact = CTX_data_active_object(C); - Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); - bGPdata *gpd = (bGPdata *)ob_eval->data; - - Scene *scene = CTX_data_scene(C); - View3D *v3d = CTX_wm_view3d(C); - - float *cursor = scene->cursor.location; - float centroid[3] = {0.0f}; - float min[3], max[3]; - size_t count = 0; - - INIT_MINMAX(min, max); - + bool changed = false; /* calculate midpoints from selected points */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ @@ -2960,29 +3431,61 @@ static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) float fpt[3]; mul_v3_m4v3(fpt, diff_mat, &pt->x); - add_v3_v3(centroid, fpt); - minmax_v3v3_v3(min, max, fpt); + add_v3_v3(r_centroid, fpt); + minmax_v3v3_v3(r_min, r_max, fpt); - count++; + (*count)++; } } + + changed = true; } } } - if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_BOUNDS) { - mid_v3_v3v3(cursor, min, max); + return changed; +} + +static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *op) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *obact = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + + float *cursor = scene->cursor.location; + float centroid[3] = {0.0f}; + float min[3], max[3]; + size_t count = 0; + + INIT_MINMAX(min, max); + + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); } - else { /* #V3D_AROUND_CENTER_MEDIAN. */ - zero_v3(cursor); - if (count) { - mul_v3_fl(centroid, 1.0f / (float)count); - copy_v3_v3(cursor, centroid); - } + else { + changed = gpencil_stroke_points_centroid(depsgraph, C, obact, gpd, centroid, min, max, &count); } - DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); + if (changed) { + if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_BOUNDS) { + mid_v3_v3v3(cursor, min, max); + } + else { /* #V3D_AROUND_CENTER_MEDIAN. */ + zero_v3(cursor); + if (count) { + mul_v3_fl(centroid, 1.0f / (float)count); + copy_v3_v3(cursor, centroid); + } + } + + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); + } return OPERATOR_FINISHED; } @@ -3030,6 +3533,7 @@ static int gpencil_stroke_apply_thickness_exec(bContext *C, wmOperator *UNUSED(o } } } + /* clear value */ gpl->thickness = 0.0f; gpl->line_change = 0; @@ -3073,6 +3577,7 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) const int type = RNA_enum_get(op->ptr, "type"); const bool geometry = RNA_boolean_get(op->ptr, "geometry"); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); bGPDstroke *gps = NULL; /* sanity checks */ @@ -3080,6 +3585,7 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; /* loop all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -3103,6 +3609,7 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) continue; } + bool before = (bool)(gps->flag & GP_STROKE_CYCLIC); switch (type) { case GP_STROKE_CYCLIC_CLOSE: /* Close all (enable) */ @@ -3121,9 +3628,19 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) break; } - /* Create new geometry. */ - if ((gps->flag & GP_STROKE_CYCLIC) && (geometry)) { - BKE_gpencil_stroke_close(gps); + if (before != (gps->flag & GP_STROKE_CYCLIC)) { + /* Create new geometry. */ + if (is_curve_edit) { + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + else if ((gps->flag & GP_STROKE_CYCLIC) && geometry) { + BKE_gpencil_stroke_close(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + changed = true; } } @@ -3136,13 +3653,31 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) } 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); + if (changed) { + /* 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; } +static bool gpencil_cyclical_set_curve_edit_poll_property(const bContext *C, + wmOperator *UNUSED(op), + const PropertyRNA *prop) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + if (gpd != NULL && GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + const char *prop_id = RNA_property_identifier(prop); + /* Only show type in curve edit mode */ + if (!STREQ(prop_id, "type")) { + return false; + } + } + + return true; +} + /** * Similar to #CURVE_OT_cyclic_toggle or #MASK_OT_cyclic_toggle, but with * option to force opened/closed strokes instead of just toggle behavior. @@ -3166,6 +3701,7 @@ void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_stroke_cyclical_set_exec; ot->poll = gpencil_active_layer_poll; + ot->poll_property = gpencil_cyclical_set_curve_edit_poll_property; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -3194,7 +3730,6 @@ static int gpencil_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 */ @@ -3202,6 +3737,7 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; /* loop all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { if (gpl->actframe == NULL) { @@ -3221,6 +3757,9 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) continue; } + short prev_first = gps->caps[0]; + short prev_last = gps->caps[1]; + if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_START)) { ++gps->caps[0]; if (gps->caps[0] >= GP_STROKE_CAP_MAX) { @@ -3237,13 +3776,19 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) gps->caps[0] = GP_STROKE_CAP_ROUND; gps->caps[1] = GP_STROKE_CAP_ROUND; } + + if (prev_first != gps->caps[0] || prev_last != gps->caps[1]) { + changed = true; + } } } 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); + if (changed) { + /* 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; } @@ -3539,6 +4084,11 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + if (is_curve_edit) { + return OPERATOR_CANCELLED; + } + if (activegpl->flag & GP_LAYER_LOCKED) { return OPERATOR_CANCELLED; } @@ -3597,7 +4147,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) elem->used = true; /* Create a new stroke. */ - bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(elem->gps, true); + bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(elem->gps, true, true); gps_new->flag &= ~GP_STROKE_CYCLIC; BLI_insertlinkbefore(&elem->gpf->strokes, elem->gps, gps_new); @@ -3614,7 +4164,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) } /* Calc geometry data for new stroke. */ - BKE_gpencil_stroke_geometry_update(gps_new); + BKE_gpencil_stroke_geometry_update(gpd, gps_new); /* If join only, delete old strokes. */ if (type == GP_STROKE_JOIN) { @@ -3670,7 +4220,7 @@ void GPENCIL_OT_stroke_join(wmOperatorType *ot) /** \name Stroke Flip Operator * \{ */ -static int gpencil_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) +static int gpencil_stroke_flip_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); Object *ob = CTX_data_active_object(C); @@ -3680,6 +4230,8 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; /* read all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *gpf = gpl->actframe; @@ -3698,16 +4250,25 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) continue; } - /* flip stroke */ - gpencil_flip_stroke(gps); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* flip stroke */ + gpencil_flip_stroke(gps); + } + + changed = true; } } } 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); + if (changed) { + /* 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; } @@ -3742,19 +4303,26 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) int oldframe = (int)DEG_get_ctime(depsgraph); const eGP_ReprojectModes mode = RNA_enum_get(op->ptr, "type"); const bool keep_original = RNA_boolean_get(op->ptr, "keep_original"); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); - /* Init space conversion stuff. */ - GP_SpaceConversion gsc = {NULL}; - SnapObjectContext *sctx = NULL; - gpencil_point_conversion_init(C, &gsc); /* Init snap context for geometry projection. */ + SnapObjectContext *sctx = NULL; sctx = ED_transform_snap_object_context_create_view3d(scene, 0, region, CTX_wm_view3d(C)); + bool changed = false; + /* Init space conversion stuff. */ + GP_SpaceConversion gsc = {NULL}; + gpencil_point_conversion_init(C, &gsc); int cfra_prv = INT_MIN; /* Go through each editable + selected stroke, adjusting each of its points one by one... */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { + bool curve_select = false; + if (is_curve_edit && gps->editcurve != NULL) { + curve_select = gps->editcurve->flag & GP_CURVE_SELECT; + } + + if (gps->flag & GP_STROKE_SELECT || curve_select) { /* update frame to get the new location of objects */ if ((mode == GP_REPROJECT_SURFACE) && (cfra_prv != gpf_->framenum)) { @@ -3764,6 +4332,17 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) } ED_gpencil_stroke_reproject(depsgraph, &gsc, sctx, gpl, gpf_, gps, mode, keep_original); + + if (is_curve_edit && gps->editcurve != NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + /* Update the selection from the stroke to the curve. */ + BKE_gpencil_editcurve_stroke_sync_selection(gps, gps->editcurve); + + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + changed = true; } } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -3776,9 +4355,12 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) ED_transform_snap_object_context_destroy(sctx); } - /* update changed data */ - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + /* update changed data */ + 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; } @@ -3846,10 +4428,11 @@ static int gpencil_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op)) LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } + /* update changed data */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -3943,134 +4526,176 @@ static int gpencil_count_subdivision_cuts(bGPDstroke *gps) return totnewpoints; } -static int gpencil_stroke_subdivide_exec(bContext *C, wmOperator *op) +static void gpencil_stroke_subdivide(bGPDstroke *gps, const int cuts) { - bGPdata *gpd = ED_gpencil_data_get_active(C); bGPDspoint *temp_points; - const int cuts = RNA_int_get(op->ptr, "number_cuts"); - int totnewpoints, oldtotpoints; int i2; + /* loop as many times as cuts */ + for (int s = 0; s < cuts; s++) { + totnewpoints = gpencil_count_subdivision_cuts(gps); + if (totnewpoints == 0) { + continue; + } + /* duplicate points in a temp area */ + temp_points = MEM_dupallocN(gps->points); + oldtotpoints = gps->totpoints; - /* sanity checks */ - if (ELEM(NULL, gpd)) { - return OPERATOR_CANCELLED; - } + MDeformVert *temp_dverts = NULL; + MDeformVert *dvert_final = NULL; + MDeformVert *dvert = NULL; + MDeformVert *dvert_next = NULL; + if (gps->dvert != NULL) { + temp_dverts = MEM_dupallocN(gps->dvert); + } - /* Go through each editable + selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { - /* loop as many times as cuts */ - for (int s = 0; s < cuts; s++) { - totnewpoints = gpencil_count_subdivision_cuts(gps); - if (totnewpoints == 0) { - continue; - } - /* duplicate points in a temp area */ - temp_points = MEM_dupallocN(gps->points); - oldtotpoints = gps->totpoints; - - MDeformVert *temp_dverts = NULL; - MDeformVert *dvert_final = NULL; - MDeformVert *dvert = NULL; - MDeformVert *dvert_next = NULL; - if (gps->dvert != NULL) { - temp_dverts = MEM_dupallocN(gps->dvert); - } + /* 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); + } - /* 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); - } + /* loop and interpolate */ + i2 = 0; + for (int i = 0; i < oldtotpoints; i++) { + bGPDspoint *pt = &temp_points[i]; + bGPDspoint *pt_final = &gps->points[i2]; - /* loop and interpolate */ - i2 = 0; - for (int i = 0; i < oldtotpoints; i++) { - bGPDspoint *pt = &temp_points[i]; - bGPDspoint *pt_final = &gps->points[i2]; - - /* copy current point */ - 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; - copy_v4_v4(pt_final->vert_color, pt->vert_color); - - if (gps->dvert != NULL) { - dvert = &temp_dverts[i]; - dvert_final = &gps->dvert[i2]; - dvert_final->totweight = dvert->totweight; - dvert_final->dw = dvert->dw; - } - i2++; + /* copy current point */ + 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; + copy_v4_v4(pt_final->vert_color, pt->vert_color); - /* if next point is selected add a half way point */ - if (pt->flag & GP_SPOINT_SELECT) { - if (i + 1 < oldtotpoints) { - if (temp_points[i + 1].flag & GP_SPOINT_SELECT) { - pt_final = &gps->points[i2]; - if (gps->dvert != NULL) { - dvert_final = &gps->dvert[i2]; - } - /* Interpolate all values */ - bGPDspoint *next = &temp_points[i + 1]; - interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); - pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); - pt_final->strength = interpf(pt->strength, next->strength, 0.5f); - CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); - interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f); - pt_final->time = interpf(pt->time, next->time, 0.5f); - pt_final->flag |= GP_SPOINT_SELECT; - - /* interpolate weights */ - if (gps->dvert != NULL) { - dvert = &temp_dverts[i]; - dvert_next = &temp_dverts[i + 1]; - dvert_final = &gps->dvert[i2]; - - dvert_final->totweight = dvert->totweight; - dvert_final->dw = MEM_dupallocN(dvert->dw); - - /* interpolate weight values */ - for (int d = 0; d < dvert->totweight; d++) { - MDeformWeight *dw_a = &dvert->dw[d]; - if (dvert_next->totweight > d) { - MDeformWeight *dw_b = &dvert_next->dw[d]; - MDeformWeight *dw_final = &dvert_final->dw[d]; - dw_final->weight = interpf(dw_a->weight, dw_b->weight, 0.5f); - } - } - } + if (gps->dvert != NULL) { + dvert = &temp_dverts[i]; + dvert_final = &gps->dvert[i2]; + dvert_final->totweight = dvert->totweight; + dvert_final->dw = dvert->dw; + } + i2++; - i2++; + /* if next point is selected add a half way point */ + if (pt->flag & GP_SPOINT_SELECT) { + if (i + 1 < oldtotpoints) { + if (temp_points[i + 1].flag & GP_SPOINT_SELECT) { + pt_final = &gps->points[i2]; + if (gps->dvert != NULL) { + dvert_final = &gps->dvert[i2]; + } + /* Interpolate all values */ + bGPDspoint *next = &temp_points[i + 1]; + interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); + pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); + pt_final->strength = interpf(pt->strength, next->strength, 0.5f); + CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); + interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f); + pt_final->time = interpf(pt->time, next->time, 0.5f); + pt_final->flag |= GP_SPOINT_SELECT; + + /* interpolate weights */ + if (gps->dvert != NULL) { + dvert = &temp_dverts[i]; + dvert_next = &temp_dverts[i + 1]; + dvert_final = &gps->dvert[i2]; + + dvert_final->totweight = dvert->totweight; + dvert_final->dw = MEM_dupallocN(dvert->dw); + + /* interpolate weight values */ + for (int d = 0; d < dvert->totweight; d++) { + MDeformWeight *dw_a = &dvert->dw[d]; + if (dvert_next->totweight > d) { + MDeformWeight *dw_b = &dvert_next->dw[d]; + MDeformWeight *dw_final = &dvert_final->dw[d]; + dw_final->weight = interpf(dw_a->weight, dw_b->weight, 0.5f); + } } } + + i2++; } } - /* free temp memory */ - MEM_SAFE_FREE(temp_points); - MEM_SAFE_FREE(temp_dverts); } + } + /* free temp memory */ + MEM_SAFE_FREE(temp_points); + MEM_SAFE_FREE(temp_dverts); + } +} - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); +static int gpencil_stroke_subdivide_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + const int cuts = RNA_int_get(op->ptr, "number_cuts"); + + /* sanity checks */ + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + bool changed = false; + if (is_curve_edit) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_SELECT) { + BKE_gpencil_editcurve_subdivide(gps, cuts); + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + changed = true; + } } + GP_EDITABLE_CURVES_END(gps_iter); } - GP_EDITABLE_STROKES_END(gpstroke_iter); + else { + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + gpencil_stroke_subdivide(gps, cuts); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps); + changed = true; + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); - /* smooth stroke */ - gpencil_smooth_stroke(C, op); + if (changed) { + /* smooth stroke */ + gpencil_smooth_stroke(C, op); + } + } - /* 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); + if (changed) { + /* 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; } +static bool gpencil_subdivide_curve_edit_poll_property(const bContext *C, + wmOperator *UNUSED(op), + const PropertyRNA *prop) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + if (gpd != NULL && GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + const char *prop_id = RNA_property_identifier(prop); + /* Only show number_cuts in curve edit mode */ + if (!STREQ(prop_id, "number_cuts")) { + return false; + } + } + + return true; +} + void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) { PropertyRNA *prop; @@ -4085,6 +4710,7 @@ void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_stroke_subdivide_exec; ot->poll = gpencil_active_layer_poll; + ot->poll_property = gpencil_subdivide_curve_edit_poll_property; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -4120,18 +4746,29 @@ static int gpencil_stroke_simplify_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Go through each editable + selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { - /* simplify stroke using Ramer-Douglas-Peucker algorithm */ - BKE_gpencil_stroke_simplify_adaptive(gps, factor); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + /* simplify stroke using Ramer-Douglas-Peucker algorithm */ + BKE_gpencil_stroke_simplify_adaptive(gpd, gps, factor); + changed = true; + } } + GP_EDITABLE_STROKES_END(gpstroke_iter); } - GP_EDITABLE_STROKES_END(gpstroke_iter); - /* 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); + if (changed) { + /* 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; } @@ -4169,19 +4806,30 @@ static int gpencil_stroke_simplify_fixed_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Go through each editable + selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { - for (int i = 0; i < steps; i++) { - BKE_gpencil_stroke_simplify_fixed(gps); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + changed |= true; + for (int i = 0; i < steps; i++) { + BKE_gpencil_stroke_simplify_fixed(gpd, gps); + } } } + GP_EDITABLE_STROKES_END(gpstroke_iter); } - GP_EDITABLE_STROKES_END(gpstroke_iter); - /* 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); + if (changed) { + /* 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; } @@ -4223,7 +4871,7 @@ static int gpencil_stroke_sample_exec(bContext *C, wmOperator *op) /* Go through each editable + selected stroke */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_stroke_sample(gps, length, true); + BKE_gpencil_stroke_sample(gpd, gps, length, true); } } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -4246,7 +4894,7 @@ void GPENCIL_OT_stroke_sample(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_stroke_sample_exec; - ot->poll = gpencil_active_layer_poll; + ot->poll = gpencil_stroke_not_in_curve_edit_mode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -4263,7 +4911,7 @@ void GPENCIL_OT_stroke_sample(wmOperatorType *ot) /** \name Stroke Trim Operator * \{ */ -static int gpencil_stroke_trim_exec(bContext *C, wmOperator *UNUSED(op)) +static int gpencil_stroke_trim_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); @@ -4274,6 +4922,7 @@ static int gpencil_stroke_trim_exec(bContext *C, wmOperator *UNUSED(op)) /* Go through each editable + selected stroke */ const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -4293,7 +4942,12 @@ static int gpencil_stroke_trim_exec(bContext *C, wmOperator *UNUSED(op)) } if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_stroke_trim(gps); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + BKE_gpencil_stroke_trim(gpd, gps); + } } } /* if not multiedit, exit loop*/ @@ -4373,6 +5027,7 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) } const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_src); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd_src); /* Create a new object. */ /* Take into account user preferences for duplicating actions. */ @@ -4434,27 +5089,32 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) /* selected points mode */ if (mode == GP_SEPARATE_POINT) { - /* make copy of source stroke */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* make copy of source stroke */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true, true); - /* Reassign material. */ - gps_dst->mat_nr = idx; + /* Reassign material. */ + gps_dst->mat_nr = idx; - /* link to destination frame */ - BLI_addtail(&gpf_dst->strokes, gps_dst); + /* link to destination frame */ + BLI_addtail(&gpf_dst->strokes, gps_dst); - /* Invert selection status of all points in destination stroke */ - for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { - pt->flag ^= GP_SPOINT_SELECT; - } + /* Invert selection status of all points in destination stroke */ + for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { + pt->flag ^= GP_SPOINT_SELECT; + } - /* delete selected points from destination stroke */ - gpencil_stroke_delete_tagged_points( - gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); + /* delete selected points from destination stroke */ + gpencil_stroke_delete_tagged_points( + gpd_dst, gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); - /* delete selected points from origin stroke */ - gpencil_stroke_delete_tagged_points( - gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + /* delete selected points from origin stroke */ + gpencil_stroke_delete_tagged_points( + gpd_src, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + } } /* selected strokes mode */ else if (mode == GP_SEPARATE_STROKE) { @@ -4574,7 +5234,7 @@ void GPENCIL_OT_stroke_separate(wmOperatorType *ot) /** \name Stroke Split Operator * \{ */ -static int gpencil_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) +static int gpencil_stroke_split_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); @@ -4586,6 +5246,7 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); /* loop strokes and split parts */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { @@ -4610,22 +5271,29 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) } /* Split selected strokes. */ if (gps->flag & GP_STROKE_SELECT) { - /* make copy of source stroke */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* make copy of source stroke */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true, true); - /* link to same frame */ - BLI_addtail(&gpf->strokes, gps_dst); + /* link to same frame */ + BLI_addtail(&gpf->strokes, gps_dst); - /* invert selection status of all points in destination stroke */ - for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { - pt->flag ^= GP_SPOINT_SELECT; - } + /* invert selection status of all points in destination stroke */ + for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { + pt->flag ^= GP_SPOINT_SELECT; + } - /* delete selected points from destination stroke */ - gpencil_stroke_delete_tagged_points(gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0); + /* delete selected points from destination stroke */ + gpencil_stroke_delete_tagged_points( + gpd, gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0); - /* delete selected points from origin stroke */ - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + /* delete selected points from origin stroke */ + gpencil_stroke_delete_tagged_points( + gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + } } } /* select again tagged points */ @@ -4705,7 +5373,7 @@ void GPENCIL_OT_stroke_smooth(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_stroke_smooth_exec; - ot->poll = gpencil_active_layer_poll; + ot->poll = gpencil_stroke_not_in_curve_edit_mode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -4761,7 +5429,8 @@ typedef bool (*GPencilTestFn)(bGPDstroke *gps, const float diff_mat[4][4], void *user_data); -static void gpencil_cutter_dissolve(bGPDlayer *hit_layer, +static void gpencil_cutter_dissolve(bGPdata *gpd, + bGPDlayer *hit_layer, bGPDstroke *hit_stroke, const bool flat_caps) { @@ -4819,7 +5488,7 @@ static void gpencil_cutter_dissolve(bGPDlayer *hit_layer, } gpencil_stroke_delete_tagged_points( - hit_layer->actframe, hit_stroke, gpsn, GP_SPOINT_TAG, false, 1); + gpd, hit_layer->actframe, hit_stroke, gpsn, GP_SPOINT_TAG, false, 1); } } @@ -4877,7 +5546,7 @@ static int gpencil_cutter_lasso_select(bContext *C, 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); + ED_gpencil_select_stroke_segment(gpd, gpl, gps, pt, true, true, scale, r_hita, r_hitb); } /* avoid infinite loops */ if (gps->totpoints > oldtot) { @@ -4907,7 +5576,7 @@ static int gpencil_cutter_lasso_select(bContext *C, } LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_SELECT) { - gpencil_cutter_dissolve(gpl, gps, flat_caps); + gpencil_cutter_dissolve(gpd, gpl, gps, flat_caps); } } } @@ -5039,13 +5708,20 @@ static int gpencil_merge_by_distance_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Go through each editable selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_stroke_merge_distance(gpf_, gps, threshold, unselected); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + if (is_curve_edit) { + /* TODO: merge curve points by distance */ + } + else { + /* Go through each editable selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + BKE_gpencil_stroke_merge_distance(gpd, gpf_, gps, threshold, unselected); + } } + GP_EDITABLE_STROKES_END(gpstroke_iter); } - GP_EDITABLE_STROKES_END(gpstroke_iter); /* notifiers */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_edit_curve.c b/source/blender/editors/gpencil/gpencil_edit_curve.c new file mode 100644 index 00000000000..60d1d2169b4 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_edit_curve.c @@ -0,0 +1,214 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2008, Blender Foundation + * This is a new part of Blender + * Operators for editing Grease Pencil strokes + */ + +/** \file + * \ingroup edgpencil + */ + +#include <math.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_gpencil_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" +#include "BKE_gpencil_geom.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_gpencil.h" + +#include "DEG_depsgraph.h" + +#include "gpencil_intern.h" + +/* Poll callback for checking if there is an active layer and we are in curve edit mode. */ +static bool gpencil_curve_edit_mode_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + bGPdata *gpd = (bGPdata *)ob->data; + if (!GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + return false; + } + + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + return (gpl != NULL); +} + +static int gpencil_stroke_enter_editcurve_mode_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + + float error_threshold = RNA_float_get(op->ptr, "error_threshold"); + gpd->curve_edit_threshold = error_threshold; + + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + if (gpf == gpl->actframe) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* only allow selected and non-converted strokes to be transformed */ + if ((gps->flag & GP_STROKE_SELECT && gps->editcurve == NULL) || + (gps->editcurve != NULL && gps->editcurve->flag & GP_CURVE_NEEDS_STROKE_UPDATE)) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + /* Update the selection from the stroke to the curve. */ + BKE_gpencil_editcurve_stroke_sync_selection(gps, gps->editcurve); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + } + } + } + + gpd->flag |= GP_DATA_CURVE_EDIT_MODE; + + /* 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; +} + +void GPENCIL_OT_stroke_enter_editcurve_mode(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Enter curve edit mode"; + ot->idname = "GPENCIL_OT_stroke_enter_editcurve_mode"; + ot->description = "Called to transform a stroke into a curve"; + + /* api callbacks */ + ot->exec = gpencil_stroke_enter_editcurve_mode_exec; + ot->poll = gpencil_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_float(ot->srna, + "error_threshold", + 0.1f, + FLT_MIN, + 100.0f, + "Error Threshold", + "Threshold on the maximum deviation from the actual stroke", + FLT_MIN, + 10.f); + RNA_def_property_ui_range(prop, FLT_MIN, 10.0f, 0.1f, 5); +} + +static int gpencil_editcurve_set_handle_type_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + const int handle_type = RNA_enum_get(op->ptr, "type"); + + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + BezTriple *bezt = &gpc_pt->bezt; + + if (bezt->f2 & SELECT) { + bezt->h1 = handle_type; + bezt->h2 = handle_type; + } + else { + if (bezt->f1 & SELECT) { + bezt->h1 = handle_type; + } + if (bezt->f3 & SELECT) { + bezt->h2 = handle_type; + } + } + } + } + + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + GP_EDITABLE_CURVES_END(gps_iter); + + /* 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; +} + +void GPENCIL_OT_stroke_editcurve_set_handle_type(wmOperatorType *ot) +{ + static const EnumPropertyItem editcurve_handle_type_items[] = { + {HD_FREE, "FREE", 0, "Free", ""}, + {HD_AUTO, "AUTOMATIC", 0, "Automatic", ""}, + {HD_VECT, "VECTOR", 0, "Vector", ""}, + {HD_ALIGN, "ALIGNED", 0, "Aligned", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Set handle type"; + ot->idname = "GPENCIL_OT_stroke_editcurve_set_handle_type"; + ot->description = "Set the type of a edit curve handle"; + + /* api callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = gpencil_editcurve_set_handle_type_exec; + ot->poll = gpencil_curve_edit_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "type", editcurve_handle_type_items, 1, "Type", "Spline type"); +} + +/** \} */ diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index f06a1d6b6c8..93941ea3766 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -1299,11 +1299,11 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) /* simplify stroke */ for (int b = 0; b < tgpf->fill_simplylvl; b++) { - BKE_gpencil_stroke_simplify_fixed(gps); + BKE_gpencil_stroke_simplify_fixed(tgpf->gpd, gps); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(tgpf->gpd, gps); } /* ----------------------- */ diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index e3e2199f8a3..e4e9a3ae0ab 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -343,7 +343,8 @@ struct GHash *gpencil_copybuf_validate_colormap(struct bContext *C); /* Stroke Editing ------------------------------------ */ -void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, +void gpencil_stroke_delete_tagged_points(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags, @@ -351,7 +352,7 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, int limit); int gpencil_delete_selected_point_wrap(bContext *C); -void gpencil_subdivide_stroke(bGPDstroke *gps, const int subdivide); +void gpencil_subdivide_stroke(bGPdata *gpd, bGPDstroke *gps, const int subdivide); /* Layers Enums -------------------------------------- */ @@ -447,6 +448,11 @@ void GPENCIL_OT_snap_cursor_to_selected(struct wmOperatorType *ot); void GPENCIL_OT_reproject(struct wmOperatorType *ot); void GPENCIL_OT_recalc_geometry(struct wmOperatorType *ot); +/* stroke editcurve */ + +void GPENCIL_OT_stroke_enter_editcurve_mode(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_editcurve_set_handle_type(struct wmOperatorType *ot); + /* stroke sculpting -- */ void GPENCIL_OT_sculpt_paint(struct wmOperatorType *ot); @@ -693,6 +699,55 @@ struct GP_EditableStrokes_Iter { (void)0 /** + * Iterate over all editable editcurves in the current context, + * stopping on each usable layer + stroke + curve pair (i.e. gpl, gps and gpc) + * to perform some operations on the curve. + * + * \param gpl: The identifier to use for the layer of the stroke being processed. + * Choose a suitable value to avoid name clashes. + * \param gps: The identifier to use for current stroke being processed. + * Choose a suitable value to avoid name clashes. + * \param gpc: The identifier to use for current editcurve being processed. + * Choose a suitable value to avoid name clashes. + */ +#define GP_EDITABLE_CURVES_BEGIN(gpstroke_iter, C, gpl, gps, gpc) \ + { \ + struct GP_EditableStrokes_Iter gpstroke_iter = {{{0}}}; \ + Depsgraph *depsgraph_ = CTX_data_ensure_evaluated_depsgraph(C); \ + Object *obact_ = CTX_data_active_object(C); \ + bGPdata *gpd_ = CTX_data_gpencil_data(C); \ + const bool is_multiedit_ = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_); \ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { \ + bGPDframe *init_gpf_ = (is_multiedit_) ? gpl->frames.first : gpl->actframe; \ + for (bGPDframe *gpf_ = init_gpf_; gpf_; gpf_ = gpf_->next) { \ + if ((gpf_ == gpl->actframe) || ((gpf_->flag & GP_FRAME_SELECT) && is_multiedit_)) { \ + BKE_gpencil_parent_matrix_get(depsgraph_, obact_, gpl, gpstroke_iter.diff_mat); \ + invert_m4_m4(gpstroke_iter.inverse_diff_mat, gpstroke_iter.diff_mat); \ + /* loop over strokes */ \ + bGPDstroke *gpsn_; \ + for (bGPDstroke *gps = gpf_->strokes.first; gps; gps = gpsn_) { \ + gpsn_ = gps->next; \ + /* skip strokes that are invalid for current view */ \ + if (ED_gpencil_stroke_can_use(C, gps) == false) \ + continue; \ + if (gps->editcurve == NULL) \ + continue; \ + bGPDcurve *gpc = gps->editcurve; \ + /* ... Do Stuff With Strokes ... */ + +#define GP_EDITABLE_CURVES_END(gpstroke_iter) \ + } \ + } \ + if (!is_multiedit_) { \ + break; \ + } \ + } \ + } \ + CTX_DATA_END; \ + } \ + (void)0 + +/** * Iterate over all editable strokes using evaluated data in the current context, * stopping on each usable layer + stroke pair (i.e. gpl and gps) * to perform some operations on the stroke. diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index 3a3a9bde38b..3617f20763e 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -184,7 +184,7 @@ static void gpencil_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgp /* Add temp strokes. */ if (gpf) { - bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true); + bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true, true); gps_eval->flag |= GP_STROKE_TAG; BLI_addtail(&gpf->strokes, gps_eval); } @@ -327,7 +327,7 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) } /* create new stroke */ - new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true); + new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); if (valid) { /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ @@ -353,7 +353,7 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); /* add to strokes */ BLI_addtail(&tgpil->interFrame->strokes, new_stroke); } @@ -608,11 +608,11 @@ static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent } /* make copy of source stroke, then adjust pointer to points too */ - gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); gps_dst->flag &= ~GP_STROKE_TAG; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps_dst); + BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps_dst); BLI_addtail(&gpf_dst->strokes, gps_dst); } @@ -1050,7 +1050,7 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) } /* create new stroke */ - bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true); + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ if (gps_from->totpoints > gps_to->totpoints) { @@ -1075,7 +1075,7 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); /* add to strokes */ BLI_addtail(&interFrame->strokes, new_stroke); diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c index 938f4ab26af..f795ed01bb8 100644 --- a/source/blender/editors/gpencil/gpencil_merge.c +++ b/source/blender/editors/gpencil/gpencil_merge.c @@ -172,6 +172,9 @@ static void gpencil_get_elements_len(bContext *C, int *totstrokes, int *totpoint static void gpencil_dissolve_points(bContext *C) { + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *gpf = gpl->actframe; if (gpf == NULL) { @@ -179,7 +182,7 @@ static void gpencil_dissolve_points(bContext *C) } LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } } CTX_DATA_END; @@ -519,7 +522,7 @@ static int gpencil_stroke_merge_exec(bContext *C, wmOperator *op) gpencil_dissolve_points(C); } - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* free memory */ MEM_SAFE_FREE(original_array); diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 9f7725e01f5..33ac0c5bbbf 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -69,6 +69,13 @@ static bool gpencil_stroke_editmode_poll(bContext *C) return (gpd && (gpd->flag & GP_DATA_STROKE_EDITMODE)); } +/* Poll callback for stroke curve editing mode */ +static bool gpencil_stroke_editmode_curve_poll(bContext *C) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + return (GPENCIL_EDIT_MODE(gpd) && GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)); +} + /* Poll callback for stroke painting mode */ static bool gpencil_stroke_paintmode_poll(bContext *C) { @@ -315,6 +322,15 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) keymap->poll = gpencil_stroke_editmode_poll; } +/* Stroke Curve Editing Keymap - Only when editmode is enabled and in curve edit mode */ +static void ed_keymap_gpencil_curve_editing(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Curve Edit Mode", 0, 0); + + /* set poll callback - so that this keymap only gets enabled when curve editmode is enabled */ + keymap->poll = gpencil_stroke_editmode_curve_poll; +} + /* keys for draw with a drawing brush (no fill) */ static void ed_keymap_gpencil_painting_draw(wmKeyConfig *keyconf) { @@ -471,6 +487,7 @@ static void ed_keymap_gpencil_weightpainting_draw(wmKeyConfig *keyconf) void ED_keymap_gpencil(wmKeyConfig *keyconf) { ed_keymap_gpencil_general(keyconf); + ed_keymap_gpencil_curve_editing(keyconf); ed_keymap_gpencil_editing(keyconf); ed_keymap_gpencil_painting(keyconf); ed_keymap_gpencil_painting_draw(keyconf); @@ -568,6 +585,11 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_sculpt_paint); WM_operatortype_append(GPENCIL_OT_weight_paint); + /* Edit stroke editcurve */ + + WM_operatortype_append(GPENCIL_OT_stroke_enter_editcurve_mode); + WM_operatortype_append(GPENCIL_OT_stroke_editcurve_set_handle_type); + /* Editing (Buttons) ------------ */ WM_operatortype_append(GPENCIL_OT_annotation_add); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 82c30dea91d..e544093cd1d 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1195,7 +1195,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) /* subdivide and smooth the stroke */ if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (subdivide > 0)) { - gpencil_subdivide_stroke(gps, subdivide); + gpencil_subdivide_stroke(gpd, gps, subdivide); } /* Smooth stroke after subdiv - only if there's something to do for each iteration, @@ -1226,7 +1226,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) /* Simplify adaptive */ if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (brush->gpencil_settings->simplify_f > 0.0f)) { - BKE_gpencil_stroke_simplify_adaptive(gps, brush->gpencil_settings->simplify_f); + BKE_gpencil_stroke_simplify_adaptive(gpd, gps, brush->gpencil_settings->simplify_f); } /* reproject to plane (only in 3d space) */ @@ -1279,11 +1279,11 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) /* post process stroke */ if ((p->brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && p->brush->gpencil_settings->flag & GP_BRUSH_TRIM_STROKE) { - BKE_gpencil_stroke_trim(gps); + BKE_gpencil_stroke_trim(gpd, gps); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gpencil_stroke_added_enable(p); } @@ -1652,7 +1652,7 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, gpencil_stroke_soft_refine(gps); } - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + gpencil_stroke_delete_tagged_points(p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } gpencil_update_cache(p->gpd); } diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 55180885c5d..801dacb3e6b 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -1082,7 +1082,7 @@ static void gpencil_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* Update evaluated data. */ ED_gpencil_sbuffer_update_eval(tgpi->gpd, tgpi->ob_eval); @@ -1323,7 +1323,7 @@ static void gpencil_primitive_interaction_end(bContext *C, copy_v2_v2(gps->aspect_ratio, brush_settings->aspect_ratio); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps); } /* transfer stroke from temporary buffer to the actual frame */ diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c index 1d500223ab8..ed18c2eed5d 100644 --- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c +++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c @@ -303,7 +303,7 @@ static void gpencil_update_geometry(bGPdata *gpd) LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_TAG) { - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps->flag &= ~GP_STROKE_TAG; } } @@ -1021,7 +1021,7 @@ static void gpencil_brush_clone_add(bContext *C, tGP_BrushEditData *gso) bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); /* Make a new stroke */ - new_stroke = BKE_gpencil_stroke_duplicate(gps, true); + new_stroke = BKE_gpencil_stroke_duplicate(gps, true, true); new_stroke->next = new_stroke->prev = NULL; BLI_addtail(&gpf->strokes, new_stroke); @@ -1574,6 +1574,7 @@ static bool gpencil_sculpt_brush_do_frame(bContext *C, bool changed = false; bool redo_geom = false; Object *ob = gso->object; + bGPdata *gpd = ob->data; char tool = gso->brush->gpencil_sculpt_tool; LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { @@ -1672,7 +1673,7 @@ static bool gpencil_sculpt_brush_do_frame(bContext *C, MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); /* Update active frame now, only if material has fill. */ if (gp_style->flag & GP_MATERIAL_FILL_SHOW) { - BKE_gpencil_stroke_geometry_update(gps_active); + BKE_gpencil_stroke_geometry_update(gpd, gps_active); } else { gpencil_recalc_geometry_tag(gps_active); diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index 82d5eedf576..ddd8a11d9b5 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -43,10 +43,13 @@ #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" +#include "BKE_gpencil_geom.h" #include "BKE_material.h" #include "BKE_report.h" #include "UI_interface.h" +#include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" @@ -58,6 +61,7 @@ #include "ED_gpencil.h" #include "ED_select_utils.h" +#include "ED_view3d.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -126,6 +130,86 @@ static bool gpencil_select_poll(bContext *C) return false; } +static bool gpencil_3d_point_to_screen_space(ARegion *region, + const float diff_mat[4][4], + const float co[3], + int r_co[2]) +{ + float parent_co[3]; + mul_v3_m4v3(parent_co, diff_mat, co); + int screen_co[2]; + if (ED_view3d_project_int_global( + region, parent_co, screen_co, V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == + V3D_PROJ_RET_OK) { + if (!ELEM(V2D_IS_CLIPPED, screen_co[0], screen_co[1])) { + copy_v2_v2_int(r_co, screen_co); + return true; + } + } + r_co[0] = V2D_IS_CLIPPED; + r_co[1] = V2D_IS_CLIPPED; + return false; +} + +/* helper to deselect all selected strokes/points */ +static void deselect_all_selected(bContext *C) +{ + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + /* deselect stroke and its points if selected */ + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + + /* deselect points */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } + + /* deselect stroke itself too */ + gps->flag &= ~GP_STROKE_SELECT; + } + + /* deselect curve and curve points */ + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + for (int j = 0; j < gpc->tot_curve_points; j++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[j]; + BezTriple *bezt = &gpc_pt->bezt; + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + + gpc->flag &= ~GP_CURVE_SELECT; + } + } + CTX_DATA_END; +} + +static void select_all_curve_points(bGPDstroke *gps, bGPDcurve *gpc, bool deselect) +{ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (deselect == false) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + } + + if (deselect == false) { + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + gps->flag &= ~GP_STROKE_SELECT; + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -149,6 +233,7 @@ static int gpencil_select_all_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); int action = RNA_enum_get(op->ptr, "action"); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -168,7 +253,12 @@ static int gpencil_select_all_exec(bContext *C, wmOperator *op) } } - ED_gpencil_select_toggle_all(C, action); + if (is_curve_edit) { + ED_gpencil_select_curve_toggle_all(C, action); + } + else { + ED_gpencil_select_toggle_all(C, action); + } /* updates */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); @@ -207,6 +297,7 @@ void GPENCIL_OT_select_all(wmOperatorType *ot) static int gpencil_select_linked_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -218,18 +309,34 @@ static int gpencil_select_linked_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* select all points in selected strokes */ - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; + if (is_curve_edit) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_SELECT) { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + } + } + GP_EDITABLE_CURVES_END(gps_iter); + } + else { + /* select all points in selected strokes */ + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag |= GP_SPOINT_SELECT; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag |= GP_SPOINT_SELECT; + } } } + CTX_DATA_END; } - CTX_DATA_END; /* updates */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); @@ -265,8 +372,9 @@ void GPENCIL_OT_select_linked(wmOperatorType *ot) static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) { - const bool unselect_ends = RNA_boolean_get(op->ptr, "unselect_ends"); bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool unselect_ends = RNA_boolean_get(op->ptr, "unselect_ends"); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -278,47 +386,94 @@ static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* select all points in selected strokes */ - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if ((gps->flag & GP_STROKE_SELECT) && (gps->totpoints > 1)) { - bGPDspoint *pt; - int row = 0; - int start = 0; - if (unselect_ends) { - start = 1; - } + bool changed = false; + if (is_curve_edit) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if ((gps->flag & GP_STROKE_SELECT) && (gps->totpoints > 1)) { + int idx = 0; + int start = 0; + if (unselect_ends) { + start = 1; + } - for (int i = start; i < gps->totpoints; i++) { - pt = &gps->points[i]; - if ((row % 2) == 0) { - pt->flag |= GP_SPOINT_SELECT; + for (int i = start; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + if ((idx % 2) == 0) { + gpc_pt->flag |= GP_SPOINT_SELECT; + BEZT_SEL_ALL(&gpc_pt->bezt); + } + else { + gpc_pt->flag &= ~GP_SPOINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + idx++; } - else { - pt->flag &= ~GP_SPOINT_SELECT; + + if (unselect_ends) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[0]; + gpc_pt->flag &= ~GP_SPOINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + + gpc_pt = &gpc->curve_points[gpc->tot_curve_points - 1]; + gpc_pt->flag &= ~GP_SPOINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); } - row++; + + BKE_gpencil_curve_sync_selection(gps); + changed = true; } + } + GP_EDITABLE_CURVES_END(gps_iter); + } + else { + /* select all points in selected strokes */ + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if ((gps->flag & GP_STROKE_SELECT) && (gps->totpoints > 1)) { + bGPDspoint *pt; + int row = 0; + int start = 0; + if (unselect_ends) { + start = 1; + } - /* unselect start and end points */ - if (unselect_ends) { - pt = &gps->points[0]; - pt->flag &= ~GP_SPOINT_SELECT; + for (int i = start; i < gps->totpoints; i++) { + pt = &gps->points[i]; + if ((row % 2) == 0) { + pt->flag |= GP_SPOINT_SELECT; + } + else { + pt->flag &= ~GP_SPOINT_SELECT; + } + row++; + } - pt = &gps->points[gps->totpoints - 1]; - pt->flag &= ~GP_SPOINT_SELECT; + /* unselect start and end points */ + if (unselect_ends) { + pt = &gps->points[0]; + pt->flag &= ~GP_SPOINT_SELECT; + + pt = &gps->points[gps->totpoints - 1]; + pt->flag &= ~GP_SPOINT_SELECT; + } + + changed = true; } } + CTX_DATA_END; } - CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, 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); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -365,10 +520,13 @@ typedef enum eGP_SelectGrouped { /* ----------------------------------- */ /* On each visible layer, check for selected strokes - if found, select all others */ -static void gpencil_select_same_layer(bContext *C) +static bool gpencil_select_same_layer(bContext *C) { Scene *scene = CTX_data_scene(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV); bGPDstroke *gps; @@ -390,29 +548,55 @@ static void gpencil_select_same_layer(bContext *C) /* Select all if found */ if (found) { - for (gps = gpf->strokes.first; gps; gps = gps->next) { - if (ED_gpencil_stroke_can_use(C, gps)) { - bGPDspoint *pt; - int i; - - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag |= GP_SPOINT_SELECT; + if (is_curve_edit) { + for (gps = gpf->strokes.first; gps; gps = gps->next) { + if (gps->editcurve != NULL && ED_gpencil_stroke_can_use(C, gps)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc_pt->bezt); + } + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + + changed = true; } + } + } + else { + for (gps = gpf->strokes.first; gps; gps = gps->next) { + if (ED_gpencil_stroke_can_use(C, gps)) { + bGPDspoint *pt; + int i; + + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag |= GP_SPOINT_SELECT; + } + + gps->flag |= GP_STROKE_SELECT; - gps->flag |= GP_STROKE_SELECT; + changed = true; + } } } } } CTX_DATA_END; + + return changed; } /* Select all strokes with same colors as selected ones */ -static void gpencil_select_same_material(bContext *C) +static bool gpencil_select_same_material(bContext *C) { + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); /* First, build set containing all the colors of selected strokes */ GSet *selected_colors = BLI_gset_str_new("GP Selected Colors"); + bool changed = false; + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { if (gps->flag & GP_STROKE_SELECT) { /* add instead of insert here, otherwise the uniqueness check gets skipped, @@ -424,25 +608,48 @@ static void gpencil_select_same_material(bContext *C) CTX_DATA_END; /* Second, select any visible stroke that uses these colors */ - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (BLI_gset_haskey(selected_colors, &gps->mat_nr)) { - /* select this stroke */ - bGPDspoint *pt; - int i; + if (is_curve_edit) { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (gps->editcurve != NULL && BLI_gset_haskey(selected_colors, &gps->mat_nr)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc_pt->bezt); + } + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag |= GP_SPOINT_SELECT; + changed = true; } + } + CTX_DATA_END; + } + else { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (BLI_gset_haskey(selected_colors, &gps->mat_nr)) { + /* select this stroke */ + bGPDspoint *pt; + int i; - gps->flag |= GP_STROKE_SELECT; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag |= GP_SPOINT_SELECT; + } + + gps->flag |= GP_STROKE_SELECT; + + changed = true; + } } + CTX_DATA_END; } - CTX_DATA_END; /* free memomy */ if (selected_colors != NULL) { BLI_gset_free(selected_colors, NULL); } + + return changed; } /* ----------------------------------- */ @@ -456,12 +663,14 @@ static int gpencil_select_grouped_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; + switch (mode) { case GP_SEL_SAME_LAYER: - gpencil_select_same_layer(C); + changed = gpencil_select_same_layer(C); break; case GP_SEL_SAME_MATERIAL: - gpencil_select_same_material(C); + changed = gpencil_select_same_material(C); break; default: @@ -469,14 +678,16 @@ static int gpencil_select_grouped_exec(bContext *C, wmOperator *op) break; } - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, 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); + WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + } return OPERATOR_FINISHED; } @@ -515,6 +726,8 @@ void GPENCIL_OT_select_grouped(wmOperatorType *ot) static int gpencil_select_first_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; @@ -523,6 +736,7 @@ static int gpencil_select_first_exec(bContext *C, wmOperator *op) const bool only_selected = RNA_boolean_get(op->ptr, "only_selected_strokes"); const bool extend = RNA_boolean_get(op->ptr, "extend"); + bool changed = false; CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { /* skip stroke if we're only manipulating selected strokes */ if (only_selected && !(gps->flag & GP_STROKE_SELECT)) { @@ -532,30 +746,53 @@ static int gpencil_select_first_exec(bContext *C, wmOperator *op) /* select first point */ BLI_assert(gps->totpoints >= 1); - gps->points->flag |= GP_SPOINT_SELECT; - gps->flag |= GP_STROKE_SELECT; + if (is_curve_edit) { + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + gpc->curve_points[0].flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc->curve_points[0].bezt); + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + if ((extend == false) && (gps->totpoints > 1)) { + for (int i = 1; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + } + changed = true; + } + } + else { + gps->points->flag |= GP_SPOINT_SELECT; + gps->flag |= GP_STROKE_SELECT; - /* deselect rest? */ - if ((extend == false) && (gps->totpoints > 1)) { - /* start from index 1, to skip the first point that we'd just selected... */ - bGPDspoint *pt = &gps->points[1]; - int i = 1; + /* deselect rest? */ + if ((extend == false) && (gps->totpoints > 1)) { + /* start from index 1, to skip the first point that we'd just selected... */ + bGPDspoint *pt = &gps->points[1]; + int i = 1; - for (; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + for (; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } } + changed = true; } } CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, 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); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -590,12 +827,14 @@ void GPENCIL_OT_select_first(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Select First +/** \name Select Last * \{ */ static int gpencil_select_last_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; @@ -604,6 +843,7 @@ static int gpencil_select_last_exec(bContext *C, wmOperator *op) const bool only_selected = RNA_boolean_get(op->ptr, "only_selected_strokes"); const bool extend = RNA_boolean_get(op->ptr, "extend"); + bool changed = false; CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { /* skip stroke if we're only manipulating selected strokes */ if (only_selected && !(gps->flag & GP_STROKE_SELECT)) { @@ -613,30 +853,54 @@ static int gpencil_select_last_exec(bContext *C, wmOperator *op) /* select last point */ BLI_assert(gps->totpoints >= 1); - gps->points[gps->totpoints - 1].flag |= GP_SPOINT_SELECT; - gps->flag |= GP_STROKE_SELECT; + if (is_curve_edit) { + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + gpc->curve_points[gpc->tot_curve_points - 1].flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc->curve_points[gpc->tot_curve_points - 1].bezt); + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + if ((extend == false) && (gps->totpoints > 1)) { + for (int i = 0; i < gpc->tot_curve_points - 1; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + } + changed = true; + } + } + else { + gps->points[gps->totpoints - 1].flag |= GP_SPOINT_SELECT; + gps->flag |= GP_STROKE_SELECT; - /* deselect rest? */ - if ((extend == false) && (gps->totpoints > 1)) { - /* don't include the last point... */ - bGPDspoint *pt = gps->points; - int i = 1; + /* deselect rest? */ + if ((extend == false) && (gps->totpoints > 1)) { + /* don't include the last point... */ + bGPDspoint *pt = gps->points; + int i = 0; - for (; i < gps->totpoints - 1; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + for (; i < gps->totpoints - 1; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } } + + changed = true; } } CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, 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); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -677,64 +941,118 @@ void GPENCIL_OT_select_last(wmOperatorType *ot) static int gpencil_select_more_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; } - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; - bool prev_sel; - - /* First Pass: Go in forward order, - * expanding selection if previous was selected (pre changes). - * - This pass covers the "after" edges of selection islands - */ - prev_sel = false; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - /* selected point - just set flag for next point */ - prev_sel = true; + bool changed = false; + if (is_curve_edit) { + GP_EDITABLE_STROKES_BEGIN (gp_iter, C, gpl, gps) { + if (gps->editcurve != NULL && gps->flag & GP_STROKE_SELECT) { + bGPDcurve *editcurve = gps->editcurve; + + bool prev_sel = false; + for (int i = 0; i < editcurve->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &editcurve->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + /* selected point - just set flag for next point */ + prev_sel = true; + } + else { + /* unselected point - expand selection if previous was selected... */ + if (prev_sel) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + changed = true; + } + prev_sel = false; + } } - else { - /* unselected point - expand selection if previous was selected... */ - if (prev_sel) { - pt->flag |= GP_SPOINT_SELECT; + + prev_sel = false; + for (int i = editcurve->tot_curve_points - 1; i >= 0; i--) { + bGPDcurve_point *gpc_pt = &editcurve->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + prev_sel = true; + } + else { + /* unselected point - expand selection if previous was selected... */ + if (prev_sel) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + changed = true; + } + prev_sel = false; } - prev_sel = false; } } + } + GP_EDITABLE_STROKES_END(gp_iter); + } + else { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + bool prev_sel; - /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) - * - This pass covers the "before" edges of selection islands - */ - prev_sel = false; - for (pt -= 1; i > 0; i--, pt--) { - if (pt->flag & GP_SPOINT_SELECT) { - prev_sel = true; + /* First Pass: Go in forward order, + * expanding selection if previous was selected (pre changes). + * - This pass covers the "after" edges of selection islands + */ + prev_sel = false; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + /* selected point - just set flag for next point */ + prev_sel = true; + } + else { + /* unselected point - expand selection if previous was selected... */ + if (prev_sel) { + pt->flag |= GP_SPOINT_SELECT; + changed = true; + } + prev_sel = false; + } } - else { - /* unselected point - expand selection if previous was selected... */ - if (prev_sel) { - pt->flag |= GP_SPOINT_SELECT; + + /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) + * - This pass covers the "before" edges of selection islands + */ + prev_sel = false; + for (pt -= 1; i > 0; i--, pt--) { + if (pt->flag & GP_SPOINT_SELECT) { + prev_sel = true; + } + else { + /* unselected point - expand selection if previous was selected... */ + if (prev_sel) { + pt->flag |= GP_SPOINT_SELECT; + changed = true; + } + prev_sel = false; } - prev_sel = false; } } } + CTX_DATA_END; } - CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, 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); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -762,65 +1080,125 @@ void GPENCIL_OT_select_more(wmOperatorType *ot) static int gpencil_select_less_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; } - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; - bool prev_sel; + bool changed = false; + if (is_curve_edit) { + GP_EDITABLE_STROKES_BEGIN (gp_iter, C, gpl, gps) { + if (gps->editcurve != NULL && gps->flag & GP_STROKE_SELECT) { + bGPDcurve *editcurve = gps->editcurve; + int i; - /* First Pass: Go in forward order, shrinking selection - * if previous was not selected (pre changes). - * - This pass covers the "after" edges of selection islands - */ - prev_sel = false; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - /* shrink if previous wasn't selected */ - if (prev_sel == false) { - pt->flag &= ~GP_SPOINT_SELECT; + bool prev_sel = false; + for (i = 0; i < editcurve->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &editcurve->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + changed = true; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; } - prev_sel = true; } - else { - /* mark previous as being unselected - and hence, is trigger for shrinking */ - prev_sel = false; + + /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) + * - This pass covers the "before" edges of selection islands + */ + prev_sel = false; + for (i = editcurve->tot_curve_points - 1; i > 0; i--) { + bGPDcurve_point *gpc_pt = &editcurve->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + changed = true; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; + } } } + } + GP_EDITABLE_STROKES_END(gp_iter); + } + else { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + bool prev_sel; - /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) - * - This pass covers the "before" edges of selection islands - */ - prev_sel = false; - for (pt -= 1; i > 0; i--, pt--) { - if (pt->flag & GP_SPOINT_SELECT) { - /* shrink if previous wasn't selected */ - if (prev_sel == false) { - pt->flag &= ~GP_SPOINT_SELECT; + /* First Pass: Go in forward order, shrinking selection + * if previous was not selected (pre changes). + * - This pass covers the "after" edges of selection islands + */ + prev_sel = false; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + pt->flag &= ~GP_SPOINT_SELECT; + changed = true; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; } - prev_sel = true; } - else { - /* mark previous as being unselected - and hence, is trigger for shrinking */ - prev_sel = false; + + /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) + * - This pass covers the "before" edges of selection islands + */ + prev_sel = false; + for (pt -= 1; i > 0; i--, pt--) { + if (pt->flag & GP_SPOINT_SELECT) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + pt->flag &= ~GP_SPOINT_SELECT; + changed = true; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; + } } } } + CTX_DATA_END; } - CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, 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); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -852,7 +1230,7 @@ void GPENCIL_OT_select_less(wmOperatorType *ot) * from gpencil_paint.c #gpencil_stroke_eraser_dostroke(). * It would be great to de-duplicate the logic here sometime, but that can wait. */ -static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd), +static bool gpencil_stroke_do_circle_sel(bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps, GP_SpaceConversion *gsc, @@ -863,7 +1241,8 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd), rcti *rect, const float diff_mat[4][4], const int selectmode, - const float scale) + const float scale, + const bool is_curve_edit) { bGPDspoint *pt = NULL; int x0 = 0, y0 = 0; @@ -906,7 +1285,7 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd), float r_hita[3], r_hitb[3]; bool hit_select = (bool)(pt_active->flag & GP_SPOINT_SELECT); ED_gpencil_select_stroke_segment( - gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); + gpd, gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); } } } @@ -927,17 +1306,119 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd), } } + /* If curve edit mode, generate the curve. */ + if (is_curve_edit && hit && gps_active->editcurve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps_active); + gps_active->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + /* Select all curve points. */ + select_all_curve_points(gps_active, gps_active->editcurve, false); + BKE_gpencil_stroke_geometry_update(gpd, gps_active); + changed = true; + } + /* Ensure that stroke selection is in sync with its points. */ BKE_gpencil_stroke_sync_selection(gps_active); return changed; } +static bool gpencil_do_curve_circle_sel(bContext *C, + bGPDstroke *gps, + bGPDcurve *gpc, + const int mx, + const int my, + const int radius, + const bool select, + rcti *rect, + const float diff_mat[4][4], + const int selectmode) +{ + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + const bool only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); + + bool hit = false; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (bezt->hide == 1) { + continue; + } + + const bool handles_visible = (v3d->overlay.handle_display != CURVE_HANDLE_NONE) && + (!only_selected || BEZT_ISSEL_ANY(bezt)); + + /* if the handles are not visible only check ctrl point (vec[1])*/ + int from = (!handles_visible) ? 1 : 0; + int to = (!handles_visible) ? 2 : 3; + + for (int j = from; j < to; j++) { + float parent_co[3]; + mul_v3_m4v3(parent_co, diff_mat, bezt->vec[j]); + int screen_co[2]; + /* do 2d projection */ + if (ED_view3d_project_int_global( + region, parent_co, screen_co, V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) != + V3D_PROJ_RET_OK) { + continue; + } + + /* view and bounding box test */ + if (ELEM(V2D_IS_CLIPPED, screen_co[0], screen_co[1]) && + !BLI_rcti_isect_pt(rect, screen_co[0], screen_co[1])) { + continue; + } + + /* test inside circle */ + int dist_x = screen_co[0] - mx; + int dist_y = screen_co[1] - my; + int dist = dist_x * dist_x + dist_y * dist_y; + if (dist <= radius * radius) { + hit = true; + /* change selection */ + if (select) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(bezt, j); + } + else { + BEZT_DESEL_IDX(bezt, j); + if (!BEZT_ISSEL_ANY(bezt)) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + } + } + } + } + } + + /* select the entire curve */ + if (hit && (selectmode == GP_SELECTMODE_STROKE)) { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (select) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + } + } + + BKE_gpencil_curve_sync_selection(gps); + + return hit; +} + 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); Object *ob = CTX_data_active_object(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); int selectmode; if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { @@ -963,12 +1444,6 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) const int my = RNA_int_get(op->ptr, "y"); const int radius = RNA_int_get(op->ptr, "radius"); - GP_SpaceConversion gsc = {NULL}; - /* for bounding rect around circle (for quicky intersection testing) */ - rcti rect = {0}; - - bool changed = false; - /* sanity checks */ if (area == NULL) { BKE_report(op->reports, RPT_ERROR, "No active area"); @@ -978,36 +1453,57 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) const eSelectOp sel_op = ED_select_op_modal(RNA_enum_get(op->ptr, "mode"), WM_gesture_is_modal_first(op->customdata)); const bool select = (sel_op != SEL_OP_SUB); - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - ED_gpencil_select_toggle_all(C, SEL_DESELECT); - changed = true; - } - /* init space conversion stuff */ - gpencil_point_conversion_init(C, &gsc); - - /* rect is rectangle of selection circle */ + bool changed = false; + /* for bounding rect around circle (for quicky intersection testing) */ + rcti rect = {0}; rect.xmin = mx - radius; rect.ymin = my - radius; rect.xmax = mx + radius; rect.ymax = my + radius; - /* find visible strokes, and select if hit */ - GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - changed |= gpencil_stroke_do_circle_sel(gpd, - gpl, - gps, - &gsc, - mx, - my, - radius, - select, - &rect, - gpstroke_iter.diff_mat, - selectmode, - scale); + if (is_curve_edit) { + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + ED_gpencil_select_curve_toggle_all(C, SEL_DESELECT); + changed = true; + } + + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + changed |= gpencil_do_curve_circle_sel( + C, gps, gpc, mx, my, radius, select, &rect, gps_iter.diff_mat, selectmode); + } + GP_EDITABLE_CURVES_END(gps_iter); + } + + if (changed == false) { + GP_SpaceConversion gsc = {NULL}; + /* init space conversion stuff */ + gpencil_point_conversion_init(C, &gsc); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + ED_gpencil_select_toggle_all(C, SEL_DESELECT); + changed = true; + } + + /* find visible strokes, and select if hit */ + GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + changed |= gpencil_stroke_do_circle_sel(gpd, + gpl, + gps, + &gsc, + mx, + my, + radius, + select, + &rect, + gpstroke_iter.diff_mat, + selectmode, + scale, + is_curve_edit); + } + GP_EVALUATED_STROKES_END(gpstroke_iter); } - GP_EVALUATED_STROKES_END(gpstroke_iter); /* updates */ if (changed) { @@ -1054,49 +1550,245 @@ void GPENCIL_OT_select_circle(wmOperatorType *ot) * * \{ */ -typedef bool (*GPencilTestFn)(bGPDstroke *gps, - bGPDspoint *pt, - const GP_SpaceConversion *gsc, - const float diff_mat[4][4], - void *user_data); +typedef struct GP_SelectUserData { + int mx, my, radius; + /* Bounding box rect */ + rcti rect; + const int (*lasso_coords)[2]; + int lasso_coords_len; +} GP_SelectUserData; -static int gpencil_generic_select_exec( - bContext *C, wmOperator *op, GPencilTestFn is_inside_fn, rcti box, void *user_data) +typedef bool (*GPencilTestFn)(ARegion *region, + const float diff_mat[4][4], + const float pt[3], + GP_SelectUserData *user_data); + +#if 0 +static bool gpencil_stroke_fill_isect_rect(ARegion *region, + bGPDstroke *gps, + const float diff_mat[4][4], + rcti rect) { - Object *ob = CTX_data_active_object(C); - bGPdata *gpd = ED_gpencil_data_get_active(C); - ToolSettings *ts = CTX_data_tool_settings(C); - ScrArea *area = CTX_wm_area(C); + int min[2] = {-INT_MAX, -INT_MAX}; + int max[2] = {INT_MAX, INT_MAX}; - int selectmode; - if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { - selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); - } - else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { - selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); - } - else { - selectmode = ts->gpencil_selectmode_edit; + int(*points2d)[2] = MEM_callocN(sizeof(int[2]) * gps->totpoints, __func__); + + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + int *pt2d = points2d[i]; + + int screen_co[2]; + gpencil_3d_point_to_screen_space(region, diff_mat, &pt->x, screen_co); + DO_MINMAX2(screen_co, min, max); + + copy_v2_v2_int(pt2d, screen_co); } - const bool strokemode = ((selectmode == GP_SELECTMODE_STROKE) && - ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); - const bool segmentmode = ((selectmode == GP_SELECTMODE_SEGMENT) && - ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); + bool hit = false; + /* check bounding box */ + rcti bb = {min[0], max[0], min[1], max[1]}; + if (BLI_rcti_isect(&rect, &bb, NULL)) { + for (int i = 0; i < gps->tot_triangles; i++) { + bGPDtriangle *tri = &gps->triangles[i]; + int pt1[2], pt2[2], pt3[2]; + int tri_min[2] = {-INT_MAX, -INT_MAX}; + int tri_max[2] = {INT_MAX, INT_MAX}; + + copy_v2_v2_int(pt1, points2d[tri->verts[0]]); + copy_v2_v2_int(pt2, points2d[tri->verts[1]]); + copy_v2_v2_int(pt3, points2d[tri->verts[2]]); + + DO_MINMAX2(pt1, tri_min, tri_max); + DO_MINMAX2(pt2, tri_min, tri_max); + DO_MINMAX2(pt3, tri_min, tri_max); + + rcti tri_bb = {tri_min[0], tri_max[0], tri_min[1], tri_max[1]}; + /* Case 1: triangle is entirely inside box selection */ + /* (XXX: Can this even happen with no point inside the box?) */ + if (BLI_rcti_inside_rcti(&tri_bb, &rect)) { + hit = true; + break; + } - const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); - const float scale = ts->gp_sculpt.isect_threshold; + /* Case 2: rectangle intersects sides of triangle */ + if (BLI_rcti_isect_segment(&rect, pt1, pt2) || BLI_rcti_isect_segment(&rect, pt2, pt3) || + BLI_rcti_isect_segment(&rect, pt3, pt1)) { + hit = true; + break; + } - GP_SpaceConversion gsc = {NULL}; + /* TODO: Case 3: rectangle is inside the triangle */ + } + } + + MEM_freeN(points2d); + return hit; +} +#endif + +static bool gpencil_generic_curve_select(bContext *C, + Object *UNUSED(ob), + GPencilTestFn is_inside_fn, + rcti UNUSED(box), + GP_SelectUserData *user_data, + const bool strokemode, + const eSelectOp sel_op) +{ + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + const bool handle_only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); + const bool handle_all = (v3d->overlay.handle_display == CURVE_HANDLE_ALL); + bool hit = false; bool changed = false; + bool whole = false; - /* sanity checks */ - if (area == NULL) { - BKE_report(op->reports, RPT_ERROR, "No active area"); - return OPERATOR_CANCELLED; + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + bool any_select = false; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (bezt->hide == 1) { + continue; + } + + const bool handles_visible = (handle_all || (handle_only_selected && + (gpc_pt->flag & GP_CURVE_POINT_SELECT))); + + if (handles_visible) { + for (int j = 0; j < 3; j++) { + const bool is_select = BEZT_ISSEL_IDX(bezt, j); + bool is_inside = is_inside_fn(region, gps_iter.diff_mat, bezt->vec[j], user_data); + if (strokemode) { + if (is_inside) { + hit = true; + any_select = true; + break; + } + } + else { + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + if (sel_op_result) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(bezt, j); + any_select = true; + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_IDX(bezt, j); + } + changed = true; + hit = true; + } + else { + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_IDX(bezt, j); + } + } + } + } + } + /* if the handles are not visible only check ctrl point (vec[1])*/ + else { + const bool is_select = bezt->f2; + bool is_inside = is_inside_fn(region, gps_iter.diff_mat, bezt->vec[1], user_data); + if (strokemode) { + if (is_inside) { + hit = true; + any_select = true; + } + } + else { + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + if (sel_op_result) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + bezt->f2 |= SELECT; + any_select = true; + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + bezt->f2 &= ~SELECT; + } + changed = true; + hit = true; + } + else { + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + bezt->f2 &= ~SELECT; + } + } + } + } + } + + /* TODO: Fix selection for filled in curves. */ +#if 0 + if (!hit) { + /* check if we selected the inside of a filled curve */ + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + if ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0) { + continue; + } + + whole = gpencil_stroke_fill_isect_rect(region, gps, gps_iter.diff_mat, box); + } +#endif + /* select the entire curve */ + if (strokemode || whole) { + const int sel_op_result = ED_select_op_action_deselected(sel_op, any_select, hit || whole); + if (sel_op_result != -1) { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (sel_op_result) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + } + + if (sel_op_result) { + gpc->flag |= GP_CURVE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + } + changed = true; + } + } + + BKE_gpencil_curve_sync_selection(gps); } + GP_EDITABLE_CURVES_END(gps_iter); + return changed; +} + +static bool gpencil_generic_stroke_select(bContext *C, + Object *ob, + bGPdata *gpd, + GPencilTestFn is_inside_fn, + rcti box, + GP_SelectUserData *user_data, + const bool strokemode, + const bool segmentmode, + const eSelectOp sel_op, + const float scale, + const bool is_curve_edit) +{ + GP_SpaceConversion gsc = {NULL}; + bool changed = false; /* init space conversion stuff */ gpencil_point_conversion_init(C, &gsc); @@ -1130,7 +1822,7 @@ static int gpencil_generic_select_exec( bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; /* convert point coords to screenspace */ - const bool is_inside = is_inside_fn(gps, pt, &gsc, gpstroke_iter.diff_mat, user_data); + const bool is_inside = is_inside_fn(gsc.region, gpstroke_iter.diff_mat, &pt->x, user_data); if (strokemode == false) { const bool is_select = (pt_active->flag & GP_SPOINT_SELECT) != 0; const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); @@ -1144,7 +1836,7 @@ static int gpencil_generic_select_exec( bool hit_select = (bool)(pt_active->flag & GP_SPOINT_SELECT); float r_hita[3], r_hitb[3]; ED_gpencil_select_stroke_segment( - gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); + gpd, gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); } } } @@ -1191,11 +1883,82 @@ static int gpencil_generic_select_exec( } } + /* If curve edit mode, generate the curve. */ + if (is_curve_edit && (hit || whole) && gps_active->editcurve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps_active); + gps_active->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + /* Select all curve points. */ + select_all_curve_points(gps_active, gps_active->editcurve, false); + BKE_gpencil_stroke_geometry_update(gpd, gps_active); + changed = true; + } + /* Ensure that stroke selection is in sync with its points */ BKE_gpencil_stroke_sync_selection(gps_active); } GP_EVALUATED_STROKES_END(gpstroke_iter); + return changed; +} + +static int gpencil_generic_select_exec(bContext *C, + wmOperator *op, + GPencilTestFn is_inside_fn, + rcti box, + GP_SelectUserData *user_data) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + ToolSettings *ts = CTX_data_tool_settings(C); + ScrArea *area = CTX_wm_area(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + int selectmode; + if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { + selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); + } + else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { + selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + } + else { + selectmode = ts->gpencil_selectmode_edit; + } + + const bool strokemode = ((selectmode == GP_SELECTMODE_STROKE) && + ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); + const bool segmentmode = ((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; + + bool changed = false; + + /* sanity checks */ + if (area == NULL) { + BKE_report(op->reports, RPT_ERROR, "No active area"); + return OPERATOR_CANCELLED; + } + + if (is_curve_edit) { + changed = gpencil_generic_curve_select( + C, ob, is_inside_fn, box, user_data, strokemode, sel_op); + } + + if (changed == false) { + changed = gpencil_generic_stroke_select(C, + ob, + gpd, + is_inside_fn, + box, + user_data, + strokemode, + segmentmode, + sel_op, + scale, + is_curve_edit); + } + /* if paint mode,delete selected points */ if (GPENCIL_PAINT_MODE(gpd)) { gpencil_delete_selected_point_wrap(C); @@ -1222,27 +1985,21 @@ static int gpencil_generic_select_exec( /** \name Box Select Operator * \{ */ -struct GP_SelectBoxUserData { - rcti rect; -}; - -static bool gpencil_test_box(bGPDstroke *gps, - bGPDspoint *pt, - const GP_SpaceConversion *gsc, +static bool gpencil_test_box(ARegion *region, const float diff_mat[4][4], - void *user_data) + const float pt[3], + GP_SelectUserData *user_data) { - const struct GP_SelectBoxUserData *data = user_data; - bGPDspoint pt2; - int x0, y0; - gpencil_point_to_parent_space(pt, diff_mat, &pt2); - gpencil_point_to_xy(gsc, gps, &pt2, &x0, &y0); - return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&data->rect, x0, y0)); + int co[2] = {0}; + if (gpencil_3d_point_to_screen_space(region, diff_mat, pt, co)) { + return BLI_rcti_isect_pt(&user_data->rect, co[0], co[1]); + } + return false; } static int gpencil_box_select_exec(bContext *C, wmOperator *op) { - struct GP_SelectBoxUserData data = {0}; + GP_SelectUserData data = {0}; WM_operator_properties_border_to_rcti(op, &data.rect); rcti rect = data.rect; return gpencil_generic_select_exec(C, op, gpencil_test_box, rect, &data); @@ -1277,45 +2034,38 @@ void GPENCIL_OT_select_box(wmOperatorType *ot) /** \name Lasso Select Operator * \{ */ -struct GP_SelectLassoUserData { - rcti rect; - const int (*mcoords)[2]; - int mcoords_len; -}; - -static bool gpencil_test_lasso(bGPDstroke *gps, - bGPDspoint *pt, - const GP_SpaceConversion *gsc, +static bool gpencil_test_lasso(ARegion *region, const float diff_mat[4][4], - void *user_data) + const float pt[3], + GP_SelectUserData *user_data) { - const struct GP_SelectLassoUserData *data = user_data; - bGPDspoint pt2; - int x0, y0; - gpencil_point_to_parent_space(pt, diff_mat, &pt2); - gpencil_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->mcoords, data->mcoords_len, x0, y0, INT_MAX)); + int co[2] = {0}; + if (gpencil_3d_point_to_screen_space(region, diff_mat, pt, co)) { + /* test if in lasso boundbox + within the lasso noose */ + return (BLI_rcti_isect_pt(&user_data->rect, co[0], co[1]) && + BLI_lasso_is_point_inside( + user_data->lasso_coords, user_data->lasso_coords_len, co[0], co[1], INT_MAX)); + } + return false; } static int gpencil_lasso_select_exec(bContext *C, wmOperator *op) { - struct GP_SelectLassoUserData data = {0}; - data.mcoords = WM_gesture_lasso_path_to_array(C, op, &data.mcoords_len); + struct GP_SelectUserData data = {0}; + data.lasso_coords = WM_gesture_lasso_path_to_array(C, op, &data.lasso_coords_len); /* Sanity check. */ - if (data.mcoords == NULL) { + if (data.lasso_coords == NULL) { return OPERATOR_PASS_THROUGH; } /* Compute boundbox of lasso (for faster testing later). */ - BLI_lasso_boundbox(&data.rect, data.mcoords, data.mcoords_len); + BLI_lasso_boundbox(&data.rect, data.lasso_coords, data.lasso_coords_len); rcti rect = data.rect; int ret = gpencil_generic_select_exec(C, op, gpencil_test_lasso, rect, &data); - MEM_freeN((void *)data.mcoords); + MEM_freeN((void *)data.lasso_coords); return ret; } @@ -1346,25 +2096,56 @@ void GPENCIL_OT_select_lasso(wmOperatorType *ot) /** \name Mouse Pick Select Operator * \{ */ -/* helper to deselect all selected strokes/points */ -static void deselect_all_selected(bContext *C) +static void gpencil_select_curve_point(bContext *C, + const int mval[2], + const int radius_squared, + bGPDlayer **r_gpl, + bGPDstroke **r_gps, + bGPDcurve **r_gpc, + bGPDcurve_point **r_pt, + char *handle) { - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - /* deselect stroke and its points if selected */ - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + const bool only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); - /* deselect points */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + int hit_distance = radius_squared; + + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (bezt->hide == 1) { + continue; } - /* deselect stroke itself too */ - gps->flag &= ~GP_STROKE_SELECT; + const bool handles_visible = (v3d->overlay.handle_display != CURVE_HANDLE_NONE) && + (!only_selected || BEZT_ISSEL_ANY(bezt)); + + /* if the handles are not visible only check ctrl point (vec[1])*/ + int from = (!handles_visible) ? 1 : 0; + int to = (!handles_visible) ? 2 : 3; + + for (int j = from; j < to; j++) { + int screen_co[2]; + if (gpencil_3d_point_to_screen_space(region, gps_iter.diff_mat, bezt->vec[j], screen_co)) { + const int pt_distance = len_manhattan_v2v2_int(mval, screen_co); + + if (pt_distance <= radius_squared && pt_distance < hit_distance) { + *r_gpl = gpl; + *r_gps = gps; + *r_gpc = gpc; + *r_pt = gpc_pt; + *handle = j; + hit_distance = pt_distance; + } + } + } } } - CTX_DATA_END; + GP_EDITABLE_CURVES_END(gps_iter); } static int gpencil_select_exec(bContext *C, wmOperator *op) @@ -1374,6 +2155,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) bGPdata *gpd = ED_gpencil_data_get_active(C); ToolSettings *ts = CTX_data_tool_settings(C); const float scale = ts->gp_sculpt.isect_threshold; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); /* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */ const float radius = 0.4f * U.widget_unit; @@ -1387,12 +2169,17 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all") && !use_shift_extend; int mval[2] = {0}; + /* get mouse location */ + RNA_int_get_array(op->ptr, "location", mval); GP_SpaceConversion gsc = {NULL}; bGPDlayer *hit_layer = NULL; bGPDstroke *hit_stroke = NULL; bGPDspoint *hit_point = NULL; + bGPDcurve *hit_curve = NULL; + bGPDcurve_point *hit_curve_point = NULL; + char hit_curve_handle = 0; int hit_distance = radius_squared; /* sanity checks */ @@ -1414,47 +2201,57 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) whole = (bool)(ts->gpencil_selectmode_edit == GP_SELECTMODE_STROKE); } - /* init space conversion stuff */ - gpencil_point_conversion_init(C, &gsc); + if (is_curve_edit) { + gpencil_select_curve_point(C, + mval, + radius_squared, + &hit_layer, + &hit_stroke, + &hit_curve, + &hit_curve_point, + &hit_curve_handle); + } - /* get mouse location */ - RNA_int_get_array(op->ptr, "location", mval); + if (hit_curve == NULL) { + /* init space conversion stuff */ + gpencil_point_conversion_init(C, &gsc); - /* First Pass: Find stroke point which gets hit */ - GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; - bGPDspoint *pt; - int i; + /* First Pass: Find stroke point which gets hit */ + GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + bGPDspoint *pt; + int i; - /* firstly, check for hit-point */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - int xy[2]; - - bGPDspoint pt2; - gpencil_point_to_parent_space(pt, gpstroke_iter.diff_mat, &pt2); - gpencil_point_to_xy(&gsc, gps, &pt2, &xy[0], &xy[1]); - - /* do boundbox check first */ - if (!ELEM(V2D_IS_CLIPPED, xy[0], xy[1])) { - const int pt_distance = len_manhattan_v2v2_int(mval, xy); - - /* check if point is inside */ - 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_active; - hit_point = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; - hit_distance = pt_distance; + /* firstly, check for hit-point */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + int xy[2]; + + bGPDspoint pt2; + gpencil_point_to_parent_space(pt, gpstroke_iter.diff_mat, &pt2); + gpencil_point_to_xy(&gsc, gps, &pt2, &xy[0], &xy[1]); + + /* do boundbox check first */ + if (!ELEM(V2D_IS_CLIPPED, xy[0], xy[1])) { + const int pt_distance = len_manhattan_v2v2_int(mval, xy); + + /* check if point is inside */ + 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_active; + hit_point = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + hit_distance = pt_distance; + } } } } } + GP_EVALUATED_STROKES_END(gpstroke_iter); } - GP_EVALUATED_STROKES_END(gpstroke_iter); /* Abort if nothing hit... */ - if (ELEM(NULL, hit_stroke, hit_point)) { + if (!hit_curve && !hit_curve_point && !hit_point && !hit_stroke) { if (deselect_all) { /* since left mouse select change, deselect all if click outside any hit */ @@ -1472,9 +2269,26 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + /* select all handles if the click was on the curve but not on a handle */ + if (is_curve_edit && hit_point != NULL) { + whole = true; + hit_curve = hit_stroke->editcurve; + } + /* adjust selection behavior - for toggle option */ if (toggle) { - deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0; + if (hit_curve_point != NULL) { + BezTriple *bezt = &hit_curve_point->bezt; + if (bezt->f1 & SELECT && hit_curve_handle == 0) + deselect = true; + if (bezt->f2 & SELECT && hit_curve_handle == 1) + deselect = true; + if (bezt->f3 & SELECT && hit_curve_handle == 2) + deselect = true; + } + else { + deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0; + } } /* If not extending selection, deselect everything else */ @@ -1484,64 +2298,94 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) /* Perform selection operations... */ if (whole) { - bGPDspoint *pt; - int i; + /* Generate editcurve if it does not exist */ + if (is_curve_edit && hit_curve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, hit_layer, hit_stroke); + hit_stroke->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, hit_stroke); + hit_curve = hit_stroke->editcurve; + } + /* select all curve points */ + if (hit_curve != NULL) { + select_all_curve_points(hit_stroke, hit_curve, deselect); + } + else { + bGPDspoint *pt; + int i; - /* entire stroke's points */ - for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) { + /* entire stroke's points */ + for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) { + if (deselect == false) { + pt->flag |= GP_SPOINT_SELECT; + } + else { + pt->flag &= ~GP_SPOINT_SELECT; + } + } + + /* stroke too... */ if (deselect == false) { - pt->flag |= GP_SPOINT_SELECT; + hit_stroke->flag |= GP_STROKE_SELECT; } else { - pt->flag &= ~GP_SPOINT_SELECT; + hit_stroke->flag &= ~GP_STROKE_SELECT; } } - - /* stroke too... */ - if (deselect == false) { - hit_stroke->flag |= GP_STROKE_SELECT; - } - else { - hit_stroke->flag &= ~GP_STROKE_SELECT; - } } else { /* just the point (and the stroke) */ if (deselect == false) { - /* 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 */ - int selectmode; - if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { - selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); - } - else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { - selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + if (hit_curve_point != NULL) { + hit_curve_point->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(&hit_curve_point->bezt, hit_curve_handle); + hit_curve->flag |= GP_CURVE_SELECT; + hit_stroke->flag |= GP_STROKE_SELECT; } else { - selectmode = ts->gpencil_selectmode_edit; - } + /* 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 */ + int selectmode; + if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { + selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); + } + else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { + selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + } + else { + selectmode = ts->gpencil_selectmode_edit; + } - if (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); + if (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( + gpd, hit_layer, hit_stroke, hit_point, hit_select, false, scale, r_hita, r_hitb); + } } } else { - /* deselect point */ - hit_point->flag &= ~GP_SPOINT_SELECT; + if (hit_curve_point != NULL) { + BEZT_DESEL_IDX(&hit_curve_point->bezt, hit_curve_handle); + if (!BEZT_ISSEL_ANY(&hit_curve_point->bezt)) { + hit_curve_point->flag &= ~GP_CURVE_POINT_SELECT; + } + BKE_gpencil_curve_sync_selection(hit_stroke); + } + else { + /* deselect point */ + hit_point->flag &= ~GP_SPOINT_SELECT; - /* ensure that stroke is selected correctly */ - BKE_gpencil_stroke_sync_selection(hit_stroke); + /* ensure that stroke is selected correctly */ + BKE_gpencil_stroke_sync_selection(hit_stroke); + } } } /* updates */ - if (hit_point != NULL) { + if (hit_curve_point != NULL || hit_point != NULL) { DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); /* copy on write tag is needed, or else no refresh happens */ @@ -1679,13 +2523,13 @@ static int gpencil_select_vertex_color_exec(bContext *C, wmOperator *op) { ToolSettings *ts = CTX_data_tool_settings(C); Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); const float threshold = RNA_int_get(op->ptr, "threshold"); const int selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); - bGPdata *gpd = (bGPdata *)ob->data; const float range = pow(10, 5 - threshold); - bool done = false; + bool changed = false; /* Create a hash table with all selected colors. */ GHash *hue_table = BLI_ghash_int_new(__func__); @@ -1722,7 +2566,6 @@ static int gpencil_select_vertex_color_exec(bContext *C, wmOperator *op) if (gps_selected) { gps->flag |= GP_STROKE_SELECT; - done = true; /* Extend stroke selection. */ if (selectmode == GP_SELECTMODE_STROKE) { @@ -1736,7 +2579,7 @@ static int gpencil_select_vertex_color_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - if (done) { + if (changed) { /* updates */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_trace_utils.c b/source/blender/editors/gpencil/gpencil_trace_utils.c index 544cb4fef1e..ada777d43f3 100644 --- a/source/blender/editors/gpencil/gpencil_trace_utils.c +++ b/source/blender/editors/gpencil/gpencil_trace_utils.c @@ -352,13 +352,14 @@ void ED_gpencil_trace_data_to_strokes(Main *bmain, * long stroke. Here the length is checked and removed if the length is too big. */ float length = BKE_gpencil_stroke_length(gps, true); if (length <= MAX_LENGTH) { + bGPdata *gpd = ob->data; if (sample > 0.0f) { /* Resample stroke. Don't need to call to BKE_gpencil_stroke_geometry_update() because * the sample function already call that. */ - BKE_gpencil_stroke_sample(gps, sample, false); + BKE_gpencil_stroke_sample(gpd, gps, sample, false); } else { - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } else { diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 0fdd70c55bc..a1c2ff12866 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -55,6 +55,7 @@ #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" @@ -1144,7 +1145,7 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, bGPDstroke *gps_active = gps; /* if duplicate, deselect all points. */ if (keep_original) { - gps_active = BKE_gpencil_stroke_duplicate(gps, true); + gps_active = BKE_gpencil_stroke_duplicate(gps, true, true); gps_active->flag &= ~GP_STROKE_SELECT; for (i = 0, pt = gps_active->points; i < gps_active->totpoints; i++, pt++) { pt->flag &= ~GP_SPOINT_SELECT; @@ -1320,10 +1321,11 @@ void ED_gpencil_project_point_to_plane(const Scene *scene, /** * Subdivide a stroke once, by adding a point half way between each pair of existing points + * \param gpd: Datablock * \param gps: Stroke data * \param subdivide: Number of times to subdivide */ -void gpencil_subdivide_stroke(bGPDstroke *gps, const int subdivide) +void gpencil_subdivide_stroke(bGPdata *gpd, bGPDstroke *gps, const int subdivide) { bGPDspoint *temp_points; int totnewpoints, oldtotpoints; @@ -1413,7 +1415,7 @@ void gpencil_subdivide_stroke(bGPDstroke *gps, const int subdivide) MEM_SAFE_FREE(temp_points); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } /* Reset parent matrix for all layers. */ @@ -2243,8 +2245,12 @@ static void gpencil_copy_points( } } -static void gpencil_insert_point( - bGPDstroke *gps, bGPDspoint *a_pt, bGPDspoint *b_pt, const float co_a[3], const float co_b[3]) +static void gpencil_insert_point(bGPdata *gpd, + bGPDstroke *gps, + bGPDspoint *a_pt, + bGPDspoint *b_pt, + const float co_a[3], + float co_b[3]) { bGPDspoint *temp_points; int totnewpoints, oldtotpoints; @@ -2303,8 +2309,8 @@ static void gpencil_insert_point( i2++; } - /* Calculate geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps); MEM_SAFE_FREE(temp_points); } @@ -2328,7 +2334,8 @@ static float gpencil_calc_factor(const float p2d_a1[2], } /* extend selection to stroke intersections */ -int ED_gpencil_select_stroke_segment(bGPDlayer *gpl, +int ED_gpencil_select_stroke_segment(bGPdata *gpd, + bGPDlayer *gpl, bGPDstroke *gps, bGPDspoint *pt, bool select, @@ -2483,7 +2490,7 @@ int ED_gpencil_select_stroke_segment(bGPDlayer *gpl, /* insert new point in the collision points */ if (insert) { - gpencil_insert_point(gps, hit_pointa, hit_pointb, r_hita, r_hitb); + gpencil_insert_point(gpd, gps, hit_pointa, hit_pointb, r_hita, r_hitb); } /* free memory */ @@ -2611,6 +2618,82 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) } } +void ED_gpencil_select_curve_toggle_all(bContext *C, int action) +{ + /* if toggle, check if we need to select or deselect */ + if (action == SEL_TOGGLE) { + action = SEL_SELECT; + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_SELECT) { + action = SEL_DESELECT; + } + } + GP_EDITABLE_CURVES_END(gps_iter); + } + + if (action == SEL_DESELECT) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + gpc->flag &= ~GP_CURVE_SELECT; + gps->flag &= ~GP_STROKE_SELECT; + } + GP_EDITABLE_CURVES_END(gps_iter); + } + else { + GP_EDITABLE_STROKES_BEGIN(gps_iter, C, gpl, gps){ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + bool selected = false; + + /* Make sure stroke has an editcurve */ + if (gps->editcurve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + switch (action) { + case SEL_SELECT: + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + break; + case SEL_INVERT: + gpc_pt->flag ^= GP_CURVE_POINT_SELECT; + BEZT_SEL_INVERT(bezt); + break; + default: + break; + } + + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + selected = true; + } + } + + if (selected) { + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + gps->flag &= ~GP_STROKE_SELECT; + } + } + GP_EDITABLE_STROKES_END(gps_iter); + } +} + /** * Ensure the #tGPspoint buffer (while drawing stroke) * size is enough to save all points of the stroke. diff --git a/source/blender/editors/gpencil/gpencil_uv.c b/source/blender/editors/gpencil/gpencil_uv.c index 3bd2c3e6be6..1d1bc72aaec 100644 --- a/source/blender/editors/gpencil/gpencil_uv.c +++ b/source/blender/editors/gpencil/gpencil_uv.c @@ -273,7 +273,7 @@ static bool gpencil_uv_transform_calc(bContext *C, wmOperator *op) changed = true; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); i++; } } @@ -291,7 +291,7 @@ static bool gpencil_uv_transform_calc(bContext *C, wmOperator *op) gps->uv_rotation = opdata->array_rot[i] - uv_rotation; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); i++; } } @@ -316,7 +316,7 @@ static bool gpencil_uv_transform_calc(bContext *C, wmOperator *op) if (gps->flag & GP_STROKE_SELECT) { gps->uv_scale = opdata->array_scale[i] + scale; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); i++; } } @@ -512,7 +512,7 @@ static int gpencil_reset_transform_fill_exec(bContext *C, wmOperator *op) gps->uv_scale = 1.0f; } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); changed = true; } } diff --git a/source/blender/editors/gpencil/gpencil_vertex_paint.c b/source/blender/editors/gpencil/gpencil_vertex_paint.c index c6ee30ad6e3..a4dc677f0dc 100644 --- a/source/blender/editors/gpencil/gpencil_vertex_paint.c +++ b/source/blender/editors/gpencil/gpencil_vertex_paint.c @@ -819,8 +819,9 @@ static void gpencil_save_selected_point(tGP_BrushVertexpaintData *gso, gso->pbuffer_used++; } -/* Select points in this stroke and add to an array to be used later. */ -static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, +/* Select points in this stroke and add to an array to be used later. + * Returns true if any point was hit and got saved */ +static bool gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, bGPDstroke *gps, const char tool, const float diff_mat[4][4]) @@ -841,9 +842,11 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, int index; bool include_last = false; + bool saved = false; + /* Check if the stroke collide with brush. */ if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, diff_mat)) { - return; + return false; } if (gps->totpoints == 1) { @@ -862,6 +865,7 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, /* apply operation to this point */ if (pt_active != NULL) { gpencil_save_selected_point(gso, gps_active, 0, pc1); + saved = true; } } } @@ -913,6 +917,7 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, } hit = true; gpencil_save_selected_point(gso, gps_active, index, pc1); + saved = true; } /* Only do the second point if this is the last segment, @@ -931,6 +936,7 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, hit = true; gpencil_save_selected_point(gso, gps_active, index, pc2); include_last = false; + saved = true; } } else { @@ -949,8 +955,8 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, if (pt_active != NULL) { hit = true; gpencil_save_selected_point(gso, gps_active, index, pc1); - include_last = false; + saved = true; } } } @@ -970,10 +976,13 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, for (int repeat = 0; repeat < 50; repeat++) { gpencil_save_selected_point(gso, gps_active, -1, NULL); } + saved = true; } } } } + + return saved; } /* Apply vertex paint brushes to strokes in the given frame. */ @@ -1008,7 +1017,13 @@ static bool gpencil_vertexpaint_brush_do_frame(bContext *C, } /* Check points below the brush. */ - gpencil_vertexpaint_select_stroke(gso, gps, tool, diff_mat); + bool hit = gpencil_vertexpaint_select_stroke(gso, gps, tool, diff_mat); + + /* If stroke was hit and has an editcurve the curve needs an update. */ + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + if (gps_active->editcurve != NULL && hit) { + gps_active->editcurve->flag |= GP_CURVE_NEEDS_STROKE_UPDATE; + } } /* For Average tool, need calculate the average resulting color from all colors diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 48d323c5d57..17aa407bd76 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -314,7 +314,8 @@ void ED_gpencil_update_color_uv(struct Main *bmain, struct Material *mat); * 2 - Hit in point B * 3 - Hit in point A and B */ -int ED_gpencil_select_stroke_segment(struct bGPDlayer *gpl, +int ED_gpencil_select_stroke_segment(struct bGPdata *gpd, + struct bGPDlayer *gpl, struct bGPDstroke *gps, struct bGPDspoint *pt, bool select, @@ -324,6 +325,7 @@ int ED_gpencil_select_stroke_segment(struct bGPDlayer *gpl, float r_hitb[3]); void ED_gpencil_select_toggle_all(struct bContext *C, int action); +void ED_gpencil_select_curve_toggle_all(struct bContext *C, int action); /* Ensure stroke sbuffer size is enough */ struct tGPspoint *ED_gpencil_sbuffer_ensure(struct tGPspoint *buffer_array, diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index ce049cfcaac..690d6c8055a 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1738,6 +1738,10 @@ static void ed_default_handlers( wmKeyMap *keymap_general = WM_keymap_ensure(wm->defaultconf, "Grease Pencil", 0, 0); WM_event_add_keymap_handler(handlers, keymap_general); + wmKeyMap *keymap_curve_edit = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Curve Edit Mode", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_curve_edit); + wmKeyMap *keymap_edit = WM_keymap_ensure( wm->defaultconf, "Grease Pencil Stroke Edit Mode", 0, 0); WM_event_add_keymap_handler(handlers, keymap_edit); diff --git a/source/blender/editors/transform/transform_convert_gpencil.c b/source/blender/editors/transform/transform_convert_gpencil.c index 84885dd8c49..0a742ec4470 100644 --- a/source/blender/editors/transform/transform_convert_gpencil.c +++ b/source/blender/editors/transform/transform_convert_gpencil.c @@ -31,7 +31,9 @@ #include "BKE_colortools.h" #include "BKE_context.h" +#include "BKE_curve.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "ED_gpencil.h" @@ -63,33 +65,351 @@ static void createTransGPencil_center_get(bGPDstroke *gps, float r_center[3]) } } -void createTransGPencil(bContext *C, TransInfo *t) +static short get_bezt_sel_triple_flag(BezTriple *bezt, const bool handles_visible) { - if (t->data_container_len == 0) { +#define SEL_F1 (1 << 0) +#define SEL_F2 (1 << 1) +#define SEL_F3 (1 << 2) +#define SEL_ALL ((1 << 0) | (1 << 1) | (1 << 2)) + + short flag = 0; + + if (handles_visible) { + flag = ((bezt->f1 & SELECT) ? SEL_F1 : 0) | ((bezt->f2 & SELECT) ? SEL_F2 : 0) | + ((bezt->f3 & SELECT) ? SEL_F3 : 0); + } + else { + if (bezt->f2 & SELECT) { + flag = SEL_ALL; + } + } + + /* Special case for auto & aligned handles */ + if (flag != SEL_ALL && flag & SEL_F2) { + if (ELEM(bezt->h1, HD_AUTO, HD_ALIGN) && ELEM(bezt->h2, HD_AUTO, HD_ALIGN)) { + flag = SEL_ALL; + } + } + +#undef SEL_F1 +#undef SEL_F2 +#undef SEL_F3 + return flag; +} + +static void createTransGPencil_curves(bContext *C, + TransInfo *t, + Depsgraph *depsgraph, + ToolSettings *ts, + Object *obact, + bGPdata *gpd, + const int cfra_scene, + const bool is_multiedit, + const bool use_multiframe_falloff, + const bool is_prop_edit, + const bool is_prop_edit_connected, + const bool is_scale_thickness) +{ +#define SEL_F1 (1 << 0) +#define SEL_F2 (1 << 1) +#define SEL_F3 (1 << 2) + + View3D *v3d = t->view; + const bool handle_only_selected_visible = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); + const bool handle_all_visible = (v3d->overlay.handle_display == CURVE_HANDLE_ALL); + + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + tc->data_len = 0; + + /* Number of selected curve points */ + uint32_t tot_curve_points = 0, tot_sel_curve_points = 0, tot_points = 0, tot_sel_points = 0; + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* Only editable and visible layers are considered. */ + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* Check if the color is editable. */ + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { + continue; + } + /* Check if stroke has an editcurve */ + if (gps->editcurve == NULL) { + continue; + } + + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (bezt->hide) { + continue; + } + + const bool handles_visible = (handle_all_visible || + (handle_only_selected_visible && + (gpc_pt->flag & GP_CURVE_POINT_SELECT))); + + const short sel_flag = get_bezt_sel_triple_flag(bezt, handles_visible); + if (sel_flag & (SEL_F1 | SEL_F2 | SEL_F3)) { + if (sel_flag & SEL_F1) { + tot_sel_points++; + } + if (sel_flag & SEL_F2) { + tot_sel_points++; + } + if (sel_flag & SEL_F3) { + tot_sel_points++; + } + tot_sel_curve_points++; + } + + if (is_prop_edit) { + tot_points += 3; + tot_curve_points++; + } + } + } + } + + /* If not multiedit out of loop. */ + if (!is_multiedit) { + break; + } + } + } + } + + if (((is_prop_edit && !is_prop_edit_connected) ? tot_curve_points : tot_sel_points) == 0) { + tc->data_len = 0; return; } - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - bGPdata *gpd = ED_gpencil_data_get_active(C); - ToolSettings *ts = CTX_data_tool_settings(C); + int data_len_pt = 0; - bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); - bool use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0; + if (is_prop_edit) { + tc->data_len = tot_points; + data_len_pt = tot_curve_points; + } + else { + tc->data_len = tot_sel_points; + data_len_pt = tot_sel_curve_points; + } - Object *obact = CTX_data_active_object(C); - TransData *td = NULL; - float mtx[3][3], smtx[3][3]; + if (tc->data_len == 0) { + return; + } - const Scene *scene = CTX_data_scene(C); - const int cfra_scene = CFRA; + transform_around_single_fallback_ex(t, data_len_pt); - const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; - const bool is_prop_edit_connected = (t->flag & T_PROP_CONNECTED) != 0; - const bool is_scale_thickness = ((t->mode == TFM_GPENCIL_SHRINKFATTEN) || - (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_SCALE_THICKNESS)); + tc->data = MEM_callocN(tc->data_len * sizeof(TransData), __func__); + TransData *td = tc->data; - TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + const bool use_around_origins_for_handles_test = ((t->around == V3D_AROUND_LOCAL_ORIGINS) && + transform_mode_use_local_origins(t)); + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* Only editable and visible layers are considered. */ + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + const int cfra = (gpl->flag & GP_LAYER_FRAMELOCK) ? gpl->actframe->framenum : cfra_scene; + bGPDframe *gpf = gpl->actframe; + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + float diff_mat[4][4], mtx[3][3]; + float smtx[3][3]; + + /* Init multiframe falloff options. */ + int f_init = 0; + int f_end = 0; + + if (use_multiframe_falloff) { + BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end); + } + + if ((gpf->framenum != cfra) && (!is_multiedit)) { + gpf = BKE_gpencil_frame_addcopy(gpl, cfra); + /* in some weird situations (framelock enabled) return NULL */ + if (gpf == NULL) { + continue; + } + if (!is_multiedit) { + init_gpf = gpf; + } + } + + /* Calculate difference matrix. */ + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); + copy_m3_m4(mtx, diff_mat); + pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); + + for (gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + /* If multi-frame and falloff, recalculate and save value. */ + float falloff = 1.0f; /* by default no falloff */ + if ((is_multiedit) && (use_multiframe_falloff)) { + /* Falloff depends on distance to active frame + * (relative to the overall frame range). */ + falloff = BKE_gpencil_multiframe_falloff_calc( + gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff); + } + + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* Check if the color is editable. */ + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { + continue; + } + /* Check if stroke has an editcurve */ + if (gps->editcurve == NULL) { + continue; + } + TransData *head, *tail; + head = tail = td; + + gps->runtime.multi_frame_falloff = falloff; + bool need_handle_recalc = false; + + bGPDcurve *gpc = gps->editcurve; + const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (bezt->hide) { + continue; + } + + TransDataCurveHandleFlags *hdata = NULL; + bool bezt_use = false; + const bool handles_visible = (handle_all_visible || + (handle_only_selected_visible && + (gpc_pt->flag & GP_CURVE_POINT_SELECT))); + const short sel_flag = get_bezt_sel_triple_flag(bezt, handles_visible); + /* Iterate over bezier triple */ + for (int j = 0; j < 3; j++) { + bool is_ctrl_point = (j == 1); + bool sel = sel_flag & (1 << j); + + if (is_prop_edit || sel) { + copy_v3_v3(td->iloc, bezt->vec[j]); + td->loc = bezt->vec[j]; + bool rotate_around_ctrl = !handles_visible || + (t->around == V3D_AROUND_LOCAL_ORIGINS) || + (bezt->f2 & SELECT); + copy_v3_v3(td->center, bezt->vec[rotate_around_ctrl ? 1 : j]); + + if (!handles_visible || is_ctrl_point) { + if (bezt->f2 & SELECT) { + td->flag = TD_SELECTED; + } + else { + td->flag = 0; + } + } + else if (handles_visible) { + if (BEZT_ISSEL_IDX(bezt, j)) { + td->flag = TD_SELECTED; + } + else { + td->flag = 0; + } + } + + td->ext = NULL; + if (is_ctrl_point) { + if (t->mode != TFM_MIRROR) { + if (t->mode != TFM_GPENCIL_OPACITY) { + if (is_scale_thickness) { + td->val = &(gpc_pt->pressure); + td->ival = gpc_pt->pressure; + } + } + else { + td->val = &(gpc_pt->strength); + td->ival = gpc_pt->strength; + } + } + } + else { + td->val = NULL; + } + + if (hdata == NULL) { + if (is_ctrl_point && ((sel_flag & SEL_F1 & SEL_F3) == 0)) { + hdata = initTransDataCurveHandles(td, bezt); + } + else if (!is_ctrl_point) { + hdata = initTransDataCurveHandles(td, bezt); + } + } + + td->extra = gps; + td->ob = obact; + + copy_m3_m3(td->smtx, smtx); + copy_m3_m3(td->mtx, mtx); + copy_m3_m3(td->axismtx, mtx); + + td++; + tail++; + } + + bezt_use |= sel; + } + + /* Update the handle types so transformation is possible */ + if (bezt_use && !ELEM(t->mode, TFM_GPENCIL_OPACITY, TFM_GPENCIL_SHRINKFATTEN)) { + BKE_nurb_bezt_handle_test( + bezt, SELECT, handles_visible, use_around_origins_for_handles_test); + need_handle_recalc = true; + } + } + if (is_prop_edit && (head != tail)) { + calc_distanceCurveVerts(head, tail - 1, is_cyclic); + } + + if (need_handle_recalc) { + BKE_gpencil_editcurve_recalculate_handles(gps); + } + } + } + + /* If not multiedit out of loop. */ + if (!is_multiedit) { + break; + } + } + } + } +#undef SEL_F1 +#undef SEL_F2 +#undef SEL_F3 +} + +static void createTransGPencil_strokes(bContext *C, + TransInfo *t, + Depsgraph *depsgraph, + ToolSettings *ts, + Object *obact, + bGPdata *gpd, + const int cfra_scene, + const bool is_multiedit, + const bool use_multiframe_falloff, + const bool is_prop_edit, + const bool is_prop_edit_connected, + const bool is_scale_thickness) +{ + TransData *td = NULL; + float mtx[3][3], smtx[3][3]; + + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); /* == Grease Pencil Strokes to Transform Data == * Grease Pencil stroke points can be a mixture of 2D (screen-space), * or 3D coordinates. However, they're always saved as 3D points. @@ -98,15 +418,6 @@ void createTransGPencil(bContext *C, TransInfo *t) */ tc->data_len = 0; - if (gpd == NULL) { - return; - } - - /* initialize falloff curve */ - if (is_multiedit) { - BKE_curvemapping_init(ts->gp_sculpt.cur_falloff); - } - /* First Pass: Count the number of data-points required for the strokes, * (and additional info about the configuration - e.g. 2D/3D?). */ @@ -368,6 +679,71 @@ void createTransGPencil(bContext *C, TransInfo *t) } } +void createTransGPencil(bContext *C, TransInfo *t) +{ + if (t->data_container_len == 0) { + return; + } + + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + const Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + Object *obact = CTX_data_active_object(C); + bGPdata *gpd = obact->data; + BLI_assert(gpd != NULL); + + const int cfra_scene = CFRA; + + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != + 0; + + const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; + const bool is_prop_edit_connected = (t->flag & T_PROP_CONNECTED) != 0; + const bool is_scale_thickness = ((t->mode == TFM_GPENCIL_SHRINKFATTEN) || + (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_SCALE_THICKNESS)); + + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + /* initialize falloff curve */ + if (is_multiedit) { + BKE_curvemapping_init(ts->gp_sculpt.cur_falloff); + } + + if (gpd == NULL) { + return; + } + + if (is_curve_edit) { + createTransGPencil_curves(C, + t, + depsgraph, + ts, + obact, + gpd, + cfra_scene, + is_multiedit, + use_multiframe_falloff, + is_prop_edit, + is_prop_edit_connected, + is_scale_thickness); + } + else { + createTransGPencil_strokes(C, + t, + depsgraph, + ts, + obact, + gpd, + cfra_scene, + is_multiedit, + use_multiframe_falloff, + is_prop_edit, + is_prop_edit_connected, + is_scale_thickness); + } +} + /* force recalculation of triangles during transformation */ void recalcData_gpencil_strokes(TransInfo *t) { @@ -375,13 +751,19 @@ void recalcData_gpencil_strokes(TransInfo *t) GHash *strokes = BLI_ghash_ptr_new(__func__); TransData *td = tc->data; + bGPdata *gpd = td->ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); for (int i = 0; i < tc->data_len; i++, td++) { bGPDstroke *gps = td->extra; if ((gps != NULL) && (!BLI_ghash_haskey(strokes, gps))) { BLI_ghash_insert(strokes, gps, gps); + if (is_curve_edit && gps->editcurve != NULL) { + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } BLI_ghash_free(strokes, NULL, NULL); diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index 60848eb5678..8a7ec7a99e9 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -799,7 +799,7 @@ void postTrans(bContext *C, TransInfo *t) if (t->data_len_all != 0) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { /* free data malloced per trans-data */ - if (ELEM(t->obedit_type, OB_CURVE, OB_SURF) || (t->spacetype == SPACE_GRAPH)) { + if (ELEM(t->obedit_type, OB_CURVE, OB_SURF, OB_GPENCIL) || (t->spacetype == SPACE_GRAPH)) { TransData *td = tc->data; for (int a = 0; a < tc->data_len; a++, td++) { if (td->flag & TD_BEZTRIPLE) { diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index a71915d8122..d5c6ea2dfdd 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -653,6 +653,7 @@ int ED_transform_calc_gizmo_stats(const bContext *C, Object *ob = OBACT(view_layer); bGPdata *gpd = CTX_data_gpencil_data(C); const bool is_gp_edit = GPENCIL_ANY_MODE(gpd); + const bool is_curve_edit = GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); int a, totsel = 0; const int pivot_point = scene->toolsettings->transform_pivot_point; @@ -711,16 +712,39 @@ int ED_transform_calc_gizmo_stats(const bContext *C, continue; } - /* we're only interested in selected points here... */ - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; + if (is_curve_edit) { + if (gps->editcurve == NULL) { + continue; + } - /* Change selection status of all points, then make the stroke match */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - calc_tw_center_with_matrix(tbounds, &pt->x, use_mat_local, diff_mat); - totsel++; + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + for (uint32_t i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + for (uint32_t j = 0; j < 3; j++) { + if (BEZT_ISSEL_IDX(bezt, j)) { + calc_tw_center_with_matrix(tbounds, bezt->vec[j], use_mat_local, diff_mat); + totsel++; + } + } + } + } + } + } + else { + /* we're only interested in selected points here... */ + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + + /* Change selection status of all points, then make the stroke match */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + calc_tw_center_with_matrix(tbounds, &pt->x, use_mat_local, diff_mat); + totsel++; + } } } } diff --git a/source/blender/editors/transform/transform_mode_gpopacity.c b/source/blender/editors/transform/transform_mode_gpopacity.c index 103eb17c273..e67c6c03481 100644 --- a/source/blender/editors/transform/transform_mode_gpopacity.c +++ b/source/blender/editors/transform/transform_mode_gpopacity.c @@ -29,6 +29,8 @@ #include "BKE_context.h" #include "BKE_unit.h" +#include "DNA_gpencil_types.h" + #include "ED_screen.h" #include "UI_interface.h" @@ -68,8 +70,16 @@ static void applyGPOpacity(TransInfo *t, const int UNUSED(mval[2])) BLI_snprintf(str, sizeof(str), TIP_("Opacity: %3f"), ratio); } + bool recalc = false; FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData *td = tc->data; + bGPdata *gpd = td->ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* Only recalculate data when in curve edit mode. */ + if (is_curve_edit) { + recalc = true; + } + for (i = 0; i < tc->data_len; i++, td++) { if (td->flag & TD_SKIP) { continue; @@ -84,6 +94,10 @@ static void applyGPOpacity(TransInfo *t, const int UNUSED(mval[2])) } } + if (recalc) { + recalcData(t); + } + ED_area_status_text(t->area, str); } diff --git a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c index 14e7c1df4f4..95e3d89d2b7 100644 --- a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c @@ -29,6 +29,8 @@ #include "BKE_context.h" #include "BKE_unit.h" +#include "DNA_gpencil_types.h" + #include "ED_screen.h" #include "UI_interface.h" @@ -68,8 +70,16 @@ static void applyGPShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) BLI_snprintf(str, sizeof(str), TIP_("Shrink/Fatten: %3f"), ratio); } + bool recalc = false; FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData *td = tc->data; + bGPdata *gpd = td->ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* Only recalculate data when in curve edit mode. */ + if (is_curve_edit) { + recalc = true; + } + for (i = 0; i < tc->data_len; i++, td++) { if (td->flag & TD_SKIP) { continue; @@ -86,6 +96,10 @@ static void applyGPShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) } } + if (recalc) { + recalcData(t); + } + ED_area_status_text(t->area, str); } |