GPencil: Changes in Fill and new 3D Cursor View Plane
[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 Selection";
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_point(
761         bGPDstroke *gps,
762         bGPDspoint *temp_points,
763         MDeformVert *temp_dverts,
764         int from_idx, int to_idx)
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                 dvert_final->dw = dvert->dw;
783         }
784 }
785
786 static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps)
787 {
788         bGPDspoint *temp_points = NULL;
789         MDeformVert *temp_dverts = NULL;
790         bGPDspoint *pt = NULL;
791         const bGPDspoint *pt_start = &gps->points[0];
792         const bGPDspoint *pt_last = &gps->points[gps->totpoints - 1];
793         const bool do_first = (pt_start->flag & GP_SPOINT_SELECT);
794         const bool do_last = ((pt_last->flag & GP_SPOINT_SELECT) && (pt_start != pt_last));
795         const bool do_stroke = (do_first || do_last);
796
797         /* review points in the midle of stroke to create new strokes */
798         for (int i = 0; i < gps->totpoints; i++) {
799                 /* skip first and last point */
800                 if ((i == 0) || (i == gps->totpoints - 1)) {
801                         continue;
802                 }
803
804                 pt = &gps->points[i];
805                 if (pt->flag == GP_SPOINT_SELECT) {
806                         /* duplicate original stroke data */
807                         bGPDstroke *gps_new = MEM_dupallocN(gps);
808                         gps_new->prev = gps_new->next = NULL;
809
810                         /* add new points array */
811                         gps_new->totpoints = 1;
812                         gps_new->points = MEM_callocN(sizeof(bGPDspoint), __func__);
813                         gps_new->dvert = NULL;
814
815                         if (gps->dvert != NULL) {
816                                 gps_new->dvert = MEM_callocN(sizeof(MDeformVert), __func__);
817                         }
818
819                         gps->flag |= GP_STROKE_RECALC_GEOMETRY;
820                         gps_new->triangles = NULL;
821                         gps_new->tot_triangles = 0;
822                         BLI_insertlinkafter(&gpf->strokes, gps, gps_new);
823
824                         /* copy selected point data to new stroke */
825                         copy_point(gps_new, gps->points, gps->dvert, i, 0);
826
827                         /* deselect orinal point */
828                         pt->flag &= ~GP_SPOINT_SELECT;
829                 }
830         }
831
832         /* review first and last point to reuse same stroke */
833         int i2 = 0;
834         int totnewpoints, oldtotpoints;
835         /* if first or last, reuse stroke and resize */
836         if ((do_first) || (do_last)) {
837                 totnewpoints = gps->totpoints;
838                 if (do_first) {
839                         totnewpoints++;
840                 }
841                 if (do_last) {
842                         totnewpoints++;
843                 }
844
845                 /* duplicate points in a temp area */
846                 temp_points = MEM_dupallocN(gps->points);
847                 oldtotpoints = gps->totpoints;
848                 if (gps->dvert != NULL) {
849                         temp_dverts = MEM_dupallocN(gps->dvert);
850                 }
851
852                 /* if first point, need move all one position */
853                 if (do_first) {
854                         i2 = 1;
855                 }
856
857                 /* resize the points arrays */
858                 gps->totpoints = totnewpoints;
859                 gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
860                 if (gps->dvert != NULL) {
861                         gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
862                 }
863
864                 /* move points to new position */
865                 for (int i = 0; i < oldtotpoints; i++) {
866                         copy_point(gps, temp_points, temp_dverts, i, i2);
867                         i2++;
868                 }
869                 gps->flag |= GP_STROKE_RECALC_GEOMETRY;
870
871                 /* if first point, add new point at the begining */
872                 if (do_first) {
873                         copy_point(gps, temp_points, temp_dverts, 0, 0);
874                         /* deselect old */
875                         pt = &gps->points[1];
876                         pt->flag &= ~GP_SPOINT_SELECT;
877                         /* select new */
878                         pt = &gps->points[0];
879                         pt->flag |= GP_SPOINT_SELECT;
880                 }
881
882                 /* if last point, add new point at the end */
883                 if (do_last) {
884                         copy_point(
885                                 gps, temp_points, temp_dverts,
886                                 oldtotpoints - 1, gps->totpoints - 1);
887
888                         /* deselect old */
889                         pt = &gps->points[gps->totpoints - 2];
890                         pt->flag &= ~GP_SPOINT_SELECT;
891                         /* select new */
892                         pt = &gps->points[gps->totpoints - 1];
893                         pt->flag |= GP_SPOINT_SELECT;
894                 }
895
896                 MEM_SAFE_FREE(temp_points);
897                 MEM_SAFE_FREE(temp_dverts);
898         }
899
900         /* if the stroke is not reused, deselect */
901         if (!do_stroke) {
902                 gps->flag &= ~GP_STROKE_SELECT;
903         }
904 }
905
906 static int gp_extrude_exec(bContext *C, wmOperator *op)
907 {
908         Object *obact = CTX_data_active_object(C);
909         bGPdata *gpd = (bGPdata *)obact->data;
910         const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
911         bGPDstroke *gps = NULL;
912
913         if (gpd == NULL) {
914                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
915                 return OPERATOR_CANCELLED;
916         }
917
918         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
919         {
920                 bGPDframe *init_gpf = gpl->actframe;
921                 if (is_multiedit) {
922                         init_gpf = gpl->frames.first;
923                 }
924
925                 for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
926                         if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
927                                 if (gpf == NULL)
928                                         continue;
929
930                                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
931                                         /* skip strokes that are invalid for current view */
932                                         if (ED_gpencil_stroke_can_use(C, gps) == false)
933                                                 continue;
934
935                                         if (gps->flag & GP_STROKE_SELECT) {
936                                                 gpencil_add_move_points(gpf, gps);
937                                         }
938                                 }
939                                 /* if not multiedit, exit loop*/
940                                 if (!is_multiedit) {
941                                         break;
942                                 }
943                         }
944                 }
945         }
946         CTX_DATA_END;
947
948         /* updates */
949         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
950         DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE);
951         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
952
953         return OPERATOR_FINISHED;
954 }
955
956 void GPENCIL_OT_extrude(wmOperatorType *ot)
957 {
958         /* identifiers */
959         ot->name = "Extrude Stroke Points";
960         ot->idname = "GPENCIL_OT_extrude";
961         ot->description = "Extrude the selected Grease Pencil points";
962
963         /* callbacks */
964         ot->exec = gp_extrude_exec;
965         ot->poll = gp_stroke_edit_poll;
966
967         /* flags */
968         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
969 }
970
971
972 /* ******************* Copy/Paste Strokes ************************* */
973 /* Grease Pencil stroke data copy/paste buffer:
974  * - The copy operation collects all segments of selected strokes,
975  *   dumping "ready to be copied" copies of the strokes into the buffer.
976  * - The paste operation makes a copy of those elements, and adds them
977  *   to the active layer. This effectively flattens down the strokes
978  *   from several different layers into a single layer.
979  */
980
981  /* list of bGPDstroke instances */
982  /* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */
983 ListBase gp_strokes_copypastebuf = {NULL, NULL};
984
985 /* Hash for hanging on to all the colors used by strokes in the buffer
986  *
987  * This is needed to prevent dangling and unsafe pointers when pasting across datablocks,
988  * or after a color used by a stroke in the buffer gets deleted (via user action or undo).
989  */
990 static GHash *gp_strokes_copypastebuf_colors = NULL;
991
992 static GHash *gp_strokes_copypastebuf_colors_material_to_name_create(Main *bmain)
993 {
994         GHash *ma_to_name = BLI_ghash_ptr_new(__func__);
995
996         for (Material *ma = bmain->materials.first; ma != NULL; ma = ma->id.next) {
997                 char *name = BKE_id_to_unique_string_key(&ma->id);
998                 BLI_ghash_insert(ma_to_name, ma, name);
999         }
1000
1001         return ma_to_name;
1002 }
1003
1004 static void gp_strokes_copypastebuf_colors_material_to_name_free(GHash *ma_to_name)
1005 {
1006         BLI_ghash_free(ma_to_name, NULL, MEM_freeN);
1007 }
1008
1009 static GHash *gp_strokes_copypastebuf_colors_name_to_material_create(Main *bmain)
1010 {
1011         GHash *name_to_ma = BLI_ghash_str_new(__func__);
1012
1013         for (Material *ma = bmain->materials.first; ma != NULL; ma = ma->id.next) {
1014                 char *name = BKE_id_to_unique_string_key(&ma->id);
1015                 BLI_ghash_insert(name_to_ma, name, ma);
1016         }
1017
1018         return name_to_ma;
1019 }
1020
1021 static void gp_strokes_copypastebuf_colors_name_to_material_free(GHash *name_to_ma)
1022 {
1023         BLI_ghash_free(name_to_ma, MEM_freeN, NULL);
1024 }
1025
1026 /* Free copy/paste buffer data */
1027 void ED_gpencil_strokes_copybuf_free(void)
1028 {
1029         bGPDstroke *gps, *gpsn;
1030
1031         /* Free the colors buffer
1032          * NOTE: This is done before the strokes so that the ptrs are still safe
1033          */
1034         if (gp_strokes_copypastebuf_colors) {
1035                 BLI_ghash_free(gp_strokes_copypastebuf_colors, NULL, MEM_freeN);
1036                 gp_strokes_copypastebuf_colors = NULL;
1037         }
1038
1039         /* Free the stroke buffer */
1040         for (gps = gp_strokes_copypastebuf.first; gps; gps = gpsn) {
1041                 gpsn = gps->next;
1042
1043                 if (gps->points) {
1044                         MEM_freeN(gps->points);
1045                 }
1046                 if (gps->dvert) {
1047                         BKE_gpencil_free_stroke_weights(gps);
1048                         MEM_freeN(gps->dvert);
1049                 }
1050
1051                 MEM_SAFE_FREE(gps->triangles);
1052
1053                 BLI_freelinkN(&gp_strokes_copypastebuf, gps);
1054         }
1055
1056         gp_strokes_copypastebuf.first = gp_strokes_copypastebuf.last = NULL;
1057 }
1058
1059 /* Ensure that destination datablock has all the colours the pasted strokes need
1060  * Helper function for copy-pasting strokes
1061  */
1062 GHash *gp_copybuf_validate_colormap(bContext *C)
1063 {
1064         Main *bmain = CTX_data_main(C);
1065         Object *ob = CTX_data_active_object(C);
1066         GHash *new_colors = BLI_ghash_int_new("GPencil Paste Dst Colors");
1067         GHashIterator gh_iter;
1068
1069         /* For each color, check if exist and add if not */
1070         GHash *name_to_ma = gp_strokes_copypastebuf_colors_name_to_material_create(bmain);
1071
1072         GHASH_ITER(gh_iter, gp_strokes_copypastebuf_colors) {
1073                 int *key = BLI_ghashIterator_getKey(&gh_iter);
1074                 char *ma_name = BLI_ghashIterator_getValue(&gh_iter);
1075                 Material *ma = BLI_ghash_lookup(name_to_ma, ma_name);
1076
1077                 if (ma != NULL && BKE_gpencil_get_material_index(ob, ma) == 0) {
1078                         BKE_object_material_slot_add(bmain, ob);
1079                         assign_material(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
1080                 }
1081
1082                 /* Store this mapping (for use later when pasting) */
1083                 if (!BLI_ghash_haskey(new_colors, POINTER_FROM_INT(*key))) {
1084                         BLI_ghash_insert(new_colors, POINTER_FROM_INT(*key), ma);
1085                 }
1086         }
1087
1088         gp_strokes_copypastebuf_colors_name_to_material_free(name_to_ma);
1089
1090         return new_colors;
1091 }
1092
1093 /* --------------------- */
1094 /* Copy selected strokes */
1095
1096 static int gp_strokes_copy_exec(bContext *C, wmOperator *op)
1097 {
1098         Main *bmain = CTX_data_main(C);
1099         Object *ob = CTX_data_active_object(C);
1100         bGPdata *gpd = ED_gpencil_data_get_active(C);
1101
1102         if (gpd == NULL) {
1103                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
1104                 return OPERATOR_CANCELLED;
1105         }
1106
1107         if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) {
1108                 BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition");
1109                 return OPERATOR_CANCELLED;
1110         }
1111
1112         /* clear the buffer first */
1113         ED_gpencil_strokes_copybuf_free();
1114
1115         /* for each visible (and editable) layer's selected strokes,
1116          * copy the strokes into a temporary buffer, then append
1117          * once all done
1118          */
1119         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1120         {
1121                 bGPDframe *gpf = gpl->actframe;
1122                 bGPDstroke *gps;
1123
1124                 if (gpf == NULL)
1125                         continue;
1126
1127                 /* make copies of selected strokes, and deselect these once we're done */
1128                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
1129                         /* skip strokes that are invalid for current view */
1130                         if (ED_gpencil_stroke_can_use(C, gps) == false)
1131                                 continue;
1132
1133                         if (gps->flag & GP_STROKE_SELECT) {
1134                                 if (gps->totpoints == 1) {
1135                                         /* Special Case: If there's just a single point in this stroke... */
1136                                         bGPDstroke *gpsd;
1137
1138                                         /* make direct copies of the stroke and its points */
1139                                         gpsd = MEM_dupallocN(gps);
1140                                         /* saves original layer name */
1141                                         BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo));
1142                                         gpsd->points = MEM_dupallocN(gps->points);
1143                                         if (gps->dvert != NULL) {
1144                                                 gpsd->dvert = MEM_dupallocN(gps->dvert);
1145                                                 BKE_gpencil_stroke_weights_duplicate(gps, gpsd);
1146                                         }
1147
1148                                         /* triangles cache - will be recalculated on next redraw */
1149                                         gpsd->flag |= GP_STROKE_RECALC_GEOMETRY;
1150                                         gpsd->tot_triangles = 0;
1151                                         gpsd->triangles = NULL;
1152
1153                                         /* add to temp buffer */
1154                                         gpsd->next = gpsd->prev = NULL;
1155                                         BLI_addtail(&gp_strokes_copypastebuf, gpsd);
1156                                 }
1157                                 else {
1158                                         /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
1159                                         gp_duplicate_points(gps, &gp_strokes_copypastebuf, gpl->info);
1160                                 }
1161                         }
1162                 }
1163         }
1164         CTX_DATA_END;
1165
1166         /* Build up hash of material colors used in these strokes */
1167         if (gp_strokes_copypastebuf.first) {
1168                 gp_strokes_copypastebuf_colors = BLI_ghash_int_new("GPencil CopyBuf Colors");
1169                 GHash *ma_to_name = gp_strokes_copypastebuf_colors_material_to_name_create(bmain);
1170                 for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
1171                         if (ED_gpencil_stroke_can_use(C, gps)) {
1172                                 char **ma_name_val;
1173                                 if (!BLI_ghash_ensure_p(gp_strokes_copypastebuf_colors, &gps->mat_nr, (void ***)&ma_name_val)) {
1174                                         Material *ma = give_current_material(ob, gps->mat_nr + 1);
1175                                         char *ma_name = BLI_ghash_lookup(ma_to_name, ma);
1176                                         *ma_name_val = MEM_dupallocN(ma_name);
1177                                 }
1178                         }
1179                 }
1180                 gp_strokes_copypastebuf_colors_material_to_name_free(ma_to_name);
1181         }
1182
1183         /* updates (to ensure operator buttons are refreshed, when used via hotkeys) */
1184         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL); // XXX?
1185
1186         /* done */
1187         return OPERATOR_FINISHED;
1188 }
1189
1190 void GPENCIL_OT_copy(wmOperatorType *ot)
1191 {
1192         /* identifiers */
1193         ot->name = "Copy Strokes";
1194         ot->idname = "GPENCIL_OT_copy";
1195         ot->description = "Copy selected Grease Pencil points and strokes";
1196
1197         /* callbacks */
1198         ot->exec = gp_strokes_copy_exec;
1199         ot->poll = gp_stroke_edit_poll;
1200
1201         /* flags */
1202         //ot->flag = OPTYPE_REGISTER;
1203 }
1204
1205 /* --------------------- */
1206 /* Paste selected strokes */
1207
1208 static bool gp_strokes_paste_poll(bContext *C)
1209 {
1210         /* 1) Must have GP datablock to paste to
1211          *    - We don't need to have an active layer though, as that can easily get added
1212          *    - If the active layer is locked, we can't paste there, but that should prompt a warning instead
1213          * 2) Copy buffer must at least have something (though it may be the wrong sort...)
1214          */
1215         return (ED_gpencil_data_get_active(C) != NULL) && (!BLI_listbase_is_empty(&gp_strokes_copypastebuf));
1216 }
1217
1218 typedef enum eGP_PasteMode {
1219         GP_COPY_ONLY = -1,
1220         GP_COPY_MERGE = 1,
1221 } eGP_PasteMode;
1222
1223 static int gp_strokes_paste_exec(bContext *C, wmOperator *op)
1224 {
1225         Object *ob = CTX_data_active_object(C);
1226         bGPdata *gpd = ED_gpencil_data_get_active(C);
1227         bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); /* only use active for copy merge */
1228         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1229         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1230         bGPDframe *gpf;
1231
1232         eGP_PasteMode type = RNA_enum_get(op->ptr, "type");
1233         GHash *new_colors;
1234
1235         /* check for various error conditions */
1236         if (gpd == NULL) {
1237                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
1238                 return OPERATOR_CANCELLED;
1239         }
1240         else if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) {
1241                 BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition");
1242                 return OPERATOR_CANCELLED;
1243         }
1244         else if (BLI_listbase_is_empty(&gp_strokes_copypastebuf)) {
1245                 BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again");
1246                 return OPERATOR_CANCELLED;
1247         }
1248         else if (gpl == NULL) {
1249                 /* no active layer - let's just create one */
1250                 gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
1251         }
1252         else if ((gpencil_layer_is_editable(gpl) == false) && (type == GP_COPY_MERGE)) {
1253                 BKE_report(op->reports, RPT_ERROR, "Can not paste strokes when active layer is hidden or locked");
1254                 return OPERATOR_CANCELLED;
1255         }
1256         else {
1257                 /* Check that some of the strokes in the buffer can be used */
1258                 bGPDstroke *gps;
1259                 bool ok = false;
1260
1261                 for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
1262                         if (ED_gpencil_stroke_can_use(C, gps)) {
1263                                 ok = true;
1264                                 break;
1265                         }
1266                 }
1267
1268                 if (ok == false) {
1269                         /* XXX: this check is not 100% accurate (i.e. image editor is incompatible with normal 2D strokes),
1270                          * but should be enough to give users a good idea of what's going on
1271                          */
1272                         if (CTX_wm_area(C)->spacetype == SPACE_VIEW3D)
1273                                 BKE_report(op->reports, RPT_ERROR, "Cannot paste 2D strokes in 3D View");
1274                         else
1275                                 BKE_report(op->reports, RPT_ERROR, "Cannot paste 3D strokes in 2D editors");
1276
1277                         return OPERATOR_CANCELLED;
1278                 }
1279         }
1280
1281         /* Deselect all strokes first */
1282         CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
1283         {
1284                 bGPDspoint *pt;
1285                 int i;
1286
1287                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1288                         pt->flag &= ~GP_SPOINT_SELECT;
1289                 }
1290
1291                 gps->flag &= ~GP_STROKE_SELECT;
1292         }
1293         CTX_DATA_END;
1294
1295         /* Ensure that all the necessary colors exist */
1296         new_colors = gp_copybuf_validate_colormap(C);
1297
1298         /* Copy over the strokes from the buffer (and adjust the colors) */
1299         for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
1300                 if (ED_gpencil_stroke_can_use(C, gps)) {
1301                         /* Need to verify if layer exists */
1302                         if (type != GP_COPY_MERGE) {
1303                                 gpl = BLI_findstring(&gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info));
1304                                 if (gpl == NULL) {
1305                                         /* no layer - use active (only if layer deleted before paste) */
1306                                         gpl = CTX_data_active_gpencil_layer(C);
1307                                 }
1308                         }
1309
1310                         /* Ensure we have a frame to draw into
1311                          * NOTE: Since this is an op which creates strokes,
1312                          *       we are obliged to add a new frame if one
1313                          *       doesn't exist already
1314                          */
1315                         gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_ADD_NEW);
1316                         if (gpf) {
1317                                 /* Create new stroke */
1318                                 bGPDstroke *new_stroke = MEM_dupallocN(gps);
1319                                 new_stroke->runtime.tmp_layerinfo[0] = '\0';
1320
1321                                 new_stroke->points = MEM_dupallocN(gps->points);
1322                                 if (gps->dvert != NULL) {
1323                                         new_stroke->dvert = MEM_dupallocN(gps->dvert);
1324                                         BKE_gpencil_stroke_weights_duplicate(gps, new_stroke);
1325                                 }
1326                                 new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY;
1327                                 new_stroke->triangles = NULL;
1328
1329                                 new_stroke->next = new_stroke->prev = NULL;
1330                                 BLI_addtail(&gpf->strokes, new_stroke);
1331
1332                                 /* Remap material */
1333                                 Material *ma = BLI_ghash_lookup(new_colors, POINTER_FROM_INT(new_stroke->mat_nr));
1334                                 if ((ma) && (BKE_gpencil_get_material_index(ob, ma) > 0)) {
1335                                         new_stroke->mat_nr = BKE_gpencil_get_material_index(ob, ma) - 1;
1336                                         CLAMP_MIN(new_stroke->mat_nr, 0);
1337                                 }
1338                                 else {
1339                                         new_stroke->mat_nr = 0; /* only if the color is not found */
1340                                 }
1341
1342                         }
1343                 }
1344         }
1345
1346         /* free temp data */
1347         BLI_ghash_free(new_colors, NULL, NULL);
1348
1349         /* updates */
1350         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1351         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1352
1353         return OPERATOR_FINISHED;
1354 }
1355
1356 void GPENCIL_OT_paste(wmOperatorType *ot)
1357 {
1358         static const EnumPropertyItem copy_type[] = {
1359                 {GP_COPY_ONLY, "COPY", 0, "Copy", ""},
1360                 {GP_COPY_MERGE, "MERGE", 0, "Merge", ""},
1361                 {0, NULL, 0, NULL, NULL},
1362         };
1363
1364         /* identifiers */
1365         ot->name = "Paste Strokes";
1366         ot->idname = "GPENCIL_OT_paste";
1367         ot->description = "Paste previously copied strokes or copy and merge in active layer";
1368
1369         /* callbacks */
1370         ot->exec = gp_strokes_paste_exec;
1371         ot->poll = gp_strokes_paste_poll;
1372
1373         /* flags */
1374         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA;
1375
1376         /* properties */
1377         ot->prop = RNA_def_enum(ot->srna, "type", copy_type, 0, "Type", "");
1378 }
1379
1380 /* ******************* Move To Layer ****************************** */
1381
1382 static int gp_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
1383 {
1384         uiPopupMenu *pup;
1385         uiLayout *layout;
1386
1387         /* call the menu, which will call this operator again, hence the canceled */
1388         pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE);
1389         layout = UI_popup_menu_layout(pup);
1390         uiItemsEnumO(layout, "GPENCIL_OT_move_to_layer", "layer");
1391         UI_popup_menu_end(C, pup);
1392
1393         return OPERATOR_INTERFACE;
1394 }
1395
1396 // FIXME: allow moving partial strokes
1397 static int gp_move_to_layer_exec(bContext *C, wmOperator *op)
1398 {
1399         bGPdata *gpd = CTX_data_gpencil_data(C);
1400         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1401         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1402         bGPDlayer *target_layer = NULL;
1403         ListBase strokes = {NULL, NULL};
1404         int layer_num = RNA_enum_get(op->ptr, "layer");
1405         const bool use_autolock = (bool)(gpd->flag & GP_DATA_AUTOLOCK_LAYERS);
1406
1407         if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) {
1408                 BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition");
1409                 return OPERATOR_CANCELLED;
1410         }
1411
1412         /* if autolock enabled, disabled now */
1413         if (use_autolock) {
1414                 gpd->flag &= ~GP_DATA_AUTOLOCK_LAYERS;
1415         }
1416
1417         /* Get layer or create new one */
1418         if (layer_num == -1) {
1419                 /* Create layer */
1420                 target_layer = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
1421         }
1422         else {
1423                 /* Try to get layer */
1424                 target_layer = BLI_findlink(&gpd->layers, layer_num);
1425
1426                 if (target_layer == NULL) {
1427                         /* back autolock status */
1428                         if (use_autolock) {
1429                                 gpd->flag |= GP_DATA_AUTOLOCK_LAYERS;
1430                         }
1431                         BKE_reportf(op->reports, RPT_ERROR, "There is no layer number %d", layer_num);
1432                         return OPERATOR_CANCELLED;
1433                 }
1434         }
1435
1436         /* Extract all strokes to move to this layer
1437          * NOTE: We need to do this in a two-pass system to avoid conflicts with strokes
1438          *       getting repeatedly moved
1439          */
1440         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1441         {
1442                 bGPDframe *gpf = gpl->actframe;
1443                 bGPDstroke *gps, *gpsn;
1444
1445                 /* skip if no frame with strokes, or if this is the layer we're moving strokes to */
1446                 if ((gpl == target_layer) || (gpf == NULL))
1447                         continue;
1448
1449                 /* make copies of selected strokes, and deselect these once we're done */
1450                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
1451                         gpsn = gps->next;
1452
1453                         /* skip strokes that are invalid for current view */
1454                         if (ED_gpencil_stroke_can_use(C, gps) == false)
1455                                 continue;
1456
1457                         /* TODO: Don't just move entire strokes - instead, only copy the selected portions... */
1458                         if (gps->flag & GP_STROKE_SELECT) {
1459                                 BLI_remlink(&gpf->strokes, gps);
1460                                 BLI_addtail(&strokes, gps);
1461                         }
1462                 }
1463
1464                 /* if new layer and autolock, lock old layer */
1465                 if ((layer_num == -1) && (use_autolock)) {
1466                         gpl->flag |= GP_LAYER_LOCKED;
1467                 }
1468         }
1469         CTX_DATA_END;
1470
1471         /* Paste them all in one go */
1472         if (strokes.first) {
1473                 bGPDframe *gpf = BKE_gpencil_layer_getframe(target_layer, cfra_eval, GP_GETFRAME_ADD_NEW);
1474
1475                 BLI_movelisttolist(&gpf->strokes, &strokes);
1476                 BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL));
1477         }
1478
1479         /* back autolock status */
1480         if (use_autolock) {
1481                 gpd->flag |= GP_DATA_AUTOLOCK_LAYERS;
1482         }
1483
1484         /* updates */
1485         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1486         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1487
1488         return OPERATOR_FINISHED;
1489 }
1490
1491 void GPENCIL_OT_move_to_layer(wmOperatorType *ot)
1492 {
1493         /* identifiers */
1494         ot->name = "Move Strokes to Layer";
1495         ot->idname = "GPENCIL_OT_move_to_layer";
1496         ot->description = "Move selected strokes to another layer"; // XXX: allow moving individual points too?
1497
1498         /* callbacks */
1499         ot->invoke = gp_move_to_layer_invoke;
1500         ot->exec = gp_move_to_layer_exec;
1501         ot->poll = gp_stroke_edit_poll; // XXX?
1502
1503         /* flags */
1504         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1505
1506         /* gp layer to use (dynamic enum) */
1507         ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", "");
1508         RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf);
1509 }
1510
1511 /* ********************* Add Blank Frame *************************** */
1512
1513 /* Basically the same as the drawing op */
1514 static bool UNUSED_FUNCTION(gp_blank_frame_add_poll)(bContext *C)
1515 {
1516         if (ED_operator_regionactive(C)) {
1517                 /* check if current context can support GPencil data */
1518                 if (ED_gpencil_data_get_pointers(C, NULL) != NULL) {
1519                         return 1;
1520                 }
1521                 else {
1522                         CTX_wm_operator_poll_msg_set(C, "Failed to find Grease Pencil data to draw into");
1523                 }
1524         }
1525         else {
1526                 CTX_wm_operator_poll_msg_set(C, "Active region not set");
1527         }
1528
1529         return 0;
1530 }
1531
1532 static int gp_blank_frame_add_exec(bContext *C, wmOperator *op)
1533 {
1534         bGPdata *gpd = ED_gpencil_data_get_active(C);
1535         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1536         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1537
1538         bGPDlayer *active_gpl = BKE_gpencil_layer_getactive(gpd);
1539
1540         const bool all_layers = RNA_boolean_get(op->ptr, "all_layers");
1541
1542         /* Initialise datablock and an active layer if nothing exists yet */
1543         if (ELEM(NULL, gpd, active_gpl)) {
1544                 /* let's just be lazy, and call the "Add New Layer" operator, which sets everything up as required */
1545                 WM_operator_name_call(C, "GPENCIL_OT_layer_add", WM_OP_EXEC_DEFAULT, NULL);
1546         }
1547
1548         /* Go through each layer, adding a frame after the active one
1549          * and/or shunting all the others out of the way
1550          */
1551         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1552         {
1553                 if ((all_layers == false) && (gpl != active_gpl)) {
1554                         continue;
1555                 }
1556
1557                 /* 1) Check for an existing frame on the current frame */
1558                 bGPDframe *gpf = BKE_gpencil_layer_find_frame(gpl, cfra_eval);
1559                 if (gpf) {
1560                         /* Shunt all frames after (and including) the existing one later by 1-frame */
1561                         for (; gpf; gpf = gpf->next) {
1562                                 gpf->framenum += 1;
1563                         }
1564                 }
1565
1566                 /* 2) Now add a new frame, with nothing in it */
1567                 gpl->actframe = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_ADD_NEW);
1568         }
1569         CTX_DATA_END;
1570
1571         /* notifiers */
1572         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1573         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1574
1575         return OPERATOR_FINISHED;
1576 }
1577
1578 void GPENCIL_OT_blank_frame_add(wmOperatorType *ot)
1579 {
1580         /* identifiers */
1581         ot->name = "Insert Blank Frame";
1582         ot->idname = "GPENCIL_OT_blank_frame_add";
1583         ot->description = "Insert a blank frame on the current frame "
1584                 "(all subsequently existing frames, if any, are shifted right by one frame)";
1585
1586         /* callbacks */
1587         ot->exec = gp_blank_frame_add_exec;
1588         ot->poll = gp_add_poll;
1589
1590         /* properties */
1591         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1592         RNA_def_boolean(ot->srna, "all_layers", false, "All Layers", "Create blank frame in all layers, not only active");
1593 }
1594
1595 /* ******************* Delete Active Frame ************************ */
1596
1597 static bool gp_actframe_delete_poll(bContext *C)
1598 {
1599         bGPdata *gpd = ED_gpencil_data_get_active(C);
1600         bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd);
1601
1602         /* only if there's an active layer with an active frame */
1603         return (gpl && gpl->actframe);
1604 }
1605
1606 /* delete active frame - wrapper around API calls */
1607 static int gp_actframe_delete_exec(bContext *C, wmOperator *op)
1608 {
1609         bGPdata *gpd = ED_gpencil_data_get_active(C);
1610         bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd);
1611
1612         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1613         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1614
1615         bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_USE_PREV);
1616
1617         /* if there's no existing Grease-Pencil data there, add some */
1618         if (gpd == NULL) {
1619                 BKE_report(op->reports, RPT_ERROR, "No grease pencil data");
1620                 return OPERATOR_CANCELLED;
1621         }
1622         if (ELEM(NULL, gpl, gpf)) {
1623                 BKE_report(op->reports, RPT_ERROR, "No active frame to delete");
1624                 return OPERATOR_CANCELLED;
1625         }
1626
1627         /* delete it... */
1628         BKE_gpencil_layer_delframe(gpl, gpf);
1629
1630         /* notifiers */
1631         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1632         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1633
1634         return OPERATOR_FINISHED;
1635 }
1636
1637 void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
1638 {
1639         /* identifiers */
1640         ot->name = "Delete Active Frame";
1641         ot->idname = "GPENCIL_OT_active_frame_delete";
1642         ot->description = "Delete the active frame for the active Grease Pencil Layer";
1643
1644         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1645
1646         /* callbacks */
1647         ot->exec = gp_actframe_delete_exec;
1648         ot->poll = gp_actframe_delete_poll;
1649 }
1650
1651 /* **************** Delete All Active Frames ****************** */
1652
1653 static bool gp_actframe_delete_all_poll(bContext *C)
1654 {
1655         bGPdata *gpd = ED_gpencil_data_get_active(C);
1656
1657         /* 1) There must be grease pencil data
1658          * 2) Hopefully some of the layers have stuff we can use
1659          */
1660         return (gpd && gpd->layers.first);
1661 }
1662
1663 static int gp_actframe_delete_all_exec(bContext *C, wmOperator *op)
1664 {
1665         bGPdata *gpd = ED_gpencil_data_get_active(C);
1666         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1667         int cfra_eval = (int)DEG_get_ctime(depsgraph);
1668
1669         bool success = false;
1670
1671         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1672         {
1673                 /* try to get the "active" frame - but only if it actually occurs on this frame */
1674                 bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_USE_PREV);
1675
1676                 if (gpf == NULL)
1677                         continue;
1678
1679                 /* delete it... */
1680                 BKE_gpencil_layer_delframe(gpl, gpf);
1681
1682                 /* we successfully modified something */
1683                 success = true;
1684         }
1685         CTX_DATA_END;
1686
1687         /* updates */
1688         if (success) {
1689                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1690                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1691                 return OPERATOR_FINISHED;
1692         }
1693         else {
1694                 BKE_report(op->reports, RPT_ERROR, "No active frame(s) to delete");
1695                 return OPERATOR_CANCELLED;
1696         }
1697 }
1698
1699 void GPENCIL_OT_active_frames_delete_all(wmOperatorType *ot)
1700 {
1701         /* identifiers */
1702         ot->name = "Delete All Active Frames";
1703         ot->idname = "GPENCIL_OT_active_frames_delete_all";
1704         ot->description = "Delete the active frame(s) of all editable Grease Pencil layers";
1705
1706         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1707
1708         /* callbacks */
1709         ot->exec = gp_actframe_delete_all_exec;
1710         ot->poll = gp_actframe_delete_all_poll;
1711 }
1712
1713 /* ******************* Delete Operator ************************ */
1714
1715 typedef enum eGP_DeleteMode {
1716         /* delete selected stroke points */
1717         GP_DELETEOP_POINTS          = 0,
1718         /* delete selected strokes */
1719         GP_DELETEOP_STROKES         = 1,
1720         /* delete active frame */
1721         GP_DELETEOP_FRAME           = 2,
1722 } eGP_DeleteMode;
1723
1724 typedef enum eGP_DissolveMode {
1725         /* dissolve all selected points */
1726         GP_DISSOLVE_POINTS = 0,
1727         /* dissolve between selected points */
1728         GP_DISSOLVE_BETWEEN = 1,
1729         /* dissolve unselected points */
1730         GP_DISSOLVE_UNSELECT = 2,
1731 } eGP_DissolveMode;
1732
1733 /* ----------------------------------- */
1734
1735 /* Delete selected strokes */
1736 static int gp_delete_selected_strokes(bContext *C)
1737 {
1738         bool changed = false;
1739         bGPdata *gpd = ED_gpencil_data_get_active(C);
1740         const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
1741
1742         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1743         {
1744                 bGPDframe *init_gpf = gpl->actframe;
1745                 if (is_multiedit) {
1746                         init_gpf = gpl->frames.first;
1747                 }
1748
1749                 for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
1750                         if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
1751                                 bGPDstroke *gps, *gpsn;
1752
1753                                 if (gpf == NULL)
1754                                         continue;
1755
1756                                 /* simply delete strokes which are selected */
1757                                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
1758                                         gpsn = gps->next;
1759
1760                                         /* skip strokes that are invalid for current view */
1761                                         if (ED_gpencil_stroke_can_use(C, gps) == false)
1762                                                 continue;
1763
1764                                         /* free stroke if selected */
1765                                         if (gps->flag & GP_STROKE_SELECT) {
1766                                                 /* free stroke memory arrays, then stroke itself */
1767                                                 if (gps->points) {
1768                                                         MEM_freeN(gps->points);
1769                                                 }
1770                                                 if (gps->dvert) {
1771                                                         BKE_gpencil_free_stroke_weights(gps);
1772                                                         MEM_freeN(gps->dvert);
1773                                                 }
1774                                                 MEM_SAFE_FREE(gps->triangles);
1775                                                 BLI_freelinkN(&gpf->strokes, gps);
1776
1777                                                 changed = true;
1778                                         }
1779                                 }
1780                         }
1781                 }
1782         }
1783         CTX_DATA_END;
1784
1785         if (changed) {
1786                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1787                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
1788                 return OPERATOR_FINISHED;
1789         }
1790         else {
1791                 return OPERATOR_CANCELLED;
1792         }
1793 }
1794
1795 /* ----------------------------------- */
1796
1797 /* Delete selected points but keep the stroke */
1798 static int gp_dissolve_selected_points(bContext *C, eGP_DissolveMode mode)
1799 {
1800         Object *ob = CTX_data_active_object(C);
1801         bGPdata *gpd = ED_gpencil_data_get_active(C);
1802         const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
1803         bool changed = false;
1804         int first = 0;
1805         int last = 0;
1806
1807         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
1808         {
1809                 bGPDframe *init_gpf = gpl->actframe;
1810                 if (is_multiedit) {
1811                         init_gpf = gpl->frames.first;
1812                 }
1813
1814                 for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
1815                         if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
1816
1817                                 bGPDstroke *gps, *gpsn;
1818
1819                                 if (gpf == NULL)
1820                                         continue;
1821
1822                                 /* simply delete points from selected strokes
1823                                  * NOTE: we may still have to remove the stroke if it ends up having no points!
1824                                  */
1825                                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
1826                                         gpsn = gps->next;
1827
1828                                         /* skip strokes that are invalid for current view */
1829                                         if (ED_gpencil_stroke_can_use(C, gps) == false)
1830                                                 continue;
1831                                         /* check if the color is editable */
1832                                         if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false)
1833                                                 continue;
1834
1835                                         /* the stroke must have at least one point selected for any operator */
1836                                         if (gps->flag & GP_STROKE_SELECT) {
1837                                                 bGPDspoint *pt;
1838                                                 MDeformVert *dvert = NULL;
1839                                                 int i;
1840
1841                                                 int tot = gps->totpoints; /* number of points in new buffer */
1842
1843                                                 /* first pass: count points to remove */
1844                                                 switch (mode) {
1845                                                         case GP_DISSOLVE_POINTS:
1846                                                                 /* Count how many points are selected (i.e. how many to remove) */
1847                                                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1848                                                                         if (pt->flag & GP_SPOINT_SELECT) {
1849                                                                                 /* selected point - one of the points to remove */
1850                                                                                 tot--;
1851                                                                         }
1852                                                                 }
1853                                                                 break;
1854                                                         case GP_DISSOLVE_BETWEEN:
1855                                                                 /* need to find first and last point selected */
1856                                                                 first = -1;
1857                                                                 last = 0;
1858                                                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1859                                                                         if (pt->flag & GP_SPOINT_SELECT) {
1860                                                                                 if (first < 0) {
1861                                                                                         first = i;
1862                                                                                 }
1863                                                                                 last = i;
1864                                                                         }
1865                                                                 }
1866                                                                 /* count unselected points in the range */
1867                                                                 for (i = first, pt = gps->points + first; i < last; i++, pt++) {
1868                                                                         if ((pt->flag & GP_SPOINT_SELECT) == 0) {
1869                                                                                 tot--;
1870                                                                         }
1871                                                                 }
1872                                                                 break;
1873                                                         case GP_DISSOLVE_UNSELECT:
1874                                                                 /* count number of unselected points */
1875                                                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1876                                                                         if ((pt->flag & GP_SPOINT_SELECT) == 0) {
1877                                                                                 tot--;
1878                                                                         }
1879                                                                 }
1880                                                                 break;
1881                                                         default:
1882                                                                 return false;
1883                                                                 break;
1884                                                 }
1885
1886                                                 /* if no points are left, we simply delete the entire stroke */
1887                                                 if (tot <= 0) {
1888                                                         /* remove the entire stroke */
1889                                                         if (gps->points) {
1890                                                                 MEM_freeN(gps->points);
1891                                                         }
1892                                                         if (gps->dvert) {
1893                                                                 BKE_gpencil_free_stroke_weights(gps);
1894                                                                 MEM_freeN(gps->dvert);
1895                                                         }
1896                                                         if (gps->triangles) {
1897                                                                 MEM_freeN(gps->triangles);
1898                                                         }
1899                                                         BLI_freelinkN(&gpf->strokes, gps);
1900                                                         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
1901                                                 }
1902                                                 else {
1903                                                         /* just copy all points to keep into a smaller buffer */
1904                                                         bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy");
1905                                                         bGPDspoint *npt = new_points;
1906
1907                                                         MDeformVert *new_dvert = NULL;
1908                                                         MDeformVert *ndvert = NULL;
1909
1910                                                         if (gps->dvert != NULL) {
1911                                                                 new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy");
1912                                                                 ndvert = new_dvert;
1913                                                         }
1914
1915                                                         switch (mode) {
1916                                                                 case GP_DISSOLVE_POINTS:
1917                                                                         (gps->dvert != NULL) ? dvert = gps->dvert : NULL;
1918                                                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1919                                                                                 if ((pt->flag & GP_SPOINT_SELECT) == 0) {
1920                                                                                         *npt = *pt;
1921                                                                                         npt++;
1922
1923                                                                                         if (gps->dvert != NULL) {
1924                                                                                                 *ndvert = *dvert;
1925                                                                                                 ndvert->dw = MEM_dupallocN(dvert->dw);
1926                                                                                                 ndvert++;
1927                                                                                                 dvert++;
1928                                                                                         }
1929                                                                                 }
1930                                                                         }
1931                                                                         break;
1932                                                                 case GP_DISSOLVE_BETWEEN:
1933                                                                         /* copy first segment */
1934                                                                         (gps->dvert != NULL) ? dvert = gps->dvert : NULL;
1935                                                                         for (i = 0, pt = gps->points; i < first; i++, pt++) {
1936                                                                                 *npt = *pt;
1937                                                                                 npt++;
1938
1939                                                                                 if (gps->dvert != NULL) {
1940                                                                                         *ndvert = *dvert;
1941                                                                                         ndvert->dw = MEM_dupallocN(dvert->dw);
1942                                                                                         ndvert++;
1943                                                                                         dvert++;
1944                                                                                 }
1945                                                                         }
1946                                                                         /* copy segment (selected points) */
1947                                                                         (gps->dvert != NULL) ? dvert = gps->dvert + first : NULL;
1948                                                                         for (i = first, pt = gps->points + first; i < last; i++, pt++) {
1949                                                                                 if (pt->flag & GP_SPOINT_SELECT) {
1950                                                                                         *npt = *pt;
1951                                                                                         npt++;
1952
1953                                                                                         if (gps->dvert != NULL) {
1954                                                                                                 *ndvert = *dvert;
1955                                                                                                 ndvert->dw = MEM_dupallocN(dvert->dw);
1956                                                                                                 ndvert++;
1957                                                                                                 dvert++;
1958                                                                                         }
1959                                                                                 }
1960                                                                         }
1961                                                                         /* copy last segment */
1962                                                                         (gps->dvert != NULL) ? dvert = gps->dvert + last : NULL;
1963                                                                         for (i = last, pt = gps->points + last; i < gps->totpoints; i++, pt++) {
1964                                                                                 *npt = *pt;
1965                                                                                 npt++;
1966
1967                                                                                 if (gps->dvert != NULL) {
1968                                                                                         *ndvert = *dvert;
1969                                                                                         ndvert->dw = MEM_dupallocN(dvert->dw);
1970                                                                                         ndvert++;
1971                                                                                         dvert++;
1972                                                                                 }
1973                                                                         }
1974
1975                                                                         break;
1976                                                                 case GP_DISSOLVE_UNSELECT:
1977                                                                         /* copy any selected point */
1978                                                                         (gps->dvert != NULL) ? dvert = gps->dvert : NULL;
1979                                                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1980                                                                                 if (pt->flag & GP_SPOINT_SELECT) {
1981                                                                                         *npt = *pt;
1982                                                                                         npt++;
1983
1984                                                                                         if (gps->dvert != NULL) {
1985                                                                                                 *ndvert = *dvert;
1986                                                                                                 ndvert->dw = MEM_dupallocN(dvert->dw);
1987                                                                                                 ndvert++;
1988                                                                                                 dvert++;
1989                                                                                         }
1990                                                                                 }
1991                                                                         }
1992                                                                         break;
1993                                                         }
1994
1995                                                         /* free the old buffer */
1996                                                         if (gps->points) {
1997                                                                 MEM_freeN(gps->points);
1998                                                         }
1999                                                         if (gps->dvert) {
2000                                                                 BKE_gpencil_free_stroke_weights(gps);
2001                                                                 MEM_freeN(gps->dvert);
2002                                                         }
2003
2004                                                         /* save the new buffer */
2005                                                         gps->points = new_points;
2006                                                         gps->dvert = new_dvert;
2007                                                         gps->totpoints = tot;
2008
2009                                                         /* triangles cache needs to be recalculated */
2010                                                         gps->flag |= GP_STROKE_RECALC_GEOMETRY;
2011                                                         gps->tot_triangles = 0;
2012
2013                                                         /* deselect the stroke, since none of its selected points will still be selected */
2014                                                         gps->flag &= ~GP_STROKE_SELECT;
2015                                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2016                                                                 pt->flag &= ~GP_SPOINT_SELECT;
2017                                                         }
2018                                                 }
2019
2020                                                 changed = true;
2021                                         }
2022                                 }
2023                         }
2024                 }
2025         }
2026         CTX_DATA_END;
2027
2028         if (changed) {
2029                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2030                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2031                 return OPERATOR_FINISHED;
2032         }
2033         else {
2034                 return OPERATOR_CANCELLED;
2035         }
2036 }
2037
2038 /* ----------------------------------- */
2039
2040 /* Temp data for storing information about an "island" of points
2041  * that should be kept when splitting up a stroke. Used in:
2042  * gp_stroke_delete_tagged_points()
2043  */
2044 typedef struct tGPDeleteIsland {
2045         int start_idx;
2046         int end_idx;
2047 } tGPDeleteIsland;
2048
2049 static void gp_stroke_join_islands(bGPDframe *gpf, bGPDstroke *gps_first, bGPDstroke *gps_last)
2050 {
2051         bGPDspoint *pt = NULL;
2052         bGPDspoint *pt_final = NULL;
2053         const int totpoints = gps_first->totpoints + gps_last->totpoints;
2054
2055         /* create new stroke */
2056         bGPDstroke *join_stroke = MEM_dupallocN(gps_first);
2057
2058         join_stroke->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__);
2059         join_stroke->totpoints = totpoints;
2060         join_stroke->flag &= ~GP_STROKE_CYCLIC;
2061
2062         /* copy points (last before) */
2063         int e1 = 0;
2064         int e2 = 0;
2065         float delta = 0.0f;
2066
2067         for (int i = 0; i < totpoints; i++) {
2068                 pt_final = &join_stroke->points[i];
2069                 if (i < gps_last->totpoints) {
2070                         pt = &gps_last->points[e1];
2071                         e1++;
2072                 }
2073                 else {
2074                         pt = &gps_first->points[e2];
2075                         e2++;
2076                 }
2077
2078                 /* copy current point */
2079                 copy_v3_v3(&pt_final->x, &pt->x);
2080                 pt_final->pressure = pt->pressure;
2081                 pt_final->strength = pt->strength;
2082                 pt_final->time = delta;
2083                 pt_final->flag = pt->flag;
2084
2085                 /* retiming with fixed time interval (we cannot determine real time) */
2086                 delta += 0.01f;
2087         }
2088
2089         /* Copy over vertex weight data (if available) */
2090         if ((gps_first->dvert != NULL) || (gps_last->dvert != NULL)) {
2091                 join_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * totpoints, __func__);
2092                 MDeformVert *dvert_src = NULL;
2093                 MDeformVert *dvert_dst = NULL;
2094
2095                 /* Copy weights (last before)*/
2096                 e1 = 0;
2097                 e2 = 0;
2098                 for (int i = 0; i < totpoints; i++) {
2099                         dvert_dst = &join_stroke->dvert[i];
2100                         dvert_src = NULL;
2101                         if (i < gps_last->totpoints) {
2102                                 if (gps_last->dvert) {
2103                                         dvert_src = &gps_last->dvert[e1];
2104                                         e1++;
2105                                 }
2106                         }
2107                         else {
2108                                 if (gps_first->dvert) {
2109                                         dvert_src = &gps_first->dvert[e2];
2110                                         e2++;
2111                                 }
2112                         }
2113
2114                         if ((dvert_src) && (dvert_src->dw)) {
2115                                 dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
2116                         }
2117                 }
2118         }
2119
2120         /* add new stroke at head */
2121         BLI_addhead(&gpf->strokes, join_stroke);
2122
2123         /* remove first stroke */
2124         BLI_remlink(&gpf->strokes, gps_first);
2125         BKE_gpencil_free_stroke(gps_first);
2126
2127         /* remove last stroke */
2128         BLI_remlink(&gpf->strokes, gps_last);
2129         BKE_gpencil_free_stroke(gps_last);
2130 }
2131
2132
2133 /* Split the given stroke into several new strokes, partitioning
2134  * it based on whether the stroke points have a particular flag
2135  * is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always)
2136  *
2137  * The algorithm used here is as follows:
2138  * 1) We firstly identify the number of "islands" of non-tagged points
2139  *    which will all end up being in new strokes.
2140  *    - In the most extreme case (i.e. every other vert is a 1-vert island),
2141  *      we have at most n / 2 islands
2142  *    - Once we start having larger islands than that, the number required
2143  *      becomes much less
2144  * 2) Each island gets converted to a new stroke
2145  * If the number of points is <= limit, the stroke is deleted
2146  */
2147 void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke,
2148         int tag_flags, bool select, int limit)
2149 {
2150         tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
2151         bool in_island  = false;
2152         int num_islands = 0;
2153
2154         bGPDstroke *gps_first = NULL;
2155         const bool is_cyclic = (bool)(gps->flag & GP_STROKE_CYCLIC);
2156
2157         /* First Pass: Identify start/end of islands */
2158         bGPDspoint *pt = gps->points;
2159         for (int i = 0; i < gps->totpoints; i++, pt++) {
2160                 if (pt->flag & tag_flags) {
2161                         /* selected - stop accumulating to island */
2162                         in_island = false;
2163                 }
2164                 else {
2165                         /* unselected - start of a new island? */
2166                         int idx;
2167
2168                         if (in_island) {
2169                                 /* extend existing island */
2170                                 idx = num_islands - 1;
2171                                 islands[idx].end_idx = i;
2172                         }
2173                         else {
2174                                 /* start of new island */
2175                                 in_island = true;
2176                                 num_islands++;
2177
2178                                 idx = num_islands - 1;
2179                                 islands[idx].start_idx = islands[idx].end_idx = i;
2180                         }
2181                 }
2182         }
2183
2184         /* Watch out for special case where No islands = All points selected = Delete Stroke only */
2185         if (num_islands) {
2186                 /* there are islands, so create a series of new strokes, adding them before the "next" stroke */
2187                 int idx;
2188                 bGPDstroke *new_stroke = NULL;
2189
2190                 /* Create each new stroke... */
2191                 for (idx = 0; idx < num_islands; idx++) {
2192                         tGPDeleteIsland *island = &islands[idx];
2193                         new_stroke = MEM_dupallocN(gps);
2194
2195                         /* if cyclic and first stroke, save to join later */
2196                         if ((is_cyclic) && (gps_first == NULL)) {
2197                                 gps_first = new_stroke;
2198                         }
2199
2200                         /* initialize triangle memory  - to be calculated on next redraw */
2201                         new_stroke->triangles = NULL;
2202                         new_stroke->flag |= GP_STROKE_RECALC_GEOMETRY;
2203                         new_stroke->flag &= ~GP_STROKE_CYCLIC;
2204                         new_stroke->tot_triangles = 0;
2205
2206                         /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
2207                         new_stroke->totpoints = island->end_idx - island->start_idx + 1;
2208
2209                         /* Copy over the relevant point data */
2210                         new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
2211                         memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
2212
2213                         /* Copy over vertex weight data (if available) */
2214                         if (gps->dvert != NULL) {
2215                                 /* Copy over the relevant vertex-weight points */
2216                                 new_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints, "gp delete stroke fragment weight");
2217                                 memcpy(new_stroke->dvert, gps->dvert + island->start_idx, sizeof(MDeformVert) * new_stroke->totpoints);
2218
2219                                 /* Copy weights */
2220                                 int e = island->start_idx;
2221                                 for (int i = 0; i < new_stroke->totpoints; i++) {
2222                                         MDeformVert *dvert_src = &gps->dvert[e];
2223                                         MDeformVert *dvert_dst = &new_stroke->dvert[i];
2224                                         if (dvert_src->dw) {
2225                                                 dvert_dst->dw = MEM_dupallocN(dvert_src->dw);
2226                                         }
2227                                         e++;
2228                                 }
2229                         }
2230                         /* Each island corresponds to a new stroke. We must adjust the
2231                          * timings of these new strokes:
2232                          *
2233                          * Each point's timing data is a delta from stroke's inittime, so as we erase some points from
2234                          * the start of the stroke, we have to offset this inittime and all remaining points' delta values.
2235                          * This way we get a new stroke with exactly the same timing as if user had started drawing from
2236                          * the first non-removed point...
2237                          */
2238                         {
2239                                 bGPDspoint *pts;
2240                                 float delta = gps->points[island->start_idx].time;
2241                                 int j;
2242
2243                                 new_stroke->inittime += (double)delta;
2244
2245                                 pts = new_stroke->points;
2246                                 for (j = 0; j < new_stroke->totpoints; j++, pts++) {
2247                                         pts->time -= delta;
2248                                         /* set flag for select again later */
2249                                         if (select == true) {
2250                                                 pts->flag &= ~GP_SPOINT_SELECT;
2251                                                 pts->flag |= GP_SPOINT_TAG;
2252                                         }
2253                                 }
2254                         }
2255
2256                         /* Add new stroke to the frame or delete if below limit */
2257                         if ((limit > 0) && (new_stroke->totpoints <= limit)) {
2258                                 BKE_gpencil_free_stroke(new_stroke);
2259                         }
2260                         else {
2261                                 if (next_stroke) {
2262                                         BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
2263                                 }
2264                                 else {
2265                                         BLI_addtail(&gpf->strokes, new_stroke);
2266                                 }
2267                         }
2268                 }
2269                 /* if cyclic, need to join last stroke with first stroke */
2270                 if ((is_cyclic) && (gps_first != NULL) && (gps_first != new_stroke)) {
2271                         gp_stroke_join_islands(gpf, gps_first, new_stroke);
2272                 }
2273
2274         }
2275
2276         /* free islands */
2277         MEM_freeN(islands);
2278
2279         /* Delete the old stroke */
2280         BLI_remlink(&gpf->strokes, gps);
2281         BKE_gpencil_free_stroke(gps);
2282 }
2283
2284 /* Split selected strokes into segments, splitting on selected points */
2285 static int gp_delete_selected_points(bContext *C)
2286 {
2287         Object *ob = CTX_data_active_object(C);
2288         bGPdata *gpd = ED_gpencil_data_get_active(C);
2289         const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
2290         bool changed = false;
2291
2292         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
2293         {
2294                 bGPDframe *init_gpf = gpl->actframe;
2295                 if (is_multiedit) {
2296                         init_gpf = gpl->frames.first;
2297                 }
2298
2299                 for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
2300                         if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
2301                                 bGPDstroke *gps, *gpsn;
2302
2303                                 if (gpf == NULL)
2304                                         continue;
2305
2306                                 /* simply delete strokes which are selected */
2307                                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
2308                                         gpsn = gps->next;
2309
2310                                         /* skip strokes that are invalid for current view */
2311                                         if (ED_gpencil_stroke_can_use(C, gps) == false)
2312                                                 continue;
2313                                         /* check if the color is editable */
2314                                         if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false)
2315                                                 continue;
2316
2317
2318                                         if (gps->flag & GP_STROKE_SELECT) {
2319                                                 /* deselect old stroke, since it will be used as template for the new strokes */
2320                                                 gps->flag &= ~GP_STROKE_SELECT;
2321
2322                                                 /* delete unwanted points by splitting stroke into several smaller ones */
2323                                                 gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0);
2324
2325                                                 changed = true;
2326                                         }
2327                                 }
2328                         }
2329                 }
2330         }
2331         CTX_DATA_END;
2332
2333         if (changed) {
2334                 DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2335                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2336                 return OPERATOR_FINISHED;
2337         }
2338         else {
2339                 return OPERATOR_CANCELLED;
2340         }
2341 }
2342
2343 /* simple wrapper to external call */
2344 int gp_delete_selected_point_wrap(bContext *C)
2345 {
2346         return gp_delete_selected_points(C);
2347 }
2348
2349 /* ----------------------------------- */
2350
2351 static int gp_delete_exec(bContext *C, wmOperator *op)
2352 {
2353         eGP_DeleteMode mode = RNA_enum_get(op->ptr, "type");
2354         int result = OPERATOR_CANCELLED;
2355
2356         switch (mode) {
2357                 case GP_DELETEOP_STROKES:       /* selected strokes */
2358                         result = gp_delete_selected_strokes(C);
2359                         break;
2360
2361                 case GP_DELETEOP_POINTS:        /* selected points (breaks the stroke into segments) */
2362                         result = gp_delete_selected_points(C);
2363                         break;
2364
2365                 case GP_DELETEOP_FRAME:         /* active frame */
2366                         result = gp_actframe_delete_exec(C, op);
2367                         break;
2368         }
2369
2370         return result;
2371 }
2372
2373 void GPENCIL_OT_delete(wmOperatorType *ot)
2374 {
2375         static const EnumPropertyItem prop_gpencil_delete_types[] = {
2376                 {GP_DELETEOP_POINTS, "POINTS", 0, "Points", "Delete selected points and split strokes into segments"},
2377                 {GP_DELETEOP_STROKES, "STROKES", 0, "Strokes", "Delete selected strokes"},
2378                 {GP_DELETEOP_FRAME, "FRAME", 0, "Frame", "Delete active frame"},
2379                 {0, NULL, 0, NULL, NULL},
2380         };
2381
2382         /* identifiers */
2383         ot->name = "Delete";
2384         ot->idname = "GPENCIL_OT_delete";
2385         ot->description = "Delete selected Grease Pencil strokes, vertices, or frames";
2386
2387         /* callbacks */
2388         ot->invoke = WM_menu_invoke;
2389         ot->exec = gp_delete_exec;
2390         ot->poll = gp_stroke_edit_poll;
2391
2392         /* flags */
2393         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
2394
2395         /* props */
2396         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_delete_types, 0, "Type", "Method used for deleting Grease Pencil data");
2397 }
2398
2399 static int gp_dissolve_exec(bContext *C, wmOperator *op)
2400 {
2401         eGP_DissolveMode mode = RNA_enum_get(op->ptr, "type");
2402
2403         return gp_dissolve_selected_points(C, mode);
2404 }
2405
2406 void GPENCIL_OT_dissolve(wmOperatorType *ot)
2407 {
2408         static EnumPropertyItem prop_gpencil_dissolve_types[] = {
2409                 {GP_DISSOLVE_POINTS, "POINTS", 0, "Dissolve", "Dissolve selected points"},
2410                 {GP_DISSOLVE_BETWEEN, "BETWEEN", 0, "Dissolve Between", "Dissolve points between selected points"},
2411                 {GP_DISSOLVE_UNSELECT, "UNSELECT", 0, "Dissolve Unselect", "Dissolve all unselected points"},
2412                 {0, NULL, 0, NULL, NULL},
2413         };
2414
2415         /* identifiers */
2416         ot->name = "Dissolve";
2417         ot->idname = "GPENCIL_OT_dissolve";
2418         ot->description = "Delete selected points without splitting strokes";
2419
2420         /* callbacks */
2421         ot->invoke = WM_menu_invoke;
2422         ot->exec = gp_dissolve_exec;
2423         ot->poll = gp_stroke_edit_poll;
2424
2425         /* flags */
2426         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
2427
2428         /* props */
2429         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_dissolve_types, 0,
2430                                 "Type", "Method used for dissolving Stroke points");
2431 }
2432
2433 /* ****************** Snapping - Strokes <-> Cursor ************************ */
2434
2435 /* Poll callback for snap operators */
2436 /* NOTE: For now, we only allow these in the 3D view, as other editors do not
2437  *       define a cursor or gridstep which can be used
2438  */
2439 static bool gp_snap_poll(bContext *C)
2440 {
2441         bGPdata *gpd = CTX_data_gpencil_data(C);
2442         ScrArea *sa = CTX_wm_area(C);
2443
2444         return (gpd != NULL) && ((sa != NULL) && (sa->spacetype == SPACE_VIEW3D));
2445 }
2446
2447 /* --------------------------------- */
2448
2449 static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op))
2450 {
2451         bGPdata *gpd = ED_gpencil_data_get_active(C);
2452         RegionView3D *rv3d = CTX_wm_region_data(C);
2453         View3D *v3d = CTX_wm_view3d(C);
2454         Scene *scene = CTX_data_scene(C);
2455         Depsgraph *depsgraph = CTX_data_depsgraph(C);
2456         Object *obact = CTX_data_active_object(C);
2457         const float gridf = ED_view3d_grid_view_scale(scene, v3d, rv3d, NULL);
2458
2459         for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
2460                 /* only editable and visible layers are considered */
2461                 if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) {
2462                         bGPDframe *gpf = gpl->actframe;
2463                         float diff_mat[4][4];
2464
2465                         /* calculate difference matrix object */
2466                         ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat);
2467
2468                         for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
2469                                 bGPDspoint *pt;
2470                                 int i;
2471
2472                                 /* skip strokes that are invalid for current view */
2473                                 if (ED_gpencil_stroke_can_use(C, gps) == false)
2474                                         continue;
2475                                 /* check if the color is editable */
2476                                 if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false)
2477                                         continue;
2478
2479                                 // TODO: if entire stroke is selected, offset entire stroke by same amount?
2480                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2481                                         /* only if point is selected */
2482                                         if (pt->flag & GP_SPOINT_SELECT) {
2483                                                 /* apply parent transformations */
2484                                                 float fpt[3];
2485                                                 mul_v3_m4v3(fpt, diff_mat, &pt->x);
2486
2487                                                 fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf);
2488                                                 fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf);
2489                                                 fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf);
2490
2491                                                 /* return data */
2492                                                 copy_v3_v3(&pt->x, fpt);
2493                                                 gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt);
2494                                         }
2495                                 }
2496                         }
2497                 }
2498         }
2499
2500         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2501         DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE);
2502         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2503         return OPERATOR_FINISHED;
2504 }
2505
2506 void GPENCIL_OT_snap_to_grid(wmOperatorType *ot)
2507 {
2508         /* identifiers */
2509         ot->name = "Snap Selection to Grid";
2510         ot->idname = "GPENCIL_OT_snap_to_grid";
2511         ot->description = "Snap selected points to the nearest grid points";
2512
2513         /* callbacks */
2514         ot->exec = gp_snap_to_grid;
2515         ot->poll = gp_snap_poll;
2516
2517         /* flags */
2518         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2519 }
2520
2521 /* ------------------------------- */
2522
2523 static int gp_snap_to_cursor(bContext *C, wmOperator *op)
2524 {
2525         bGPdata *gpd = ED_gpencil_data_get_active(C);
2526
2527         Scene *scene = CTX_data_scene(C);
2528         Depsgraph *depsgraph = CTX_data_depsgraph(C);
2529         Object *obact = CTX_data_active_object(C);
2530
2531         const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
2532         const float *cursor_global = scene->cursor.location;
2533
2534         for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
2535                 /* only editable and visible layers are considered */
2536                 if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) {
2537                         bGPDframe *gpf = gpl->actframe;
2538                         float diff_mat[4][4];
2539
2540                         /* calculate difference matrix */
2541                         ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat);
2542
2543                         for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
2544                                 bGPDspoint *pt;
2545                                 int i;
2546
2547                                 /* skip strokes that are invalid for current view */
2548                                 if (ED_gpencil_stroke_can_use(C, gps) == false)
2549                                         continue;
2550                                 /* check if the color is editable */
2551                                 if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false)
2552                                         continue;
2553                                 /* only continue if this stroke is selected (editable doesn't guarantee this)... */
2554                                 if ((gps->flag & GP_STROKE_SELECT) == 0)
2555                                         continue;
2556
2557                                 if (use_offset) {
2558                                         float offset[3];
2559
2560                                         /* compute offset from first point of stroke to cursor */
2561                                         /* TODO: Allow using midpoint instead? */
2562                                         sub_v3_v3v3(offset, cursor_global, &gps->points->x);
2563
2564                                         /* apply offset to all points in the stroke */
2565                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2566                                                 add_v3_v3(&pt->x, offset);
2567                                         }
2568                                 }
2569                                 else {
2570                                         /* affect each selected point */
2571                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2572                                                 if (pt->flag & GP_SPOINT_SELECT) {
2573                                                         copy_v3_v3(&pt->x, cursor_global);
2574                                                         gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt);
2575                                                 }
2576                                         }
2577                                 }
2578                         }
2579
2580                 }
2581         }
2582
2583         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2584         DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE);
2585         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2586         return OPERATOR_FINISHED;
2587 }
2588
2589 void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot)
2590 {
2591         /* identifiers */
2592         ot->name = "Snap Selection to Cursor";
2593         ot->idname = "GPENCIL_OT_snap_to_cursor";
2594         ot->description = "Snap selected points/strokes to the cursor";
2595
2596         /* callbacks */
2597         ot->exec = gp_snap_to_cursor;
2598         ot->poll = gp_snap_poll;
2599
2600         /* flags */
2601         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2602
2603         /* props */
2604         ot->prop = RNA_def_boolean(
2605                 ot->srna, "use_offset", true, "With Offset",
2606                 "Offset the entire stroke instead of selected points only");
2607 }
2608
2609 /* ------------------------------- */
2610
2611 static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op))
2612 {
2613         bGPdata *gpd = ED_gpencil_data_get_active(C);
2614
2615         Scene *scene = CTX_data_scene(C);
2616         View3D *v3d = CTX_wm_view3d(C);
2617         Depsgraph *depsgraph = CTX_data_depsgraph(C);
2618         Object *obact = CTX_data_active_object(C);
2619
2620         float *cursor = scene->cursor.location;
2621         float centroid[3] = {0.0f};
2622         float min[3], max[3];
2623         size_t count = 0;
2624
2625         INIT_MINMAX(min, max);
2626
2627         /* calculate midpoints from selected points */
2628         for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
2629                 /* only editable and visible layers are considered */
2630                 if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) {
2631                         bGPDframe *gpf = gpl->actframe;
2632                         float diff_mat[4][4];
2633
2634                         /* calculate difference matrix */
2635                         ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat);
2636
2637                         for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
2638                                 bGPDspoint *pt;
2639                                 int i;
2640
2641                                 /* skip strokes that are invalid for current view */
2642                                 if (ED_gpencil_stroke_can_use(C, gps) == false)
2643                                         continue;
2644                                 /* check if the color is editable */
2645                                 if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false)
2646                                         continue;
2647                                 /* only continue if this stroke is selected (editable doesn't guarantee this)... */
2648                                 if ((gps->flag & GP_STROKE_SELECT) == 0)
2649                                         continue;
2650
2651                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2652                                         if (pt->flag & GP_SPOINT_SELECT) {
2653                                                 /* apply parent transformations */
2654                                                 float fpt[3];
2655                                                 mul_v3_m4v3(fpt, diff_mat, &pt->x);
2656
2657                                                 add_v3_v3(centroid, fpt);
2658                                                 minmax_v3v3_v3(min, max, fpt);
2659
2660                                                 count++;
2661                                         }
2662                                 }
2663
2664                         }
2665                 }
2666         }
2667
2668         if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_MEDIAN && count) {
2669                 mul_v3_fl(centroid, 1.0f / (float)count);
2670                 copy_v3_v3(cursor, centroid);
2671         }
2672         else {
2673                 mid_v3_v3v3(cursor, min, max);
2674         }
2675
2676
2677         DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
2678         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
2679
2680         return OPERATOR_FINISHED;
2681 }
2682
2683 void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot)
2684 {
2685         /* identifiers */
2686         ot->name = "Snap Cursor to Selected Points";
2687         ot->idname = "GPENCIL_OT_snap_cursor_to_selected";
2688         ot->description = "Snap cursor to center of selected points";
2689
2690         /* callbacks */
2691         ot->exec = gp_snap_cursor_to_sel;
2692         ot->poll = gp_snap_poll;
2693
2694         /* flags */
2695         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2696 }
2697
2698 /* ******************* Apply layer thickness change to strokes ************************** */
2699
2700 static int gp_stroke_apply_thickness_exec(bContext *C, wmOperator *UNUSED(op))
2701 {
2702         bGPdata *gpd = ED_gpencil_data_get_active(C);
2703         bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd);
2704
2705         /* sanity checks */
2706         if (ELEM(NULL, gpd, gpl, gpl->frames.first))
2707                 return OPERATOR_CANCELLED;
2708
2709         /* loop all strokes */
2710         for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
2711                 for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
2712                         /* Apply thickness */
2713                         if ((gps->thickness == 0) && (gpl->line_change == 0)) {
2714                                 gps->thickness = gpl->thickness;
2715                         }
2716                         else {
2717                                 gps->thickness = gps->thickness + gpl->line_change;
2718                         }
2719                 }
2720         }
2721         /* clear value */
2722         gpl->thickness = 0.0f;
2723         gpl->line_change = 0;
2724
2725         /* notifiers */
2726         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2727         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2728
2729         return OPERATOR_FINISHED;
2730 }
2731
2732 void GPENCIL_OT_stroke_apply_thickness(wmOperatorType *ot)
2733 {
2734         /* identifiers */
2735         ot->name = "Apply Stroke Thickness";
2736         ot->idname = "GPENCIL_OT_stroke_apply_thickness";
2737         ot->description = "Apply the thickness change of the layer to its strokes";
2738
2739         /* api callbacks */
2740         ot->exec = gp_stroke_apply_thickness_exec;
2741         ot->poll = gp_active_layer_poll;
2742 }
2743
2744 /* ******************* Close Strokes ************************** */
2745
2746 enum {
2747         GP_STROKE_CYCLIC_CLOSE = 1,
2748         GP_STROKE_CYCLIC_OPEN = 2,
2749         GP_STROKE_CYCLIC_TOGGLE = 3,
2750 };
2751
2752 static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op)
2753 {
2754         bGPdata *gpd = ED_gpencil_data_get_active(C);
2755         Object *ob = CTX_data_active_object(C);
2756
2757         const int type = RNA_enum_get(op->ptr, "type");
2758
2759         /* sanity checks */
2760         if (ELEM(NULL, gpd))
2761                 return OPERATOR_CANCELLED;
2762
2763         /* loop all selected strokes */
2764         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
2765         {
2766                 if (gpl->actframe == NULL)
2767                         continue;
2768
2769                 for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) {
2770                         MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1);
2771
2772                         /* skip strokes that are not selected or invalid for current view */
2773                         if (((gps->flag & GP_STROKE_SELECT) == 0) || ED_gpencil_stroke_can_use(C, gps) == false)
2774                                 continue;
2775                         /* skip hidden or locked colors */
2776                         if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || (gp_style->flag & GP_STYLE_COLOR_LOCKED))
2777                                 continue;
2778
2779                         switch (type) {
2780                                 case GP_STROKE_CYCLIC_CLOSE:
2781                                         /* Close all (enable) */
2782                                         gps->flag |= GP_STROKE_CYCLIC;
2783                                         break;
2784                                 case GP_STROKE_CYCLIC_OPEN:
2785                                         /* Open all (disable) */
2786                                         gps->flag &= ~GP_STROKE_CYCLIC;
2787                                         break;
2788                                 case GP_STROKE_CYCLIC_TOGGLE:
2789                                         /* Just toggle flag... */
2790                                         gps->flag ^= GP_STROKE_CYCLIC;
2791                                         break;
2792                                 default:
2793                                         BLI_assert(0);
2794                                         break;
2795                         }
2796                 }
2797         }
2798         CTX_DATA_END;
2799
2800         /* notifiers */
2801         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2802         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2803
2804         return OPERATOR_FINISHED;
2805 }
2806
2807 /**
2808  * Similar to #CURVE_OT_cyclic_toggle or #MASK_OT_cyclic_toggle, but with
2809  * option to force opened/closed strokes instead of just toggle behavior.
2810  */
2811 void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot)
2812 {
2813         static const EnumPropertyItem cyclic_type[] = {
2814                 {GP_STROKE_CYCLIC_CLOSE, "CLOSE", 0, "Close all", ""},
2815                 {GP_STROKE_CYCLIC_OPEN, "OPEN", 0, "Open all", ""},
2816                 {GP_STROKE_CYCLIC_TOGGLE, "TOGGLE", 0, "Toggle", ""},
2817                 {0, NULL, 0, NULL, NULL},
2818         };
2819
2820         /* identifiers */
2821         ot->name = "Set Cyclical State";
2822         ot->idname = "GPENCIL_OT_stroke_cyclical_set";
2823         ot->description = "Close or open the selected stroke adding an edge from last to first point";
2824
2825         /* api callbacks */
2826         ot->exec = gp_stroke_cyclical_set_exec;
2827         ot->poll = gp_active_layer_poll;
2828
2829         /* flags */
2830         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2831
2832         /* properties */
2833         ot->prop = RNA_def_enum(ot->srna, "type", cyclic_type, GP_STROKE_CYCLIC_TOGGLE, "Type", "");
2834 }
2835
2836 /* ******************* Flat Stroke Caps ************************** */
2837
2838 enum {
2839         GP_STROKE_CAPS_TOGGLE_BOTH    = 0,
2840         GP_STROKE_CAPS_TOGGLE_START   = 1,
2841         GP_STROKE_CAPS_TOGGLE_END     = 2,
2842         GP_STROKE_CAPS_TOGGLE_DEFAULT = 3,
2843 };
2844
2845 static int gp_stroke_caps_set_exec(bContext *C, wmOperator *op)
2846 {
2847         bGPdata *gpd = ED_gpencil_data_get_active(C);
2848         Object *ob = CTX_data_active_object(C);
2849
2850         const int type = RNA_enum_get(op->ptr, "type");
2851
2852         /* sanity checks */
2853         if (ELEM(NULL, gpd))
2854                 return OPERATOR_CANCELLED;
2855
2856         /* loop all selected strokes */
2857         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
2858         {
2859                 if (gpl->actframe == NULL)
2860                         continue;
2861
2862                 for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) {
2863                         MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1);
2864
2865                         /* skip strokes that are not selected or invalid for current view */
2866                         if (((gps->flag & GP_STROKE_SELECT) == 0) ||
2867                             (ED_gpencil_stroke_can_use(C, gps) == false))
2868                         {
2869                                 continue;
2870                         }
2871                         /* skip hidden or locked colors */
2872                         if (!gp_style ||
2873                             (gp_style->flag & GP_STYLE_COLOR_HIDE) ||
2874                             (gp_style->flag & GP_STYLE_COLOR_LOCKED))
2875                         {
2876                                 continue;
2877                         }
2878
2879                         if ((type == GP_STROKE_CAPS_TOGGLE_BOTH) ||
2880                             (type == GP_STROKE_CAPS_TOGGLE_START))
2881                         {
2882                                 ++gps->caps[0];
2883                                 if (gps->caps[0] >= GP_STROKE_CAP_MAX) {
2884                                         gps->caps[0] = GP_STROKE_CAP_ROUND;
2885                                 }
2886                         }
2887                         if ((type == GP_STROKE_CAPS_TOGGLE_BOTH) ||
2888                             (type == GP_STROKE_CAPS_TOGGLE_END))
2889                         {
2890                                 ++gps->caps[1];
2891                                 if (gps->caps[1] >= GP_STROKE_CAP_MAX) {
2892                                         gps->caps[1] = GP_STROKE_CAP_ROUND;
2893                                 }
2894                         }
2895                         if (type == GP_STROKE_CAPS_TOGGLE_DEFAULT) {
2896                                 gps->caps[0] = GP_STROKE_CAP_ROUND;
2897                                 gps->caps[1] = GP_STROKE_CAP_ROUND;
2898                         }
2899                 }
2900         }
2901         CTX_DATA_END;
2902
2903         /* notifiers */
2904         DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
2905         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
2906
2907         return OPERATOR_FINISHED;
2908 }
2909
2910 /**
2911  * Change Stroke caps mode Rounded or Flat
2912  */
2913 void GPENCIL_OT_stroke_caps_set(wmOperatorType *ot)
2914 {
2915         static const EnumPropertyItem toggle_type[] = {
2916                 {GP_STROKE_CAPS_TOGGLE_BOTH, "TOGGLE", 0, "Both", ""},
2917                 {GP_STROKE_CAPS_TOGGLE_START, "START", 0, "Start", ""},
2918                 {GP_STROKE_CAPS_TOGGLE_END, "END", 0, "End", ""},
2919                 {GP_STROKE_CAPS_TOGGLE_DEFAULT, "TOGGLE", 0, "Default", "Set as default rounded"},
2920                 {0, NULL, 0, NULL, NULL},
2921         };
2922
2923         /* identifiers */
2924         ot->name = "Set Caps Mode";
2925         ot->idname = "GPENCIL_OT_stroke_caps_set";
2926         ot->description = "Change Stroke caps mode (rounded or flat)";
2927
2928         /* api callbacks */
2929         ot->exec = gp_stroke_caps_set_exec;
2930         ot->poll = gp_active_layer_poll;
2931
2932         /* flags */
2933         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2934
2935         /* properties */
2936         ot->prop = RNA_def_enum(ot->srna, "type", toggle_type, GP_STROKE_CAPS_TOGGLE_BOTH, "Type", "");
2937 }
2938
2939 /* ******************* Stroke join ************************** */
2940
2941 /* Helper: flip stroke */
2942 static void gpencil_flip_stroke(bGPDstroke *gps)
2943 {
2944         int end = gps->totpoints - 1;
2945
2946         for (int i = 0; i < gps->totpoints / 2; i++) {
2947                 bGPDspoint *point, *point2;
2948                 bGPDspoint pt;
2949
2950                 /* save first point */
2951                 point = &gps->points[i];
2952                 pt.x = point->x;
2953                 pt.y = point->y;
2954                 pt.z = point->z;
2955                 pt.flag = point->flag;
2956                 pt.pressure = point->pressure;
2957                 pt.strength = point->strength;
2958                 pt.time = point->time;
2959
2960                 /* replace&