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.
This commit is contained in:
parent
9d4a8cbd88
commit
b9d0f33530
|
@ -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()
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue