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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors')
-rw-r--r--source/blender/editors/gpencil/annotate_paint.c4
-rw-r--r--source/blender/editors/gpencil/gpencil_convert.c3
-rw-r--r--source/blender/editors/gpencil/gpencil_edit.c572
-rw-r--r--source/blender/editors/gpencil/gpencil_intern.h60
-rw-r--r--source/blender/editors/gpencil/gpencil_merge.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c22
-rw-r--r--source/blender/editors/gpencil/gpencil_primitive.c25
-rw-r--r--source/blender/editors/gpencil/gpencil_utils.c178
-rw-r--r--source/blender/editors/include/ED_gpencil.h18
9 files changed, 287 insertions, 597 deletions
diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c
index a73bf8154d9..1b53141294e 100644
--- a/source/blender/editors/gpencil/annotate_paint.c
+++ b/source/blender/editors/gpencil/annotate_paint.c
@@ -42,6 +42,7 @@
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
#include "BKE_layer.h"
#include "BKE_main.h"
#include "BKE_report.h"
@@ -1198,7 +1199,8 @@ static void annotation_stroke_eraser_dostroke(tGPsdata *p,
/* Second Pass: Remove any points that are tagged */
if (do_cull) {
- gpencil_stroke_delete_tagged_points(p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0);
+ BKE_gpencil_stroke_delete_tagged_points(
+ p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0);
}
}
}
diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c
index 293ce370a0b..33b02d525d5 100644
--- a/source/blender/editors/gpencil/gpencil_convert.c
+++ b/source/blender/editors/gpencil/gpencil_convert.c
@@ -1860,7 +1860,8 @@ static int image_to_gpencil_exec(bContext *C, wmOperator *op)
if (done) {
/* Delete any selected point. */
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
- gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0);
+ BKE_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_edit.c b/source/blender/editors/gpencil/gpencil_edit.c
index f2c3804a067..8ce3d176525 100644
--- a/source/blender/editors/gpencil/gpencil_edit.c
+++ b/source/blender/editors/gpencil/gpencil_edit.c
@@ -99,8 +99,6 @@
/** \name Stroke Edit Mode Management
* \{ */
-static void gpencil_flip_stroke(bGPDstroke *gps);
-
/* poll callback for all stroke editing operators */
static bool gpencil_stroke_edit_poll(bContext *C)
{
@@ -1181,7 +1179,7 @@ static void gpencil_add_move_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gp
/* Flip stroke if it was only one point to consider extrude point as last point. */
if (gps->totpoints == 2) {
- gpencil_flip_stroke(gps);
+ BKE_gpencil_stroke_flip(gps);
}
/* Calc geometry data. */
@@ -2584,372 +2582,6 @@ 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:
- * gpencil_stroke_delete_tagged_points()
- */
-typedef struct tGPDeleteIsland {
- int start_idx;
- int end_idx;
-} tGPDeleteIsland;
-
-static void gpencil_stroke_join_islands(bGPdata *gpd,
- bGPDframe *gpf,
- bGPDstroke *gps_first,
- bGPDstroke *gps_last)
-{
- bGPDspoint *pt = NULL;
- bGPDspoint *pt_final = NULL;
- const int totpoints = gps_first->totpoints + gps_last->totpoints;
-
- /* create new stroke */
- bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false, true);
-
- join_stroke->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__);
- join_stroke->totpoints = totpoints;
- join_stroke->flag &= ~GP_STROKE_CYCLIC;
-
- /* copy points (last before) */
- int e1 = 0;
- int e2 = 0;
- float delta = 0.0f;
-
- for (int i = 0; i < totpoints; i++) {
- pt_final = &join_stroke->points[i];
- if (i < gps_last->totpoints) {
- pt = &gps_last->points[e1];
- e1++;
- }
- else {
- pt = &gps_first->points[e2];
- e2++;
- }
-
- /* copy current point */
- copy_v3_v3(&pt_final->x, &pt->x);
- pt_final->pressure = pt->pressure;
- pt_final->strength = pt->strength;
- pt_final->time = delta;
- pt_final->flag = pt->flag;
- copy_v4_v4(pt_final->vert_color, pt->vert_color);
-
- /* retiming with fixed time interval (we cannot determine real time) */
- delta += 0.01f;
- }
-
- /* Copy over vertex weight data (if available) */
- if ((gps_first->dvert != NULL) || (gps_last->dvert != NULL)) {
- join_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * totpoints, __func__);
- MDeformVert *dvert_src = NULL;
- MDeformVert *dvert_dst = NULL;
-
- /* Copy weights (last before)*/
- e1 = 0;
- e2 = 0;
- for (int i = 0; i < totpoints; i++) {
- dvert_dst = &join_stroke->dvert[i];
- dvert_src = NULL;
- if (i < gps_last->totpoints) {
- if (gps_last->dvert) {
- dvert_src = &gps_last->dvert[e1];
- e1++;
- }
- }
- else {
- if (gps_first->dvert) {
- dvert_src = &gps_first->dvert[e2];
- e2++;
- }
- }
-
- if ((dvert_src) && (dvert_src->dw)) {
- dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
- }
- }
- }
-
- /* add new stroke at head */
- BLI_addhead(&gpf->strokes, join_stroke);
- /* Calc geometry data. */
- BKE_gpencil_stroke_geometry_update(gpd, join_stroke);
-
- /* remove first stroke */
- BLI_remlink(&gpf->strokes, gps_first);
- BKE_gpencil_free_stroke(gps_first);
-
- /* remove last stroke */
- BLI_remlink(&gpf->strokes, gps_last);
- BKE_gpencil_free_stroke(gps_last);
-}
-
-/* Split the given stroke into several new strokes, partitioning
- * it based on whether the stroke points have a particular flag
- * is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always)
- *
- * The algorithm used here is as follows:
- * 1) We firstly identify the number of "islands" of non-tagged points
- * which will all end up being in new strokes.
- * - In the most extreme case (i.e. every other vert is a 1-vert island),
- * we have at most n / 2 islands
- * - Once we start having larger islands than that, the number required
- * becomes much less
- * 2) Each island gets converted to a new stroke
- * If the number of points is <= limit, the stroke is deleted
- */
-void gpencil_stroke_delete_tagged_points(bGPdata *gpd,
- bGPDframe *gpf,
- bGPDstroke *gps,
- bGPDstroke *next_stroke,
- int tag_flags,
- bool select,
- int limit)
-{
- tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2,
- "gp_point_islands");
- bool in_island = false;
- int num_islands = 0;
-
- bGPDstroke *gps_first = NULL;
- const bool is_cyclic = (bool)(gps->flag & GP_STROKE_CYCLIC);
-
- /* First Pass: Identify start/end of islands */
- bGPDspoint *pt = gps->points;
- for (int i = 0; i < gps->totpoints; i++, pt++) {
- if (pt->flag & tag_flags) {
- /* selected - stop accumulating to island */
- in_island = false;
- }
- else {
- /* unselected - start of a new island? */
- int idx;
-
- if (in_island) {
- /* extend existing island */
- idx = num_islands - 1;
- islands[idx].end_idx = i;
- }
- else {
- /* start of new island */
- in_island = true;
- num_islands++;
-
- idx = num_islands - 1;
- islands[idx].start_idx = islands[idx].end_idx = i;
- }
- }
- }
-
- /* Watch out for special case where No islands = All points selected = Delete Stroke only */
- if (num_islands) {
- /* There are islands, so create a series of new strokes,
- * adding them before the "next" stroke. */
- int idx;
- bGPDstroke *new_stroke = NULL;
-
- /* Create each new stroke... */
- for (idx = 0; idx < num_islands; idx++) {
- tGPDeleteIsland *island = &islands[idx];
- new_stroke = BKE_gpencil_stroke_duplicate(gps, false, true);
-
- /* if cyclic and first stroke, save to join later */
- if ((is_cyclic) && (gps_first == NULL)) {
- gps_first = new_stroke;
- }
-
- new_stroke->flag &= ~GP_STROKE_CYCLIC;
-
- /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
- new_stroke->totpoints = island->end_idx - island->start_idx + 1;
-
- /* Copy over the relevant point data */
- new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints,
- "gp delete stroke fragment");
- memcpy(new_stroke->points,
- gps->points + island->start_idx,
- sizeof(bGPDspoint) * new_stroke->totpoints);
-
- /* Copy over vertex weight data (if available) */
- if (gps->dvert != NULL) {
- /* Copy over the relevant vertex-weight points */
- new_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints,
- "gp delete stroke fragment weight");
- memcpy(new_stroke->dvert,
- gps->dvert + island->start_idx,
- sizeof(MDeformVert) * new_stroke->totpoints);
-
- /* Copy weights */
- int e = island->start_idx;
- for (int i = 0; i < new_stroke->totpoints; i++) {
- MDeformVert *dvert_src = &gps->dvert[e];
- MDeformVert *dvert_dst = &new_stroke->dvert[i];
- if (dvert_src->dw) {
- dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
- }
- e++;
- }
- }
- /* Each island corresponds to a new stroke.
- * We must adjust the timings of these new strokes:
- *
- * Each point's timing data is a delta from stroke's inittime, so as we erase some points
- * from the start of the stroke, we have to offset this inittime and all remaining points'
- * delta values. This way we get a new stroke with exactly the same timing as if user had
- * started drawing from the first non-removed point.
- */
- {
- bGPDspoint *pts;
- float delta = gps->points[island->start_idx].time;
- int j;
-
- new_stroke->inittime += (double)delta;
-
- pts = new_stroke->points;
- for (j = 0; j < new_stroke->totpoints; j++, pts++) {
- pts->time -= delta;
- /* set flag for select again later */
- if (select == true) {
- pts->flag &= ~GP_SPOINT_SELECT;
- pts->flag |= GP_SPOINT_TAG;
- }
- }
- }
-
- /* Add new stroke to the frame or delete if below limit */
- if ((limit > 0) && (new_stroke->totpoints <= limit)) {
- BKE_gpencil_free_stroke(new_stroke);
- }
- else {
- /* 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);
- }
- }
- }
- /* if cyclic, need to join last stroke with first stroke */
- if ((is_cyclic) && (gps_first != NULL) && (gps_first != new_stroke)) {
- gpencil_stroke_join_islands(gpd, gpf, gps_first, new_stroke);
- }
- }
-
- /* free islands */
- MEM_freeN(islands);
-
- /* Delete the old stroke */
- BLI_remlink(&gpf->strokes, gps);
- 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)
{
@@ -2987,12 +2619,12 @@ static int gpencil_delete_selected_points(bContext *C)
if (is_curve_edit) {
bGPDcurve *gpc = gps->editcurve;
- gpencil_curve_delete_tagged_points(
+ BKE_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(
+ BKE_gpencil_stroke_delete_tagged_points(
gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0);
}
@@ -3828,188 +3460,6 @@ void GPENCIL_OT_stroke_caps_set(wmOperatorType *ot)
/** \name Stroke Join Operator
* \{ */
-/* Helper: flip stroke */
-static void gpencil_flip_stroke(bGPDstroke *gps)
-{
- int end = gps->totpoints - 1;
-
- for (int i = 0; i < gps->totpoints / 2; i++) {
- bGPDspoint *point, *point2;
- bGPDspoint pt;
-
- /* save first point */
- point = &gps->points[i];
- pt.x = point->x;
- pt.y = point->y;
- pt.z = point->z;
- pt.flag = point->flag;
- pt.pressure = point->pressure;
- pt.strength = point->strength;
- pt.time = point->time;
- copy_v4_v4(pt.vert_color, point->vert_color);
-
- /* replace first point with last point */
- point2 = &gps->points[end];
- point->x = point2->x;
- point->y = point2->y;
- point->z = point2->z;
- point->flag = point2->flag;
- point->pressure = point2->pressure;
- point->strength = point2->strength;
- point->time = point2->time;
- copy_v4_v4(point->vert_color, point2->vert_color);
-
- /* replace last point with first saved before */
- point = &gps->points[end];
- point->x = pt.x;
- point->y = pt.y;
- point->z = pt.z;
- point->flag = pt.flag;
- point->pressure = pt.pressure;
- point->strength = pt.strength;
- point->time = pt.time;
- copy_v4_v4(point->vert_color, pt.vert_color);
-
- end--;
- }
-}
-
-/* Helper: copy point between strokes */
-static void gpencil_stroke_copy_point(bGPDstroke *gps,
- MDeformVert *dvert,
- bGPDspoint *point,
- const float delta[3],
- float pressure,
- float strength,
- float deltatime)
-{
- bGPDspoint *newpoint;
-
- gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1));
- if (gps->dvert != NULL) {
- gps->dvert = MEM_reallocN(gps->dvert, sizeof(MDeformVert) * (gps->totpoints + 1));
- }
- else {
- /* If destination has weight add weight to origin. */
- if (dvert != NULL) {
- gps->dvert = MEM_callocN(sizeof(MDeformVert) * (gps->totpoints + 1), __func__);
- }
- }
-
- gps->totpoints++;
- newpoint = &gps->points[gps->totpoints - 1];
-
- newpoint->x = point->x * delta[0];
- newpoint->y = point->y * delta[1];
- newpoint->z = point->z * delta[2];
- newpoint->flag = point->flag;
- newpoint->pressure = pressure;
- newpoint->strength = strength;
- newpoint->time = point->time + deltatime;
- copy_v4_v4(newpoint->vert_color, point->vert_color);
-
- if (gps->dvert != NULL) {
- MDeformVert *newdvert = &gps->dvert[gps->totpoints - 1];
-
- if (dvert != NULL) {
- newdvert->totweight = dvert->totweight;
- newdvert->dw = MEM_dupallocN(dvert->dw);
- }
- else {
- newdvert->totweight = 0;
- newdvert->dw = NULL;
- }
- }
-}
-
-/* Helper: join two strokes using the shortest distance (reorder stroke if necessary ) */
-static void gpencil_stroke_join_strokes(bGPDstroke *gps_a,
- bGPDstroke *gps_b,
- const bool leave_gaps)
-{
- bGPDspoint point;
- bGPDspoint *pt;
- int i;
- const float delta[3] = {1.0f, 1.0f, 1.0f};
- float deltatime = 0.0f;
-
- /* sanity checks */
- if (ELEM(NULL, gps_a, gps_b)) {
- return;
- }
-
- if ((gps_a->totpoints == 0) || (gps_b->totpoints == 0)) {
- return;
- }
-
- /* define start and end points of each stroke */
- float start_a[3], start_b[3], end_a[3], end_b[3];
- pt = &gps_a->points[0];
- copy_v3_v3(start_a, &pt->x);
-
- pt = &gps_a->points[gps_a->totpoints - 1];
- copy_v3_v3(end_a, &pt->x);
-
- pt = &gps_b->points[0];
- copy_v3_v3(start_b, &pt->x);
-
- pt = &gps_b->points[gps_b->totpoints - 1];
- copy_v3_v3(end_b, &pt->x);
-
- /* Check if need flip strokes. */
- float dist = len_squared_v3v3(end_a, start_b);
- bool flip_a = false;
- bool flip_b = false;
- float lowest = dist;
-
- dist = len_squared_v3v3(end_a, end_b);
- if (dist < lowest) {
- lowest = dist;
- flip_a = false;
- flip_b = true;
- }
-
- dist = len_squared_v3v3(start_a, start_b);
- if (dist < lowest) {
- lowest = dist;
- flip_a = true;
- flip_b = false;
- }
-
- dist = len_squared_v3v3(start_a, end_b);
- if (dist < lowest) {
- lowest = dist;
- flip_a = true;
- flip_b = true;
- }
-
- if (flip_a) {
- gpencil_flip_stroke(gps_a);
- }
- if (flip_b) {
- gpencil_flip_stroke(gps_b);
- }
-
- /* don't visibly link the first and last points? */
- if (leave_gaps) {
- /* 1st: add one tail point to start invisible area */
- point = gps_a->points[gps_a->totpoints - 1];
- deltatime = point.time;
-
- gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, 0.0f);
-
- /* 2nd: add one head point to finish invisible area */
- point = gps_b->points[0];
- gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, deltatime);
- }
-
- /* 3rd: add all points */
- for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) {
- MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : NULL;
- gpencil_stroke_copy_point(gps_a, dvert, pt, delta, pt->pressure, pt->strength, deltatime);
- }
-}
-
typedef struct tJoinStrokes {
bGPDframe *gpf;
bGPDstroke *gps;
@@ -4159,7 +3609,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op)
}
elem = &strokes_list[i];
/* Join new_stroke and stroke B. */
- gpencil_stroke_join_strokes(gps_new, elem->gps, leave_gaps);
+ BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true);
elem->used = true;
}
@@ -4254,8 +3704,8 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *op)
BKE_report(op->reports, RPT_ERROR, "Not implemented!");
}
else {
- /* flip stroke */
- gpencil_flip_stroke(gps);
+ /* Flip stroke. */
+ BKE_gpencil_stroke_flip(gps);
}
changed = true;
@@ -5108,11 +4558,11 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op)
}
/* delete selected points from destination stroke */
- gpencil_stroke_delete_tagged_points(
+ BKE_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(
+ BKE_gpencil_stroke_delete_tagged_points(
gpd_src, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0);
}
}
@@ -5287,11 +4737,11 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *op)
}
/* delete selected points from destination stroke */
- gpencil_stroke_delete_tagged_points(
+ BKE_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(
+ BKE_gpencil_stroke_delete_tagged_points(
gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0);
}
}
@@ -5487,7 +4937,7 @@ static void gpencil_cutter_dissolve(bGPdata *gpd,
}
}
- gpencil_stroke_delete_tagged_points(
+ BKE_gpencil_stroke_delete_tagged_points(
gpd, hit_layer->actframe, hit_stroke, gpsn, GP_SPOINT_TAG, false, 1);
}
}
diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h
index e4e9a3ae0ab..0bdd2033491 100644
--- a/source/blender/editors/gpencil/gpencil_intern.h
+++ b/source/blender/editors/gpencil/gpencil_intern.h
@@ -152,6 +152,31 @@ typedef struct tGPDinterpolate {
NumInput num; /* numeric input */
} tGPDinterpolate;
+/* Modal Operator Drawing Callbacks ------------------------ */
+void ED_gpencil_draw_fill(struct tGPDdraw *tgpw);
+
+/* ***************************************************** */
+/* Internal API */
+
+/* Stroke Coordinates API ------------------------------ */
+/* gpencil_utils.c */
+
+typedef struct GP_SpaceConversion {
+ struct Scene *scene;
+ struct Object *ob;
+ struct bGPdata *gpd;
+ struct bGPDlayer *gpl;
+
+ struct ScrArea *area;
+ struct ARegion *region;
+ struct View2D *v2d;
+
+ rctf *subrect; /* for using the camera rect within the 3d view */
+ rctf subrect_data;
+
+ float mat[4][4]; /* transform matrix on the strokes (introduced in [b770964]) */
+} GP_SpaceConversion;
+
/* Temporary primitive operation data */
typedef struct tGPDprimitive {
/** main database pointer */
@@ -180,6 +205,9 @@ typedef struct tGPDprimitive {
/** current brush */
struct Brush *brush;
+ /** Settings to pass to gp_points_to_xy(). */
+ GP_SpaceConversion gsc;
+
/** current frame number */
int cframe;
/** layer */
@@ -248,31 +276,6 @@ typedef struct tGPDprimitive {
} tGPDprimitive;
-/* Modal Operator Drawing Callbacks ------------------------ */
-void ED_gpencil_draw_fill(struct tGPDdraw *tgpw);
-
-/* ***************************************************** */
-/* Internal API */
-
-/* Stroke Coordinates API ------------------------------ */
-/* gpencil_utils.c */
-
-typedef struct GP_SpaceConversion {
- struct Scene *scene;
- struct Object *ob;
- struct bGPdata *gpd;
- struct bGPDlayer *gpl;
-
- struct ScrArea *area;
- struct ARegion *region;
- struct View2D *v2d;
-
- rctf *subrect; /* for using the camera rect within the 3d view */
- rctf subrect_data;
-
- float mat[4][4]; /* transform matrix on the strokes (introduced in [b770964]) */
-} GP_SpaceConversion;
-
bool gpencil_stroke_inside_circle(const float mval[2], int rad, int x0, int y0, int x1, int y1);
void gpencil_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc);
@@ -343,13 +346,6 @@ struct GHash *gpencil_copybuf_validate_colormap(struct bContext *C);
/* Stroke Editing ------------------------------------ */
-void gpencil_stroke_delete_tagged_points(bGPdata *gpd,
- bGPDframe *gpf,
- bGPDstroke *gps,
- bGPDstroke *next_stroke,
- int tag_flags,
- bool select,
- int limit);
int gpencil_delete_selected_point_wrap(bContext *C);
void gpencil_subdivide_stroke(bGPdata *gpd, bGPDstroke *gps, const int subdivide);
diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c
index f795ed01bb8..9f2bf3818a4 100644
--- a/source/blender/editors/gpencil/gpencil_merge.c
+++ b/source/blender/editors/gpencil/gpencil_merge.c
@@ -182,7 +182,7 @@ static void gpencil_dissolve_points(bContext *C)
}
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
- gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0);
+ BKE_gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0);
}
}
CTX_DATA_END;
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index e544093cd1d..14313a50118 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -1282,6 +1282,25 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
BKE_gpencil_stroke_trim(gpd, gps);
}
+ /* Join with existing strokes. */
+ if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) {
+ if (gps->prev != NULL) {
+ int pt_index = 0;
+ bool doit = true;
+ while (doit && gps) {
+ bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(
+ p->C, &p->gsc, gpl, gpl->actframe, gps, GPENCIL_MINIMUM_JOIN_DIST, &pt_index);
+ if (gps_target != NULL) {
+ gps = ED_gpencil_stroke_join_and_trim(p->gpd, p->gpf, gps, gps_target, pt_index);
+ }
+ else {
+ doit = false;
+ }
+ }
+ }
+ ED_gpencil_stroke_close_by_distance(gps, 0.02f);
+ }
+
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps);
@@ -1652,7 +1671,8 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p,
gpencil_stroke_soft_refine(gps);
}
- gpencil_stroke_delete_tagged_points(p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0);
+ BKE_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 801dacb3e6b..b457cd819d2 100644
--- a/source/blender/editors/gpencil/gpencil_primitive.c
+++ b/source/blender/editors/gpencil/gpencil_primitive.c
@@ -1197,6 +1197,9 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op)
/* set GP datablock */
tgpi->gpd = gpd;
+ /* Setup space conversions. */
+ gpencil_point_conversion_init(C, &tgpi->gsc);
+
/* if brush doesn't exist, create a new set (fix damaged files from old versions) */
if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) {
BKE_brush_gpencil_paint_presets(bmain, ts, true);
@@ -1347,6 +1350,28 @@ static void gpencil_primitive_interaction_end(bContext *C,
}
}
+ /* Join previous stroke. */
+ if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) {
+ if (ELEM(tgpi->type, GP_STROKE_ARC, GP_STROKE_LINE, GP_STROKE_CURVE, GP_STROKE_POLYLINE)) {
+ if (gps->prev != NULL) {
+ int pt_index = 0;
+ bool doit = true;
+ while (doit && gps) {
+ bGPDstroke *gps_target = ED_gpencil_stroke_nearest_to_ends(
+ C, &tgpi->gsc, tgpi->gpl, gpf, gps, GPENCIL_MINIMUM_JOIN_DIST, &pt_index);
+ if (gps_target != NULL) {
+ gps = ED_gpencil_stroke_join_and_trim(tgpi->gpd, gpf, gps, gps_target, pt_index);
+ }
+ else {
+ doit = false;
+ }
+ }
+ }
+ ED_gpencil_stroke_close_by_distance(gps, 0.02f);
+ }
+ BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps);
+ }
+
DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_COPY_ON_WRITE);
DEG_id_tag_update(&tgpi->gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c
index 628a35f8c76..af63c5ca3b2 100644
--- a/source/blender/editors/gpencil/gpencil_utils.c
+++ b/source/blender/editors/gpencil/gpencil_utils.c
@@ -3105,3 +3105,181 @@ bool ED_gpencil_stroke_point_is_inside(bGPDstroke *gps,
return hit;
}
+
+bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C,
+ GP_SpaceConversion *gsc,
+ bGPDlayer *gpl,
+ bGPDframe *gpf,
+ bGPDstroke *gps,
+ const float radius,
+ int *r_index)
+{
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ Object *ob = CTX_data_active_object(C);
+ bGPDstroke *gps_rtn = NULL;
+ const float radius_sqr = radius * radius;
+
+ /* calculate difference matrix object */
+ float diff_mat[4][4];
+ BKE_gpencil_parent_matrix_get(depsgraph, ob, gpl, diff_mat);
+
+ /* Calculate the extremes of the stroke in 2D. */
+ bGPDspoint pt_parent;
+ float pt2d_start[2], pt2d_end[2];
+
+ bGPDspoint *pt = &gps->points[0];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
+ gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_start[0], &pt2d_start[1]);
+
+ pt = &gps->points[gps->totpoints - 1];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
+ gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_end[0], &pt2d_end[1]);
+
+ /* Loop all strokes of the active frame. */
+ float dist_min = FLT_MAX;
+ LISTBASE_FOREACH (bGPDstroke *, gps_target, &gpf->strokes) {
+ /* Check if the color is editable. */
+ if ((gps_target == gps) || (ED_gpencil_stroke_color_use(ob, gpl, gps) == false)) {
+ continue;
+ }
+
+ /* Check if one of the ends is inside target stroke bounding box. */
+ if (!ED_gpencil_stroke_check_collision(gsc, gps, pt2d_start, radius, diff_mat)) {
+ continue;
+ }
+ if (!ED_gpencil_stroke_check_collision(gsc, gps, pt2d_end, radius, diff_mat)) {
+ continue;
+ }
+ /* Check the distance of the ends with the ends of target stroke to avoid middle contact.
+ * All is done in 2D plane. */
+ float pt2d_target_start[2], pt2d_target_end[2];
+
+ pt = &gps_target->points[0];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
+ gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_start[0], &pt2d_target_start[1]);
+
+ pt = &gps_target->points[gps_target->totpoints - 1];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
+ gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_end[0], &pt2d_target_end[1]);
+
+ if ((len_squared_v2v2(pt2d_start, pt2d_target_start) > radius_sqr) &&
+ (len_squared_v2v2(pt2d_start, pt2d_target_end) > radius_sqr) &&
+ (len_squared_v2v2(pt2d_end, pt2d_target_start) > radius_sqr) &&
+ (len_squared_v2v2(pt2d_end, pt2d_target_end) > radius_sqr)) {
+ continue;
+ }
+
+ /* Loop all points and check what is the nearest point. */
+ int i;
+ for (i = 0, pt = gps_target->points; i < gps_target->totpoints; i++, pt++) {
+ /* Convert point to 2D. */
+ float pt2d[2];
+ gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
+ gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d[0], &pt2d[1]);
+
+ /* Check with Start point. */
+ float dist = len_squared_v2v2(pt2d, pt2d_start);
+ if ((dist <= radius_sqr) && (dist < dist_min)) {
+ *r_index = i;
+ dist_min = dist;
+ gps_rtn = gps_target;
+ }
+ /* Check with End point. */
+ dist = len_squared_v2v2(pt2d, pt2d_end);
+ if ((dist <= radius_sqr) && (dist < dist_min)) {
+ *r_index = i;
+ dist_min = dist;
+ gps_rtn = gps_target;
+ }
+ }
+ }
+
+ return gps_rtn;
+}
+
+/* Join two stroke using a contact point index and trimming the rest. */
+bGPDstroke *ED_gpencil_stroke_join_and_trim(
+ bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *gps_dst, const int pt_index)
+{
+ if ((gps->totpoints < 1) || (gps_dst->totpoints < 1)) {
+ return false;
+ }
+ BLI_assert(pt_index >= 0 && pt_index < gps_dst->totpoints);
+
+ bGPDspoint *pt = NULL;
+
+ /* Cannot be cyclic. */
+ gps->flag &= ~GP_STROKE_CYCLIC;
+ gps_dst->flag &= ~GP_STROKE_CYCLIC;
+
+ /* Trim stroke. */
+ bGPDstroke *gps_final = gps_dst;
+ if ((pt_index > 0) && (pt_index < gps_dst->totpoints - 2)) {
+ /* Untag any pending operation. */
+ gps_dst->flag &= ~GP_STROKE_TAG;
+ for (int i = 0; i < gps_dst->totpoints; i++) {
+ gps_dst->points[i].flag &= ~GP_SPOINT_TAG;
+ }
+
+ /* Delete points of the shorter extreme */
+ pt = &gps_dst->points[0];
+ float dist_to_start = BKE_gpencil_stroke_segment_length(gps_dst, 0, pt_index, true);
+ pt = &gps_dst->points[gps_dst->totpoints - 1];
+ float dist_to_end = BKE_gpencil_stroke_segment_length(
+ gps_dst, pt_index, gps_dst->totpoints - 1, true);
+
+ if (dist_to_start < dist_to_end) {
+ for (int i = 0; i < pt_index; i++) {
+ gps_dst->points[i].flag |= GP_SPOINT_TAG;
+ }
+ }
+ else {
+ for (int i = pt_index + 1; i < gps_dst->totpoints; i++) {
+ gps_dst->points[i].flag |= GP_SPOINT_TAG;
+ }
+ }
+ /* Remove tagged points to trim stroke. */
+ gps_final = BKE_gpencil_stroke_delete_tagged_points(
+ gpd, gpf, gps_dst, gps_dst->next, GP_SPOINT_TAG, false, 0);
+ }
+
+ /* Join both strokes. */
+ int totpoint = gps_final->totpoints;
+ BKE_gpencil_stroke_join(gps_final, gps, false, true);
+
+ /* Select the join points and merge if the distance is very small. */
+ pt = &gps_final->points[totpoint - 1];
+ pt->flag |= GP_SPOINT_SELECT;
+
+ pt = &gps_final->points[totpoint];
+ pt->flag |= GP_SPOINT_SELECT;
+ BKE_gpencil_stroke_merge_distance(gpd, gpf, gps_final, 0.01f, false);
+
+ /* Unselect all points. */
+ for (int i = 0; i < gps_final->totpoints; i++) {
+ gps_final->points[i].flag &= ~GP_SPOINT_SELECT;
+ }
+
+ /* Delete old stroke. */
+ BLI_remlink(&gpf->strokes, gps);
+ BKE_gpencil_free_stroke(gps);
+
+ return gps_final;
+}
+
+/* Close if the distance between extremes is below threshold. */
+void ED_gpencil_stroke_close_by_distance(bGPDstroke *gps, const float threshold)
+{
+ if (gps == NULL) {
+ return;
+ }
+ bGPDspoint *pt_start = &gps->points[0];
+ bGPDspoint *pt_end = &gps->points[gps->totpoints - 1];
+
+ const float threshold_sqr = threshold * threshold;
+ float dist_to_close = len_squared_v3v3(&pt_start->x, &pt_end->x);
+ if (dist_to_close < threshold_sqr) {
+ gps->flag |= GP_STROKE_CYCLIC;
+ BKE_gpencil_stroke_close(gps);
+ }
+}
diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h
index 17aa407bd76..be2f714dfe1 100644
--- a/source/blender/editors/include/ED_gpencil.h
+++ b/source/blender/editors/include/ED_gpencil.h
@@ -63,6 +63,8 @@ struct bAnimContext;
struct wmKeyConfig;
struct wmOperator;
+#define GPENCIL_MINIMUM_JOIN_DIST 20.0f
+
/* Reproject stroke modes. */
typedef enum eGP_ReprojectModes {
/* Axis */
@@ -366,6 +368,22 @@ bool ED_gpencil_stroke_point_is_inside(struct bGPDstroke *gps,
int mouse[2],
const float diff_mat[4][4]);
+struct bGPDstroke *ED_gpencil_stroke_nearest_to_ends(struct bContext *C,
+ struct GP_SpaceConversion *gsc,
+ struct bGPDlayer *gpl,
+ struct bGPDframe *gpf,
+ struct bGPDstroke *gps,
+ const float radius,
+ int *r_index);
+
+struct bGPDstroke *ED_gpencil_stroke_join_and_trim(struct bGPdata *gpd,
+ struct bGPDframe *gpf,
+ struct bGPDstroke *gps,
+ struct bGPDstroke *gps_dst,
+ const int pt_index);
+
+void ED_gpencil_stroke_close_by_distance(struct bGPDstroke *gps, const float threshold);
+
#ifdef __cplusplus
}
#endif