From b9d0f33530f095fb318890dca4f8933a9afd1904 Mon Sep 17 00:00:00 2001 From: Antonioya Date: Thu, 8 Aug 2019 10:23:05 +0200 Subject: Fix T67545: GPencil - New Merge by Distance operator Merge points when the distance is less than a predefined value. The method to interpolate the position created a wrong merge. Now, always the secondary point is merged with the first one (merge at first), except the last point. --- .../bl_ui/properties_grease_pencil_common.py | 1 + source/blender/blenkernel/BKE_gpencil.h | 4 ++ source/blender/blenkernel/intern/gpencil.c | 78 ++++++++++++++++++++++ source/blender/editors/gpencil/gpencil_edit.c | 70 +++++++++++++++++++ source/blender/editors/gpencil/gpencil_intern.h | 1 + source/blender/editors/gpencil/gpencil_ops.c | 1 + 6 files changed, 155 insertions(+) diff --git a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py index 1e5aac4cc0d..bef4f395233 100644 --- a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -606,6 +606,7 @@ class GPENCIL_MT_cleanup(Menu): def draw(self, _context): layout = self.layout + layout.operator("gpencil.stroke_merge_by_distance", text="Merge by Distance") layout.operator("gpencil.frame_clean_loose", text="Loose Points") layout.separator() diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 9ec872f3676..3dfd6eb466c 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -197,6 +197,10 @@ void BKE_gpencil_simplify_stroke(struct bGPDstroke *gps, float factor); void BKE_gpencil_simplify_fixed(struct bGPDstroke *gps); void BKE_gpencil_subdivide(struct bGPDstroke *gps, int level, int flag); bool BKE_gpencil_trim_stroke(struct bGPDstroke *gps); +void BKE_gpencil_merge_distance_stroke(struct bGPDframe *gpf, + struct bGPDstroke *gps, + const float threshold, + const bool use_unselected); void BKE_gpencil_stroke_2d_flat(const struct bGPDspoint *points, int totpoints, diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 3ae20642b15..efdb35d4409 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -2138,3 +2138,81 @@ void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short ta gps->tot_triangles = 0; } } + +/* Merge by distance ------------------------------------- */ +/* Reduce a series of points when the distance is below a threshold. + * Special case for first and last points (both are keeped) for other points, + * the merge point always is at first point. + * \param gpf: Grease Pencil frame + * \param gps: Grease Pencil stroke + * \param threshold: Distance between points + * \param use_unselected: Set to true to analyze all stroke and not only selected points + */ +void BKE_gpencil_merge_distance_stroke(bGPDframe *gpf, + bGPDstroke *gps, + const float threshold, + const bool use_unselected) +{ + bGPDspoint *pt = NULL; + bGPDspoint *pt_next = NULL; + float tagged = false; + /* Use square distance to speed up loop */ + const float th_square = threshold * threshold; + /* Need to have something to merge. */ + if (gps->totpoints < 2) { + return; + } + int i = 0; + int step = 1; + while ((i < gps->totpoints - 1) && (i + step < gps->totpoints)) { + pt = &gps->points[i]; + if (pt->flag & GP_SPOINT_TAG) { + i++; + step = 1; + continue; + } + pt_next = &gps->points[i + step]; + /* Do not recalc tagged points. */ + if (pt_next->flag & GP_SPOINT_TAG) { + step++; + continue; + } + /* Check if contiguous points are selected. */ + if (!use_unselected) { + if (((pt->flag & GP_SPOINT_SELECT) == 0) || ((pt_next->flag & GP_SPOINT_SELECT) == 0)) { + i++; + step = 1; + continue; + } + } + float len_square = len_squared_v3v3(&pt->x, &pt_next->x); + if (len_square <= th_square) { + tagged = true; + if (i != gps->totpoints - 1) { + /* Tag second point for delete. */ + pt_next->flag |= GP_SPOINT_TAG; + } + else { + pt->flag |= GP_SPOINT_TAG; + } + /* Jump to next pair of points, keeping first point segment equals.*/ + step++; + } + else { + /* Analyze next point. */ + i++; + step = 1; + } + } + + /* Always untag extremes. */ + pt = &gps->points[0]; + pt->flag &= ~GP_SPOINT_TAG; + pt = &gps->points[gps->totpoints - 1]; + pt->flag &= ~GP_SPOINT_TAG; + + /* Dissolve tagged points */ + if (tagged) { + BKE_gpencil_dissolve_points(gpf, gps, GP_SPOINT_TAG); + } +} diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 277628f4363..a7b9bb24bf8 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -4522,3 +4522,73 @@ bool ED_object_gpencil_exit(struct Main *bmain, Object *ob) } return ok; } + +/* ** merge by distance *** */ +bool gp_merge_by_distance_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (ob == NULL) { + return false; + } + bGPdata *gpd = (bGPdata *)ob->data; + if (gpd == NULL) { + return false; + } + + bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); + + return ((gpl != NULL) && (ob->mode == OB_MODE_EDIT_GPENCIL)); +} + +static int gp_merge_by_distance_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + const float threshold = RNA_float_get(op->ptr, "threshold"); + const bool unselected = RNA_boolean_get(op->ptr, "use_unselected"); + + /* sanity checks */ + if (ELEM(NULL, gpd)) { + 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_merge_distance_stroke(gpf_, gps, threshold, unselected); + } + } + 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); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_merge_by_distance(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Merge by Distance"; + ot->idname = "GPENCIL_OT_stroke_merge_by_distance"; + ot->description = "Merge points by distance"; + + /* api callbacks */ + ot->exec = gp_merge_by_distance_exec; + ot->poll = gp_merge_by_distance_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_float(ot->srna, "threshold", 0.001f, 0.0f, 100.0f, "Threshold", "", 0.0f, 100.0f); + /* avoid re-using last var */ + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + prop = RNA_def_boolean( + ot->srna, "use_unselected", 0, "Unselected", "Use whole stroke, not only selected points"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 1c20da4bed3..3776dd09eb5 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -489,6 +489,7 @@ void GPENCIL_OT_stroke_smooth(struct wmOperatorType *ot); void GPENCIL_OT_stroke_merge(struct wmOperatorType *ot); void GPENCIL_OT_stroke_cutter(struct wmOperatorType *ot); void GPENCIL_OT_stroke_trim(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_merge_by_distance(struct wmOperatorType *ot); void GPENCIL_OT_brush_presets_create(struct wmOperatorType *ot); diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index a1645a5c67d..54e99802c54 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -317,6 +317,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_stroke_merge); WM_operatortype_append(GPENCIL_OT_stroke_cutter); WM_operatortype_append(GPENCIL_OT_stroke_trim); + WM_operatortype_append(GPENCIL_OT_stroke_merge_by_distance); WM_operatortype_append(GPENCIL_OT_brush_presets_create); -- cgit v1.2.3