Fix T42961: GPencil - Refactor object/scene data behaviour
[blender.git] / source / blender / editors / gpencil / gpencil_edit.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung
19  * This is a new part of Blender
20  *
21  * Contributor(s): Joshua Leung
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/editors/gpencil/gpencil_edit.c
27  *  \ingroup edgpencil
28  */
29
30 #include <stdio.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <stddef.h>
34 #include <math.h>
35
36 #include "MEM_guardedalloc.h"
37
38 #include "BLI_math.h"
39 #include "BLI_blenlib.h"
40 #include "BLI_rand.h"
41 #include "BLI_utildefines.h"
42
43 #include "BLF_translation.h"
44
45 #include "DNA_anim_types.h"
46 #include "DNA_curve_types.h"
47 #include "DNA_object_types.h"
48 #include "DNA_node_types.h"
49 #include "DNA_scene_types.h"
50 #include "DNA_screen_types.h"
51 #include "DNA_space_types.h"
52 #include "DNA_view3d_types.h"
53 #include "DNA_gpencil_types.h"
54
55 #include "BKE_context.h"
56 #include "BKE_curve.h"
57 #include "BKE_depsgraph.h"
58 #include "BKE_fcurve.h"
59 #include "BKE_global.h"
60 #include "BKE_gpencil.h"
61 #include "BKE_library.h"
62 #include "BKE_object.h"
63 #include "BKE_report.h"
64 #include "BKE_scene.h"
65 #include "BKE_screen.h"
66 #include "BKE_tracking.h"
67
68 #include "UI_interface.h"
69
70 #include "WM_api.h"
71 #include "WM_types.h"
72
73 #include "RNA_access.h"
74 #include "RNA_define.h"
75
76 #include "UI_view2d.h"
77
78 #include "ED_gpencil.h"
79 #include "ED_view3d.h"
80 #include "ED_clip.h"
81 #include "ED_keyframing.h"
82
83 #include "gpencil_intern.h"
84
85
86 /* ************************************************ */
87 /* Context Wrangling... */
88
89 /* Get pointer to active Grease Pencil datablock, and an RNA-pointer to trace back to whatever owns it,
90  * when context info is not available.
91  */
92 bGPdata **ED_gpencil_data_get_pointers_direct(ID *screen_id, Scene *scene, ScrArea *sa, Object *ob, PointerRNA *ptr)
93 {
94         /* if there's an active area, check if the particular editor may
95          * have defined any special Grease Pencil context for editing...
96          */
97         if (sa) {
98                 SpaceLink *sl = sa->spacedata.first;
99                 
100                 switch (sa->spacetype) {
101                         case SPACE_VIEW3D: /* 3D-View */
102                         case SPACE_TIME: /* Timeline - XXX: this is a hack to get it to show GP keyframes for 3D view */
103                         {
104                                 BLI_assert(scene && ELEM(scene->toolsettings->gpencil_src,
105                                                          GP_TOOL_SOURCE_SCENE, GP_TOOL_SOURCE_OBJECT));
106
107                                 if (scene->toolsettings->gpencil_src == GP_TOOL_SOURCE_OBJECT) {
108                                         /* legacy behaviour for usage with old addons requiring object-linked to objects */
109                                         
110                                         /* just in case no active/selected object... */
111                                         if (ob && (ob->flag & SELECT)) {
112                                                 /* for now, as long as there's an object, default to using that in 3D-View */
113                                                 if (ptr) RNA_id_pointer_create(&ob->id, ptr);
114                                                 return &ob->gpd;
115                                         }
116                                         /* else: defaults to scene... */
117                                 }
118                                 else {
119                                         if (ptr) RNA_id_pointer_create(&scene->id, ptr);
120                                         return &scene->gpd;
121                                 }
122                                 break;
123                         }
124                         case SPACE_NODE: /* Nodes Editor */
125                         {
126                                 SpaceNode *snode = (SpaceNode *)sl;
127
128                                 /* return the GP data for the active node block/node */
129                                 if (snode && snode->nodetree) {
130                                         /* for now, as long as there's an active node tree, default to using that in the Nodes Editor */
131                                         if (ptr) RNA_id_pointer_create(&snode->nodetree->id, ptr);
132                                         return &snode->nodetree->gpd;
133                                 }
134
135                                 /* even when there is no node-tree, don't allow this to flow to scene */
136                                 return NULL;
137                         }
138                         case SPACE_SEQ: /* Sequencer */
139                         {
140                                 SpaceSeq *sseq = (SpaceSeq *)sl;
141
142                                 /* for now, Grease Pencil data is associated with the space (actually preview region only) */
143                                 /* XXX our convention for everything else is to link to data though... */
144                                 if (ptr) RNA_pointer_create(screen_id, &RNA_SpaceSequenceEditor, sseq, ptr);
145                                 return &sseq->gpd;
146                         }
147                         case SPACE_IMAGE: /* Image/UV Editor */
148                         {
149                                 SpaceImage *sima = (SpaceImage *)sl;
150
151                                 /* for now, Grease Pencil data is associated with the space... */
152                                 /* XXX our convention for everything else is to link to data though... */
153                                 if (ptr) RNA_pointer_create(screen_id, &RNA_SpaceImageEditor, sima, ptr);
154                                 return &sima->gpd;
155                         }
156                         case SPACE_CLIP: /* Nodes Editor */
157                         {
158                                 SpaceClip *sc = (SpaceClip *)sl;
159                                 MovieClip *clip = ED_space_clip_get_clip(sc);
160
161                                 if (clip) {
162                                         if (sc->gpencil_src == SC_GPENCIL_SRC_TRACK) {
163                                                 MovieTrackingTrack *track = BKE_tracking_track_get_active(&clip->tracking);
164
165                                                 if (!track)
166                                                         return NULL;
167
168                                                 if (ptr)
169                                                         RNA_pointer_create(&clip->id, &RNA_MovieTrackingTrack, track, ptr);
170
171                                                 return &track->gpd;
172                                         }
173                                         else {
174                                                 if (ptr)
175                                                         RNA_id_pointer_create(&clip->id, ptr);
176
177                                                 return &clip->gpd;
178                                         }
179                                 }
180                                 break;
181                         }
182                         default: /* unsupported space */
183                                 return NULL;
184                 }
185         }
186
187         /* just fall back on the scene's GP data */
188         if (ptr) RNA_id_pointer_create((ID *)scene, ptr);
189         return (scene) ? &scene->gpd : NULL;
190 }
191
192 /* Get pointer to active Grease Pencil datablock, and an RNA-pointer to trace back to whatever owns it */
193 bGPdata **ED_gpencil_data_get_pointers(const bContext *C, PointerRNA *ptr)
194 {
195         ID *screen_id = (ID *)CTX_wm_screen(C);
196         Scene *scene = CTX_data_scene(C);
197         ScrArea *sa = CTX_wm_area(C);
198         Object *ob = CTX_data_active_object(C);
199         
200         return ED_gpencil_data_get_pointers_direct(screen_id, scene, sa, ob, ptr);
201 }
202
203 /* -------------------------------------------------------- */
204
205 /* Get the active Grease Pencil datablock, when context is not available */
206 bGPdata *ED_gpencil_data_get_active_direct(ID *screen_id, Scene *scene, ScrArea *sa, Object *ob)
207 {
208         bGPdata **gpd_ptr = ED_gpencil_data_get_pointers_direct(screen_id, scene, sa, ob, NULL);
209         return (gpd_ptr) ? *(gpd_ptr) : NULL;
210 }
211
212 /* Get the active Grease Pencil datablock */
213 bGPdata *ED_gpencil_data_get_active(const bContext *C)
214 {
215         bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL);
216         return (gpd_ptr) ? *(gpd_ptr) : NULL;
217 }
218
219 /* -------------------------------------------------------- */
220
221 bGPdata *ED_gpencil_data_get_active_v3d(Scene *scene, View3D *v3d)
222 {
223         Base *base = scene->basact;
224         bGPdata *gpd = NULL;
225         /* We have to make sure active object is actually visible and selected, else we must use default scene gpd,
226          * to be consistent with ED_gpencil_data_get_active's behavior.
227          */
228
229         if (base && TESTBASE(v3d, base)) {
230                 gpd = base->object->gpd;
231         }
232         return gpd ? gpd : scene->gpd;
233 }
234
235 /* ************************************************ */
236 /* Panel Operators */
237
238 /* poll callback for adding data/layers - special */
239 static int gp_add_poll(bContext *C)
240 {
241         /* the base line we have is that we have somewhere to add Grease Pencil data */
242         return ED_gpencil_data_get_pointers(C, NULL) != NULL;
243 }
244
245 /* poll callback for checking if there is an active layer */
246 static int gp_active_layer_poll(bContext *C)
247 {
248         bGPdata *gpd = ED_gpencil_data_get_active(C);
249         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
250         
251         return (gpl != NULL);
252 }
253
254 /* ******************* Add New Data ************************ */
255
256 /* add new datablock - wrapper around API */
257 static int gp_data_add_exec(bContext *C, wmOperator *op)
258 {
259         bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL);
260
261         if (gpd_ptr == NULL) {
262                 BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go");
263                 return OPERATOR_CANCELLED;
264         }
265         else {
266                 /* decrement user count and add new datablock */
267                 bGPdata *gpd = (*gpd_ptr);
268
269                 id_us_min(&gpd->id);
270                 *gpd_ptr = gpencil_data_addnew(DATA_("GPencil"));
271         }
272
273         /* notifiers */
274         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
275
276         return OPERATOR_FINISHED;
277 }
278
279 void GPENCIL_OT_data_add(wmOperatorType *ot)
280 {
281         /* identifiers */
282         ot->name = "Grease Pencil Add New";
283         ot->idname = "GPENCIL_OT_data_add";
284         ot->description = "Add new Grease Pencil datablock";
285         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
286
287         /* callbacks */
288         ot->exec = gp_data_add_exec;
289         ot->poll = gp_add_poll;
290 }
291
292 /* ******************* Unlink Data ************************ */
293
294 /* poll callback for adding data/layers - special */
295 static int gp_data_unlink_poll(bContext *C)
296 {
297         bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL);
298
299         /* if we have access to some active data, make sure there's a datablock before enabling this */
300         return (gpd_ptr && *gpd_ptr);
301 }
302
303
304 /* unlink datablock - wrapper around API */
305 static int gp_data_unlink_exec(bContext *C, wmOperator *op)
306 {
307         bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL);
308
309         if (gpd_ptr == NULL) {
310                 BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go");
311                 return OPERATOR_CANCELLED;
312         }
313         else {
314                 /* just unlink datablock now, decreasing its user count */
315                 bGPdata *gpd = (*gpd_ptr);
316
317                 id_us_min(&gpd->id);
318                 *gpd_ptr = NULL;
319         }
320
321         /* notifiers */
322         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); 
323
324         return OPERATOR_FINISHED;
325 }
326
327 void GPENCIL_OT_data_unlink(wmOperatorType *ot)
328 {
329         /* identifiers */
330         ot->name = "Grease Pencil Unlink";
331         ot->idname = "GPENCIL_OT_data_unlink";
332         ot->description = "Unlink active Grease Pencil datablock";
333         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
334
335         /* callbacks */
336         ot->exec = gp_data_unlink_exec;
337         ot->poll = gp_data_unlink_poll;
338 }
339
340 /* ******************* Add New Layer ************************ */
341
342 /* add new layer - wrapper around API */
343 static int gp_layer_add_exec(bContext *C, wmOperator *op)
344 {
345         bGPdata **gpd_ptr = ED_gpencil_data_get_pointers(C, NULL);
346
347         /* if there's no existing Grease-Pencil data there, add some */
348         if (gpd_ptr == NULL) {
349                 BKE_report(op->reports, RPT_ERROR, "Nowhere for grease pencil data to go");
350                 return OPERATOR_CANCELLED;
351         }
352         if (*gpd_ptr == NULL)
353                 *gpd_ptr = gpencil_data_addnew(DATA_("GPencil"));
354
355         /* add new layer now */
356         gpencil_layer_addnew(*gpd_ptr, DATA_("GP_Layer"), 1);
357
358         /* notifiers */
359         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
360
361         return OPERATOR_FINISHED;
362 }
363
364 void GPENCIL_OT_layer_add(wmOperatorType *ot)
365 {
366         /* identifiers */
367         ot->name = "Add New Layer";
368         ot->idname = "GPENCIL_OT_layer_add";
369         ot->description = "Add new Grease Pencil layer for the active Grease Pencil datablock";
370         
371         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
372         
373         /* callbacks */
374         ot->exec = gp_layer_add_exec;
375         ot->poll = gp_add_poll;
376 }
377
378 /* ******************* Remove Active Layer ************************* */
379
380 static int gp_layer_remove_exec(bContext *C, wmOperator *op)
381 {
382         bGPdata *gpd = ED_gpencil_data_get_active(C);
383         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
384         
385         /* sanity checks */
386         if (ELEM(NULL, gpd, gpl))
387                 return OPERATOR_CANCELLED;
388         
389         if (gpl->flag & GP_LAYER_LOCKED) {
390                 BKE_report(op->reports, RPT_ERROR, "Cannot delete locked layers");
391                 return OPERATOR_CANCELLED;
392         }       
393         
394         /* make the layer before this the new active layer 
395          * - use the one after if this is the first
396          * - if this is the only layer, this naturally becomes NULL
397          */
398         if (gpl->prev)
399                 gpencil_layer_setactive(gpd, gpl->prev);
400         else
401                 gpencil_layer_setactive(gpd, gpl->next);
402                 
403         /* delete the layer now... */
404         gpencil_layer_delete(gpd, gpl);
405         
406         /* notifiers */
407         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
408         
409         return OPERATOR_FINISHED;
410 }
411
412 void GPENCIL_OT_layer_remove(wmOperatorType *ot)
413 {
414         /* identifiers */
415         ot->name = "Remove Layer";
416         ot->idname = "GPENCIL_OT_layer_remove";
417         ot->description = "Remove active Grease Pencil layer";
418         
419         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
420         
421         /* callbacks */
422         ot->exec = gp_layer_remove_exec;
423         ot->poll = gp_active_layer_poll;
424 }
425
426 /* ******************* Move Layer Up/Down ************************** */
427
428 enum {
429         GP_LAYER_MOVE_UP   = -1,
430         GP_LAYER_MOVE_DOWN = 1
431 };
432
433 static int gp_layer_move_exec(bContext *C, wmOperator *op)
434 {
435         bGPdata *gpd = ED_gpencil_data_get_active(C);
436         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
437         
438         int direction = RNA_enum_get(op->ptr, "type");
439         
440         /* sanity checks */
441         if (ELEM(NULL, gpd, gpl))
442                 return OPERATOR_CANCELLED;
443                 
444         /* up or down? */
445         if (direction == GP_LAYER_MOVE_UP) {
446                 /* up */
447                 BLI_remlink(&gpd->layers, gpl);
448                 BLI_insertlinkbefore(&gpd->layers, gpl->prev, gpl);
449         }
450         else {
451                 /* down */
452                 BLI_remlink(&gpd->layers, gpl);
453                 BLI_insertlinkafter(&gpd->layers, gpl->next, gpl);
454         }
455         
456         /* notifiers */
457         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
458         
459         return OPERATOR_FINISHED;
460 }
461
462 void GPENCIL_OT_layer_move(wmOperatorType *ot)
463 {
464         static EnumPropertyItem slot_move[] = {
465                 {GP_LAYER_MOVE_UP, "UP", 0, "Up", ""},
466                 {GP_LAYER_MOVE_DOWN, "DOWN", 0, "Down", ""},
467                 {0, NULL, 0, NULL, NULL}
468         };
469
470         /* identifiers */
471         ot->name = "Move Grease Pencil Layer";
472         ot->idname = "GPENCIL_OT_layer_move";
473         ot->description = "Move the active Grease Pencil layer up/down in the list";
474
475         /* api callbacks */
476         ot->exec = gp_layer_move_exec;
477         ot->poll = gp_active_layer_poll;
478
479         /* flags */
480         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
481
482         ot->prop = RNA_def_enum(ot->srna, "type", slot_move, 0, "Type", "");
483 }
484
485 /* ************************************************ */
486 /* Stroke Editing Operators */
487
488 /* poll callback for all stroke editing operators */
489 static int gp_stroke_edit_poll(bContext *C)
490 {
491         /* NOTE: this is a bit slower, but is the most accurate... */
492         return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
493 }
494
495 /* ************** Duplicate Selected Strokes **************** */
496
497 /* Make copies of selected point segments in a selected stroke */
498 static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes)
499 {
500         bGPDspoint *pt;
501         int i;
502         
503         int start_idx = -1;
504         
505         
506         /* Step through the original stroke's points:
507          * - We accumulate selected points (from start_idx to current index)
508          *   and then convert that to a new stroke
509          */
510         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
511                 /* searching for start, are waiting for end? */
512                 if (start_idx == -1) {
513                         /* is this the first selected point for a new island? */
514                         if (pt->flag & GP_SPOINT_SELECT) {
515                                 start_idx = i;
516                         }
517                 }
518                 else {
519                         size_t len = 0;
520                         
521                         /* is this the end of current island yet? 
522                          * 1) Point i-1 was the last one that was selected
523                          * 2) Point i is the last in the array
524                          */
525                         if ((pt->flag & GP_SPOINT_SELECT) == 0) {
526                                 len = i - start_idx;
527                         }
528                         else if (i == gps->totpoints - 1) {
529                                 len = i - start_idx + 1;
530                         }
531                         //printf("copying from %d to %d = %d\n", start_idx, i, len);
532                         
533                         /* make copies of the relevant data */
534                         if (len) {
535                                 bGPDstroke *gpsd;
536                                 
537                                 /* make a stupid copy first of the entire stroke (to get the flags too) */
538                                 gpsd = MEM_dupallocN(gps);
539                                 
540                                 /* now, make a new points array, and copy of the relevant parts */
541                                 gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy");
542                                 memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len);
543                                 gpsd->totpoints = len;
544                                 
545                                 /* add to temp buffer */
546                                 gpsd->next = gpsd->prev = NULL;
547                                 BLI_addtail(new_strokes, gpsd);
548                                 
549                                 /* cleanup + reset for next */
550                                 start_idx = -1;
551                         }
552                 }
553         }
554 }
555
556 static int gp_duplicate_exec(bContext *C, wmOperator *op)
557 {
558         bGPdata *gpd = ED_gpencil_data_get_active(C);
559         
560         if (gpd == NULL) {
561                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
562                 return OPERATOR_CANCELLED;
563         }
564         
565         /* for each visible (and editable) layer's selected strokes,
566          * copy the strokes into a temporary buffer, then append
567          * once all done
568          */
569         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
570         {
571                 ListBase new_strokes = {NULL, NULL};
572                 bGPDframe *gpf = gpl->actframe;
573                 bGPDstroke *gps;
574                 
575                 if (gpf == NULL)
576                         continue;
577                 
578                 /* make copies of selected strokes, and deselect these once we're done */
579                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
580                         if (gps->flag & GP_STROKE_SELECT) {
581                                 if (gps->totpoints == 1) {
582                                         /* Special Case: If there's just a single point in this stroke... */
583                                         bGPDstroke *gpsd;
584                                         
585                                         /* make direct copies of the stroke and its points */
586                                         gpsd = MEM_dupallocN(gps);
587                                         gpsd->points = MEM_dupallocN(gps->points);
588                                         
589                                         /* add to temp buffer */
590                                         gpsd->next = gpsd->prev = NULL;
591                                         BLI_addtail(&new_strokes, gpsd);
592                                 }
593                                 else {
594                                         /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
595                                         gp_duplicate_points(gps, &new_strokes);
596                                 }
597                                 
598                                 /* deselect original stroke, or else the originals get moved too 
599                                  * (when using the copy + move macro)
600                                  */
601                                 gps->flag &= ~GP_STROKE_SELECT;
602                         }
603                 }
604                 
605                 /* add all new strokes in temp buffer to the frame (preventing double-copies) */
606                 BLI_movelisttolist(&gpf->strokes, &new_strokes);
607                 BLI_assert(new_strokes.first == NULL);
608         }
609         CTX_DATA_END;
610         
611         /* updates */
612         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
613         
614         return OPERATOR_FINISHED;
615 }
616
617 void GPENCIL_OT_duplicate(wmOperatorType *ot)
618 {
619         /* identifiers */
620         ot->name = "Duplicate Strokes";
621         ot->idname = "GPENCIL_OT_duplicate";
622         ot->description = "Duplicate the selected Grease Pencil strokes";
623         
624         /* callbacks */
625         ot->exec = gp_duplicate_exec;
626         ot->poll = gp_stroke_edit_poll;
627         
628         /* flags */
629         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
630 }
631
632 /* ******************* Delete Active Frame ************************ */
633
634 static int gp_actframe_delete_poll(bContext *C)
635 {
636         bGPdata *gpd = ED_gpencil_data_get_active(C);
637         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
638
639         /* only if there's an active layer with an active frame */
640         return (gpl && gpl->actframe);
641 }
642
643 /* delete active frame - wrapper around API calls */
644 static int gp_actframe_delete_exec(bContext *C, wmOperator *op)
645 {
646         Scene *scene = CTX_data_scene(C);
647         bGPdata *gpd = ED_gpencil_data_get_active(C);
648         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
649         bGPDframe *gpf = gpencil_layer_getframe(gpl, CFRA, 0);
650
651         /* if there's no existing Grease-Pencil data there, add some */
652         if (gpd == NULL) {
653                 BKE_report(op->reports, RPT_ERROR, "No grease pencil data");
654                 return OPERATOR_CANCELLED;
655         }
656         if (ELEM(NULL, gpl, gpf)) {
657                 BKE_report(op->reports, RPT_ERROR, "No active frame to delete");
658                 return OPERATOR_CANCELLED;
659         }
660
661         /* delete it... */
662         gpencil_layer_delframe(gpl, gpf);
663
664         /* notifiers */
665         WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
666
667         return OPERATOR_FINISHED;
668 }
669
670 void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
671 {
672         /* identifiers */
673         ot->name = "Delete Active Frame";
674         ot->idname = "GPENCIL_OT_active_frame_delete";
675         ot->description = "Delete the active frame for the active Grease Pencil datablock";
676         
677         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
678
679         /* callbacks */
680         ot->exec = gp_actframe_delete_exec;
681         ot->poll = gp_actframe_delete_poll;
682 }
683
684 /* ******************* Delete Operator ************************ */
685
686 typedef enum eGP_DeleteMode {
687         /* delete selected stroke points */
688         GP_DELETEOP_POINTS          = 0,
689         /* delete selected strokes */
690         GP_DELETEOP_STROKES         = 1,
691         /* delete active frame */
692         GP_DELETEOP_FRAME           = 2,
693         /* delete selected stroke points (without splitting stroke) */
694         GP_DELETEOP_POINTS_DISSOLVE = 3,
695 } eGP_DeleteMode;
696
697
698 /* Delete selected strokes */
699 static int gp_delete_selected_strokes(bContext *C)
700 {
701         bool changed = false;
702         
703         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
704         {
705                 bGPDframe *gpf = gpl->actframe;
706                 bGPDstroke *gps, *gpsn;
707                 
708                 if (gpf == NULL)
709                         continue;
710                 
711                 /* simply delete strokes which are selected */
712                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
713                         gpsn = gps->next;
714                         
715                         if (gps->flag & GP_STROKE_SELECT) {
716                                 /* free stroke memory arrays, then stroke itself */
717                                 if (gps->points) MEM_freeN(gps->points);
718                                 BLI_freelinkN(&gpf->strokes, gps);
719                                 
720                                 changed = true;
721                         }
722                 }
723         }
724         CTX_DATA_END;
725         
726         if (changed) {
727                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
728                 return OPERATOR_FINISHED;
729         }
730         else {
731                 return OPERATOR_CANCELLED;
732         }
733 }
734
735 /* Delete selected points but keep the stroke */
736 static int gp_dissolve_selected_points(bContext *C)
737 {
738         bool changed = false;
739         
740         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
741         {
742                 bGPDframe *gpf = gpl->actframe;
743                 bGPDstroke *gps, *gpsn;
744                 
745                 if (gpf == NULL)
746                         continue;
747                 
748                 /* simply delete points from selected strokes
749                  * NOTE: we may still have to remove the stroke if it ends up having no points!
750                  */
751                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
752                         gpsn = gps->next;
753                         
754                         if (gps->flag & GP_STROKE_SELECT) {
755                                 bGPDspoint *pt;
756                                 int i;
757                                 
758                                 int tot = gps->totpoints; /* number of points in new buffer */
759                                 
760                                 /* First Pass: Count how many points are selected (i.e. how many to remove) */
761                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
762                                         if (pt->flag & GP_SPOINT_SELECT) {
763                                                 /* selected point - one of the points to remove */
764                                                 tot--;
765                                         }
766                                 }
767                                 
768                                 /* if no points are left, we simply delete the entire stroke */
769                                 if (tot <= 0) {
770                                         /* remove the entire stroke */
771                                         MEM_freeN(gps->points);
772                                         BLI_freelinkN(&gpf->strokes, gps);
773                                 }
774                                 else {  
775                                         /* just copy all unselected into a smaller buffer */
776                                         bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy");
777                                         bGPDspoint *npt        = new_points;
778                                         
779                                         for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
780                                                 if ((pt->flag & GP_SPOINT_SELECT) == 0) {
781                                                         *npt = *pt;
782                                                         npt++;
783                                                 }
784                                         }
785                                         
786                                         /* free the old buffer */
787                                         MEM_freeN(gps->points);
788                                         
789                                         /* save the new buffer */
790                                         gps->points = new_points;
791                                         gps->totpoints = tot;
792                                         
793                                         /* deselect the stroke, since none of its selected points will still be selected */
794                                         gps->flag &= ~GP_STROKE_SELECT;
795                                 }
796                                 
797                                 changed = true;
798                         }
799                 }
800         }
801         CTX_DATA_END;
802         
803         if (changed) {
804                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
805                 return OPERATOR_FINISHED;
806         }
807         else {
808                 return OPERATOR_CANCELLED;
809         }
810 }
811
812 /* Split selected strokes into segments, splitting on selected points */
813 static int gp_delete_selected_points(bContext *C)
814 {
815         bool changed = false;
816         
817         CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
818         {
819                 bGPDframe *gpf = gpl->actframe;
820                 bGPDstroke *gps, *gpsn;
821                 
822                 if (gpf == NULL)
823                         continue;
824                 
825                 /* simply delete strokes which are selected */
826                 for (gps = gpf->strokes.first; gps; gps = gpsn) {
827                         gpsn = gps->next;
828                         
829                         if (gps->flag & GP_STROKE_SELECT) {
830                                 bGPDspoint *pt;
831                                 int i;
832                                 
833                                 /* The algorithm used here is as follows:
834                                  * 1) We firstly identify the number of "islands" of non-selected points
835                                  *    which will all end up being in new strokes.
836                                  *    - In the most extreme case (i.e. every other vert is a 1-vert island), 
837                                  *      we have at most n / 2 islands
838                                  *    - Once we start having larger islands than that, the number required
839                                  *      becomes much less
840                                  * 2) Each island gets converted to a new stroke
841                                  */
842                                 typedef struct tGPDeleteIsland {
843                                         int start_idx;
844                                         int end_idx;
845                                 } tGPDeleteIsland;
846                                 
847                                 tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
848                                 bool in_island  = false;
849                                 int num_islands = 0;
850                                 
851                                 /* First Pass: Identify start/end of islands */
852                                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
853                                         if (pt->flag & GP_SPOINT_SELECT) {
854                                                 /* selected - stop accumulating to island */
855                                                 in_island = false;
856                                         }
857                                         else {
858                                                 /* unselected - start of a new island? */
859                                                 int idx;
860                                                 
861                                                 if (in_island) {
862                                                         /* extend existing island */
863                                                         idx = num_islands - 1;
864                                                         islands[idx].end_idx = i;
865                                                 }
866                                                 else {
867                                                         /* start of new island */
868                                                         in_island = true;
869                                                         num_islands++;
870                                                         
871                                                         idx = num_islands - 1;
872                                                         islands[idx].start_idx = islands[idx].end_idx = i;
873                                                 }
874                                         }
875                                 }
876                                 
877                                 /* Watch out for special case where No islands = All points selected = Delete Stroke only */
878                                 if (num_islands) {
879                                         /* there are islands, so create a series of new strokes, adding them before the "next" stroke */
880                                         int idx;
881                                         
882                                         /* deselect old stroke, since it will be used as template for the new strokes */
883                                         gps->flag &= ~GP_STROKE_SELECT;
884                                         
885                                         /* create each new stroke... */
886                                         for (idx = 0; idx < num_islands; idx++) {
887                                                 tGPDeleteIsland *island = &islands[idx];
888                                                 bGPDstroke *new_stroke  = MEM_dupallocN(gps);
889                                                 
890                                                 /* compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
891                                                 new_stroke->totpoints = island->end_idx - island->start_idx + 1;
892                                                 new_stroke->points    = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
893                                                 
894                                                 /* copy over the relevant points */
895                                                 memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
896                                                 
897                                                 /* add new stroke to the frame */
898                                                 if (gpsn) {
899                                                         BLI_insertlinkbefore(&gpf->strokes, gpsn, new_stroke);
900                                                 }
901                                                 else {
902                                                         BLI_addtail(&gpf->strokes, new_stroke);
903                                                 }
904                                         }
905                                 }
906                                 
907                                 /* free islands */
908                                 MEM_freeN(islands);
909                                 
910                                 /* Delete the old stroke */
911                                 MEM_freeN(gps->points);
912                                 BLI_freelinkN(&gpf->strokes, gps);
913                                 
914                                 changed = true;
915                         }
916                 }
917         }
918         CTX_DATA_END;
919         
920         if (changed) {
921                 WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
922                 return OPERATOR_FINISHED;
923         }
924         else {
925                 return OPERATOR_CANCELLED;
926         }
927 }
928
929
930 static int gp_delete_exec(bContext *C, wmOperator *op)
931 {       
932         eGP_DeleteMode mode = RNA_enum_get(op->ptr, "type");
933         int result = OPERATOR_CANCELLED;
934         
935         switch (mode) {
936                 case GP_DELETEOP_STROKES:       /* selected strokes */
937                         result = gp_delete_selected_strokes(C);
938                         break;
939                 
940                 case GP_DELETEOP_POINTS:        /* selected points (breaks the stroke into segments) */
941                         result = gp_delete_selected_points(C);
942                         break;
943                 
944                 case GP_DELETEOP_POINTS_DISSOLVE: /* selected points (without splitting the stroke) */
945                         result = gp_dissolve_selected_points(C);
946                         break;
947                         
948                 case GP_DELETEOP_FRAME:         /* active frame */
949                         result = gp_actframe_delete_exec(C, op);
950                         break;
951         }
952         
953         return result;
954 }
955
956 void GPENCIL_OT_delete(wmOperatorType *ot)
957 {
958         static EnumPropertyItem prop_gpencil_delete_types[] = {
959                 {GP_DELETEOP_POINTS, "POINTS", 0, "Points", "Delete selected points and split strokes into segments"},
960                 {GP_DELETEOP_STROKES, "STROKES", 0, "Strokes", "Delete selected strokes"},
961                 {GP_DELETEOP_FRAME, "FRAME", 0, "Frame", "Delete active frame"},
962                 {0, "", 0, NULL, NULL},
963                 {GP_DELETEOP_POINTS_DISSOLVE, "DISSOLVE_POINTS", 0, "Dissolve Points",
964                                               "Delete selected points without splitting strokes"},
965                 {0, NULL, 0, NULL, NULL}
966         };
967         
968         /* identifiers */
969         ot->name = "Delete...";
970         ot->idname = "GPENCIL_OT_delete";
971         ot->description = "Delete selected Grease Pencil strokes, vertices, or frames";
972         
973         /* callbacks */
974         ot->invoke = WM_menu_invoke;
975         ot->exec = gp_delete_exec;
976         ot->poll = gp_stroke_edit_poll;
977         
978         /* flags */
979         ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
980         
981         /* props */
982         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_delete_types, 0, "Type", "Method used for deleting Grease Pencil data");
983 }
984
985 /* ************************************************ */
986 /* Grease Pencil to Data Operator */
987
988 /* defines for possible modes */
989 enum {
990         GP_STROKECONVERT_PATH = 1,
991         GP_STROKECONVERT_CURVE,
992         GP_STROKECONVERT_POLY,
993 };
994
995 /* Defines for possible timing modes */
996 enum {
997         GP_STROKECONVERT_TIMING_NONE = 1,
998         GP_STROKECONVERT_TIMING_LINEAR = 2,
999         GP_STROKECONVERT_TIMING_FULL = 3,
1000         GP_STROKECONVERT_TIMING_CUSTOMGAP = 4,
1001 };
1002
1003 /* RNA enum define */
1004 static EnumPropertyItem prop_gpencil_convertmodes[] = {
1005         {GP_STROKECONVERT_PATH, "PATH", 0, "Path", ""},
1006         {GP_STROKECONVERT_CURVE, "CURVE", 0, "Bezier Curve", ""},
1007         {GP_STROKECONVERT_POLY, "POLY", 0, "Polygon Curve", ""},
1008         {0, NULL, 0, NULL, NULL}
1009 };
1010
1011 static EnumPropertyItem prop_gpencil_convert_timingmodes_restricted[] = {
1012         {GP_STROKECONVERT_TIMING_NONE, "NONE", 0, "No Timing", "Ignore timing"},
1013         {GP_STROKECONVERT_TIMING_LINEAR, "LINEAR", 0, "Linear", "Simple linear timing"},
1014         {0, NULL, 0, NULL, NULL},
1015 };
1016
1017 static EnumPropertyItem prop_gpencil_convert_timingmodes[] = {
1018         {GP_STROKECONVERT_TIMING_NONE, "NONE", 0, "No Timing", "Ignore timing"},
1019         {GP_STROKECONVERT_TIMING_LINEAR, "LINEAR", 0, "Linear", "Simple linear timing"},
1020         {GP_STROKECONVERT_TIMING_FULL, "FULL", 0, "Original", "Use the original timing, gaps included"},
1021         {GP_STROKECONVERT_TIMING_CUSTOMGAP, "CUSTOMGAP", 0, "Custom Gaps",
1022                                             "Use the original timing, but with custom gap lengths (in frames)"},
1023         {0, NULL, 0, NULL, NULL},
1024 };
1025
1026 static EnumPropertyItem *rna_GPConvert_mode_items(bContext *UNUSED(C), PointerRNA *ptr, PropertyRNA *UNUSED(prop),
1027                                                   bool *UNUSED(r_free))
1028 {
1029         if (RNA_boolean_get(ptr, "use_timing_data")) {
1030                 return prop_gpencil_convert_timingmodes;
1031         }
1032         return prop_gpencil_convert_timingmodes_restricted;
1033 }
1034
1035 /* --- */
1036
1037 /* convert the coordinates from the given stroke point into 3d-coordinates 
1038  *      - assumes that the active space is the 3D-View
1039  */
1040 static void gp_strokepoint_convertcoords(bContext *C, bGPDstroke *gps, bGPDspoint *pt, float p3d[3], rctf *subrect)
1041 {
1042         Scene *scene = CTX_data_scene(C);
1043         View3D *v3d = CTX_wm_view3d(C);
1044         ARegion *ar = CTX_wm_region(C);
1045
1046         if (gps->flag & GP_STROKE_3DSPACE) {
1047                 /* directly use 3d-coordinates */
1048                 copy_v3_v3(p3d, &pt->x);
1049         }
1050         else {
1051                 const float *fp = ED_view3d_cursor3d_get(scene, v3d);
1052                 float mvalf[2];
1053
1054                 /* get screen coordinate */
1055                 if (gps->flag & GP_STROKE_2DSPACE) {
1056                         View2D *v2d = &ar->v2d;
1057                         UI_view2d_view_to_region_fl(v2d, pt->x, pt->y, &mvalf[0], &mvalf[1]);
1058                 }
1059                 else {
1060                         if (subrect) {
1061                                 mvalf[0] = (((float)pt->x / 100.0f) * BLI_rctf_size_x(subrect)) + subrect->xmin;
1062                                 mvalf[1] = (((float)pt->y / 100.0f) * BLI_rctf_size_y(subrect)) + subrect->ymin;
1063                         }
1064                         else {
1065                                 mvalf[0] = (float)pt->x / 100.0f * ar->winx;
1066                                 mvalf[1] = (float)pt->y / 100.0f * ar->winy;
1067                         }
1068                 }
1069
1070                 ED_view3d_win_to_3d(ar, fp, mvalf, p3d);
1071         }
1072 }
1073
1074 /* --- */
1075
1076 /* temp struct for gp_stroke_path_animation() */
1077 typedef struct tGpTimingData {
1078         /* Data set from operator settings */
1079         int mode;
1080         int frame_range; /* Number of frames evaluated for path animation */
1081         int start_frame, end_frame;
1082         bool realtime; /* Will overwrite end_frame in case of Original or CustomGap timing... */
1083         float gap_duration, gap_randomness; /* To be used with CustomGap mode*/
1084         int seed;
1085
1086         /* Data set from points, used to compute final timing FCurve */
1087         int num_points, cur_point;
1088
1089         /* Distances */
1090         float *dists;
1091         float tot_dist;
1092
1093         /* Times */
1094         float *times; /* Note: Gap times will be negative! */
1095         float tot_time, gap_tot_time;
1096         double inittime;
1097
1098         /* Only used during creation of dists & times lists. */
1099         float offset_time;
1100 } tGpTimingData;
1101
1102 /* Init point buffers for timing data.
1103  * Note this assumes we only grow those arrays!
1104  */
1105 static void gp_timing_data_set_nbr(tGpTimingData *gtd, const int nbr)
1106 {
1107         float *tmp;
1108
1109         BLI_assert(nbr > gtd->num_points);
1110
1111         /* distances */
1112         tmp = gtd->dists;
1113         gtd->dists = MEM_callocN(sizeof(float) * nbr, __func__);
1114         if (tmp) {
1115                 memcpy(gtd->dists, tmp, sizeof(float) * gtd->num_points);
1116                 MEM_freeN(tmp);
1117         }
1118
1119         /* times */
1120         tmp = gtd->times;
1121         gtd->times = MEM_callocN(sizeof(float) * nbr, __func__);
1122         if (tmp) {
1123                 memcpy(gtd->times, tmp, sizeof(float) * gtd->num_points);
1124                 MEM_freeN(tmp);
1125         }
1126
1127         gtd->num_points = nbr;
1128 }
1129
1130 /* add stroke point to timing buffers */
1131 static void gp_timing_data_add_point(tGpTimingData *gtd, const double stroke_inittime, const float time,
1132                                      const float delta_dist)
1133 {
1134         float delta_time = 0.0f;
1135         const int cur_point = gtd->cur_point;
1136
1137         if (!cur_point) {
1138                 /* Special case, first point, if time is not 0.0f we have to compensate! */
1139                 gtd->offset_time = -time;
1140                 gtd->times[cur_point] = 0.0f;
1141         }
1142         else if (time < 0.0f) {
1143                 /* This is a gap, negative value! */
1144                 gtd->times[cur_point] = -(((float)(stroke_inittime - gtd->inittime)) + time + gtd->offset_time);
1145                 delta_time = -gtd->times[cur_point] - gtd->times[cur_point - 1];
1146
1147                 gtd->gap_tot_time += delta_time;
1148         }
1149         else {
1150                 gtd->times[cur_point] = (((float)(stroke_inittime - gtd->inittime)) + time + gtd->offset_time);
1151                 delta_time = gtd->times[cur_point] - fabsf(gtd->times[cur_point - 1]);
1152         }
1153
1154         gtd->tot_time += delta_time;
1155         gtd->tot_dist += delta_dist;
1156         gtd->dists[cur_point] = gtd->tot_dist;
1157
1158         gtd->cur_point++;
1159 }
1160
1161 /* In frames! Binary search for FCurve keys have a threshold of 0.01, so we can't set
1162  * arbitrarily close points - this is esp. important with NoGaps mode!
1163  */
1164 #define MIN_TIME_DELTA 0.02f
1165
1166 /* Loop over next points to find the end of the stroke, and compute */
1167 static int gp_find_end_of_stroke_idx(tGpTimingData *gtd, RNG *rng, const int idx, const int nbr_gaps,
1168                                      int *nbr_done_gaps, const float tot_gaps_time, const float delta_time,
1169                                      float *next_delta_time)
1170 {
1171         int j;
1172
1173         for (j = idx + 1; j < gtd->num_points; j++) {
1174                 if (gtd->times[j] < 0) {
1175                         gtd->times[j] = -gtd->times[j];
1176                         if (gtd->mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
1177                                 /* In this mode, gap time between this stroke and the next should be 0 currently...
1178                                  * So we have to compute its final duration!
1179                                  */
1180                                 if (gtd->gap_randomness > 0.0f) {
1181                                         /* We want gaps that are in gtd->gap_duration +/- gtd->gap_randomness range,
1182                                          * and which sum to exactly tot_gaps_time...
1183                                          */
1184                                         int rem_gaps = nbr_gaps - (*nbr_done_gaps);
1185                                         if (rem_gaps < 2) {
1186                                                 /* Last gap, just give remaining time! */
1187                                                 *next_delta_time = tot_gaps_time;
1188                                         }
1189                                         else {
1190                                                 float delta, min, max;
1191
1192                                                 /* This code ensures that if the first gaps have been shorter than average gap_duration,
1193                                                  * next gaps will tend to be longer (i.e. try to recover the lateness), and vice-versa!
1194                                                  */
1195                                                 delta = delta_time - (gtd->gap_duration * (*nbr_done_gaps));
1196
1197                                                 /* Clamp min between [-gap_randomness, 0.0], with lower delta giving higher min */
1198                                                 min = -gtd->gap_randomness - delta;
1199                                                 CLAMP(min, -gtd->gap_randomness, 0.0f);
1200
1201                                                 /* Clamp max between [0.0, gap_randomness], with lower delta giving higher max */
1202                                                 max = gtd->gap_randomness - delta;
1203                                                 CLAMP(max, 0.0f, gtd->gap_randomness);
1204                                                 *next_delta_time += gtd->gap_duration + (BLI_rng_get_float(rng) * (max - min)) + min;
1205                                         }
1206                                 }
1207                                 else {
1208                                         *next_delta_time += gtd->gap_duration;
1209                                 }
1210                         }
1211                         (*nbr_done_gaps)++;
1212                         break;
1213                 }
1214         }
1215
1216         return j - 1;
1217 }
1218
1219 static void gp_stroke_path_animation_preprocess_gaps(tGpTimingData *gtd, RNG *rng, int *nbr_gaps, float *tot_gaps_time)
1220 {
1221         int i;
1222         float delta_time = 0.0f;
1223
1224         for (i = 0; i < gtd->num_points; i++) {
1225                 if (gtd->times[i] < 0 && i) {
1226                         (*nbr_gaps)++;
1227                         gtd->times[i] = -gtd->times[i] - delta_time;
1228                         delta_time += gtd->times[i] - gtd->times[i - 1];
1229                         gtd->times[i] = -gtd->times[i - 1]; /* Temp marker, values *have* to be different! */
1230                 }
1231                 else {
1232                         gtd->times[i] -= delta_time;
1233                 }
1234         }
1235         gtd->tot_time -= delta_time;
1236
1237         *tot_gaps_time = (float)(*nbr_gaps) * gtd->gap_duration;
1238         gtd->tot_time += *tot_gaps_time;
1239         if (G.debug & G_DEBUG) {
1240                 printf("%f, %f, %f, %d\n", gtd->tot_time, delta_time, *tot_gaps_time, *nbr_gaps);
1241         }
1242         if (gtd->gap_randomness > 0.0f) {
1243                 BLI_rng_srandom(rng, gtd->seed);
1244         }
1245 }
1246
1247 static void gp_stroke_path_animation_add_keyframes(ReportList *reports, PointerRNA ptr, PropertyRNA *prop, FCurve *fcu,
1248                                                    Curve *cu, tGpTimingData *gtd, RNG *rng, const float time_range,
1249                                                    const int nbr_gaps, const float tot_gaps_time)
1250 {
1251         /* Use actual recorded timing! */
1252         const float time_start = (float)gtd->start_frame;
1253
1254         float last_valid_time = 0.0f;
1255         int end_stroke_idx = -1, start_stroke_idx = 0;
1256         float end_stroke_time = 0.0f;
1257
1258         /* CustomGaps specific */
1259         float delta_time = 0.0f, next_delta_time = 0.0f;
1260         int nbr_done_gaps = 0;
1261
1262         int i;
1263         float cfra;
1264
1265         /* This is a bit tricky, as:
1266          * - We can't add arbitrarily close points on FCurve (in time).
1267          * - We *must* have all "caps" points of all strokes in FCurve, as much as possible!
1268          */
1269         for (i = 0; i < gtd->num_points; i++) {
1270                 /* If new stroke... */
1271                 if (i > end_stroke_idx) {
1272                         start_stroke_idx = i;
1273                         delta_time = next_delta_time;
1274                         /* find end of that new stroke */
1275                         end_stroke_idx = gp_find_end_of_stroke_idx(gtd, rng, i, nbr_gaps, &nbr_done_gaps,
1276                                                                    tot_gaps_time, delta_time, &next_delta_time);
1277                         /* This one should *never* be negative! */
1278                         end_stroke_time = time_start + ((gtd->times[end_stroke_idx] + delta_time) / gtd->tot_time * time_range);
1279                 }
1280
1281                 /* Simple proportional stuff... */
1282                 cu->ctime = gtd->dists[i] / gtd->tot_dist * cu->pathlen;
1283                 cfra = time_start + ((gtd->times[i] + delta_time) / gtd->tot_time * time_range);
1284
1285                 /* And now, the checks about timing... */
1286                 if (i == start_stroke_idx) {
1287                         /* If first point of a stroke, be sure it's enough ahead of last valid keyframe, and
1288                          * that the end point of the stroke is far enough!
1289                          * In case it is not, we keep the end point...
1290                          * Note that with CustomGaps mode, this is here we set the actual gap timing!
1291                          */
1292                         if ((end_stroke_time - last_valid_time) > MIN_TIME_DELTA * 2) {
1293                                 if ((cfra - last_valid_time) < MIN_TIME_DELTA) {
1294                                         cfra = last_valid_time + MIN_TIME_DELTA;
1295                                 }
1296                                 insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
1297                                 last_valid_time = cfra;
1298                         }
1299                         else if (G.debug & G_DEBUG) {
1300                                 printf("\t Skipping start point %d, too close from end point %d\n", i, end_stroke_idx);
1301                         }
1302                 }
1303                 else if (i == end_stroke_idx) {
1304                         /* Always try to insert end point of a curve (should be safe enough, anyway...) */
1305                         if ((cfra - last_valid_time) < MIN_TIME_DELTA) {
1306                                 cfra = last_valid_time + MIN_TIME_DELTA;
1307                         }
1308                         insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
1309                         last_valid_time = cfra;
1310                 }
1311                 else {
1312                         /* Else ("middle" point), we only insert it if it's far enough from last keyframe,
1313                          * and also far enough from (not yet added!) end_stroke keyframe!
1314                          */
1315                         if ((cfra - last_valid_time) > MIN_TIME_DELTA && (end_stroke_time - cfra) > MIN_TIME_DELTA) {
1316                                 insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
1317                                 last_valid_time = cfra;
1318                         }
1319                         else if (G.debug & G_DEBUG) {
1320                                 printf("\t Skipping \"middle\" point %d, too close from last added point or end point %d\n",
1321                                        i, end_stroke_idx);
1322                         }
1323                 }
1324         }
1325 }
1326
1327 static void gp_stroke_path_animation(bContext *C, ReportList *reports, Curve *cu, tGpTimingData *gtd)
1328 {
1329         Scene *scene = CTX_data_scene(C);
1330         bAction *act;
1331         FCurve *fcu;
1332         PointerRNA ptr;
1333         PropertyRNA *prop = NULL;
1334         int nbr_gaps = 0, i;
1335
1336         if (gtd->mode == GP_STROKECONVERT_TIMING_NONE)
1337                 return;
1338
1339         /* gap_duration and gap_randomness are in frames, but we need seconds!!! */
1340         gtd->gap_duration = FRA2TIME(gtd->gap_duration);
1341         gtd->gap_randomness = FRA2TIME(gtd->gap_randomness);
1342
1343         /* Enable path! */
1344         cu->flag |= CU_PATH;
1345         cu->pathlen = gtd->frame_range;
1346
1347         /* Get RNA pointer to read/write path time values */
1348         RNA_id_pointer_create((ID *)cu, &ptr);
1349         prop = RNA_struct_find_property(&ptr, "eval_time");
1350
1351         /* Ensure we have an F-Curve to add keyframes to */
1352         act = verify_adt_action((ID *)cu, true);
1353         fcu = verify_fcurve(act, NULL, &ptr, "eval_time", 0, true);
1354
1355         if (G.debug & G_DEBUG) {
1356                 printf("%s: tot len: %f\t\ttot time: %f\n", __func__, gtd->tot_dist, gtd->tot_time);
1357                 for (i = 0; i < gtd->num_points; i++) {
1358                         printf("\tpoint %d:\t\tlen: %f\t\ttime: %f\n", i, gtd->dists[i], gtd->times[i]);
1359                 }
1360         }
1361
1362         if (gtd->mode == GP_STROKECONVERT_TIMING_LINEAR) {
1363                 float cfra;
1364
1365                 /* Linear extrapolation! */
1366                 fcu->extend = FCURVE_EXTRAPOLATE_LINEAR;
1367
1368                 cu->ctime = 0.0f;
1369                 cfra = (float)gtd->start_frame;
1370                 insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
1371
1372                 cu->ctime = cu->pathlen;
1373                 if (gtd->realtime) {
1374                         cfra += (float)TIME2FRA(gtd->tot_time); /* Seconds to frames */
1375                 }
1376                 else {
1377                         cfra = (float)gtd->end_frame;
1378                 }
1379                 insert_keyframe_direct(reports, ptr, prop, fcu, cfra, INSERTKEY_FAST);
1380         }
1381         else {
1382                 /* Use actual recorded timing! */
1383                 RNG *rng = BLI_rng_new(0);
1384                 float time_range;
1385
1386                 /* CustomGaps specific */
1387                 float tot_gaps_time = 0.0f;
1388
1389                 /* Pre-process gaps, in case we don't want to keep their original timing */
1390                 if (gtd->mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
1391                         gp_stroke_path_animation_preprocess_gaps(gtd, rng, &nbr_gaps, &tot_gaps_time);
1392                 }
1393
1394                 if (gtd->realtime) {
1395                         time_range = (float)TIME2FRA(gtd->tot_time); /* Seconds to frames */
1396                 }
1397                 else {
1398                         time_range = (float)(gtd->end_frame - gtd->start_frame);
1399                 }
1400
1401                 if (G.debug & G_DEBUG) {
1402                         printf("GP Stroke Path Conversion: Starting keying!\n");
1403                 }
1404
1405                 gp_stroke_path_animation_add_keyframes(reports, ptr, prop, fcu, cu, gtd, rng, time_range,
1406                                                        nbr_gaps, tot_gaps_time);
1407
1408                 BLI_rng_free(rng);
1409         }
1410
1411         /* As we used INSERTKEY_FAST mode, we need to recompute all curve's handles now */
1412         calchandles_fcurve(fcu);
1413
1414         if (G.debug & G_DEBUG) {
1415                 printf("%s: \ntot len: %f\t\ttot time: %f\n", __func__, gtd->tot_dist, gtd->tot_time);
1416                 for (i = 0; i < gtd->num_points; i++) {
1417                         printf("\tpoint %d:\t\tlen: %f\t\ttime: %f\n", i, gtd->dists[i], gtd->times[i]);
1418                 }
1419                 printf("\n\n");
1420         }
1421
1422         WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
1423
1424         /* send updates */
1425         DAG_id_tag_update(&cu->id, 0);
1426 }
1427
1428 #undef MIN_TIME_DELTA
1429
1430 #define GAP_DFAC 0.01f
1431 #define WIDTH_CORR_FAC 0.1f
1432 #define BEZT_HANDLE_FAC 0.3f
1433
1434 /* convert stroke to 3d path */
1435
1436 /* helper */
1437 static void gp_stroke_to_path_add_point(tGpTimingData *gtd, BPoint *bp, const float p[3], const float prev_p[3],
1438                                         const bool do_gtd, const double inittime, const float time,
1439                                         const float width, const float rad_fac, float minmax_weights[2])
1440 {
1441         copy_v3_v3(bp->vec, p);
1442         bp->vec[3] = 1.0f;
1443
1444         /* set settings */
1445         bp->f1 = SELECT;
1446         bp->radius = width * rad_fac;
1447         bp->weight = width;
1448         CLAMP(bp->weight, 0.0f, 1.0f);
1449         if (bp->weight < minmax_weights[0]) {
1450                 minmax_weights[0] = bp->weight;
1451         }
1452         else if (bp->weight > minmax_weights[1]) {
1453                 minmax_weights[1] = bp->weight;
1454         }
1455
1456         /* Update timing data */
1457         if (do_gtd) {
1458                 gp_timing_data_add_point(gtd, inittime, time, len_v3v3(prev_p, p));
1459         }
1460 }
1461
1462 static void gp_stroke_to_path(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect, Nurb **curnu,
1463                               float minmax_weights[2], const float rad_fac, bool stitch, const bool add_start_point,
1464                               const bool add_end_point, tGpTimingData *gtd)
1465 {
1466         bGPDspoint *pt;
1467         Nurb *nu = (curnu) ? *curnu : NULL;
1468         BPoint *bp, *prev_bp = NULL;
1469         const bool do_gtd = (gtd->mode != GP_STROKECONVERT_TIMING_NONE);
1470         const int add_start_end_points = (add_start_point ? 1 : 0) + (add_end_point ? 1 : 0);
1471         int i, old_nbp = 0;
1472
1473         /* create new 'nurb' or extend current one within the curve */
1474         if (nu) {
1475                 old_nbp = nu->pntsu;
1476
1477                 /* If stitch, the first point of this stroke is already present in current nu.
1478                  * Else, we have to add two additional points to make the zero-radius link between strokes.
1479                  */
1480                 BKE_nurb_points_add(nu, gps->totpoints + (stitch ? -1 : 2) + add_start_end_points);
1481         }
1482         else {
1483                 nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_path(nurb)");
1484
1485                 nu->pntsu = gps->totpoints + add_start_end_points;
1486                 nu->pntsv = 1;
1487                 nu->orderu = 2; /* point-to-point! */
1488                 nu->type = CU_NURBS;
1489                 nu->flagu = CU_NURB_ENDPOINT;
1490                 nu->resolu = cu->resolu;
1491                 nu->resolv = cu->resolv;
1492                 nu->knotsu = NULL;
1493
1494                 nu->bp = (BPoint *)MEM_callocN(sizeof(BPoint) * nu->pntsu, "bpoints");
1495
1496                 stitch = false; /* Security! */
1497         }
1498
1499         if (do_gtd) {
1500                 gp_timing_data_set_nbr(gtd, nu->pntsu);
1501         }
1502
1503         /* If needed, make the link between both strokes with two zero-radius additional points */
1504         /* About "zero-radius" point interpolations:
1505          * - If we have at least two points in current curve (most common case), we linearly extrapolate
1506          *   the last segment to get the first point (p1) position and timing.
1507          * - If we do not have those (quite odd, but may happen), we linearly interpolate the last point
1508          *   with the first point of the current stroke.
1509          * The same goes for the second point, first segment of the current stroke is "negatively" extrapolated
1510          * if it exists, else (if the stroke is a single point), linear interpolation with last curve point...
1511          */
1512         if (curnu && !stitch && old_nbp) {
1513                 float p1[3], p2[3], p[3], next_p[3];
1514                 float dt1 = 0.0f, dt2 = 0.0f;
1515
1516                 BLI_assert(gps->prev != NULL);
1517
1518                 prev_bp = NULL;
1519                 if ((old_nbp > 1) && (gps->prev->totpoints > 1)) {
1520                         /* Only use last curve segment if previous stroke was not a single-point one! */
1521                         prev_bp = &nu->bp[old_nbp - 2];
1522                 }
1523                 bp = &nu->bp[old_nbp - 1];
1524
1525                 /* First point */
1526                 gp_strokepoint_convertcoords(C, gps, gps->points, p, subrect);
1527                 if (prev_bp) {
1528                         interp_v3_v3v3(p1, bp->vec, prev_bp->vec, -GAP_DFAC);
1529                         if (do_gtd) {
1530                                 const int idx = gps->prev->totpoints - 1;
1531                                 dt1 = interpf(gps->prev->points[idx - 1].time, gps->prev->points[idx].time, -GAP_DFAC);
1532                         }
1533                 }
1534                 else {
1535                         interp_v3_v3v3(p1, bp->vec, p, GAP_DFAC);
1536                         if (do_gtd) {
1537                                 dt1 = interpf(gps->inittime - gps->prev->inittime, 0.0f, GAP_DFAC);
1538                         }
1539                 }
1540                 bp++;
1541                 gp_stroke_to_path_add_point(gtd, bp, p1, (bp - 1)->vec, do_gtd, gps->prev->inittime, dt1,
1542                                             0.0f, rad_fac, minmax_weights);
1543
1544                 /* Second point */
1545                 /* Note dt2 is always negative, which marks the gap. */
1546                 if (gps->totpoints > 1) {
1547                         gp_strokepoint_convertcoords(C, gps, gps->points + 1, next_p, subrect);
1548                         interp_v3_v3v3(p2, p, next_p, -GAP_DFAC);
1549                         if (do_gtd) {
1550                                 dt2 = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC);
1551                         }
1552                 }
1553                 else {
1554                         interp_v3_v3v3(p2, p, bp->vec, GAP_DFAC);
1555                         if (do_gtd) {
1556                                 dt2 = interpf(gps->prev->inittime - gps->inittime, 0.0f, GAP_DFAC);
1557                         }
1558                 }
1559                 bp++;
1560                 gp_stroke_to_path_add_point(gtd, bp, p2, p1, do_gtd, gps->inittime, dt2, 0.0f, rad_fac, minmax_weights);
1561
1562                 old_nbp += 2;
1563         }
1564         else if (add_start_point) {
1565                 float p[3], next_p[3];
1566                 float dt = 0.0f;
1567
1568                 gp_strokepoint_convertcoords(C, gps, gps->points, p, subrect);
1569                 if (gps->totpoints > 1) {
1570                         gp_strokepoint_convertcoords(C, gps, gps->points + 1, next_p, subrect);
1571                         interp_v3_v3v3(p, p, next_p, -GAP_DFAC);
1572                         if (do_gtd) {
1573                                 dt = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC);
1574                         }
1575                 }
1576                 else {
1577                         p[0] -= GAP_DFAC;  /* Rather arbitrary... */
1578                         dt = -GAP_DFAC;  /* Rather arbitrary too! */
1579                 }
1580                 bp = &nu->bp[old_nbp];
1581                 /* Note we can't give anything else than 0.0 as time here, since a negative one (which would be expected value)
1582                  * would not work (it would be *before* gtd->inittime, which is not supported currently).
1583                  */
1584                 gp_stroke_to_path_add_point(gtd, bp, p, p, do_gtd, gps->inittime, dt, 0.0f, rad_fac, minmax_weights);
1585
1586                 old_nbp++;
1587         }
1588
1589         if (old_nbp) {
1590                 prev_bp = &nu->bp[old_nbp - 1];
1591         }
1592
1593         /* add points */
1594         for (i = (stitch) ? 1 : 0, pt = &gps->points[(stitch) ? 1 : 0], bp = &nu->bp[old_nbp];
1595              i < gps->totpoints;
1596              i++, pt++, bp++)
1597         {
1598                 float p[3];
1599                 float width = pt->pressure * gpl->thickness * WIDTH_CORR_FAC;
1600
1601                 /* get coordinates to add at */
1602                 gp_strokepoint_convertcoords(C, gps, pt, p, subrect);
1603
1604                 gp_stroke_to_path_add_point(gtd, bp, p, (prev_bp) ? prev_bp->vec : p, do_gtd, gps->inittime, pt->time,
1605                                             width, rad_fac, minmax_weights);
1606
1607                 prev_bp = bp;
1608         }
1609
1610         if (add_end_point) {
1611                 float p[3];
1612                 float dt = 0.0f;
1613
1614                 if (gps->totpoints > 1) {
1615                         interp_v3_v3v3(p, prev_bp->vec, (prev_bp - 1)->vec, -GAP_DFAC);
1616                         if (do_gtd) {
1617                                 const int idx = gps->totpoints - 1;
1618                                 dt = interpf(gps->points[idx - 1].time, gps->points[idx].time, -GAP_DFAC);
1619                         }
1620                 }
1621                 else {
1622                         copy_v3_v3(p, prev_bp->vec);
1623                         p[0] += GAP_DFAC;  /* Rather arbitrary... */
1624                         dt = GAP_DFAC;  /* Rather arbitrary too! */
1625                 }
1626                 /* Note bp has already been incremented in main loop above, so it points to the right place. */
1627                 gp_stroke_to_path_add_point(gtd, bp, p, prev_bp->vec, do_gtd, gps->inittime, dt, 0.0f, rad_fac, minmax_weights);
1628         }
1629
1630         /* add nurb to curve */
1631         if (!curnu || !*curnu) {
1632                 BLI_addtail(&cu->nurb, nu);
1633         }
1634         if (curnu) {
1635                 *curnu = nu;
1636         }
1637
1638         BKE_nurb_knot_calc_u(nu);
1639 }
1640
1641 /* convert stroke to 3d bezier */
1642
1643 /* helper */
1644 static void gp_stroke_to_bezier_add_point(tGpTimingData *gtd, BezTriple *bezt,
1645                                           const float p[3], const float h1[3], const float h2[3], const float prev_p[3],
1646                                           const bool do_gtd, const double inittime, const float time,
1647                                           const float width, const float rad_fac, float minmax_weights[2])
1648 {
1649         copy_v3_v3(bezt->vec[0], h1);
1650         copy_v3_v3(bezt->vec[1], p);
1651         copy_v3_v3(bezt->vec[2], h2);
1652
1653         /* set settings */
1654         bezt->h1 = bezt->h2 = HD_FREE;
1655         bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
1656         bezt->radius = width * rad_fac;
1657         bezt->weight = width;
1658         CLAMP(bezt->weight, 0.0f, 1.0f);
1659         if (bezt->weight < minmax_weights[0]) {
1660                 minmax_weights[0] = bezt->weight;
1661         }
1662         else if (bezt->weight > minmax_weights[1]) {
1663                 minmax_weights[1] = bezt->weight;
1664         }
1665
1666         /* Update timing data */
1667         if (do_gtd) {
1668                 gp_timing_data_add_point(gtd, inittime, time, len_v3v3(prev_p, p));
1669         }
1670 }
1671
1672 static void gp_stroke_to_bezier(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect, Nurb **curnu,
1673                               float minmax_weights[2], const float rad_fac, bool stitch, const bool add_start_point,
1674                               const bool add_end_point, tGpTimingData *gtd)
1675 {
1676         bGPDspoint *pt;
1677         Nurb *nu = (curnu) ? *curnu : NULL;
1678         BezTriple *bezt, *prev_bezt = NULL;
1679         int i, tot, old_nbezt = 0;
1680         const int add_start_end_points = (add_start_point ? 1 : 0) + (add_end_point ? 1 : 0);
1681         float p3d_cur[3], p3d_prev[3], p3d_next[3], h1[3], h2[3];
1682         const bool do_gtd = (gtd->mode != GP_STROKECONVERT_TIMING_NONE);
1683
1684         /* create new 'nurb' or extend current one within the curve */
1685         if (nu) {
1686                 old_nbezt = nu->pntsu;
1687                 /* If we do stitch, first point of current stroke is assumed the same as last point of previous stroke,
1688                  * so no need to add it.
1689                  * If no stitch, we want to add two additional points to make a "zero-radius" link between both strokes.
1690                  */
1691                 BKE_nurb_bezierPoints_add(nu, gps->totpoints + ((stitch) ? -1 : 2) + add_start_end_points);
1692         }
1693         else {
1694                 nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_bezier(nurb)");
1695
1696                 nu->pntsu = gps->totpoints + add_start_end_points;
1697                 nu->resolu = 12;
1698                 nu->resolv = 12;
1699                 nu->type = CU_BEZIER;
1700                 nu->bezt = (BezTriple *)MEM_callocN(sizeof(BezTriple) * nu->pntsu, "bezts");
1701
1702                 stitch = false; /* Security! */
1703         }
1704
1705         if (do_gtd) {
1706                 gp_timing_data_set_nbr(gtd, nu->pntsu);
1707         }
1708
1709         tot = gps->totpoints;
1710
1711         /* get initial coordinates */
1712         pt = gps->points;
1713         if (tot) {
1714                 gp_strokepoint_convertcoords(C, gps, pt, (stitch) ? p3d_prev : p3d_cur, subrect);
1715                 if (tot > 1) {
1716                         gp_strokepoint_convertcoords(C, gps, pt + 1, (stitch) ? p3d_cur : p3d_next, subrect);
1717                 }
1718                 if (stitch && tot > 2) {
1719                         gp_strokepoint_convertcoords(C, gps, pt + 2, p3d_next, subrect);
1720                 }
1721         }
1722
1723         /* If needed, make the link between both strokes with two zero-radius additional points */
1724         if (curnu && old_nbezt) {
1725                 BLI_assert(gps->prev != NULL);
1726
1727                 /* Update last point's second handle */
1728                 if (stitch) {
1729                         bezt = &nu->bezt[old_nbezt - 1];
1730                         interp_v3_v3v3(h2, bezt->vec[1], p3d_cur, BEZT_HANDLE_FAC);
1731                         copy_v3_v3(bezt->vec[2], h2);
1732                         pt++;
1733                 }
1734
1735                 /* Create "link points" */
1736                 /* About "zero-radius" point interpolations:
1737                  * - If we have at least two points in current curve (most common case), we linearly extrapolate
1738                  *   the last segment to get the first point (p1) position and timing.
1739                  * - If we do not have those (quite odd, but may happen), we linearly interpolate the last point
1740                  *   with the first point of the current stroke.
1741                  * The same goes for the second point, first segment of the current stroke is "negatively" extrapolated
1742                  * if it exists, else (if the stroke is a single point), linear interpolation with last curve point...
1743                  */
1744                 else {
1745                         float p1[3], p2[3];
1746                         float dt1 = 0.0f, dt2 = 0.0f;
1747
1748                         prev_bezt = NULL;
1749                         if ((old_nbezt > 1) && (gps->prev->totpoints > 1)) {
1750                                 /* Only use last curve segment if previous stroke was not a single-point one! */
1751                                 prev_bezt = &nu->bezt[old_nbezt - 2];
1752                         }
1753                         bezt = &nu->bezt[old_nbezt - 1];
1754
1755                         /* First point */
1756                         if (prev_bezt) {
1757                                 interp_v3_v3v3(p1, prev_bezt->vec[1], bezt->vec[1], 1.0f + GAP_DFAC);
1758                                 if (do_gtd) {
1759                                         const int idx = gps->prev->totpoints - 1;
1760                                         dt1 = interpf(gps->prev->points[idx - 1].time, gps->prev->points[idx].time, -GAP_DFAC);
1761                                 }
1762                         }
1763                         else {
1764                                 interp_v3_v3v3(p1, bezt->vec[1], p3d_cur, GAP_DFAC);
1765                                 if (do_gtd) {
1766                                         dt1 = interpf(gps->inittime - gps->prev->inittime, 0.0f, GAP_DFAC);
1767                                 }
1768                         }
1769
1770                         /* Second point */
1771                         /* Note dt2 is always negative, which marks the gap. */
1772                         if (tot > 1) {
1773                                 interp_v3_v3v3(p2, p3d_cur, p3d_next, -GAP_DFAC);
1774                                 if (do_gtd) {
1775                                         dt2 = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC);
1776                                 }
1777                         }
1778                         else {
1779                                 interp_v3_v3v3(p2, p3d_cur, bezt->vec[1], GAP_DFAC);
1780                                 if (do_gtd) {
1781                                         dt2 = interpf(gps->prev->inittime - gps->inittime, 0.0f, GAP_DFAC);
1782                                 }
1783                         }
1784
1785                         /* Second handle of last point of previous stroke. */
1786                         interp_v3_v3v3(h2, bezt->vec[1], p1, BEZT_HANDLE_FAC);
1787                         copy_v3_v3(bezt->vec[2], h2);
1788
1789                         /* First point */
1790                         interp_v3_v3v3(h1, p1, bezt->vec[1], BEZT_HANDLE_FAC);
1791                         interp_v3_v3v3(h2, p1, p2, BEZT_HANDLE_FAC);
1792                         bezt++;
1793                         gp_stroke_to_bezier_add_point(gtd, bezt, p1, h1, h2, (bezt - 1)->vec[1], do_gtd, gps->prev->inittime, dt1,
1794                                                       0.0f, rad_fac, minmax_weights);
1795
1796                         /* Second point */
1797                         interp_v3_v3v3(h1, p2, p1, BEZT_HANDLE_FAC);
1798                         interp_v3_v3v3(h2, p2, p3d_cur, BEZT_HANDLE_FAC);
1799                         bezt++;
1800                         gp_stroke_to_bezier_add_point(gtd, bezt, p2, h1, h2, p1, do_gtd, gps->inittime, dt2,
1801                                                       0.0f, rad_fac, minmax_weights);
1802
1803                         old_nbezt += 2;
1804                         copy_v3_v3(p3d_prev, p2);
1805                 }
1806         }
1807         else if (add_start_point) {
1808                 float p[3];
1809                 float dt = 0.0f;
1810
1811                 if (gps->totpoints > 1) {
1812                         interp_v3_v3v3(p, p3d_cur, p3d_next, -GAP_DFAC);
1813                         if (do_gtd) {
1814                                 dt = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC);
1815                         }
1816                 }
1817                 else {
1818                         copy_v3_v3(p, p3d_cur);
1819                         p[0] -= GAP_DFAC;  /* Rather arbitrary... */
1820                         dt = -GAP_DFAC;  /* Rather arbitrary too! */
1821                 }
1822                 interp_v3_v3v3(h1, p, p3d_cur, -BEZT_HANDLE_FAC);
1823                 interp_v3_v3v3(h2, p, p3d_cur, BEZT_HANDLE_FAC);
1824                 bezt = &nu->bezt[old_nbezt];
1825                 gp_stroke_to_bezier_add_point(gtd, bezt, p, h1, h2, p, do_gtd, gps->inittime, dt,
1826                                               0.0f, rad_fac, minmax_weights);
1827
1828                 old_nbezt++;
1829                 copy_v3_v3(p3d_prev, p);
1830         }
1831
1832         if (old_nbezt) {
1833                 prev_bezt = &nu->bezt[old_nbezt - 1];
1834         }
1835
1836         /* add points */
1837         for (i = stitch ? 1 : 0, bezt = &nu->bezt[old_nbezt]; i < tot; i++, pt++, bezt++) {
1838                 float width = pt->pressure * gpl->thickness * WIDTH_CORR_FAC;
1839
1840                 if (i || old_nbezt) {
1841                         interp_v3_v3v3(h1, p3d_cur, p3d_prev, BEZT_HANDLE_FAC);
1842                 }
1843                 else {
1844                         interp_v3_v3v3(h1, p3d_cur, p3d_next, -BEZT_HANDLE_FAC);
1845                 }
1846
1847                 if (i < tot - 1) {
1848                         interp_v3_v3v3(h2, p3d_cur, p3d_next, BEZT_HANDLE_FAC);
1849                 }
1850                 else {
1851                         interp_v3_v3v3(h2, p3d_cur, p3d_prev, -BEZT_HANDLE_FAC);
1852                 }
1853
1854                 gp_stroke_to_bezier_add_point(gtd, bezt, p3d_cur, h1, h2, prev_bezt ? prev_bezt->vec[1] : p3d_cur,
1855                                               do_gtd, gps->inittime, pt->time, width, rad_fac, minmax_weights);
1856
1857                 /* shift coord vects */
1858                 copy_v3_v3(p3d_prev, p3d_cur);
1859                 copy_v3_v3(p3d_cur, p3d_next);
1860
1861                 if (i + 2 < tot) {
1862                         gp_strokepoint_convertcoords(C, gps, pt + 2, p3d_next, subrect);
1863                 }
1864
1865                 prev_bezt = bezt;
1866         }
1867
1868         if (add_end_point) {
1869                 float p[3];
1870                 float dt = 0.0f;
1871
1872                 if (gps->totpoints > 1) {
1873                         interp_v3_v3v3(p, prev_bezt->vec[1], (prev_bezt - 1)->vec[1], -GAP_DFAC);
1874                         if (do_gtd) {
1875                                 const int idx = gps->totpoints - 1;
1876                                 dt = interpf(gps->points[idx - 1].time, gps->points[idx].time, -GAP_DFAC);
1877                         }
1878                 }
1879                 else {
1880                         copy_v3_v3(p, prev_bezt->vec[1]);
1881                         p[0] += GAP_DFAC;  /* Rather arbitrary... */
1882                         dt = GAP_DFAC;  /* Rather arbitrary too! */
1883                 }
1884
1885                 /* Second handle of last point of this stroke. */
1886                 interp_v3_v3v3(h2, prev_bezt->vec[1], p, BEZT_HANDLE_FAC);
1887                 copy_v3_v3(prev_bezt->vec[2], h2);
1888
1889                 /* The end point */
1890                 interp_v3_v3v3(h1, p, prev_bezt->vec[1], BEZT_HANDLE_FAC);
1891                 interp_v3_v3v3(h2, p, prev_bezt->vec[1], -BEZT_HANDLE_FAC);
1892                 /* Note bezt has already been incremented in main loop above, so it points to the right place. */
1893                 gp_stroke_to_bezier_add_point(gtd, bezt, p, h1, h2, prev_bezt->vec[1], do_gtd, gps->inittime, dt,
1894                                               0.0f, rad_fac, minmax_weights);
1895         }
1896
1897         /* must calculate handles or else we crash */
1898         BKE_nurb_handles_calc(nu);
1899
1900         if (!curnu || !*curnu) {
1901                 BLI_addtail(&cu->nurb, nu);
1902         }
1903         if (curnu) {
1904                 *curnu = nu;
1905         }
1906 }
1907
1908 #undef GAP_DFAC
1909 #undef WIDTH_CORR_FAC
1910 #undef BEZT_HANDLE_FAC
1911
1912 static void gp_stroke_finalize_curve_endpoints(Curve *cu)
1913 {
1914         /* start */
1915         Nurb *nu = cu->nurb.first;
1916         int i = 0;
1917         if (nu->bezt) {
1918                 BezTriple *bezt = nu->bezt;
1919                 if (bezt) {
1920                         bezt[i].weight = bezt[i].radius = 0.0f;
1921                 }
1922         }
1923         else if (nu->bp) {
1924                 BPoint *bp = nu->bp;
1925                 if (bp) {
1926                         bp[i].weight = bp[i].radius = 0.0f;
1927                 }
1928         }
1929
1930         /* end */
1931         nu = cu->nurb.last;
1932         i = nu->pntsu - 1;
1933         if (nu->bezt) {
1934                 BezTriple *bezt = nu->bezt;
1935                 if (bezt) {
1936                         bezt[i].weight = bezt[i].radius = 0.0f;
1937                 }
1938         }
1939         else if (nu->bp) {
1940                 BPoint *bp = nu->bp;
1941                 if (bp) {
1942                         bp[i].weight = bp[i].radius = 0.0f;
1943                 }
1944         }
1945 }
1946
1947 static void gp_stroke_norm_curve_weights(Curve *cu, const float minmax_weights[2])
1948 {
1949         Nurb *nu;
1950         const float delta = minmax_weights[0];
1951         float fac;
1952         int i;
1953
1954         /* when delta == minmax_weights[0] == minmax_weights[1], we get div by zero [#35686] */
1955         if (IS_EQF(delta, minmax_weights[1]))
1956                 fac = 1.0f;
1957         else
1958                 fac = 1.0f / (minmax_weights[1] - delta);
1959
1960         for (nu = cu->nurb.first; nu; nu = nu->next) {
1961                 if (nu->bezt) {
1962                         BezTriple *bezt = nu->bezt;
1963                         for (i = 0; i < nu->pntsu; i++, bezt++) {
1964                                 bezt->weight = (bezt->weight - delta) * fac;
1965                         }
1966                 }
1967                 else if (nu->bp) {
1968                         BPoint *bp = nu->bp;
1969                         for (i = 0; i < nu->pntsu; i++, bp++) {
1970                                 bp->weight = (bp->weight - delta) * fac;
1971                         }
1972                 }
1973         }
1974 }
1975
1976 static int gp_camera_view_subrect(bContext *C, rctf *subrect)
1977 {
1978         View3D *v3d = CTX_wm_view3d(C);
1979         ARegion *ar = CTX_wm_region(C);
1980
1981         if (v3d) {
1982                 RegionView3D *rv3d = ar->regiondata;
1983
1984                 /* for camera view set the subrect */
1985                 if (rv3d->persp == RV3D_CAMOB) {
1986                         Scene *scene = CTX_data_scene(C);
1987                         ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, subrect, true); /* no shift */
1988                         return 1;
1989                 }
1990         }
1991
1992         return 0;
1993 }
1994
1995 /* convert a given grease-pencil layer to a 3d-curve representation (using current view if appropriate) */
1996 static void gp_layer_to_curve(bContext *C, ReportList *reports, bGPdata *gpd, bGPDlayer *gpl, const int mode,
1997                               const bool norm_weights, const float rad_fac, const bool link_strokes, tGpTimingData *gtd)
1998 {
1999         struct Main *bmain = CTX_data_main(C);
2000         View3D *v3d = CTX_wm_view3d(C);  /* may be NULL */
2001         Scene *scene = CTX_data_scene(C);
2002         bGPDframe *gpf = gpencil_layer_getframe(gpl, CFRA, 0);
2003         bGPDstroke *gps, *prev_gps = NULL;
2004         Object *ob;
2005         Curve *cu;
2006         Nurb *nu = NULL;
2007         Base *base_orig = BASACT, *base_new = NULL;
2008         float minmax_weights[2] = {1.0f, 0.0f};
2009
2010         /* camera framing */
2011         rctf subrect, *subrect_ptr = NULL;
2012
2013         /* error checking */
2014         if (ELEM(NULL, gpd, gpl, gpf))
2015                 return;
2016
2017         /* only convert if there are any strokes on this layer's frame to convert */
2018         if (BLI_listbase_is_empty(&gpf->strokes))
2019                 return;
2020
2021         /* initialize camera framing */
2022         if (gp_camera_view_subrect(C, &subrect)) {
2023                 subrect_ptr = &subrect;
2024         }
2025
2026         /* init the curve object (remove rotation and get curve data from it)
2027          *      - must clear transforms set on object, as those skew our results
2028          */
2029         ob = BKE_object_add_only_object(bmain, OB_CURVE, gpl->info);
2030         cu = ob->data = BKE_curve_add(bmain, gpl->info, OB_CURVE);
2031         base_new = BKE_scene_base_add(scene, ob);
2032
2033         cu->flag |= CU_3D;
2034
2035         gtd->inittime = ((bGPDstroke *)gpf->strokes.first)->inittime;
2036
2037         /* add points to curve */
2038         for (gps = gpf->strokes.first; gps; gps = gps->next) {
2039                 const bool add_start_point = (link_strokes && !(prev_gps));
2040                 const bool add_end_point = (link_strokes && !(gps->next));
2041
2042                 /* Detect new strokes created because of GP_STROKE_BUFFER_MAX reached, and stitch them to previous one. */
2043                 bool stitch = false;
2044                 if (prev_gps) {
2045                         bGPDspoint *pt1 = &prev_gps->points[prev_gps->totpoints - 1];
2046                         bGPDspoint *pt2 = &gps->points[0];
2047
2048                         if ((pt1->x == pt2->x) && (pt1->y == pt2->y)) {
2049                                 stitch = true;
2050                         }
2051                 }
2052
2053                 /* Decide whether we connect this stroke to previous one */
2054                 if (!(stitch || link_strokes)) {
2055                         nu = NULL;
2056                 }
2057
2058                 switch (mode) {
2059                         case GP_STROKECONVERT_PATH: 
2060                                 gp_stroke_to_path(C, gpl, gps, cu, subrect_ptr, &nu, minmax_weights, rad_fac, stitch,
2061                                                   add_start_point, add_end_point, gtd);
2062                                 break;
2063                         case GP_STROKECONVERT_CURVE:
2064                         case GP_STROKECONVERT_POLY:  /* convert after */
2065                                 gp_stroke_to_bezier(C, gpl, gps, cu, subrect_ptr, &nu, minmax_weights, rad_fac, stitch,
2066                                                     add_start_point, add_end_point, gtd);
2067                                 break;
2068                         default:
2069                                 BLI_assert(!"invalid mode");
2070                                 break;
2071                 }
2072                 prev_gps = gps;
2073         }
2074
2075         /* If link_strokes, be sure first and last points have a zero weight/size! */
2076         if (link_strokes) {
2077                 gp_stroke_finalize_curve_endpoints(cu);
2078         }
2079
2080         /* Update curve's weights, if needed */
2081         if (norm_weights && ((minmax_weights[0] > 0.0f) || (minmax_weights[1] < 1.0f))) {
2082                 gp_stroke_norm_curve_weights(cu, minmax_weights);
2083         }
2084
2085         /* Create the path animation, if needed */
2086         gp_stroke_path_animation(C, reports, cu, gtd);
2087
2088         if (mode == GP_STROKECONVERT_POLY) {
2089                 for (nu = cu->nurb.first; nu; nu = nu->next) {
2090                         BKE_nurb_type_convert(nu, CU_POLY, false);
2091                 }
2092         }
2093
2094         /* set the layer and select */
2095         base_new->lay  = ob->lay  = base_orig ? base_orig->lay : BKE_screen_view3d_layer_active(v3d, scene);
2096         base_new->flag = ob->flag = base_new->flag | SELECT;
2097 }
2098
2099 /* --- */
2100
2101 /* Check a GP layer has valid timing data! Else, most timing options are hidden in the operator.
2102  * op may be NULL.
2103  */
2104 static bool gp_convert_check_has_valid_timing(bContext *C, bGPDlayer *gpl, wmOperator *op)
2105 {
2106         Scene *scene = CTX_data_scene(C);
2107         bGPDframe *gpf = NULL;
2108         bGPDstroke *gps = NULL;
2109         bGPDspoint *pt;
2110         double base_time, cur_time, prev_time = -1.0;
2111         int i;
2112         bool valid = true;
2113
2114         if (!gpl || !(gpf = gpencil_layer_getframe(gpl, CFRA, 0)) || !(gps = gpf->strokes.first))
2115                 return false;
2116
2117         do {
2118                 base_time = cur_time = gps->inittime;
2119                 if (cur_time <= prev_time) {
2120                         valid = false;
2121                         break;
2122                 }
2123
2124                 prev_time = cur_time;
2125                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
2126                         cur_time = base_time + (double)pt->time;
2127                         /* First point of a stroke should have the same time as stroke's inittime,
2128                          * so it's the only case where equality is allowed!
2129                          */
2130                         if ((i && cur_time <= prev_time) || (cur_time < prev_time)) {
2131                                 valid = false;
2132                                 break;
2133                         }
2134                         prev_time = cur_time;
2135                 }
2136
2137                 if (!valid) {
2138                         break;
2139                 }
2140         } while ((gps = gps->next));
2141
2142         if (op) {
2143                 RNA_boolean_set(op->ptr, "use_timing_data", valid);
2144         }
2145         return valid;
2146 }
2147
2148 /* Check end_frame is always > start frame! */
2149 static void gp_convert_set_end_frame(struct Main *UNUSED(main), struct Scene *UNUSED(scene), struct PointerRNA *ptr)
2150 {
2151         int start_frame = RNA_int_get(ptr, "start_frame");
2152         int end_frame = RNA_int_get(ptr, "end_frame");
2153
2154         if (end_frame <= start_frame) {
2155                 RNA_int_set(ptr, "end_frame", start_frame + 1);
2156         }
2157 }
2158
2159 static int gp_convert_poll(bContext *C)
2160 {
2161         bGPdata *gpd = ED_gpencil_data_get_active(C);
2162         bGPDlayer *gpl = NULL;
2163         bGPDframe *gpf = NULL;
2164         ScrArea *sa = CTX_wm_area(C);
2165         Scene *scene = CTX_data_scene(C);
2166
2167         /* only if the current view is 3D View, if there's valid data (i.e. at least one stroke!),
2168          * and if we are not in edit mode!
2169          */
2170         return ((sa && sa->spacetype == SPACE_VIEW3D) &&
2171                 (gpl = gpencil_layer_getactive(gpd)) &&
2172                 (gpf = gpencil_layer_getframe(gpl, CFRA, 0)) &&
2173                 (gpf->strokes.first) &&
2174                 (scene->obedit == NULL));
2175 }
2176
2177 static int gp_convert_layer_exec(bContext *C, wmOperator *op)
2178 {
2179         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_timing_data");
2180         bGPdata *gpd = ED_gpencil_data_get_active(C);
2181         bGPDlayer *gpl = gpencil_layer_getactive(gpd);
2182         Scene *scene = CTX_data_scene(C);
2183         const int mode = RNA_enum_get(op->ptr, "type");
2184         const bool norm_weights = RNA_boolean_get(op->ptr, "use_normalize_weights");
2185         const float rad_fac = RNA_float_get(op->ptr, "radius_multiplier");
2186         const bool link_strokes = RNA_boolean_get(op->ptr, "use_link_strokes");
2187         bool valid_timing;
2188         tGpTimingData gtd;
2189
2190         /* check if there's data to work with */
2191         if (gpd == NULL) {
2192                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data to work on");
2193                 return OPERATOR_CANCELLED;
2194         }
2195
2196         if (!RNA_property_is_set(op->ptr, prop) && !gp_convert_check_has_valid_timing(C, gpl, op)) {
2197                 BKE_report(op->reports, RPT_WARNING,
2198                            "Current Grease Pencil strokes have no valid timing data, most timing options will be hidden!");
2199         }
2200         valid_timing = RNA_property_boolean_get(op->ptr, prop);
2201
2202         gtd.mode = RNA_enum_get(op->ptr, "timing_mode");
2203         /* Check for illegal timing mode! */
2204         if (!valid_timing && !ELEM(gtd.mode, GP_STROKECONVERT_TIMING_NONE, GP_STROKECONVERT_TIMING_LINEAR)) {
2205                 gtd.mode = GP_STROKECONVERT_TIMING_LINEAR;
2206                 RNA_enum_set(op->ptr, "timing_mode", gtd.mode);
2207         }
2208         if (!link_strokes) {
2209                 gtd.mode = GP_STROKECONVERT_TIMING_NONE;
2210         }
2211
2212         /* grab all relevant settings */
2213         gtd.frame_range = RNA_int_get(op->ptr, "frame_range");
2214         gtd.start_frame = RNA_int_get(op->ptr, "start_frame");
2215         gtd.realtime = valid_timing ? RNA_boolean_get(op->ptr, "use_realtime") : false;
2216         gtd.end_frame = RNA_int_get(op->ptr, "end_frame");
2217         gtd.gap_duration = RNA_float_get(op->ptr, "gap_duration");
2218         gtd.gap_randomness = RNA_float_get(op->ptr, "gap_randomness");
2219         gtd.gap_randomness = min_ff(gtd.gap_randomness, gtd.gap_duration);
2220         gtd.seed = RNA_int_get(op->ptr, "seed");
2221         gtd.num_points = gtd.cur_point = 0;
2222         gtd.dists = gtd.times = NULL;
2223         gtd.tot_dist = gtd.tot_time = gtd.gap_tot_time = 0.0f;
2224         gtd.inittime = 0.0;
2225         gtd.offset_time = 0.0f;
2226
2227         /* perform conversion */
2228         gp_layer_to_curve(C, op->reports, gpd, gpl, mode, norm_weights, rad_fac, link_strokes, &gtd);
2229
2230         /* free temp memory */
2231         if (gtd.dists) {
2232                 MEM_freeN(gtd.dists);
2233                 gtd.dists = NULL;
2234         }
2235         if (gtd.times) {
2236                 MEM_freeN(gtd.times);
2237                 gtd.times = NULL;
2238         }
2239
2240         /* notifiers */
2241         WM_event_add_notifier(C, NC_OBJECT | NA_ADDED, NULL);
2242         WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
2243
2244         /* done */
2245         return OPERATOR_FINISHED;
2246 }
2247
2248 static bool gp_convert_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
2249 {
2250         const char *prop_id = RNA_property_identifier(prop);
2251         const bool link_strokes = RNA_boolean_get(ptr, "use_link_strokes");
2252         int timing_mode = RNA_enum_get(ptr, "timing_mode");
2253         bool realtime = RNA_boolean_get(ptr, "use_realtime");
2254         float gap_duration = RNA_float_get(ptr, "gap_duration");
2255         float gap_randomness = RNA_float_get(ptr, "gap_randomness");
2256         const bool valid_timing = RNA_boolean_get(ptr, "use_timing_data");
2257
2258         /* Always show those props */
2259         if (strcmp(prop_id, "type") == 0 ||
2260             strcmp(prop_id, "use_normalize_weights") == 0 ||
2261             strcmp(prop_id, "radius_multiplier") == 0 ||
2262             strcmp(prop_id, "use_link_strokes") == 0)
2263         {
2264                 return true;
2265         }
2266
2267         /* Never show this prop */
2268         if (strcmp(prop_id, "use_timing_data") == 0)
2269                 return false;
2270
2271         if (link_strokes) {
2272                 /* Only show when link_stroke is true */
2273                 if (strcmp(prop_id, "timing_mode") == 0)
2274                         return true;
2275
2276                 if (timing_mode != GP_STROKECONVERT_TIMING_NONE) {
2277                         /* Only show when link_stroke is true and stroke timing is enabled */
2278                         if (strcmp(prop_id, "frame_range") == 0 ||
2279                             strcmp(prop_id, "start_frame") == 0)
2280                         {
2281                                 return true;
2282                         }
2283
2284                         /* Only show if we have valid timing data! */
2285                         if (valid_timing && strcmp(prop_id, "use_realtime") == 0)
2286                                 return true;
2287
2288                         /* Only show if realtime or valid_timing is false! */
2289                         if ((!realtime || !valid_timing) && strcmp(prop_id, "end_frame") == 0)
2290                                 return true;
2291
2292                         if (valid_timing && timing_mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
2293                                 /* Only show for custom gaps! */
2294                                 if (strcmp(prop_id, "gap_duration") == 0)
2295                                         return true;
2296
2297                                 /* Only show randomness for non-null custom gaps! */
2298                                 if (strcmp(prop_id, "gap_randomness") == 0 && (gap_duration > 0.0f))
2299                                         return true;
2300
2301                                 /* Only show seed for randomize action! */
2302                                 if (strcmp(prop_id, "seed") == 0 && (gap_duration > 0.0f) && (gap_randomness > 0.0f))
2303                                         return true;
2304                         }
2305                 }
2306         }
2307
2308         /* Else, hidden! */
2309         return false;
2310 }
2311
2312 static void gp_convert_ui(bContext *C, wmOperator *op)
2313 {
2314         uiLayout *layout = op->layout;
2315         wmWindowManager *wm = CTX_wm_manager(C);
2316         PointerRNA ptr;
2317
2318         RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
2319
2320         /* Main auto-draw call */
2321         uiDefAutoButsRNA(layout, &ptr, gp_convert_draw_check_prop, '\0');
2322 }
2323
2324 void GPENCIL_OT_convert(wmOperatorType *ot)
2325 {
2326         PropertyRNA *prop;
2327
2328         /* identifiers */
2329         ot->name = "Convert Grease Pencil";
2330         ot->idname = "GPENCIL_OT_convert";
2331         ot->description = "Convert the active Grease Pencil layer to a new Curve Object";
2332
2333         /* callbacks */
2334         ot->invoke = WM_menu_invoke;
2335         ot->exec = gp_convert_layer_exec;
2336         ot->poll = gp_convert_poll;
2337         ot->ui = gp_convert_ui;
2338
2339         /* flags */
2340         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2341
2342         /* properties */
2343         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_convertmodes, 0, "Type", "Which type of curve to convert to");
2344
2345         RNA_def_boolean(ot->srna, "use_normalize_weights", true, "Normalize Weight",
2346                         "Normalize weight (set from stroke width)");
2347         RNA_def_float(ot->srna, "radius_multiplier", 1.0f, 0.0f, 1000.0f, "Radius Fac",
2348                       "Multiplier for the points' radii (set from stroke width)", 0.0f, 10.0f);
2349         RNA_def_boolean(ot->srna, "use_link_strokes", true, "Link Strokes",
2350                         "Whether to link strokes with zero-radius sections of curves");
2351
2352         prop = RNA_def_enum(ot->srna, "timing_mode", prop_gpencil_convert_timingmodes, GP_STROKECONVERT_TIMING_FULL,
2353                             "Timing Mode", "How to use timing data stored in strokes");
2354         RNA_def_enum_funcs(prop, rna_GPConvert_mode_items);
2355
2356         RNA_def_int(ot->srna, "frame_range", 100, 1, 10000, "Frame Range",
2357                     "The duration of evaluation of the path control curve", 1, 1000);
2358         RNA_def_int(ot->srna, "start_frame", 1, 1, 100000, "Start Frame",
2359                     "The start frame of the path control curve", 1, 100000);
2360         RNA_def_boolean(ot->srna, "use_realtime", false, "Realtime",
2361                         "Whether the path control curve reproduces the drawing in realtime, starting from Start Frame");
2362         prop = RNA_def_int(ot->srna, "end_frame", 250, 1, 100000, "End Frame",
2363                            "The end frame of the path control curve (if Realtime is not set)", 1, 100000);
2364         RNA_def_property_update_runtime(prop, gp_convert_set_end_frame);
2365
2366         RNA_def_float(ot->srna, "gap_duration", 0.0f, 0.0f, 10000.0f, "Gap Duration",
2367                       "Custom Gap mode: (Average) length of gaps, in frames "
2368                       "(Note: Realtime value, will be scaled if Realtime is not set)", 0.0f, 1000.0f);
2369         RNA_def_float(ot->srna, "gap_randomness", 0.0f, 0.0f, 10000.0f, "Gap Randomness",
2370                       "Custom Gap mode: Number of frames that gap lengths can vary", 0.0f, 1000.0f);
2371         RNA_def_int(ot->srna, "seed", 0, 0, 1000, "Random Seed",
2372                     "Custom Gap mode: Random generator seed", 0, 100);
2373
2374         /* Note: Internal use, this one will always be hidden by UI code... */
2375         prop = RNA_def_boolean(ot->srna, "use_timing_data", false, "Has Valid Timing",
2376                                "Whether the converted Grease Pencil layer has valid timing data (internal use)");
2377         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
2378 }
2379
2380 /* ************************************************ */