GPencil Editing: Copy and Paste selected stroke segments with Ctrl-C and Ctrl-V
authorJoshua Leung <aligorith@gmail.com>
Wed, 31 Dec 2014 23:36:01 +0000 (12:36 +1300)
committerJoshua Leung <aligorith@gmail.com>
Wed, 31 Dec 2014 23:49:59 +0000 (12:49 +1300)
source/blender/editors/gpencil/gpencil_edit.c
source/blender/editors/gpencil/gpencil_intern.h
source/blender/editors/gpencil/gpencil_ops.c
source/blender/editors/include/ED_gpencil.h
source/blender/windowmanager/intern/wm_init_exit.c

index be4cac666bd2573f0dfa2a0ffe5a46e8beccd5ce..3dae25263e8b844fd45b653500a21c3257995995 100644 (file)
@@ -673,8 +673,185 @@ void GPENCIL_OT_duplicate(wmOperatorType *ot)
 }
 
 /* ******************* Copy/Paste Strokes ************************* */
+/* Grease Pencil stroke data copy/paste buffer:
+ * - The copy operation collects all segments of selected strokes,
+ *   dumping "ready to be copied" copies of the strokes into the buffer.
+ * - The paste operation makes a copy of those elements, and adds them
+ *   to the active layer. This effectively flattens down the strokes
+ *   from several different layers into a single layer.
+ */
+
+/* list of bGPDstroke instances */
+static ListBase gp_strokes_copypastebuf = {NULL, NULL};
+
+/* Free copy/paste buffer data */
+void ED_gpencil_strokes_copybuf_free(void)
+{
+       bGPDstroke *gps, *gpsn;
+       
+       for (gps = gp_strokes_copypastebuf.first; gps; gps = gpsn) {
+               gpsn = gps->next;
+               
+               MEM_freeN(gps->points);
+               BLI_freelinkN(&gp_strokes_copypastebuf, gps);
+       }
+       
+       gp_strokes_copypastebuf.first = gp_strokes_copypastebuf.last = NULL;
+}
+
+/* --------------------- */
+/* Copy selected strokes */
+
+static int gp_strokes_copy_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* clear the buffer first */
+       ED_gpencil_strokes_copybuf_free();
+       
+       /* for each visible (and editable) layer's selected strokes,
+        * copy the strokes into a temporary buffer, then append
+        * once all done
+        */
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps;
+               
+               if (gpf == NULL)
+                       continue;
+               
+               /* make copies of selected strokes, and deselect these once we're done */
+               for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               if (gps->totpoints == 1) {
+                                       /* Special Case: If there's just a single point in this stroke... */
+                                       bGPDstroke *gpsd;
+                                       
+                                       /* make direct copies of the stroke and its points */
+                                       gpsd = MEM_dupallocN(gps);
+                                       gpsd->points = MEM_dupallocN(gps->points);
+                                       
+                                       /* add to temp buffer */
+                                       gpsd->next = gpsd->prev = NULL;
+                                       BLI_addtail(&gp_strokes_copypastebuf, gpsd);
+                               }
+                               else {
+                                       /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
+                                       gp_duplicate_points(gps, &gp_strokes_copypastebuf);
+                               }
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       /* done - no updates needed */
+       return OPERATOR_FINISHED;
+}
 
-// TODO:
+void GPENCIL_OT_copy(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Copy Strokes";
+       ot->idname = "GPENCIL_OT_copy";
+       ot->description = "Copy selected Grease Pencil points and strokes";
+       
+       /* callbacks */
+       ot->exec = gp_strokes_copy_exec;
+       ot->poll = gp_stroke_edit_poll;
+       
+       /* flags */
+       //ot->flag = OPTYPE_REGISTER;
+}
+
+/* --------------------- */
+/* Paste selected strokes */
+
+static int gp_strokes_paste_exec(bContext *C, wmOperator *op)
+{
+       Scene *scene = CTX_data_scene(C);
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
+       bGPDframe *gpf;
+       
+       /* check for various error conditions */
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
+               return OPERATOR_CANCELLED;
+       }
+       else if (gp_strokes_copypastebuf.first == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again");
+               return OPERATOR_CANCELLED;
+       }
+       else if (gpl == NULL) {
+               /* no active layer - let's just create one */
+               gpl = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), 1);
+       }
+       else if (gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) {
+               BKE_report(op->reports, RPT_ERROR, "Can not paste strokes when active layer is hidden or locked");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* Deselect all strokes first */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       pt->flag &= ~GP_SPOINT_SELECT;
+               }
+               
+               gps->flag &= ~GP_STROKE_SELECT;
+       }
+       CTX_DATA_END;
+       
+       /* Ensure we have a frame to draw into
+        * NOTE: Since this is an op which creates strokes,
+        *       we are obliged to add a new frame if one
+        *       doesn't exist already
+        */
+       gpf = gpencil_layer_getframe(gpl, CFRA, true);
+       
+       if (gpf) {
+               bGPDstroke *gps;
+               
+               /* Copy each stroke into the layer */
+               for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
+                       bGPDstroke *new_stroke = MEM_dupallocN(gps);
+                       
+                       new_stroke->points = MEM_dupallocN(gps->points);
+                       new_stroke->next = new_stroke->prev = NULL;
+                       
+                       BLI_addtail(&gpf->strokes, new_stroke);
+               }
+       }
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_paste(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Paste Strokes";
+       ot->idname = "GPENCIL_OT_paste";
+       ot->description = "Paste previously copied strokes into active layer";
+       
+       /* callbacks */
+       ot->exec = gp_strokes_paste_exec;
+       ot->poll = gp_stroke_edit_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
 
 /* ******************* Delete Active Frame ************************ */
 
index 3bf6355ccd1b7c7c72445496cd15594d630de098..564204344949e48f934dfe5280ee03174558e2e2 100644 (file)
@@ -125,6 +125,8 @@ void GPENCIL_OT_select_less(struct wmOperatorType *ot);
 
 void GPENCIL_OT_duplicate(struct wmOperatorType *ot);
 void GPENCIL_OT_delete(struct wmOperatorType *ot);
+void GPENCIL_OT_copy(struct wmOperatorType *ot);
+void GPENCIL_OT_paste(struct wmOperatorType *ot);
 
 /* buttons editing --- */
 
index e4776732b230779c977a01db2284af745c9ec9e9..8955443fd2cc1e36b94beba9987f61e552c043a7 100644 (file)
@@ -163,6 +163,14 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
        /* delete */
        WM_keymap_add_item(keymap, "GPENCIL_OT_delete", XKEY, KM_PRESS, 0, 0);
        
+       /* copy + paste */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_copy", CKEY, KM_PRESS, KM_CTRL, 0);
+       WM_keymap_add_item(keymap, "GPENCIL_OT_paste", VKEY, KM_PRESS, KM_CTRL, 0);
+       
+#ifdef __APPLE__
+       WM_keymap_add_item(keymap, "GPENCIL_OT_copy", CKEY, KM_PRESS, KM_OSKEY, 0);
+       WM_keymap_add_item(keymap, "GPENCIL_OT_paste", VKEY, KM_PRESS, KM_OSKEY, 0);
+#endif 
        
        /* Transform Tools */
        kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_translate", GKEY, KM_PRESS, 0, 0);
@@ -224,6 +232,8 @@ void ED_operatortypes_gpencil(void)
        
        WM_operatortype_append(GPENCIL_OT_duplicate);
        WM_operatortype_append(GPENCIL_OT_delete);
+       WM_operatortype_append(GPENCIL_OT_copy);
+       WM_operatortype_append(GPENCIL_OT_paste);
        
        /* Editing (Buttons) ------------ */
        
index cad33c34fe3828950f9a4d2410426461bf39bf58..b0d1be1bf5d3f75397a87ca9346804dec464b845 100644 (file)
@@ -87,6 +87,12 @@ void ED_keymap_gpencil(struct wmKeyConfig *keyconf);
 void ED_operatortypes_gpencil(void);
 void ED_operatormacros_gpencil(void);
 
+/* ------------- Copy-Paste Buffers -------------------- */
+
+/* Strokes copybuf */
+void ED_gpencil_strokes_copybuf_free(void);
+
+
 /* ------------ Grease-Pencil Drawing API ------------------ */
 /* drawgpencil.c */
 
index 1e100031f11c967034d88d0eaf8c62b4af50c297..cb03d022afd7f793183bb354650db5c8e71bd1ca 100644 (file)
@@ -96,6 +96,7 @@
 #include "wm_window.h"
 
 #include "ED_armature.h"
+#include "ED_gpencil.h"
 #include "ED_keyframing.h"
 #include "ED_node.h"
 #include "ED_render.h"
@@ -475,6 +476,7 @@ void WM_exit_ext(bContext *C, const bool do_python)
        free_anim_copybuf();
        free_anim_drivers_copybuf();
        free_fmodifiers_copybuf();
+       ED_gpencil_strokes_copybuf_free();
        ED_clipboard_posebuf_free();
        BKE_node_clipboard_clear();