Grease pencil: non-blocking sketch sessions
authorSergey Sharybin <sergey.vfx@gmail.com>
Tue, 6 Sep 2011 07:59:18 +0000 (07:59 +0000)
committerSergey Sharybin <sergey.vfx@gmail.com>
Tue, 6 Sep 2011 07:59:18 +0000 (07:59 +0000)
- Implement own undo stack for grease pencil, so now there'll be no keymaps conflicts.
- Supported redo's during sketch session.
- Get rid of flag stored in Globals -- use undo stack to check if grease pencil session is active.

source/blender/blenkernel/BKE_global.h
source/blender/editors/gpencil/CMakeLists.txt
source/blender/editors/gpencil/drawgpencil.c
source/blender/editors/gpencil/gpencil_intern.h
source/blender/editors/gpencil/gpencil_paint.c
source/blender/editors/gpencil/gpencil_undo.c [new file with mode: 0644]
source/blender/editors/include/ED_gpencil.h
source/blender/editors/util/undo.c

index 17876c6ec9d3faea372b1ce8a89063bcad1f7601..0e48673f1b1c688399fc870858c1d95acedbe7aa 100644 (file)
@@ -111,7 +111,7 @@ typedef struct Global {
 #define G_SCRIPT_OVERRIDE_PREF (1 << 14) /* when this flag is set ignore the userprefs */
 
 /* #define G_NOFROZEN  (1 << 17) also removed */
-#define G_GREASEPENCIL         (1 << 17)
+/* #define G_GREASEPENCIL      (1 << 17)   also removed */
 
 /* #define G_AUTOMATKEYS       (1 << 30)   also removed */
 
index 7a2f196fd6d314df5469d11e97a71264de057296..b312f39793936dfbd0ca93a8595ecf614d41001e 100644 (file)
@@ -42,6 +42,7 @@ set(SRC
        gpencil_edit.c
        gpencil_ops.c
        gpencil_paint.c
+       gpencil_undo.c
 
        gpencil_intern.h
 )
index 440d5ee7c4dd6c1e96bf88e3d733141e1ad307e8..cfa9585868e0179778df9e802cc77656ccd27897 100644 (file)
@@ -644,7 +644,7 @@ static void gp_draw_data (bGPdata *gpd, int offsx, int offsy, int winx, int winy
                /* Check if may need to draw the active stroke cache, only if this layer is the active layer
                 * that is being edited. (Stroke buffer is currently stored in gp-data)
                 */
-               if ((G.f & G_GREASEPENCIL) && (gpl->flag & GP_LAYER_ACTIVE) &&
+               if (ED_gpencil_session_active() && (gpl->flag & GP_LAYER_ACTIVE) &&
                        (gpf->flag & GP_FRAME_PAINT)) 
                {
                        /* Buffer stroke needs to be drawn with a different linestyle to help differentiate them from normal strokes. */
index c31de8d30a75353533f8146fa638137778f3b6f1..db4315a8ca659bfafb887edf53af3ec69e8ee125 100644 (file)
@@ -37,6 +37,7 @@
 /* ***************************************************** */
 /* Operator Defines */
 
+struct bGPdata;
 struct wmOperatorType;
 
 /* drawing ---------- */
@@ -61,6 +62,11 @@ void GPENCIL_OT_active_frame_delete(struct wmOperatorType *ot);
 
 void GPENCIL_OT_convert(struct wmOperatorType *ot);
 
+/* undo stack ---------- */
+
+void gpencil_undo_init(struct bGPdata *gpd);
+void gpencil_undo_push(struct bGPdata *gpd);
+void gpencil_undo_finish(void);
 
 /******************************************************* */
 /* FILTERED ACTION DATA - TYPES  ---> XXX DEPRECEATED OLD ANIM SYSTEM CODE! */
index 04565eab1551629521d8223203e08cb50134acd6..df5fa65c980edf1ed4787fd8a23e0871ee616a57 100644 (file)
@@ -152,7 +152,7 @@ static int gpencil_draw_poll (bContext *C)
                /* check if current context can support GPencil data */
                if (gpencil_data_get_pointers(C, NULL) != NULL) {
                        /* check if Grease Pencil isn't already running */
-                       if ((G.f & G_GREASEPENCIL) == 0)
+                       if (ED_gpencil_session_active() == 0)
                                return 1;
                        else
                                CTX_wm_operator_poll_msg_set(C, "Grease Pencil operator is already active");
@@ -893,8 +893,10 @@ static void gp_session_validatebuffer (tGPsdata *p)
        /* clear memory of buffer (or allocate it if starting a new session) */
        if (gpd->sbuffer)
                memset(gpd->sbuffer, 0, sizeof(tGPspoint)*GP_STROKE_BUFFER_MAX);
-       else
+       else {
+               //printf("\t\tGP - allocate sbuffer\n");
                gpd->sbuffer= MEM_callocN(sizeof(tGPspoint)*GP_STROKE_BUFFER_MAX, "gp_session_strokebuffer");
+       }
        
        /* reset indices */
        gpd->sbuffer_size = 0;
@@ -1051,8 +1053,11 @@ static tGPsdata *gp_session_initpaint (bContext *C)
                p->gpd= *gpd_ptr;
        }
        
-       /* set edit flags - so that buffer will get drawn */
-       G.f |= G_GREASEPENCIL;
+       if(ED_gpencil_session_active()==0) {
+               /* initialize undo stack,
+                  also, existing undo stack would make buffer drawn */
+               gpencil_undo_init(p->gpd);
+       }
        
        /* clear out buffer (stored in gp-data), in case something contaminated it */
        gp_session_validatebuffer(p);
@@ -1078,6 +1083,7 @@ static void gp_session_cleanup (tGPsdata *p)
        
        /* free stroke buffer */
        if (gpd->sbuffer) {
+               //printf("\t\tGP - free sbuffer\n");
                MEM_freeN(gpd->sbuffer);
                gpd->sbuffer= NULL;
        }
@@ -1247,7 +1253,8 @@ static void gp_paint_strokeend (tGPsdata *p)
 static void gp_paint_cleanup (tGPsdata *p)
 {
        /* finish off a stroke */
-       gp_paint_strokeend(p);
+       if(p->gpd)
+               gp_paint_strokeend(p);
        
        /* "unlock" frame */
        if (p->gpf)
@@ -1260,8 +1267,8 @@ static void gpencil_draw_exit (bContext *C, wmOperator *op)
 {
        tGPsdata *p= op->customdata;
        
-       /* clear edit flags */
-       G.f &= ~G_GREASEPENCIL;
+       /* clear undo stack */
+       gpencil_undo_finish();
        
        /* restore cursor to indicate end of drawing */
        WM_cursor_restore(CTX_wm_window(C));
@@ -1592,6 +1599,7 @@ static int gpencil_draw_invoke (bContext *C, wmOperator *op, wmEvent *event)
                //printf("\tGP - hotkey invoked... waiting for click-drag\n");
        }
        
+       WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL, NULL);
        /* add a modal handler for this operator, so that we can then draw continuous strokes */
        WM_event_add_modal_handler(C, op);
        return OPERATOR_RUNNING_MODAL;
@@ -1609,16 +1617,60 @@ static int gpencil_area_exists(bContext *C, ScrArea *satest)
        return 0;
 }
 
+static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op)
+{
+       tGPsdata *p= op->customdata;
+
+       /* we must check that we're still within the area that we're set up to work from
+        * otherwise we could crash (see bug #20586)
+        */
+       if (CTX_wm_area(C) != p->sa) {
+               printf("\t\t\tGP - wrong area execution abort! \n");
+               p->status= GP_STATUS_ERROR;
+       }
+
+       /* free pointer used by previous stroke */
+       if(p)
+               MEM_freeN(p);
+
+       //printf("\t\tGP - start stroke \n");
+
+       /* we may need to set up paint env again if we're resuming */
+       // XXX: watch it with the paintmode! in future, it'd be nice to allow changing paint-mode when in sketching-sessions
+       // XXX: with tablet events, we may event want to check for eraser here, for nicer tablet support
+
+       gpencil_draw_init(C, op);
+
+       p= op->customdata;
+
+       if(p->status != GP_STATUS_ERROR)
+               p->status= GP_STATUS_PAINTING;
+
+       return op->customdata;
+}
+
+static void gpencil_stroke_end(wmOperator *op)
+{
+       tGPsdata *p= op->customdata;
+
+       gp_paint_cleanup(p);
+
+       gpencil_undo_push(p->gpd);
+
+       gp_session_cleanup(p);
+
+       p->status= GP_STATUS_IDLING;
+
+       p->gpd= NULL;
+       p->gpl= NULL;
+       p->gpf= NULL;
+}
+
 /* events handling during interactive drawing part of operator */
 static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
 {
        tGPsdata *p= op->customdata;
-       //int estate = OPERATOR_PASS_THROUGH; /* default exit state - not handled, so let others have a share of the pie */
-       /* currently, grease pencil conflicts with such operators as undo and set object mode
-          which makes behavior of operator totally unpredictable and crash for some cases.
-          the only way to solve this proper is to ger rid of pointers to data which can
-          chage stored in operator custom data (sergey) */
-       int estate = OPERATOR_RUNNING_MODAL;
+       int estate = OPERATOR_PASS_THROUGH; /* default exit state - not handled, so let others have a share of the pie */
        
        // if (event->type == NDOF_MOTION)
        //      return OPERATOR_PASS_THROUGH;
@@ -1652,11 +1704,13 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
                        if (GPENCIL_SKETCH_SESSIONS_ON(p->scene)) {
                                /* end stroke only, and then wait to resume painting soon */
                                //printf("\t\tGP - end stroke only\n");
-                               gp_paint_cleanup(p);
-                               p->status= GP_STATUS_IDLING;
+                               gpencil_stroke_end(op);
                                
                                /* we've just entered idling state, so this event was processed (but no others yet) */
                                estate = OPERATOR_RUNNING_MODAL;
+
+                               /* stroke could be smoothed, send notifier to refresh screen */
+                               ED_region_tag_redraw(p->ar);
                        }
                        else {
                                //printf("\t\tGP - end of stroke + op\n");
@@ -1664,35 +1718,19 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
                                estate = OPERATOR_FINISHED;
                        }
                }
-               else {
+               else if (event->val == KM_PRESS) {
                        /* not painting, so start stroke (this should be mouse-button down) */
                        
-                       /* we must check that we're still within the area that we're set up to work from
-                        * otherwise we could crash (see bug #20586)
-                        */
-                       if (CTX_wm_area(C) != p->sa) {
-                               //printf("\t\t\tGP - wrong area execution abort! \n");
-                               p->status= GP_STATUS_ERROR;
+                       p= gpencil_stroke_begin(C, op);
+
+                       if (p->status == GP_STATUS_ERROR) {
                                estate = OPERATOR_CANCELLED;
                        }
-                       else {
-                               //printf("\t\tGP - start stroke \n");
-                               p->status= GP_STATUS_PAINTING;
-                               
-                               /* we may need to set up paint env again if we're resuming */
-                               // XXX: watch it with the paintmode! in future, it'd be nice to allow changing paint-mode when in sketching-sessions
-                               // XXX: with tablet events, we may event want to check for eraser here, for nicer tablet support
-                               gp_paint_initstroke(p, p->paintmode);
-                               
-                               if (p->status == GP_STATUS_ERROR) {
-                                       estate = OPERATOR_CANCELLED;
-                               }
-                       }
+               } else {
+                       p->status = GP_STATUS_IDLING;
                }
        }
        
-       
-       
        /* handle mode-specific events */
        if (p->status == GP_STATUS_PAINTING) {
                /* handle painting mouse-movements? */
@@ -1704,7 +1742,7 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
                        
                        /* finish painting operation if anything went wrong just now */
                        if (p->status == GP_STATUS_ERROR) {
-                               //printf("\t\t\t\tGP - add error done! \n");
+                               printf("\t\t\t\tGP - add error done! \n");
                                estate = OPERATOR_CANCELLED;
                        }
                        else {
@@ -1721,28 +1759,6 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
                        estate = OPERATOR_RUNNING_MODAL;
                }
        }
-       else if (p->status == GP_STATUS_IDLING) {
-               /* standard undo/redo shouldn't be allowed to execute or else it causes crashes, so catch it here */
-               // FIXME: this is a hardcoded hotkey that can't be changed
-               // TODO: catch redo as well, but how?
-               if (event->type == ZKEY && event->val == KM_RELEASE) {
-                       /* oskey = cmd key on macs as they seem to use cmd-z for undo as well? */
-                       if ((event->ctrl) || (event->oskey)) {
-                               /* just delete last stroke, which will look like undo to the end user */
-                               //printf("caught attempted undo event... deleting last stroke \n");
-                               gpencil_frame_delete_laststroke(p->gpl, p->gpf);
-                               /* undoing the last line can free p->gpf
-                                * note, could do this in a bit more of an elegant way then a search but it at least prevents a crash */
-                               if(BLI_findindex(&p->gpl->frames, p->gpf) == -1) {
-                                       p->gpf= NULL;
-                               }
-
-                               /* event handled, so force refresh */
-                               ED_region_tag_redraw(p->ar); /* just active area for now, since doing whole screen is too slow */
-                               estate = OPERATOR_RUNNING_MODAL; 
-                       }
-               }
-       }
        
        /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */
        if(0==gpencil_area_exists(C, p->sa))
diff --git a/source/blender/editors/gpencil/gpencil_undo.c b/source/blender/editors/gpencil/gpencil_undo.c
new file mode 100644 (file)
index 0000000..1154975
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * $Id$
+ *
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2011 Blender Foundation.
+ * All rights reserved.
+ *
+ *
+ * Contributor(s): Blender Foundation,
+ *                 Sergey Sharybin
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_listBase.h"
+#include "DNA_windowmanager_types.h"
+
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+
+#include "BLI_listbase.h"
+
+#include "ED_gpencil.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "gpencil_intern.h"
+
+#define MAXUNDONAME    64
+
+typedef struct bGPundonode {
+       struct bGPundonode *next, *prev;
+
+       char name[MAXUNDONAME];
+       struct bGPdata *gpd;
+} bGPundonode;
+
+static ListBase undo_nodes = {NULL, NULL};
+static bGPundonode *cur_node = NULL;
+
+int ED_gpencil_session_active(void)
+{
+       return undo_nodes.first != NULL;
+}
+
+int ED_undo_gpencil_step(bContext *C, int step, const char *name)
+{
+       bGPdata **gpd_ptr= NULL, *new_gpd= NULL;
+
+       gpd_ptr= gpencil_data_get_pointers(C, NULL);
+
+       if(step==1) {   /* undo */
+               //printf("\t\tGP - undo step\n");
+               if(cur_node->prev) {
+                       if(!name || strcmp(cur_node->name, name) == 0) {
+                               cur_node= cur_node->prev;
+                               new_gpd= cur_node->gpd;
+                       }
+               }
+       }
+       else if (step==-1) {
+               //printf("\t\tGP - redo step\n");
+               if(cur_node->next) {
+                       if(!name || strcmp(cur_node->name, name) == 0) {
+                               cur_node= cur_node->next;
+                               new_gpd= cur_node->gpd;
+                       }
+               }
+       }
+
+       if(new_gpd) {
+               if(gpd_ptr) {
+                       if(*gpd_ptr) {
+                               bGPdata *gpd= *gpd_ptr;
+                               bGPDlayer *gpl, *gpld;
+
+                               free_gpencil_layers(&gpd->layers);
+
+                               /* copy layers */
+                               gpd->layers.first= gpd->layers.last= NULL;
+
+                               for (gpl= new_gpd->layers.first; gpl; gpl= gpl->next) {
+                                       /* make a copy of source layer and its data */
+                                       gpld= gpencil_layer_duplicate(gpl);
+                                       BLI_addtail(&gpd->layers, gpld);
+                               }
+                       }
+               }
+       }
+
+       WM_event_add_notifier(C, NC_SCREEN|ND_GPENCIL|NA_EDITED, NULL);
+
+       return OPERATOR_FINISHED;
+}
+
+void gpencil_undo_init(bGPdata *gpd)
+{
+       gpencil_undo_push(gpd);
+}
+
+void gpencil_undo_push(bGPdata *gpd)
+{
+       bGPundonode *undo_node;
+
+       //printf("\t\tGP - undo push\n");
+
+       if(cur_node) {
+               /* remove all un-done nodes from stack */
+               undo_node= cur_node->next;
+
+               while(undo_node) {
+                       bGPundonode *next_node= undo_node->next;
+
+                       free_gpencil_data(undo_node->gpd);
+                       MEM_freeN(undo_node->gpd);
+
+                       BLI_freelinkN(&undo_nodes, undo_node);
+
+                       undo_node= next_node;
+               }
+       }
+
+       /* create new undo node */
+       undo_node= MEM_callocN(sizeof(bGPundonode), "gpencil undo node");
+       undo_node->gpd= gpencil_data_duplicate(gpd);
+
+       cur_node= undo_node;
+
+       BLI_addtail(&undo_nodes, undo_node);
+}
+
+void gpencil_undo_finish(void)
+{
+       bGPundonode *undo_node= undo_nodes.first;
+
+       while(undo_node) {
+               free_gpencil_data(undo_node->gpd);
+               MEM_freeN(undo_node->gpd);
+
+               undo_node= undo_node->next;
+       }
+
+       BLI_freelistN(&undo_nodes);
+
+       cur_node= NULL;
+}
index 07dcc959e32c619fecc56550b7beed98d7aaf05f..bfd16487ae5e7dc71efdc652bf48f797220f16ba 100644 (file)
@@ -106,4 +106,8 @@ void paste_gpdata(void);
 void snap_gplayer_frames(struct bGPDlayer *gpl, short mode);
 void mirror_gplayer_frames(struct bGPDlayer *gpl, short mode);
 
+/* ------------ Grease-Pencil Undo System ------------------ */
+int ED_gpencil_session_active(void);
+int ED_undo_gpencil_step(struct bContext *C, int step, const char *name);
+
 #endif /*  ED_GPENCIL_H */
index a2381a208ef015a452d0e73fa1eeb8a5a3ae62dd..c1aca61f7950e60e6401dd935d2348d9e7438c62 100644 (file)
@@ -54,6 +54,7 @@
 #include "ED_armature.h"
 #include "ED_particle.h"
 #include "ED_curve.h"
+#include "ED_gpencil.h"
 #include "ED_mball.h"
 #include "ED_mesh.h"
 #include "ED_object.h"
@@ -126,6 +127,11 @@ static int ed_undo_step(bContext *C, int step, const char *undoname)
        Object *obact= CTX_data_active_object(C);
        ScrArea *sa= CTX_wm_area(C);
 
+       /* grease pencil can be can be used in plenty of spaces, so check it first */
+       if(ED_gpencil_session_active()) {
+               return ED_undo_gpencil_step(C, step, undoname);
+       }
+
        if(sa && sa->spacetype==SPACE_IMAGE) {
                SpaceImage *sima= (SpaceImage *)sa->spacedata.first;