Merging r39717 through r39983 from trunk into soc-2011-tomato
[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                 /* just add new datablock now */
201                 *gpd_ptr= gpencil_data_addnew("GPencil");
202         }
203         
204         /* notifiers */
205         WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX need a nicer one that will work  
206         
207         return OPERATOR_FINISHED;
208 }
209
210 void GPENCIL_OT_data_add (wmOperatorType *ot)
211 {
212         /* identifiers */
213         ot->name= "Grease Pencil Add New";
214         ot->idname= "GPENCIL_OT_data_add";
215         ot->description= "Add new Grease Pencil datablock";
216         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
217         
218         /* callbacks */
219         ot->exec= gp_data_add_exec;
220         ot->poll= gp_add_poll;
221 }
222
223 /* ******************* Unlink Data ************************ */
224
225 /* poll callback for adding data/layers - special */
226 static int gp_data_unlink_poll (bContext *C)
227 {
228         bGPdata **gpd_ptr= gpencil_data_get_pointers(C, NULL);
229         
230         /* if we have access to some active data, make sure there's a datablock before enabling this */
231         return (gpd_ptr && *gpd_ptr);
232 }
233
234
235 /* unlink datablock - wrapper around API */
236 static int gp_data_unlink_exec (bContext *C, wmOperator *op)
237 {
238         bGPdata **gpd_ptr= gpencil_data_get_pointers(C, NULL);
239         
240         if (gpd_ptr == NULL) {
241                 BKE_report(op->reports, RPT_ERROR, "Nowhere for Grease Pencil data to go");
242                 return OPERATOR_CANCELLED;
243         }
244         else {
245                 /* just unlink datablock now, decreasing its user count */
246                 bGPdata *gpd= (*gpd_ptr);
247                 
248                 gpd->id.us--;
249                 *gpd_ptr= NULL;
250         }
251         
252         /* notifiers */
253         WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX need a nicer one that will work  
254         
255         return OPERATOR_FINISHED;
256 }
257
258 void GPENCIL_OT_data_unlink (wmOperatorType *ot)
259 {
260         /* identifiers */
261         ot->name= "Grease Pencil Unlink";
262         ot->idname= "GPENCIL_OT_data_unlink";
263         ot->description= "Unlink active Grease Pencil datablock";
264         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
265         
266         /* callbacks */
267         ot->exec= gp_data_unlink_exec;
268         ot->poll= gp_data_unlink_poll;
269 }
270
271 /* ******************* Add New Layer ************************ */
272
273 /* add new layer - wrapper around API */
274 static int gp_layer_add_exec (bContext *C, wmOperator *op)
275 {
276         bGPdata **gpd_ptr= gpencil_data_get_pointers(C, NULL);
277         
278         /* if there's no existing Grease-Pencil data there, add some */
279         if (gpd_ptr == NULL) {
280                 BKE_report(op->reports, RPT_ERROR, "Nowhere for Grease Pencil data to go");
281                 return OPERATOR_CANCELLED;
282         }
283         if (*gpd_ptr == NULL)
284                 *gpd_ptr= gpencil_data_addnew("GPencil");
285                 
286         /* add new layer now */
287         gpencil_layer_addnew(*gpd_ptr);
288         
289         /* notifiers */
290         WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX please work!
291         
292         return OPERATOR_FINISHED;
293 }
294
295 void GPENCIL_OT_layer_add (wmOperatorType *ot)
296 {
297         /* identifiers */
298         ot->name= "Add New Layer";
299         ot->idname= "GPENCIL_OT_layer_add";
300         ot->description= "Add new Grease Pencil layer for the active Grease Pencil datablock";
301         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
302         
303         /* callbacks */
304         ot->exec= gp_layer_add_exec;
305         ot->poll= gp_add_poll;
306 }
307
308 /* ******************* Delete Active Frame ************************ */
309
310 static int gp_actframe_delete_poll (bContext *C)
311 {
312         bGPdata *gpd= gpencil_data_get_active(C);
313         bGPDlayer *gpl= gpencil_layer_getactive(gpd);
314         
315         /* only if there's an active layer with an active frame */
316         return (gpl && gpl->actframe);
317 }
318
319 /* delete active frame - wrapper around API calls */
320 static int gp_actframe_delete_exec (bContext *C, wmOperator *op)
321 {
322         Scene *scene= CTX_data_scene(C);
323         bGPdata *gpd= gpencil_data_get_active(C);
324         bGPDlayer *gpl= gpencil_layer_getactive(gpd);
325         bGPDframe *gpf= gpencil_layer_getframe(gpl, CFRA, 0);
326         
327         /* if there's no existing Grease-Pencil data there, add some */
328         if (gpd == NULL) {
329                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
330                 return OPERATOR_CANCELLED;
331         }
332         if ELEM(NULL, gpl, gpf) {
333                 BKE_report(op->reports, RPT_ERROR, "No active frame to delete");
334                 return OPERATOR_CANCELLED;
335         }
336         
337         /* delete it... */
338         gpencil_layer_delframe(gpl, gpf);
339         
340         /* notifiers */
341         WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX please work!
342         
343         return OPERATOR_FINISHED;
344 }
345
346 void GPENCIL_OT_active_frame_delete (wmOperatorType *ot)
347 {
348         /* identifiers */
349         ot->name= "Delete Active Frame";
350         ot->idname= "GPENCIL_OT_active_frame_delete";
351         ot->description= "Delete the active frame for the active Grease Pencil datablock";
352         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
353         
354         /* callbacks */
355         ot->exec= gp_actframe_delete_exec;
356         ot->poll= gp_actframe_delete_poll;
357 }
358
359 /* ************************************************ */
360 /* Grease Pencil to Data Operator */
361
362 /* defines for possible modes */
363 enum {
364         GP_STROKECONVERT_PATH = 1,
365         GP_STROKECONVERT_CURVE,
366 };
367
368 /* RNA enum define */
369 static EnumPropertyItem prop_gpencil_convertmodes[] = {
370         {GP_STROKECONVERT_PATH, "PATH", 0, "Path", ""},
371         {GP_STROKECONVERT_CURVE, "CURVE", 0, "Bezier Curve", ""},
372         {0, NULL, 0, NULL, NULL}
373 };
374
375 /* --- */
376
377 /* convert the coordinates from the given stroke point into 3d-coordinates 
378  *      - assumes that the active space is the 3D-View
379  */
380 static void gp_strokepoint_convertcoords (bContext *C, bGPDstroke *gps, bGPDspoint *pt, float p3d[3], rctf *subrect)
381 {
382         Scene *scene= CTX_data_scene(C);
383         View3D *v3d= CTX_wm_view3d(C);
384         ARegion *ar= CTX_wm_region(C);
385         
386         if (gps->flag & GP_STROKE_3DSPACE) {
387                 /* directly use 3d-coordinates */
388                 copy_v3_v3(p3d, &pt->x);
389         }
390         else {
391                 float *fp= give_cursor(scene, v3d);
392                 float mvalf[2];
393                 
394                 /* get screen coordinate */
395                 if (gps->flag & GP_STROKE_2DSPACE) {
396                         int mvali[2];
397                         View2D *v2d= &ar->v2d;
398                         UI_view2d_view_to_region(v2d, pt->x, pt->y, mvali, mvali+1);
399                         VECCOPY2D(mvalf, mvali);
400                 }
401                 else {
402                         if(subrect) {
403                                 mvalf[0]= (((float)pt->x/100.0f) * (subrect->xmax - subrect->xmin)) + subrect->xmin;
404                                 mvalf[1]= (((float)pt->y/100.0f) * (subrect->ymax - subrect->ymin)) + subrect->ymin;
405                         }
406                         else {
407                                 mvalf[0]= (float)pt->x / 100.0f * ar->winx;
408                                 mvalf[1]= (float)pt->y / 100.0f * ar->winy;
409                         }
410                 }
411
412                 /* convert screen coordinate to 3d coordinates 
413                  *      - method taken from editview.c - mouse_cursor() 
414                  */
415                 ED_view3d_win_to_3d(ar, fp, mvalf, p3d);
416         }
417 }
418
419 /* --- */
420
421 /* convert stroke to 3d path */
422 static void gp_stroke_to_path (bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect)
423 {
424         bGPDspoint *pt;
425         Nurb *nu;
426         BPoint *bp;
427         int i;
428
429         /* create new 'nurb' within the curve */
430         nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_path(nurb)");
431         
432         nu->pntsu= gps->totpoints;
433         nu->pntsv= 1;
434         nu->orderu= gps->totpoints;
435         nu->flagu= CU_NURB_ENDPOINT;
436         nu->resolu= 32;
437         
438         nu->bp= (BPoint *)MEM_callocN(sizeof(BPoint)*gps->totpoints, "bpoints");
439         
440         /* add points */
441         for (i=0, pt=gps->points, bp=nu->bp; i < gps->totpoints; i++, pt++, bp++) {
442                 float p3d[3];
443                 
444                 /* get coordinates to add at */
445                 gp_strokepoint_convertcoords(C, gps, pt, p3d, subrect);
446                 copy_v3_v3(bp->vec, p3d);
447                 
448                 /* set settings */
449                 bp->f1= SELECT;
450                 bp->radius = bp->weight = pt->pressure * gpl->thickness;
451         }
452         
453         /* add nurb to curve */
454         BLI_addtail(&cu->nurb, nu);
455 }
456
457 static int gp_camera_view_subrect(bContext *C, rctf *subrect)
458 {
459         View3D *v3d= CTX_wm_view3d(C);
460         ARegion *ar= CTX_wm_region(C);
461
462         if (v3d) {
463                 RegionView3D *rv3d= ar->regiondata;
464
465                 /* for camera view set the subrect */
466                 if (rv3d->persp == RV3D_CAMOB) {
467                         Scene *scene= CTX_data_scene(C);
468                         ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, subrect, -1); /* negative shift */
469                         return 1;
470                 }
471         }
472
473         return 0;
474 }
475
476 /* convert stroke to 3d bezier */
477 static void gp_stroke_to_bezier (bContext *C, bGPDlayer *gpl, bGPDstroke *gps, Curve *cu, rctf *subrect)
478 {
479         bGPDspoint *pt;
480         Nurb *nu;
481         BezTriple *bezt;
482         int i, tot;
483         float p3d_cur[3], p3d_prev[3], p3d_next[3];
484
485         /* create new 'nurb' within the curve */
486         nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_bezier(nurb)");
487
488         nu->pntsu= gps->totpoints;
489         nu->resolu= 12;
490         nu->resolv= 12;
491         nu->type= CU_BEZIER;
492         nu->bezt = (BezTriple *)MEM_callocN(gps->totpoints*sizeof(BezTriple), "bezts");
493
494         tot= gps->totpoints;
495
496         /* get initial coordinates */
497         pt=gps->points;
498         if (tot) {
499                 gp_strokepoint_convertcoords(C, gps, pt, p3d_cur, subrect);
500                 if (tot > 1) {
501                         gp_strokepoint_convertcoords(C, gps, pt+1, p3d_next, subrect);
502                 }
503         }
504
505         /* add points */
506         for (i=0, bezt=nu->bezt; i < tot; i++, pt++, bezt++) {
507                 float h1[3], h2[3];
508
509                 if (i) interp_v3_v3v3(h1, p3d_cur, p3d_prev, 0.3);
510                 else interp_v3_v3v3(h1, p3d_cur, p3d_next, -0.3);
511
512                 if (i < tot-1) interp_v3_v3v3(h2, p3d_cur, p3d_next, 0.3);
513                 else interp_v3_v3v3(h2, p3d_cur, p3d_prev, -0.3);
514
515                 copy_v3_v3(bezt->vec[0], h1);
516                 copy_v3_v3(bezt->vec[1], p3d_cur);
517                 copy_v3_v3(bezt->vec[2], h2);
518
519                 /* set settings */
520                 bezt->h1= bezt->h2= HD_FREE;
521                 bezt->f1= bezt->f2= bezt->f3= SELECT;
522                 bezt->radius = bezt->weight = pt->pressure * gpl->thickness * 0.1f;
523
524                 /* shift coord vects */
525                 copy_v3_v3(p3d_prev, p3d_cur);
526                 copy_v3_v3(p3d_cur, p3d_next);
527
528                 if (i + 2 < tot) {
529                         gp_strokepoint_convertcoords(C, gps, pt + 2, p3d_next, subrect);
530                 }
531         }
532
533         /* must calculate handles or else we crash */
534         calchandlesNurb(nu);
535
536         /* add nurb to curve */
537         BLI_addtail(&cu->nurb, nu);
538 }
539
540 /* convert a given grease-pencil layer to a 3d-curve representation (using current view if appropriate) */
541 static void gp_layer_to_curve (bContext *C, bGPdata *gpd, bGPDlayer *gpl, short mode)
542 {
543         Scene *scene= CTX_data_scene(C);
544         bGPDframe *gpf= gpencil_layer_getframe(gpl, CFRA, 0);
545         bGPDstroke *gps;
546         Object *ob;
547         Curve *cu;
548
549         /* camera framing */
550         rctf subrect, *subrect_ptr= NULL;
551
552         /* error checking */
553         if (ELEM3(NULL, gpd, gpl, gpf))
554                 return;
555                 
556         /* only convert if there are any strokes on this layer's frame to convert */
557         if (gpf->strokes.first == NULL)
558                 return;
559
560         /* initialize camera framing */
561         if(gp_camera_view_subrect(C, &subrect)) {
562                 subrect_ptr= &subrect;
563         }
564
565         /* init the curve object (remove rotation and get curve data from it)
566          *      - must clear transforms set on object, as those skew our results
567          */
568         ob= add_object(scene, OB_CURVE);
569         ob->loc[0]= ob->loc[1]= ob->loc[2]= 0;
570         ob->rot[0]= ob->rot[1]= ob->rot[2]= 0;
571         cu= ob->data;
572         cu->flag |= CU_3D;
573         
574         /* rename object and curve to layer name */
575         rename_id((ID *)ob, gpl->info);
576         rename_id((ID *)cu, gpl->info);
577         
578         /* add points to curve */
579         for (gps= gpf->strokes.first; gps; gps= gps->next) {
580                 switch (mode) {
581                         case GP_STROKECONVERT_PATH: 
582                                 gp_stroke_to_path(C, gpl, gps, cu, subrect_ptr);
583                                 break;
584                         case GP_STROKECONVERT_CURVE:
585                                 gp_stroke_to_bezier(C, gpl, gps, cu, subrect_ptr);
586                                 break;
587                 }
588         }
589 }
590
591 /* --- */
592
593 static int gp_convert_poll (bContext *C)
594 {
595         bGPdata *gpd= gpencil_data_get_active(C);
596         ScrArea *sa= CTX_wm_area(C);
597         Scene *scene= CTX_data_scene(C);
598
599         /* only if there's valid data, and the current view is 3D View */
600         return ((sa && sa->spacetype == SPACE_VIEW3D) && gpencil_layer_getactive(gpd) && (scene->obedit == NULL));
601 }
602
603 static int gp_convert_layer_exec (bContext *C, wmOperator *op)
604 {
605         bGPdata *gpd= gpencil_data_get_active(C);
606         bGPDlayer *gpl= gpencil_layer_getactive(gpd);
607         Scene *scene= CTX_data_scene(C);
608         int mode= RNA_enum_get(op->ptr, "type");
609
610         /* check if there's data to work with */
611         if (gpd == NULL) {
612                 BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data to work on.");
613                 return OPERATOR_CANCELLED;
614         }
615
616         /* handle conversion modes */
617         switch (mode) {
618                 case GP_STROKECONVERT_PATH:
619                 case GP_STROKECONVERT_CURVE:
620                         gp_layer_to_curve(C, gpd, gpl, mode);
621                         break;
622                         
623                 default: /* unsupoorted */
624                         BKE_report(op->reports, RPT_ERROR, "Unknown conversion option.");
625                         return OPERATOR_CANCELLED;
626         }
627
628         /* notifiers */
629         WM_event_add_notifier(C, NC_OBJECT|NA_ADDED, NULL);
630         WM_event_add_notifier(C, NC_SCENE|ND_OB_ACTIVE, scene);
631
632         /* done */
633         return OPERATOR_FINISHED;
634 }
635
636 void GPENCIL_OT_convert (wmOperatorType *ot)
637 {
638         /* identifiers */
639         ot->name= "Convert Grease Pencil";
640         ot->idname= "GPENCIL_OT_convert";
641         ot->description= "Convert the active Grease Pencil layer to a new Object";
642         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
643         
644         /* callbacks */
645         ot->invoke= WM_menu_invoke;
646         ot->exec= gp_convert_layer_exec;
647         ot->poll= gp_convert_poll;
648         
649         /* flags */
650         ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
651         
652         /* properties */
653         ot->prop= RNA_def_enum(ot->srna, "type", prop_gpencil_convertmodes, 0, "Type", "");
654 }
655
656 /* ************************************************ */