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