Merge branch 'master' into blender2.8
[blender.git] / source / blender / editors / gpencil / gpencil_convert.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
19  * This is a new part of Blender
20  *
21  * Contributor(s): Joshua Leung
22  *                 Bastien Montagne
23  *
24  * ***** END GPL LICENSE BLOCK *****
25  *
26  * Operator for converting Grease Pencil data to geometry
27  */
28
29 /** \file blender/editors/gpencil/gpencil_convert.c
30  *  \ingroup edgpencil
31  */
32
33
34 #include <stdio.h>
35 #include <string.h>
36 #include <stdlib.h>
37 #include <stddef.h>
38 #include <math.h>
39
40 #include "MEM_guardedalloc.h"
41
42 #include "BLI_math.h"
43 #include "BLI_blenlib.h"
44 #include "BLI_rand.h"
45 #include "BLI_utildefines.h"
46
47 #include "BLT_translation.h"
48
49 #include "DNA_anim_types.h"
50 #include "DNA_curve_types.h"
51 #include "DNA_group_types.h"
52 #include "DNA_object_types.h"
53 #include "DNA_node_types.h"
54 #include "DNA_scene_types.h"
55 #include "DNA_screen_types.h"
56 #include "DNA_space_types.h"
57 #include "DNA_view3d_types.h"
58 #include "DNA_gpencil_types.h"
59 #include "DNA_workspace_types.h"
60
61 #include "BKE_collection.h"
62 #include "BKE_context.h"
63 #include "BKE_curve.h"
64 #include "BKE_fcurve.h"
65 #include "BKE_global.h"
66 #include "BKE_gpencil.h"
67 #include "BKE_layer.h"
68 #include "BKE_library.h"
69 #include "BKE_main.h"
70 #include "BKE_object.h"
71 #include "BKE_report.h"
72 #include "BKE_scene.h"
73 #include "BKE_screen.h"
74 #include "BKE_tracking.h"
75
76 #include "DEG_depsgraph.h"
77
78 #include "UI_interface.h"
79
80 #include "WM_api.h"
81 #include "WM_types.h"
82
83 #include "RNA_access.h"
84 #include "RNA_define.h"
85
86 #include "UI_resources.h"
87 #include "UI_view2d.h"
88
89 #include "ED_gpencil.h"
90 #include "ED_view3d.h"
91 #include "ED_clip.h"
92 #include "ED_keyframing.h"
93
94 #include "gpencil_intern.h"
95
96 /* ************************************************ */
97 /* Grease Pencil to Data Operator */
98
99 /* defines for possible modes */
100 enum {
101         GP_STROKECONVERT_PATH = 1,
102         GP_STROKECONVERT_CURVE,
103         GP_STROKECONVERT_POLY,
104 };
105
106 /* Defines for possible timing modes */
107 enum {
108         GP_STROKECONVERT_TIMING_NONE = 1,
109         GP_STROKECONVERT_TIMING_LINEAR = 2,
110         GP_STROKECONVERT_TIMING_FULL = 3,
111         GP_STROKECONVERT_TIMING_CUSTOMGAP = 4,
112 };
113
114 /* RNA enum define */
115 static const EnumPropertyItem prop_gpencil_convertmodes[] = {
116         {GP_STROKECONVERT_PATH, "PATH", ICON_CURVE_PATH, "Path", "Animation path"},
117         {GP_STROKECONVERT_CURVE, "CURVE", ICON_CURVE_BEZCURVE, "Bezier Curve", "Smooth Bezier curve"},
118         {GP_STROKECONVERT_POLY, "POLY", ICON_MESH_DATA, "Polygon Curve", "Bezier curve with straight-line segments (vector handles)"},
119         {0, NULL, 0, NULL, NULL}
120 };
121
122 static const EnumPropertyItem prop_gpencil_convert_timingmodes_restricted[] = {
123         {GP_STROKECONVERT_TIMING_NONE, "NONE", 0, "No Timing", "Ignore timing"},
124         {GP_STROKECONVERT_TIMING_LINEAR, "LINEAR", 0, "Linear", "Simple linear timing"},
125         {0, NULL, 0, NULL, NULL},
126 };
127
128 static const EnumPropertyItem prop_gpencil_convert_timingmodes[] = {
129         {GP_STROKECONVERT_TIMING_NONE, "NONE", 0, "No Timing", "Ignore timing"},
130         {GP_STROKECONVERT_TIMING_LINEAR, "LINEAR", 0, "Linear", "Simple linear timing"},
131         {GP_STROKECONVERT_TIMING_FULL, "FULL", 0, "Original", "Use the original timing, gaps included"},
132         {GP_STROKECONVERT_TIMING_CUSTOMGAP, "CUSTOMGAP", 0, "Custom Gaps",
133                                             "Use the original timing, but with custom gap lengths (in frames)"},
134         {0, NULL, 0, NULL, NULL},
135 };
136
137 static const EnumPropertyItem *rna_GPConvert_mode_items(bContext *UNUSED(C), PointerRNA *ptr, PropertyRNA *UNUSED(prop),
138                                                   bool *UNUSED(r_free))
139 {
140         if (RNA_boolean_get(ptr, "use_timing_data")) {
141                 return prop_gpencil_convert_timingmodes;
142         }
143         return prop_gpencil_convert_timingmodes_restricted;
144 }
145
146 /* --- */
147
148 /* convert the coordinates from the given stroke point into 3d-coordinates
149  *      - assumes that the active space is the 3D-View
150  */
151 static void gp_strokepoint_convertcoords(
152         bContext *C, bGPDlayer *gpl, bGPDstroke *gps, bGPDspoint *source_pt,
153         float p3d[3], const rctf *subrect)
154 {
155         Scene *scene = CTX_data_scene(C);
156         View3D *v3d = CTX_wm_view3d(C);
157         ARegion *ar = CTX_wm_region(C);
158         bGPDspoint mypt, *pt;
159
160         float diff_mat[4][4];
161         pt = &mypt;
162
163         /* calculate difference matrix if parent object */
164         if (gpl->parent == NULL) {
165                 copy_v3_v3(&pt->x, &source_pt->x);
166         }
167         else {
168                 /* apply parent transform */
169                 float fpt[3];
170                 ED_gpencil_parent_location(gpl, diff_mat);
171                 mul_v3_m4v3(fpt, diff_mat, &source_pt->x);
172                 copy_v3_v3(&pt->x, fpt);
173         }
174
175
176         if (gps->flag & GP_STROKE_3DSPACE) {
177                 /* directly use 3d-coordinates */
178                 copy_v3_v3(p3d, &pt->x);
179         }
180         else {
181                 const float *fp = ED_view3d_cursor3d_get(scene, v3d)->location;
182                 float mvalf[2];
183
184                 /* get screen coordinate */
185                 if (gps->flag & GP_STROKE_2DSPACE) {
186                         View2D *v2d = &ar->v2d;
187                         UI_view2d_view_to_region_fl(v2d, pt->x, pt->y, &mvalf[0], &mvalf[1]);
188                 }
189                 else {
190                         if (subrect) {
191                                 mvalf[0] = (((float)pt->x / 100.0f) * BLI_rctf_size_x(subrect)) + subrect->xmin;
192                                 mvalf[1] = (((float)pt->y / 100.0f) * BLI_rctf_size_y(subrect)) + subrect->ymin;
193                         }
194                         else {
195                                 mvalf[0] = (float)pt->x / 100.0f * ar->winx;
196                                 mvalf[1] = (float)pt->y / 100.0f * ar->winy;
197                         }
198                 }
199
200                 ED_view3d_win_to_3d(v3d, ar, fp, mvalf, p3d);
201         }
202 }
203
204 /* --- */
205
206 /* temp struct for gp_stroke_path_animation() */
207 typedef struct tGpTimingData {
208         /* Data set from operator settings */
209         int mode;
210         int frame_range; /* Number of frames evaluated for path animation */
211         int start_frame, end_frame;
212         bool realtime; /* Will overwrite end_frame in case of Original or CustomGap timing... */
213         float gap_duration, gap_randomness; /* To be used with CustomGap mode*/
214         int seed;
215
216         /* Data set from points, used to compute final timing FCurve */
217         int num_points, cur_point;
218
219         /* Distances */
220         float *dists;
221         float tot_dist;
222
223         /* Times */
224         float *times; /* Note: Gap times will be negative! */
225         float tot_time, gap_tot_time;
226         double inittime;
227
228         /* Only used during creation of dists & times lists. */
229         float offset_time;
230 } tGpTimingData;
231
232 /* Init point buffers for timing data.
233  * Note this assumes we only grow those arrays!
234  */
235 static void gp_timing_data_set_nbr(tGpTimingData *gtd, const int nbr)
236 {
237         float *tmp;
238
239         BLI_assert(nbr > gtd->num_points);
240
241         /* distances */
242         tmp = gtd->dists;
243         gtd->dists = MEM_callocN(sizeof(float) * nbr, __func__);
244         if (tmp) {
245                 memcpy(gtd->dists, tmp, sizeof(float) * gtd->num_points);
246                 MEM_freeN(tmp);
247         }
248
249         /* times */
250         tmp = gtd->times;
251         gtd->times = MEM_callocN(sizeof(float) * nbr, __func__);
252         if (tmp) {
253                 memcpy(gtd->times, tmp, sizeof(float) * gtd->num_points);
254                 MEM_freeN(tmp);
255         }
256
257         gtd->num_points = nbr;
258 }
259
260 /* add stroke point to timing buffers */
261 static void gp_timing_data_add_point(tGpTimingData *gtd, const double stroke_inittime, const float time,
262                                      const float delta_dist)
263 {
264         float delta_time = 0.0f;
265         const int cur_point = gtd->cur_point;
266
267         if (!cur_point) {
268                 /* Special case, first point, if time is not 0.0f we have to compensate! */
269                 gtd->offset_time = -time;
270                 gtd->times[cur_point] = 0.0f;
271         }
272         else if (time < 0.0f) {
273                 /* This is a gap, negative value! */
274                 gtd->times[cur_point] = -(((float)(stroke_inittime - gtd->inittime)) + time + gtd->offset_time);
275                 delta_time = -gtd->times[cur_point] - gtd->times[cur_point - 1];
276
277                 gtd->gap_tot_time += delta_time;
278         }
279         else {
280                 gtd->times[cur_point] = (((float)(stroke_inittime - gtd->inittime)) + time + gtd->offset_time);
281                 delta_time = gtd->times[cur_point] - fabsf(gtd->times[cur_point - 1]);
282         }
283
284         gtd->tot_time += delta_time;
285         gtd->tot_dist += delta_dist;
286         gtd->dists[cur_point] = gtd->tot_dist;
287
288         gtd->cur_point++;
289 }
290
291 /* In frames! Binary search for FCurve keys have a threshold of 0.01, so we can't set
292  * arbitrarily close points - this is esp. important with NoGaps mode!
293  */
294 #define MIN_TIME_DELTA 0.02f
295
296 /* Loop over next points to find the end of the stroke, and compute */
297 static int gp_find_end_of_stroke_idx(tGpTimingData *gtd, RNG *rng, const int idx, const int nbr_gaps,
298                                      int *nbr_done_gaps, const float tot_gaps_time, const float delta_time,
299                                      float *next_delta_time)
300 {
301         int j;
302
303         for (j = idx + 1; j < gtd->num_points; j++) {
304                 if (gtd->times[j] < 0) {
305                         gtd->times[j] = -gtd->times[j];
306                         if (gtd->mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
307                                 /* In this mode, gap time between this stroke and the next should be 0 currently...
308                                  * So we have to compute its final duration!
309                                  */
310                                 if (gtd->gap_randomness > 0.0f) {
311                                         /* We want gaps that are in gtd->gap_duration +/- gtd->gap_randomness range,
312                                          * and which sum to exactly tot_gaps_time...
313                                          */
314                                         int rem_gaps = nbr_gaps - (*nbr_done_gaps);
315                                         if (rem_gaps < 2) {
316                                                 /* Last gap, just give remaining time! */
317                                                 *next_delta_time = tot_gaps_time;
318                                         }
319                                         else {
320                                                 float delta, min, max;
321
322                                                 /* This code ensures that if the first gaps have been shorter than average gap_duration,
323                                                  * next gaps will tend to be longer (i.e. try to recover the lateness), and vice-versa!
324                                                  */
325                                                 delta = delta_time - (gtd->gap_duration * (*nbr_done_gaps));
326
327                                                 /* Clamp min between [-gap_randomness, 0.0], with lower delta giving higher min */
328                                                 min = -gtd->gap_randomness - delta;
329                                                 CLAMP(min, -gtd->gap_randomness, 0.0f);
330
331                                                 /* Clamp max between [0.0, gap_randomness], with lower delta giving higher max */
332                                                 max = gtd->gap_randomness - delta;
333                                                 CLAMP(max, 0.0f, gtd->gap_randomness);
334                                                 *next_delta_time += gtd->gap_duration + (BLI_rng_get_float(rng) * (max - min)) + min;
335                                         }
336                                 }
337                                 else {
338                                         *next_delta_time += gtd->gap_duration;
339                                 }
340                         }
341                         (*nbr_done_gaps)++;
342                         break;
343                 }
344         }
345
346         return j - 1;
347 }
348
349 static void gp_stroke_path_animation_preprocess_gaps(tGpTimingData *gtd, RNG *rng, int *nbr_gaps, float *tot_gaps_time)
350 {
351         int i;
352         float delta_time = 0.0f;
353
354         for (i = 0; i < gtd->num_points; i++) {
355                 if (gtd->times[i] < 0 && i) {
356                         (*nbr_gaps)++;
357                         gtd->times[i] = -gtd->times[i] - delta_time;
358                         delta_time += gtd->times[i] - gtd->times[i - 1];
359                         gtd->times[i] = -gtd->times[i - 1]; /* Temp marker, values *have* to be different! */
360                 }
361                 else {
362                         gtd->times[i] -= delta_time;
363                 }
364         }
365         gtd->tot_time -= delta_time;
366
367         *tot_gaps_time = (float)(*nbr_gaps) * gtd->gap_duration;
368         gtd->tot_time += *tot_gaps_time;
369         if (G.debug & G_DEBUG) {
370                 printf("%f, %f, %f, %d\n", gtd->tot_time, delta_time, *tot_gaps_time, *nbr_gaps);
371         }
372         if (gtd->gap_randomness > 0.0f) {
373                 BLI_rng_srandom(rng, gtd->seed);
374         }
375 }
376
377 static void gp_stroke_path_animation_add_keyframes(Depsgraph *depsgraph, ReportList *reports, PointerRNA ptr, PropertyRNA *prop, FCurve *fcu,
378                                                    Curve *cu, tGpTimingData *gtd, RNG *rng, const float time_range,
379                                                    const int nbr_gaps, const float tot_gaps_time)
380 {
381         /* Use actual recorded timing! */
382         const float time_start = (float)gtd->start_frame;
383
384         float last_valid_time = 0.0f;
385         int end_stroke_idx = -1, start_stroke_idx = 0;
386         float end_stroke_time = 0.0f;
387
388         /* CustomGaps specific */
389         float delta_time = 0.0f, next_delta_time = 0.0f;
390         int nbr_done_gaps = 0;
391
392         int i;
393         float cfra;
394
395         /* This is a bit tricky, as:
396          * - We can't add arbitrarily close points on FCurve (in time).
397          * - We *must* have all "caps" points of all strokes in FCurve, as much as possible!
398          */
399         for (i = 0; i < gtd->num_points; i++) {
400                 /* If new stroke... */
401                 if (i > end_stroke_idx) {
402                         start_stroke_idx = i;
403                         delta_time = next_delta_time;
404                         /* find end of that new stroke */
405                         end_stroke_idx = gp_find_end_of_stroke_idx(gtd, rng, i, nbr_gaps, &nbr_done_gaps,
406                                                                    tot_gaps_time, delta_time, &next_delta_time);
407                         /* This one should *never* be negative! */
408                         end_stroke_time = time_start + ((gtd->times[end_stroke_idx] + delta_time) / gtd->tot_time * time_range);
409                 }
410
411                 /* Simple proportional stuff... */
412                 cu->ctime = gtd->dists[i] / gtd->tot_dist * cu->pathlen;
413                 cfra = time_start + ((gtd->times[i] + delta_time) / gtd->tot_time * time_range);
414
415                 /* And now, the checks about timing... */
416                 if (i == start_stroke_idx) {
417                         /* If first point of a stroke, be sure it's enough ahead of last valid keyframe, and
418                          * that the end point of the stroke is far enough!
419                          * In case it is not, we keep the end point...
420                          * Note that with CustomGaps mode, this is here we set the actual gap timing!
421                          */
422                         if ((end_stroke_time - last_valid_time) > MIN_TIME_DELTA * 2) {
423                                 if ((cfra - last_valid_time) < MIN_TIME_DELTA) {
424                                         cfra = last_valid_time + MIN_TIME_DELTA;
425                                 }
426                                 insert_keyframe_direct(depsgraph, reports, ptr, prop, fcu, cfra, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST);
427                                 last_valid_time = cfra;
428                         }
429                         else if (G.debug & G_DEBUG) {
430                                 printf("\t Skipping start point %d, too close from end point %d\n", i, end_stroke_idx);
431                         }
432                 }
433                 else if (i == end_stroke_idx) {
434                         /* Always try to insert end point of a curve (should be safe enough, anyway...) */
435                         if ((cfra - last_valid_time) < MIN_TIME_DELTA) {
436                                 cfra = last_valid_time + MIN_TIME_DELTA;
437                         }
438                         insert_keyframe_direct(depsgraph, reports, ptr, prop, fcu, cfra, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST);
439                         last_valid_time = cfra;
440                 }
441                 else {
442                         /* Else ("middle" point), we only insert it if it's far enough from last keyframe,
443                          * and also far enough from (not yet added!) end_stroke keyframe!
444                          */
445                         if ((cfra - last_valid_time) > MIN_TIME_DELTA && (end_stroke_time - cfra) > MIN_TIME_DELTA) {
446                                 insert_keyframe_direct(depsgraph, reports, ptr, prop, fcu, cfra, BEZT_KEYTYPE_BREAKDOWN, INSERTKEY_FAST);
447                                 last_valid_time = cfra;
448                         }
449                         else if (G.debug & G_DEBUG) {
450                                 printf("\t Skipping \"middle\" point %d, too close from last added point or end point %d\n",
451                                        i, end_stroke_idx);
452                         }
453                 }
454         }
455 }
456
457 static void gp_stroke_path_animation(bContext *C, ReportList *reports, Curve *cu, tGpTimingData *gtd)
458 {
459         Depsgraph *depsgraph = CTX_data_depsgraph(C);
460         Main *bmain = CTX_data_main(C);
461         Scene *scene = CTX_data_scene(C);
462         bAction *act;
463         FCurve *fcu;
464         PointerRNA ptr;
465         PropertyRNA *prop = NULL;
466         int nbr_gaps = 0, i;
467
468         if (gtd->mode == GP_STROKECONVERT_TIMING_NONE)
469                 return;
470
471         /* gap_duration and gap_randomness are in frames, but we need seconds!!! */
472         gtd->gap_duration = FRA2TIME(gtd->gap_duration);
473         gtd->gap_randomness = FRA2TIME(gtd->gap_randomness);
474
475         /* Enable path! */
476         cu->flag |= CU_PATH;
477         cu->pathlen = gtd->frame_range;
478
479         /* Get RNA pointer to read/write path time values */
480         RNA_id_pointer_create((ID *)cu, &ptr);
481         prop = RNA_struct_find_property(&ptr, "eval_time");
482
483         /* Ensure we have an F-Curve to add keyframes to */
484         act = verify_adt_action(bmain, (ID *)cu, true);
485         fcu = verify_fcurve(act, NULL, &ptr, "eval_time", 0, true);
486
487         if (G.debug & G_DEBUG) {
488                 printf("%s: tot len: %f\t\ttot time: %f\n", __func__, gtd->tot_dist, gtd->tot_time);
489                 for (i = 0; i < gtd->num_points; i++) {
490                         printf("\tpoint %d:\t\tlen: %f\t\ttime: %f\n", i, gtd->dists[i], gtd->times[i]);
491                 }
492         }
493
494         if (gtd->mode == GP_STROKECONVERT_TIMING_LINEAR) {
495                 float cfra;
496
497                 /* Linear extrapolation! */
498                 fcu->extend = FCURVE_EXTRAPOLATE_LINEAR;
499
500                 cu->ctime = 0.0f;
501                 cfra = (float)gtd->start_frame;
502                 insert_keyframe_direct(depsgraph, reports, ptr, prop, fcu, cfra, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST);
503
504                 cu->ctime = cu->pathlen;
505                 if (gtd->realtime) {
506                         cfra += (float)TIME2FRA(gtd->tot_time); /* Seconds to frames */
507                 }
508                 else {
509                         cfra = (float)gtd->end_frame;
510                 }
511                 insert_keyframe_direct(depsgraph, reports, ptr, prop, fcu, cfra, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST);
512         }
513         else {
514                 /* Use actual recorded timing! */
515                 RNG *rng = BLI_rng_new(0);
516                 float time_range;
517
518                 /* CustomGaps specific */
519                 float tot_gaps_time = 0.0f;
520
521                 /* Pre-process gaps, in case we don't want to keep their original timing */
522                 if (gtd->mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
523                         gp_stroke_path_animation_preprocess_gaps(gtd, rng, &nbr_gaps, &tot_gaps_time);
524                 }
525
526                 if (gtd->realtime) {
527                         time_range = (float)TIME2FRA(gtd->tot_time); /* Seconds to frames */
528                 }
529                 else {
530                         time_range = (float)(gtd->end_frame - gtd->start_frame);
531                 }
532
533                 if (G.debug & G_DEBUG) {
534                         printf("GP Stroke Path Conversion: Starting keying!\n");
535                 }
536
537                 gp_stroke_path_animation_add_keyframes(depsgraph, reports, ptr, prop, fcu, cu, gtd, rng, time_range,
538                                                        nbr_gaps, tot_gaps_time);
539
540                 BLI_rng_free(rng);
541         }
542
543         /* As we used INSERTKEY_FAST mode, we need to recompute all curve's handles now */
544         calchandles_fcurve(fcu);
545
546         if (G.debug & G_DEBUG) {
547                 printf("%s: \ntot len: %f\t\ttot time: %f\n", __func__, gtd->tot_dist, gtd->tot_time);
548                 for (i = 0; i < gtd->num_points; i++) {
549                         printf("\tpoint %d:\t\tlen: %f\t\ttime: %f\n", i, gtd->dists[i], gtd->times[i]);
550                 }
551                 printf("\n\n");
552         }
553
554         WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
555
556         /* send updates */
557         DEG_id_tag_update(&cu->id, 0);
558 }
559
560 #undef MIN_TIME_DELTA
561
562 #define GAP_DFAC 0.01f
563 #define WIDTH_CORR_FAC 0.1f
564 #define BEZT_HANDLE_FAC 0.3f
565
566 /* convert stroke to 3d path */
567
568 /* helper */
569 static void gp_stroke_to_path_add_point(tGpTimingData *gtd, BPoint *bp, const float p[3], const float prev_p[3],
570                                         const bool do_gtd, const double inittime, const float time,
571                                         const float width, const float rad_fac, float minmax_weights[2])
572 {
573         copy_v3_v3(bp->vec, p);
574         bp->vec[3] = 1.0f;
575
576         /* set settings */
577         bp->f1 = SELECT;
578         bp->radius = width * rad_fac;
579         bp->weight = width;
580         CLAMP(bp->weight, 0.0f, 1.0f);
581         if (bp->weight < minmax_weights[0]) {
582                 minmax_weights[0] = bp->weight;
583         }
584         else if (bp->weight > minmax_weights[1]) {
585                 minmax_weights[1] = bp->weight;
586         }
587
588         /* Update timing data */
589         if (do_gtd) {
590                 gp_timing_data_add_point(gtd, inittime, time, len_v3v3(prev_p, p));
591         }
592 }
593
594 static void gp_stroke_to_path(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect, Nurb **curnu,
595                               float minmax_weights[2], const float rad_fac, bool stitch, const bool add_start_point,
596                               const bool add_end_point, tGpTimingData *gtd)
597 {
598         bGPDspoint *pt;
599         Nurb *nu = (curnu) ? *curnu : NULL;
600         BPoint *bp, *prev_bp = NULL;
601         const bool do_gtd = (gtd->mode != GP_STROKECONVERT_TIMING_NONE);
602         const int add_start_end_points = (add_start_point ? 1 : 0) + (add_end_point ? 1 : 0);
603         int i, old_nbp = 0;
604
605         /* create new 'nurb' or extend current one within the curve */
606         if (nu) {
607                 old_nbp = nu->pntsu;
608
609                 /* If stitch, the first point of this stroke is already present in current nu.
610                  * Else, we have to add two additional points to make the zero-radius link between strokes.
611                  */
612                 BKE_nurb_points_add(nu, gps->totpoints + (stitch ? -1 : 2) + add_start_end_points);
613         }
614         else {
615                 nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_path(nurb)");
616
617                 nu->pntsu = gps->totpoints + add_start_end_points;
618                 nu->pntsv = 1;
619                 nu->orderu = 2; /* point-to-point! */
620                 nu->type = CU_NURBS;
621                 nu->flagu = CU_NURB_ENDPOINT;
622                 nu->resolu = cu->resolu;
623                 nu->resolv = cu->resolv;
624                 nu->knotsu = NULL;
625
626                 nu->bp = (BPoint *)MEM_callocN(sizeof(BPoint) * nu->pntsu, "bpoints");
627
628                 stitch = false; /* Security! */
629         }
630
631         if (do_gtd) {
632                 gp_timing_data_set_nbr(gtd, nu->pntsu);
633         }
634
635         /* If needed, make the link between both strokes with two zero-radius additional points */
636         /* About "zero-radius" point interpolations:
637          * - If we have at least two points in current curve (most common case), we linearly extrapolate
638          *   the last segment to get the first point (p1) position and timing.
639          * - If we do not have those (quite odd, but may happen), we linearly interpolate the last point
640          *   with the first point of the current stroke.
641          * The same goes for the second point, first segment of the current stroke is "negatively" extrapolated
642          * if it exists, else (if the stroke is a single point), linear interpolation with last curve point...
643          */
644         if (curnu && !stitch && old_nbp) {
645                 float p1[3], p2[3], p[3], next_p[3];
646                 float dt1 = 0.0f, dt2 = 0.0f;
647
648                 BLI_assert(gps->prev != NULL);
649
650                 prev_bp = NULL;
651                 if ((old_nbp > 1) && (gps->prev->totpoints > 1)) {
652                         /* Only use last curve segment if previous stroke was not a single-point one! */
653                         prev_bp = &nu->bp[old_nbp - 2];
654                 }
655                 bp = &nu->bp[old_nbp - 1];
656
657                 /* First point */
658                 gp_strokepoint_convertcoords(C, gpl, gps, gps->points, p, subrect);
659                 if (prev_bp) {
660                         interp_v3_v3v3(p1, bp->vec, prev_bp->vec, -GAP_DFAC);
661                         if (do_gtd) {
662                                 const int idx = gps->prev->totpoints - 1;
663                                 dt1 = interpf(gps->prev->points[idx - 1].time, gps->prev->points[idx].time, -GAP_DFAC);
664                         }
665                 }
666                 else {
667                         interp_v3_v3v3(p1, bp->vec, p, GAP_DFAC);
668                         if (do_gtd) {
669                                 dt1 = interpf(gps->inittime - gps->prev->inittime, 0.0f, GAP_DFAC);
670                         }
671                 }
672                 bp++;
673                 gp_stroke_to_path_add_point(gtd, bp, p1, (bp - 1)->vec, do_gtd, gps->prev->inittime, dt1,
674                                             0.0f, rad_fac, minmax_weights);
675
676                 /* Second point */
677                 /* Note dt2 is always negative, which marks the gap. */
678                 if (gps->totpoints > 1) {
679                         gp_strokepoint_convertcoords(C, gpl, gps, gps->points + 1, next_p, subrect);
680                         interp_v3_v3v3(p2, p, next_p, -GAP_DFAC);
681                         if (do_gtd) {
682                                 dt2 = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC);
683                         }
684                 }
685                 else {
686                         interp_v3_v3v3(p2, p, bp->vec, GAP_DFAC);
687                         if (do_gtd) {
688                                 dt2 = interpf(gps->prev->inittime - gps->inittime, 0.0f, GAP_DFAC);
689                         }
690                 }
691                 bp++;
692                 gp_stroke_to_path_add_point(gtd, bp, p2, p1, do_gtd, gps->inittime, dt2, 0.0f, rad_fac, minmax_weights);
693
694                 old_nbp += 2;
695         }
696         else if (add_start_point) {
697                 float p[3], next_p[3];
698                 float dt = 0.0f;
699
700                 gp_strokepoint_convertcoords(C, gpl, gps, gps->points, p, subrect);
701                 if (gps->totpoints > 1) {
702                         gp_strokepoint_convertcoords(C, gpl, gps, gps->points + 1, next_p, subrect);
703                         interp_v3_v3v3(p, p, next_p, -GAP_DFAC);
704                         if (do_gtd) {
705                                 dt = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC);
706                         }
707                 }
708                 else {
709                         p[0] -= GAP_DFAC;  /* Rather arbitrary... */
710                         dt = -GAP_DFAC;  /* Rather arbitrary too! */
711                 }
712                 bp = &nu->bp[old_nbp];
713                 /* Note we can't give anything else than 0.0 as time here, since a negative one (which would be expected value)
714                  * would not work (it would be *before* gtd->inittime, which is not supported currently).
715                  */
716                 gp_stroke_to_path_add_point(gtd, bp, p, p, do_gtd, gps->inittime, dt, 0.0f, rad_fac, minmax_weights);
717
718                 old_nbp++;
719         }
720
721         if (old_nbp) {
722                 prev_bp = &nu->bp[old_nbp - 1];
723         }
724
725         /* add points */
726         for (i = (stitch) ? 1 : 0, pt = &gps->points[(stitch) ? 1 : 0], bp = &nu->bp[old_nbp];
727              i < gps->totpoints;
728              i++, pt++, bp++)
729         {
730                 float p[3];
731                 float width = pt->pressure * (gps->thickness + gpl->thickness) * WIDTH_CORR_FAC;
732
733                 /* get coordinates to add at */
734                 gp_strokepoint_convertcoords(C, gpl, gps, pt, p, subrect);
735
736                 gp_stroke_to_path_add_point(gtd, bp, p, (prev_bp) ? prev_bp->vec : p, do_gtd, gps->inittime, pt->time,
737                                             width, rad_fac, minmax_weights);
738
739                 prev_bp = bp;
740         }
741
742         if (add_end_point) {
743                 float p[3];
744                 float dt = 0.0f;
745
746                 if (gps->totpoints > 1) {
747                         interp_v3_v3v3(p, prev_bp->vec, (prev_bp - 1)->vec, -GAP_DFAC);
748                         if (do_gtd) {
749                                 const int idx = gps->totpoints - 1;
750                                 dt = interpf(gps->points[idx - 1].time, gps->points[idx].time, -GAP_DFAC);
751                         }
752                 }
753                 else {
754                         copy_v3_v3(p, prev_bp->vec);
755                         p[0] += GAP_DFAC;  /* Rather arbitrary... */
756                         dt = GAP_DFAC;  /* Rather arbitrary too! */
757                 }
758                 /* Note bp has already been incremented in main loop above, so it points to the right place. */
759                 gp_stroke_to_path_add_point(gtd, bp, p, prev_bp->vec, do_gtd, gps->inittime, dt, 0.0f, rad_fac, minmax_weights);
760         }
761
762         /* add nurb to curve */
763         if (!curnu || !*curnu) {
764                 BLI_addtail(&cu->nurb, nu);
765         }
766         if (curnu) {
767                 *curnu = nu;
768         }
769
770         BKE_nurb_knot_calc_u(nu);
771 }
772
773 /* convert stroke to 3d bezier */
774
775 /* helper */
776 static void gp_stroke_to_bezier_add_point(tGpTimingData *gtd, BezTriple *bezt,
777                                           const float p[3], const float h1[3], const float h2[3], const float prev_p[3],
778                                           const bool do_gtd, const double inittime, const float time,
779                                           const float width, const float rad_fac, float minmax_weights[2])
780 {
781         copy_v3_v3(bezt->vec[0], h1);
782         copy_v3_v3(bezt->vec[1], p);
783         copy_v3_v3(bezt->vec[2], h2);
784
785         /* set settings */
786         bezt->h1 = bezt->h2 = HD_FREE;
787         bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
788         bezt->radius = width * rad_fac;
789         bezt->weight = width;
790         CLAMP(bezt->weight, 0.0f, 1.0f);
791         if (bezt->weight < minmax_weights[0]) {
792                 minmax_weights[0] = bezt->weight;
793         }
794         else if (bezt->weight > minmax_weights[1]) {
795                 minmax_weights[1] = bezt->weight;
796         }
797
798         /* Update timing data */
799         if (do_gtd) {
800                 gp_timing_data_add_point(gtd, inittime, time, len_v3v3(prev_p, p));
801         }
802 }
803
804 static void gp_stroke_to_bezier(bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect, Nurb **curnu,
805                                 float minmax_weights[2], const float rad_fac, bool stitch, const bool add_start_point,
806                                 const bool add_end_point, tGpTimingData *gtd)
807 {
808         bGPDspoint *pt;
809         Nurb *nu = (curnu) ? *curnu : NULL;
810         BezTriple *bezt, *prev_bezt = NULL;
811         int i, tot, old_nbezt = 0;
812         const int add_start_end_points = (add_start_point ? 1 : 0) + (add_end_point ? 1 : 0);
813         float p3d_cur[3], p3d_prev[3], p3d_next[3], h1[3], h2[3];
814         const bool do_gtd = (gtd->mode != GP_STROKECONVERT_TIMING_NONE);
815
816         /* create new 'nurb' or extend current one within the curve */
817         if (nu) {
818                 old_nbezt = nu->pntsu;
819                 /* If we do stitch, first point of current stroke is assumed the same as last point of previous stroke,
820                  * so no need to add it.
821                  * If no stitch, we want to add two additional points to make a "zero-radius" link between both strokes.
822                  */
823                 BKE_nurb_bezierPoints_add(nu, gps->totpoints + ((stitch) ? -1 : 2) + add_start_end_points);
824         }
825         else {
826                 nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_bezier(nurb)");
827
828                 nu->pntsu = gps->totpoints + add_start_end_points;
829                 nu->resolu = 12;
830                 nu->resolv = 12;
831                 nu->type = CU_BEZIER;
832                 nu->bezt = (BezTriple *)MEM_callocN(sizeof(BezTriple) * nu->pntsu, "bezts");
833
834                 stitch = false; /* Security! */
835         }
836
837         if (do_gtd) {
838                 gp_timing_data_set_nbr(gtd, nu->pntsu);
839         }
840
841         tot = gps->totpoints;
842
843         /* get initial coordinates */
844         pt = gps->points;
845         if (tot) {
846                 gp_strokepoint_convertcoords(C, gpl, gps, pt, (stitch) ? p3d_prev : p3d_cur, subrect);
847                 if (tot > 1) {
848                         gp_strokepoint_convertcoords(C, gpl, gps, pt + 1, (stitch) ? p3d_cur : p3d_next, subrect);
849                 }
850                 if (stitch && tot > 2) {
851                         gp_strokepoint_convertcoords(C, gpl, gps, pt + 2, p3d_next, subrect);
852                 }
853         }
854
855         /* If needed, make the link between both strokes with two zero-radius additional points */
856         if (curnu && old_nbezt) {
857                 BLI_assert(gps->prev != NULL);
858
859                 /* Update last point's second handle */
860                 if (stitch) {
861                         bezt = &nu->bezt[old_nbezt - 1];
862                         interp_v3_v3v3(h2, bezt->vec[1], p3d_cur, BEZT_HANDLE_FAC);
863                         copy_v3_v3(bezt->vec[2], h2);
864                         pt++;
865                 }
866
867                 /* Create "link points" */
868                 /* About "zero-radius" point interpolations:
869                  * - If we have at least two points in current curve (most common case), we linearly extrapolate
870                  *   the last segment to get the first point (p1) position and timing.
871                  * - If we do not have those (quite odd, but may happen), we linearly interpolate the last point
872                  *   with the first point of the current stroke.
873                  * The same goes for the second point, first segment of the current stroke is "negatively" extrapolated
874                  * if it exists, else (if the stroke is a single point), linear interpolation with last curve point...
875                  */
876                 else {
877                         float p1[3], p2[3];
878                         float dt1 = 0.0f, dt2 = 0.0f;
879
880                         prev_bezt = NULL;
881                         if ((old_nbezt > 1) && (gps->prev->totpoints > 1)) {
882                                 /* Only use last curve segment if previous stroke was not a single-point one! */
883                                 prev_bezt = &nu->bezt[old_nbezt - 2];
884                         }
885                         bezt = &nu->bezt[old_nbezt - 1];
886
887                         /* First point */
888                         if (prev_bezt) {
889                                 interp_v3_v3v3(p1, prev_bezt->vec[1], bezt->vec[1], 1.0f + GAP_DFAC);
890                                 if (do_gtd) {
891                                         const int idx = gps->prev->totpoints - 1;
892                                         dt1 = interpf(gps->prev->points[idx - 1].time, gps->prev->points[idx].time, -GAP_DFAC);
893                                 }
894                         }
895                         else {
896                                 interp_v3_v3v3(p1, bezt->vec[1], p3d_cur, GAP_DFAC);
897                                 if (do_gtd) {
898                                         dt1 = interpf(gps->inittime - gps->prev->inittime, 0.0f, GAP_DFAC);
899                                 }
900                         }
901
902                         /* Second point */
903                         /* Note dt2 is always negative, which marks the gap. */
904                         if (tot > 1) {
905                                 interp_v3_v3v3(p2, p3d_cur, p3d_next, -GAP_DFAC);
906                                 if (do_gtd) {
907                                         dt2 = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC);
908                                 }
909                         }
910                         else {
911                                 interp_v3_v3v3(p2, p3d_cur, bezt->vec[1], GAP_DFAC);
912                                 if (do_gtd) {
913                                         dt2 = interpf(gps->prev->inittime - gps->inittime, 0.0f, GAP_DFAC);
914                                 }
915                         }
916
917                         /* Second handle of last point of previous stroke. */
918                         interp_v3_v3v3(h2, bezt->vec[1], p1, BEZT_HANDLE_FAC);
919                         copy_v3_v3(bezt->vec[2], h2);
920
921                         /* First point */
922                         interp_v3_v3v3(h1, p1, bezt->vec[1], BEZT_HANDLE_FAC);
923                         interp_v3_v3v3(h2, p1, p2, BEZT_HANDLE_FAC);
924                         bezt++;
925                         gp_stroke_to_bezier_add_point(gtd, bezt, p1, h1, h2, (bezt - 1)->vec[1], do_gtd, gps->prev->inittime, dt1,
926                                                       0.0f, rad_fac, minmax_weights);
927
928                         /* Second point */
929                         interp_v3_v3v3(h1, p2, p1, BEZT_HANDLE_FAC);
930                         interp_v3_v3v3(h2, p2, p3d_cur, BEZT_HANDLE_FAC);
931                         bezt++;
932                         gp_stroke_to_bezier_add_point(gtd, bezt, p2, h1, h2, p1, do_gtd, gps->inittime, dt2,
933                                                       0.0f, rad_fac, minmax_weights);
934
935                         old_nbezt += 2;
936                         copy_v3_v3(p3d_prev, p2);
937                 }
938         }
939         else if (add_start_point) {
940                 float p[3];
941                 float dt = 0.0f;
942
943                 if (gps->totpoints > 1) {
944                         interp_v3_v3v3(p, p3d_cur, p3d_next, -GAP_DFAC);
945                         if (do_gtd) {
946                                 dt = interpf(gps->points[1].time, gps->points[0].time, -GAP_DFAC);
947                         }
948                 }
949                 else {
950                         copy_v3_v3(p, p3d_cur);
951                         p[0] -= GAP_DFAC;  /* Rather arbitrary... */
952                         dt = -GAP_DFAC;  /* Rather arbitrary too! */
953                 }
954                 interp_v3_v3v3(h1, p, p3d_cur, -BEZT_HANDLE_FAC);
955                 interp_v3_v3v3(h2, p, p3d_cur, BEZT_HANDLE_FAC);
956                 bezt = &nu->bezt[old_nbezt];
957                 gp_stroke_to_bezier_add_point(gtd, bezt, p, h1, h2, p, do_gtd, gps->inittime, dt,
958                                               0.0f, rad_fac, minmax_weights);
959
960                 old_nbezt++;
961                 copy_v3_v3(p3d_prev, p);
962         }
963
964         if (old_nbezt) {
965                 prev_bezt = &nu->bezt[old_nbezt - 1];
966         }
967
968         /* add points */
969         for (i = stitch ? 1 : 0, bezt = &nu->bezt[old_nbezt]; i < tot; i++, pt++, bezt++) {
970                 float width = pt->pressure * (gps->thickness + gpl->thickness) * WIDTH_CORR_FAC;
971
972                 if (i || old_nbezt) {
973                         interp_v3_v3v3(h1, p3d_cur, p3d_prev, BEZT_HANDLE_FAC);
974                 }
975                 else {
976                         interp_v3_v3v3(h1, p3d_cur, p3d_next, -BEZT_HANDLE_FAC);
977                 }
978
979                 if (i < tot - 1) {
980                         interp_v3_v3v3(h2, p3d_cur, p3d_next, BEZT_HANDLE_FAC);
981                 }
982                 else {
983                         interp_v3_v3v3(h2, p3d_cur, p3d_prev, -BEZT_HANDLE_FAC);
984                 }
985
986                 gp_stroke_to_bezier_add_point(gtd, bezt, p3d_cur, h1, h2, prev_bezt ? prev_bezt->vec[1] : p3d_cur,
987                                               do_gtd, gps->inittime, pt->time, width, rad_fac, minmax_weights);
988
989                 /* shift coord vects */
990                 copy_v3_v3(p3d_prev, p3d_cur);
991                 copy_v3_v3(p3d_cur, p3d_next);
992
993                 if (i + 2 < tot) {
994                         gp_strokepoint_convertcoords(C, gpl, gps, pt + 2, p3d_next, subrect);
995                 }
996
997                 prev_bezt = bezt;
998         }
999
1000         if (add_end_point) {
1001                 float p[3];
1002                 float dt = 0.0f;
1003
1004                 if (gps->totpoints > 1) {
1005                         interp_v3_v3v3(p, prev_bezt->vec[1], (prev_bezt - 1)->vec[1], -GAP_DFAC);
1006                         if (do_gtd) {
1007                                 const int idx = gps->totpoints - 1;
1008                                 dt = interpf(gps->points[idx - 1].time, gps->points[idx].time, -GAP_DFAC);
1009                         }
1010                 }
1011                 else {
1012                         copy_v3_v3(p, prev_bezt->vec[1]);
1013                         p[0] += GAP_DFAC;  /* Rather arbitrary... */
1014                         dt = GAP_DFAC;  /* Rather arbitrary too! */
1015                 }
1016
1017                 /* Second handle of last point of this stroke. */
1018                 interp_v3_v3v3(h2, prev_bezt->vec[1], p, BEZT_HANDLE_FAC);
1019                 copy_v3_v3(prev_bezt->vec[2], h2);
1020
1021                 /* The end point */
1022                 interp_v3_v3v3(h1, p, prev_bezt->vec[1], BEZT_HANDLE_FAC);
1023                 interp_v3_v3v3(h2, p, prev_bezt->vec[1], -BEZT_HANDLE_FAC);
1024                 /* Note bezt has already been incremented in main loop above, so it points to the right place. */
1025                 gp_stroke_to_bezier_add_point(gtd, bezt, p, h1, h2, prev_bezt->vec[1], do_gtd, gps->inittime, dt,
1026                                               0.0f, rad_fac, minmax_weights);
1027         }
1028
1029         /* must calculate handles or else we crash */
1030         BKE_nurb_handles_calc(nu);
1031
1032         if (!curnu || !*curnu) {
1033                 BLI_addtail(&cu->nurb, nu);
1034         }
1035         if (curnu) {
1036                 *curnu = nu;
1037         }
1038 }
1039
1040 #undef GAP_DFAC
1041 #undef WIDTH_CORR_FAC
1042 #undef BEZT_HANDLE_FAC
1043
1044 static void gp_stroke_finalize_curve_endpoints(Curve *cu)
1045 {
1046         /* start */
1047         Nurb *nu = cu->nurb.first;
1048         int i = 0;
1049         if (nu->bezt) {
1050                 BezTriple *bezt = nu->bezt;
1051                 if (bezt) {
1052                         bezt[i].weight = bezt[i].radius = 0.0f;
1053                 }
1054         }
1055         else if (nu->bp) {
1056                 BPoint *bp = nu->bp;
1057                 if (bp) {
1058                         bp[i].weight = bp[i].radius = 0.0f;
1059                 }
1060         }
1061
1062         /* end */
1063         nu = cu->nurb.last;
1064         i = nu->pntsu - 1;
1065         if (nu->bezt) {
1066                 BezTriple *bezt = nu->bezt;
1067                 if (bezt) {
1068                         bezt[i].weight = bezt[i].radius = 0.0f;
1069                 }
1070         }
1071         else if (nu->bp) {
1072                 BPoint *bp = nu->bp;
1073                 if (bp) {
1074                         bp[i].weight = bp[i].radius = 0.0f;
1075                 }
1076         }
1077 }
1078
1079 static void gp_stroke_norm_curve_weights(Curve *cu, const float minmax_weights[2])
1080 {
1081         Nurb *nu;
1082         const float delta = minmax_weights[0];
1083         float fac;
1084         int i;
1085
1086         /* when delta == minmax_weights[0] == minmax_weights[1], we get div by zero [#35686] */
1087         if (IS_EQF(delta, minmax_weights[1]))
1088                 fac = 1.0f;
1089         else
1090                 fac = 1.0f / (minmax_weights[1] - delta);
1091
1092         for (nu = cu->nurb.first; nu; nu = nu->next) {
1093                 if (nu->bezt) {
1094                         BezTriple *bezt = nu->bezt;
1095                         for (i = 0; i < nu->pntsu; i++, bezt++) {
1096                                 bezt->weight = (bezt->weight - delta) * fac;
1097                         }
1098                 }
1099                 else if (nu->bp) {
1100                         BPoint *bp = nu->bp;
1101                         for (i = 0; i < nu->pntsu; i++, bp++) {
1102                                 bp->weight = (bp->weight - delta) * fac;
1103                         }
1104                 }
1105         }
1106 }
1107
1108 static int gp_camera_view_subrect(bContext *C, rctf *subrect)
1109 {
1110         View3D *v3d = CTX_wm_view3d(C);
1111         ARegion *ar = CTX_wm_region(C);
1112
1113         if (v3d) {
1114                 RegionView3D *rv3d = ar->regiondata;
1115
1116                 /* for camera view set the subrect */
1117                 if (rv3d->persp == RV3D_CAMOB) {
1118                         Scene *scene = CTX_data_scene(C);
1119                         Depsgraph *depsgraph = CTX_data_depsgraph(C);
1120                         ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, subrect, true); /* no shift */
1121                         return 1;
1122                 }
1123         }
1124
1125         return 0;
1126 }
1127
1128 /* convert a given grease-pencil layer to a 3d-curve representation (using current view if appropriate) */
1129 static void gp_layer_to_curve(bContext *C, ReportList *reports, bGPdata *gpd, bGPDlayer *gpl, const int mode,
1130                               const bool norm_weights, const float rad_fac, const bool link_strokes, tGpTimingData *gtd)
1131 {
1132         struct Main *bmain = CTX_data_main(C);
1133         Scene *scene = CTX_data_scene(C);
1134         ViewLayer *view_layer = CTX_data_view_layer(C);
1135         Collection *collection = CTX_data_collection(C);
1136         bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, 0);
1137         bGPDstroke *gps, *prev_gps = NULL;
1138         Object *ob;
1139         Curve *cu;
1140         Nurb *nu = NULL;
1141         Base *base_new = NULL;
1142         float minmax_weights[2] = {1.0f, 0.0f};
1143
1144         /* camera framing */
1145         rctf subrect, *subrect_ptr = NULL;
1146
1147         /* error checking */
1148         if (ELEM(NULL, gpd, gpl, gpf))
1149                 return;
1150
1151         /* only convert if there are any strokes on this layer's frame to convert */
1152         if (BLI_listbase_is_empty(&gpf->strokes))
1153                 return;
1154
1155         /* initialize camera framing */
1156         if (gp_camera_view_subrect(C, &subrect)) {
1157                 subrect_ptr = &subrect;
1158         }
1159
1160         /* init the curve object (remove rotation and get curve data from it)
1161          *      - must clear transforms set on object, as those skew our results
1162          */
1163         ob = BKE_object_add_only_object(bmain, OB_CURVE, gpl->info);
1164         cu = ob->data = BKE_curve_add(bmain, gpl->info, OB_CURVE);
1165         BKE_collection_object_add(bmain, collection, ob);
1166         base_new = BKE_view_layer_base_find(view_layer, ob);
1167
1168         cu->flag |= CU_3D;
1169
1170         gtd->inittime = ((bGPDstroke *)gpf->strokes.first)->inittime;
1171
1172         /* add points to curve */
1173         for (gps = gpf->strokes.first; gps; gps = gps->next) {
1174                 const bool add_start_point = (link_strokes && !(prev_gps));
1175                 const bool add_end_point = (link_strokes && !(gps->next));
1176
1177                 /* Detect new strokes created because of GP_STROKE_BUFFER_MAX reached, and stitch them to previous one. */
1178                 bool stitch = false;
1179                 if (prev_gps) {
1180                         bGPDspoint *pt1 = &prev_gps->points[prev_gps->totpoints - 1];
1181                         bGPDspoint *pt2 = &gps->points[0];
1182
1183                         if ((pt1->x == pt2->x) && (pt1->y == pt2->y)) {
1184                                 stitch = true;
1185                         }
1186                 }
1187
1188                 /* Decide whether we connect this stroke to previous one */
1189                 if (!(stitch || link_strokes)) {
1190                         nu = NULL;
1191                 }
1192
1193                 switch (mode) {
1194                         case GP_STROKECONVERT_PATH:
1195                                 gp_stroke_to_path(C, gpl, gps, cu, subrect_ptr, &nu, minmax_weights, rad_fac, stitch,
1196                                                   add_start_point, add_end_point, gtd);
1197                                 break;
1198                         case GP_STROKECONVERT_CURVE:
1199                         case GP_STROKECONVERT_POLY:  /* convert after */
1200                                 gp_stroke_to_bezier(C, gpl, gps, cu, subrect_ptr, &nu, minmax_weights, rad_fac, stitch,
1201                                                     add_start_point, add_end_point, gtd);
1202                                 break;
1203                         default:
1204                                 BLI_assert(!"invalid mode");
1205                                 break;
1206                 }
1207                 prev_gps = gps;
1208         }
1209
1210         /* If link_strokes, be sure first and last points have a zero weight/size! */
1211         if (link_strokes) {
1212                 gp_stroke_finalize_curve_endpoints(cu);
1213         }
1214
1215         /* Update curve's weights, if needed */
1216         if (norm_weights && ((minmax_weights[0] > 0.0f) || (minmax_weights[1] < 1.0f))) {
1217                 gp_stroke_norm_curve_weights(cu, minmax_weights);
1218         }
1219
1220         /* Create the path animation, if needed */
1221         gp_stroke_path_animation(C, reports, cu, gtd);
1222
1223         if (mode == GP_STROKECONVERT_POLY) {
1224                 for (nu = cu->nurb.first; nu; nu = nu->next) {
1225                         BKE_nurb_type_convert(nu, CU_POLY, false);
1226                 }
1227         }
1228
1229         /* set the layer and select */
1230         base_new->flag |= SELECT;
1231         BKE_scene_object_base_flag_sync_from_base(base_new);
1232 }
1233
1234 /* --- */
1235
1236 /* Check a GP layer has valid timing data! Else, most timing options are hidden in the operator.
1237  * op may be NULL.
1238  */
1239 static bool gp_convert_check_has_valid_timing(bContext *C, bGPDlayer *gpl, wmOperator *op)
1240 {
1241         Scene *scene = CTX_data_scene(C);
1242         bGPDframe *gpf = NULL;
1243         bGPDstroke *gps = NULL;
1244         bGPDspoint *pt;
1245         double base_time, cur_time, prev_time = -1.0;
1246         int i;
1247         bool valid = true;
1248
1249         if (!gpl || !(gpf = BKE_gpencil_layer_getframe(gpl, CFRA, 0)) || !(gps = gpf->strokes.first))
1250                 return false;
1251
1252         do {
1253                 base_time = cur_time = gps->inittime;
1254                 if (cur_time <= prev_time) {
1255                         valid = false;
1256                         break;
1257                 }
1258
1259                 prev_time = cur_time;
1260                 for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
1261                         cur_time = base_time + (double)pt->time;
1262                         /* First point of a stroke should have the same time as stroke's inittime,
1263                          * so it's the only case where equality is allowed!
1264                          */
1265                         if ((i && cur_time <= prev_time) || (cur_time < prev_time)) {
1266                                 valid = false;
1267                                 break;
1268                         }
1269                         prev_time = cur_time;
1270                 }
1271
1272                 if (!valid) {
1273                         break;
1274                 }
1275         } while ((gps = gps->next));
1276
1277         if (op) {
1278                 RNA_boolean_set(op->ptr, "use_timing_data", valid);
1279         }
1280         return valid;
1281 }
1282
1283 /* Check end_frame is always > start frame! */
1284 static void gp_convert_set_end_frame(struct Main *UNUSED(main), struct Scene *UNUSED(scene), struct PointerRNA *ptr)
1285 {
1286         int start_frame = RNA_int_get(ptr, "start_frame");
1287         int end_frame = RNA_int_get(ptr, "end_frame");
1288
1289         if (end_frame <= start_frame) {
1290                 RNA_int_set(ptr, "end_frame", start_frame + 1);
1291         }
1292 }
1293
1294 static int gp_convert_poll(bContext *C)
1295 {
1296         bGPdata *gpd = ED_gpencil_data_get_active(C);
1297         bGPDlayer *gpl = NULL;
1298         bGPDframe *gpf = NULL;
1299         ScrArea *sa = CTX_wm_area(C);
1300         Scene *scene = CTX_data_scene(C);
1301         ViewLayer *view_layer = CTX_data_view_layer(C);
1302
1303         /* only if the current view is 3D View, if there's valid data (i.e. at least one stroke!),
1304          * and if we are not in edit mode!
1305          */
1306         return ((sa && sa->spacetype == SPACE_VIEW3D) &&
1307                 (gpl = BKE_gpencil_layer_getactive(gpd)) &&
1308                 (gpf = BKE_gpencil_layer_getframe(gpl, CFRA, 0)) &&
1309                 (gpf->strokes.first) &&
1310                 (OBEDIT_FROM_VIEW_LAYER(view_layer) == NULL));
1311 }
1312
1313 static int gp_convert_layer_exec(bContext *C, wmOperator *op)
1314 {
1315         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_timing_data");
1316         bGPdata *gpd = ED_gpencil_data_get_active(C);
1317         bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd);
1318         Scene *scene = CTX_data_scene(C);
1319         const int mode = RNA_enum_get(op->ptr, "type");
1320         const bool norm_weights = RNA_boolean_get(op->ptr, "use_normalize_weights");
1321         const float rad_fac = RNA_float_get(op->ptr, "radius_multiplier");
1322         const bool link_strokes = RNA_boolean_get(op->ptr, "use_link_strokes");
1323         bool valid_timing;
1324         tGpTimingData gtd;
1325
1326         /* check if there's data to work with */
1327         if (gpd == NULL) {
1328                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data to work on");
1329                 return OPERATOR_CANCELLED;
1330         }
1331
1332         if (!RNA_property_is_set(op->ptr, prop) && !gp_convert_check_has_valid_timing(C, gpl, op)) {
1333                 BKE_report(op->reports, RPT_WARNING,
1334                            "Current Grease Pencil strokes have no valid timing data, most timing options will be hidden!");
1335         }
1336         valid_timing = RNA_property_boolean_get(op->ptr, prop);
1337
1338         gtd.mode = RNA_enum_get(op->ptr, "timing_mode");
1339         /* Check for illegal timing mode! */
1340         if (!valid_timing && !ELEM(gtd.mode, GP_STROKECONVERT_TIMING_NONE, GP_STROKECONVERT_TIMING_LINEAR)) {
1341                 gtd.mode = GP_STROKECONVERT_TIMING_LINEAR;
1342                 RNA_enum_set(op->ptr, "timing_mode", gtd.mode);
1343         }
1344         if (!link_strokes) {
1345                 gtd.mode = GP_STROKECONVERT_TIMING_NONE;
1346         }
1347
1348         /* grab all relevant settings */
1349         gtd.frame_range = RNA_int_get(op->ptr, "frame_range");
1350         gtd.start_frame = RNA_int_get(op->ptr, "start_frame");
1351         gtd.realtime = valid_timing ? RNA_boolean_get(op->ptr, "use_realtime") : false;
1352         gtd.end_frame = RNA_int_get(op->ptr, "end_frame");
1353         gtd.gap_duration = RNA_float_get(op->ptr, "gap_duration");
1354         gtd.gap_randomness = RNA_float_get(op->ptr, "gap_randomness");
1355         gtd.gap_randomness = min_ff(gtd.gap_randomness, gtd.gap_duration);
1356         gtd.seed = RNA_int_get(op->ptr, "seed");
1357         gtd.num_points = gtd.cur_point = 0;
1358         gtd.dists = gtd.times = NULL;
1359         gtd.tot_dist = gtd.tot_time = gtd.gap_tot_time = 0.0f;
1360         gtd.inittime = 0.0;
1361         gtd.offset_time = 0.0f;
1362
1363         /* perform conversion */
1364         gp_layer_to_curve(C, op->reports, gpd, gpl, mode, norm_weights, rad_fac, link_strokes, &gtd);
1365
1366         /* free temp memory */
1367         if (gtd.dists) {
1368                 MEM_freeN(gtd.dists);
1369                 gtd.dists = NULL;
1370         }
1371         if (gtd.times) {
1372                 MEM_freeN(gtd.times);
1373                 gtd.times = NULL;
1374         }
1375
1376         /* notifiers */
1377         WM_event_add_notifier(C, NC_OBJECT | NA_ADDED, NULL);
1378         WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
1379
1380         /* done */
1381         return OPERATOR_FINISHED;
1382 }
1383
1384 static bool gp_convert_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
1385 {
1386         const char *prop_id = RNA_property_identifier(prop);
1387         const bool link_strokes = RNA_boolean_get(ptr, "use_link_strokes");
1388         int timing_mode = RNA_enum_get(ptr, "timing_mode");
1389         bool realtime = RNA_boolean_get(ptr, "use_realtime");
1390         float gap_duration = RNA_float_get(ptr, "gap_duration");
1391         float gap_randomness = RNA_float_get(ptr, "gap_randomness");
1392         const bool valid_timing = RNA_boolean_get(ptr, "use_timing_data");
1393
1394         /* Always show those props */
1395         if (STREQ(prop_id, "type") ||
1396             STREQ(prop_id, "use_normalize_weights") ||
1397             STREQ(prop_id, "radius_multiplier") ||
1398             STREQ(prop_id, "use_link_strokes"))
1399         {
1400                 return true;
1401         }
1402
1403         /* Never show this prop */
1404         if (STREQ(prop_id, "use_timing_data"))
1405                 return false;
1406
1407         if (link_strokes) {
1408                 /* Only show when link_stroke is true */
1409                 if (STREQ(prop_id, "timing_mode"))
1410                         return true;
1411
1412                 if (timing_mode != GP_STROKECONVERT_TIMING_NONE) {
1413                         /* Only show when link_stroke is true and stroke timing is enabled */
1414                         if (STREQ(prop_id, "frame_range") ||
1415                             STREQ(prop_id, "start_frame"))
1416                         {
1417                                 return true;
1418                         }
1419
1420                         /* Only show if we have valid timing data! */
1421                         if (valid_timing && STREQ(prop_id, "use_realtime"))
1422                                 return true;
1423
1424                         /* Only show if realtime or valid_timing is false! */
1425                         if ((!realtime || !valid_timing) && STREQ(prop_id, "end_frame"))
1426                                 return true;
1427
1428                         if (valid_timing && timing_mode == GP_STROKECONVERT_TIMING_CUSTOMGAP) {
1429                                 /* Only show for custom gaps! */
1430                                 if (STREQ(prop_id, "gap_duration"))
1431                                         return true;
1432
1433                                 /* Only show randomness for non-null custom gaps! */
1434                                 if (STREQ(prop_id, "gap_randomness") && (gap_duration > 0.0f))
1435                                         return true;
1436
1437                                 /* Only show seed for randomize action! */
1438                                 if (STREQ(prop_id, "seed") && (gap_duration > 0.0f) && (gap_randomness > 0.0f))
1439                                         return true;
1440                         }
1441                 }
1442         }
1443
1444         /* Else, hidden! */
1445         return false;
1446 }
1447
1448 static void gp_convert_ui(bContext *C, wmOperator *op)
1449 {
1450         uiLayout *layout = op->layout;
1451         wmWindowManager *wm = CTX_wm_manager(C);
1452         PointerRNA ptr;
1453
1454         RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
1455
1456         /* Main auto-draw call */
1457         uiDefAutoButsRNA(layout, &ptr, gp_convert_draw_check_prop, UI_BUT_LABEL_ALIGN_NONE, false);
1458 }
1459
1460 void GPENCIL_OT_convert(wmOperatorType *ot)
1461 {
1462         PropertyRNA *prop;
1463
1464         /* identifiers */
1465         ot->name = "Convert Grease Pencil";
1466         ot->idname = "GPENCIL_OT_convert";
1467         ot->description = "Convert the active Grease Pencil layer to a new Curve Object";
1468
1469         /* callbacks */
1470         ot->invoke = WM_menu_invoke;
1471         ot->exec = gp_convert_layer_exec;
1472         ot->poll = gp_convert_poll;
1473         ot->ui = gp_convert_ui;
1474
1475         /* flags */
1476         ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1477
1478         /* properties */
1479         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_convertmodes, 0, "Type", "Which type of curve to convert to");
1480
1481         RNA_def_boolean(ot->srna, "use_normalize_weights", true, "Normalize Weight",
1482                         "Normalize weight (set from stroke width)");
1483         RNA_def_float(ot->srna, "radius_multiplier", 1.0f, 0.0f, 1000.0f, "Radius Fac",
1484                       "Multiplier for the points' radii (set from stroke width)", 0.0f, 10.0f);
1485         RNA_def_boolean(ot->srna, "use_link_strokes", true, "Link Strokes",
1486                         "Whether to link strokes with zero-radius sections of curves");
1487
1488         prop = RNA_def_enum(ot->srna, "timing_mode", prop_gpencil_convert_timingmodes, GP_STROKECONVERT_TIMING_FULL,
1489                             "Timing Mode", "How to use timing data stored in strokes");
1490         RNA_def_enum_funcs(prop, rna_GPConvert_mode_items);
1491
1492         RNA_def_int(ot->srna, "frame_range", 100, 1, 10000, "Frame Range",
1493                     "The duration of evaluation of the path control curve", 1, 1000);
1494         RNA_def_int(ot->srna, "start_frame", 1, 1, 100000, "Start Frame",
1495                     "The start frame of the path control curve", 1, 100000);
1496         RNA_def_boolean(ot->srna, "use_realtime", false, "Realtime",
1497                         "Whether the path control curve reproduces the drawing in realtime, starting from Start Frame");
1498         prop = RNA_def_int(ot->srna, "end_frame", 250, 1, 100000, "End Frame",
1499                            "The end frame of the path control curve (if Realtime is not set)", 1, 100000);
1500         RNA_def_property_update_runtime(prop, gp_convert_set_end_frame);
1501
1502         RNA_def_float(ot->srna, "gap_duration", 0.0f, 0.0f, 10000.0f, "Gap Duration",
1503                       "Custom Gap mode: (Average) length of gaps, in frames "
1504                       "(Note: Realtime value, will be scaled if Realtime is not set)", 0.0f, 1000.0f);
1505         RNA_def_float(ot->srna, "gap_randomness", 0.0f, 0.0f, 10000.0f, "Gap Randomness",
1506                       "Custom Gap mode: Number of frames that gap lengths can vary", 0.0f, 1000.0f);
1507         RNA_def_int(ot->srna, "seed", 0, 0, 1000, "Random Seed",
1508                     "Custom Gap mode: Random generator seed", 0, 100);
1509
1510         /* Note: Internal use, this one will always be hidden by UI code... */
1511         prop = RNA_def_boolean(ot->srna, "use_timing_data", false, "Has Valid Timing",
1512                                "Whether the converted Grease Pencil layer has valid timing data (internal use)");
1513         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1514 }
1515
1516 /* ************************************************ */