|
Blender
V2.59
|
00001 /* 00002 * $Id: gpencil_paint.c 39304 2011-08-11 13:40:47Z nazgul $ 00003 * 00004 * ***** BEGIN GPL LICENSE BLOCK ***** 00005 * 00006 * This program is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU General Public License 00008 * as published by the Free Software Foundation; either version 2 00009 * of the License, or (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License 00017 * along with this program; if not, write to the Free Software Foundation, 00018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 * 00020 * The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung 00021 * This is a new part of Blender 00022 * 00023 * Contributor(s): Joshua Leung 00024 * 00025 * ***** END GPL LICENSE BLOCK ***** 00026 */ 00027 00033 #include <stdio.h> 00034 #include <stddef.h> 00035 #include <stdlib.h> 00036 #include <string.h> 00037 #include <math.h> 00038 00039 #include "MEM_guardedalloc.h" 00040 00041 #include "BLI_blenlib.h" 00042 #include "BLI_math.h" 00043 #include "BLI_utildefines.h" 00044 00045 #include "BKE_gpencil.h" 00046 #include "BKE_context.h" 00047 #include "BKE_global.h" 00048 #include "BKE_report.h" 00049 00050 #include "DNA_object_types.h" 00051 #include "DNA_scene_types.h" 00052 #include "DNA_gpencil_types.h" 00053 #include "DNA_windowmanager_types.h" 00054 00055 #include "UI_view2d.h" 00056 00057 #include "ED_gpencil.h" 00058 #include "ED_screen.h" 00059 #include "ED_view3d.h" 00060 00061 #include "RNA_access.h" 00062 00063 #include "RNA_define.h" 00064 #include "WM_api.h" 00065 #include "WM_types.h" 00066 00067 #include "gpencil_intern.h" 00068 00069 /* ******************************************* */ 00070 /* 'Globals' and Defines */ 00071 00072 /* Temporary 'Stroke' Operation data */ 00073 typedef struct tGPsdata { 00074 Scene *scene; /* current scene from context */ 00075 00076 wmWindow *win; /* window where painting originated */ 00077 ScrArea *sa; /* area where painting originated */ 00078 ARegion *ar; /* region where painting originated */ 00079 View2D *v2d; /* needed for GP_STROKE_2DSPACE */ 00080 rctf *subrect; /* for using the camera rect within the 3d view */ 00081 rctf subrect_data; 00082 00083 00084 #if 0 // XXX review this 2d image stuff... 00085 ImBuf *ibuf; /* needed for GP_STROKE_2DIMAGE */ 00086 struct IBufViewSettings { 00087 int offsx, offsy; /* offsets */ 00088 int sizex, sizey; /* dimensions to use as scale-factor */ 00089 } im2d_settings; /* needed for GP_STROKE_2DIMAGE */ 00090 #endif 00091 00092 PointerRNA ownerPtr;/* pointer to owner of gp-datablock */ 00093 bGPdata *gpd; /* gp-datablock layer comes from */ 00094 bGPDlayer *gpl; /* layer we're working on */ 00095 bGPDframe *gpf; /* frame we're working on */ 00096 00097 short status; /* current status of painting */ 00098 short paintmode; /* mode for painting */ 00099 00100 int mval[2]; /* current mouse-position */ 00101 int mvalo[2]; /* previous recorded mouse-position */ 00102 00103 float pressure; /* current stylus pressure */ 00104 float opressure; /* previous stylus pressure */ 00105 00106 short radius; /* radius of influence for eraser */ 00107 short flags; /* flags that can get set during runtime */ 00108 } tGPsdata; 00109 00110 /* values for tGPsdata->status */ 00111 enum { 00112 GP_STATUS_IDLING = 0, /* stroke isn't in progress yet */ 00113 GP_STATUS_PAINTING, /* a stroke is in progress */ 00114 GP_STATUS_ERROR, /* something wasn't correctly set up */ 00115 GP_STATUS_DONE /* painting done */ 00116 }; 00117 00118 /* Return flags for adding points to stroke buffer */ 00119 enum { 00120 GP_STROKEADD_INVALID = -2, /* error occurred - insufficient info to do so */ 00121 GP_STROKEADD_OVERFLOW = -1, /* error occurred - cannot fit any more points */ 00122 GP_STROKEADD_NORMAL, /* point was successfully added */ 00123 GP_STROKEADD_FULL /* cannot add any more points to buffer */ 00124 }; 00125 00126 /* Runtime flags */ 00127 enum { 00128 GP_PAINTFLAG_FIRSTRUN = (1<<0), /* operator just started */ 00129 }; 00130 00131 /* ------ */ 00132 00133 /* maximum sizes of gp-session buffer */ 00134 #define GP_STROKE_BUFFER_MAX 5000 00135 00136 /* Macros for accessing sensitivity thresholds... */ 00137 /* minimum number of pixels mouse should move before new point created */ 00138 #define MIN_MANHATTEN_PX (U.gp_manhattendist) 00139 /* minimum length of new segment before new point can be added */ 00140 #define MIN_EUCLIDEAN_PX (U.gp_euclideandist) 00141 00142 /* ------ */ 00143 /* Forward defines for some functions... */ 00144 00145 static void gp_session_validatebuffer(tGPsdata *p); 00146 00147 /* ******************************************* */ 00148 /* Context Wrangling... */ 00149 00150 /* check if context is suitable for drawing */ 00151 static int gpencil_draw_poll (bContext *C) 00152 { 00153 if (ED_operator_regionactive(C)) { 00154 /* check if current context can support GPencil data */ 00155 if (gpencil_data_get_pointers(C, NULL) != NULL) { 00156 /* check if Grease Pencil isn't already running */ 00157 if ((G.f & G_GREASEPENCIL) == 0) 00158 return 1; 00159 else 00160 CTX_wm_operator_poll_msg_set(C, "Grease Pencil operator is already active"); 00161 } 00162 else { 00163 CTX_wm_operator_poll_msg_set(C, "Failed to find Grease Pencil data to draw into"); 00164 } 00165 } 00166 else { 00167 CTX_wm_operator_poll_msg_set(C, "Active region not set"); 00168 } 00169 00170 return 0; 00171 } 00172 00173 /* check if projecting strokes into 3d-geometry in the 3D-View */ 00174 static int gpencil_project_check (tGPsdata *p) 00175 { 00176 bGPdata *gpd= p->gpd; 00177 return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (p->gpd->flag & (GP_DATA_DEPTH_VIEW | GP_DATA_DEPTH_STROKE))); 00178 } 00179 00180 /* ******************************************* */ 00181 /* Calculations/Conversions */ 00182 00183 /* Utilities --------------------------------- */ 00184 00185 /* get the reference point for stroke-point conversions */ 00186 static void gp_get_3d_reference (tGPsdata *p, float *vec) 00187 { 00188 View3D *v3d= p->sa->spacedata.first; 00189 float *fp= give_cursor(p->scene, v3d); 00190 00191 /* the reference point used depends on the owner... */ 00192 #if 0 // XXX: disabled for now, since we can't draw relative to the owner yet 00193 if (p->ownerPtr.type == &RNA_Object) 00194 { 00195 Object *ob= (Object *)p->ownerPtr.data; 00196 00197 /* active Object 00198 * - use relative distance of 3D-cursor from object center 00199 */ 00200 sub_v3_v3v3(vec, fp, ob->loc); 00201 } 00202 else 00203 #endif 00204 { 00205 /* use 3D-cursor */ 00206 copy_v3_v3(vec, fp); 00207 } 00208 } 00209 00210 /* Stroke Editing ---------------------------- */ 00211 00212 /* check if the current mouse position is suitable for adding a new point */ 00213 static short gp_stroke_filtermval (tGPsdata *p, const int mval[2], int pmval[2]) 00214 { 00215 int dx= abs(mval[0] - pmval[0]); 00216 int dy= abs(mval[1] - pmval[1]); 00217 00218 /* if buffer is empty, just let this go through (i.e. so that dots will work) */ 00219 if (p->gpd->sbuffer_size == 0) 00220 return 1; 00221 00222 /* check if mouse moved at least certain distance on both axes (best case) 00223 * - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand 00224 */ 00225 else if ((dx > MIN_MANHATTEN_PX) && (dy > MIN_MANHATTEN_PX)) 00226 return 1; 00227 00228 /* check if the distance since the last point is significant enough 00229 * - prevents points being added too densely 00230 * - distance here doesn't use sqrt to prevent slowness... we should still be safe from overflows though 00231 */ 00232 else if ((dx*dx + dy*dy) > MIN_EUCLIDEAN_PX*MIN_EUCLIDEAN_PX) 00233 return 1; 00234 00235 /* mouse 'didn't move' */ 00236 else 00237 return 0; 00238 } 00239 00240 /* convert screen-coordinates to buffer-coordinates */ 00241 // XXX this method needs a total overhaul! 00242 static void gp_stroke_convertcoords (tGPsdata *p, const int mval[2], float out[3], float *depth) 00243 { 00244 bGPdata *gpd= p->gpd; 00245 00246 /* in 3d-space - pt->x/y/z are 3 side-by-side floats */ 00247 if (gpd->sbuffer_sflag & GP_STROKE_3DSPACE) { 00248 if (gpencil_project_check(p) && (ED_view3d_autodist_simple(p->ar, mval, out, 0, depth))) { 00249 /* projecting onto 3D-Geometry 00250 * - nothing more needs to be done here, since view_autodist_simple() has already done it 00251 */ 00252 } 00253 else { 00254 int mval_prj[2]; 00255 float rvec[3], dvec[3]; 00256 float mval_f[2]; 00257 00258 /* Current method just converts each point in screen-coordinates to 00259 * 3D-coordinates using the 3D-cursor as reference. In general, this 00260 * works OK, but it could of course be improved. 00261 * 00262 * TODO: 00263 * - investigate using nearest point(s) on a previous stroke as 00264 * reference point instead or as offset, for easier stroke matching 00265 */ 00266 00267 gp_get_3d_reference(p, rvec); 00268 00269 /* method taken from editview.c - mouse_cursor() */ 00270 project_int_noclip(p->ar, rvec, mval_prj); 00271 00272 VECSUB2D(mval_f, mval_prj, mval); 00273 ED_view3d_win_to_delta(p->ar, mval_f, dvec); 00274 sub_v3_v3v3(out, rvec, dvec); 00275 } 00276 } 00277 00278 /* 2d - on 'canvas' (assume that p->v2d is set) */ 00279 else if ((gpd->sbuffer_sflag & GP_STROKE_2DSPACE) && (p->v2d)) { 00280 UI_view2d_region_to_view(p->v2d, mval[0], mval[1], &out[0], &out[1]); 00281 } 00282 00283 #if 0 00284 /* 2d - on image 'canvas' (assume that p->v2d is set) */ 00285 else if (gpd->sbuffer_sflag & GP_STROKE_2DIMAGE) { 00286 int sizex, sizey, offsx, offsy; 00287 00288 /* get stored settings 00289 * - assume that these have been set already (there are checks that set sane 'defaults' just in case) 00290 */ 00291 sizex= p->im2d_settings.sizex; 00292 sizey= p->im2d_settings.sizey; 00293 offsx= p->im2d_settings.offsx; 00294 offsy= p->im2d_settings.offsy; 00295 00296 /* calculate new points */ 00297 out[0]= (float)(mval[0] - offsx) / (float)sizex; 00298 out[1]= (float)(mval[1] - offsy) / (float)sizey; 00299 } 00300 #endif 00301 00302 /* 2d - relative to screen (viewport area) */ 00303 else { 00304 if (p->subrect == NULL) { /* normal 3D view */ 00305 out[0] = (float)(mval[0]) / (float)(p->ar->winx) * 100; 00306 out[1] = (float)(mval[1]) / (float)(p->ar->winy) * 100; 00307 } 00308 else { /* camera view, use subrect */ 00309 out[0]= ((mval[0] - p->subrect->xmin) / ((p->subrect->xmax - p->subrect->xmin))) * 100; 00310 out[1]= ((mval[1] - p->subrect->ymin) / ((p->subrect->ymax - p->subrect->ymin))) * 100; 00311 } 00312 } 00313 } 00314 00315 /* add current stroke-point to buffer (returns whether point was successfully added) */ 00316 static short gp_stroke_addpoint (tGPsdata *p, const int mval[2], float pressure) 00317 { 00318 bGPdata *gpd= p->gpd; 00319 tGPspoint *pt; 00320 00321 /* check painting mode */ 00322 if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { 00323 /* straight lines only - i.e. only store start and end point in buffer */ 00324 if (gpd->sbuffer_size == 0) { 00325 /* first point in buffer (start point) */ 00326 pt= (tGPspoint *)(gpd->sbuffer); 00327 00328 /* store settings */ 00329 pt->x= mval[0]; 00330 pt->y= mval[1]; 00331 pt->pressure= pressure; 00332 00333 /* increment buffer size */ 00334 gpd->sbuffer_size++; 00335 } 00336 else { 00337 /* normally, we just reset the endpoint to the latest value 00338 * - assume that pointers for this are always valid... 00339 */ 00340 pt= ((tGPspoint *)(gpd->sbuffer) + 1); 00341 00342 /* store settings */ 00343 pt->x= mval[0]; 00344 pt->y= mval[1]; 00345 pt->pressure= pressure; 00346 00347 /* if this is just the second point we've added, increment the buffer size 00348 * so that it will be drawn properly... 00349 * otherwise, just leave it alone, otherwise we get problems 00350 */ 00351 if (gpd->sbuffer_size != 2) 00352 gpd->sbuffer_size= 2; 00353 } 00354 00355 /* can keep carrying on this way :) */ 00356 return GP_STROKEADD_NORMAL; 00357 } 00358 else if (p->paintmode == GP_PAINTMODE_DRAW) { /* normal drawing */ 00359 /* check if still room in buffer */ 00360 if (gpd->sbuffer_size >= GP_STROKE_BUFFER_MAX) 00361 return GP_STROKEADD_OVERFLOW; 00362 00363 /* get pointer to destination point */ 00364 pt= ((tGPspoint *)(gpd->sbuffer) + gpd->sbuffer_size); 00365 00366 /* store settings */ 00367 pt->x= mval[0]; 00368 pt->y= mval[1]; 00369 pt->pressure= pressure; 00370 00371 /* increment counters */ 00372 gpd->sbuffer_size++; 00373 00374 /* check if another operation can still occur */ 00375 if (gpd->sbuffer_size == GP_STROKE_BUFFER_MAX) 00376 return GP_STROKEADD_FULL; 00377 else 00378 return GP_STROKEADD_NORMAL; 00379 } 00380 00381 /* return invalid state for now... */ 00382 return GP_STROKEADD_INVALID; 00383 } 00384 00385 00386 /* temp struct for gp_stroke_smooth() */ 00387 typedef struct tGpSmoothCo { 00388 int x; 00389 int y; 00390 } tGpSmoothCo; 00391 00392 /* smooth a stroke (in buffer) before storing it */ 00393 static void gp_stroke_smooth (tGPsdata *p) 00394 { 00395 bGPdata *gpd= p->gpd; 00396 tGpSmoothCo *smoothArray, *spc; 00397 int i=0, cmx=gpd->sbuffer_size; 00398 00399 /* only smooth if smoothing is enabled, and we're not doing a straight line */ 00400 if (!(U.gp_settings & GP_PAINT_DOSMOOTH) || (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT)) 00401 return; 00402 00403 /* don't try if less than 2 points in buffer */ 00404 if ((cmx <= 2) || (gpd->sbuffer == NULL)) 00405 return; 00406 00407 /* create a temporary smoothing coordinates buffer, use to store calculated values to prevent sequential error */ 00408 smoothArray = MEM_callocN(sizeof(tGpSmoothCo)*cmx, "gp_stroke_smooth smoothArray"); 00409 00410 /* first pass: calculate smoothing coordinates using weighted-averages */ 00411 for (i=0, spc=smoothArray; i < gpd->sbuffer_size; i++, spc++) { 00412 const tGPspoint *pc= (((tGPspoint *)gpd->sbuffer) + i); 00413 const tGPspoint *pb= (i-1 > 0)?(pc-1):(pc); 00414 const tGPspoint *pa= (i-2 > 0)?(pc-2):(pb); 00415 const tGPspoint *pd= (i+1 < cmx)?(pc+1):(pc); 00416 const tGPspoint *pe= (i+2 < cmx)?(pc+2):(pd); 00417 00418 spc->x= (int)(0.1*pa->x + 0.2*pb->x + 0.4*pc->x + 0.2*pd->x + 0.1*pe->x); 00419 spc->y= (int)(0.1*pa->y + 0.2*pb->y + 0.4*pc->y + 0.2*pd->y + 0.1*pe->y); 00420 } 00421 00422 /* second pass: apply smoothed coordinates */ 00423 for (i=0, spc=smoothArray; i < gpd->sbuffer_size; i++, spc++) { 00424 tGPspoint *pc= (((tGPspoint *)gpd->sbuffer) + i); 00425 00426 pc->x = spc->x; 00427 pc->y = spc->y; 00428 } 00429 00430 /* free temp array */ 00431 MEM_freeN(smoothArray); 00432 } 00433 00434 /* simplify a stroke (in buffer) before storing it 00435 * - applies a reverse Chaikin filter 00436 * - code adapted from etch-a-ton branch (editarmature_sketch.c) 00437 */ 00438 static void gp_stroke_simplify (tGPsdata *p) 00439 { 00440 bGPdata *gpd= p->gpd; 00441 tGPspoint *old_points= (tGPspoint *)gpd->sbuffer; 00442 short num_points= gpd->sbuffer_size; 00443 short flag= gpd->sbuffer_sflag; 00444 short i, j; 00445 00446 /* only simplify if simplification is enabled, and we're not doing a straight line */ 00447 if (!(U.gp_settings & GP_PAINT_DOSIMPLIFY) || (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT)) 00448 return; 00449 00450 /* don't simplify if less than 4 points in buffer */ 00451 if ((num_points <= 4) || (old_points == NULL)) 00452 return; 00453 00454 /* clear buffer (but don't free mem yet) so that we can write to it 00455 * - firstly set sbuffer to NULL, so a new one is allocated 00456 * - secondly, reset flag after, as it gets cleared auto 00457 */ 00458 gpd->sbuffer= NULL; 00459 gp_session_validatebuffer(p); 00460 gpd->sbuffer_sflag = flag; 00461 00462 /* macro used in loop to get position of new point 00463 * - used due to the mixture of datatypes in use here 00464 */ 00465 #define GP_SIMPLIFY_AVPOINT(offs, sfac) \ 00466 { \ 00467 co[0] += (float)(old_points[offs].x * sfac); \ 00468 co[1] += (float)(old_points[offs].y * sfac); \ 00469 pressure += old_points[offs].pressure * sfac; \ 00470 } 00471 00472 for (i = 0, j = 0; i < num_points; i++) 00473 { 00474 if (i - j == 3) 00475 { 00476 float co[2], pressure; 00477 int mco[2]; 00478 00479 /* initialise values */ 00480 co[0]= 0; 00481 co[1]= 0; 00482 pressure = 0; 00483 00484 /* using macro, calculate new point */ 00485 GP_SIMPLIFY_AVPOINT(j, -0.25f); 00486 GP_SIMPLIFY_AVPOINT(j+1, 0.75f); 00487 GP_SIMPLIFY_AVPOINT(j+2, 0.75f); 00488 GP_SIMPLIFY_AVPOINT(j+3, -0.25f); 00489 00490 /* set values for adding */ 00491 mco[0]= (int)co[0]; 00492 mco[1]= (int)co[1]; 00493 00494 /* ignore return values on this... assume to be ok for now */ 00495 gp_stroke_addpoint(p, mco, pressure); 00496 00497 j += 2; 00498 } 00499 } 00500 00501 /* free old buffer */ 00502 MEM_freeN(old_points); 00503 } 00504 00505 00506 /* make a new stroke from the buffer data */ 00507 static void gp_stroke_newfrombuffer (tGPsdata *p) 00508 { 00509 bGPdata *gpd= p->gpd; 00510 bGPDstroke *gps; 00511 bGPDspoint *pt; 00512 tGPspoint *ptc; 00513 int i, totelem; 00514 /* since strokes are so fine, when using their depth we need a margin otherwise they might get missed */ 00515 int depth_margin = (p->gpd->flag & GP_DATA_DEPTH_STROKE) ? 4 : 0; 00516 00517 /* get total number of points to allocate space for 00518 * - drawing straight-lines only requires the endpoints 00519 */ 00520 if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) 00521 totelem = (gpd->sbuffer_size >= 2) ? 2: gpd->sbuffer_size; 00522 else 00523 totelem = gpd->sbuffer_size; 00524 00525 /* exit with error if no valid points from this stroke */ 00526 if (totelem == 0) { 00527 if (G.f & G_DEBUG) 00528 printf("Error: No valid points in stroke buffer to convert (tot=%d) \n", gpd->sbuffer_size); 00529 return; 00530 } 00531 00532 /* allocate memory for a new stroke */ 00533 gps= MEM_callocN(sizeof(bGPDstroke), "gp_stroke"); 00534 00535 /* allocate enough memory for a continuous array for storage points */ 00536 pt= gps->points= MEM_callocN(sizeof(bGPDspoint)*totelem, "gp_stroke_points"); 00537 00538 /* copy appropriate settings for stroke */ 00539 gps->totpoints= totelem; 00540 gps->thickness= p->gpl->thickness; 00541 gps->flag= gpd->sbuffer_sflag; 00542 00543 /* copy points from the buffer to the stroke */ 00544 if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { 00545 /* straight lines only -> only endpoints */ 00546 { 00547 /* first point */ 00548 ptc= gpd->sbuffer; 00549 00550 /* convert screen-coordinates to appropriate coordinates (and store them) */ 00551 gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); 00552 00553 /* copy pressure */ 00554 pt->pressure= ptc->pressure; 00555 00556 pt++; 00557 } 00558 00559 if (totelem == 2) { 00560 /* last point if applicable */ 00561 ptc= ((tGPspoint *)gpd->sbuffer) + (gpd->sbuffer_size - 1); 00562 00563 /* convert screen-coordinates to appropriate coordinates (and store them) */ 00564 gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); 00565 00566 /* copy pressure */ 00567 pt->pressure= ptc->pressure; 00568 } 00569 } 00570 else { 00571 float *depth_arr= NULL; 00572 00573 /* get an array of depths, far depths are blended */ 00574 if (gpencil_project_check(p)) { 00575 int mval[2], mval_prev[2]= {0}; 00576 int interp_depth = 0; 00577 int found_depth = 0; 00578 00579 depth_arr= MEM_mallocN(sizeof(float) * gpd->sbuffer_size, "depth_points"); 00580 00581 for (i=0, ptc=gpd->sbuffer; i < gpd->sbuffer_size; i++, ptc++, pt++) { 00582 mval[0]= ptc->x; mval[1]= ptc->y; 00583 00584 if ((ED_view3d_autodist_depth(p->ar, mval, depth_margin, depth_arr+i) == 0) && 00585 (i && (ED_view3d_autodist_depth_seg(p->ar, mval, mval_prev, depth_margin + 1, depth_arr+i) == 0)) 00586 ) { 00587 interp_depth= TRUE; 00588 } 00589 else { 00590 found_depth= TRUE; 00591 } 00592 00593 VECCOPY2D(mval_prev, mval); 00594 } 00595 00596 if (found_depth == FALSE) { 00597 /* eeh... not much we can do.. :/, ignore depth in this case, use the 3D cursor */ 00598 for (i=gpd->sbuffer_size-1; i >= 0; i--) 00599 depth_arr[i] = 0.9999f; 00600 } 00601 else { 00602 if (p->gpd->flag & GP_DATA_DEPTH_STROKE_ENDPOINTS) { 00603 /* remove all info between the valid endpoints */ 00604 int first_valid = 0; 00605 int last_valid = 0; 00606 00607 for (i=0; i < gpd->sbuffer_size; i++) { 00608 if (depth_arr[i] != FLT_MAX) 00609 break; 00610 } 00611 first_valid= i; 00612 00613 for (i=gpd->sbuffer_size-1; i >= 0; i--) { 00614 if (depth_arr[i] != FLT_MAX) 00615 break; 00616 } 00617 last_valid= i; 00618 00619 /* invalidate non-endpoints, so only blend between first and last */ 00620 for (i=first_valid+1; i < last_valid; i++) 00621 depth_arr[i]= FLT_MAX; 00622 00623 interp_depth= TRUE; 00624 } 00625 00626 if (interp_depth) { 00627 interp_sparse_array(depth_arr, gpd->sbuffer_size, FLT_MAX); 00628 } 00629 } 00630 } 00631 00632 00633 pt= gps->points; 00634 00635 /* convert all points (normal behaviour) */ 00636 for (i=0, ptc=gpd->sbuffer; i < gpd->sbuffer_size && ptc; i++, ptc++, pt++) { 00637 /* convert screen-coordinates to appropriate coordinates (and store them) */ 00638 gp_stroke_convertcoords(p, &ptc->x, &pt->x, depth_arr ? depth_arr+i:NULL); 00639 00640 /* copy pressure */ 00641 pt->pressure= ptc->pressure; 00642 } 00643 00644 if (depth_arr) 00645 MEM_freeN(depth_arr); 00646 } 00647 00648 /* add stroke to frame */ 00649 BLI_addtail(&p->gpf->strokes, gps); 00650 } 00651 00652 /* --- 'Eraser' for 'Paint' Tool ------ */ 00653 00654 /* eraser tool - remove segment from stroke/split stroke (after lasso inside) */ 00655 static short gp_stroke_eraser_splitdel (bGPDframe *gpf, bGPDstroke *gps, int i) 00656 { 00657 bGPDspoint *pt_tmp= gps->points; 00658 bGPDstroke *gsn = NULL; 00659 00660 /* if stroke only had two points, get rid of stroke */ 00661 if (gps->totpoints == 2) { 00662 /* free stroke points, then stroke */ 00663 MEM_freeN(pt_tmp); 00664 BLI_freelinkN(&gpf->strokes, gps); 00665 00666 /* nothing left in stroke, so stop */ 00667 return 1; 00668 } 00669 00670 /* if last segment, just remove segment from the stroke */ 00671 else if (i == gps->totpoints - 2) { 00672 /* allocate new points array, and assign most of the old stroke there */ 00673 gps->totpoints--; 00674 gps->points= MEM_callocN(sizeof(bGPDspoint)*gps->totpoints, "gp_stroke_points"); 00675 memcpy(gps->points, pt_tmp, sizeof(bGPDspoint)*gps->totpoints); 00676 00677 /* free temp buffer */ 00678 MEM_freeN(pt_tmp); 00679 00680 /* nothing left in stroke, so stop */ 00681 return 1; 00682 } 00683 00684 /* if first segment, just remove segment from the stroke */ 00685 else if (i == 0) { 00686 /* allocate new points array, and assign most of the old stroke there */ 00687 gps->totpoints--; 00688 gps->points= MEM_callocN(sizeof(bGPDspoint)*gps->totpoints, "gp_stroke_points"); 00689 memcpy(gps->points, pt_tmp + 1, sizeof(bGPDspoint)*gps->totpoints); 00690 00691 /* free temp buffer */ 00692 MEM_freeN(pt_tmp); 00693 00694 /* no break here, as there might still be stuff to remove in this stroke */ 00695 return 0; 00696 } 00697 00698 /* segment occurs in 'middle' of stroke, so split */ 00699 else { 00700 /* duplicate stroke, and assign 'later' data to that stroke */ 00701 gsn= MEM_dupallocN(gps); 00702 gsn->prev= gsn->next= NULL; 00703 BLI_insertlinkafter(&gpf->strokes, gps, gsn); 00704 00705 gsn->totpoints= gps->totpoints - i; 00706 gsn->points= MEM_callocN(sizeof(bGPDspoint)*gsn->totpoints, "gp_stroke_points"); 00707 memcpy(gsn->points, pt_tmp + i, sizeof(bGPDspoint)*gsn->totpoints); 00708 00709 /* adjust existing stroke */ 00710 gps->totpoints= i; 00711 gps->points= MEM_callocN(sizeof(bGPDspoint)*gps->totpoints, "gp_stroke_points"); 00712 memcpy(gps->points, pt_tmp, sizeof(bGPDspoint)*i); 00713 00714 /* free temp buffer */ 00715 MEM_freeN(pt_tmp); 00716 00717 /* nothing left in stroke, so stop */ 00718 return 1; 00719 } 00720 } 00721 00722 /* eraser tool - check if part of stroke occurs within last segment drawn by eraser */ 00723 static short gp_stroke_eraser_strokeinside (int mval[], int UNUSED(mvalo[]), short rad, short x0, short y0, short x1, short y1) 00724 { 00725 /* simple within-radius check for now */ 00726 if (edge_inside_circle(mval[0], mval[1], rad, x0, y0, x1, y1)) 00727 return 1; 00728 00729 /* not inside */ 00730 return 0; 00731 } 00732 00733 /* eraser tool - evaluation per stroke */ 00734 // TODO: this could really do with some optimisation (KD-Tree/BVH?) 00735 static void gp_stroke_eraser_dostroke (tGPsdata *p, int mval[], int mvalo[], short rad, rcti *rect, bGPDframe *gpf, bGPDstroke *gps) 00736 { 00737 bGPDspoint *pt1, *pt2; 00738 int x0=0, y0=0, x1=0, y1=0; 00739 int xyval[2]; 00740 int i; 00741 00742 if (gps->totpoints == 0) { 00743 /* just free stroke */ 00744 if (gps->points) 00745 MEM_freeN(gps->points); 00746 BLI_freelinkN(&gpf->strokes, gps); 00747 } 00748 else if (gps->totpoints == 1) { 00749 /* get coordinates */ 00750 if (gps->flag & GP_STROKE_3DSPACE) { 00751 project_int(p->ar, &gps->points->x, xyval); 00752 x0= xyval[0]; 00753 y0= xyval[1]; 00754 } 00755 else if (gps->flag & GP_STROKE_2DSPACE) { 00756 UI_view2d_view_to_region(p->v2d, gps->points->x, gps->points->y, &x0, &y0); 00757 } 00758 #if 0 00759 else if (gps->flag & GP_STROKE_2DIMAGE) { 00760 int offsx, offsy, sizex, sizey; 00761 00762 /* get stored settings */ 00763 sizex= p->im2d_settings.sizex; 00764 sizey= p->im2d_settings.sizey; 00765 offsx= p->im2d_settings.offsx; 00766 offsy= p->im2d_settings.offsy; 00767 00768 /* calculate new points */ 00769 x0= (int)((gps->points->x * sizex) + offsx); 00770 y0= (int)((gps->points->y * sizey) + offsy); 00771 } 00772 #endif 00773 else { 00774 if (p->subrect == NULL) { /* normal 3D view */ 00775 x0= (int)(gps->points->x / 100 * p->ar->winx); 00776 y0= (int)(gps->points->y / 100 * p->ar->winy); 00777 } 00778 else { /* camera view, use subrect */ 00779 x0= (int)((gps->points->x / 100) * (p->subrect->xmax - p->subrect->xmin)) + p->subrect->xmin; 00780 y0= (int)((gps->points->y / 100) * (p->subrect->ymax - p->subrect->ymin)) + p->subrect->ymin; 00781 } 00782 } 00783 00784 /* do boundbox check first */ 00785 if (BLI_in_rcti(rect, x0, y0)) { 00786 /* only check if point is inside */ 00787 if ( ((x0-mval[0])*(x0-mval[0]) + (y0-mval[1])*(y0-mval[1])) <= rad*rad ) { 00788 /* free stroke */ 00789 MEM_freeN(gps->points); 00790 BLI_freelinkN(&gpf->strokes, gps); 00791 } 00792 } 00793 } 00794 else { 00795 /* loop over the points in the stroke, checking for intersections 00796 * - an intersection will require the stroke to be split 00797 */ 00798 for (i=0; (i+1) < gps->totpoints; i++) { 00799 /* get points to work with */ 00800 pt1= gps->points + i; 00801 pt2= gps->points + i + 1; 00802 00803 /* get coordinates */ 00804 if (gps->flag & GP_STROKE_3DSPACE) { 00805 project_int(p->ar, &pt1->x, xyval); 00806 x0= xyval[0]; 00807 y0= xyval[1]; 00808 00809 project_int(p->ar, &pt2->x, xyval); 00810 x1= xyval[0]; 00811 y1= xyval[1]; 00812 } 00813 else if (gps->flag & GP_STROKE_2DSPACE) { 00814 UI_view2d_view_to_region(p->v2d, pt1->x, pt1->y, &x0, &y0); 00815 00816 UI_view2d_view_to_region(p->v2d, pt2->x, pt2->y, &x1, &y1); 00817 } 00818 #if 0 00819 else if (gps->flag & GP_STROKE_2DIMAGE) { 00820 int offsx, offsy, sizex, sizey; 00821 00822 /* get stored settings */ 00823 sizex= p->im2d_settings.sizex; 00824 sizey= p->im2d_settings.sizey; 00825 offsx= p->im2d_settings.offsx; 00826 offsy= p->im2d_settings.offsy; 00827 00828 /* calculate new points */ 00829 x0= (int)((pt1->x * sizex) + offsx); 00830 y0= (int)((pt1->y * sizey) + offsy); 00831 00832 x1= (int)((pt2->x * sizex) + offsx); 00833 y1= (int)((pt2->y * sizey) + offsy); 00834 } 00835 #endif 00836 else { 00837 if(p->subrect == NULL) { /* normal 3D view */ 00838 x0= (int)(pt1->x / 100 * p->ar->winx); 00839 y0= (int)(pt1->y / 100 * p->ar->winy); 00840 x1= (int)(pt2->x / 100 * p->ar->winx); 00841 y1= (int)(pt2->y / 100 * p->ar->winy); 00842 } 00843 else { /* camera view, use subrect */ 00844 x0= (int)((pt1->x / 100) * (p->subrect->xmax - p->subrect->xmin)) + p->subrect->xmin; 00845 y0= (int)((pt1->y / 100) * (p->subrect->ymax - p->subrect->ymin)) + p->subrect->ymin; 00846 x1= (int)((pt2->x / 100) * (p->subrect->xmax - p->subrect->xmin)) + p->subrect->xmin; 00847 y1= (int)((pt2->y / 100) * (p->subrect->ymax - p->subrect->ymin)) + p->subrect->ymin; 00848 } 00849 } 00850 00851 /* check that point segment of the boundbox of the eraser stroke */ 00852 if (BLI_in_rcti(rect, x0, y0) || BLI_in_rcti(rect, x1, y1)) { 00853 /* check if point segment of stroke had anything to do with 00854 * eraser region (either within stroke painted, or on its lines) 00855 * - this assumes that linewidth is irrelevant 00856 */ 00857 if (gp_stroke_eraser_strokeinside(mval, mvalo, rad, x0, y0, x1, y1)) { 00858 /* if function returns true, break this loop (as no more point to check) */ 00859 if (gp_stroke_eraser_splitdel(gpf, gps, i)) 00860 break; 00861 } 00862 } 00863 } 00864 } 00865 } 00866 00867 /* erase strokes which fall under the eraser strokes */ 00868 static void gp_stroke_doeraser (tGPsdata *p) 00869 { 00870 bGPDframe *gpf= p->gpf; 00871 bGPDstroke *gps, *gpn; 00872 rcti rect; 00873 00874 /* rect is rectangle of eraser */ 00875 rect.xmin= p->mval[0] - p->radius; 00876 rect.ymin= p->mval[1] - p->radius; 00877 rect.xmax= p->mval[0] + p->radius; 00878 rect.ymax= p->mval[1] + p->radius; 00879 00880 /* loop over strokes, checking segments for intersections */ 00881 for (gps= gpf->strokes.first; gps; gps= gpn) { 00882 gpn= gps->next; 00883 gp_stroke_eraser_dostroke(p, p->mval, p->mvalo, p->radius, &rect, gpf, gps); 00884 } 00885 } 00886 00887 /* ******************************************* */ 00888 /* Sketching Operator */ 00889 00890 /* clear the session buffers (call this before AND after a paint operation) */ 00891 static void gp_session_validatebuffer (tGPsdata *p) 00892 { 00893 bGPdata *gpd= p->gpd; 00894 00895 /* clear memory of buffer (or allocate it if starting a new session) */ 00896 if (gpd->sbuffer) 00897 memset(gpd->sbuffer, 0, sizeof(tGPspoint)*GP_STROKE_BUFFER_MAX); 00898 else 00899 gpd->sbuffer= MEM_callocN(sizeof(tGPspoint)*GP_STROKE_BUFFER_MAX, "gp_session_strokebuffer"); 00900 00901 /* reset indices */ 00902 gpd->sbuffer_size = 0; 00903 00904 /* reset flags */ 00905 gpd->sbuffer_sflag= 0; 00906 } 00907 00908 /* init new painting session */ 00909 static tGPsdata *gp_session_initpaint (bContext *C) 00910 { 00911 tGPsdata *p = NULL; 00912 bGPdata **gpd_ptr = NULL; 00913 ScrArea *curarea= CTX_wm_area(C); 00914 ARegion *ar= CTX_wm_region(C); 00915 00916 /* make sure the active view (at the starting time) is a 3d-view */ 00917 if (curarea == NULL) { 00918 if (G.f & G_DEBUG) 00919 printf("Error: No active view for painting \n"); 00920 return NULL; 00921 } 00922 00923 /* create new context data */ 00924 p= MEM_callocN(sizeof(tGPsdata), "GPencil Drawing Data"); 00925 00926 /* pass on current scene and window */ 00927 p->scene= CTX_data_scene(C); 00928 p->win= CTX_wm_window(C); 00929 00930 switch (curarea->spacetype) { 00931 /* supported views first */ 00932 case SPACE_VIEW3D: 00933 { 00934 // View3D *v3d= curarea->spacedata.first; 00935 // RegionView3D *rv3d= ar->regiondata; 00936 00937 /* set current area 00938 * - must verify that region data is 3D-view (and not something else) 00939 */ 00940 p->sa= curarea; 00941 p->ar= ar; 00942 00943 if (ar->regiondata == NULL) { 00944 p->status= GP_STATUS_ERROR; 00945 if (G.f & G_DEBUG) 00946 printf("Error: 3D-View active region doesn't have any region data, so cannot be drawable \n"); 00947 return p; 00948 } 00949 00950 #if 0 // XXX will this sort of antiquated stuff be restored? 00951 /* check that gpencil data is allowed to be drawn */ 00952 if ((v3d->flag2 & V3D_DISPGP)==0) { 00953 p->status= GP_STATUS_ERROR; 00954 if (G.f & G_DEBUG) 00955 printf("Error: In active view, Grease Pencil not shown \n"); 00956 return p; 00957 } 00958 #endif 00959 } 00960 break; 00961 00962 case SPACE_NODE: 00963 { 00964 //SpaceNode *snode= curarea->spacedata.first; 00965 00966 /* set current area */ 00967 p->sa= curarea; 00968 p->ar= ar; 00969 p->v2d= &ar->v2d; 00970 00971 #if 0 // XXX will this sort of antiquated stuff be restored? 00972 /* check that gpencil data is allowed to be drawn */ 00973 if ((snode->flag & SNODE_DISPGP)==0) { 00974 p->status= GP_STATUS_ERROR; 00975 if (G.f & G_DEBUG) 00976 printf("Error: In active view, Grease Pencil not shown \n"); 00977 return; 00978 } 00979 #endif 00980 } 00981 break; 00982 #if 0 // XXX these other spaces will come over time... 00983 case SPACE_SEQ: 00984 { 00985 SpaceSeq *sseq= curarea->spacedata.first; 00986 00987 /* set current area */ 00988 p->sa= curarea; 00989 p->ar= ar; 00990 p->v2d= &ar->v2d; 00991 00992 /* check that gpencil data is allowed to be drawn */ 00993 if (sseq->mainb == SEQ_DRAW_SEQUENCE) { 00994 p->status= GP_STATUS_ERROR; 00995 if (G.f & G_DEBUG) 00996 printf("Error: In active view (sequencer), active mode doesn't support Grease Pencil \n"); 00997 return; 00998 } 00999 if ((sseq->flag & SEQ_DRAW_GPENCIL)==0) { 01000 p->status= GP_STATUS_ERROR; 01001 if (G.f & G_DEBUG) 01002 printf("Error: In active view, Grease Pencil not shown \n"); 01003 return; 01004 } 01005 } 01006 break; 01007 #endif 01008 case SPACE_IMAGE: 01009 { 01010 //SpaceImage *sima= curarea->spacedata.first; 01011 01012 /* set the current area */ 01013 p->sa= curarea; 01014 p->ar= ar; 01015 p->v2d= &ar->v2d; 01016 //p->ibuf= BKE_image_get_ibuf(sima->image, &sima->iuser); 01017 01018 #if 0 // XXX disabled for now 01019 /* check that gpencil data is allowed to be drawn */ 01020 if ((sima->flag & SI_DISPGP)==0) { 01021 p->status= GP_STATUS_ERROR; 01022 if (G.f & G_DEBUG) 01023 printf("Error: In active view, Grease Pencil not shown \n"); 01024 return p; 01025 } 01026 #endif 01027 } 01028 break; 01029 01030 /* unsupported views */ 01031 default: 01032 { 01033 p->status= GP_STATUS_ERROR; 01034 if (G.f & G_DEBUG) 01035 printf("Error: Active view not appropriate for Grease Pencil drawing \n"); 01036 return p; 01037 } 01038 break; 01039 } 01040 01041 /* get gp-data */ 01042 gpd_ptr= gpencil_data_get_pointers(C, &p->ownerPtr); 01043 if (gpd_ptr == NULL) { 01044 p->status= GP_STATUS_ERROR; 01045 if (G.f & G_DEBUG) 01046 printf("Error: Current context doesn't allow for any Grease Pencil data \n"); 01047 return p; 01048 } 01049 else { 01050 /* if no existing GPencil block exists, add one */ 01051 if (*gpd_ptr == NULL) 01052 *gpd_ptr= gpencil_data_addnew("GPencil"); 01053 p->gpd= *gpd_ptr; 01054 } 01055 01056 /* set edit flags - so that buffer will get drawn */ 01057 G.f |= G_GREASEPENCIL; 01058 01059 /* clear out buffer (stored in gp-data), in case something contaminated it */ 01060 gp_session_validatebuffer(p); 01061 01062 #if 0 01063 /* set 'default' im2d_settings just in case something that uses this doesn't set it */ 01064 p->im2d_settings.sizex= 1; 01065 p->im2d_settings.sizey= 1; 01066 #endif 01067 01068 /* return context data for running paint operator */ 01069 return p; 01070 } 01071 01072 /* cleanup after a painting session */ 01073 static void gp_session_cleanup (tGPsdata *p) 01074 { 01075 bGPdata *gpd= (p) ? p->gpd : NULL; 01076 01077 /* error checking */ 01078 if (gpd == NULL) 01079 return; 01080 01081 /* free stroke buffer */ 01082 if (gpd->sbuffer) { 01083 MEM_freeN(gpd->sbuffer); 01084 gpd->sbuffer= NULL; 01085 } 01086 01087 /* clear flags */ 01088 gpd->sbuffer_size= 0; 01089 gpd->sbuffer_sflag= 0; 01090 } 01091 01092 /* init new stroke */ 01093 static void gp_paint_initstroke (tGPsdata *p, short paintmode) 01094 { 01095 /* get active layer (or add a new one if non-existent) */ 01096 p->gpl= gpencil_layer_getactive(p->gpd); 01097 if (p->gpl == NULL) 01098 p->gpl= gpencil_layer_addnew(p->gpd); 01099 if (p->gpl->flag & GP_LAYER_LOCKED) { 01100 p->status= GP_STATUS_ERROR; 01101 if (G.f & G_DEBUG) 01102 printf("Error: Cannot paint on locked layer \n"); 01103 return; 01104 } 01105 01106 /* get active frame (add a new one if not matching frame) */ 01107 p->gpf= gpencil_layer_getframe(p->gpl, p->scene->r.cfra, 1); 01108 if (p->gpf == NULL) { 01109 p->status= GP_STATUS_ERROR; 01110 if (G.f & G_DEBUG) 01111 printf("Error: No frame created (gpencil_paint_init) \n"); 01112 return; 01113 } 01114 else 01115 p->gpf->flag |= GP_FRAME_PAINT; 01116 01117 /* set 'eraser' for this stroke if using eraser */ 01118 p->paintmode= paintmode; 01119 if (p->paintmode == GP_PAINTMODE_ERASER) 01120 p->gpd->sbuffer_sflag |= GP_STROKE_ERASER; 01121 01122 /* set 'initial run' flag, which is only used to denote when a new stroke is starting */ 01123 p->flags |= GP_PAINTFLAG_FIRSTRUN; 01124 01125 01126 /* when drawing in the camera view, in 2D space, set the subrect */ 01127 if (!(p->gpd->flag & GP_DATA_VIEWALIGN)) { 01128 if (p->sa->spacetype == SPACE_VIEW3D) { 01129 View3D *v3d= p->sa->spacedata.first; 01130 RegionView3D *rv3d= p->ar->regiondata; 01131 01132 /* for camera view set the subrect */ 01133 if (rv3d->persp == RV3D_CAMOB) { 01134 ED_view3d_calc_camera_border(p->scene, p->ar, v3d, rv3d, &p->subrect_data, -1); /* negative shift */ 01135 p->subrect= &p->subrect_data; 01136 } 01137 } 01138 } 01139 01140 /* check if points will need to be made in view-aligned space */ 01141 if (p->gpd->flag & GP_DATA_VIEWALIGN) { 01142 switch (p->sa->spacetype) { 01143 case SPACE_VIEW3D: 01144 { 01145 RegionView3D *rv3d= p->ar->regiondata; 01146 float rvec[3]; 01147 01148 /* get reference point for 3d space placement */ 01149 gp_get_3d_reference(p, rvec); 01150 initgrabz(rv3d, rvec[0], rvec[1], rvec[2]); 01151 01152 p->gpd->sbuffer_sflag |= GP_STROKE_3DSPACE; 01153 } 01154 break; 01155 01156 case SPACE_NODE: 01157 { 01158 p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; 01159 } 01160 break; 01161 #if 0 // XXX other spacetypes to be restored in due course 01162 case SPACE_SEQ: 01163 { 01164 SpaceSeq *sseq= (SpaceSeq *)p->sa->spacedata.first; 01165 int rectx, recty; 01166 float zoom, zoomx, zoomy; 01167 01168 /* set draw 2d-stroke flag */ 01169 p->gpd->sbuffer_sflag |= GP_STROKE_2DIMAGE; 01170 01171 /* calculate zoom factor */ 01172 zoom= (float)(SEQ_ZOOM_FAC(sseq->zoom)); 01173 if (sseq->mainb == SEQ_DRAW_IMG_IMBUF) { 01174 zoomx = zoom * (p->scene->r.xasp / p->scene->r.yasp); 01175 zoomy = zoom; 01176 } 01177 else 01178 zoomx = zoomy = zoom; 01179 01180 /* calculate rect size to use to calculate the size of the drawing area 01181 * - We use the size of the output image not the size of the ibuf being shown 01182 * as it is too messy getting the ibuf (and could be too slow). This should be 01183 * a reasonable for most cases anyway. 01184 */ 01185 rectx= (p->scene->r.size * p->scene->r.xsch) / 100; 01186 recty= (p->scene->r.size * p->scene->r.ysch) / 100; 01187 01188 /* set offset and scale values for opertations to use */ 01189 p->im2d_settings.sizex= (int)(zoomx * rectx); 01190 p->im2d_settings.sizey= (int)(zoomy * recty); 01191 p->im2d_settings.offsx= (int)((p->sa->winx-p->im2d_settings.sizex)/2 + sseq->xof); 01192 p->im2d_settings.offsy= (int)((p->sa->winy-p->im2d_settings.sizey)/2 + sseq->yof); 01193 } 01194 break; 01195 #endif 01196 case SPACE_IMAGE: 01197 { 01198 SpaceImage *sima= (SpaceImage *)p->sa->spacedata.first; 01199 01200 /* only set these flags if the image editor doesn't have an image active, 01201 * otherwise user will be confused by strokes not appearing after they're drawn 01202 * 01203 * Admittedly, this is a bit hacky, but it works much nicer from an ergonomic standpoint! 01204 */ 01205 if ELEM(NULL, sima, sima->image) { 01206 /* make strokes be drawn in screen space */ 01207 p->gpd->sbuffer_sflag &= ~GP_STROKE_2DSPACE; 01208 p->gpd->flag &= ~GP_DATA_VIEWALIGN; 01209 } 01210 else 01211 p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; 01212 } 01213 break; 01214 } 01215 } 01216 } 01217 01218 /* finish off a stroke (clears buffer, but doesn't finish the paint operation) */ 01219 static void gp_paint_strokeend (tGPsdata *p) 01220 { 01221 /* for surface sketching, need to set the right OpenGL context stuff so that 01222 * the conversions will project the values correctly... 01223 */ 01224 if (gpencil_project_check(p)) { 01225 View3D *v3d= p->sa->spacedata.first; 01226 01227 /* need to restore the original projection settings before packing up */ 01228 view3d_region_operator_needs_opengl(p->win, p->ar); 01229 ED_view3d_autodist_init(p->scene, p->ar, v3d, (p->gpd->flag & GP_DATA_DEPTH_STROKE) ? 1:0); 01230 } 01231 01232 /* check if doing eraser or not */ 01233 if ((p->gpd->sbuffer_sflag & GP_STROKE_ERASER) == 0) { 01234 /* smooth stroke before transferring? */ 01235 gp_stroke_smooth(p); 01236 01237 /* simplify stroke before transferring? */ 01238 gp_stroke_simplify(p); 01239 01240 /* transfer stroke to frame */ 01241 gp_stroke_newfrombuffer(p); 01242 } 01243 01244 /* clean up buffer now */ 01245 gp_session_validatebuffer(p); 01246 } 01247 01248 /* finish off stroke painting operation */ 01249 static void gp_paint_cleanup (tGPsdata *p) 01250 { 01251 /* finish off a stroke */ 01252 gp_paint_strokeend(p); 01253 01254 /* "unlock" frame */ 01255 if (p->gpf) 01256 p->gpf->flag &= ~GP_FRAME_PAINT; 01257 } 01258 01259 /* ------------------------------- */ 01260 01261 static void gpencil_draw_exit (bContext *C, wmOperator *op) 01262 { 01263 tGPsdata *p= op->customdata; 01264 01265 /* clear edit flags */ 01266 G.f &= ~G_GREASEPENCIL; 01267 01268 /* restore cursor to indicate end of drawing */ 01269 WM_cursor_restore(CTX_wm_window(C)); 01270 01271 /* don't assume that operator data exists at all */ 01272 if (p) { 01273 /* check size of buffer before cleanup, to determine if anything happened here */ 01274 if (p->paintmode == GP_PAINTMODE_ERASER) { 01275 // TODO clear radial cursor thing 01276 // XXX draw_sel_circle(NULL, p.mvalo, 0, p.radius, 0); 01277 } 01278 01279 /* cleanup */ 01280 gp_paint_cleanup(p); 01281 gp_session_cleanup(p); 01282 01283 /* finally, free the temp data */ 01284 MEM_freeN(p); 01285 } 01286 01287 op->customdata= NULL; 01288 } 01289 01290 static int gpencil_draw_cancel (bContext *C, wmOperator *op) 01291 { 01292 /* this is just a wrapper around exit() */ 01293 gpencil_draw_exit(C, op); 01294 return OPERATOR_CANCELLED; 01295 } 01296 01297 /* ------------------------------- */ 01298 01299 01300 static int gpencil_draw_init (bContext *C, wmOperator *op) 01301 { 01302 tGPsdata *p; 01303 int paintmode= RNA_enum_get(op->ptr, "mode"); 01304 01305 /* check context */ 01306 p= op->customdata= gp_session_initpaint(C); 01307 if ((p == NULL) || (p->status == GP_STATUS_ERROR)) { 01308 /* something wasn't set correctly in context */ 01309 gpencil_draw_exit(C, op); 01310 return 0; 01311 } 01312 01313 /* init painting data */ 01314 gp_paint_initstroke(p, paintmode); 01315 if (p->status == GP_STATUS_ERROR) { 01316 gpencil_draw_exit(C, op); 01317 return 0; 01318 } 01319 01320 /* radius for eraser circle is defined in userprefs now */ 01321 p->radius= U.gp_eraser; 01322 01323 /* everything is now setup ok */ 01324 return 1; 01325 } 01326 01327 /* ------------------------------- */ 01328 01329 /* update UI indicators of status, including cursor and header prints */ 01330 static void gpencil_draw_status_indicators (tGPsdata *p) 01331 { 01332 /* header prints */ 01333 switch (p->status) { 01334 case GP_STATUS_PAINTING: 01335 /* only print this for paint-sessions, otherwise it gets annoying */ 01336 if (GPENCIL_SKETCH_SESSIONS_ON(p->scene)) 01337 ED_area_headerprint(p->sa, "Grease Pencil: Drawing/erasing stroke... Release to end stroke"); 01338 break; 01339 01340 case GP_STATUS_IDLING: 01341 /* print status info */ 01342 switch (p->paintmode) { 01343 case GP_PAINTMODE_ERASER: 01344 ED_area_headerprint(p->sa, "Grease Pencil Erase Session: Hold and drag LMB or RMB to erase | ESC/Enter to end"); 01345 break; 01346 case GP_PAINTMODE_DRAW_STRAIGHT: 01347 ED_area_headerprint(p->sa, "Grease Pencil Line Session: Hold and drag LMB to draw | ESC/Enter to end"); 01348 break; 01349 case GP_PAINTMODE_DRAW: 01350 ED_area_headerprint(p->sa, "Grease Pencil Freehand Session: Hold and drag LMB to draw | ESC/Enter to end"); 01351 break; 01352 01353 default: /* unhandled future cases */ 01354 ED_area_headerprint(p->sa, "Grease Pencil Session: ESC/Enter to end"); 01355 break; 01356 } 01357 break; 01358 01359 case GP_STATUS_ERROR: 01360 case GP_STATUS_DONE: 01361 /* clear status string */ 01362 ED_area_headerprint(p->sa, NULL); 01363 break; 01364 } 01365 } 01366 01367 /* ------------------------------- */ 01368 01369 /* create a new stroke point at the point indicated by the painting context */ 01370 static void gpencil_draw_apply (wmOperator *op, tGPsdata *p) 01371 { 01372 /* handle drawing/erasing -> test for erasing first */ 01373 if (p->paintmode == GP_PAINTMODE_ERASER) { 01374 /* do 'live' erasing now */ 01375 gp_stroke_doeraser(p); 01376 01377 /* store used values */ 01378 p->mvalo[0]= p->mval[0]; 01379 p->mvalo[1]= p->mval[1]; 01380 p->opressure= p->pressure; 01381 } 01382 /* only add current point to buffer if mouse moved (even though we got an event, it might be just noise) */ 01383 else if (gp_stroke_filtermval(p, p->mval, p->mvalo)) { 01384 /* try to add point */ 01385 short ok= gp_stroke_addpoint(p, p->mval, p->pressure); 01386 01387 /* handle errors while adding point */ 01388 if ((ok == GP_STROKEADD_FULL) || (ok == GP_STROKEADD_OVERFLOW)) { 01389 /* finish off old stroke */ 01390 gp_paint_strokeend(p); 01391 01392 /* start a new stroke, starting from previous point */ 01393 gp_stroke_addpoint(p, p->mvalo, p->opressure); 01394 ok= gp_stroke_addpoint(p, p->mval, p->pressure); 01395 } 01396 else if (ok == GP_STROKEADD_INVALID) { 01397 /* the painting operation cannot continue... */ 01398 BKE_report(op->reports, RPT_ERROR, "Cannot paint stroke"); 01399 p->status = GP_STATUS_ERROR; 01400 01401 if (G.f & G_DEBUG) 01402 printf("Error: Grease-Pencil Paint - Add Point Invalid \n"); 01403 return; 01404 } 01405 01406 /* store used values */ 01407 p->mvalo[0]= p->mval[0]; 01408 p->mvalo[1]= p->mval[1]; 01409 p->opressure= p->pressure; 01410 } 01411 } 01412 01413 /* handle draw event */ 01414 static void gpencil_draw_apply_event (wmOperator *op, wmEvent *event) 01415 { 01416 tGPsdata *p= op->customdata; 01417 PointerRNA itemptr; 01418 float mousef[2]; 01419 int tablet=0; 01420 01421 /* convert from window-space to area-space mouse coordintes */ 01422 // NOTE: float to ints conversions, +1 factor is probably used to ensure a bit more accurate rounding... 01423 p->mval[0]= event->mval[0] + 1; 01424 p->mval[1]= event->mval[1] + 1; 01425 01426 /* handle pressure sensitivity (which is supplied by tablets) */ 01427 if (event->custom == EVT_DATA_TABLET) { 01428 wmTabletData *wmtab= event->customdata; 01429 01430 tablet= (wmtab->Active != EVT_TABLET_NONE); 01431 p->pressure= wmtab->Pressure; 01432 01433 //if (wmtab->Active == EVT_TABLET_ERASER) 01434 // TODO... this should get caught by the keymaps which call drawing in the first place 01435 } 01436 else 01437 p->pressure= 1.0f; 01438 01439 /* fill in stroke data (not actually used directly by gpencil_draw_apply) */ 01440 RNA_collection_add(op->ptr, "stroke", &itemptr); 01441 01442 mousef[0]= p->mval[0]; 01443 mousef[1]= p->mval[1]; 01444 RNA_float_set_array(&itemptr, "mouse", mousef); 01445 RNA_float_set(&itemptr, "pressure", p->pressure); 01446 RNA_boolean_set(&itemptr, "is_start", (p->flags & GP_PAINTFLAG_FIRSTRUN)); 01447 01448 /* special exception for start of strokes (i.e. maybe for just a dot) */ 01449 if (p->flags & GP_PAINTFLAG_FIRSTRUN) { 01450 p->flags &= ~GP_PAINTFLAG_FIRSTRUN; 01451 01452 p->mvalo[0]= p->mval[0]; 01453 p->mvalo[1]= p->mval[1]; 01454 p->opressure= p->pressure; 01455 01456 /* special exception here for too high pressure values on first touch in 01457 * windows for some tablets, then we just skip first touch .. 01458 */ 01459 if (tablet && (p->pressure >= 0.99f)) 01460 return; 01461 } 01462 01463 /* apply the current latest drawing point */ 01464 gpencil_draw_apply(op, p); 01465 01466 /* force refresh */ 01467 ED_region_tag_redraw(p->ar); /* just active area for now, since doing whole screen is too slow */ 01468 } 01469 01470 /* ------------------------------- */ 01471 01472 /* operator 'redo' (i.e. after changing some properties, but also for repeat last) */ 01473 static int gpencil_draw_exec (bContext *C, wmOperator *op) 01474 { 01475 tGPsdata *p = NULL; 01476 01477 //printf("GPencil - Starting Re-Drawing \n"); 01478 01479 /* try to initialise context data needed while drawing */ 01480 if (!gpencil_draw_init(C, op)) { 01481 if (op->customdata) MEM_freeN(op->customdata); 01482 //printf("\tGP - no valid data \n"); 01483 return OPERATOR_CANCELLED; 01484 } 01485 else 01486 p= op->customdata; 01487 01488 //printf("\tGP - Start redrawing stroke \n"); 01489 01490 /* loop over the stroke RNA elements recorded (i.e. progress of mouse movement), 01491 * setting the relevant values in context at each step, then applying 01492 */ 01493 RNA_BEGIN(op->ptr, itemptr, "stroke") 01494 { 01495 float mousef[2]; 01496 01497 //printf("\t\tGP - stroke elem \n"); 01498 01499 /* get relevant data for this point from stroke */ 01500 RNA_float_get_array(&itemptr, "mouse", mousef); 01501 p->mval[0] = (int)mousef[0]; 01502 p->mval[1] = (int)mousef[1]; 01503 p->pressure= RNA_float_get(&itemptr, "pressure"); 01504 01505 if (RNA_boolean_get(&itemptr, "is_start")) { 01506 /* if first-run flag isn't set already (i.e. not true first stroke), 01507 * then we must terminate the previous one first before continuing 01508 */ 01509 if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) { 01510 // TODO: both of these ops can set error-status, but we probably don't need to worry 01511 gp_paint_strokeend(p); 01512 gp_paint_initstroke(p, p->paintmode); 01513 } 01514 } 01515 01516 /* if first run, set previous data too */ 01517 if (p->flags & GP_PAINTFLAG_FIRSTRUN) { 01518 p->flags &= ~GP_PAINTFLAG_FIRSTRUN; 01519 01520 p->mvalo[0]= p->mval[0]; 01521 p->mvalo[1]= p->mval[1]; 01522 p->opressure= p->pressure; 01523 } 01524 01525 /* apply this data as necessary now (as per usual) */ 01526 gpencil_draw_apply(op, p); 01527 } 01528 RNA_END; 01529 01530 //printf("\tGP - done \n"); 01531 01532 /* cleanup */ 01533 gpencil_draw_exit(C, op); 01534 01535 /* refreshes */ 01536 WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX need a nicer one that will work 01537 01538 /* done */ 01539 return OPERATOR_FINISHED; 01540 } 01541 01542 /* ------------------------------- */ 01543 01544 /* start of interactive drawing part of operator */ 01545 static int gpencil_draw_invoke (bContext *C, wmOperator *op, wmEvent *event) 01546 { 01547 tGPsdata *p = NULL; 01548 wmWindow *win= CTX_wm_window(C); 01549 01550 if (G.f & G_DEBUG) 01551 printf("GPencil - Starting Drawing \n"); 01552 01553 /* try to initialise context data needed while drawing */ 01554 if (!gpencil_draw_init(C, op)) { 01555 if (op->customdata) 01556 MEM_freeN(op->customdata); 01557 if (G.f & G_DEBUG) 01558 printf("\tGP - no valid data \n"); 01559 return OPERATOR_CANCELLED; 01560 } 01561 else 01562 p= op->customdata; 01563 01564 // TODO: set any additional settings that we can take from the events? 01565 // TODO? if tablet is erasing, force eraser to be on? 01566 01567 // TODO: move cursor setting stuff to stroke-start so that paintmode can be changed midway... 01568 01569 /* if eraser is on, draw radial aid */ 01570 if (p->paintmode == GP_PAINTMODE_ERASER) { 01571 // TODO: this involves mucking around with radial control, so we leave this for now.. 01572 } 01573 01574 /* set cursor */ 01575 if (p->paintmode == GP_PAINTMODE_ERASER) 01576 WM_cursor_modal(win, BC_CROSSCURSOR); // XXX need a better cursor 01577 else 01578 WM_cursor_modal(win, BC_PAINTBRUSHCURSOR); 01579 01580 /* special hack: if there was an initial event, then we were invoked via a hotkey, and 01581 * painting should start immediately. Otherwise, this was called from a toolbar, in which 01582 * case we should wait for the mouse to be clicked. 01583 */ 01584 if (event->type) { 01585 /* hotkey invoked - start drawing */ 01586 //printf("\tGP - set first spot\n"); 01587 p->status= GP_STATUS_PAINTING; 01588 01589 /* handle the initial drawing - i.e. for just doing a simple dot */ 01590 gpencil_draw_apply_event(op, event); 01591 } 01592 else { 01593 /* toolbar invoked - don't start drawing yet... */ 01594 //printf("\tGP - hotkey invoked... waiting for click-drag\n"); 01595 } 01596 01597 /* add a modal handler for this operator, so that we can then draw continuous strokes */ 01598 WM_event_add_modal_handler(C, op); 01599 return OPERATOR_RUNNING_MODAL; 01600 } 01601 01602 /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ 01603 static int gpencil_area_exists(bContext *C, ScrArea *satest) 01604 { 01605 bScreen *sc= CTX_wm_screen(C); 01606 ScrArea *sa; 01607 01608 for(sa= sc->areabase.first; sa; sa= sa->next) 01609 if(sa==satest) 01610 return 1; 01611 return 0; 01612 } 01613 01614 /* events handling during interactive drawing part of operator */ 01615 static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event) 01616 { 01617 tGPsdata *p= op->customdata; 01618 //int estate = OPERATOR_PASS_THROUGH; /* default exit state - not handled, so let others have a share of the pie */ 01619 /* currently, grease pencil conflicts with such operators as undo and set object mode 01620 which makes behavior of operator totally unpredictable and crash for some cases. 01621 the only way to solve this proper is to ger rid of pointers to data which can 01622 chage stored in operator custom data (sergey) */ 01623 int estate = OPERATOR_RUNNING_MODAL; 01624 01625 // if (event->type == NDOF_MOTION) 01626 // return OPERATOR_PASS_THROUGH; 01627 // ------------------------------- 01628 // [mce] Not quite what I was looking 01629 // for, but a good start! GP continues to 01630 // draw on the screen while the 3D mouse 01631 // moves the viewpoint. Problem is that 01632 // the stroke is converted to 3D only after 01633 // it is finished. This approach should work 01634 // better in tools that immediately apply 01635 // in 3D space. 01636 01637 //printf("\tGP - handle modal event...\n"); 01638 01639 /* exit painting mode (and/or end current stroke) */ 01640 if (ELEM4(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY)) { 01641 /* exit() ends the current stroke before cleaning up */ 01642 //printf("\t\tGP - end of paint op + end of stroke\n"); 01643 p->status= GP_STATUS_DONE; 01644 estate = OPERATOR_FINISHED; 01645 } 01646 01647 /* toggle painting mode upon mouse-button movement */ 01648 if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE)) { 01649 /* if painting, end stroke */ 01650 if (p->status == GP_STATUS_PAINTING) { 01651 /* basically, this should be mouse-button up = end stroke 01652 * BUT what happens next depends on whether we 'painting sessions' is enabled 01653 */ 01654 if (GPENCIL_SKETCH_SESSIONS_ON(p->scene)) { 01655 /* end stroke only, and then wait to resume painting soon */ 01656 //printf("\t\tGP - end stroke only\n"); 01657 gp_paint_cleanup(p); 01658 p->status= GP_STATUS_IDLING; 01659 01660 /* we've just entered idling state, so this event was processed (but no others yet) */ 01661 estate = OPERATOR_RUNNING_MODAL; 01662 } 01663 else { 01664 //printf("\t\tGP - end of stroke + op\n"); 01665 p->status= GP_STATUS_DONE; 01666 estate = OPERATOR_FINISHED; 01667 } 01668 } 01669 else { 01670 /* not painting, so start stroke (this should be mouse-button down) */ 01671 01672 /* we must check that we're still within the area that we're set up to work from 01673 * otherwise we could crash (see bug #20586) 01674 */ 01675 if (CTX_wm_area(C) != p->sa) { 01676 //printf("\t\t\tGP - wrong area execution abort! \n"); 01677 p->status= GP_STATUS_ERROR; 01678 estate = OPERATOR_CANCELLED; 01679 } 01680 else { 01681 //printf("\t\tGP - start stroke \n"); 01682 p->status= GP_STATUS_PAINTING; 01683 01684 /* we may need to set up paint env again if we're resuming */ 01685 // XXX: watch it with the paintmode! in future, it'd be nice to allow changing paint-mode when in sketching-sessions 01686 // XXX: with tablet events, we may event want to check for eraser here, for nicer tablet support 01687 gp_paint_initstroke(p, p->paintmode); 01688 01689 if (p->status == GP_STATUS_ERROR) { 01690 estate = OPERATOR_CANCELLED; 01691 } 01692 } 01693 } 01694 } 01695 01696 01697 01698 /* handle mode-specific events */ 01699 if (p->status == GP_STATUS_PAINTING) { 01700 /* handle painting mouse-movements? */ 01701 if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) 01702 { 01703 /* handle drawing event */ 01704 //printf("\t\tGP - add point\n"); 01705 gpencil_draw_apply_event(op, event); 01706 01707 /* finish painting operation if anything went wrong just now */ 01708 if (p->status == GP_STATUS_ERROR) { 01709 //printf("\t\t\t\tGP - add error done! \n"); 01710 estate = OPERATOR_CANCELLED; 01711 } 01712 else { 01713 /* event handled, so just tag as running modal */ 01714 //printf("\t\t\t\tGP - add point handled!\n"); 01715 estate = OPERATOR_RUNNING_MODAL; 01716 } 01717 } 01718 /* there shouldn't be any other events, but just in case there are, let's swallow them 01719 * (i.e. to prevent problems with with undo) 01720 */ 01721 else { 01722 /* swallow event to save ourselves trouble */ 01723 estate = OPERATOR_RUNNING_MODAL; 01724 } 01725 } 01726 else if (p->status == GP_STATUS_IDLING) { 01727 /* standard undo/redo shouldn't be allowed to execute or else it causes crashes, so catch it here */ 01728 // FIXME: this is a hardcoded hotkey that can't be changed 01729 // TODO: catch redo as well, but how? 01730 if (event->type == ZKEY && event->val == KM_RELEASE) { 01731 /* oskey = cmd key on macs as they seem to use cmd-z for undo as well? */ 01732 if ((event->ctrl) || (event->oskey)) { 01733 /* just delete last stroke, which will look like undo to the end user */ 01734 //printf("caught attempted undo event... deleting last stroke \n"); 01735 gpencil_frame_delete_laststroke(p->gpl, p->gpf); 01736 /* undoing the last line can free p->gpf 01737 * note, could do this in a bit more of an elegant way then a search but it at least prevents a crash */ 01738 if(BLI_findindex(&p->gpl->frames, p->gpf) == -1) { 01739 p->gpf= NULL; 01740 } 01741 01742 /* event handled, so force refresh */ 01743 ED_region_tag_redraw(p->ar); /* just active area for now, since doing whole screen is too slow */ 01744 estate = OPERATOR_RUNNING_MODAL; 01745 } 01746 } 01747 } 01748 01749 /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ 01750 if(0==gpencil_area_exists(C, p->sa)) 01751 estate= OPERATOR_CANCELLED; 01752 else 01753 /* update status indicators - cursor, header, etc. */ 01754 gpencil_draw_status_indicators(p); 01755 01756 /* process last operations before exiting */ 01757 switch (estate) { 01758 case OPERATOR_FINISHED: 01759 /* one last flush before we're done */ 01760 gpencil_draw_exit(C, op); 01761 WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL); // XXX need a nicer one that will work 01762 break; 01763 01764 case OPERATOR_CANCELLED: 01765 gpencil_draw_exit(C, op); 01766 break; 01767 01768 case OPERATOR_RUNNING_MODAL|OPERATOR_PASS_THROUGH: 01769 /* event doesn't need to be handled */ 01770 //printf("unhandled event -> %d (mmb? = %d | mmv? = %d)\n", event->type, event->type == MIDDLEMOUSE, event->type==MOUSEMOVE); 01771 break; 01772 } 01773 01774 /* return status code */ 01775 return estate; 01776 } 01777 01778 /* ------------------------------- */ 01779 01780 static EnumPropertyItem prop_gpencil_drawmodes[] = { 01781 {GP_PAINTMODE_DRAW, "DRAW", 0, "Draw Freehand", ""}, 01782 {GP_PAINTMODE_DRAW_STRAIGHT, "DRAW_STRAIGHT", 0, "Draw Straight Lines", ""}, 01783 {GP_PAINTMODE_ERASER, "ERASER", 0, "Eraser", ""}, 01784 {0, NULL, 0, NULL, NULL} 01785 }; 01786 01787 void GPENCIL_OT_draw (wmOperatorType *ot) 01788 { 01789 /* identifiers */ 01790 ot->name= "Grease Pencil Draw"; 01791 ot->idname= "GPENCIL_OT_draw"; 01792 ot->description= "Make annotations on the active data"; 01793 01794 /* api callbacks */ 01795 ot->exec= gpencil_draw_exec; 01796 ot->invoke= gpencil_draw_invoke; 01797 ot->modal= gpencil_draw_modal; 01798 ot->cancel= gpencil_draw_cancel; 01799 ot->poll= gpencil_draw_poll; 01800 01801 /* flags */ 01802 ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING; 01803 01804 /* settings for drawing */ 01805 RNA_def_enum(ot->srna, "mode", prop_gpencil_drawmodes, 0, "Mode", "Way to intepret mouse movements."); 01806 01807 RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); 01808 }