diff options
Diffstat (limited to 'source/blender/editors/gpencil/gpencil_edit.c')
-rw-r--r-- | source/blender/editors/gpencil/gpencil_edit.c | 1966 |
1 files changed, 1665 insertions, 301 deletions
diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 312521d3ffb..072f4d0e317 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -25,9 +25,9 @@ * Operators for editing Grease Pencil strokes */ -/** \file blender/editors/gpencil/gpencil_edit.c - * \ingroup edgpencil - */ + /** \file blender/editors/gpencil/gpencil_edit.c + * \ingroup edgpencil + */ #include <stdio.h> @@ -47,6 +47,7 @@ #include "BLT_translation.h" +#include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" @@ -54,18 +55,26 @@ #include "DNA_view3d_types.h" #include "DNA_gpencil_types.h" +#include "BKE_brush.h" #include "BKE_context.h" +#include "BKE_global.h" #include "BKE_gpencil.h" #include "BKE_library.h" #include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object.h" +#include "BKE_paint.h" #include "BKE_report.h" #include "BKE_screen.h" +#include "BKE_workspace.h" #include "UI_interface.h" #include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" +#include "WM_message.h" +#include "WM_toolsystem.h" #include "RNA_access.h" #include "RNA_define.h" @@ -79,39 +88,128 @@ #include "ED_view3d.h" #include "ED_space_api.h" +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + #include "gpencil_intern.h" -/* ************************************************ */ -/* Stroke Edit Mode Management */ + /* ************************************************ */ + /* Stroke Edit Mode Management */ + +/* poll callback for all stroke editing operators */ +static bool gp_stroke_edit_poll(bContext *C) +{ + /* edit only supported with grease pencil objects */ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + + /* NOTE: this is a bit slower, but is the most accurate... */ + return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0; +} + +/* poll callback to verify edit mode in 3D view only */ +static bool gp_strokes_edit3d_poll(bContext *C) +{ + /* edit only supported with grease pencil objects */ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + + + /* 2 Requirements: + * - 1) Editable GP data + * - 2) 3D View only + */ + return (gp_stroke_edit_poll(C) && ED_operator_view3d_active(C)); +} static bool gpencil_editmode_toggle_poll(bContext *C) { + /* edit only supported with grease pencil objects */ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + + /* if using gpencil object, use this gpd */ + if (ob->type == OB_GPENCIL) { + return ob->data != NULL; + } + return ED_gpencil_data_get_active(C) != NULL; } -static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *op) { + const int back = RNA_boolean_get(op->ptr, "back"); + + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); bGPdata *gpd = ED_gpencil_data_get_active(C); + bool is_object = false; + short mode; + /* if using a gpencil object, use this datablock */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + gpd = ob->data; + is_object = true; + } - if (gpd == NULL) + if (gpd == NULL) { + BKE_report(op->reports, RPT_ERROR, "No active GP data"); return OPERATOR_CANCELLED; + } /* Just toggle editmode flag... */ gpd->flag ^= GP_DATA_STROKE_EDITMODE; /* recalculate parent matrix */ if (gpd->flag & GP_DATA_STROKE_EDITMODE) { - ED_gpencil_reset_layers_parent(gpd); + ED_gpencil_reset_layers_parent(depsgraph, ob, gpd); + } + /* set mode */ + if (gpd->flag & GP_DATA_STROKE_EDITMODE) { + mode = OB_MODE_GPENCIL_EDIT; + } + else { + mode = OB_MODE_OBJECT; + } + + if (is_object) { + /* try to back previous mode */ + if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_EDITMODE) == 0) && (back == 1)) { + mode = ob->restore_mode; + } + ob->restore_mode = ob->mode; + ob->mode = mode; } + /* setup other modes */ + ED_gpencil_setup_modes(C, gpd, mode); + /* set cache as dirty */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL); WM_event_add_notifier(C, NC_GPENCIL | ND_GPENCIL_EDITMODE, NULL); WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); + if (is_object) { + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + } + if (G.background == false) { + WM_toolsystem_update_from_context_view3d(C); + } + return OPERATOR_FINISHED; } void GPENCIL_OT_editmode_toggle(wmOperatorType *ot) { + PropertyRNA *prop; + /* identifiers */ ot->name = "Strokes Edit Mode Toggle"; ot->idname = "GPENCIL_OT_editmode_toggle"; @@ -123,33 +221,344 @@ void GPENCIL_OT_editmode_toggle(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } -/* ************************************************ */ -/* Stroke Editing Operators */ +/* set select mode */ +static int gpencil_selectmode_toggle_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = CTX_data_tool_settings(C); + const int mode = RNA_int_get(op->ptr, "mode"); -/* poll callback for all stroke editing operators */ -static bool gp_stroke_edit_poll(bContext *C) + /* Just set mode */ + ts->gpencil_selectmode = mode; + + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_selectmode_toggle(wmOperatorType *ot) { - /* NOTE: this is a bit slower, but is the most accurate... */ - return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0; + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Mode Toggle"; + ot->idname = "GPENCIL_OT_selectmode_toggle"; + ot->description = "Set selection mode for Grease Pencil strokes"; + + /* callbacks */ + ot->exec = gpencil_selectmode_toggle_exec; + ot->poll = gp_strokes_edit3d_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_int(ot->srna, "mode", 0, 0, 1, "Select mode", "Select mode", 0, 1); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } -/* ************ Stroke Hide selection Toggle ************** */ +/* Stroke Paint Mode Management */ -static int gpencil_hideselect_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +static bool gpencil_paintmode_toggle_poll(bContext *C) { + /* if using gpencil object, use this gpd */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + return ob->data != NULL; + } + return ED_gpencil_data_get_active(C) != NULL; +} + +static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op) +{ + const bool back = RNA_boolean_get(op->ptr, "back"); + + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + Main *bmain = CTX_data_main(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); ToolSettings *ts = CTX_data_tool_settings(C); - if (ts == NULL) + bool is_object = false; + short mode; + /* if using a gpencil object, use this datablock */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + gpd = ob->data; + is_object = true; + } + + if (gpd == NULL) + return OPERATOR_CANCELLED; + + /* Just toggle paintmode flag... */ + gpd->flag ^= GP_DATA_STROKE_PAINTMODE; + /* set mode */ + if (gpd->flag & GP_DATA_STROKE_PAINTMODE) { + mode = OB_MODE_GPENCIL_PAINT; + } + else { + mode = OB_MODE_OBJECT; + } + + if (is_object) { + /* try to back previous mode */ + if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0) && (back == 1)) { + mode = ob->restore_mode; + } + ob->restore_mode = ob->mode; + ob->mode = mode; + } + + if (mode == OB_MODE_GPENCIL_PAINT) { + /* be sure we have brushes */ + BKE_paint_ensure(ts, (Paint **)&ts->gp_paint); + Paint *paint = &ts->gp_paint->paint; + /* if not exist, create a new one */ + if (paint->brush == NULL) { + BKE_brush_gpencil_presets(C); + } + BKE_paint_toolslots_brush_validate(bmain, &ts->gp_paint->paint); + } + + /* setup other modes */ + ED_gpencil_setup_modes(C, gpd, mode); + /* set cache as dirty */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); + + if (is_object) { + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + } + if (G.background == false) { + WM_toolsystem_update_from_context_view3d(C); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_paintmode_toggle(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Strokes Paint Mode Toggle"; + ot->idname = "GPENCIL_OT_paintmode_toggle"; + ot->description = "Enter/Exit paint mode for Grease Pencil strokes"; + + /* callbacks */ + ot->exec = gpencil_paintmode_toggle_exec; + ot->poll = gpencil_paintmode_toggle_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + +/* Stroke Sculpt Mode Management */ + +static bool gpencil_sculptmode_toggle_poll(bContext *C) +{ + /* if using gpencil object, use this gpd */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + return ob->data != NULL; + } + return ED_gpencil_data_get_active(C) != NULL; +} + +static int gpencil_sculptmode_toggle_exec(bContext *C, wmOperator *op) +{ + const bool back = RNA_boolean_get(op->ptr, "back"); + + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + bool is_object = false; + short mode; + /* if using a gpencil object, use this datablock */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + gpd = ob->data; + is_object = true; + } + + if (gpd == NULL) + return OPERATOR_CANCELLED; + + /* Just toggle sculptmode flag... */ + gpd->flag ^= GP_DATA_STROKE_SCULPTMODE; + /* set mode */ + if (gpd->flag & GP_DATA_STROKE_SCULPTMODE) { + mode = OB_MODE_GPENCIL_SCULPT; + } + else { + mode = OB_MODE_OBJECT; + } + + if (is_object) { + /* try to back previous mode */ + if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_SCULPTMODE) == 0) && (back == 1)) { + mode = ob->restore_mode; + } + ob->restore_mode = ob->mode; + ob->mode = mode; + } + + /* setup other modes */ + ED_gpencil_setup_modes(C, gpd, mode); + /* set cache as dirty */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); + + if (is_object) { + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + } + if (G.background == false) { + WM_toolsystem_update_from_context_view3d(C); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_sculptmode_toggle(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Strokes Sculpt Mode Toggle"; + ot->idname = "GPENCIL_OT_sculptmode_toggle"; + ot->description = "Enter/Exit sculpt mode for Grease Pencil strokes"; + + /* callbacks */ + ot->exec = gpencil_sculptmode_toggle_exec; + ot->poll = gpencil_sculptmode_toggle_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + +/* Stroke Weight Paint Mode Management */ + +static bool gpencil_weightmode_toggle_poll(bContext *C) +{ + /* if using gpencil object, use this gpd */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + return ob->data != NULL; + } + return ED_gpencil_data_get_active(C) != NULL; +} + +static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op) +{ + const bool back = RNA_boolean_get(op->ptr, "back"); + + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + bool is_object = false; + short mode; + /* if using a gpencil object, use this datablock */ + Object *ob = CTX_data_active_object(C); + if ((ob) && (ob->type == OB_GPENCIL)) { + gpd = ob->data; + is_object = true; + } + + if (gpd == NULL) + return OPERATOR_CANCELLED; + + /* Just toggle weightmode flag... */ + gpd->flag ^= GP_DATA_STROKE_WEIGHTMODE; + /* set mode */ + if (gpd->flag & GP_DATA_STROKE_WEIGHTMODE) { + mode = OB_MODE_GPENCIL_WEIGHT; + } + else { + mode = OB_MODE_OBJECT; + } + + if (is_object) { + /* try to back previous mode */ + if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_WEIGHTMODE) == 0) && (back == 1)) { + mode = ob->restore_mode; + } + ob->restore_mode = ob->mode; + ob->mode = mode; + } + + /* setup other modes */ + ED_gpencil_setup_modes(C, gpd, mode); + /* set cache as dirty */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); + + if (is_object) { + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + } + if (G.background == false) { + WM_toolsystem_update_from_context_view3d(C); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_weightmode_toggle(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Strokes Weight Mode Toggle"; + ot->idname = "GPENCIL_OT_weightmode_toggle"; + ot->description = "Enter/Exit weight paint mode for Grease Pencil strokes"; + + /* callbacks */ + ot->exec = gpencil_weightmode_toggle_exec; + ot->poll = gpencil_weightmode_toggle_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + +/* ************************************************ */ +/* Stroke Editing Operators */ + +/* ************ Stroke Hide selection Toggle ************** */ + +static int gpencil_hideselect_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +{ + View3D *v3d = CTX_wm_view3d(C); + if (v3d == NULL) return OPERATOR_CANCELLED; /* Just toggle alpha... */ - if (ts->gp_sculpt.alpha > 0.0f) { - ts->gp_sculpt.alpha = 0.0f; + if (v3d->vertex_opacity > 0.0f) { + v3d->vertex_opacity = 0.0f; } else { - ts->gp_sculpt.alpha = 1.0f; + v3d->vertex_opacity = 1.0f; } WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL); @@ -218,7 +627,7 @@ static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes, co /* make a stupid copy first of the entire stroke (to get the flags too) */ gpsd = MEM_dupallocN(gps); - BLI_strncpy(gpsd->tmp_layerinfo, layername, sizeof(gpsd->tmp_layerinfo)); /* saves original layer name */ + BLI_strncpy(gpsd->runtime.tmp_layerinfo, layername, sizeof(gpsd->runtime.tmp_layerinfo)); /* saves original layer name */ /* initialize triangle memory - will be calculated on next redraw */ gpsd->triangles = NULL; @@ -230,6 +639,20 @@ static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes, co memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len); gpsd->totpoints = len; + if (gps->dvert != NULL) { + gpsd->dvert = MEM_callocN(sizeof(MDeformVert) * len, "gps stroke weights copy"); + memcpy(gpsd->dvert, gps->dvert + start_idx, sizeof(MDeformVert) * len); + + /* Copy weights */ + int e = start_idx; + for (int j = 0; j < gpsd->totpoints; j++) { + MDeformVert *dvert_dst = &gps->dvert[e]; + MDeformVert *dvert_src = &gps->dvert[j]; + dvert_dst->dw = MEM_dupallocN(dvert_src->dw); + e++; + } + } + /* add to temp buffer */ gpsd->next = gpsd->prev = NULL; BLI_addtail(new_strokes, gpsd); @@ -250,6 +673,11 @@ static int gp_duplicate_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) { + BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition"); + return OPERATOR_CANCELLED; + } + /* for each visible (and editable) layer's selected strokes, * copy the strokes into a temporary buffer, then append * once all done @@ -277,8 +705,12 @@ static int gp_duplicate_exec(bContext *C, wmOperator *op) /* make direct copies of the stroke and its points */ gpsd = MEM_dupallocN(gps); - BLI_strncpy(gpsd->tmp_layerinfo, gpl->info, sizeof(gpsd->tmp_layerinfo)); + BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); gpsd->points = MEM_dupallocN(gps->points); + if (gps->dvert != NULL) { + gpsd->dvert = MEM_dupallocN(gps->dvert); + BKE_gpencil_stroke_weights_duplicate(gps, gpsd); + } /* triangle information - will be calculated on next redraw */ gpsd->flag |= GP_STROKE_RECALC_CACHES; @@ -307,6 +739,7 @@ static int gp_duplicate_exec(bContext *C, wmOperator *op) CTX_DATA_END; /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -336,24 +769,58 @@ void GPENCIL_OT_duplicate(wmOperatorType *ot) * from several different layers into a single layer. */ -/* list of bGPDstroke instances */ -/* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */ + /* list of bGPDstroke instances */ + /* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */ ListBase gp_strokes_copypastebuf = {NULL, NULL}; -/* Hash for hanging on to all the palette colors used by strokes in the buffer +/* Hash for hanging on to all the colors used by strokes in the buffer * * This is needed to prevent dangling and unsafe pointers when pasting across datablocks, * or after a color used by a stroke in the buffer gets deleted (via user action or undo). */ static GHash *gp_strokes_copypastebuf_colors = NULL; +static GHash *gp_strokes_copypastebuf_colors_material_to_name_create(Main *bmain) +{ + GHash *ma_to_name = BLI_ghash_ptr_new(__func__); + + for (Material *ma = bmain->mat.first; ma != NULL; ma = ma->id.next) { + char *name = BKE_id_to_unique_string_key(&ma->id); + BLI_ghash_insert(ma_to_name, ma, name); + } + + return ma_to_name; +} + +static void gp_strokes_copypastebuf_colors_material_to_name_free(GHash *ma_to_name) +{ + BLI_ghash_free(ma_to_name, NULL, MEM_freeN); +} + +static GHash *gp_strokes_copypastebuf_colors_name_to_material_create(Main *bmain) +{ + GHash *name_to_ma = BLI_ghash_str_new(__func__); + + for (Material *ma = bmain->mat.first; ma != NULL; ma = ma->id.next) { + char *name = BKE_id_to_unique_string_key(&ma->id); + BLI_ghash_insert(name_to_ma, name, ma); + } + + return name_to_ma; +} + +static void gp_strokes_copypastebuf_colors_name_to_material_free(GHash *name_to_ma) +{ + BLI_ghash_free(name_to_ma, MEM_freeN, NULL); +} + /* Free copy/paste buffer data */ void ED_gpencil_strokes_copybuf_free(void) { bGPDstroke *gps, *gpsn; - /* Free the palettes buffer - * NOTE: This is done before the strokes so that the name ptrs (keys) are still safe + /* Free the colors buffer + * NOTE: This is done before the strokes so that the ptrs are still safe */ if (gp_strokes_copypastebuf_colors) { BLI_ghash_free(gp_strokes_copypastebuf_colors, NULL, MEM_freeN); @@ -364,8 +831,15 @@ void ED_gpencil_strokes_copybuf_free(void) for (gps = gp_strokes_copypastebuf.first; gps; gps = gpsn) { gpsn = gps->next; - if (gps->points) MEM_freeN(gps->points); - if (gps->triangles) MEM_freeN(gps->triangles); + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } + + MEM_SAFE_FREE(gps->triangles); BLI_freelinkN(&gp_strokes_copypastebuf, gps); } @@ -376,40 +850,32 @@ void ED_gpencil_strokes_copybuf_free(void) /* Ensure that destination datablock has all the colours the pasted strokes need * Helper function for copy-pasting strokes */ -GHash *gp_copybuf_validate_colormap(bGPdata *gpd) +GHash *gp_copybuf_validate_colormap(bContext *C) { - GHash *new_colors = BLI_ghash_str_new("GPencil Paste Dst Colors"); + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + GHash *new_colors = BLI_ghash_int_new("GPencil Paste Dst Colors"); GHashIterator gh_iter; - /* If there's no active palette yet (i.e. new datablock), add one */ - bGPDpalette *palette = BKE_gpencil_palette_getactive(gpd); - if (palette == NULL) { - palette = BKE_gpencil_palette_addnew(gpd, "Pasted Palette", true); - } + /* For each color, check if exist and add if not */ + GHash *name_to_ma = gp_strokes_copypastebuf_colors_name_to_material_create(bmain); - /* For each color, figure out what to map to... */ GHASH_ITER(gh_iter, gp_strokes_copypastebuf_colors) { - bGPDpalettecolor *palcolor; - char *name = BLI_ghashIterator_getKey(&gh_iter); - - /* Look for existing color to map to */ - /* XXX: What to do if same name but different color? Behaviour here should depend on a property? */ - palcolor = BKE_gpencil_palettecolor_getbyname(palette, name); - if (palcolor == NULL) { - /* Doesn't Exist - Create new matching color for this palette */ - /* XXX: This still doesn't fix the pasting across file boundaries problem... */ - bGPDpalettecolor *src_color = BLI_ghashIterator_getValue(&gh_iter); - - palcolor = MEM_dupallocN(src_color); - BLI_addtail(&palette->colors, palcolor); + int *key = BLI_ghashIterator_getKey(&gh_iter); + char *ma_name = BLI_ghashIterator_getValue(&gh_iter); + Material *ma = BLI_ghash_lookup(name_to_ma, ma_name); - BLI_uniquename(&palette->colors, palcolor, DATA_("GP Color"), '.', offsetof(bGPDpalettecolor, info), sizeof(palcolor->info)); + if (ma != NULL && BKE_gpencil_get_material_index(ob, ma) == 0) { + BKE_object_material_slot_add(bmain, ob); + assign_material(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF); } /* Store this mapping (for use later when pasting) */ - BLI_ghash_insert(new_colors, name, palcolor); + BLI_ghash_insert(new_colors, key, ma); } + gp_strokes_copypastebuf_colors_name_to_material_free(name_to_ma); + return new_colors; } @@ -418,6 +884,8 @@ GHash *gp_copybuf_validate_colormap(bGPdata *gpd) static int gp_strokes_copy_exec(bContext *C, wmOperator *op) { + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); if (gpd == NULL) { @@ -425,6 +893,11 @@ static int gp_strokes_copy_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) { + BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition"); + return OPERATOR_CANCELLED; + } + /* clear the buffer first */ ED_gpencil_strokes_copybuf_free(); @@ -453,8 +926,12 @@ static int gp_strokes_copy_exec(bContext *C, wmOperator *op) /* make direct copies of the stroke and its points */ gpsd = MEM_dupallocN(gps); - BLI_strncpy(gpsd->tmp_layerinfo, gpl->info, sizeof(gpsd->tmp_layerinfo)); /* saves original layer name */ + BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); /* saves original layer name */ gpsd->points = MEM_dupallocN(gps->points); + if (gps->dvert != NULL) { + gpsd->dvert = MEM_dupallocN(gps->dvert); + BKE_gpencil_stroke_weights_duplicate(gps, gpsd); + } /* triangles cache - will be recalculated on next redraw */ gpsd->flag |= GP_STROKE_RECALC_CACHES; @@ -474,20 +951,21 @@ static int gp_strokes_copy_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - /* Build up hash of colors used in these strokes, making copies of these to protect against dangling pointers */ + /* Build up hash of material colors used in these strokes */ if (gp_strokes_copypastebuf.first) { - gp_strokes_copypastebuf_colors = BLI_ghash_str_new("GPencil CopyBuf Colors"); - + gp_strokes_copypastebuf_colors = BLI_ghash_int_new("GPencil CopyBuf Colors"); + GHash *ma_to_name = gp_strokes_copypastebuf_colors_material_to_name_create(bmain); for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { if (ED_gpencil_stroke_can_use(C, gps)) { - if (BLI_ghash_haskey(gp_strokes_copypastebuf_colors, gps->colorname) == false) { - bGPDpalettecolor *color = MEM_dupallocN(gps->palcolor); - - BLI_ghash_insert(gp_strokes_copypastebuf_colors, gps->colorname, color); - gps->palcolor = color; + char **ma_name_val; + if (!BLI_ghash_ensure_p(gp_strokes_copypastebuf_colors, &gps->mat_nr, (void ***)&ma_name_val)) { + Material *ma = give_current_material(ob, gps->mat_nr + 1); + char *ma_name = BLI_ghash_lookup(ma_to_name, ma); + *ma_name_val = MEM_dupallocN(ma_name); } } } + gp_strokes_copypastebuf_colors_material_to_name_free(ma_to_name); } /* updates (to ensure operator buttons are refreshed, when used via hotkeys) */ @@ -532,9 +1010,11 @@ typedef enum eGP_PasteMode { static int gp_strokes_paste_exec(bContext *C, wmOperator *op) { - Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); /* only use active for copy merge */ + Depsgraph *depsgraph = CTX_data_depsgraph(C); + int cfra_eval = (int)DEG_get_ctime(depsgraph); bGPDframe *gpf; eGP_PasteMode type = RNA_enum_get(op->ptr, "type"); @@ -545,6 +1025,10 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); return OPERATOR_CANCELLED; } + else if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) { + BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition"); + return OPERATOR_CANCELLED; + } else if (BLI_listbase_is_empty(&gp_strokes_copypastebuf)) { BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again"); return OPERATOR_CANCELLED; @@ -597,14 +1081,14 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) CTX_DATA_END; /* Ensure that all the necessary colors exist */ - new_colors = gp_copybuf_validate_colormap(gpd); + new_colors = gp_copybuf_validate_colormap(C); /* Copy over the strokes from the buffer (and adjust the colors) */ for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { if (ED_gpencil_stroke_can_use(C, gps)) { /* Need to verify if layer exists */ if (type != GP_COPY_MERGE) { - gpl = BLI_findstring(&gpd->layers, gps->tmp_layerinfo, offsetof(bGPDlayer, info)); + gpl = BLI_findstring(&gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info)); if (gpl == NULL) { /* no layer - use active (only if layer deleted before paste) */ gpl = CTX_data_active_gpencil_layer(C); @@ -616,28 +1100,33 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) * we are obliged to add a new frame if one * doesn't exist already */ - gpf = BKE_gpencil_layer_getframe(gpl, CFRA, true); + gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_ADD_NEW); if (gpf) { /* Create new stroke */ bGPDstroke *new_stroke = MEM_dupallocN(gps); - new_stroke->tmp_layerinfo[0] = '\0'; + new_stroke->runtime.tmp_layerinfo[0] = '\0'; new_stroke->points = MEM_dupallocN(gps->points); - + if (gps->dvert != NULL) { + new_stroke->dvert = MEM_dupallocN(gps->dvert); + BKE_gpencil_stroke_weights_duplicate(gps, new_stroke); + } new_stroke->flag |= GP_STROKE_RECALC_CACHES; new_stroke->triangles = NULL; new_stroke->next = new_stroke->prev = NULL; BLI_addtail(&gpf->strokes, new_stroke); - /* Fix color references */ - BLI_assert(new_stroke->colorname[0] != '\0'); - new_stroke->palcolor = BLI_ghash_lookup(new_colors, new_stroke->colorname); - - BLI_assert(new_stroke->palcolor != NULL); - BLI_strncpy(new_stroke->colorname, new_stroke->palcolor->info, sizeof(new_stroke->colorname)); + /* Remap material */ + Material *ma = BLI_ghash_lookup(new_colors, &new_stroke->mat_nr); + if ((ma) && (BKE_gpencil_get_material_index(ob, ma) > 0)) { + gps->mat_nr = BKE_gpencil_get_material_index(ob, ma) - 1; + CLAMP_MIN(gps->mat_nr, 0); + } + else { + gps->mat_nr = 0; /* only if the color is not found */ + } - /*new_stroke->flag |= GP_STROKE_RECALC_COLOR; */ } } } @@ -646,6 +1135,7 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op) BLI_ghash_free(new_colors, NULL, NULL); /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -669,7 +1159,7 @@ void GPENCIL_OT_paste(wmOperatorType *ot) ot->poll = gp_strokes_paste_poll; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA; /* properties */ ot->prop = RNA_def_enum(ot->srna, "type", copy_type, 0, "Type", ""); @@ -695,10 +1185,17 @@ static int gp_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *U static int gp_move_to_layer_exec(bContext *C, wmOperator *op) { bGPdata *gpd = CTX_data_gpencil_data(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); + int cfra_eval = (int)DEG_get_ctime(depsgraph); bGPDlayer *target_layer = NULL; ListBase strokes = {NULL, NULL}; int layer_num = RNA_enum_get(op->ptr, "layer"); + if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) { + BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition"); + return OPERATOR_CANCELLED; + } + /* Get layer or create new one */ if (layer_num == -1) { /* Create layer */ @@ -746,14 +1243,14 @@ static int gp_move_to_layer_exec(bContext *C, wmOperator *op) /* Paste them all in one go */ if (strokes.first) { - Scene *scene = CTX_data_scene(C); - bGPDframe *gpf = BKE_gpencil_layer_getframe(target_layer, CFRA, true); + bGPDframe *gpf = BKE_gpencil_layer_getframe(target_layer, cfra_eval, GP_GETFRAME_ADD_NEW); BLI_movelisttolist(&gpf->strokes, &strokes); BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL)); } /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -802,8 +1299,10 @@ static bool UNUSED_FUNCTION(gp_blank_frame_add_poll)(bContext *C) static int gp_blank_frame_add_exec(bContext *C, wmOperator *op) { - Scene *scene = CTX_data_scene(C); bGPdata *gpd = ED_gpencil_data_get_active(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); + int cfra_eval = (int)DEG_get_ctime(depsgraph); + bGPDlayer *active_gpl = BKE_gpencil_layer_getactive(gpd); const bool all_layers = RNA_boolean_get(op->ptr, "all_layers"); @@ -824,7 +1323,7 @@ static int gp_blank_frame_add_exec(bContext *C, wmOperator *op) } /* 1) Check for an existing frame on the current frame */ - bGPDframe *gpf = BKE_gpencil_layer_find_frame(gpl, CFRA); + bGPDframe *gpf = BKE_gpencil_layer_find_frame(gpl, cfra_eval); if (gpf) { /* Shunt all frames after (and including) the existing one later by 1-frame */ for (; gpf; gpf = gpf->next) { @@ -833,11 +1332,12 @@ static int gp_blank_frame_add_exec(bContext *C, wmOperator *op) } /* 2) Now add a new frame, with nothing in it */ - gpl->actframe = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_NEW); + gpl->actframe = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_ADD_NEW); } CTX_DATA_END; /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -849,7 +1349,7 @@ void GPENCIL_OT_blank_frame_add(wmOperatorType *ot) ot->name = "Insert Blank Frame"; ot->idname = "GPENCIL_OT_blank_frame_add"; ot->description = "Insert a blank frame on the current frame " - "(all subsequently existing frames, if any, are shifted right by one frame)"; + "(all subsequently existing frames, if any, are shifted right by one frame)"; /* callbacks */ ot->exec = gp_blank_frame_add_exec; @@ -874,10 +1374,13 @@ static bool gp_actframe_delete_poll(bContext *C) /* delete active frame - wrapper around API calls */ static int gp_actframe_delete_exec(bContext *C, wmOperator *op) { - Scene *scene = CTX_data_scene(C); bGPdata *gpd = ED_gpencil_data_get_active(C); bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, 0); + + Depsgraph *depsgraph = CTX_data_depsgraph(C); + int cfra_eval = (int)DEG_get_ctime(depsgraph); + + bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_USE_PREV); /* if there's no existing Grease-Pencil data there, add some */ if (gpd == NULL) { @@ -893,6 +1396,7 @@ static int gp_actframe_delete_exec(bContext *C, wmOperator *op) BKE_gpencil_layer_delframe(gpl, gpf); /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -926,13 +1430,16 @@ static bool gp_actframe_delete_all_poll(bContext *C) static int gp_actframe_delete_all_exec(bContext *C, wmOperator *op) { - Scene *scene = CTX_data_scene(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); + int cfra_eval = (int)DEG_get_ctime(depsgraph); + bool success = false; CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) { /* try to get the "active" frame - but only if it actually occurs on this frame */ - bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, 0); + bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, GP_GETFRAME_USE_PREV); if (gpf == NULL) continue; @@ -947,6 +1454,7 @@ static int gp_actframe_delete_all_exec(bContext *C, wmOperator *op) /* updates */ if (success) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; } @@ -981,43 +1489,69 @@ typedef enum eGP_DeleteMode { GP_DELETEOP_FRAME = 2, } eGP_DeleteMode; +typedef enum eGP_DissolveMode { + /* dissolve all selected points */ + GP_DISSOLVE_POINTS = 0, + /* dissolve between selected points */ + GP_DISSOLVE_BETWEEN = 1, + /* dissolve unselected points */ + GP_DISSOLVE_UNSELECT = 2, +} eGP_DissolveMode; + /* ----------------------------------- */ /* Delete selected strokes */ static int gp_delete_selected_strokes(bContext *C) { bool changed = false; + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps, *gpsn; + bGPDframe *init_gpf = gpl->actframe; + if (is_multiedit) { + init_gpf = gpl->frames.first; + } - if (gpf == NULL) - continue; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + bGPDstroke *gps, *gpsn; - /* simply delete strokes which are selected */ - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + if (gpf == NULL) + continue; - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) - continue; + /* simply delete strokes which are selected */ + for (gps = gpf->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; - /* free stroke if selected */ - if (gps->flag & GP_STROKE_SELECT) { - /* free stroke memory arrays, then stroke itself */ - if (gps->points) MEM_freeN(gps->points); - if (gps->triangles) MEM_freeN(gps->triangles); - BLI_freelinkN(&gpf->strokes, gps); + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) + continue; - changed = true; + /* free stroke if selected */ + if (gps->flag & GP_STROKE_SELECT) { + /* free stroke memory arrays, then stroke itself */ + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } + MEM_SAFE_FREE(gps->triangles); + BLI_freelinkN(&gpf->strokes, gps); + + changed = true; + } + } } } } CTX_DATA_END; if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; } @@ -1029,88 +1563,238 @@ static int gp_delete_selected_strokes(bContext *C) /* ----------------------------------- */ /* Delete selected points but keep the stroke */ -static int gp_dissolve_selected_points(bContext *C) +static int gp_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) { + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); bool changed = false; + int first = 0; + int last = 0; CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps, *gpsn; + bGPDframe *init_gpf = gpl->actframe; + if (is_multiedit) { + init_gpf = gpl->frames.first; + } - if (gpf == NULL) - continue; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { - /* simply delete points from selected strokes - * NOTE: we may still have to remove the stroke if it ends up having no points! - */ - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + bGPDstroke *gps, *gpsn; - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) - continue; - /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(gpl, gps) == false) - continue; + if (gpf == NULL) + continue; - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; + /* simply delete points from selected strokes + * NOTE: we may still have to remove the stroke if it ends up having no points! + */ + for (gps = gpf->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; + + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) + continue; + /* check if the color is editable */ + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) + continue; + + /* the stroke must have at least one point selected for any operator */ + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + MDeformVert *dvert = NULL; + int i; + + int tot = gps->totpoints; /* number of points in new buffer */ + + /* first pass: count points to remove */ + switch (mode) { + case GP_DISSOLVE_POINTS: + /* Count how many points are selected (i.e. how many to remove) */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + /* selected point - one of the points to remove */ + tot--; + } + } + break; + case GP_DISSOLVE_BETWEEN: + /* need to find first and last point selected */ + first = -1; + last = 0; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + if (first < 0) { + first = i; + } + last = i; + } + } + /* count unselected points in the range */ + for (i = first, pt = gps->points + first; i < last; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) == 0) { + tot--; + } + } + break; + case GP_DISSOLVE_UNSELECT: + /* count number of unselected points */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) == 0) { + tot--; + } + } + break; + default: + return false; + break; + } - int tot = gps->totpoints; /* number of points in new buffer */ + /* if no points are left, we simply delete the entire stroke */ + if (tot <= 0) { + /* remove the entire stroke */ + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } + if (gps->triangles) { + MEM_freeN(gps->triangles); + } + BLI_freelinkN(&gpf->strokes, gps); + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + } + else { + /* just copy all points to keep into a smaller buffer */ + bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); + bGPDspoint *npt = new_points; - /* First Pass: Count how many points are selected (i.e. how many to remove) */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - /* selected point - one of the points to remove */ - tot--; - } - } + MDeformVert *new_dvert = NULL; + MDeformVert *ndvert = NULL; - /* if no points are left, we simply delete the entire stroke */ - if (tot <= 0) { - /* remove the entire stroke */ - MEM_freeN(gps->points); - if (gps->triangles) { - MEM_freeN(gps->triangles); - } - BLI_freelinkN(&gpf->strokes, gps); - } - else { - /* just copy all unselected into a smaller buffer */ - bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); - bGPDspoint *npt = new_points; + if (gps->dvert != NULL) { + new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy"); + ndvert = new_dvert; + } - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if ((pt->flag & GP_SPOINT_SELECT) == 0) { - *npt = *pt; - npt++; - } - } + switch (mode) { + case GP_DISSOLVE_POINTS: + (gps->dvert != NULL) ? dvert = gps->dvert : NULL; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) == 0) { + *npt = *pt; + npt++; + + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + dvert++; + } + } + } + break; + case GP_DISSOLVE_BETWEEN: + /* copy first segment */ + (gps->dvert != NULL) ? dvert = gps->dvert : NULL; + for (i = 0, pt = gps->points; i < first; i++, pt++) { + *npt = *pt; + npt++; + + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + dvert++; + } + } + /* copy segment (selected points) */ + (gps->dvert != NULL) ? dvert = gps->dvert + first : NULL; + for (i = first, pt = gps->points + first; i < last; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + *npt = *pt; + npt++; + + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + dvert++; + } + } + } + /* copy last segment */ + (gps->dvert != NULL) ? dvert = gps->dvert + last : NULL; + for (i = last, pt = gps->points + last; i < gps->totpoints; i++, pt++) { + *npt = *pt; + npt++; + + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + dvert++; + } + } + + break; + case GP_DISSOLVE_UNSELECT: + /* copy any selected point */ + (gps->dvert != NULL) ? dvert = gps->dvert : NULL; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + *npt = *pt; + npt++; + + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + dvert++; + } + } + } + break; + } - /* free the old buffer */ - MEM_freeN(gps->points); + /* free the old buffer */ + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } - /* save the new buffer */ - gps->points = new_points; - gps->totpoints = tot; + /* save the new buffer */ + gps->points = new_points; + gps->dvert = new_dvert; + gps->totpoints = tot; - /* triangles cache needs to be recalculated */ - gps->flag |= GP_STROKE_RECALC_CACHES; - gps->tot_triangles = 0; + /* triangles cache needs to be recalculated */ + gps->flag |= GP_STROKE_RECALC_CACHES; + gps->tot_triangles = 0; - /* deselect the stroke, since none of its selected points will still be selected */ - gps->flag &= ~GP_STROKE_SELECT; - } + /* deselect the stroke, since none of its selected points will still be selected */ + gps->flag &= ~GP_STROKE_SELECT; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } + } - changed = true; + changed = true; + } + } } } } CTX_DATA_END; if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; } @@ -1144,17 +1828,17 @@ typedef struct tGPDeleteIsland { * becomes much less * 2) Each island gets converted to a new stroke */ -void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags) +void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, + int tag_flags, bool select) { tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands"); bool in_island = false; int num_islands = 0; - bGPDspoint *pt; - int i; /* First Pass: Identify start/end of islands */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + bGPDspoint *pt = gps->points; + for (int i = 0; i < gps->totpoints; i++, pt++) { if (pt->flag & tag_flags) { /* selected - stop accumulating to island */ in_island = false; @@ -1187,7 +1871,7 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke /* Create each new stroke... */ for (idx = 0; idx < num_islands; idx++) { tGPDeleteIsland *island = &islands[idx]; - bGPDstroke *new_stroke = MEM_dupallocN(gps); + bGPDstroke *new_stroke = MEM_dupallocN(gps); /* initialize triangle memory - to be calculated on next redraw */ new_stroke->triangles = NULL; @@ -1196,11 +1880,26 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */ new_stroke->totpoints = island->end_idx - island->start_idx + 1; - new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment"); - /* Copy over the relevant points */ + /* Copy over the relevant point data */ + new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment"); memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints); + /* Copy over vertex weight data (if available) */ + if (new_stroke->dvert != NULL) { + /* Copy over the relevant vertex-weight points */ + new_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints, "gp delete stroke fragment weight"); + memcpy(new_stroke->dvert, gps->dvert + island->start_idx, sizeof(MDeformVert) * new_stroke->totpoints); + + /* Copy weights */ + int e = island->start_idx; + for (int i = 0; i < new_stroke->totpoints; i++) { + MDeformVert *dvert_dst = &gps->dvert[e]; + MDeformVert *dvert_src = &new_stroke->dvert[i]; + dvert_dst->dw = MEM_dupallocN(dvert_src->dw); + e++; + } + } /* Each island corresponds to a new stroke. We must adjust the * timings of these new strokes: @@ -1220,6 +1919,11 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke pts = new_stroke->points; for (j = 0; j < new_stroke->totpoints; j++, pts++) { pts->time -= delta; + /* set flag for select again later */ + if (select == true) { + pts->flag &= ~GP_SPOINT_SELECT; + pts->flag |= GP_SPOINT_TAG; + } } } @@ -1237,53 +1941,70 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke MEM_freeN(islands); /* Delete the old stroke */ - MEM_freeN(gps->points); + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } if (gps->triangles) { MEM_freeN(gps->triangles); } BLI_freelinkN(&gpf->strokes, gps); } - /* Split selected strokes into segments, splitting on selected points */ static int gp_delete_selected_points(bContext *C) { + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); bool changed = false; CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps, *gpsn; + bGPDframe *init_gpf = gpl->actframe; + if (is_multiedit) { + init_gpf = gpl->frames.first; + } - if (gpf == NULL) - continue; + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + bGPDstroke *gps, *gpsn; - /* simply delete strokes which are selected */ - for (gps = gpf->strokes.first; gps; gps = gpsn) { - gpsn = gps->next; + if (gpf == NULL) + continue; - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) - continue; - /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(gpl, gps) == false) - continue; + /* simply delete strokes which are selected */ + for (gps = gpf->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) + continue; + /* check if the color is editable */ + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) + continue; - if (gps->flag & GP_STROKE_SELECT) { - /* deselect old stroke, since it will be used as template for the new strokes */ - gps->flag &= ~GP_STROKE_SELECT; - /* delete unwanted points by splitting stroke into several smaller ones */ - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT); + if (gps->flag & GP_STROKE_SELECT) { + /* deselect old stroke, since it will be used as template for the new strokes */ + gps->flag &= ~GP_STROKE_SELECT; + + /* delete unwanted points by splitting stroke into several smaller ones */ + gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false); - changed = true; + changed = true; + } + } } } } CTX_DATA_END; if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; } @@ -1292,6 +2013,12 @@ static int gp_delete_selected_points(bContext *C) } } +/* simple wrapper to external call */ +int gp_delete_selected_point_wrap(bContext *C) +{ + return gp_delete_selected_points(C); +} + /* ----------------------------------- */ static int gp_delete_exec(bContext *C, wmOperator *op) @@ -1342,24 +2069,37 @@ void GPENCIL_OT_delete(wmOperatorType *ot) ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_delete_types, 0, "Type", "Method used for deleting Grease Pencil data"); } -static int gp_dissolve_exec(bContext *C, wmOperator *UNUSED(op)) +static int gp_dissolve_exec(bContext *C, wmOperator *op) { - return gp_dissolve_selected_points(C); + eGP_DissolveMode mode = RNA_enum_get(op->ptr, "type"); + + return gp_dissolve_selected_points(C, mode); } void GPENCIL_OT_dissolve(wmOperatorType *ot) { + static EnumPropertyItem prop_gpencil_dissolve_types[] = { + {GP_DISSOLVE_POINTS, "POINTS", 0, "Dissolve", "Dissolve selected points"}, + {GP_DISSOLVE_BETWEEN, "BETWEEN", 0, "Dissolve Between", "Dissolve points between selected points"}, + {GP_DISSOLVE_UNSELECT, "UNSELECT", 0, "Dissolve Unselect", "Dissolve all unselected points"}, + {0, NULL, 0, NULL, NULL} + }; + /* identifiers */ ot->name = "Dissolve"; ot->idname = "GPENCIL_OT_dissolve"; ot->description = "Delete selected points without splitting strokes"; /* callbacks */ + ot->invoke = WM_menu_invoke; ot->exec = gp_dissolve_exec; ot->poll = gp_stroke_edit_poll; /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; + + /* props */ + ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_dissolve_types, 0, "Type", "Method used for disolving Stroke points"); } /* ****************** Snapping - Strokes <-> Cursor ************************ */ @@ -1381,8 +2121,11 @@ static bool gp_snap_poll(bContext *C) static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); - RegionView3D *rv3d = CTX_wm_region_data(C); - const float gridf = rv3d->gridview; + View3D *v3d = CTX_wm_view3d(C); + Scene *scene = CTX_data_scene(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); + Object *obact = CTX_data_active_object(C); + const float gridf = ED_view3d_grid_scale(scene, v3d, NULL); for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { /* only editable and visible layers are considered */ @@ -1390,10 +2133,8 @@ static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) bGPDframe *gpf = gpl->actframe; float diff_mat[4][4]; - /* calculate difference matrix if parent object */ - if (gpl->parent != NULL) { - ED_gpencil_parent_location(gpl, diff_mat); - } + /* calculate difference matrix object */ + ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { bGPDspoint *pt; @@ -1403,37 +2144,32 @@ static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) if (ED_gpencil_stroke_can_use(C, gps) == false) continue; /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(gpl, gps) == false) + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) continue; // TODO: if entire stroke is selected, offset entire stroke by same amount? for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { /* only if point is selected */ if (pt->flag & GP_SPOINT_SELECT) { - if (gpl->parent == NULL) { - pt->x = gridf * floorf(0.5f + pt->x / gridf); - pt->y = gridf * floorf(0.5f + pt->y / gridf); - pt->z = gridf * floorf(0.5f + pt->z / gridf); - } - else { - /* apply parent transformations */ - float fpt[3]; - mul_v3_m4v3(fpt, diff_mat, &pt->x); + /* apply parent transformations */ + float fpt[3]; + mul_v3_m4v3(fpt, diff_mat, &pt->x); - fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); - fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); - fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); + fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); + fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); + fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); - /* return data */ - copy_v3_v3(&pt->x, fpt); - gp_apply_parent_point(gpl, pt); - } + /* return data */ + copy_v3_v3(&pt->x, fpt); + gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); } } } } } + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + 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; } @@ -1460,10 +2196,11 @@ static int gp_snap_to_cursor(bContext *C, wmOperator *op) bGPdata *gpd = ED_gpencil_data_get_active(C); Scene *scene = CTX_data_scene(C); - View3D *v3d = CTX_wm_view3d(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); + Object *obact = CTX_data_active_object(C); const bool use_offset = RNA_boolean_get(op->ptr, "use_offset"); - const float *cursor_global = ED_view3d_cursor3d_get(scene, v3d); + const float *cursor_global = scene->cursor.location; for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { /* only editable and visible layers are considered */ @@ -1471,10 +2208,8 @@ static int gp_snap_to_cursor(bContext *C, wmOperator *op) bGPDframe *gpf = gpl->actframe; float diff_mat[4][4]; - /* calculate difference matrix if parent object */ - if (gpl->parent != NULL) { - ED_gpencil_parent_location(gpl, diff_mat); - } + /* calculate difference matrix */ + ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { bGPDspoint *pt; @@ -1484,7 +2219,7 @@ static int gp_snap_to_cursor(bContext *C, wmOperator *op) if (ED_gpencil_stroke_can_use(C, gps) == false) continue; /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(gpl, gps) == false) + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) continue; /* only continue if this stroke is selected (editable doesn't guarantee this)... */ if ((gps->flag & GP_STROKE_SELECT) == 0) @@ -1507,9 +2242,7 @@ static int gp_snap_to_cursor(bContext *C, wmOperator *op) for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (pt->flag & GP_SPOINT_SELECT) { copy_v3_v3(&pt->x, cursor_global); - if (gpl->parent != NULL) { - gp_apply_parent_point(gpl, pt); - } + gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); } } } @@ -1518,6 +2251,8 @@ static int gp_snap_to_cursor(bContext *C, wmOperator *op) } } + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + 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; } @@ -1538,7 +2273,7 @@ void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot) /* props */ ot->prop = RNA_def_boolean(ot->srna, "use_offset", true, "With Offset", - "Offset the entire stroke instead of selected points only"); + "Offset the entire stroke instead of selected points only"); } /* ------------------------------- */ @@ -1549,8 +2284,10 @@ static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); + Object *obact = CTX_data_active_object(C); - float *cursor = ED_view3d_cursor3d_get(scene, v3d); + float *cursor = scene->cursor.location; float centroid[3] = {0.0f}; float min[3], max[3]; size_t count = 0; @@ -1564,10 +2301,8 @@ static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) bGPDframe *gpf = gpl->actframe; float diff_mat[4][4]; - /* calculate difference matrix if parent object */ - if (gpl->parent != NULL) { - ED_gpencil_parent_location(gpl, diff_mat); - } + /* calculate difference matrix */ + ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { bGPDspoint *pt; @@ -1577,7 +2312,7 @@ static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) if (ED_gpencil_stroke_can_use(C, gps) == false) continue; /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(gpl, gps) == false) + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) continue; /* only continue if this stroke is selected (editable doesn't guarantee this)... */ if ((gps->flag & GP_STROKE_SELECT) == 0) @@ -1585,18 +2320,13 @@ static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (pt->flag & GP_SPOINT_SELECT) { - if (gpl->parent == NULL) { - add_v3_v3(centroid, &pt->x); - minmax_v3v3_v3(min, max, &pt->x); - } - else { - /* apply parent transformations */ - float fpt[3]; - mul_v3_m4v3(fpt, diff_mat, &pt->x); + /* apply parent transformations */ + float fpt[3]; + mul_v3_m4v3(fpt, diff_mat, &pt->x); + + add_v3_v3(centroid, fpt); + minmax_v3v3_v3(min, max, fpt); - add_v3_v3(centroid, fpt); - minmax_v3v3_v3(min, max, fpt); - } count++; } } @@ -1605,7 +2335,7 @@ static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) } } - if (v3d->around == V3D_AROUND_CENTER_MEDIAN && count) { + if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_MEDIAN && count) { mul_v3_fl(centroid, 1.0f / (float)count); copy_v3_v3(cursor, centroid); } @@ -1614,7 +2344,9 @@ static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) } - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); + return OPERATOR_FINISHED; } @@ -1648,13 +2380,20 @@ static int gp_stroke_apply_thickness_exec(bContext *C, wmOperator *UNUSED(op)) for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { /* Apply thickness */ - gps->thickness = gps->thickness + gpl->thickness; + if ((gps->thickness == 0) && (gpl->line_change == 0)) { + gps->thickness = gpl->thickness; + } + else { + gps->thickness = gps->thickness + gpl->line_change; + } } } /* clear value */ gpl->thickness = 0.0f; + gpl->line_change = 0; /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -1683,6 +2422,8 @@ enum { static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + Object *ob = CTX_data_active_object(C); + const int type = RNA_enum_get(op->ptr, "type"); /* sanity checks */ @@ -1696,13 +2437,13 @@ static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) continue; for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { - bGPDpalettecolor *palcolor = gps->palcolor; + MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1); /* skip strokes that are not selected or invalid for current view */ if (((gps->flag & GP_STROKE_SELECT) == 0) || ED_gpencil_stroke_can_use(C, gps) == false) continue; /* skip hidden or locked colors */ - if (!palcolor || (palcolor->flag & PC_COLOR_HIDE) || (palcolor->flag & PC_COLOR_LOCKED)) + if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || (gp_style->flag & GP_STYLE_COLOR_LOCKED)) continue; switch (type) { @@ -1727,6 +2468,7 @@ static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) CTX_DATA_END; /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -1807,15 +2549,18 @@ static void gpencil_flip_stroke(bGPDstroke *gps) } /* Helper: copy point between strokes */ -static void gpencil_stroke_copy_point(bGPDstroke *gps, bGPDspoint *point, float delta[3], - float pressure, float strength, float deltatime) +static void gpencil_stroke_copy_point(bGPDstroke *gps, bGPDspoint *point, int idx, float delta[3], + float pressure, float strength, float deltatime) { bGPDspoint *newpoint; gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); + if (gps->dvert != NULL) { + gps->dvert = MEM_reallocN(gps->dvert, sizeof(MDeformVert) * (gps->totpoints + 1)); + } gps->totpoints++; - newpoint = &gps->points[gps->totpoints - 1]; + newpoint->x = point->x * delta[0]; newpoint->y = point->y * delta[1]; newpoint->z = point->z * delta[2]; @@ -1823,6 +2568,14 @@ static void gpencil_stroke_copy_point(bGPDstroke *gps, bGPDspoint *point, float newpoint->pressure = pressure; newpoint->strength = strength; newpoint->time = point->time + deltatime; + + if (gps->dvert != NULL) { + MDeformVert *dvert = &gps->dvert[idx]; + MDeformVert *newdvert = &gps->dvert[gps->totpoints - 1]; + + newdvert->totweight = dvert->totweight; + newdvert->dw = MEM_dupallocN(dvert->dw); + } } /* Helper: join two strokes using the shortest distance (reorder stroke if necessary ) */ @@ -1868,18 +2621,18 @@ static void gpencil_stroke_join_strokes(bGPDstroke *gps_a, bGPDstroke *gps_b, co /* 1st: add one tail point to start invisible area */ point = gps_a->points[gps_a->totpoints - 1]; deltatime = point.time; - gpencil_stroke_copy_point(gps_a, &point, delta, 0.0f, 0.0f, 0.0f); + gpencil_stroke_copy_point(gps_a, &point, gps_a->totpoints - 1, delta, 0.0f, 0.0f, 0.0f); /* 2nd: add one head point to finish invisible area */ point = gps_b->points[0]; - gpencil_stroke_copy_point(gps_a, &point, delta, 0.0f, 0.0f, deltatime); + gpencil_stroke_copy_point(gps_a, &point, 0, delta, 0.0f, 0.0f, deltatime); } /* 3rd: add all points */ for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) { /* check if still room in buffer */ if (gps_a->totpoints <= GP_STROKE_BUFFER_MAX - 2) { - gpencil_stroke_copy_point(gps_a, pt, delta, pt->pressure, pt->strength, deltatime); + gpencil_stroke_copy_point(gps_a, pt, i, delta, pt->pressure, pt->strength, deltatime); } } } @@ -1889,8 +2642,7 @@ static int gp_stroke_join_exec(bContext *C, wmOperator *op) bGPdata *gpd = ED_gpencil_data_get_active(C); bGPDlayer *activegpl = BKE_gpencil_layer_getactive(gpd); bGPDstroke *gps, *gpsn; - bGPDpalette *palette = BKE_gpencil_palette_getactive(gpd); - bGPDpalettecolor *palcolor = BKE_gpencil_palettecolor_getactive(palette); + Object *ob = CTX_data_active_object(C); bGPDframe *gpf_a = NULL; bGPDstroke *stroke_a = NULL; @@ -1926,7 +2678,7 @@ static int gp_stroke_join_exec(bContext *C, wmOperator *op) continue; } /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(gpl, gps) == false) { + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { continue; } @@ -1946,15 +2698,17 @@ static int gp_stroke_join_exec(bContext *C, wmOperator *op) if (new_stroke == NULL) { new_stroke = MEM_dupallocN(stroke_a); new_stroke->points = MEM_dupallocN(stroke_a->points); + if (stroke_a->dvert != NULL) { + new_stroke->dvert = MEM_dupallocN(stroke_a->dvert); + BKE_gpencil_stroke_weights_duplicate(stroke_a, new_stroke); + } new_stroke->triangles = NULL; new_stroke->tot_triangles = 0; new_stroke->flag |= GP_STROKE_RECALC_CACHES; /* if new, set current color */ if (type == GP_STROKE_JOINCOPY) { - new_stroke->palcolor = palcolor; - BLI_strncpy(new_stroke->colorname, palcolor->info, sizeof(new_stroke->colorname)); - new_stroke->flag |= GP_STROKE_RECALC_COLOR; + new_stroke->mat_nr = stroke_a->mat_nr; } } @@ -1993,6 +2747,7 @@ static int gp_stroke_join_exec(bContext *C, wmOperator *op) } /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -2028,6 +2783,7 @@ void GPENCIL_OT_stroke_join(wmOperatorType *ot) static int gp_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); + Object *ob = CTX_data_active_object(C); /* sanity checks */ if (ELEM(NULL, gpd)) @@ -2047,7 +2803,7 @@ static int gp_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) continue; } /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(gpl, gps) == false) { + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { continue; } @@ -2059,6 +2815,7 @@ static int gp_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) CTX_DATA_END; /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -2082,27 +2839,30 @@ void GPENCIL_OT_stroke_flip(wmOperatorType *ot) /* ***************** Reproject Strokes ********************** */ typedef enum eGP_ReprojectModes { + /* Axis */ + GP_REPROJECT_FRONT = 0, + GP_REPROJECT_SIDE, + GP_REPROJECT_TOP, /* On same plane, parallel to viewplane */ - GP_REPROJECT_PLANAR = 0, + GP_REPROJECT_VIEW, /* Reprojected on to the scene geometry */ GP_REPROJECT_SURFACE, } eGP_ReprojectModes; -static bool gp_strokes_reproject_poll(bContext *C) -{ - /* 2 Requirements: - * - 1) Editable GP data - * - 2) 3D View only (2D editors don't have projection issues) - */ - return (gp_stroke_edit_poll(C) && ED_operator_view3d_active(C)); -} - static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) { - Main *bmain = CTX_data_main(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); Scene *scene = CTX_data_scene(C); + ToolSettings *ts = CTX_data_tool_settings(C); + Depsgraph *depsgraph = CTX_data_depsgraph(C); + Object *ob = CTX_data_active_object(C); + ARegion *ar = CTX_wm_region(C); + RegionView3D *rv3d = ar->regiondata; + GP_SpaceConversion gsc = {NULL}; - eGP_ReprojectModes mode = RNA_boolean_get(op->ptr, "type"); + eGP_ReprojectModes mode = RNA_enum_get(op->ptr, "type"); + + float origin[3]; /* init space conversion stuff */ gp_point_conversion_init(C, &gsc); @@ -2110,13 +2870,13 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) /* init autodist for geometry projection */ if (mode == GP_REPROJECT_SURFACE) { view3d_region_operator_needs_opengl(CTX_wm_window(C), gsc.ar); - ED_view3d_autodist_init(bmain, scene, gsc.ar, CTX_wm_view3d(C), 0); + ED_view3d_autodist_init(depsgraph, gsc.ar, CTX_wm_view3d(C), 0); } // TODO: For deforming geometry workflow, create new frames? /* Go through each editable + selected stroke, adjusting each of its points one by one... */ - GP_EDITABLE_STROKES_BEGIN(C, gpl, gps) + GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { bGPDspoint *pt; @@ -2125,9 +2885,7 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) /* Compute inverse matrix for unapplying parenting once instead of doing per-point */ /* TODO: add this bit to the iteration macro? */ - if (gpl->parent) { - invert_m4_m4(inverse_diff_mat, diff_mat); - } + invert_m4_m4(inverse_diff_mat, gpstroke_iter.diff_mat); /* Adjust each point */ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { @@ -2138,19 +2896,51 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) * coordinates, resulting in lost precision, which in turn causes stairstepping * artifacts in the final points. */ - if (gpl->parent == NULL) { - gp_point_to_xy_fl(&gsc, gps, pt, &xy[0], &xy[1]); - } - else { - bGPDspoint pt2; - gp_point_to_parent_space(pt, diff_mat, &pt2); - gp_point_to_xy_fl(&gsc, gps, &pt2, &xy[0], &xy[1]); - } + bGPDspoint pt2; + gp_point_to_parent_space(pt, gpstroke_iter.diff_mat, &pt2); + gp_point_to_xy_fl(&gsc, gps, &pt2, &xy[0], &xy[1]); + + /* Project stroke in one axis */ + if (ELEM(mode, GP_REPROJECT_FRONT, GP_REPROJECT_SIDE, GP_REPROJECT_TOP)) { + ED_gp_get_drawing_reference(scene, ob, gpl, + ts->gpencil_v3d_align, origin); + + int axis = 0; + switch (mode) { + case GP_REPROJECT_FRONT: + { + axis = 1; + break; + } + case GP_REPROJECT_SIDE: + { + axis = 0; + break; + } + case GP_REPROJECT_TOP: + { + axis = 2; + break; + } + default: + { + axis = 1; + break; + } + } + + ED_gp_project_point_to_plane(ob, rv3d, origin, + axis, &pt2); + copy_v3_v3(&pt->x, &pt2.x); + + /* apply parent again */ + gp_apply_parent_point(depsgraph, ob, gpd, gpl, pt); + } /* Project screenspace back to 3D space (from current perspective) * so that all points have been treated the same way */ - if (mode == GP_REPROJECT_PLANAR) { + else if (mode == GP_REPROJECT_VIEW) { /* Planar - All on same plane parallel to the viewplane */ gp_point_xy_to_3d(&gsc, scene, xy, &pt->x); } @@ -2173,14 +2963,15 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) } /* Unapply parent corrections */ - if (gpl->parent) { + if (!ELEM(mode, GP_REPROJECT_FRONT, GP_REPROJECT_SIDE, GP_REPROJECT_TOP)) { mul_m4_v3(inverse_diff_mat, &pt->x); } } } } - GP_EDITABLE_STROKES_END; + GP_EDITABLE_STROKES_END(gpstroke_iter); + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; } @@ -2188,7 +2979,13 @@ static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) void GPENCIL_OT_reproject(wmOperatorType *ot) { static const EnumPropertyItem reproject_type[] = { - {GP_REPROJECT_PLANAR, "PLANAR", 0, "Planar", + {GP_REPROJECT_FRONT, "FRONT", 0, "Front", + "Reproject the strokes using the X-Z plane"}, + {GP_REPROJECT_SIDE, "SIDE", 0, "Side", + "Reproject the strokes using the Y-Z plane"}, + {GP_REPROJECT_TOP, "TOP", 0, "Top", + "Reproject the strokes using the X-Y plane"}, + {GP_REPROJECT_VIEW, "VIEW", 0, "View", "Reproject the strokes to end up on the same plane, as if drawn from the current viewpoint " "using 'Cursor' Stroke Placement"}, {GP_REPROJECT_SURFACE, "SURFACE", 0, "Surface", @@ -2200,22 +2997,69 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) ot->name = "Reproject Strokes"; ot->idname = "GPENCIL_OT_reproject"; ot->description = "Reproject the selected strokes from the current viewpoint as if they had been newly drawn " - "(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, " - "or for matching deforming geometry)"; + "(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, " + "or for matching deforming geometry)"; /* callbacks */ ot->invoke = WM_menu_invoke; ot->exec = gp_strokes_reproject_exec; - ot->poll = gp_strokes_reproject_poll; + ot->poll = gp_strokes_edit3d_poll; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA; /* properties */ - ot->prop = RNA_def_enum(ot->srna, "type", reproject_type, GP_REPROJECT_PLANAR, "Projection Type", ""); + ot->prop = RNA_def_enum(ot->srna, "type", reproject_type, GP_REPROJECT_VIEW, "Projection Type", ""); } /* ******************* Stroke subdivide ************************** */ +/* helper to smooth */ +static void gp_smooth_stroke(bContext *C, wmOperator *op) +{ + const int repeat = RNA_int_get(op->ptr, "repeat"); + float factor = RNA_float_get(op->ptr, "factor"); + const bool only_selected = RNA_boolean_get(op->ptr, "only_selected"); + const bool smooth_position = RNA_boolean_get(op->ptr, "smooth_position"); + const bool smooth_thickness = RNA_boolean_get(op->ptr, "smooth_thickness"); + const bool smooth_strength = RNA_boolean_get(op->ptr, "smooth_strength"); + const bool smooth_uv = RNA_boolean_get(op->ptr, "smooth_uv"); + + if (factor == 0.0f) { + return; + } + + GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) + { + if (gps->flag & GP_STROKE_SELECT) { + for (int r = 0; r < repeat; r++) { + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + if ((only_selected) && ((pt->flag & GP_SPOINT_SELECT) == 0)) { + continue; + } + + /* perform smoothing */ + if (smooth_position) { + BKE_gpencil_smooth_stroke(gps, i, factor); + } + if (smooth_strength) { + BKE_gpencil_smooth_stroke_strength(gps, i, factor); + } + if (smooth_thickness) { + /* thickness need to repeat process several times */ + for (int r2 = 0; r2 < r * 10; r2++) { + BKE_gpencil_smooth_stroke_thickness(gps, i, factor); + } + } + if (smooth_uv) { + BKE_gpencil_smooth_stroke_uv(gps, i, factor); + } + } + } + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); +} /* helper: Count how many points need to be inserted */ static int gp_count_subdivision_cuts(bGPDstroke *gps) @@ -2227,7 +3071,7 @@ static int gp_count_subdivision_cuts(bGPDstroke *gps) if (pt->flag & GP_SPOINT_SELECT) { if (i + 1 < gps->totpoints) { if (gps->points[i + 1].flag & GP_SPOINT_SELECT) { - ++totnewpoints; + totnewpoints++; } } } @@ -2235,6 +3079,7 @@ static int gp_count_subdivision_cuts(bGPDstroke *gps) return totnewpoints; } + static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); @@ -2249,7 +3094,7 @@ static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; /* Go through each editable + selected stroke */ - GP_EDITABLE_STROKES_BEGIN(C, gpl, gps) + GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { /* loop as many times as cuts */ @@ -2262,9 +3107,20 @@ static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) temp_points = MEM_dupallocN(gps->points); oldtotpoints = gps->totpoints; - /* resize the points arrys */ + MDeformVert *temp_dverts = NULL; + MDeformVert *dvert_final = NULL; + MDeformVert *dvert = NULL; + MDeformVert *dvert_next = NULL; + if (gps->dvert != NULL) { + temp_dverts = MEM_dupallocN(gps->dvert); + } + + /* 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); + } gps->flag |= GP_STROKE_RECALC_CACHES; /* loop and interpolate */ @@ -2279,13 +3135,23 @@ static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) pt_final->strength = pt->strength; pt_final->time = pt->time; pt_final->flag = pt->flag; - ++i2; + + if (gps->dvert != NULL) { + dvert = &temp_dverts[i]; + dvert_final = &gps->dvert[i2]; + dvert_final->totweight = dvert->totweight; + dvert_final->dw = dvert->dw; + } + i2++; /* if next point is selected add a half way point */ if (pt->flag & GP_SPOINT_SELECT) { if (i + 1 < oldtotpoints) { if (temp_points[i + 1].flag & GP_SPOINT_SELECT) { pt_final = &gps->points[i2]; + if (gps->dvert != NULL) { + dvert_final = &gps->dvert[i2]; + } /* Interpolate all values */ bGPDspoint *next = &temp_points[i + 1]; interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); @@ -2294,19 +3160,49 @@ static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); pt_final->time = interpf(pt->time, next->time, 0.5f); pt_final->flag |= GP_SPOINT_SELECT; - ++i2; + + /* interpolate weights */ + if (gps->dvert != NULL) { + dvert = &temp_dverts[i]; + dvert_next = &temp_dverts[i + 1]; + dvert_final = &gps->dvert[i2]; + + dvert_final->totweight = dvert->totweight; + dvert_final->dw = MEM_dupallocN(dvert->dw); + + /* interpolate weight values */ + for (int d = 0; d < dvert->totweight; d++) { + MDeformWeight *dw_a = &dvert->dw[d]; + if (dvert_next->totweight > d) { + MDeformWeight *dw_b = &dvert_next->dw[d]; + MDeformWeight *dw_final = &dvert_final->dw[d]; + dw_final->weight = interpf(dw_a->weight, dw_b->weight, 0.5f); + } + } + } + + i2++; } } } } /* free temp memory */ - MEM_freeN(temp_points); + MEM_SAFE_FREE(temp_points); + MEM_SAFE_FREE(temp_dverts); } + + /* triangles cache needs to be recalculated */ + gps->flag |= GP_STROKE_RECALC_CACHES; + gps->tot_triangles = 0; } } - GP_EDITABLE_STROKES_END; + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* smooth stroke */ + gp_smooth_stroke(C, op); /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -2326,11 +3222,479 @@ void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) ot->poll = gp_active_layer_poll; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 10, "Number of Cuts", "", 1, 5); /* avoid re-using last var because it can cause _very_ high value and annoy users */ RNA_def_property_flag(prop, PROP_SKIP_SAVE); + /* Smooth parameters */ + RNA_def_float(ot->srna, "factor", 0.0f, 0.0f, 2.0f, "Smooth", "", 0.0f, 2.0f); + prop = RNA_def_int(ot->srna, "repeat", 1, 1, 10, "Repeat", "", 1, 5); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + RNA_def_boolean(ot->srna, "only_selected", true, "Selected Points", + "Smooth only selected points in the stroke"); + RNA_def_boolean(ot->srna, "smooth_position", true, "Position", ""); + RNA_def_boolean(ot->srna, "smooth_thickness", true, "Thickness", ""); + RNA_def_boolean(ot->srna, "smooth_strength", false, "Strength", ""); + RNA_def_boolean(ot->srna, "smooth_uv", false, "UV", ""); + +} + +/* ** simplify stroke *** */ +static int gp_stroke_simplify_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + float factor = RNA_float_get(op->ptr, "factor"); + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) + { + if (gps->flag & GP_STROKE_SELECT) { + /* simplify stroke using Ramer-Douglas-Peucker algorithm */ + BKE_gpencil_simplify_stroke(gps, factor); + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_simplify(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Simplify Stroke"; + ot->idname = "GPENCIL_OT_stroke_simplify"; + ot->description = "Simplify selected stroked reducing number of points"; + + /* api callbacks */ + ot->exec = gp_stroke_simplify_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_float(ot->srna, "factor", 0.0f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f); + /* avoid re-using last var */ + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/* ** simplify stroke using fixed algorithm *** */ +static int gp_stroke_simplify_fixed_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + int steps = RNA_int_get(op->ptr, "step"); + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) + { + if (gps->flag & GP_STROKE_SELECT) { + for (int i = 0; i < steps; i++) { + BKE_gpencil_simplify_fixed(gps); + } + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_simplify_fixed(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Simplify Fixed Stroke"; + ot->idname = "GPENCIL_OT_stroke_simplify_fixed"; + ot->description = "Simplify selected stroked reducing number of points using fixed algorithm"; + + /* api callbacks */ + ot->exec = gp_stroke_simplify_fixed_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_int(ot->srna, "step", 1, 1, 100, "Steps", "Number of simplify steps", 1, 10); + + /* avoid re-using last var */ + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + +} + +/* ***************** Separate Strokes ********************** */ +typedef enum eGP_SeparateModes { + /* Points */ + GP_SEPARATE_POINT = 0, + /* Selected Strokes */ + GP_SEPARATE_STROKE, + /* Current Layer */ + GP_SEPARATE_LAYER, +} eGP_SeparateModes; + +static int gp_stroke_separate_exec(bContext *C, wmOperator *op) +{ + Base *base_new; + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Base *base_old = CTX_data_active_base(C); + bGPdata *gpd_src = ED_gpencil_data_get_active(C); + Object *ob = CTX_data_active_object(C); + + Object *ob_dst = NULL; + bGPdata *gpd_dst = NULL; + bGPDlayer *gpl_dst = NULL; + bGPDframe *gpf_dst = NULL; + bGPDspoint *pt; + Material *ma = NULL; + int i, idx; + + eGP_SeparateModes mode = RNA_enum_get(op->ptr, "mode"); + + /* sanity checks */ + if (ELEM(NULL, gpd_src)) { + return OPERATOR_CANCELLED; + } + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_src); + + /* create a new object */ + base_new = ED_object_add_duplicate(bmain, scene, view_layer, base_old, 0); + ob_dst = base_new->object; + ob_dst->mode = OB_MODE_OBJECT; + /* create new grease pencil datablock */ + // XXX: check usercounts + gpd_dst = BKE_gpencil_data_addnew(bmain, gpd_src->id.name + 2); + ob_dst->data = (bGPdata *)gpd_dst; + + int totslots = ob_dst->totcol; + int totadd = 0; + + /* loop old datablock and separate parts */ + if ((mode == GP_SEPARATE_POINT) || (mode == GP_SEPARATE_STROKE)) { + CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) + { + gpl_dst = NULL; + 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))) { + bGPDstroke *gps, *gpsn; + + if (gpf == NULL) { + continue; + } + + gpf_dst = NULL; + + for (gps = gpf->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; + + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { + continue; + } + /* separate selected strokes */ + if (gps->flag & GP_STROKE_SELECT) { + /* add layer if not created before */ + if (gpl_dst == NULL) { + gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl->info, false); + } + + /* add frame if not created before */ + if (gpf_dst == NULL) { + gpf_dst = BKE_gpencil_layer_getframe(gpl_dst, gpf->framenum, GP_GETFRAME_ADD_NEW); + } + + /* add duplicate materials */ + ma = give_current_material(ob, gps->mat_nr + 1); + idx = BKE_gpencil_get_material_index(ob_dst, ma); + if (idx == 0) { + + totadd++; + ob_dst->actcol = totadd; + ob_dst->totcol = totadd; + + if (totadd > totslots) { + BKE_object_material_slot_add(bmain, ob_dst); + } + + assign_material(bmain, ob_dst, ma, ob_dst->totcol, BKE_MAT_ASSIGN_USERPREF); + idx = totadd; + } + + /* selected points mode */ + if (mode == GP_SEPARATE_POINT) { + /* make copy of source stroke */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps); + + /* reasign material */ + gps_dst->mat_nr = idx - 1; + + /* link to destination frame */ + BLI_addtail(&gpf_dst->strokes, gps_dst); + + /* Invert selection status of all points in destination stroke */ + for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { + pt->flag ^= GP_SPOINT_SELECT; + } + + /* delete selected points from destination stroke */ + gp_stroke_delete_tagged_points(gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false); + + /* delete selected points from origin stroke */ + gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false); + } + /* selected strokes mode */ + else if (mode == GP_SEPARATE_STROKE) { + /* deselect old stroke */ + gps->flag &= ~GP_STROKE_SELECT; + /* unlink from source frame */ + BLI_remlink(&gpf->strokes, gps); + gps->prev = gps->next = NULL; + /* relink to destination frame */ + BLI_addtail(&gpf_dst->strokes, gps); + /* reasign material */ + gps->mat_nr = idx - 1; + } + } + } + } + + /* if not multiedit, exit loop*/ + if (!is_multiedit) { + break; + } + } + } + CTX_DATA_END; + } + else if (mode == GP_SEPARATE_LAYER) { + bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); + if (gpl) { + /* try to set a new active layer in source datablock */ + if (gpl->prev) { + BKE_gpencil_layer_setactive(gpd_src, gpl->prev); + } + else if (gpl->next) { + BKE_gpencil_layer_setactive(gpd_src, gpl->next); + } + /* unlink from source datablock */ + BLI_remlink(&gpd_src->layers, gpl); + gpl->prev = gpl->next = NULL; + /* relink to destination datablock */ + BLI_addtail(&gpd_dst->layers, gpl); + } + } + DEG_id_tag_update(&gpd_src->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + DEG_id_tag_update(&gpd_dst->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + DEG_relations_tag_update(bmain); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, NULL); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_separate(wmOperatorType *ot) +{ + static const EnumPropertyItem separate_type[] = { + {GP_SEPARATE_POINT, "POINT", 0, "Selected Points", "Separate the selected points"}, + {GP_SEPARATE_STROKE, "STROKE", 0, "Selected Strokes", "Separate the selected strokes"}, + {GP_SEPARATE_LAYER, "LAYER", 0, "Active Layer", "Separate the strokes of the current layer"}, + {0, NULL, 0, NULL, NULL} + }; + + /* identifiers */ + ot->name = "Separate Strokes"; + ot->idname = "GPENCIL_OT_stroke_separate"; + ot->description = "Separate the selected strokes or layer in a new grease pencil object"; + + /* callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = gp_stroke_separate_exec; + ot->poll = gp_strokes_edit3d_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "mode", separate_type, GP_SEPARATE_POINT, "Mode", ""); +} + +/* ***************** Split Strokes ********************** */ +static int gp_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDspoint *pt; + int i; + + /* sanity checks */ + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + + /* loop strokes and split parts */ + 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))) { + bGPDstroke *gps, *gpsn; + + if (gpf == NULL) { + continue; + } + + for (gps = gpf->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; + + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_color_use(ob, gpl, gps) == false) { + continue; + } + /* split selected strokes */ + if (gps->flag & GP_STROKE_SELECT) { + /* make copy of source stroke */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps); + + /* link to same frame */ + BLI_addtail(&gpf->strokes, gps_dst); + + /* invert selection status of all points in destination stroke */ + for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { + pt->flag ^= GP_SPOINT_SELECT; + } + + /* delete selected points from destination stroke */ + gp_stroke_delete_tagged_points(gpf, gps_dst, NULL, GP_SPOINT_SELECT, true); + + /* delete selected points from origin stroke */ + gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false); + } + } + /* select again tagged points */ + for (gps = gpf->strokes.first; gps; gps = gps->next) { + bGPDspoint *ptn = gps->points; + for (int i2 = 0; i2 < gps->totpoints; i2++, ptn++) { + if (ptn->flag & GP_SPOINT_TAG) { + ptn->flag |= GP_SPOINT_SELECT; + ptn->flag &= ~GP_SPOINT_TAG; + } + } + } + } + + /* if not multiedit, exit loop*/ + if (!is_multiedit) { + break; + } + } + } + CTX_DATA_END; + + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_split(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Split Strokes"; + ot->idname = "GPENCIL_OT_stroke_split"; + ot->description = "Split selected points as new stroke on same frame"; + + /* callbacks */ + ot->exec = gp_stroke_split_exec; + ot->poll = gp_strokes_edit3d_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int gp_stroke_smooth_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + gp_smooth_stroke(C, op); + + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_smooth(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Smooth Stroke"; + ot->idname = "GPENCIL_OT_stroke_smooth"; + ot->description = "Smooth selected strokes"; + + /* api callbacks */ + ot->exec = gp_stroke_smooth_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_int(ot->srna, "repeat", 1, 1, 10, "Repeat", "", 1, 5); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 2.0f, "Factor", "", 0.0f, 2.0f); + RNA_def_boolean(ot->srna, "only_selected", true, "Selected Points", + "Smooth only selected points in the stroke"); + RNA_def_boolean(ot->srna, "smooth_position", true, "Position", ""); + RNA_def_boolean(ot->srna, "smooth_thickness", true, "Thickness", ""); + RNA_def_boolean(ot->srna, "smooth_strength", false, "Strength", ""); + RNA_def_boolean(ot->srna, "smooth_uv", false, "UV", ""); } |