34a95cd89bfcae2d39f5ad91ad7954ce11bf8187
[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
32 #include <stdio.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <stddef.h>
36 #include <math.h>
37
38 #include "MEM_guardedalloc.h"
39
40
41 #include "BLI_math.h"
42 #include "BLI_blenlib.h"
43 #include "BLI_utildefines.h"
44
45 #include "DNA_curve_types.h"
46 #include "DNA_object_types.h"
47 #include "DNA_node_types.h"
48 #include "DNA_scene_types.h"
49 #include "DNA_screen_types.h"
50 #include "DNA_space_types.h"
51 #include "DNA_view3d_types.h"
52 #include "DNA_gpencil_types.h"
53
54 #include "BKE_context.h"
55 #include "BKE_curve.h"
56 #include "BKE_gpencil.h"
57 #include "BKE_library.h"
58 #include "BKE_object.h"
59 #include "BKE_report.h"
60
61
62 #include "WM_api.h"
63 #include "WM_types.h"
64
65 #include "RNA_access.h"
66 #include "RNA_define.h"
67
68 #include "UI_view2d.h"
69
70 #include "ED_gpencil.h"
71 #include "ED_view3d.h"
72 #include "ED_clip.h"
73
74 #include "gpencil_intern.h"
75
76 /* ************************************************ */
77 /* Context Wrangling... */
78
79 /* Get pointer to active Grease Pencil datablock, and an RNA-pointer to trace back to whatever owns it */
80 bGPdata **gpencil_data_get_pointers (bContext *C, PointerRNA *ptr)
81 {
82         Scene *scene= CTX_data_scene(C);
83         ScrArea *sa= CTX_wm_area(C);
84         
85         /* if there's an active area, check if the particular editor may
86          * have defined any special Grease Pencil context for editing...
87          */
88         if (sa) {
89                 switch (sa->spacetype) {
90                         case SPACE_VIEW3D: /* 3D-View */
91                         {
92                                 Object *ob= CTX_data_active_object(C);
93                                 
94                                 // TODO: we can include other data-types such as bones later if need be...
95                                 
96                                 /* just in case no active object */
97                                 if (ob) {
98                                         /* for now, as long as there's an object, default to using that in 3D-View */
99                                         if (ptr) RNA_id_pointer_create(&ob->id, ptr);
100                                         return &ob->gpd;
101                                 }
102                         }
103                                 break;
104                         
105                         case SPACE_NODE: /* Nodes Editor */
106                         {
107                                 SpaceNode *snode= (SpaceNode *)CTX_wm_space_data(C);
108                                 
109                                 /* return the GP data for the active node block/node */
110                                 if (snode && snode->nodetree) {
111                                         /* for now, as long as there's an active node tree, default to using that in the Nodes Editor */
112                                         if (ptr) RNA_id_pointer_create(&snode->nodetree->id, ptr);
113                                         return &snode->nodetree->gpd;
114                                 }
115                                 else {
116                                         /* even when there is no node-tree, don't allow this to flow to scene */
117                                         return NULL;
118                                 }
119                         }
120                                 break;
121                                 
122                         case SPACE_SEQ: /* Sequencer */
123                         {
124                                 //SpaceSeq *sseq= (SpaceSeq *)CTX_wm_space_data(C);
125                                 
126                                 /* return the GP data for the active strips/image/etc. */
127                         }
128                                 break;
129                                 
130                         case SPACE_IMAGE: /* Image/UV Editor */
131                         {
132                                 SpaceImage *sima= (SpaceImage *)CTX_wm_space_data(C);
133                                 
134                                 /* for now, Grease Pencil data is associated with the space... */
135                                 // XXX our convention for everything else is to link to data though...
136                                 if (ptr) RNA_pointer_create((ID *)CTX_wm_screen(C), &RNA_SpaceImageEditor, sima, ptr);
137                                 return &sima->gpd;
138                         }
139                                 break;
140                                 
141                         case SPACE_CLIP: /* Nodes Editor */
142                         {
143                                 SpaceClip *sc= (SpaceClip *)CTX_wm_space_data(C);
144                                 MovieClip *clip= ED_space_clip(sc);
145
146                                 if (clip) {
147                                         /* for now, as long as there's a clip, default to using that in Clip Editor */
148                                         if (ptr) RNA_id_pointer_create(&clip->id, ptr);
149                                         return &clip->gpd;
150                                 }
151                         }
152                                 break;
153                                 
154                         default: /* unsupported space */
155                                 return NULL;
156                 }
157         }
158         
159         /* just fall back on the scene's GP data */
160         if (ptr) RNA_id_pointer_create((ID *)scene, ptr);
161         return (scene) ? &scene->gpd : NULL;
162 }
163
164 /* Get the active Grease Pencil datablock */
165 bGPdata *gpencil_data_get_active (bContext *C)
166 {
167         bGPdata **gpd_ptr= gpencil_data_get_pointers(C, NULL);
168         return (gpd_ptr) ? *(gpd_ptr) : NULL;
169 }
170
171 /* needed for offscreen rendering */
172 bGPdata *gpencil_data_get_active_v3d (Scene *scene)
173 {
174         bGPdata *gpd= scene->basact ? scene->basact->object->gpd : NULL;
175         return gpd ? gpd : scene->gpd;
176 }
177
178 /* ************************************************ */
179 /* Panel Operators */
180
181 /* poll callback for adding data/layers - special */
182 static int gp_add_poll (bContext *C)
183 {
184         /* the base line we have is that we have somewhere to add Grease Pencil data */
185         return gpencil_data_get_pointers(C, NULL) != NULL;
186 }
187
188 /* ******************* Add New Data ************************ */
189
190 /* add new datablock - wrapper around API */
191 static int gp_data_add_exec (bContext *C, wmOperator *op)
192 {
193         bGPdata **gpd_ptr= gpencil_data_get_pointers(C, NULL);
194         
195         if (gpd_ptr == NULL) {
196                 BKE_report(op->reports, RPT_ERROR, "Nowhere for Grease Pencil data to go");
197                 return OPERATOR_CANCELLED;
198         }
199         else {
200                 /* decrement user count and add new datablock */
201                 bGPdata *gpd= (*gpd_ptr);
202
203                 id_us_min(&gpd->id);
204                 *gpd_ptr= gpencil_data_addnew("GPencil");
205         }
206         
207         /* notifiers */
208         WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX need a nicer one that will work  
209         
210         return OPERATOR_FINISHED;
211 }
212
213 void GPENCIL_OT_data_add(wmOperatorType *ot)
214 {
215         /* identifiers */
216         ot->name = "Grease Pencil Add New";
217         ot->idname = "GPENCIL_OT_data_add";
218         ot->description = "Add new Grease Pencil datablock";
219         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
220         
221         /* callbacks */
222         ot->exec = gp_data_add_exec;
223         ot->poll = gp_add_poll;
224 }
225
226 /* ******************* Unlink Data ************************ */
227
228 /* poll callback for adding data/layers - special */
229 static int gp_data_unlink_poll (bContext *C)
230 {
231         bGPdata **gpd_ptr= gpencil_data_get_pointers(C, NULL);
232         
233         /* if we have access to some active data, make sure there's a datablock before enabling this */
234         return (gpd_ptr && *gpd_ptr);
235 }
236
237
238 /* unlink datablock - wrapper around API */
239 static int gp_data_unlink_exec (bContext *C, wmOperator *op)
240 {
241         bGPdata **gpd_ptr= gpencil_data_get_pointers(C, NULL);
242         
243         if (gpd_ptr == NULL) {
244                 BKE_report(op->reports, RPT_ERROR, "Nowhere for Grease Pencil data to go");
245                 return OPERATOR_CANCELLED;
246         }
247         else {
248                 /* just unlink datablock now, decreasing its user count */
249                 bGPdata *gpd= (*gpd_ptr);
250                 
251                 id_us_min(&gpd->id);
252                 *gpd_ptr= NULL;
253         }
254         
255         /* notifiers */
256         WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX need a nicer one that will work  
257         
258         return OPERATOR_FINISHED;
259 }
260
261 void GPENCIL_OT_data_unlink(wmOperatorType *ot)
262 {
263         /* identifiers */
264         ot->name = "Grease Pencil Unlink";
265         ot->idname = "GPENCIL_OT_data_unlink";
266         ot->description = "Unlink active Grease Pencil datablock";
267         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
268         
269         /* callbacks */
270         ot->exec = gp_data_unlink_exec;
271         ot->poll = gp_data_unlink_poll;
272 }
273
274 /* ******************* Add New Layer ************************ */
275
276 /* add new layer - wrapper around API */
277 static int gp_layer_add_exec (bContext *C, wmOperator *op)
278 {
279         bGPdata **gpd_ptr= gpencil_data_get_pointers(C, NULL);
280         
281         /* if there's no existing Grease-Pencil data there, add some */
282         if (gpd_ptr == NULL) {
283                 BKE_report(op->reports, RPT_ERROR, "Nowhere for Grease Pencil data to go");
284                 return OPERATOR_CANCELLED;
285         }
286         if (*gpd_ptr == NULL)
287                 *gpd_ptr= gpencil_data_addnew("GPencil");
288                 
289         /* add new layer now */
290         gpencil_layer_addnew(*gpd_ptr);
291         
292         /* notifiers */
293         WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX please work!
294         
295         return OPERATOR_FINISHED;
296 }
297
298 void GPENCIL_OT_layer_add(wmOperatorType *ot)
299 {
300         /* identifiers */
301         ot->name = "Add New Layer";
302         ot->idname = "GPENCIL_OT_layer_add";
303         ot->description = "Add new Grease Pencil layer for the active Grease Pencil datablock";
304         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
305         
306         /* callbacks */
307         ot->exec = gp_layer_add_exec;
308         ot->poll = gp_add_poll;
309 }
310
311 /* ******************* Delete Active Frame ************************ */
312
313 static int gp_actframe_delete_poll (bContext *C)
314 {
315         bGPdata *gpd= gpencil_data_get_active(C);
316         bGPDlayer *gpl= gpencil_layer_getactive(gpd);
317         
318         /* only if there's an active layer with an active frame */
319         return (gpl && gpl->actframe);
320 }
321
322 /* delete active frame - wrapper around API calls */
323 static int gp_actframe_delete_exec (bContext *C, wmOperator *op)
324 {
325         Scene *scene= CTX_data_scene(C);
326         bGPdata *gpd= gpencil_data_get_active(C);
327         bGPDlayer *gpl= gpencil_layer_getactive(gpd);
328         bGPDframe *gpf= gpencil_layer_getframe(gpl, CFRA, 0);
329         
330         /* if there's no existing Grease-Pencil data there, add some */
331         if (gpd == NULL) {
332                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
333                 return OPERATOR_CANCELLED;
334         }
335         if (ELEM(NULL, gpl, gpf)) {
336                 BKE_report(op->reports, RPT_ERROR, "No active frame to delete");
337                 return OPERATOR_CANCELLED;
338         }
339         
340         /* delete it... */
341         gpencil_layer_delframe(gpl, gpf);
342         
343         /* notifiers */
344         WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX please work!
345         
346         return OPERATOR_FINISHED;
347 }
348
349 void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
350 {
351         /* identifiers */
352         ot->name = "Delete Active Frame";
353         ot->idname = "GPENCIL_OT_active_frame_delete";
354         ot->description = "Delete the active frame for the active Grease Pencil datablock";
355         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
356         
357         /* callbacks */
358         ot->exec = gp_actframe_delete_exec;
359         ot->poll = gp_actframe_delete_poll;
360 }
361
362 /* ************************************************ */
363 /* Grease Pencil to Data Operator */
364
365 /* defines for possible modes */
366 enum {
367         GP_STROKECONVERT_PATH = 1,
368         GP_STROKECONVERT_CURVE,
369 };
370
371 /* RNA enum define */
372 static EnumPropertyItem prop_gpencil_convertmodes[] = {
373         {GP_STROKECONVERT_PATH, "PATH", 0, "Path", ""},
374         {GP_STROKECONVERT_CURVE, "CURVE", 0, "Bezier Curve", ""},
375         {0, NULL, 0, NULL, NULL}
376 };
377
378 /* --- */
379
380 /* convert the coordinates from the given stroke point into 3d-coordinates 
381  *      - assumes that the active space is the 3D-View
382  */
383 static void gp_strokepoint_convertcoords (bContext *C, bGPDstroke *gps, bGPDspoint *pt, float p3d[3], rctf *subrect)
384 {
385         Scene *scene= CTX_data_scene(C);
386         View3D *v3d= CTX_wm_view3d(C);
387         ARegion *ar= CTX_wm_region(C);
388         
389         if (gps->flag & GP_STROKE_3DSPACE) {
390                 /* directly use 3d-coordinates */
391                 copy_v3_v3(p3d, &pt->x);
392         }
393         else {
394                 float *fp= give_cursor(scene, v3d);
395                 float mvalf[2];
396                 
397                 /* get screen coordinate */
398                 if (gps->flag & GP_STROKE_2DSPACE) {
399                         int mvali[2];
400                         View2D *v2d= &ar->v2d;
401                         UI_view2d_view_to_region(v2d, pt->x, pt->y, mvali, mvali+1);
402                         VECCOPY2D(mvalf, mvali);
403                 }
404                 else {
405                         if (subrect) {
406                                 mvalf[0]= (((float)pt->x/100.0f) * (subrect->xmax - subrect->xmin)) + subrect->xmin;
407                                 mvalf[1]= (((float)pt->y/100.0f) * (subrect->ymax - subrect->ymin)) + subrect->ymin;
408                         }
409                         else {
410                                 mvalf[0]= (float)pt->x / 100.0f * ar->winx;
411                                 mvalf[1]= (float)pt->y / 100.0f * ar->winy;
412                         }
413                 }
414
415                 /* convert screen coordinate to 3d coordinates 
416                  *      - method taken from editview.c - mouse_cursor() 
417                  */
418                 ED_view3d_win_to_3d(ar, fp, mvalf, p3d);
419         }
420 }
421
422 /* --- */
423
424 /* convert stroke to 3d path */
425 static void gp_stroke_to_path (bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect)
426 {
427         bGPDspoint *pt;
428         Nurb *nu;
429         BPoint *bp;
430         int i;
431
432         /* create new 'nurb' within the curve */
433         nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_path(nurb)");
434         
435         nu->pntsu= gps->totpoints;
436         nu->pntsv= 1;
437         nu->orderu= gps->totpoints;
438         nu->flagu= CU_NURB_ENDPOINT;
439         nu->resolu= 32;
440         
441         nu->bp= (BPoint *)MEM_callocN(sizeof(BPoint)*gps->totpoints, "bpoints");
442         
443         /* add points */
444         for (i=0, pt=gps->points, bp=nu->bp; i < gps->totpoints; i++, pt++, bp++) {
445                 float p3d[3];
446                 
447                 /* get coordinates to add at */
448                 gp_strokepoint_convertcoords(C, gps, pt, p3d, subrect);
449                 copy_v3_v3(bp->vec, p3d);
450                 
451                 /* set settings */
452                 bp->f1= SELECT;
453                 bp->radius = bp->weight = pt->pressure * gpl->thickness;
454         }
455         
456         /* add nurb to curve */
457         BLI_addtail(&cu->nurb, nu);
458 }
459
460 static int gp_camera_view_subrect(bContext *C, rctf *subrect)
461 {
462         View3D *v3d= CTX_wm_view3d(C);
463         ARegion *ar= CTX_wm_region(C);
464
465         if (v3d) {
466                 RegionView3D *rv3d= ar->regiondata;
467
468                 /* for camera view set the subrect */
469                 if (rv3d->persp == RV3D_CAMOB) {
470                         Scene *scene= CTX_data_scene(C);
471                         ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, subrect, TRUE); /* no shift */
472                         return 1;
473                 }
474         }
475
476         return 0;
477 }
478
479 /* convert stroke to 3d bezier */
480 static void gp_stroke_to_bezier (bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect)
481 {
482         bGPDspoint *pt;
483         Nurb *nu;
484         BezTriple *bezt;
485         int i, tot;
486         float p3d_cur[3], p3d_prev[3], p3d_next[3];
487
488         /* create new 'nurb' within the curve */
489         nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_bezier(nurb)");
490
491         nu->pntsu= gps->totpoints;
492         nu->resolu= 12;
493         nu->resolv= 12;
494         nu->type= CU_BEZIER;
495         nu->bezt = (BezTriple *)MEM_callocN(gps->totpoints*sizeof(BezTriple), "bezts");
496
497         tot= gps->totpoints;
498
499         /* get initial coordinates */
500         pt=gps->points;
501         if (tot) {
502                 gp_strokepoint_convertcoords(C, gps, pt, p3d_cur, subrect);
503                 if (tot > 1) {
504                         gp_strokepoint_convertcoords(C, gps, pt+1, p3d_next, subrect);
505                 }
506         }
507
508         /* add points */
509         for (i=0, bezt=nu->bezt; i < tot; i++, pt++, bezt++) {
510                 float h1[3], h2[3];
511
512                 if (i) interp_v3_v3v3(h1, p3d_cur, p3d_prev, 0.3);
513                 else interp_v3_v3v3(h1, p3d_cur, p3d_next, -0.3);
514
515                 if (i < tot-1) interp_v3_v3v3(h2, p3d_cur, p3d_next, 0.3);
516                 else interp_v3_v3v3(h2, p3d_cur, p3d_prev, -0.3);
517
518                 copy_v3_v3(bezt->vec[0], h1);
519                 copy_v3_v3(bezt->vec[1], p3d_cur);
520                 copy_v3_v3(bezt->vec[2], h2);
521
522                 /* set settings */
523                 bezt->h1= bezt->h2= HD_FREE;
524                 bezt->f1= bezt->f2= bezt->f3= SELECT;
525                 bezt->radius = bezt->weight = pt->pressure * gpl->thickness * 0.1f;
526
527                 /* shift coord vects */
528                 copy_v3_v3(p3d_prev, p3d_cur);
529                 copy_v3_v3(p3d_cur, p3d_next);
530
531                 if (i + 2 < tot) {
532                         gp_strokepoint_convertcoords(C, gps, pt + 2, p3d_next, subrect);
533                 }
534         }
535
536         /* must calculate handles or else we crash */
537         BKE_nurb_handles_calc(nu);
538
539         /* add nurb to curve */
540         BLI_addtail(&cu->nurb, nu);
541 }
542
543 /* convert a given grease-pencil layer to a 3d-curve representation (using current view if appropriate) */
544 static void gp_layer_to_curve (bContext *C, bGPdata *gpd, bGPDlayer *gpl, short mode)
545 {
546         Scene *scene= CTX_data_scene(C);
547         bGPDframe *gpf= gpencil_layer_getframe(gpl, CFRA, 0);
548         bGPDstroke *gps;
549         Object *ob;
550         Curve *cu;
551
552         /* camera framing */
553         rctf subrect, *subrect_ptr= NULL;
554
555         /* error checking */
556         if (ELEM3(NULL, gpd, gpl, gpf))
557                 return;
558                 
559         /* only convert if there are any strokes on this layer's frame to convert */
560         if (gpf->strokes.first == NULL)
561                 return;
562
563         /* initialize camera framing */
564         if (gp_camera_view_subrect(C, &subrect)) {
565                 subrect_ptr= &subrect;
566         }
567
568         /* init the curve object (remove rotation and get curve data from it)
569          *      - must clear transforms set on object, as those skew our results
570          */
571         ob= add_object(scene, OB_CURVE);
572         zero_v3(ob->loc);
573         zero_v3(ob->rot);
574         cu= ob->data;
575         cu->flag |= CU_3D;
576         
577         /* rename object and curve to layer name */
578         rename_id((ID *)ob, gpl->info);
579         rename_id((ID *)cu, gpl->info);
580         
581         /* add points to curve */
582         for (gps= gpf->strokes.first; gps; gps= gps->next) {
583                 switch (mode) {
584                         case GP_STROKECONVERT_PATH: 
585                                 gp_stroke_to_path(C, gpl, gps, cu, subrect_ptr);
586                                 break;
587                         case GP_STROKECONVERT_CURVE:
588                                 gp_stroke_to_bezier(C, gpl, gps, cu, subrect_ptr);
589                                 break;
590                         default:
591                                 BLI_assert(!"invalid mode");
592                                 break;
593                 }
594         }
595 }
596
597 /* --- */
598
599 static int gp_convert_poll (bContext *C)
600 {
601         bGPdata *gpd= gpencil_data_get_active(C);
602         ScrArea *sa= CTX_wm_area(C);
603         Scene *scene= CTX_data_scene(C);
604
605         /* only if there's valid data, and the current view is 3D View */
606         return ((sa && sa->spacetype == SPACE_VIEW3D) && gpencil_layer_getactive(gpd) && (scene->obedit == NULL));
607 }
608
609 static int gp_convert_layer_exec (bContext *C, wmOperator *op)
610 {
611         bGPdata *gpd= gpencil_data_get_active(C);
612         bGPDlayer *gpl= gpencil_layer_getactive(gpd);
613         Scene *scene= CTX_data_scene(C);
614         int mode= RNA_enum_get(op->ptr, "type");
615
616         /* check if there's data to work with */
617         if (gpd == NULL) {
618                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data to work on");
619                 return OPERATOR_CANCELLED;
620         }
621
622         gp_layer_to_curve(C, gpd, gpl, mode);
623
624         /* notifiers */
625         WM_event_add_notifier(C, NC_OBJECT|NA_ADDED, NULL);
626         WM_event_add_notifier(C, NC_SCENE|ND_OB_ACTIVE, scene);
627
628         /* done */
629         return OPERATOR_FINISHED;
630 }
631
632 void GPENCIL_OT_convert(wmOperatorType *ot)
633 {
634         /* identifiers */
635         ot->name = "Convert Grease Pencil";
636         ot->idname = "GPENCIL_OT_convert";
637         ot->description = "Convert the active Grease Pencil layer to a new Object";
638         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
639         
640         /* callbacks */
641         ot->invoke = WM_menu_invoke;
642         ot->exec = gp_convert_layer_exec;
643         ot->poll = gp_convert_poll;
644         
645         /* flags */
646         ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
647         
648         /* properties */
649         ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_convertmodes, 0, "Type", "");
650 }
651
652 /* ************************************************ */