GPencil: New extrude operator
authorAntonioya <blendergit@gmail.com>
Mon, 4 Mar 2019 18:31:36 +0000 (19:31 +0100)
committerAntonioya <blendergit@gmail.com>
Mon, 4 Mar 2019 18:40:50 +0000 (19:40 +0100)
Keymap: E   - Extrude Gizmo supported.

If the extrude points are the first or last of the stroke, the stroke is extended.

If the extrude points are in the middle of the stroke, a new stroke is created because the grease pencil strokes can be only with 2 extremes and fold the stroke to get a new point gets very bad results.

Still pending define a new icon. Also, it could be good to set by default XYZ axis in the gizmo.

Note: There is a change in the transform_gizmo_extrude_3d.c gizmo for OB_MODE_EDIT_GPENCIL. This change must be undo when the mode will be integrated into OB_MODE_EDIT, but while we have both modes, we need to keep this code in order to keep running the gizmo.

release/scripts/presets/keyconfig/keymap_data/blender_default.py
release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
source/blender/editors/gpencil/gpencil_edit.c
source/blender/editors/gpencil/gpencil_intern.h
source/blender/editors/gpencil/gpencil_ops.c
source/blender/editors/transform/transform_gizmo_extrude_3d.c

index 72d57f8e5b8b15ced306ce0572df951449fa439e..34a6d6f46639698c9751fe58ab74ef1c2c22aff9 100644 (file)
@@ -2951,6 +2951,8 @@ def km_grease_pencil_stroke_edit_mode(params):
         *_grease_pencil_selection(params),
         # Duplicate and move selected points
         ("gpencil.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, None),
+        # Extrude and move selected points
+        ("gpencil.extrude_move", {"type": 'E', "value": 'PRESS'}, None),
         # Delete
         op_menu("VIEW3D_MT_edit_gpencil_delete", {"type": 'X', "value": 'PRESS'}),
         op_menu("VIEW3D_MT_edit_gpencil_delete", {"type": 'DEL', "value": 'PRESS'}),
index 5b6c96c3d5c0f17a33ad839f949d26f4533a7260..370c71855830565ed148d8e1fa7f3dd36bf75f3f 100644 (file)
@@ -1250,6 +1250,16 @@ class _defs_gpencil_edit:
             keymap=(),
         )
 
+    @ToolDef.from_fn
+    def extrude():
+        return dict(
+            text="Extrude",
+            icon="ops.gpencil.extrude_move",
+            widget="VIEW3D_GGT_xform_extrude",
+            keymap=(),
+            draw_settings=_template_widget.VIEW3D_GGT_xform_extrude.draw_settings,
+        )
+
 
 class _defs_gpencil_sculpt:
 
@@ -1697,9 +1707,11 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
             None,
             *_tools_transform,
             None,
+            _defs_gpencil_edit.extrude,
             _defs_gpencil_edit.bend,
             _defs_gpencil_edit.shear,
             _defs_gpencil_edit.tosphere,
+
         ],
         'SCULPT_GPENCIL': [
             *_tools_gpencil_select,
index 4ba745827747594fb9f9c40dda4a3a9afc18dc67..c9a2cd5907fb7fc385f6202f6d1ba6576f0a4aa9 100644 (file)
@@ -754,6 +754,219 @@ void GPENCIL_OT_duplicate(wmOperatorType *ot)
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
+/* ************** Extrude Selected Strokes **************** */
+
+/* helper to copy a point to temp area */
+static void copy_point(bGPDstroke *gps,
+                                       bGPDspoint *temp_points,
+                                       MDeformVert *temp_dverts,
+                                       int from_idx, int to_idx)
+{
+       bGPDspoint *pt = &temp_points[from_idx];
+       bGPDspoint *pt_final = &gps->points[to_idx];
+
+       copy_v3_v3(&pt_final->x, &pt->x);
+       pt_final->pressure = pt->pressure;
+       pt_final->strength = pt->strength;
+       pt_final->time = pt->time;
+       pt_final->flag = pt->flag;
+       pt_final->uv_fac = pt->uv_fac;
+       pt_final->uv_rot = pt->uv_rot;
+
+       if (gps->dvert != NULL) {
+               MDeformVert *dvert = &temp_dverts[from_idx];
+               MDeformVert *dvert_final = &gps->dvert[to_idx];
+
+               dvert_final->totweight = dvert->totweight;
+               dvert_final->dw = dvert->dw;
+       }
+}
+
+static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps)
+{
+       bGPDspoint *temp_points = NULL;
+       MDeformVert *temp_dverts = NULL;
+       bGPDspoint *pt = NULL;
+       const bGPDspoint *pt_start = &gps->points[0];
+       const bGPDspoint *pt_last = &gps->points[gps->totpoints - 1];
+       const bool do_first = (pt_start->flag & GP_SPOINT_SELECT);
+       const bool do_last = ((pt_last->flag & GP_SPOINT_SELECT) && (pt_start != pt_last));
+       const bool do_stroke = (do_first || do_last);
+
+       /* review points in the midle of stroke to create new strokes */
+       for (int i = 0; i < gps->totpoints; i++) {
+               /* skip first and last point */
+               if ((i == 0) || (i == gps->totpoints - 1)) {
+                       continue;
+               }
+
+               pt = &gps->points[i];
+               if (pt->flag == GP_SPOINT_SELECT) {
+                       /* duplicate original stroke data */
+                       bGPDstroke *gps_new = MEM_dupallocN(gps);
+                       gps_new->prev = gps_new->next = NULL;
+
+                       /* add new points array */
+                       gps_new->totpoints = 1;
+                       gps_new->points = MEM_callocN(sizeof(bGPDspoint), __func__);
+                       gps_new->dvert = NULL;
+
+                       if (gps->dvert != NULL) {
+                               gps_new->dvert = MEM_callocN(sizeof(MDeformVert), __func__);
+                       }
+
+                       gps->flag |= GP_STROKE_RECALC_GEOMETRY;
+                       gps_new->triangles = NULL;
+                       gps_new->tot_triangles = 0;
+                       BLI_insertlinkafter(&gpf->strokes, gps, gps_new);
+
+                       /* copy selected point data to new stroke */
+                       copy_point(gps_new, gps->points, gps->dvert, i, 0);
+
+                       /* deselect orinal point */
+                       pt->flag &= ~GP_SPOINT_SELECT;
+               }
+       }
+
+       /* review first and last point to reuse same stroke */
+       int i2 = 0;
+       int totnewpoints, oldtotpoints;
+       /* if first or last, reuse stroke and resize */
+       if ((do_first) || (do_last)) {
+               totnewpoints = gps->totpoints;
+               if (do_first) {
+                       totnewpoints++;
+               }
+               if (do_last) {
+                       totnewpoints++;
+               }
+
+               /* duplicate points in a temp area */
+               temp_points = MEM_dupallocN(gps->points);
+               oldtotpoints = gps->totpoints;
+               if (gps->dvert != NULL) {
+                       temp_dverts = MEM_dupallocN(gps->dvert);
+               }
+
+               /* if first point, need move all one position */
+               if (do_first) {
+                       i2 = 1;
+               }
+
+               /* resize the points arrays */
+               gps->totpoints = totnewpoints;
+               gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
+               if (gps->dvert != NULL) {
+                       gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
+               }
+
+               /* move points to new position */
+               for (int i = 0; i < oldtotpoints; i++) {
+                       copy_point(gps, temp_points, temp_dverts, i, i2);
+                       i2++;
+               }
+               gps->flag |= GP_STROKE_RECALC_GEOMETRY;
+
+               /* if first point, add new point at the begining */
+               if (do_first) {
+                       copy_point(gps, temp_points, temp_dverts, 0, 0);
+                       /* deselect old */
+                       pt = &gps->points[1];
+                       pt->flag &= ~GP_SPOINT_SELECT;
+                       /* select new */
+                       pt = &gps->points[0];
+                       pt->flag |= GP_SPOINT_SELECT;
+               }
+
+               /* if last point, add new point at the end */
+               if (do_last) {
+                       copy_point(gps, temp_points, temp_dverts,
+                               oldtotpoints - 1, gps->totpoints - 1);
+
+                       /* deselect old */
+                       pt = &gps->points[gps->totpoints - 2];
+                       pt->flag &= ~GP_SPOINT_SELECT;
+                       /* select new */
+                       pt = &gps->points[gps->totpoints - 1];
+                       pt->flag |= GP_SPOINT_SELECT;
+               }
+
+               MEM_SAFE_FREE(temp_points);
+               MEM_SAFE_FREE(temp_dverts);
+       }
+
+       /* if the stroke is not reused, deselect */
+       if (!do_stroke) {
+               gps->flag &= ~GP_STROKE_SELECT;
+       }
+}
+
+static int gp_extrude_exec(bContext *C, wmOperator *op)
+{
+       Object *obact = CTX_data_active_object(C);
+       bGPdata *gpd = (bGPdata *)obact->data;
+       const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
+       bGPDstroke *gps = NULL;
+
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
+               return OPERATOR_CANCELLED;
+       }
+
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *init_gpf = gpl->actframe;
+               if (is_multiedit) {
+                       init_gpf = gpl->frames.first;
+               }
+
+               for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
+                       if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
+                               if (gpf == NULL)
+                                       continue;
+
+                               for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                                       /* skip strokes that are invalid for current view */
+                                       if (ED_gpencil_stroke_can_use(C, gps) == false)
+                                               continue;
+
+                                       if (gps->flag & GP_STROKE_SELECT) {
+                                               gpencil_add_move_points(gpf, gps);
+                                       }
+                               }
+                               /* if not multiedit, exit loop*/
+                               if (!is_multiedit) {
+                                       break;
+                               }
+                       }
+               }
+       }
+       CTX_DATA_END;
+
+       /* updates */
+       DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
+       DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE);
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_extrude(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Extrude Stroke Points";
+       ot->idname = "GPENCIL_OT_extrude";
+       ot->description = "Extrude the selected Grease Pencil points";
+
+       /* callbacks */
+       ot->exec = gp_extrude_exec;
+       ot->poll = gp_stroke_edit_poll;
+
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+
 /* ******************* Copy/Paste Strokes ************************* */
 /* Grease Pencil stroke data copy/paste buffer:
  * - The copy operation collects all segments of selected strokes,
index 9341133d52060a401977ac8fa8415c36ba9739ed..4fba83a5f02f9ab057e3100d0ed762c722dd331a 100644 (file)
@@ -389,6 +389,7 @@ void GPENCIL_OT_delete(struct wmOperatorType *ot);
 void GPENCIL_OT_dissolve(struct wmOperatorType *ot);
 void GPENCIL_OT_copy(struct wmOperatorType *ot);
 void GPENCIL_OT_paste(struct wmOperatorType *ot);
+void GPENCIL_OT_extrude(struct wmOperatorType *ot);
 
 void GPENCIL_OT_move_to_layer(struct wmOperatorType *ot);
 void GPENCIL_OT_layer_change(struct wmOperatorType *ot);
index 60b2fb5d64e2a20268f70d26fa1ce1fe55cb85ea..e3b13b26672da35039627c9b5dce38d04188cc8b 100644 (file)
@@ -254,6 +254,7 @@ void ED_operatortypes_gpencil(void)
        WM_operatortype_append(GPENCIL_OT_dissolve);
        WM_operatortype_append(GPENCIL_OT_copy);
        WM_operatortype_append(GPENCIL_OT_paste);
+       WM_operatortype_append(GPENCIL_OT_extrude);
 
        WM_operatortype_append(GPENCIL_OT_move_to_layer);
        WM_operatortype_append(GPENCIL_OT_layer_change);
@@ -361,6 +362,13 @@ void ED_operatormacros_gpencil(void)
        otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
        RNA_boolean_set(otmacro->ptr, "gpencil_strokes", true);
 
+       /* Extrude + Move = Interactively add new points */
+       ot = WM_operatortype_append_macro("GPENCIL_OT_extrude_move", "Extrude Stroke Points",
+                                                                         "Extrude selected points and move them",
+                                                                         OPTYPE_UNDO | OPTYPE_REGISTER);
+       WM_operatortype_macro_define(ot, "GPENCIL_OT_extrude");
+       otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
+       RNA_boolean_set(otmacro->ptr, "gpencil_strokes", true);
 }
 
 /* ****************************************** */
index b1ea7b87efb91c372484e49ad220b37cffca7564..81f4ec8c30a663fa63b03600c5e3817acf93cd32 100644 (file)
@@ -147,7 +147,13 @@ static void gizmo_mesh_extrude_setup(const bContext *C, wmGizmoGroup *gzgroup)
        {
                const Object *obedit = CTX_data_edit_object(C);
                const char *op_idname = NULL;
-               if (obedit->type == OB_MESH) {
+               /* grease pencil does not use obedit */
+               /* GPXX: Remove if OB_MODE_EDIT_GPENCIL is merged with OB_MODE_EDIT */
+               const Object *ob = CTX_data_active_object(C);
+               if ((ob) && (ob->type == OB_GPENCIL)) {
+                       op_idname = "GPENCIL_OT_extrude_move";
+               }
+               else if (obedit->type == OB_MESH) {
                        op_idname = "MESH_OT_extrude_context_move";
                        ggd->normal_axis = 2;
                }