99d37f87da790f58dfa6c7af0d6cae68cf6e39dd
[blender.git] / source / blender / editors / gpencil / gpencil_edit.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008, Blender Foundation
17  * This is a new part of Blender
18  * Operators for editing Grease Pencil strokes
19  */
20
21 /** \file
22  * \ingroup edgpencil
23  */
24
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stddef.h>
29 #include <math.h>
30
31 #include "MEM_guardedalloc.h"
32
33 #include "BLI_blenlib.h"
34 #include "BLI_ghash.h"
35 #include "BLI_lasso_2d.h"
36 #include "BLI_math.h"
37 #include "BLI_string.h"
38 #include "BLI_utildefines.h"
39
40 #include "BLT_translation.h"
41
42 #include "DNA_meshdata_types.h"
43 #include "DNA_object_types.h"
44 #include "DNA_scene_types.h"
45 #include "DNA_screen_types.h"
46 #include "DNA_space_types.h"
47 #include "DNA_view3d_types.h"
48 #include "DNA_gpencil_types.h"
49
50 #include "BKE_brush.h"
51 #include "BKE_context.h"
52 #include "BKE_global.h"
53 #include "BKE_gpencil.h"
54 #include "BKE_library.h"
55 #include "BKE_main.h"
56 #include "BKE_material.h"
57 #include "BKE_object.h"
58 #include "BKE_paint.h"
59 #include "BKE_report.h"
60 #include "BKE_workspace.h"
61
62 #include "UI_interface.h"
63 #include "UI_resources.h"
64
65 #include "WM_api.h"
66 #include "WM_types.h"
67 #include "WM_message.h"
68 #include "WM_toolsystem.h"
69
70 #include "RNA_access.h"
71 #include "RNA_define.h"
72 #include "RNA_enum_types.h"
73
74 #include "UI_view2d.h"
75
76 #include "ED_gpencil.h"
77 #include "ED_object.h"
78 #include "ED_screen.h"
79 #include "ED_view3d.h"
80 #include "ED_select_utils.h"
81 #include "ED_space_api.h"
82
83 #include "DEG_depsgraph.h"
84 #include "DEG_depsgraph_build.h"
85 #include "DEG_depsgraph_query.h"
86
87 #include "gpencil_intern.h"
88
89   /* ************************************************ */
90   /* Stroke Edit Mode Management */
91
92 /* poll callback for all stroke editing operators */
93 static bool gp_stroke_edit_poll(bContext *C)
94 {
95         /* edit only supported with grease pencil objects */
96         Object *ob = CTX_data_active_object(C);
97         if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
98                 return false;
99         }
100
101         /* NOTE: this is a bit slower, but is the most accurate... */
102         return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
103 }
104
105 /* poll callback to verify edit mode in 3D view only */
106 static bool gp_strokes_edit3d_poll(bContext *C)
107 {
108         /* edit only supported with grease pencil objects */
109         Object *ob = CTX_data_active_object(C);
110         if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
111                 return false;
112         }
113
114
115         /* 2 Requirements:
116          * - 1) Editable GP data
117          * - 2) 3D View only
118          */
119         return (gp_stroke_edit_poll(C) && ED_operator_view3d_active(C));
120 }
121
122 static bool gpencil_editmode_toggle_poll(bContext *C)
123 {
124         /* edit only supported with grease pencil objects */
125         Object *ob = CTX_data_active_object(C);
126         if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
127                 return false;
128         }
129
130         /* if using gpencil object, use this gpd */
131         if (ob->type == OB_GPENCIL) {
132                 return ob->data != NULL;
133         }
134
135         return ED_gpencil_data_get_active(C) != NULL;
136 }
137
138 static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *op)
139 {
140         const int back = RNA_boolean_get(op->ptr, "back");
141
142         struct wmMsgBus *mbus = CTX_wm_message_bus(C);
143         Depsgraph *depsgraph = CTX_data_depsgraph(C);
144         bGPdata *gpd = ED_gpencil_data_get_active(C);
145         bool is_object = false;
146         short mode;
147         /* if using a gpencil object, use this datablock */
148         Object *ob = CTX_data_active_object(C);
149         if ((ob) && (ob->type == OB_GPENCIL)) {
150                 gpd = ob->data;
151                 is_object = true;
152         }
153
154         if (gpd == NULL) {
155                 BKE_report(op->reports, RPT_ERROR, "No active GP data");
156                 return OPERATOR_CANCELLED;
157         }
158
159         /* Just toggle editmode flag... */
160         gpd->flag ^= GP_DATA_STROKE_EDITMODE;
161         /* recalculate parent matrix */
162         if (gpd->flag & GP_DATA_STROKE_EDITMODE) {
163                 ED_gpencil_reset_layers_parent(depsgraph, ob, gpd);
164         }
165         /* set mode */
166         if (gpd->flag & GP_DATA_STROKE_EDITMODE) {
167                 mode = OB_MODE_EDIT_GPENCIL;
168         }
169         else {
170                 mode = OB_MODE_OBJECT;
171         }
172
173         if (is_object) {
174                 /* try to back previous mode */
175                 if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_EDITMODE) == 0) && (back == 1)) {
176                         mode = ob->restore_mode;
177                 }
178                 ob->restore_mode = ob->mode;
179                 ob->mode = mode;
180         }
181
182         /* setup other modes */
183         ED_gpencil_setup_modes(C, gpd, mode);
184         /* set cache as dirty */
185         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
186
187         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL);
188         WM_event_add_notifier(C, NC_GPENCIL | ND_GPENCIL_EDITMODE, NULL);
189         WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL);
190
191         if (is_object) {
192                 WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
193         }
194         if (G.background == false) {
195                 WM_toolsystem_update_from_context_view3d(C);
196         }
197
198         return OPERATOR_FINISHED;
199 }
200
201 void GPENCIL_OT_editmode_toggle(wmOperatorType *ot)
202 {
203         PropertyRNA *prop;
204
205         /* identifiers */
206         ot->name = "Strokes Edit Mode Toggle";
207         ot->idname = "GPENCIL_OT_editmode_toggle";
208         ot->description = "Enter/Exit edit mode for Grease Pencil strokes";
209
210         /* callbacks */
211         ot->exec = gpencil_editmode_toggle_exec;
212         ot->poll = gpencil_editmode_toggle_poll;
213
214         /* flags */
215         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
216
217         /* properties */
218         prop = RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode");
219         RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
220 }
221
222 /* set select mode */
223 static int gpencil_selectmode_toggle_exec(bContext *C, wmOperator *op)
224 {
225         Scene *scene = CTX_data_scene(C);
226         ToolSettings *ts = CTX_data_tool_settings(C);
227         const int mode = RNA_int_get(op->ptr, "mode");
228
229         /* Just set mode */
230         ts->gpencil_selectmode = mode;
231
232         WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL);
233         DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
234
235         return OPERATOR_FINISHED;
236 }
237
238 void GPENCIL_OT_selectmode_toggle(wmOperatorType *ot)
239 {
240         PropertyRNA *prop;
241
242         /* identifiers */
243         ot->name = "Select Mode Toggle";
244         ot->idname = "GPENCIL_OT_selectmode_toggle";
245         ot->description = "Set selection mode for Grease Pencil strokes";
246
247         /* callbacks */
248         ot->exec = gpencil_selectmode_toggle_exec;
249         ot->poll = gp_strokes_edit3d_poll;
250
251         /* flags */
252         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
253
254         /* properties */
255         prop = RNA_def_int(ot->srna, "mode", 0, 0, 2, "Select mode", "Select mode", 0, 2);
256         RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
257 }
258
259 /* Stroke Paint Mode Management */
260
261 static bool gpencil_paintmode_toggle_poll(bContext *C)
262 {
263         /* if using gpencil object, use this gpd */
264         Object *ob = CTX_data_active_object(C);
265         if ((ob) && (ob->type == OB_GPENCIL)) {
266                 return ob->data != NULL;
267         }
268         return ED_gpencil_data_get_active(C) != NULL;
269 }
270
271 static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op)
272 {
273         const bool back = RNA_boolean_get(op->ptr, "back");
274
275         struct wmMsgBus *mbus = CTX_wm_message_bus(C);
276         Main *bmain = CTX_data_main(C);
277         bGPdata *gpd = ED_gpencil_data_get_active(C);
278         ToolSettings *ts = CTX_data_tool_settings(C);
279
280         bool is_object = false;
281         short mode;
282         /* if using a gpencil object, use this datablock */
283         Object *ob = CTX_data_active_object(C);
284         if ((ob) && (ob->type == OB_GPENCIL)) {
285                 gpd = ob->data;
286                 is_object = true;
287         }
288
289         if (gpd == NULL)
290                 return OPERATOR_CANCELLED;
291
292         /* Just toggle paintmode flag... */
293         gpd->flag ^= GP_DATA_STROKE_PAINTMODE;
294         /* set mode */
295         if (gpd->flag & GP_DATA_STROKE_PAINTMODE) {
296                 mode = OB_MODE_PAINT_GPENCIL;
297         }
298         else {
299                 mode = OB_MODE_OBJECT;
300         }
301
302         if (is_object) {
303                 /* try to back previous mode */
304                 if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0) && (back == 1)) {
305                         mode = ob->restore_mode;
306                 }
307                 ob->restore_mode = ob->mode;
308                 ob->mode = mode;
309         }
310
311         if (mode == OB_MODE_PAINT_GPENCIL) {
312                 /* be sure we have brushes */
313                 BKE_paint_ensure(ts, (Paint **)&ts->gp_paint);
314                 Paint *paint = &ts->gp_paint->paint;
315                 /* if not exist, create a new one */
316                 if (paint->brush == NULL) {
317                         BKE_brush_gpencil_presets(C);
318                 }
319                 BKE_paint_toolslots_brush_validate(bmain, &ts->gp_paint->paint);
320         }
321
322         /* setup other modes */
323         ED_gpencil_setup_modes(C, gpd, mode);
324         /* set cache as dirty */
325         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
326
327         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL);
328         WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL);
329
330         if (is_object) {
331                 WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
332         }
333         if (G.background == false) {
334                 WM_toolsystem_update_from_context_view3d(C);
335         }
336
337         return OPERATOR_FINISHED;
338 }
339
340 void GPENCIL_OT_paintmode_toggle(wmOperatorType *ot)
341 {
342         PropertyRNA *prop;
343
344         /* identifiers */
345         ot->name = "Strokes Paint Mode Toggle";
346         ot->idname = "GPENCIL_OT_paintmode_toggle";
347         ot->description = "Enter/Exit paint mode for Grease Pencil strokes";
348
349         /* callbacks */
350         ot->exec = gpencil_paintmode_toggle_exec;
351         ot->poll = gpencil_paintmode_toggle_poll;
352
353         /* flags */
354         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
355
356         /* properties */
357         prop = RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode");
358         RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
359 }
360
361 /* Stroke Sculpt Mode Management */
362
363 static bool gpencil_sculptmode_toggle_poll(bContext *C)
364 {
365         /* if using gpencil object, use this gpd */
366         Object *ob = CTX_data_active_object(C);
367         if ((ob) && (ob->type == OB_GPENCIL)) {
368                 return ob->data != NULL;
369         }
370         return ED_gpencil_data_get_active(C) != NULL;
371 }
372
373 static int gpencil_sculptmode_toggle_exec(bContext *C, wmOperator *op)
374 {
375         const bool back = RNA_boolean_get(op->ptr, "back");
376
377         struct wmMsgBus *mbus = CTX_wm_message_bus(C);
378         bGPdata *gpd = ED_gpencil_data_get_active(C);
379         bool is_object = false;
380         short mode;
381         /* if using a gpencil object, use this datablock */
382         Object *ob = CTX_data_active_object(C);
383         if ((ob) && (ob->type == OB_GPENCIL)) {
384                 gpd = ob->data;
385                 is_object = true;
386         }
387
388         if (gpd == NULL)
389                 return OPERATOR_CANCELLED;
390
391         /* Just toggle sculptmode flag... */
392         gpd->flag ^= GP_DATA_STROKE_SCULPTMODE;
393         /* set mode */
394         if (gpd->flag & GP_DATA_STROKE_SCULPTMODE) {
395                 mode = OB_MODE_SCULPT_GPENCIL;
396         }
397         else {
398                 mode = OB_MODE_OBJECT;
399         }
400
401         if (is_object) {
402                 /* try to back previous mode */
403                 if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_SCULPTMODE) == 0) && (back == 1)) {
404                         mode = ob->restore_mode;
405                 }
406                 ob->restore_mode = ob->mode;
407                 ob->mode = mode;
408         }
409
410         /* setup other modes */
411         ED_gpencil_setup_modes(C, gpd, mode);
412         /* set cache as dirty */
413         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
414
415         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL);
416         WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL);
417
418         if (is_object) {
419                 WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
420         }
421         if (G.background == false) {
422                 WM_toolsystem_update_from_context_view3d(C);
423         }
424
425         return OPERATOR_FINISHED;
426 }
427
428 void GPENCIL_OT_sculptmode_toggle(wmOperatorType *ot)
429 {
430         PropertyRNA *prop;
431
432         /* identifiers */
433         ot->name = "Strokes Sculpt Mode Toggle";
434         ot->idname = "GPENCIL_OT_sculptmode_toggle";
435         ot->description = "Enter/Exit sculpt mode for Grease Pencil strokes";
436
437         /* callbacks */
438         ot->exec = gpencil_sculptmode_toggle_exec;
439         ot->poll = gpencil_sculptmode_toggle_poll;
440
441         /* flags */
442         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
443
444         /* properties */
445         prop = RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode");
446         RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
447 }
448
449 /* Stroke Weight Paint Mode Management */
450
451 static bool gpencil_weightmode_toggle_poll(bContext *C)
452 {
453         /* if using gpencil object, use this gpd */
454         Object *ob = CTX_data_active_object(C);
455         if ((ob) && (ob->type == OB_GPENCIL)) {
456                 return ob->data != NULL;
457         }
458         return ED_gpencil_data_get_active(C) != NULL;
459 }
460
461 static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op)
462 {
463         const bool back = RNA_boolean_get(op->ptr, "back");
464
465         struct wmMsgBus *mbus = CTX_wm_message_bus(C);
466         bGPdata *gpd = ED_gpencil_data_get_active(C);
467         bool is_object = false;
468         short mode;
469         /* if using a gpencil object, use this datablock */
470         Object *ob = CTX_data_active_object(C);
471         if ((ob) && (ob->type == OB_GPENCIL)) {
472                 gpd = ob->data;
473                 is_object = true;
474         }
475
476         if (gpd == NULL)
477                 return OPERATOR_CANCELLED;
478
479         /* Just toggle weightmode flag... */
480         gpd->flag ^= GP_DATA_STROKE_WEIGHTMODE;
481         /* set mode */
482         if (gpd->flag & GP_DATA_STROKE_WEIGHTMODE) {
483                 mode = OB_MODE_WEIGHT_GPENCIL;
484         }
485         else {
486                 mode = OB_MODE_OBJECT;
487         }
488
489         if (is_object) {
490                 /* try to back previous mode */
491                 if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_WEIGHTMODE) == 0) && (back == 1)) {
492                         mode = ob->restore_mode;
493                 }
494                 ob->restore_mode = ob->mode;
495                 ob->mode = mode;
496         }
497
498         /* setup other modes */
499         ED_gpencil_setup_modes(C, gpd, mode);
500         /* set cache as dirty */
501         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
502
503         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL);
504         WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL);
505
506         if (is_object) {
507                 WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
508         }
509         if (G.background == false) {
510                 WM_toolsystem_update_from_context_view3d(C);
511         }
512
513         return OPERATOR_FINISHED;
514 }
515
516 void GPENCIL_OT_weightmode_toggle(wmOperatorType *ot)
517 {
518         PropertyRNA *prop;
519
520         /* identifiers */
521         ot->name = "Strokes Weight Mode Toggle";
522         ot->idname = "GPENCIL_OT_weightmode_toggle";
523         ot->description = "Enter/Exit weight paint mode for Grease Pencil strokes";
524
525         /* callbacks */
526         ot->exec = gpencil_weightmode_toggle_exec;
527         ot->poll = gpencil_weightmode_toggle_poll;
528
529         /* flags */
530         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
531
532         /* properties */
533         prop = RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode");
534         RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
535 }
536
537 /* ************************************************ */
538 /* Stroke Editing Operators */
539
540 /* ************ Stroke Hide selection Toggle ************** */
541
542 static int gpencil_hideselect_toggle_exec(bContext *C, wmOperator *UNUSED(op))
543 {
544         View3D *v3d = CTX_wm_view3d(C);
545         if (v3d == NULL)
546                 return OPERATOR_CANCELLED;
547
548         /* Just toggle alpha... */
549         if (v3d->vertex_opacity > 0.0f) {
550                 v3d->vertex_opacity = 0.0f;
551         }
552         else {
553                 v3d->vertex_opacity = 1.0f;
554         }
555
556         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL);
557         WM_event_add_notifier(C, NC_GPENCIL | ND_GPENCIL_EDITMODE, NULL);
558         WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL);
559
560         return OPERATOR_FINISHED;
561 }
562
563 void GPENCIL_OT_selection_opacity_toggle(wmOperatorType *ot)
564 {
565         /* identifiers */
566         ot->name = "Hide Selected";
567         ot->idname = "GPENCIL_OT_selection_opacity_toggle";
568         ot->description = "Hide/Unhide selected points for Grease Pencil strokes setting alpha factor";
569
570         /* callbacks */
571         ot->exec = gpencil_hideselect_toggle_exec;
572         ot->poll = gp_stroke_edit_poll;
573
574         /* flags */
575         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
576 }
577
578 /* ************** Duplicate Selected Strokes **************** */
579
580 /* Make copies of selected point segments in a selected stroke */
581 static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes, const char *layername)
582 {
583         bGPDspoint *pt;
584         int i;
585
586         int start_idx = -1;
587
588
589         /* Step through the original stroke's points:
590          * - We accumulate selected points (from start_idx to current index)
591          *   and then convert that to a new stroke
592          */
593         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
594                 /* searching for start, are waiting for end? */
595                 if (start_idx == -1) {
596                         /* is this the first selected point for a new island? */
597                         if (pt->flag & GP_SPOINT_SELECT) {
598                                 start_idx = i;
599                         }
600                 }
601                 else {
602                         size_t len = 0;
603
604                         /* is this the end of current island yet?
605                          * 1) Point i-1 was the last one that was selected
606                          * 2) Point i is the last in the array
607                          */
608                         if ((pt->flag & GP_SPOINT_SELECT) == 0) {
609                                 len = i - start_idx;
610                         }
611                         else if (i == gps->totpoints - 1) {
612                                 len = i - start_idx + 1;
613                         }
614                         //printf("copying from %d to %d = %d\n", start_idx, i, len);
615
616                         /* make copies of the relevant data */
617                         if (len) {
618                                 bGPDstroke *gpsd;
619
620                                 /* make a stupid copy first of the entire stroke (to get the flags too) */
621                                 gpsd = MEM_dupallocN(gps);
622
623                                 /* saves original layer name */
624                                 BLI_strncpy(gpsd->runtime.tmp_layerinfo, layername, sizeof(gpsd->runtime.tmp_layerinfo));
625
626                                 /* initialize triangle memory - will be calculated on next redraw */
627                                 gpsd->triangles = NULL;
628                                 gpsd->flag |= GP_STROKE_RECALC_GEOMETRY;
629                                 gpsd->tot_triangles = 0;
630
631                                 /* now, make a new points array, and copy of the relevant parts */
632                                 gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy");
633                                 memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len);
634                                 gpsd->totpoints = len;
635
636                                 if (gps->dvert != NULL) {
637                                         gpsd->dvert = MEM_callocN(sizeof(MDeformVert) * len, "gps stroke weights copy");
638                                         memcpy(gpsd->dvert, gps->dvert + start_idx, sizeof(MDeformVert) * len);
639
640                                         /* Copy weights */
641                                         int e = start_idx;
642                                         for (int j = 0; j < gpsd->totpoints; j++) {
643                                                 MDeformVert *dvert_dst = &gps->dvert[e];
644                                                 MDeformVert *dvert_src = &gps->dvert[j];
645                                                 dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
646                                                 e++;
647                                         }
648                                 }
649
650                                 /* add to temp buffer */
651                                 gpsd->next = gpsd->prev = NULL;
652                                 BLI_addtail(new_strokes, gpsd);
653
654                                 /* cleanup + reset for next */
655                                 start_idx = -1;
656                         }
657                 }
658         }
659 }
660
661 static int gp_duplicate_exec(bContext *C, wmOperator *op)
662 {
663         bGPdata *gpd = ED_gpencil_data_get_active(C);
664
665         if (gpd == NULL) {
666                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
667                 return OPERATOR_CANCELLED;
668         }
669
670         if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) {
671                 BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition");
672                 return OPERATOR_CANCELLED;
673         }
674
675         /* for each visible (and editable) layer's selected strokes,
676          * copy the strokes into a temporary buffer, then append
677          * once all done
678          */
679         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
680         {
681                 ListBase new_strokes = {NULL, NULL};
682                 bGPDframe *gpf = gpl->actframe;
683                 bGPDstroke *gps;
684
685                 if (gpf == NULL)
686                         continue;
687
688                 /* make copies of selected strokes, and deselect these once we're done */
689                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
690                         /* skip strokes that are invalid for current view */
691                         if (ED_gpencil_stroke_can_use(C, gps) == false) {
692                                 continue;
693                         }
694
695                         if (gps->flag & GP_STROKE_SELECT) {
696                                 if (gps->totpoints == 1) {
697                                         /* Special Case: If there's just a single point in this stroke... */
698                                         bGPDstroke *gpsd;
699
700                                         /* make direct copies of the stroke and its points */
701                                         gpsd = MEM_dupallocN(gps);
702                                         BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo));
703                                         gpsd->points = MEM_dupallocN(gps->points);
704                                         if (gps->dvert != NULL) {
705                                                 gpsd->dvert = MEM_dupallocN(gps->dvert);
706                                                 BKE_gpencil_stroke_weights_duplicate(gps, gpsd);
707                                         }
708
709                                         /* triangle information - will be calculated on next redraw */
710                                         gpsd->flag |= GP_STROKE_RECALC_GEOMETRY;
711                                         gpsd->triangles = NULL;
712
713                                         /* add to temp buffer */
714                                         gpsd->next = gpsd->prev = NULL;
715                                         BLI_addtail(&new_strokes, gpsd);
716                                 }
717                                 else {
718                                         /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
719                                         gp_duplicate_points(gps, &new_strokes, gpl->info);
720                                 }
721
722                                 /* deselect original stroke, or else the originals get moved too
723                                  * (when using the copy + move macro)
724                                  */
725                                 gps->flag &= ~GP_STROKE_SELECT;
726                         }
727                 }
728
729                 /* add all new strokes in temp buffer to the frame (preventing double-copies) */
730                 BLI_movelisttolist(&gpf->strokes, &new_strokes);
731                 BLI_assert(new_strokes.first == NULL);
732         }
733         CTX_DATA_END;
734
735         /* updates */
736         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
737         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
738
739         return OPERATOR_FINISHED;
740 }
741
742 void GPENCIL_OT_duplicate(wmOperatorType *ot)
743 {
744         /* identifiers */
745         ot->name = "Duplicate Strokes";
746         ot->idname = "GPENCIL_OT_duplicate";
747         ot->description = "Duplicate the selected Grease Pencil strokes";
748
749         /* callbacks */
750         ot->exec = gp_duplicate_exec;
751         ot->poll = gp_stroke_edit_poll;
752
753         /* flags */
754         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
755 }
756
757 /* ************** Extrude Selected Strokes **************** */
758
759 /* helper to copy a point to temp area */
760 static void copy_move_point(
761         bGPDstroke *gps,
762         bGPDspoint *temp_points,
763         MDeformVert *temp_dverts,
764         int from_idx, int to_idx, const bool copy)
765 {
766         bGPDspoint *pt = &temp_points[from_idx];
767         bGPDspoint *pt_final = &gps->points[to_idx];
768
769         copy_v3_v3(&pt_final->x, &pt->x);
770         pt_final->pressure = pt->pressure;
771         pt_final->strength = pt->strength;
772         pt_final->time = pt->time;
773         pt_final->flag = pt->flag;
774         pt_final->uv_fac = pt->uv_fac;
775         pt_final->uv_rot = pt->uv_rot;
776
777         if (gps->dvert != NULL) {
778                 MDeformVert *dvert = &temp_dverts[from_idx];
779                 MDeformVert *dvert_final = &gps->dvert[to_idx];
780
781                 dvert_final->totweight = dvert->totweight;
782                 /* if copy, duplicate memory, otherwise move only the pointer */
783                 if (copy) {
784                         dvert_final->dw = MEM_dupallocN(dvert->dw);
785                 }
786                 else {
787                         dvert_final->dw = dvert->dw;
788                 }
789         }
790 }
791
792 static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps)
793 {
794         bGPDspoint *temp_points = NULL;
795         MDeformVert *temp_dverts = NULL;
796         bGPDspoint *pt = NULL;
797         const bGPDspoint *pt_start = &gps->points[0];
798         const bGPDspoint *pt_last = &gps->points[gps->totpoints - 1];
799         const bool do_first = (pt_start->flag & GP_SPOINT_SELECT);
800         const bool do_last = ((pt_last->flag & GP_SPOINT_SELECT) && (pt_start != pt_last));
801         const bool do_stroke = (do_first || do_last);
802
803         /* review points in the middle of stroke to create new strokes */
804         for (int i = 0; i < gps->totpoints; i++) {
805                 /* skip first and last point */
806                 if ((i == 0) || (i == gps->totpoints - 1)) {
807                         continue;
808                 }
809
810                 pt = &gps->points[i];
811                 if (pt->flag == GP_SPOINT_SELECT) {
812                         /* duplicate original stroke data */
813                         bGPDstroke *gps_new = MEM_dupallocN(gps);
814                         gps_new->prev = gps_new->next = NULL;
815
816                         /* add new points array */
817                         gps_new->totpoints = 1;
818                         gps_new->points = MEM_callocN(sizeof(bGPDspoint), __func__);
819                         gps_new->dvert = NULL;
820
821                         if (gps->dvert != NULL) {
822                                 gps_new->dvert = MEM_callocN(sizeof(MDeformVert), __func__);
823                         }
824
825                         gps->flag |= GP_STROKE_RECALC_GEOMETRY;
826                         gps_new->triangles = NULL;
827                         gps_new->tot_triangles = 0;
828                         BLI_insertlinkafter(&gpf->strokes, gps, gps_new);
829
830                         /* copy selected point data to new stroke */
831                         copy_move_point(gps_new, gps->points, gps->dvert, i, 0, true);
832
833                         /* deselect orinal point */
834                         pt->flag &= ~GP_SPOINT_SELECT;
835                 }
836         }
837
838         /* review first and last point to reuse same stroke */
839         int i2 = 0;
840         int totnewpoints, oldtotpoints;
841         /* if first or last, reuse stroke and resize */
842         if ((do_first) || (do_last)) {
843                 totnewpoints = gps->totpoints;
844                 if (do_first) {
845                         totnewpoints++;
846                 }
847                 if (do_last) {
848                         totnewpoints++;
849                 }
850
851                 /* duplicate points in a temp area */
852                 temp_points = MEM_dupallocN(gps->points);
853                 oldtotpoints = gps->totpoints;
854                 if (gps->dvert != NULL) {
855                         temp_dverts = MEM_dupallocN(gps->dvert);
856                 }
857
858                 /* if first point, need move all one position */
859                 if (do_first) {
860                         i2 = 1;
861                 }
862
863                 /* resize the points arrays */
864                 gps->totpoints = totnewpoints;
865                 gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
866                 if (gps->dvert != NULL) {
867                         gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
868                 }
869
870                 /* move points to new position */
871                 for (int i = 0; i < oldtotpoints; i++) {
872                         copy_move_point(gps, temp_points, temp_dverts, i, i2, false);
873                         i2++;
874                 }
875                 gps->flag |= GP_STROKE_RECALC_GEOMETRY;
876
877                 /* if first point, add new point at the begining */
878                 if (do_first) {
879                         copy_move_point(gps, temp_points, temp_dverts, 0, 0, true);
880                         /* deselect old */
881                         pt = &gps->points[1];
882                         pt->flag &= ~GP_SPOINT_SELECT;
883                         /* select new */
884                         pt = &gps->points[0];
885                         pt->flag |= GP_SPOINT_SELECT;
886                 }
887
888                 /* if last point, add new point at the end */
889                 if (do_last) {
890                         copy_move_point(
891                                 gps, temp_points, temp_dverts,
892                                 oldtotpoints - 1, gps->totpoints - 1, true);
893
894                         /* deselect old */
895                         pt = &gps->points[gps->totpoints - 2];
896                         pt->flag &= ~GP_SPOINT_SELECT;
897                         /* select new */
898                         pt = &gps->points[gps->totpoints - 1];
899                         pt->flag |= GP_SPOINT_SELECT;
900                 }
901
902                 MEM_SAFE_FREE(temp_points);
903                 MEM_SAFE_FREE(temp_dverts);
904         }
905
906         /* if the stroke is not reused, deselect */
907         if (!do_stroke) {
908                 gps->flag &= ~GP_STROKE_SELECT;
909         }
910 }
911
912 static int gp_extrude_exec(bContext *C, wmOperator *op)
913 {
914         Object *obact = CTX_data_active_object(C);
915         bGPdata *gpd = (bGPdata *)obact->data;
916         const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
917         bGPDstroke *gps = NULL;
918
919         if (gpd == NULL) {
920                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
921                 return OPERATOR_CANCELLED;
922         }
923
924         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
925         {
926                 bGPDframe *init_gpf = gpl->actframe;
927                 if (is_multiedit) {
928                         init_gpf = gpl->frames.first;
929                 }
930
931                 for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
932                         if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
933                                 if (gpf == NULL)
934                                         continue;
935
936                                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
937                                         /* skip strokes that are invalid for current view */
938                                         if (ED_gpencil_stroke_can_use(C, gps) == false)
939                                                 continue;
940
941                                         if (gps->flag & GP_STROKE_SELECT) {
942                                                 gpencil_add_move_points(gpf, gps);
943                                         }
944                                 }
945                                 /* if not multiedit, exit loop*/
946                                 if (!is_multiedit) {
947                                         break;
948                                 }
949                         }
950                 }
951         }
952         CTX_DATA_END;
953
954         /* updates */
955         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
956         DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE);
957         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
958
959         return OPERATOR_FINISHED;
960 }
961
962 void GPENCIL_OT_extrude(wmOperatorType *ot)
963 {
964         /* identifiers */
965         ot->name = "Extrude Stroke Points";
966         ot->idname = "GPENCIL_OT_extrude";
967         ot->description = "Extrude the selected Grease Pencil points";
968
969         /* callbacks */
970         ot->exec = gp_extrude_exec;
971         ot->poll = gp_stroke_edit_poll;
972
973         /* flags */
974         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
975 }
976
977
978 /* ******************* Copy/Paste Strokes ************************* */
979 /* Grease Pencil stroke data copy/paste buffer:
980  * - The copy operation collects all segments of selected strokes,
981  *   dumping "ready to be copied" copies of the strokes into the buffer.
982  * - The paste operation makes a copy of those elements, and adds them
983  *   to the active layer. This effectively flattens down the strokes
984  *   from several different layers into a single layer.
985  */
986
987  /* list of bGPDstroke instances */
988  /* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */
989 ListBase gp_strokes_copypastebuf = {NULL, NULL};
990
991 /* Hash for hanging on to all the colors used by strokes in the buffer
992  *
993  * This is needed to prevent dangling and unsafe pointers when pasting across datablocks,
994  * or after a color used by a stroke in the buffer gets deleted (via user action or undo).
995  */
996 static GHash *gp_strokes_copypastebuf_colors = NULL;
997
998 static GHash *gp_strokes_copypastebuf_colors_material_to_name_create(Main *bmain)
999 {
1000         GHash *ma_to_name = BLI_ghash_ptr_new(__func__);
1001
1002         for (Material *ma = bmain->materials.first; ma != NULL; ma = ma->id.next) {
1003                 char *name = BKE_id_to_unique_string_key(&ma->id);
1004                 BLI_ghash_insert(ma_to_name, ma, name);
1005         }
1006
1007         return ma_to_name;
1008 }
1009
1010 static void gp_strokes_copypastebuf_colors_material_to_name_free(GHash *ma_to_name)
1011 {
1012         BLI_ghash_free(ma_to_name, NULL, MEM_freeN);
1013 }
1014
1015 static GHash *gp_strokes_copypastebuf_colors_name_to_material_create(Main *bmain)
1016 {
1017         GHash *name_to_ma = BLI_ghash_str_new(__func__);
1018
1019         for (Material *ma = bmain->materials.first; ma != NULL; ma = ma->id.next) {
1020                 char *name = BKE_id_to_unique_string_key(&ma->id);
1021                 BLI_ghash_insert(name_to_ma, name, ma);
1022         }
1023
1024         return name_to_ma;
1025 }
1026
1027 static void gp_strokes_copypastebuf_colors_name_to_material_free(GHash *name_to_ma)
1028 {
1029         BLI_ghash_free(name_to_ma, MEM_freeN, NULL);
1030 }
1031
1032 /* Free copy/paste buffer data */
1033 void ED_gpencil_strokes_copybuf_free(void)
1034 {
1035         bGPDstroke *gps, *gpsn;
1036
1037         /* Free the colors buffer
1038          * NOTE: This is done before the strokes so that the ptrs are still safe
1039          */
1040         if (gp_strokes_copypastebuf_colors) {
1041                 BLI_ghash_free(gp_strokes_copypastebuf_colors, NULL, MEM_freeN);
1042                 gp_strokes_copypastebuf_colors = NULL;
1043         }
1044
1045         /* Free the stroke buffer */
1046         for (gps = gp_strokes_copypastebuf.first; gps; gps = gpsn) {
1047                 gpsn = gps->next;
1048
1049                 if (gps->points) {
1050                         MEM_freeN(gps->points);
1051                 }
1052                 if (gps->dvert) {
1053                         BKE_gpencil_free_stroke_weights(gps);
1054                         MEM_freeN(gps->dvert);
1055                 }
1056
1057                 MEM_SAFE_FREE(gps->triangles);
1058
1059                 BLI_freelinkN(&gp_strokes_copypastebuf, gps);
1060         }
1061
1062         gp_strokes_copypastebuf.first = gp_strokes_copypastebuf.last = NULL;
1063 }
1064
1065 /* Ensure that destination datablock has all the colours the pasted strokes need
1066  * Helper function for copy-pasting strokes
1067  */
1068 GHash *gp_copybuf_validate_colormap(bContext *C)
1069 {
1070         Main *bmain = CTX_data_main(C);
1071         Object *ob = CTX_data_active_object(C);
1072         GHash *new_colors = BLI_ghash_int_new("GPencil Paste Dst Colors");
1073         GHashIterator gh_iter;
1074
1075         /* For each color, check if exist and add if not */
1076         GHash *name_to_ma = gp_strokes_copypastebuf_colors_name_to_material_create(bmain);
1077
1078         GHASH_ITER(gh_iter, gp_strokes_copypastebuf_colors) {
1079                 int *key = BLI_ghashIterator_getKey(&gh_iter);
1080                 char *ma_name = BLI_ghashIterator_getValue(&gh_iter);
1081                 Material *ma = BLI_ghash_lookup(name_to_ma, ma_name);
1082
1083                 BKE_gpencil_handle_material(bmain, ob, ma);
1084
1085                 /* Store this mapping (for use later when pasting) */
1086                 if (!BLI_ghash_haskey(new_colors, POINTER_FROM_INT(*key))) {
1087                         BLI_ghash_insert(new_colors, POINTER_FROM_INT(*key), ma);
1088                 }
1089         }
1090
1091         gp_strokes_copypastebuf_colors_name_to_material_free(name_to_ma);
1092
1093         return new_colors;
1094 }
1095
1096 /* --------------------- */
1097 /* Copy selected strokes */
1098
1099 static int gp_strokes_copy_exec(bContext *C, wmOperator *op)
1100 {
1101         Main *bmain = CTX_data_main(C);
1102         Object *ob = CTX_data_active_object(C);
1103         bGPdata *gpd = ED_gpencil_data_get_active(C);
1104
1105         if (gpd == NULL) {
1106                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
1107                 return OPERATOR_CANCELLED;
1108         }
1109
1110         if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) {
1111                 BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition");
1112                 return OPERATOR_CANCELLED;
1113         }
1114
1115         /* clear the buffer first */
1116         ED_gpencil_strokes_copybuf_free();
1117
1118         /* for each visible (and editable) layer's selected strokes,
1119          * copy the strokes into a temporary buffer, then append
1120          * once all done
1121          */
1122         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1123         {
1124                 bGPDframe *gpf = gpl->actframe;
1125                 bGPDstroke *gps;
1126
1127                 if (gpf == NULL)
1128                         continue;
1129
1130                 /* make copies of selected strokes, and deselect these once we're done */
1131                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
1132                         /* skip strokes that are invalid for current view */
1133                         if (ED_gpencil_stroke_can_use(C, gps) == false)
1134                                 continue;
1135
1136                         if (gps->flag & GP_STROKE_SELECT) {
1137                                 if (gps->totpoints == 1) {
1138                                         /* Special Case: If there's just a single point in this stroke... */
1139                                         bGPDstroke *gpsd;
1140
1141                                         /* make direct copies of the stroke and its points */
1142                                         gpsd = MEM_dupallocN(gps);
1143                                         /* saves original layer name */
1144                                         BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo));
1145                                         gpsd->points = MEM_dupallocN(gps->points);
1146                                         if (gps->dvert != NULL) {
1147                                                 gpsd->dvert = MEM_dupallocN(gps->dvert);
1148                                                 BKE_gpencil_stroke_weights_duplicate(gps, gpsd);
1149                                         }
1150
1151                                         /* triangles cache - will be recalculated on next redraw */
1152                                         gpsd->flag |= GP_STROKE_RECALC_GEOMETRY;
1153                                         gpsd->tot_triangles = 0;
1154                                         gpsd->triangles = NULL;
1155
1156                                         /* add to temp buffer */
1157                                         gpsd->next = gpsd->prev = NULL;
1158                                         BLI_addtail(&gp_strokes_copypastebuf, gpsd);
1159                                 }
1160                                 else {
1161                                         /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
1162                                         gp_duplicate_points(gps, &gp_strokes_copypastebuf, gpl->info);
1163                                 }
1164                         }
1165                 }
1166         }
1167         CTX_DATA_END;
1168
1169         /* Build up hash of material colors used in these strokes */
1170         if (gp_strokes_copypastebuf.first) {
1171                 gp_strokes_copypastebuf_colors = BLI_ghash_int_new("GPencil CopyBuf Colors");
1172                 GHash *ma_to_name = gp_strokes_copypastebuf_colors_material_to_name_create(bmain);
1173                 for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
1174                         if (ED_gpencil_stroke_can_use(C, gps)) {
1175                                 char **ma_name_val;
1176                                 if (!BLI_ghash_ensure_p(gp_strokes_copypastebuf_colors, &gps->mat_nr, (void ***)&ma_name_val)) {
1177                                         Material *ma = give_current_material(ob, gps->mat_nr + 1);
1178                                         char *ma_name = BLI_ghash_lookup(ma_to_name, ma);
1179                                         *ma_name_val = MEM_dupallocN(ma_name);
1180                                 }
1181                         }
1182                 }
1183                 gp_strokes_copypastebuf_colors_material_to_name_free(ma_to_name);
1184         }
1185
1186         /* updates (to ensure operator buttons are refreshed, when used via hotkeys) */
1187         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL); // XXX?
1188
1189         /* done */
1190         return OPERATOR_FINISHED;
1191 }
1192
1193 void GPENCIL_OT_copy(wmOperatorType *ot)
1194 {
1195         /* identifiers */
1196         ot->name = "Copy Strokes";
1197         ot->idname = "GPENCIL_OT_copy";
1198         ot->description = "Copy selected Grease Pencil points and strokes";
1199
1200         /* callbacks */
1201         ot->exec = gp_strokes_copy_exec;
1202         ot->poll = gp_stroke_edit_poll;
1203
1204         /* flags */
1205         //ot->flag = OPTYPE_REGISTER;
1206 }
1207
1208 /* --------------------- */
1209 /* Paste selected strokes */
1210
1211 static bool gp_strokes_paste_poll(bContext *C)
1212 {
1213         /* 1) Must have GP datablock to paste to
1214          *    - We don't need to have an active layer though, as that can easily get added
1215          *    - If the active layer is locked, we can't paste there, but that should prompt a warning instead
1216          * 2) Copy buffer must at least have something (though it may be the wrong sort...)
1217          */
1218         return (ED_gpencil_data_get_active(C) != NULL) && (!BLI_listbase_is_empty(&gp_strokes_copypastebuf));
1219 }
1220
1221 typedef enum eGP_PasteMode {
1222         GP_COPY_ONLY = -1,
1223         GP_COPY_MERGE = 1,
1224 } eGP_PasteMode;
1225
1226 static int gp_strokes_paste_exec(bContext *C, wmOperator *op)
1227 {
1228         Object *ob = CTX_data_active_object(C);
1229         bGPdata *gpd = ED_gpencil_data_get_active(C);
1230         bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); /* only use active for copy merge */
1231         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1232         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1233         bGPDframe *gpf;
1234
1235         eGP_PasteMode type = RNA_enum_get(op->ptr, "type");
1236         GHash *new_colors;
1237
1238         /* check for various error conditions */
1239         if (gpd == NULL) {
1240                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
1241                 return OPERATOR_CANCELLED;
1242         }
1243         else if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) {
1244                 BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition");
1245                 return OPERATOR_CANCELLED;
1246         }
1247         else if (BLI_listbase_is_empty(&gp_strokes_copypastebuf)) {
1248                 BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again");
1249                 return OPERATOR_CANCELLED;
1250         }
1251         else if (gpl == NULL) {
1252                 /* no active layer - let's just create one */
1253                 gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
1254         }
1255         else if ((gpencil_layer_is_editable(gpl) == false) && (type == GP_COPY_MERGE)) {
1256                 BKE_report(op->reports, RPT_ERROR, "Can not paste strokes when active layer is hidden or locked");
1257                 return OPERATOR_CANCELLED;
1258         }
1259         else {
1260                 /* Check that some of the strokes in the buffer can be used */
1261                 bGPDstroke *gps;
1262                 bool ok = false;
1263
1264                 for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
1265                         if (ED_gpencil_stroke_can_use(C, gps)) {
1266                                 ok = true;
1267                                 break;
1268                         }
1269                 }
1270
1271                 if (ok == false) {
1272                         /* XXX: this check is not 100% accurate (i.e. image editor is incompatible with normal 2D strokes),
1273                          * but should be enough to give users a good idea of what's going on
1274                          */
1275                         if (CTX_wm_area(C)->spacetype == SPACE_VIEW3D)
1276                                 BKE_report(op->reports, RPT_ERROR, "Cannot paste 2D strokes in 3D View");
1277                         else
1278                                 BKE_report(op->reports, RPT_ERROR, "Cannot paste 3D strokes in 2D editors");
1279
1280                         return OPERATOR_CANCELLED;
1281                 }
1282         }
1283
1284         /* Deselect all strokes first */
1285         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
1286         {
1287                 bGPDspoint *pt;
1288                 int i;
1289
1290                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1291                         pt->flag &= ~GP_SPOINT_SELECT;
1292                 }
1293
1294                 gps->flag &= ~GP_STROKE_SELECT;
1295         }
1296         CTX_DATA_END;
1297
1298         /* Ensure that all the necessary colors exist */
1299         new_colors = gp_copybuf_validate_colormap(C);
1300
1301         /* Copy over the strokes from the buffer (and adjust the colors) */
1302         for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
1303                 if (ED_gpencil_stroke_can_use(C, gps)) {
1304                         /* Need to verify if layer exists */
1305                         if (type != GP_COPY_MERGE) {
1306                                 gpl = BLI_findstring(&gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info));
1307                                 if (gpl == NULL) {
1308                                         /* no layer - use active (only if layer deleted before paste) */
1309                                         gpl = CTX_data_active_gpencil_layer(C);
1310                                 }
1311                         }
1312
1313                         /* Ensure we have a frame to draw into
1314                          * NOTE: Since this is an op which creates strokes,
1315                          *       we are obliged to add a new frame if one
1316                          *       doesn't exist already
1317                          */
1318                         gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_ADD_NEW);
1319                         if (gpf) {
1320                                 /* Create new stroke */
1321                                 bGPDstroke *new_stroke = MEM_dupallocN(gps);
1322                                 new_stroke->runtime.tmp_layerinfo[0] = '\0';
1323
1324                                 new_stroke->points = MEM_dupallocN(gps->points);
1325                                 if (gps->dvert != NULL) {
1326                                         new_stroke->dvert = MEM_dupallocN(gps->dvert);
1327                                         BKE_gpencil_stroke_weights_duplicate(gps, new_stroke);
1328                                 }
1329                                 new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY;
1330                                 new_stroke->triangles = NULL;
1331
1332                                 new_stroke->next = new_stroke->prev = NULL;
1333                                 BLI_addtail(&gpf->strokes, new_stroke);
1334
1335                                 /* Remap material */
1336                                 Material *ma = BLI_ghash_lookup(new_colors, POINTER_FROM_INT(new_stroke->mat_nr));
1337                                 new_stroke->mat_nr = BKE_gpencil_get_material_index(ob, ma);
1338                                 BLI_assert(new_stroke->mat_nr >= 0); /* have to add the material first */
1339                         }
1340                 }
1341         }
1342
1343         /* free temp data */
1344         BLI_ghash_free(new_colors, NULL, NULL);
1345
1346         /* updates */
1347         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1348         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1349
1350         return OPERATOR_FINISHED;
1351 }
1352
1353 void GPENCIL_OT_paste(wmOperatorType *ot)
1354 {
1355         static const EnumPropertyItem copy_type[] = {
1356                 {GP_COPY_ONLY, "COPY", 0, "Copy", ""},
1357                 {GP_COPY_MERGE, "MERGE", 0, "Merge", ""},
1358                 {0, NULL, 0, NULL, NULL},
1359         };
1360
1361         /* identifiers */
1362         ot->name = "Paste Strokes";
1363         ot->idname = "GPENCIL_OT_paste";
1364         ot->description = "Paste previously copied strokes or copy and merge in active layer";
1365
1366         /* callbacks */
1367         ot->exec = gp_strokes_paste_exec;
1368         ot->poll = gp_strokes_paste_poll;
1369
1370         /* flags */
1371         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1372
1373         /* properties */
1374         ot->prop = RNA_def_enum(ot->srna, "type", copy_type, 0, "Type", "");
1375 }
1376
1377 /* ******************* Move To Layer ****************************** */
1378
1379 static int gp_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
1380 {
1381         uiPopupMenu *pup;
1382         uiLayout *layout;
1383
1384         /* call the menu, which will call this operator again, hence the canceled */
1385         pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE);
1386         layout = UI_popup_menu_layout(pup);
1387         uiItemsEnumO(layout, "GPENCIL_OT_move_to_layer", "layer");
1388         UI_popup_menu_end(C, pup);
1389
1390         return OPERATOR_INTERFACE;
1391 }
1392
1393 // FIXME: allow moving partial strokes
1394 static int gp_move_to_layer_exec(bContext *C, wmOperator *op)
1395 {
1396         bGPdata *gpd = CTX_data_gpencil_data(C);
1397         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1398         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1399         bGPDlayer *target_layer = NULL;
1400         ListBase strokes = {NULL, NULL};
1401         int layer_num = RNA_enum_get(op->ptr, "layer");
1402         const bool use_autolock = (bool)(gpd->flag & GP_DATA_AUTOLOCK_LAYERS);
1403
1404         if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) {
1405                 BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition");
1406                 return OPERATOR_CANCELLED;
1407         }
1408
1409         /* if autolock enabled, disabled now */
1410         if (use_autolock) {
1411                 gpd->flag &= ~GP_DATA_AUTOLOCK_LAYERS;
1412         }
1413
1414         /* Get layer or create new one */
1415         if (layer_num == -1) {
1416                 /* Create layer */
1417                 target_layer = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
1418         }
1419         else {
1420                 /* Try to get layer */
1421                 target_layer = BLI_findlink(&gpd->layers, layer_num);
1422
1423                 if (target_layer == NULL) {
1424                         /* back autolock status */
1425                         if (use_autolock) {
1426                                 gpd->flag |= GP_DATA_AUTOLOCK_LAYERS;
1427                         }
1428                         BKE_reportf(op->reports, RPT_ERROR, "There is no layer number %d", layer_num);
1429                         return OPERATOR_CANCELLED;
1430                 }
1431         }
1432
1433         /* Extract all strokes to move to this layer
1434          * NOTE: We need to do this in a two-pass system to avoid conflicts with strokes
1435          *       getting repeatedly moved
1436          */
1437         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1438         {
1439                 bGPDframe *gpf = gpl->actframe;
1440                 bGPDstroke *gps, *gpsn;
1441
1442                 /* skip if no frame with strokes, or if this is the layer we're moving strokes to */
1443                 if ((gpl == target_layer) || (gpf == NULL))
1444                         continue;
1445
1446                 /* make copies of selected strokes, and deselect these once we're done */
1447                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
1448                         gpsn = gps->next;
1449
1450                         /* skip strokes that are invalid for current view */
1451                         if (ED_gpencil_stroke_can_use(C, gps) == false)
1452                                 continue;
1453
1454                         /* TODO: Don't just move entire strokes - instead, only copy the selected portions... */
1455                         if (gps->flag & GP_STROKE_SELECT) {
1456                                 BLI_remlink(&gpf->strokes, gps);
1457                                 BLI_addtail(&strokes, gps);
1458                         }
1459                 }
1460
1461                 /* if new layer and autolock, lock old layer */
1462                 if ((layer_num == -1) && (use_autolock)) {
1463                         gpl->flag |= GP_LAYER_LOCKED;
1464                 }
1465         }
1466         CTX_DATA_END;
1467
1468         /* Paste them all in one go */
1469         if (strokes.first) {
1470                 bGPDframe *gpf = BKE_gpencil_layer_getframe(target_layer, cfra_eval, GP_GETFRAME_ADD_NEW);
1471
1472                 BLI_movelisttolist(&gpf->strokes, &strokes);
1473                 BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL));
1474         }
1475
1476         /* back autolock status */
1477         if (use_autolock) {
1478                 gpd->flag |= GP_DATA_AUTOLOCK_LAYERS;
1479         }
1480
1481         /* updates */
1482         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1483         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1484
1485         return OPERATOR_FINISHED;
1486 }
1487
1488 void GPENCIL_OT_move_to_layer(wmOperatorType *ot)
1489 {
1490         /* identifiers */
1491         ot->name = "Move Strokes to Layer";
1492         ot->idname = "GPENCIL_OT_move_to_layer";
1493         ot->description = "Move selected strokes to another layer"; // XXX: allow moving individual points too?
1494
1495         /* callbacks */
1496         ot->invoke = gp_move_to_layer_invoke;
1497         ot->exec = gp_move_to_layer_exec;
1498         ot->poll = gp_stroke_edit_poll; // XXX?
1499
1500         /* flags */
1501         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1502
1503         /* gp layer to use (dynamic enum) */
1504         ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", "");
1505         RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf);
1506 }
1507
1508 /* ********************* Add Blank Frame *************************** */
1509
1510 /* Basically the same as the drawing op */
1511 static bool UNUSED_FUNCTION(gp_blank_frame_add_poll)(bContext *C)
1512 {
1513         if (ED_operator_regionactive(C)) {
1514                 /* check if current context can support GPencil data */
1515                 if (ED_gpencil_data_get_pointers(C, NULL) != NULL) {
1516                         return 1;
1517                 }
1518                 else {
1519                         CTX_wm_operator_poll_msg_set(C, "Failed to find Grease Pencil data to draw into");
1520                 }
1521         }
1522         else {
1523                 CTX_wm_operator_poll_msg_set(C, "Active region not set");
1524         }
1525
1526         return 0;
1527 }
1528
1529 static int gp_blank_frame_add_exec(bContext *C, wmOperator *op)
1530 {
1531         bGPdata *gpd = ED_gpencil_data_get_active(C);
1532         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1533         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1534
1535         bGPDlayer *active_gpl = BKE_gpencil_layer_getactive(gpd);
1536
1537         const bool all_layers = RNA_boolean_get(op->ptr, "all_layers");
1538
1539         /* Initialise datablock and an active layer if nothing exists yet */
1540         if (ELEM(NULL, gpd, active_gpl)) {
1541                 /* let's just be lazy, and call the "Add New Layer" operator, which sets everything up as required */
1542                 WM_operator_name_call(C, "GPENCIL_OT_layer_add", WM_OP_EXEC_DEFAULT, NULL);
1543         }
1544
1545         /* Go through each layer, adding a frame after the active one
1546          * and/or shunting all the others out of the way
1547          */
1548         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1549         {
1550                 if ((all_layers == false) && (gpl != active_gpl)) {
1551                         continue;
1552                 }
1553
1554                 /* 1) Check for an existing frame on the current frame */
1555                 bGPDframe *gpf = BKE_gpencil_layer_find_frame(gpl, cfra_eval);
1556                 if (gpf) {
1557                         /* Shunt all frames after (and including) the existing one later by 1-frame */
1558                         for (; gpf; gpf = gpf->next) {
1559                                 gpf->framenum += 1;
1560                         }
1561                 }
1562
1563                 /* 2) Now add a new frame, with nothing in it */
1564                 gpl->actframe = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_ADD_NEW);
1565         }
1566         CTX_DATA_END;
1567
1568         /* notifiers */
1569         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1570         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1571
1572         return OPERATOR_FINISHED;
1573 }
1574
1575 void GPENCIL_OT_blank_frame_add(wmOperatorType *ot)
1576 {
1577         /* identifiers */
1578         ot->name = "Insert Blank Frame";
1579         ot->idname = "GPENCIL_OT_blank_frame_add";
1580         ot->description = "Insert a blank frame on the current frame "
1581                 "(all subsequently existing frames, if any, are shifted right by one frame)";
1582
1583         /* callbacks */
1584         ot->exec = gp_blank_frame_add_exec;
1585         ot->poll = gp_add_poll;
1586
1587         /* properties */
1588         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1589         RNA_def_boolean(ot->srna, "all_layers", false, "All Layers", "Create blank frame in all layers, not only active");
1590 }
1591
1592 /* ******************* Delete Active Frame ************************ */
1593
1594 static bool gp_actframe_delete_poll(bContext *C)
1595 {
1596         bGPdata *gpd = ED_gpencil_data_get_active(C);
1597         bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd);
1598
1599         /* only if there's an active layer with an active frame */
1600         return (gpl && gpl->actframe);
1601 }
1602
1603 /* delete active frame - wrapper around API calls */
1604 static int gp_actframe_delete_exec(bContext *C, wmOperator *op)
1605 {
1606         bGPdata *gpd = ED_gpencil_data_get_active(C);
1607         bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd);
1608
1609         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1610         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1611
1612         bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_USE_PREV);
1613
1614         /* if there's no existing Grease-Pencil data there, add some */
1615         if (gpd == NULL) {
1616                 BKE_report(op->reports, RPT_ERROR, "No grease pencil data");
1617                 return OPERATOR_CANCELLED;
1618         }
1619         if (ELEM(NULL, gpl, gpf)) {
1620                 BKE_report(op->reports, RPT_ERROR, "No active frame to delete");
1621                 return OPERATOR_CANCELLED;
1622         }
1623
1624         /* delete it... */
1625         BKE_gpencil_layer_delframe(gpl, gpf);
1626
1627         /* notifiers */
1628         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1629         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1630
1631         return OPERATOR_FINISHED;
1632 }
1633
1634 void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
1635 {
1636         /* identifiers */
1637         ot->name = "Delete Active Frame";
1638         ot->idname = "GPENCIL_OT_active_frame_delete";
1639         ot->description = "Delete the active frame for the active Grease Pencil Layer";
1640
1641         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1642
1643         /* callbacks */
1644         ot->exec = gp_actframe_delete_exec;
1645         ot->poll = gp_actframe_delete_poll;
1646 }
1647
1648 /* **************** Delete All Active Frames ****************** */
1649
1650 static bool gp_actframe_delete_all_poll(bContext *C)
1651 {
1652         bGPdata *gpd = ED_gpencil_data_get_active(C);
1653
1654         /* 1) There must be grease pencil data
1655          * 2) Hopefully some of the layers have stuff we can use
1656          */
1657         return (gpd && gpd->layers.first);
1658 }
1659
1660 static int gp_actframe_delete_all_exec(bContext *C, wmOperator *op)
1661 {
1662         bGPdata *gpd = ED_gpencil_data_get_active(C);
1663         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1664         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1665
1666         bool success = false;
1667
1668         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1669         {
1670                 /* try to get the "active" frame - but only if it actually occurs on this frame */
1671                 bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_USE_PREV);
1672
1673                 if (gpf == NULL)
1674                         continue;
1675
1676                 /* delete it... */
1677                 BKE_gpencil_layer_delframe(gpl, gpf);
1678
1679                 /* we successfully modified something */
1680                 success = true;
1681         }
1682         CTX_DATA_END;
1683
1684         /* updates */
1685         if (success) {
1686                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1687                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1688                 return OPERATOR_FINISHED;
1689         }
1690         else {
1691                 BKE_report(op->reports, RPT_ERROR, "No active frame(s) to delete");
1692                 return OPERATOR_CANCELLED;
1693         }
1694 }
1695
1696 void GPENCIL_OT_active_frames_delete_all(wmOperatorType *ot)
1697 {
1698         /* identifiers */
1699         ot->name = "Delete All Active Frames";
1700         ot->idname = "GPENCIL_OT_active_frames_delete_all";
1701         ot->description = "Delete the active frame(s) of all editable Grease Pencil layers";
1702
1703         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1704
1705         /* callbacks */
1706         ot->exec = gp_actframe_delete_all_exec;
1707         ot->poll = gp_actframe_delete_all_poll;
1708 }
1709
1710 /* ******************* Delete Operator ************************ */
1711
1712 typedef enum eGP_DeleteMode {
1713         /* delete selected stroke points */
1714         GP_DELETEOP_POINTS          = 0,
1715         /* delete selected strokes */
1716         GP_DELETEOP_STROKES         = 1,
1717         /* delete active frame */
1718         GP_DELETEOP_FRAME           = 2,
1719 } eGP_DeleteMode;
1720
1721 typedef enum eGP_DissolveMode {
1722         /* dissolve all selected points */
1723         GP_DISSOLVE_POINTS = 0,
1724         /* dissolve between selected points */
1725         GP_DISSOLVE_BETWEEN = 1,
1726         /* dissolve unselected points */
1727         GP_DISSOLVE_UNSELECT = 2,
1728 } eGP_DissolveMode;
1729
1730 /* ----------------------------------- */
1731
1732 /* Delete selected strokes */
1733 static int gp_delete_selected_strokes(bContext *C)
1734 {
1735         bool changed = false;
1736         bGPdata *gpd = ED_gpencil_data_get_active(C);
1737         const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
1738
1739         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1740         {
1741                 bGPDframe *init_gpf = gpl->actframe;
1742                 if (is_multiedit) {
1743                         init_gpf = gpl->frames.first;
1744                 }
1745
1746                 for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
1747                         if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
1748                                 bGPDstroke *gps, *gpsn;
1749
1750                                 if (gpf == NULL)
1751                                         continue;
1752
1753                                 /* simply delete strokes which are selected */
1754                                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
1755                                         gpsn = gps->next;
1756
1757                                         /* skip strokes that are invalid for current view */
1758                                         if (ED_gpencil_stroke_can_use(C, gps) == false)
1759                                                 continue;
1760
1761                                         /* free stroke if selected */
1762                                         if (gps->flag & GP_STROKE_SELECT) {
1763                                                 /* free stroke memory arrays, then stroke itself */
1764                                                 if (gps->points) {
1765                                                         MEM_freeN(gps->points);
1766                                                 }
1767                                                 if (gps->dvert) {
1768                                                         BKE_gpencil_free_stroke_weights(gps);
1769                                                         MEM_freeN(gps->dvert);
1770                                                 }
1771                                                 MEM_SAFE_FREE(gps->triangles);
1772                                                 BLI_freelinkN(&gpf->strokes, gps);
1773
1774                                                 changed = true;
1775                                         }
1776                                 }
1777                         }
1778                 }
1779         }
1780         CTX_DATA_END;
1781
1782         if (changed) {
1783                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1784                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1785                 return OPERATOR_FINISHED;
1786         }
1787         else {
1788                 return OPERATOR_CANCELLED;
1789         }
1790 }
1791
1792 /* ----------------------------------- */
1793
1794 /* Delete selected points but keep the stroke */
1795 static int gp_dissolve_selected_points(bContext *C, eGP_DissolveMode mode)
1796 {
1797         Object *ob = CTX_data_active_object(C);
1798         bGPdata *gpd = ED_gpencil_data_get_active(C);
1799         const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
1800         bool changed = false;
1801         int first = 0;
1802         int last = 0;
1803
1804         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1805         {
1806                 bGPDframe *init_gpf = gpl->actframe;
1807                 if (is_multiedit) {
1808                         init_gpf = gpl->frames.first;
1809                 }
1810
1811                 for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
1812                         if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
1813
1814                                 bGPDstroke *gps, *gpsn;
1815
1816                                 if (gpf == NULL)
1817                                         continue;
1818
1819                                 /* simply delete points from selected strokes
1820                                  * NOTE: we may still have to remove the stroke if it ends up having no points!
1821                                  */
1822                                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
1823                                         gpsn = gps->next;
1824
1825                                         /* skip strokes that are invalid for current view */
1826                                         if (ED_gpencil_stroke_can_use(C, gps) == false)
1827                                                 continue;
1828                                         /* check if the color is editable */
1829                                         if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false)
1830                                                 continue;
1831
1832                                         /* the stroke must have at least one point selected for any operator */
1833                                         if (gps->flag & GP_STROKE_SELECT) {
1834                                                 bGPDspoint *pt;
1835                                                 MDeformVert *dvert = NULL;
1836                                                 int i;
1837
1838                                                 int tot = gps->totpoints; /* number of points in new buffer */
1839
1840                                                 /* first pass: count points to remove */
1841                                                 switch (mode) {
1842                                                         case GP_DISSOLVE_POINTS:
1843                                                                 /* Count how many points are selected (i.e. how many to remove) */
1844                                                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1845                                                                         if (pt->flag & GP_SPOINT_SELECT) {
1846                                                                                 /* selected point - one of the points to remove */
1847                                                                                 tot--;
1848                                                                         }
1849                                                                 }
1850                                                                 break;
1851                                                         case GP_DISSOLVE_BETWEEN:
1852                                                                 /* need to find first and last point selected */
1853                                                                 first = -1;
1854                                                                 last = 0;
1855                                                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1856                                                                         if (pt->flag & GP_SPOINT_SELECT) {
1857                                                                                 if (first < 0) {
1858                                                                                         first = i;
1859                                                                                 }
1860                                                                                 last = i;
1861                                                                         }
1862                                                                 }
1863                                                                 /* count unselected points in the range */
1864                                                                 for (i = first, pt = gps->points + first; i < last; i++, pt++) {
1865                                                                         if ((pt->flag & GP_SPOINT_SELECT) == 0) {
1866                                                                                 tot--;
1867                                                                         }
1868                                                                 }
1869                                                                 break;
1870                                                         case GP_DISSOLVE_UNSELECT:
1871                                                                 /* count number of unselected points */
1872                                                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1873                                                                         if ((pt->flag & GP_SPOINT_SELECT) == 0) {
1874                                                                                 tot--;
1875                                                                         }
1876                                                                 }
1877                                                                 break;
1878                                                         default:
1879                                                                 return false;
1880                                                                 break;
1881                                                 }
1882
1883                                                 /* if no points are left, we simply delete the entire stroke */
1884                                                 if (tot <= 0) {
1885                                                         /* remove the entire stroke */
1886                                                         if (gps->points) {
1887                                                                 MEM_freeN(gps->points);
1888                                                         }
1889                                                         if (gps->dvert) {
1890                                                                 BKE_gpencil_free_stroke_weights(gps);
1891                                                                 MEM_freeN(gps->dvert);
1892                                                         }
1893                                                         if (gps->triangles) {
1894                                                                 MEM_freeN(gps->triangles);
1895                                                         }
1896                                                         BLI_freelinkN(&gpf->strokes, gps);
1897                                                         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1898                                                 }
1899                                                 else {
1900                                                         /* just copy all points to keep into a smaller buffer */
1901                                                         bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy");
1902                                                         bGPDspoint *npt = new_points;
1903
1904                                                         MDeformVert *new_dvert = NULL;
1905                                                         MDeformVert *ndvert = NULL;
1906
1907                                                         if (gps->dvert != NULL) {
1908                                                                 new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy");
1909                                                                 ndvert = new_dvert;
1910                                                         }
1911
1912                                                         switch (mode) {
1913                                                                 case GP_DISSOLVE_POINTS:
1914                                                                         (gps->dvert != NULL) ? dvert = gps->dvert : NULL;
1915                                                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1916                                                                                 if ((pt->flag & GP_SPOINT_SELECT) == 0) {
1917                                                                                         *npt = *pt;
1918                                                                                         npt++;
1919
1920                                                                                         if (gps->dvert != NULL) {
1921                                                                                                 *ndvert = *dvert;
1922                                                                                                 ndvert->dw = MEM_dupallocN(dvert->dw);
1923                                                                                                 ndvert++;
1924                                                                                                 dvert++;
1925                                                                                         }
1926                                                                                 }
1927                                                                         }
1928                                                                         break;
1929                                                                 case GP_DISSOLVE_BETWEEN:
1930                                                                         /* copy first segment */
1931                                                                         (gps->dvert != NULL) ? dvert = gps->dvert : NULL;
1932                                                                         for (i = 0, pt = gps->points; i < first; i++, pt++) {
1933                                                                                 *npt = *pt;
1934                                                                                 npt++;
1935
1936                                                                                 if (gps->dvert != NULL) {
1937                                                                                         *ndvert = *dvert;
1938                                                                                         ndvert->dw = MEM_dupallocN(dvert->dw);
1939                                                                                         ndvert++;
1940                                                                                         dvert++;
1941                                                                                 }
1942                                                                         }
1943                                                                         /* copy segment (selected points) */
1944                                                                         (gps->dvert != NULL) ? dvert = gps->dvert + first : NULL;
1945                                                                         for (i = first, pt = gps->points + first; i < last; i++, pt++) {
1946                                                                                 if (pt->flag & GP_SPOINT_SELECT) {
1947                                                                                         *npt = *pt;
1948                                                                                         npt++;
1949
1950                                                                                         if (gps->dvert != NULL) {
1951                                                                                                 *ndvert = *dvert;
1952                                                                                                 ndvert->dw = MEM_dupallocN(dvert->dw);
1953                                                                                                 ndvert++;
1954                                                                                                 dvert++;
1955                                                                                         }
1956                                                                                 }
1957                                                                         }
1958                                                                         /* copy last segment */
1959                                                                         (gps->dvert != NULL) ? dvert = gps->dvert + last : NULL;
1960                                                                         for (i = last, pt = gps->points + last; i < gps->totpoints; i++, pt++) {
1961                                                                                 *npt = *pt;
1962                                                                                 npt++;
1963
1964                                                                                 if (gps->dvert != NULL) {
1965                                                                                         *ndvert = *dvert;
1966                                                                                         ndvert->dw = MEM_dupallocN(dvert->dw);
1967                                                                                         ndvert++;
1968                                                                                         dvert++;
1969                                                                                 }
1970                                                                         }
1971
1972                                                                         break;
1973                                                                 case GP_DISSOLVE_UNSELECT:
1974                                                                         /* copy any selected point */
1975                                                                         (gps->dvert != NULL) ? dvert = gps->dvert : NULL;
1976                                                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1977                                                                                 if (pt->flag & GP_SPOINT_SELECT) {
1978                                                                                         *npt = *pt;
1979                                                                                         npt++;
1980
1981                                                                                         if (gps->dvert != NULL) {
1982                                                                                                 *ndvert = *dvert;
1983                                                                                                 ndvert->dw = MEM_dupallocN(dvert->dw);
1984                                                                                                 ndvert++;
1985                                                                                                 dvert++;
1986                                                                                         }
1987                                                                                 }
1988                                                                         }
1989                                                                         break;
1990                                                         }
1991
1992                                                         /* free the old buffer */
1993                                                         if (gps->points) {
1994                                                                 MEM_freeN(gps->points);
1995                                                         }
1996                                                         if (gps->dvert) {
1997                                                                 BKE_gpencil_free_stroke_weights(gps);
1998                                                                 MEM_freeN(gps->dvert);
1999                                                         }
2000
2001                                                         /* save the new buffer */
2002                                                         gps->points = new_points;
2003                                                         gps->dvert = new_dvert;
2004                                                         gps->totpoints = tot;
2005
2006                                                         /* triangles cache needs to be recalculated */
2007                                                         gps->flag |= GP_STROKE_RECALC_GEOMETRY;
2008                                                         gps->tot_triangles = 0;
2009
2010                                                         /* deselect the stroke, since none of its selected points will still be selected */
2011                                                         gps->flag &= ~GP_STROKE_SELECT;
2012                                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2013                                                                 pt->flag &= ~GP_SPOINT_SELECT;
2014                                                         }
2015                                                 }
2016
2017                                                 changed = true;
2018                                         }
2019                                 }
2020                         }
2021                 }
2022         }
2023         CTX_DATA_END;
2024
2025         if (changed) {
2026                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2027                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2028                 return OPERATOR_FINISHED;
2029         }
2030         else {
2031                 return OPERATOR_CANCELLED;
2032         }
2033 }
2034
2035 /* ----------------------------------- */
2036
2037 /* Temp data for storing information about an "island" of points
2038  * that should be kept when splitting up a stroke. Used in:
2039  * gp_stroke_delete_tagged_points()
2040  */
2041 typedef struct tGPDeleteIsland {
2042         int start_idx;
2043         int end_idx;
2044 } tGPDeleteIsland;
2045
2046 static void gp_stroke_join_islands(bGPDframe *gpf, bGPDstroke *gps_first, bGPDstroke *gps_last)
2047 {
2048         bGPDspoint *pt = NULL;
2049         bGPDspoint *pt_final = NULL;
2050         const int totpoints = gps_first->totpoints + gps_last->totpoints;
2051
2052         /* create new stroke */
2053         bGPDstroke *join_stroke = MEM_dupallocN(gps_first);
2054
2055         join_stroke->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__);
2056         join_stroke->totpoints = totpoints;
2057         join_stroke->flag &= ~GP_STROKE_CYCLIC;
2058
2059         /* copy points (last before) */
2060         int e1 = 0;
2061         int e2 = 0;
2062         float delta = 0.0f;
2063
2064         for (int i = 0; i < totpoints; i++) {
2065                 pt_final = &join_stroke->points[i];
2066                 if (i < gps_last->totpoints) {
2067                         pt = &gps_last->points[e1];
2068                         e1++;
2069                 }
2070                 else {
2071                         pt = &gps_first->points[e2];
2072                         e2++;
2073                 }
2074
2075                 /* copy current point */
2076                 copy_v3_v3(&pt_final->x, &pt->x);
2077                 pt_final->pressure = pt->pressure;
2078                 pt_final->strength = pt->strength;
2079                 pt_final->time = delta;
2080                 pt_final->flag = pt->flag;
2081
2082                 /* retiming with fixed time interval (we cannot determine real time) */
2083                 delta += 0.01f;
2084         }
2085
2086         /* Copy over vertex weight data (if available) */
2087         if ((gps_first->dvert != NULL) || (gps_last->dvert != NULL)) {
2088                 join_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * totpoints, __func__);
2089                 MDeformVert *dvert_src = NULL;
2090                 MDeformVert *dvert_dst = NULL;
2091
2092                 /* Copy weights (last before)*/
2093                 e1 = 0;
2094                 e2 = 0;
2095                 for (int i = 0; i < totpoints; i++) {
2096                         dvert_dst = &join_stroke->dvert[i];
2097                         dvert_src = NULL;
2098                         if (i < gps_last->totpoints) {
2099                                 if (gps_last->dvert) {
2100                                         dvert_src = &gps_last->dvert[e1];
2101                                         e1++;
2102                                 }
2103                         }
2104                         else {
2105                                 if (gps_first->dvert) {
2106                                         dvert_src = &gps_first->dvert[e2];
2107                                         e2++;
2108                                 }
2109                         }
2110
2111                         if ((dvert_src) && (dvert_src->dw)) {
2112                                 dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
2113                         }
2114                 }
2115         }
2116
2117         /* add new stroke at head */
2118         BLI_addhead(&gpf->strokes, join_stroke);
2119
2120         /* remove first stroke */
2121         BLI_remlink(&gpf->strokes, gps_first);
2122         BKE_gpencil_free_stroke(gps_first);
2123
2124         /* remove last stroke */
2125         BLI_remlink(&gpf->strokes, gps_last);
2126         BKE_gpencil_free_stroke(gps_last);
2127 }
2128
2129
2130 /* Split the given stroke into several new strokes, partitioning
2131  * it based on whether the stroke points have a particular flag
2132  * is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always)
2133  *
2134  * The algorithm used here is as follows:
2135  * 1) We firstly identify the number of "islands" of non-tagged points
2136  *    which will all end up being in new strokes.
2137  *    - In the most extreme case (i.e. every other vert is a 1-vert island),
2138  *      we have at most n / 2 islands
2139  *    - Once we start having larger islands than that, the number required
2140  *      becomes much less
2141  * 2) Each island gets converted to a new stroke
2142  * If the number of points is <= limit, the stroke is deleted
2143  */
2144 void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke,
2145         int tag_flags, bool select, int limit)
2146 {
2147         tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
2148         bool in_island  = false;
2149         int num_islands = 0;
2150
2151         bGPDstroke *gps_first = NULL;
2152         const bool is_cyclic = (bool)(gps->flag & GP_STROKE_CYCLIC);
2153
2154         /* First Pass: Identify start/end of islands */
2155         bGPDspoint *pt = gps->points;
2156         for (int i = 0; i < gps->totpoints; i++, pt++) {
2157                 if (pt->flag & tag_flags) {
2158                         /* selected - stop accumulating to island */
2159                         in_island = false;
2160                 }
2161                 else {
2162                         /* unselected - start of a new island? */
2163                         int idx;
2164
2165                         if (in_island) {
2166                                 /* extend existing island */
2167                                 idx = num_islands - 1;
2168                                 islands[idx].end_idx = i;
2169                         }
2170                         else {
2171                                 /* start of new island */
2172                                 in_island = true;
2173                                 num_islands++;
2174
2175                                 idx = num_islands - 1;
2176                                 islands[idx].start_idx = islands[idx].end_idx = i;
2177                         }
2178                 }
2179         }
2180
2181         /* Watch out for special case where No islands = All points selected = Delete Stroke only */
2182         if (num_islands) {
2183                 /* there are islands, so create a series of new strokes, adding them before the "next" stroke */
2184                 int idx;
2185                 bGPDstroke *new_stroke = NULL;
2186
2187                 /* Create each new stroke... */
2188                 for (idx = 0; idx < num_islands; idx++) {
2189                         tGPDeleteIsland *island = &islands[idx];
2190                         new_stroke = MEM_dupallocN(gps);
2191
2192                         /* if cyclic and first stroke, save to join later */
2193                         if ((is_cyclic) && (gps_first == NULL)) {
2194                                 gps_first = new_stroke;
2195                         }
2196
2197                         /* initialize triangle memory  - to be calculated on next redraw */
2198                         new_stroke->triangles = NULL;
2199                         new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY;
2200                         new_stroke->flag &= ~GP_STROKE_CYCLIC;
2201                         new_stroke->tot_triangles = 0;
2202
2203                         /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
2204                         new_stroke->totpoints = island->end_idx - island->start_idx + 1;
2205
2206                         /* Copy over the relevant point data */
2207                         new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
2208                         memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
2209
2210                         /* Copy over vertex weight data (if available) */
2211                         if (gps->dvert != NULL) {
2212                                 /* Copy over the relevant vertex-weight points */
2213                                 new_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints, "gp delete stroke fragment weight");
2214                                 memcpy(new_stroke->dvert, gps->dvert + island->start_idx, sizeof(MDeformVert) * new_stroke->totpoints);
2215
2216                                 /* Copy weights */
2217                                 int e = island->start_idx;
2218                                 for (int i = 0; i < new_stroke->totpoints; i++) {
2219                                         MDeformVert *dvert_src = &gps->dvert[e];
2220                                         MDeformVert *dvert_dst = &new_stroke->dvert[i];
2221                                         if (dvert_src->dw) {
2222                                                 dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
2223                                         }
2224                                         e++;
2225                                 }
2226                         }
2227                         /* Each island corresponds to a new stroke. We must adjust the
2228                          * timings of these new strokes:
2229                          *
2230                          * Each point's timing data is a delta from stroke's inittime, so as we erase some points from
2231                          * the start of the stroke, we have to offset this inittime and all remaining points' delta values.
2232                          * This way we get a new stroke with exactly the same timing as if user had started drawing from
2233                          * the first non-removed point...
2234                          */
2235                         {
2236                                 bGPDspoint *pts;
2237                                 float delta = gps->points[island->start_idx].time;
2238                                 int j;
2239
2240                                 new_stroke->inittime += (double)delta;
2241
2242                                 pts = new_stroke->points;
2243                                 for (j = 0; j < new_stroke->totpoints; j++, pts++) {
2244                                         pts->time -= delta;
2245                                         /* set flag for select again later */
2246                                         if (select == true) {
2247                                                 pts->flag &= ~GP_SPOINT_SELECT;
2248                                                 pts->flag |= GP_SPOINT_TAG;
2249                                         }
2250                                 }
2251                         }
2252
2253                         /* Add new stroke to the frame or delete if below limit */
2254                         if ((limit > 0) && (new_stroke->totpoints <= limit)) {
2255                                 BKE_gpencil_free_stroke(new_stroke);
2256                         }
2257                         else {
2258                                 if (next_stroke) {
2259                                         BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
2260                                 }
2261                                 else {
2262                                         BLI_addtail(&gpf->strokes, new_stroke);
2263                                 }
2264                         }
2265                 }
2266                 /* if cyclic, need to join last stroke with first stroke */
2267                 if ((is_cyclic) && (gps_first != NULL) && (gps_first != new_stroke)) {
2268                         gp_stroke_join_islands(gpf, gps_first, new_stroke);
2269                 }
2270
2271         }
2272
2273         /* free islands */
2274         MEM_freeN(islands);
2275
2276         /* Delete the old stroke */
2277         BLI_remlink(&gpf->strokes, gps);
2278         BKE_gpencil_free_stroke(gps);
2279 }
2280
2281 /* Split selected strokes into segments, splitting on selected points */
2282 static int gp_delete_selected_points(bContext *C)
2283 {
2284         Object *ob = CTX_data_active_object(C);
2285         bGPdata *gpd = ED_gpencil_data_get_active(C);
2286         const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
2287         bool changed = false;
2288
2289         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
2290         {
2291                 bGPDframe *init_gpf = gpl->actframe;
2292                 if (is_multiedit) {
2293                         init_gpf = gpl->frames.first;
2294                 }
2295
2296                 for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
2297                         if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
2298                                 bGPDstroke *gps, *gpsn;
2299
2300                                 if (gpf == NULL)
2301                                         continue;
2302
2303                                 /* simply delete strokes which are selected */
2304                                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
2305                                         gpsn = gps->next;
2306
2307                                         /* skip strokes that are invalid for current view */
2308                                         if (ED_gpencil_stroke_can_use(C, gps) == false)
2309                                                 continue;
2310                                         /* check if the color is editable */
2311                                         if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false)
2312                                                 continue;
2313
2314
2315                                         if (gps->flag & GP_STROKE_SELECT) {
2316                                                 /* deselect old stroke, since it will be used as template for the new strokes */
2317                                                 gps->flag &= ~GP_STROKE_SELECT;
2318
2319                                                 /* delete unwanted points by splitting stroke into several smaller ones */
2320                                                 gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0);
2321
2322                                                 changed = true;
2323                                         }
2324                                 }
2325                         }
2326                 }
2327         }
2328         CTX_DATA_END;
2329
2330         if (changed) {
2331                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2332                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2333                 return OPERATOR_FINISHED;
2334         }
2335         else {
2336                 return OPERATOR_CANCELLED;
2337         }
2338 }
2339
2340 /* simple wrapper to external call */
2341 int gp_delete_selected_point_wrap(bContext *C)
2342 {
2343         return gp_delete_selected_points(C);
2344 }
2345
2346 /* ----------------------------------- */
2347
2348 static int gp_delete_exec(bContext *C, wmOperator *op)
2349 {
2350         eGP_DeleteMode mode = RNA_enum_get(op->ptr, "type");
2351         int result = OPERATOR_CANCELLED;
2352
2353         switch (mode) {
2354                 case GP_DELETEOP_STROKES:       /* selected strokes */
2355                         result = gp_delete_selected_strokes(C);
2356                         break;
2357
2358                 case GP_DELETEOP_POINTS:        /* selected points (breaks the stroke into segments) */
2359                         result = gp_delete_selected_points(C);
2360                         break;
2361
2362                 case GP_DELETEOP_FRAME:         /* active frame */
2363                         result = gp_actframe_delete_exec(C, op);
2364                         break;
2365         }
2366
2367         return result;
2368 }
2369
2370 void GPENCIL_OT_delete(wmOperatorType *ot)
2371 {
2372         static const EnumPropertyItem prop_gpencil_delete_types[] = {
2373                 {GP_DELETEOP_POINTS, "POINTS", 0, "Points", "Delete selected points and split strokes into segments"},
2374                 {GP_DELETEOP_STROKES, "STROKES", 0, "Strokes", "Delete selected strokes"},
2375                 {GP_DELETEOP_FRAME, "FRAME", 0, "Frame", "Delete active frame"},
2376                 {0, NULL, 0, NULL, NULL},
2377         };
2378
2379         /* identifiers */
2380         ot->name = "Delete";
2381         ot->idname = "GPENCIL_OT_delete";
2382         ot->description = "Delete selected Grease Pencil strokes, vertices, or frames";
2383
2384         /* callbacks */
2385         ot->invoke = WM_menu_invoke;
2386         ot->exec = gp_delete_exec;
2387         ot->poll = gp_stroke_edit_poll;
2388
2389         /* flags */
2390         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
2391
2392         /* props */
2393         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_delete_types, 0, "Type", "Method used for deleting Grease Pencil data");
2394 }
2395
2396 static int gp_dissolve_exec(bContext *C, wmOperator *op)
2397 {
2398         eGP_DissolveMode mode = RNA_enum_get(op->ptr, "type");
2399
2400         return gp_dissolve_selected_points(C, mode);
2401 }
2402
2403 void GPENCIL_OT_dissolve(wmOperatorType *ot)
2404 {
2405         static EnumPropertyItem prop_gpencil_dissolve_types[] = {
2406                 {GP_DISSOLVE_POINTS, "POINTS", 0, "Dissolve", "Dissolve selected points"},
2407                 {GP_DISSOLVE_BETWEEN, "BETWEEN", 0, "Dissolve Between", "Dissolve points between selected points"},
2408                 {GP_DISSOLVE_UNSELECT, "UNSELECT", 0, "Dissolve Unselect", "Dissolve all unselected points"},
2409                 {0, NULL, 0, NULL, NULL},
2410         };
2411
2412         /* identifiers */
2413         ot->name = "Dissolve";
2414         ot->idname = "GPENCIL_OT_dissolve";
2415         ot->description = "Delete selected points without splitting strokes";
2416
2417         /* callbacks */
2418         ot->invoke = WM_menu_invoke;
2419         ot->exec = gp_dissolve_exec;
2420         ot->poll = gp_stroke_edit_poll;
2421
2422         /* flags */
2423         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
2424
2425         /* props */
2426         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_dissolve_types, 0,
2427                                 "Type", "Method used for dissolving Stroke points");
2428 }
2429
2430 /* ****************** Snapping - Strokes <-> Cursor ************************ */
2431
2432 /* Poll callback for snap operators */
2433 /* NOTE: For now, we only allow these in the 3D view, as other editors do not
2434  *       define a cursor or gridstep which can be used
2435  */
2436 static bool gp_snap_poll(bContext *C)
2437 {
2438         bGPdata *gpd = CTX_data_gpencil_data(C);
2439         ScrArea *sa = CTX_wm_area(C);
2440
2441         return (gpd != NULL) && ((sa != NULL) && (sa->spacetype == SPACE_VIEW3D));
2442 }
2443
2444 /* --------------------------------- */
2445
2446 static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op))
2447 {
2448         bGPdata *gpd = ED_gpencil_data_get_active(C);
2449         RegionView3D *rv3d = CTX_wm_region_data(C);
2450         View3D *v3d = CTX_wm_view3d(C);
2451         Scene *scene = CTX_data_scene(C);
2452         Depsgraph *depsgraph = CTX_data_depsgraph(C);
2453         Object *obact = CTX_data_active_object(C);
2454         const float gridf = ED_view3d_grid_view_scale(scene, v3d, rv3d, NULL);
2455
2456         for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
2457                 /* only editable and visible layers are considered */
2458                 if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) {
2459                         bGPDframe *gpf = gpl->actframe;
2460                         float diff_mat[4][4];
2461
2462                         /* calculate difference matrix object */
2463                         ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat);
2464
2465                         for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
2466                                 bGPDspoint *pt;
2467                                 int i;
2468
2469                                 /* skip strokes that are invalid for current view */
2470                                 if (ED_gpencil_stroke_can_use(C, gps) == false)
2471                                         continue;
2472                                 /* check if the color is editable */
2473                                 if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false)
2474                                         continue;
2475
2476                                 // TODO: if entire stroke is selected, offset entire stroke by same amount?
2477                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2478                                         /* only if point is selected */
2479                                         if (pt->flag & GP_SPOINT_SELECT) {
2480                                                 /* apply parent transformations */
2481                                                 float fpt[3];
2482                                                 mul_v3_m4v3(fpt, diff_mat, &pt->x);
2483
2484                                                 fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf);
2485                                                 fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf);
2486                                                 fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf);
2487
2488                                                 /* return data */
2489                                                 copy_v3_v3(&pt->x, fpt);
2490                                                 gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt);
2491                                         }
2492                                 }
2493                         }
2494                 }
2495         }
2496
2497         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2498         DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE);
2499         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2500         return OPERATOR_FINISHED;
2501 }
2502
2503 void GPENCIL_OT_snap_to_grid(wmOperatorType *ot)
2504 {
2505         /* identifiers */
2506         ot->name = "Snap Selection to Grid";
2507         ot->idname = "GPENCIL_OT_snap_to_grid";
2508         ot->description = "Snap selected points to the nearest grid points";
2509
2510         /* callbacks */
2511         ot->exec = gp_snap_to_grid;
2512         ot->poll = gp_snap_poll;
2513
2514         /* flags */
2515         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2516 }
2517
2518 /* ------------------------------- */
2519
2520 static int gp_snap_to_cursor(bContext *C, wmOperator *op)
2521 {
2522         bGPdata *gpd = ED_gpencil_data_get_active(C);
2523
2524         Scene *scene = CTX_data_scene(C);
2525         Depsgraph *depsgraph = CTX_data_depsgraph(C);
2526         Object *obact = CTX_data_active_object(C);
2527
2528         const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
2529         const float *cursor_global = scene->cursor.location;
2530
2531         for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
2532                 /* only editable and visible layers are considered */
2533                 if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) {
2534                         bGPDframe *gpf = gpl->actframe;
2535                         float diff_mat[4][4];
2536
2537                         /* calculate difference matrix */
2538                         ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat);
2539
2540                         for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
2541                                 bGPDspoint *pt;
2542                                 int i;
2543
2544                                 /* skip strokes that are invalid for current view */
2545                                 if (ED_gpencil_stroke_can_use(C, gps) == false)
2546                                         continue;
2547                                 /* check if the color is editable */
2548                                 if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false)
2549                                         continue;
2550                                 /* only continue if this stroke is selected (editable doesn't guarantee this)... */
2551                                 if ((gps->flag & GP_STROKE_SELECT) == 0)
2552                                         continue;
2553
2554                                 if (use_offset) {
2555                                         float offset[3];
2556
2557                                         /* compute offset from first point of stroke to cursor */
2558                                         /* TODO: Allow using midpoint instead? */
2559                                         sub_v3_v3v3(offset, cursor_global, &gps->points->x);
2560
2561                                         /* apply offset to all points in the stroke */
2562                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2563                                                 add_v3_v3(&pt->x, offset);
2564                                         }
2565                                 }
2566                                 else {
2567                                         /* affect each selected point */
2568                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2569                                                 if (pt->flag & GP_SPOINT_SELECT) {
2570                                                         copy_v3_v3(&pt->x, cursor_global);
2571                                                         gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt);
2572                                                 }
2573                                         }
2574                                 }
2575                         }
2576
2577                 }
2578         }
2579
2580         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2581         DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE);
2582         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2583         return OPERATOR_FINISHED;
2584 }
2585
2586 void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot)
2587 {
2588         /* identifiers */
2589         ot->name = "Snap Selection to Cursor";
2590         ot->idname = "GPENCIL_OT_snap_to_cursor";
2591         ot->description = "Snap selected points/strokes to the cursor";
2592
2593         /* callbacks */
2594         ot->exec = gp_snap_to_cursor;
2595         ot->poll = gp_snap_poll;
2596
2597         /* flags */
2598         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2599
2600         /* props */
2601         ot->prop = RNA_def_boolean(
2602                 ot->srna, "use_offset", true, "With Offset",
2603                 "Offset the entire stroke instead of selected points only");
2604 }
2605
2606 /* ------------------------------- */
2607
2608 static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op))
2609 {
2610         bGPdata *gpd = ED_gpencil_data_get_active(C);
2611
2612         Scene *scene = CTX_data_scene(C);
2613         View3D *v3d = CTX_wm_view3d(C);
2614         Depsgraph *depsgraph = CTX_data_depsgraph(C);
2615         Object *obact = CTX_data_active_object(C);
2616
2617         float *cursor = scene->cursor.location;
2618         float centroid[3] = {0.0f};
2619         float min[3], max[3];
2620         size_t count = 0;
2621
2622         INIT_MINMAX(min, max);
2623
2624         /* calculate midpoints from selected points */
2625         for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
2626                 /* only editable and visible layers are considered */
2627                 if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) {
2628                         bGPDframe *gpf = gpl->actframe;
2629                         float diff_mat[4][4];
2630
2631                         /* calculate difference matrix */
2632                         ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat);
2633
2634                         for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
2635                                 bGPDspoint *pt;
2636                                 int i;
2637
2638                                 /* skip strokes that are invalid for current view */
2639                                 if (ED_gpencil_stroke_can_use(C, gps) == false)
2640                                         continue;
2641                                 /* check if the color is editable */
2642                                 if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false)
2643                                         continue;
2644                                 /* only continue if this stroke is selected (editable doesn't guarantee this)... */
2645                                 if ((gps->flag & GP_STROKE_SELECT) == 0)
2646                                         continue;
2647
2648                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2649                                         if (pt->flag & GP_SPOINT_SELECT) {
2650                                                 /* apply parent transformations */
2651                                                 float fpt[3];
2652                                                 mul_v3_m4v3(fpt, diff_mat, &pt->x);
2653
2654                                                 add_v3_v3(centroid, fpt);
2655                                                 minmax_v3v3_v3(min, max, fpt);
2656
2657                                                 count++;
2658                                         }
2659                                 }
2660
2661                         }
2662                 }
2663         }
2664
2665         if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_MEDIAN && count) {
2666                 mul_v3_fl(centroid, 1.0f / (float)count);
2667                 copy_v3_v3(cursor, centroid);
2668         }
2669         else {
2670                 mid_v3_v3v3(cursor, min, max);
2671         }
2672
2673
2674         DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
2675         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
2676
2677         return OPERATOR_FINISHED;
2678 }
2679
2680 void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot)
2681 {
2682         /* identifiers */
2683         ot->name = "Snap Cursor to Selected Points";
2684         ot->idname = "GPENCIL_OT_snap_cursor_to_selected";
2685         ot->description = "Snap cursor to center of selected points";
2686
2687         /* callbacks */
2688         ot->exec = gp_snap_cursor_to_sel;
2689         ot->poll = gp_snap_poll;
2690
2691         /* flags */
2692         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2693 }
2694
2695 /* ******************* Apply layer thickness change to strokes ************************** */
2696
2697 static int gp_stroke_apply_thickness_exec(bContext *C, wmOperator *UNUSED(op))
2698 {
2699         bGPdata *gpd = ED_gpencil_data_get_active(C);
2700         bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd);
2701
2702         /* sanity checks */
2703         if (ELEM(NULL, gpd, gpl, gpl->frames.first))
2704                 return OPERATOR_CANCELLED;
2705
2706         /* loop all strokes */
2707         for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
2708                 for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
2709                         /* Apply thickness */
2710                         if ((gps->thickness == 0) && (gpl->line_change == 0)) {
2711                                 gps->thickness = gpl->thickness;
2712                         }
2713                         else {
2714                                 gps->thickness = gps->thickness + gpl->line_change;
2715                         }
2716                 }
2717         }
2718         /* clear value */
2719         gpl->thickness = 0.0f;
2720         gpl->line_change = 0;
2721
2722         /* notifiers */
2723         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2724         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2725
2726         return OPERATOR_FINISHED;
2727 }
2728
2729 void GPENCIL_OT_stroke_apply_thickness(wmOperatorType *ot)
2730 {
2731         /* identifiers */
2732         ot->name = "Apply Stroke Thickness";
2733         ot->idname = "GPENCIL_OT_stroke_apply_thickness";
2734         ot->description = "Apply the thickness change of the layer to its strokes";
2735
2736         /* api callbacks */
2737         ot->exec = gp_stroke_apply_thickness_exec;
2738         ot->poll = gp_active_layer_poll;
2739 }
2740
2741 /* ******************* Close Strokes ************************** */
2742
2743 enum {
2744         GP_STROKE_CYCLIC_CLOSE = 1,
2745         GP_STROKE_CYCLIC_OPEN = 2,
2746         GP_STROKE_CYCLIC_TOGGLE = 3,
2747 };
2748
2749 static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op)
2750 {
2751         bGPdata *gpd = ED_gpencil_data_get_active(C);
2752         Object *ob = CTX_data_active_object(C);
2753
2754         const int type = RNA_enum_get(op->ptr, "type");
2755
2756         /* sanity checks */
2757         if (ELEM(NULL, gpd))
2758                 return OPERATOR_CANCELLED;
2759
2760         /* loop all selected strokes */
2761         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
2762         {
2763                 if (gpl->actframe == NULL)
2764                         continue;
2765
2766                 for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) {
2767                         MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1);
2768
2769                         /* skip strokes that are not selected or invalid for current view */
2770                         if (((gps->flag & GP_STROKE_SELECT) == 0) || ED_gpencil_stroke_can_use(C, gps) == false)
2771                                 continue;
2772                         /* skip hidden or locked colors */
2773                         if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || (gp_style->flag & GP_STYLE_COLOR_LOCKED))
2774                                 continue;
2775
2776                         switch (type) {
2777                                 case GP_STROKE_CYCLIC_CLOSE:
2778                                         /* Close all (enable) */
2779                                         gps->flag |= GP_STROKE_CYCLIC;
2780                                         break;
2781                                 case GP_STROKE_CYCLIC_OPEN:
2782                                         /* Open all (disable) */
2783                                         gps->flag &= ~GP_STROKE_CYCLIC;
2784                                         break;
2785                                 case GP_STROKE_CYCLIC_TOGGLE:
2786                                         /* Just toggle flag... */
2787                                         gps->flag ^= GP_STROKE_CYCLIC;
2788                                         break;
2789                                 default:
2790                                         BLI_assert(0);
2791                                         break;
2792                         }
2793                 }
2794         }
2795         CTX_DATA_END;
2796
2797         /* notifiers */
2798         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2799         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2800
2801         return OPERATOR_FINISHED;
2802 }
2803
2804 /**
2805  * Similar to #CURVE_OT_cyclic_toggle or #MASK_OT_cyclic_toggle, but with
2806  * option to force opened/closed strokes instead of just toggle behavior.
2807  */
2808 void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot)
2809 {
2810         static const EnumPropertyItem cyclic_type[] = {
2811                 {GP_STROKE_CYCLIC_CLOSE, "CLOSE", 0, "Close all", ""},
2812                 {GP_STROKE_CYCLIC_OPEN, "OPEN", 0, "Open all", ""},
2813                 {GP_STROKE_CYCLIC_TOGGLE, "TOGGLE", 0, "Toggle", ""},
2814                 {0, NULL, 0, NULL, NULL},
2815         };
2816
2817         /* identifiers */
2818         ot->name = "Set Cyclical State";
2819         ot->idname = "GPENCIL_OT_stroke_cyclical_set";
2820         ot->description = "Close or open the selected stroke adding an edge from last to first point";
2821
2822         /* api callbacks */
2823         ot->exec = gp_stroke_cyclical_set_exec;
2824         ot->poll = gp_active_layer_poll;
2825
2826         /* flags */
2827         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2828
2829         /* properties */
2830         ot->prop = RNA_def_enum(ot->srna, "type", cyclic_type, GP_STROKE_CYCLIC_TOGGLE, "Type", "");
2831 }
2832
2833 /* ******************* Flat Stroke Caps ************************** */
2834
2835 enum {
2836         GP_STROKE_CAPS_TOGGLE_BOTH    = 0,
2837         GP_STROKE_CAPS_TOGGLE_START   = 1,
2838         GP_STROKE_CAPS_TOGGLE_END     = 2,
2839         GP_STROKE_CAPS_TOGGLE_DEFAULT = 3,
2840 };
2841
2842 static int gp_stroke_caps_set_exec(bContext *C, wmOperator *op)
2843 {
2844         bGPdata *gpd = ED_gpencil_data_get_active(C);
2845         Object *ob = CTX_data_active_object(C);
2846
2847         const int type = RNA_enum_get(op->ptr, "type");
2848
2849         /* sanity checks */
2850         if (ELEM(NULL, gpd))
2851                 return OPERATOR_CANCELLED;
2852
2853         /* loop all selected strokes */
2854         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
2855         {
2856                 if (gpl->actframe == NULL)
2857                         continue;
2858
2859                 for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) {
2860                         MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1);
2861
2862                         /* skip strokes that are not selected or invalid for current view */
2863                         if (((gps->flag & GP_STROKE_SELECT) == 0) ||
2864                             (ED_gpencil_stroke_can_use(C, gps) == false))
2865                         {
2866                                 continue;
2867                         }
2868                         /* skip hidden or locked colors */
2869                         if (!gp_style ||
2870                             (gp_style->flag & GP_STYLE_COLOR_HIDE) ||
2871                             (gp_style->flag & GP_STYLE_COLOR_LOCKED))
2872                         {
2873                                 continue;
2874                         }
2875
2876                         if ((type == GP_STROKE_CAPS_TOGGLE_BOTH) ||
2877                             (type == GP_STROKE_CAPS_TOGGLE_START))
2878                         {
2879                                 ++gps->caps[0];
2880                                 if (gps->caps[0] >= GP_STROKE_CAP_MAX) {
2881                                         gps->caps[0] = GP_STROKE_CAP_ROUND;
2882                                 }
2883                         }
2884                         if ((type == GP_STROKE_CAPS_TOGGLE_BOTH) ||
2885                             (type == GP_STROKE_CAPS_TOGGLE_END))
2886                         {
2887                                 ++gps->caps[1];
2888                                 if (gps->caps[1] >= GP_STROKE_CAP_MAX) {
2889                                         gps->caps[1] = GP_STROKE_CAP_ROUND;
2890                                 }
2891                         }
2892                         if (type == GP_STROKE_CAPS_TOGGLE_DEFAULT) {
2893                                 gps->caps[0] = GP_STROKE_CAP_ROUND;
2894                                 gps->caps[1] = GP_STROKE_CAP_ROUND;
2895                         }
2896                 }
2897         }
2898         CTX_DATA_END;
2899
2900         /* notifiers */
2901         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2902         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2903
2904         return OPERATOR_FINISHED;
2905 }
2906
2907 /**
2908  * Change Stroke caps mode Rounded or Flat
2909  */
2910 void GPENCIL_OT_stroke_caps_set(wmOperatorType *ot)
2911 {
2912         static const EnumPropertyItem toggle_type[] = {
2913                 {GP_STROKE_CAPS_TOGGLE_BOTH, "TOGGLE", 0, "Both", ""},
2914                 {GP_STROKE_CAPS_TOGGLE_START, "START", 0, "Start", ""},
2915                 {GP_STROKE_CAPS_TOGGLE_END, "END", 0, "End", ""},
2916                 {GP_STROKE_CAPS_TOGGLE_DEFAULT, "TOGGLE", 0, "Default", "Set as default rounded"},
2917                 {0, NULL, 0, NULL, NULL},
2918         };
2919
2920         /* identifiers */
2921         ot->name = "Set Caps Mode";
2922         ot->idname = "GPENCIL_OT_stroke_caps_set";
2923         ot->description = "Change Stroke caps mode (rounded or flat)";
2924
2925         /* api callbacks */
2926         ot->exec = gp_stroke_caps_set_exec;
2927         ot->poll = gp_active_layer_poll;
2928
2929         /* flags */
2930         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2931
2932         /* properties */
2933         ot->prop = RNA_def_enum(ot->srna, "type", toggle_type, GP_STROKE_CAPS_TOGGLE_BOTH, "Type", "");
2934 }
2935
2936 /* ******************* Stroke join ************************** */
2937
2938 /* Helper: flip stroke */
2939 static void gpencil_flip_stroke(bGPDstroke *gps)
2940 {
2941         int end = gps->totpoints - 1;
2942
2943         for (int i = 0; i < gps->totpoints / 2; i++) {
2944                 bGPDspoint *point, *point2;
2945                 bGPDspoint pt;
2946
2947                 /* save first point */
2948                 point = &gps->points[i];
2949                 pt.x = point->x;
2950                 pt.y = point->y;
2951                 pt.z = point->z;
2952                 pt.flag = point->flag;
2953                 pt.pressure = point->pressure;
2954                 pt.strength = point->strength;
2955                 pt.time = point->time;
2956
2957                 /* replace first point with last point */