Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/gpencil/gpencil_edit.c
| Context not available. | |||||
| #include "DNA_space_types.h" | #include "DNA_space_types.h" | ||||
| #include "DNA_view3d_types.h" | #include "DNA_view3d_types.h" | ||||
| #include "DNA_gpencil_types.h" | #include "DNA_gpencil_types.h" | ||||
| #include "DNA_workspace_types.h" | |||||
| #include "BKE_main.h" | |||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_global.h" | #include "BKE_global.h" | ||||
| #include "BKE_brush.h" | |||||
| #include "BKE_gpencil.h" | #include "BKE_gpencil.h" | ||||
| #include "BKE_paint.h" | |||||
| #include "BKE_library.h" | #include "BKE_library.h" | ||||
| #include "BKE_object.h" | |||||
| #include "BKE_report.h" | #include "BKE_report.h" | ||||
| #include "BKE_screen.h" | #include "BKE_screen.h" | ||||
| #include "BKE_workspace.h" | |||||
| #include "UI_interface.h" | #include "UI_interface.h" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| Context not available. | |||||
| #include "ED_space_api.h" | #include "ED_space_api.h" | ||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_build.h" | |||||
| #include "gpencil_intern.h" | #include "gpencil_intern.h" | ||||
| /* ************************************************ */ | /* ************************************************ */ | ||||
| /* Stroke Edit Mode Management */ | /* Stroke Edit Mode Management */ | ||||
| static int gpencil_editmode_toggle_poll(bContext *C) | static int gpencil_editmode_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; | 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_int_get(op->ptr, "back"); | |||||
| WorkSpace *workspace = CTX_wm_workspace(C); | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(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) | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| Context not available. | |||||
| gpd->flag ^= GP_DATA_STROKE_EDITMODE; | gpd->flag ^= GP_DATA_STROKE_EDITMODE; | ||||
| /* recalculate parent matrix */ | /* recalculate parent matrix */ | ||||
| if (gpd->flag & GP_DATA_STROKE_EDITMODE) { | if (gpd->flag & GP_DATA_STROKE_EDITMODE) { | ||||
| ED_gpencil_reset_layers_parent(gpd); | ED_gpencil_reset_layers_parent(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 ((workspace->object_mode_restore) && ((gpd->flag & GP_DATA_STROKE_EDITMODE) == 0) && (back == 1)) { | |||||
| mode = workspace->object_mode_restore; | |||||
| } | |||||
| workspace->object_mode_restore = workspace->object_mode; | |||||
| workspace->object_mode = mode; | |||||
| } | } | ||||
| /* setup other modes */ | |||||
| ED_gpencil_setup_modes(C, gpd, mode); | |||||
| /* set cache as dirty */ | |||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL); | 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_GPENCIL | ND_GPENCIL_EDITMODE, NULL); | ||||
| WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); | WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); | ||||
| Context not available. | |||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | ||||
| /* properties */ | |||||
| RNA_def_int(ot->srna, "back", 0, 0, 1, "back", "1 to back previous mode", 0, 1); | |||||
| } | |||||
| /* Stroke Paint Mode Management */ | |||||
| static int 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 int back = RNA_int_get(op->ptr, "back"); | |||||
| WorkSpace *workspace = CTX_wm_workspace(C); | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| ToolSettings *ts = CTX_data_tool_settings(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 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 ((workspace->object_mode_restore) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0) && (back == 1)) { | |||||
| mode = workspace->object_mode_restore; | |||||
| } | |||||
| workspace->object_mode_restore = workspace->object_mode; | |||||
| workspace->object_mode = mode; | |||||
| } | |||||
| /* be sure we have brushes */ | |||||
| Paint *paint = BKE_brush_get_gpencil_paint(ts); | |||||
| /* if not exist, create a new one */ | |||||
| if (paint->brush == NULL) { | |||||
| BKE_brush_gpencil_presets(C); | |||||
| } | |||||
| /* setup other modes */ | |||||
| ED_gpencil_setup_modes(C, gpd, mode); | |||||
| /* set cache as dirty */ | |||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); | |||||
| WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void GPENCIL_OT_paintmode_toggle(wmOperatorType *ot) | |||||
| { | |||||
| /* 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 */ | |||||
| RNA_def_int(ot->srna, "back", 0, 0, 1, "back", "1 to back previous mode", 0, 1); | |||||
| } | |||||
| /* Stroke Sculpt Mode Management */ | |||||
| static int 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 int back = RNA_int_get(op->ptr, "back"); | |||||
| WorkSpace *workspace = CTX_wm_workspace(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 ((workspace->object_mode_restore) && ((gpd->flag & GP_DATA_STROKE_SCULPTMODE) == 0) && (back == 1)) { | |||||
| mode = workspace->object_mode_restore; | |||||
| } | |||||
| workspace->object_mode_restore = workspace->object_mode; | |||||
| workspace->object_mode = mode; | |||||
| } | |||||
| /* setup other modes */ | |||||
| ED_gpencil_setup_modes(C, gpd, mode); | |||||
| /* set cache as dirty */ | |||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); | |||||
| WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void GPENCIL_OT_sculptmode_toggle(wmOperatorType *ot) | |||||
| { | |||||
| /* 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 */ | |||||
| RNA_def_int(ot->srna, "back", 0, 0, 1, "back", "1 to back previous mode", 0, 1); | |||||
| } | |||||
| /* Stroke Weight Paint Mode Management */ | |||||
| static int 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 int back = RNA_int_get(op->ptr, "back"); | |||||
| WorkSpace *workspace = CTX_wm_workspace(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 ((workspace->object_mode_restore) && ((gpd->flag & GP_DATA_STROKE_WEIGHTMODE) == 0) && (back == 1)) { | |||||
| mode = workspace->object_mode_restore; | |||||
| } | |||||
| workspace->object_mode_restore = workspace->object_mode; | |||||
| workspace->object_mode = mode; | |||||
| } | |||||
| /* setup other modes */ | |||||
| ED_gpencil_setup_modes(C, gpd, mode); | |||||
| /* set cache as dirty */ | |||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); | |||||
| WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void GPENCIL_OT_weightmode_toggle(wmOperatorType *ot) | |||||
| { | |||||
| /* 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 */ | |||||
| RNA_def_int(ot->srna, "back", 0, 0, 1, "back", "1 to back previous mode", 0, 1); | |||||
| } | } | ||||
| /* ************************************************ */ | /* ************************************************ */ | ||||
| Context not available. | |||||
| return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0; | return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0; | ||||
| } | } | ||||
| /* poll callback to verify edit mode in 3D view only */ | |||||
| static int gp_strokes_edit3d_poll(bContext *C) | |||||
| { | |||||
| /* 2 Requirements: | |||||
| * - 1) Editable GP data | |||||
| * - 2) 3D View only | |||||
| */ | |||||
| return (gp_stroke_edit_poll(C) && ED_operator_view3d_active(C)); | |||||
| } | |||||
| /* ************ Stroke Hide selection Toggle ************** */ | /* ************ Stroke Hide selection Toggle ************** */ | ||||
| static int gpencil_hideselect_toggle_exec(bContext *C, wmOperator *UNUSED(op)) | static int gpencil_hideselect_toggle_exec(bContext *C, wmOperator *UNUSED(op)) | ||||
| Context not available. | |||||
| ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | ||||
| } | } | ||||
| /* toggle multi edit strokes support */ | |||||
| static int gpencil_multiedit_toggle_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| const int lines = RNA_int_get(op->ptr, "lines"); | |||||
| if (gpd == NULL) | |||||
| return OPERATOR_CANCELLED; | |||||
| /* Just toggle value */ | |||||
| if (lines == 0) { | |||||
| gpd->flag ^= GP_DATA_STROKE_SHOW_EDIT_LINES; | |||||
| } | |||||
| else { | |||||
| gpd->flag ^= GP_DATA_STROKE_MULTIEDIT_LINES; | |||||
| } | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL); | |||||
| WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| void GPENCIL_OT_multiedit_toggle(wmOperatorType *ot) | |||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Edit Lines Toggle"; | |||||
| ot->idname = "GPENCIL_OT_multiedit_toggle"; | |||||
| ot->description = "Enable/Disable edit lines support"; | |||||
| /* callbacks */ | |||||
| ot->exec = gpencil_multiedit_toggle_exec; | |||||
| ot->poll = gp_stroke_edit_poll; | |||||
| /* flags */ | |||||
| ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | |||||
| /* properties */ | |||||
| RNA_def_int(ot->srna, "lines", 0, 0, 1, "lines", "1 to toggle display lines only", 0, 1); | |||||
| } | |||||
| /* ************** Duplicate Selected Strokes **************** */ | /* ************** Duplicate Selected Strokes **************** */ | ||||
| /* Make copies of selected point segments in a selected stroke */ | /* Make copies of selected point segments in a selected stroke */ | ||||
| Context not available. | |||||
| gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy"); | gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy"); | ||||
| memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len); | memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len); | ||||
| gpsd->totpoints = len; | gpsd->totpoints = len; | ||||
| /* Copy weights */ | |||||
| int e = start_idx; | |||||
| for (int j = 0; j < gpsd->totpoints; j++) { | |||||
| bGPDspoint *pt_src = &gps->points[e]; | |||||
| bGPDspoint *pt_dst = &gpsd->points[j]; | |||||
| pt_dst->weights = MEM_dupallocN(pt_src->weights); | |||||
| e++; | |||||
| } | |||||
| /* add to temp buffer */ | /* add to temp buffer */ | ||||
| gpsd->next = gpsd->prev = NULL; | gpsd->next = gpsd->prev = NULL; | ||||
| BLI_addtail(new_strokes, gpsd); | BLI_addtail(new_strokes, gpsd); | ||||
| Context not available. | |||||
| static int gp_duplicate_exec(bContext *C, wmOperator *op) | static int gp_duplicate_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| if (gpd == NULL) { | if (gpd == NULL) { | ||||
| BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); | BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); | ||||
| return OPERATOR_CANCELLED; | 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, | /* for each visible (and editable) layer's selected strokes, | ||||
| * copy the strokes into a temporary buffer, then append | * copy the strokes into a temporary buffer, then append | ||||
| Context not available. | |||||
| gpsd = MEM_dupallocN(gps); | gpsd = MEM_dupallocN(gps); | ||||
| BLI_strncpy(gpsd->tmp_layerinfo, gpl->info, sizeof(gpsd->tmp_layerinfo)); | BLI_strncpy(gpsd->tmp_layerinfo, gpl->info, sizeof(gpsd->tmp_layerinfo)); | ||||
| gpsd->points = MEM_dupallocN(gps->points); | gpsd->points = MEM_dupallocN(gps->points); | ||||
| BKE_gpencil_stroke_weights_duplicate(gps, gpsd); | |||||
| /* triangle information - will be calculated on next redraw */ | /* triangle information - will be calculated on next redraw */ | ||||
| gpsd->flag |= GP_STROKE_RECALC_CACHES; | gpsd->flag |= GP_STROKE_RECALC_CACHES; | ||||
| Context not available. | |||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* updates */ | /* updates */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| for (gps = gp_strokes_copypastebuf.first; gps; gps = gpsn) { | for (gps = gp_strokes_copypastebuf.first; gps; gps = gpsn) { | ||||
| gpsn = gps->next; | gpsn = gps->next; | ||||
| if (gps->points) MEM_freeN(gps->points); | if (gps->points) { | ||||
| if (gps->triangles) MEM_freeN(gps->triangles); | BKE_gpencil_free_stroke_weights(gps); | ||||
| MEM_freeN(gps->points); | |||||
| } | |||||
| MEM_SAFE_FREE(gps->triangles); | |||||
| BLI_freelinkN(&gp_strokes_copypastebuf, gps); | BLI_freelinkN(&gp_strokes_copypastebuf, gps); | ||||
| } | } | ||||
| Context not available. | |||||
| /* Ensure that destination datablock has all the colours the pasted strokes need | /* Ensure that destination datablock has all the colours the pasted strokes need | ||||
| * Helper function for copy-pasting strokes | * 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"); | GHash *new_colors = BLI_ghash_str_new("GPencil Paste Dst Colors"); | ||||
| GHashIterator gh_iter; | GHashIterator gh_iter; | ||||
| bGPdata *gpd = CTX_data_gpencil_data(C); | |||||
| bGPDpaletteref *palslot; | |||||
| Palette *palette; | |||||
| /* If there's no active palette yet (i.e. new datablock), add one */ | /* If there's no active palette yet (i.e. new datablock), add one */ | ||||
| bGPDpalette *palette = BKE_gpencil_palette_getactive(gpd); | palslot = BKE_gpencil_paletteslot_validate(CTX_data_main(C), gpd); | ||||
| if (palette == NULL) { | palette = palslot->palette; | ||||
| palette = BKE_gpencil_palette_addnew(gpd, "Pasted Palette", true); | |||||
| } | |||||
| /* For each color, figure out what to map to... */ | /* For each color, figure out what to map to... */ | ||||
| GHASH_ITER(gh_iter, gp_strokes_copypastebuf_colors) { | GHASH_ITER(gh_iter, gp_strokes_copypastebuf_colors) { | ||||
| bGPDpalettecolor *palcolor; | PaletteColor *palcolor; | ||||
| char *name = BLI_ghashIterator_getKey(&gh_iter); | char *name = BLI_ghashIterator_getKey(&gh_iter); | ||||
| /* Look for existing color to map to */ | /* Look for existing color to map to */ | ||||
| /* XXX: What to do if same name but different color? Behaviour here should depend on a property? */ | /* XXX: What to do if same name but different color? Behaviour here should depend on a property? */ | ||||
| palcolor = BKE_gpencil_palettecolor_getbyname(palette, name); | palcolor = BKE_palette_color_getbyname(palette, name); | ||||
| if (palcolor == NULL) { | if (palcolor == NULL) { | ||||
| /* Doesn't Exist - Create new matching color for this palette */ | /* Doesn't Exist - Create new matching color for this palette */ | ||||
| /* XXX: This still doesn't fix the pasting across file boundaries problem... */ | /* XXX: This still doesn't fix the pasting across file boundaries problem... */ | ||||
| bGPDpalettecolor *src_color = BLI_ghashIterator_getValue(&gh_iter); | PaletteColor *src_color = BLI_ghashIterator_getValue(&gh_iter); | ||||
| palcolor = MEM_dupallocN(src_color); | palcolor = MEM_dupallocN(src_color); | ||||
| BLI_addtail(&palette->colors, palcolor); | BLI_addtail(&palette->colors, palcolor); | ||||
| BLI_uniquename(&palette->colors, palcolor, DATA_("GP Color"), '.', offsetof(bGPDpalettecolor, info), sizeof(palcolor->info)); | BLI_uniquename(&palette->colors, palcolor, DATA_("GP Color"), '.', offsetof(PaletteColor, info), sizeof(palcolor->info)); | ||||
| } | } | ||||
| /* Store this mapping (for use later when pasting) */ | /* Store this mapping (for use later when pasting) */ | ||||
| Context not available. | |||||
| BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); | BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); | ||||
| return OPERATOR_CANCELLED; | 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 */ | /* clear the buffer first */ | ||||
| ED_gpencil_strokes_copybuf_free(); | ED_gpencil_strokes_copybuf_free(); | ||||
| Context not available. | |||||
| gpsd = MEM_dupallocN(gps); | gpsd = MEM_dupallocN(gps); | ||||
| BLI_strncpy(gpsd->tmp_layerinfo, gpl->info, sizeof(gpsd->tmp_layerinfo)); /* saves original layer name */ | BLI_strncpy(gpsd->tmp_layerinfo, gpl->info, sizeof(gpsd->tmp_layerinfo)); /* saves original layer name */ | ||||
| gpsd->points = MEM_dupallocN(gps->points); | gpsd->points = MEM_dupallocN(gps->points); | ||||
| BKE_gpencil_stroke_weights_duplicate(gps, gpsd); | |||||
| /* triangles cache - will be recalculated on next redraw */ | /* triangles cache - will be recalculated on next redraw */ | ||||
| gpsd->flag |= GP_STROKE_RECALC_CACHES; | gpsd->flag |= GP_STROKE_RECALC_CACHES; | ||||
| gpsd->tot_triangles = 0; | gpsd->tot_triangles = 0; | ||||
| Context not available. | |||||
| for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { | for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { | ||||
| if (ED_gpencil_stroke_can_use(C, gps)) { | if (ED_gpencil_stroke_can_use(C, gps)) { | ||||
| if (BLI_ghash_haskey(gp_strokes_copypastebuf_colors, gps->colorname) == false) { | if (BLI_ghash_haskey(gp_strokes_copypastebuf_colors, gps->colorname) == false) { | ||||
| bGPDpalettecolor *color = MEM_dupallocN(gps->palcolor); | PaletteColor *color = MEM_dupallocN(gps->palcolor); | ||||
| BLI_ghash_insert(gp_strokes_copypastebuf_colors, gps->colorname, color); | BLI_ghash_insert(gp_strokes_copypastebuf_colors, gps->colorname, color); | ||||
| gps->palcolor = color; | gps->palcolor = color; | ||||
| Context not available. | |||||
| BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); | BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); | ||||
| return OPERATOR_CANCELLED; | 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)) { | 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"); | BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again"); | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| Context not available. | |||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* Ensure that all the necessary colors exist */ | /* 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) */ | /* Copy over the strokes from the buffer (and adjust the colors) */ | ||||
| for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { | for (bGPDstroke *gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) { | ||||
| Context not available. | |||||
| new_stroke->tmp_layerinfo[0] = '\0'; | new_stroke->tmp_layerinfo[0] = '\0'; | ||||
| new_stroke->points = MEM_dupallocN(gps->points); | new_stroke->points = MEM_dupallocN(gps->points); | ||||
| BKE_gpencil_stroke_weights_duplicate(gps, new_stroke); | |||||
| new_stroke->flag |= GP_STROKE_RECALC_CACHES; | new_stroke->flag |= GP_STROKE_RECALC_CACHES; | ||||
| new_stroke->triangles = NULL; | new_stroke->triangles = NULL; | ||||
| Context not available. | |||||
| BLI_ghash_free(new_colors, NULL, NULL); | BLI_ghash_free(new_colors, NULL, NULL); | ||||
| /* updates */ | /* updates */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| bGPDlayer *target_layer = NULL; | bGPDlayer *target_layer = NULL; | ||||
| ListBase strokes = {NULL, NULL}; | ListBase strokes = {NULL, NULL}; | ||||
| int layer_num = RNA_enum_get(op->ptr, "layer"); | 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 */ | /* Get layer or create new one */ | ||||
| if (layer_num == -1) { | if (layer_num == -1) { | ||||
| /* Create layer */ | /* Create layer */ | ||||
| Context not available. | |||||
| } | } | ||||
| /* updates */ | /* updates */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| BKE_gpencil_layer_delframe(gpl, gpf); | BKE_gpencil_layer_delframe(gpl, gpf); | ||||
| /* notifiers */ | /* notifiers */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| static int gp_actframe_delete_all_exec(bContext *C, wmOperator *op) | static int gp_actframe_delete_all_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| bool success = false; | bool success = false; | ||||
| Context not available. | |||||
| /* updates */ | /* updates */ | ||||
| if (success) { | if (success) { | ||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | BKE_gpencil_batch_cache_dirty(gpd); | ||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | |||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| else { | else { | ||||
| Context not available. | |||||
| GP_DELETEOP_FRAME = 2, | GP_DELETEOP_FRAME = 2, | ||||
| } eGP_DeleteMode; | } 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 */ | /* Delete selected strokes */ | ||||
| static int gp_delete_selected_strokes(bContext *C) | static int gp_delete_selected_strokes(bContext *C) | ||||
| { | { | ||||
| bool changed = false; | bool changed = false; | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); | |||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| { | { | ||||
| bGPDframe *gpf = gpl->actframe; | bGPDframe *init_gpf = gpl->actframe; | ||||
| bGPDstroke *gps, *gpsn; | if (is_multiedit) { | ||||
| init_gpf = gpl->frames.first; | |||||
| if (gpf == NULL) | } | ||||
| continue; | |||||
| for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { | |||||
| /* simply delete strokes which are selected */ | if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { | ||||
| for (gps = gpf->strokes.first; gps; gps = gpsn) { | bGPDstroke *gps, *gpsn; | ||||
| gpsn = gps->next; | |||||
| if (gpf == NULL) | |||||
| /* skip strokes that are invalid for current view */ | continue; | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | |||||
| continue; | /* simply delete strokes which are selected */ | ||||
| for (gps = gpf->strokes.first; gps; gps = gpsn) { | |||||
| /* free stroke if selected */ | gpsn = gps->next; | ||||
| if (gps->flag & GP_STROKE_SELECT) { | |||||
| /* free stroke memory arrays, then stroke itself */ | /* skip strokes that are invalid for current view */ | ||||
| if (gps->points) MEM_freeN(gps->points); | if (ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| if (gps->triangles) MEM_freeN(gps->triangles); | continue; | ||||
| BLI_freelinkN(&gpf->strokes, gps); | |||||
| /* free stroke if selected */ | |||||
| changed = true; | if (gps->flag & GP_STROKE_SELECT) { | ||||
| /* free stroke memory arrays, then stroke itself */ | |||||
| if (gps->points) { | |||||
| BKE_gpencil_free_stroke_weights(gps); | |||||
| MEM_freeN(gps->points); | |||||
| } | |||||
| MEM_SAFE_FREE(gps->triangles); | |||||
| BLI_freelinkN(&gpf->strokes, gps); | |||||
| changed = true; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| if (changed) { | if (changed) { | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| Context not available. | |||||
| /* ----------------------------------- */ | /* ----------------------------------- */ | ||||
| /* Delete selected points but keep the stroke */ | /* 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) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); | |||||
| bool changed = false; | bool changed = false; | ||||
| int first = 0; | |||||
| int last = 0; | |||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| { | { | ||||
| bGPDframe *gpf = gpl->actframe; | bGPDframe *init_gpf = gpl->actframe; | ||||
| bGPDstroke *gps, *gpsn; | if (is_multiedit) { | ||||
| init_gpf = gpl->frames.first; | |||||
| if (gpf == NULL) | } | ||||
| continue; | |||||
| for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { | |||||
| /* simply delete points from selected strokes | if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { | ||||
| * NOTE: we may still have to remove the stroke if it ends up having no points! | |||||
| */ | bGPDstroke *gps, *gpsn; | ||||
| 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) | /* simply delete points from selected strokes | ||||
| continue; | * NOTE: we may still have to remove the stroke if it ends up having no points! | ||||
| /* check if the color is editable */ | */ | ||||
| if (ED_gpencil_stroke_color_use(gpl, gps) == false) | for (gps = gpf->strokes.first; gps; gps = gpsn) { | ||||
| continue; | gpsn = gps->next; | ||||
| if (gps->flag & GP_STROKE_SELECT) { | /* skip strokes that are invalid for current view */ | ||||
| bGPDspoint *pt; | if (ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| int i; | continue; | ||||
| /* check if the color is editable */ | |||||
| int tot = gps->totpoints; /* number of points in new buffer */ | if (ED_gpencil_stroke_color_use(gpl, gps) == false) | ||||
| continue; | |||||
| /* 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++) { | /* the stroke must have at least one point selected for any operator */ | ||||
| if (pt->flag & GP_SPOINT_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| /* selected point - one of the points to remove */ | bGPDspoint *pt; | ||||
| tot--; | int i; | ||||
| } | |||||
| } | int tot = gps->totpoints; /* number of points in new buffer */ | ||||
| /* if no points are left, we simply delete the entire stroke */ | /* first pass: count points to remove */ | ||||
| if (tot <= 0) { | switch (mode) { | ||||
| /* remove the entire stroke */ | case GP_DISSOLVE_POINTS: | ||||
| MEM_freeN(gps->points); | /* Count how many points are selected (i.e. how many to remove) */ | ||||
| if (gps->triangles) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| MEM_freeN(gps->triangles); | if (pt->flag & GP_SPOINT_SELECT) { | ||||
| } | /* selected point - one of the points to remove */ | ||||
| BLI_freelinkN(&gpf->strokes, gps); | tot--; | ||||
| } | } | ||||
| else { | } | ||||
| /* just copy all unselected into a smaller buffer */ | break; | ||||
| bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); | case GP_DISSOLVE_BETWEEN: | ||||
| bGPDspoint *npt = new_points; | /* need to find first and last point selected */ | ||||
| first = -1; | |||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | last = 0; | ||||
| if ((pt->flag & GP_SPOINT_SELECT) == 0) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| *npt = *pt; | if (pt->flag & GP_SPOINT_SELECT) { | ||||
| npt++; | 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; | |||||
| } | |||||
| /* if no points are left, we simply delete the entire stroke */ | |||||
| if (tot <= 0) { | |||||
| /* remove the entire stroke */ | |||||
| if (gps->points) { | |||||
| BKE_gpencil_free_stroke_weights(gps); | |||||
| MEM_freeN(gps->points); | |||||
| } | |||||
| if (gps->triangles) { | |||||
| MEM_freeN(gps->triangles); | |||||
| } | |||||
| BLI_freelinkN(&gpf->strokes, gps); | |||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| } | |||||
| 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; | |||||
| switch (mode) { | |||||
| case GP_DISSOLVE_POINTS: | |||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | |||||
| if ((pt->flag & GP_SPOINT_SELECT) == 0) { | |||||
| *npt = *pt; | |||||
| npt->weights = MEM_dupallocN(pt->weights); | |||||
| npt++; | |||||
| } | |||||
| } | |||||
| break; | |||||
| case GP_DISSOLVE_BETWEEN: | |||||
| /* copy first segment */ | |||||
| for (i = 0, pt = gps->points; i < first; i++, pt++) { | |||||
| *npt = *pt; | |||||
| npt->weights = MEM_dupallocN(pt->weights); | |||||
| npt++; | |||||
| } | |||||
| /* copy segment (selected points) */ | |||||
| for (i = first, pt = gps->points + first; i < last; i++, pt++) { | |||||
| if (pt->flag & GP_SPOINT_SELECT) { | |||||
| *npt = *pt; | |||||
| npt->weights = MEM_dupallocN(pt->weights); | |||||
| npt++; | |||||
| } | |||||
| } | |||||
| /* copy last segment */ | |||||
| for (i = last, pt = gps->points + last; i < gps->totpoints; i++, pt++) { | |||||
| *npt = *pt; | |||||
| npt->weights = MEM_dupallocN(pt->weights); | |||||
| npt++; | |||||
| } | |||||
| break; | |||||
| case GP_DISSOLVE_UNSELECT: | |||||
| /* copy any selected point */ | |||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | |||||
| if (pt->flag & GP_SPOINT_SELECT) { | |||||
| *npt = *pt; | |||||
| npt->weights = MEM_dupallocN(pt->weights); | |||||
| npt++; | |||||
| } | |||||
| } | |||||
| break; | |||||
| } | |||||
| /* free the old buffer */ | |||||
| if (gps->points) { | |||||
| BKE_gpencil_free_stroke_weights(gps); | |||||
| MEM_freeN(gps->points); | |||||
| } | |||||
| /* save the new buffer */ | |||||
| gps->points = new_points; | |||||
| gps->totpoints = tot; | |||||
| /* 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; | |||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | |||||
| pt->flag &= ~GP_SPOINT_SELECT; | |||||
| } | |||||
| } | } | ||||
| changed = true; | |||||
| } | } | ||||
| /* free the old buffer */ | |||||
| MEM_freeN(gps->points); | |||||
| /* save the new buffer */ | |||||
| gps->points = new_points; | |||||
| gps->totpoints = tot; | |||||
| /* 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; | |||||
| } | } | ||||
| changed = true; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| if (changed) { | if (changed) { | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| Context not available. | |||||
| * becomes much less | * becomes much less | ||||
| * 2) Each island gets converted to a new stroke | * 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"); | tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands"); | ||||
| bool in_island = false; | bool in_island = false; | ||||
| int num_islands = 0; | int num_islands = 0; | ||||
| bGPDspoint *pt; | |||||
| int i; | |||||
| /* First Pass: Identify start/end of islands */ | /* 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) { | if (pt->flag & tag_flags) { | ||||
| /* selected - stop accumulating to island */ | /* selected - stop accumulating to island */ | ||||
| in_island = false; | in_island = false; | ||||
| Context not available. | |||||
| /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */ | /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */ | ||||
| new_stroke->totpoints = island->end_idx - island->start_idx + 1; | new_stroke->totpoints = island->end_idx - island->start_idx + 1; | ||||
| new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment"); | new_stroke->points = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment"); | ||||
| /* Copy over the relevant points */ | /* Copy over the relevant points */ | ||||
| memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints); | memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints); | ||||
| /* Copy weights */ | |||||
| int e = island->start_idx; | |||||
| for (int i = 0; i < new_stroke->totpoints; i++) { | |||||
| bGPDspoint *pt_src = &gps->points[e]; | |||||
| bGPDspoint *pt_dst = &new_stroke->points[i]; | |||||
| pt_dst->weights = MEM_dupallocN(pt_src->weights); | |||||
| e++; | |||||
| } | |||||
| /* Each island corresponds to a new stroke. We must adjust the | /* Each island corresponds to a new stroke. We must adjust the | ||||
| * timings of these new strokes: | * timings of these new strokes: | ||||
| Context not available. | |||||
| pts = new_stroke->points; | pts = new_stroke->points; | ||||
| for (j = 0; j < new_stroke->totpoints; j++, pts++) { | for (j = 0; j < new_stroke->totpoints; j++, pts++) { | ||||
| pts->time -= delta; | pts->time -= delta; | ||||
| /* set flag for select again later */ | |||||
| if (select == true) { | |||||
| pts->flag &= ~GP_SPOINT_SELECT; | |||||
| pts->flag |= GP_SPOINT_TAG; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| MEM_freeN(islands); | MEM_freeN(islands); | ||||
| /* Delete the old stroke */ | /* Delete the old stroke */ | ||||
| MEM_freeN(gps->points); | if (gps->points) { | ||||
| BKE_gpencil_free_stroke_weights(gps); | |||||
| MEM_freeN(gps->points); | |||||
| } | |||||
| if (gps->triangles) { | if (gps->triangles) { | ||||
| MEM_freeN(gps->triangles); | MEM_freeN(gps->triangles); | ||||
| } | } | ||||
| BLI_freelinkN(&gpf->strokes, gps); | BLI_freelinkN(&gpf->strokes, gps); | ||||
| } | } | ||||
| /* Split selected strokes into segments, splitting on selected points */ | /* Split selected strokes into segments, splitting on selected points */ | ||||
| static int gp_delete_selected_points(bContext *C) | static int gp_delete_selected_points(bContext *C) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); | |||||
| bool changed = false; | bool changed = false; | ||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| { | { | ||||
| bGPDframe *gpf = gpl->actframe; | bGPDframe *init_gpf = gpl->actframe; | ||||
| bGPDstroke *gps, *gpsn; | if (is_multiedit) { | ||||
| init_gpf = gpl->frames.first; | |||||
| if (gpf == NULL) | } | ||||
| continue; | |||||
| for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { | |||||
| /* simply delete strokes which are selected */ | if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { | ||||
| for (gps = gpf->strokes.first; gps; gps = gpsn) { | bGPDstroke *gps, *gpsn; | ||||
| gpsn = gps->next; | |||||
| if (gpf == NULL) | |||||
| /* skip strokes that are invalid for current view */ | continue; | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | |||||
| continue; | /* simply delete strokes which are selected */ | ||||
| /* check if the color is editable */ | for (gps = gpf->strokes.first; gps; gps = gpsn) { | ||||
| if (ED_gpencil_stroke_color_use(gpl, gps) == false) | gpsn = gps->next; | ||||
| continue; | |||||
| /* skip strokes that are invalid for current view */ | |||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | |||||
| if (gps->flag & GP_STROKE_SELECT) { | continue; | ||||
| /* deselect old stroke, since it will be used as template for the new strokes */ | /* check if the color is editable */ | ||||
| gps->flag &= ~GP_STROKE_SELECT; | if (ED_gpencil_stroke_color_use(gpl, gps) == false) | ||||
| continue; | |||||
| /* 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) { | |||||
| changed = true; | /* 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; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| if (changed) { | if (changed) { | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| } | } | ||||
| /* 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) | static int gp_delete_exec(bContext *C, wmOperator *op) | ||||
| Context not available. | |||||
| ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_delete_types, 0, "Type", "Method used for deleting Grease Pencil data"); | 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) | 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 */ | /* identifiers */ | ||||
| ot->name = "Dissolve"; | ot->name = "Dissolve"; | ||||
| ot->idname = "GPENCIL_OT_dissolve"; | ot->idname = "GPENCIL_OT_dissolve"; | ||||
| ot->description = "Delete selected points without splitting strokes"; | ot->description = "Delete selected points without splitting strokes"; | ||||
| /* callbacks */ | /* callbacks */ | ||||
| ot->invoke = WM_menu_invoke; | |||||
| ot->exec = gp_dissolve_exec; | ot->exec = gp_dissolve_exec; | ||||
| ot->poll = gp_stroke_edit_poll; | ot->poll = gp_stroke_edit_poll; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | 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 ************************ */ | /* ****************** Snapping - Strokes <-> Cursor ************************ */ | ||||
| Context not available. | |||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| RegionView3D *rv3d = CTX_wm_region_data(C); | RegionView3D *rv3d = CTX_wm_region_data(C); | ||||
| Object *obact = CTX_data_active_object(C); | |||||
| const float gridf = rv3d->gridview; | const float gridf = rv3d->gridview; | ||||
| for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { | for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { | ||||
| Context not available. | |||||
| bGPDframe *gpf = gpl->actframe; | bGPDframe *gpf = gpl->actframe; | ||||
| float diff_mat[4][4]; | float diff_mat[4][4]; | ||||
| /* calculate difference matrix if parent object */ | /* calculate difference matrix object */ | ||||
| if (gpl->parent != NULL) { | ED_gpencil_parent_location(obact, gpd, gpl, diff_mat); | ||||
| ED_gpencil_parent_location(gpl, diff_mat); | |||||
| } | |||||
| for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| Context not available. | |||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| /* only if point is selected */ | /* only if point is selected */ | ||||
| if (pt->flag & GP_SPOINT_SELECT) { | if (pt->flag & GP_SPOINT_SELECT) { | ||||
| if (gpl->parent == NULL) { | /* apply parent transformations */ | ||||
| pt->x = gridf * floorf(0.5f + pt->x / gridf); | float fpt[3]; | ||||
| pt->y = gridf * floorf(0.5f + pt->y / gridf); | mul_v3_m4v3(fpt, diff_mat, &pt->x); | ||||
| pt->z = gridf * floorf(0.5f + pt->z / gridf); | |||||
| } | fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); | ||||
| else { | fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); | ||||
| /* apply parent transformations */ | fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); | ||||
| float fpt[3]; | |||||
| mul_v3_m4v3(fpt, diff_mat, &pt->x); | /* return data */ | ||||
| copy_v3_v3(&pt->x, fpt); | |||||
| fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); | gp_apply_parent_point(obact, gpd, gpl, pt); | ||||
| 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); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| Context not available. | |||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| View3D *v3d = CTX_wm_view3d(C); | View3D *v3d = CTX_wm_view3d(C); | ||||
| Object *obact = CTX_data_active_object(C); \ | |||||
| const bool use_offset = RNA_boolean_get(op->ptr, "use_offset"); | 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 = ED_view3d_cursor3d_get(scene, v3d); | ||||
| Context not available. | |||||
| bGPDframe *gpf = gpl->actframe; | bGPDframe *gpf = gpl->actframe; | ||||
| float diff_mat[4][4]; | float diff_mat[4][4]; | ||||
| /* calculate difference matrix if parent object */ | /* calculate difference matrix */ | ||||
| if (gpl->parent != NULL) { | ED_gpencil_parent_location(obact, gpd, gpl, diff_mat); | ||||
| ED_gpencil_parent_location(gpl, diff_mat); | |||||
| } | |||||
| for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| Context not available. | |||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| if (pt->flag & GP_SPOINT_SELECT) { | if (pt->flag & GP_SPOINT_SELECT) { | ||||
| copy_v3_v3(&pt->x, cursor_global); | copy_v3_v3(&pt->x, cursor_global); | ||||
| if (gpl->parent != NULL) { | gp_apply_parent_point(obact, gpd, gpl, pt); | ||||
| gp_apply_parent_point(gpl, pt); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| } | } | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| Context not available. | |||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| View3D *v3d = CTX_wm_view3d(C); | View3D *v3d = CTX_wm_view3d(C); | ||||
| Object *obact = CTX_data_active_object(C); \ | |||||
| float *cursor = ED_view3d_cursor3d_get(scene, v3d); | float *cursor = ED_view3d_cursor3d_get(scene, v3d); | ||||
| float centroid[3] = {0.0f}; | float centroid[3] = {0.0f}; | ||||
| float min[3], max[3]; | float min[3], max[3]; | ||||
| Context not available. | |||||
| bGPDframe *gpf = gpl->actframe; | bGPDframe *gpf = gpl->actframe; | ||||
| float diff_mat[4][4]; | float diff_mat[4][4]; | ||||
| /* calculate difference matrix if parent object */ | /* calculate difference matrix */ | ||||
| if (gpl->parent != NULL) { | ED_gpencil_parent_location(obact, gpd, gpl, diff_mat); | ||||
| ED_gpencil_parent_location(gpl, diff_mat); | |||||
| } | |||||
| for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| Context not available. | |||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| if (pt->flag & GP_SPOINT_SELECT) { | if (pt->flag & GP_SPOINT_SELECT) { | ||||
| if (gpl->parent == NULL) { | /* apply parent transformations */ | ||||
| add_v3_v3(centroid, &pt->x); | float fpt[3]; | ||||
| minmax_v3v3_v3(min, max, &pt->x); | mul_v3_m4v3(fpt, diff_mat, &pt->x); | ||||
| } | |||||
| else { | |||||
| /* apply parent transformations */ | |||||
| float fpt[3]; | |||||
| mul_v3_m4v3(fpt, diff_mat, &pt->x); | |||||
| add_v3_v3(centroid, fpt); | add_v3_v3(centroid, fpt); | ||||
| minmax_v3v3_v3(min, max, fpt); | minmax_v3v3_v3(min, max, fpt); | ||||
| } | |||||
| count++; | count++; | ||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| Context not available. | |||||
| gpl->thickness = 0.0f; | gpl->thickness = 0.0f; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| continue; | continue; | ||||
| for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { | for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { | ||||
| bGPDpalettecolor *palcolor = gps->palcolor; | PaletteColor *palcolor = gps->palcolor; | ||||
| /* skip strokes that are not selected or invalid for current view */ | /* 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) | if (((gps->flag & GP_STROKE_SELECT) == 0) || ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| Context not available. | |||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| newpoint->pressure = pressure; | newpoint->pressure = pressure; | ||||
| newpoint->strength = strength; | newpoint->strength = strength; | ||||
| newpoint->time = point->time + deltatime; | newpoint->time = point->time + deltatime; | ||||
| newpoint->totweight = point->totweight; | |||||
| newpoint->weights = MEM_dupallocN(point->weights); | |||||
| } | } | ||||
| /* Helper: join two strokes using the shortest distance (reorder stroke if necessary ) */ | /* Helper: join two strokes using the shortest distance (reorder stroke if necessary ) */ | ||||
| Context not available. | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| bGPDlayer *activegpl = BKE_gpencil_layer_getactive(gpd); | bGPDlayer *activegpl = BKE_gpencil_layer_getactive(gpd); | ||||
| bGPDstroke *gps, *gpsn; | bGPDstroke *gps, *gpsn; | ||||
| bGPDpalette *palette = BKE_gpencil_palette_getactive(gpd); | Palette *palette = BKE_palette_get_active_from_context(C); | ||||
| bGPDpalettecolor *palcolor = BKE_gpencil_palettecolor_getactive(palette); | PaletteColor *palcolor = BKE_palette_color_get_active(palette); | ||||
| bGPDframe *gpf_a = NULL; | bGPDframe *gpf_a = NULL; | ||||
| bGPDstroke *stroke_a = NULL; | bGPDstroke *stroke_a = NULL; | ||||
| bGPDstroke *stroke_b = NULL; | bGPDstroke *stroke_b = NULL; | ||||
| Context not available. | |||||
| if (new_stroke == NULL) { | if (new_stroke == NULL) { | ||||
| new_stroke = MEM_dupallocN(stroke_a); | new_stroke = MEM_dupallocN(stroke_a); | ||||
| new_stroke->points = MEM_dupallocN(stroke_a->points); | new_stroke->points = MEM_dupallocN(stroke_a->points); | ||||
| BKE_gpencil_stroke_weights_duplicate(stroke_a, new_stroke); | |||||
| new_stroke->triangles = NULL; | new_stroke->triangles = NULL; | ||||
| new_stroke->tot_triangles = 0; | new_stroke->tot_triangles = 0; | ||||
| new_stroke->flag |= GP_STROKE_RECALC_CACHES; | new_stroke->flag |= GP_STROKE_RECALC_CACHES; | ||||
| Context not available. | |||||
| if (type == GP_STROKE_JOINCOPY) { | if (type == GP_STROKE_JOINCOPY) { | ||||
| new_stroke->palcolor = palcolor; | new_stroke->palcolor = palcolor; | ||||
| BLI_strncpy(new_stroke->colorname, palcolor->info, sizeof(new_stroke->colorname)); | BLI_strncpy(new_stroke->colorname, palcolor->info, sizeof(new_stroke->colorname)); | ||||
| new_stroke->flag |= GP_STROKE_RECALC_COLOR; | |||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| /* notifiers */ | /* notifiers */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| /* ***************** Reproject Strokes ********************** */ | /* ***************** Reproject Strokes ********************** */ | ||||
| typedef enum eGP_ReprojectModes { | typedef enum eGP_ReprojectModes { | ||||
| /* Axis (equal to lock axis) */ | |||||
| GP_REPROJECT_AXIS = 0, | |||||
| /* On same plane, parallel to viewplane */ | /* On same plane, parallel to viewplane */ | ||||
| GP_REPROJECT_PLANAR = 0, | GP_REPROJECT_PLANAR, | ||||
| /* Reprojected on to the scene geometry */ | /* Reprojected on to the scene geometry */ | ||||
| GP_REPROJECT_SURFACE, | GP_REPROJECT_SURFACE, | ||||
| } eGP_ReprojectModes; | } eGP_ReprojectModes; | ||||
| static int 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) | static int gp_strokes_reproject_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| ToolSettings *ts = CTX_data_tool_settings(C); | |||||
| Object *ob = CTX_data_active_object(C); | |||||
| ScrArea *sa = CTX_wm_area(C); | |||||
| ARegion *ar = CTX_wm_region(C); | |||||
| RegionView3D *rv3d = ar->regiondata; | |||||
| View3D *v3d = sa->spacedata.first; | |||||
| GP_SpaceConversion gsc = {NULL}; | GP_SpaceConversion gsc = {NULL}; | ||||
| eGP_ReprojectModes mode = RNA_boolean_get(op->ptr, "type"); | eGP_ReprojectModes mode = RNA_enum_get(op->ptr, "type"); | ||||
| int lock_axis = ts->gp_sculpt.lock_axis; | |||||
| float origin[3]; | |||||
| if ((mode == GP_REPROJECT_AXIS) && (lock_axis == GP_LOCKAXIS_NONE)) { | |||||
| BKE_report(op->reports, RPT_ERROR, "To reproject by axis, a lock axis must be set before"); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| /* init space conversion stuff */ | /* init space conversion stuff */ | ||||
| gp_point_conversion_init(C, &gsc); | gp_point_conversion_init(C, &gsc); | ||||
| /* init autodist for geometry projection */ | /* init autodist for geometry projection */ | ||||
| if (mode == GP_REPROJECT_SURFACE) { | if (mode == GP_REPROJECT_SURFACE) { | ||||
| EvaluationContext eval_ctx; | EvaluationContext eval_ctx; | ||||
| Context not available. | |||||
| /* Compute inverse matrix for unapplying parenting once instead of doing per-point */ | /* Compute inverse matrix for unapplying parenting once instead of doing per-point */ | ||||
| /* TODO: add this bit to the iteration macro? */ | /* 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, diff_mat); | |||||
| } | |||||
| /* Adjust each point */ | /* Adjust each point */ | ||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| float xy[2]; | float xy[2]; | ||||
| Context not available. | |||||
| * coordinates, resulting in lost precision, which in turn causes stairstepping | * coordinates, resulting in lost precision, which in turn causes stairstepping | ||||
| * artifacts in the final points. | * artifacts in the final points. | ||||
| */ | */ | ||||
| if (gpl->parent == NULL) { | bGPDspoint pt2; | ||||
| gp_point_to_xy_fl(&gsc, gps, pt, &xy[0], &xy[1]); | gp_point_to_parent_space(pt, diff_mat, &pt2); | ||||
| } | gp_point_to_xy_fl(&gsc, gps, &pt2, &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]); | |||||
| } | |||||
| /* Project stroke in the axis locked */ | |||||
| if (mode == GP_REPROJECT_AXIS) { | |||||
| if (lock_axis > GP_LOCKAXIS_NONE) { | |||||
| ED_gp_get_drawing_reference(v3d, scene, ob, gpl, | |||||
| ts->gpencil_v3d_align, origin); | |||||
| ED_gp_project_point_to_plane(ob, rv3d, origin, | |||||
| lock_axis - 1, ts->gpencil_src, &pt2); | |||||
| copy_v3_v3(&pt->x, &pt2.x); | |||||
| /* apply parent again */ | |||||
| gp_apply_parent_point(ob, gpd, gpl, pt); | |||||
| } | |||||
| } | |||||
| /* Project screenspace back to 3D space (from current perspective) | /* Project screenspace back to 3D space (from current perspective) | ||||
| * so that all points have been treated the same way | * so that all points have been treated the same way | ||||
| */ | */ | ||||
| if (mode == GP_REPROJECT_PLANAR) { | else if (mode == GP_REPROJECT_PLANAR) { | ||||
| /* Planar - All on same plane parallel to the viewplane */ | /* Planar - All on same plane parallel to the viewplane */ | ||||
| gp_point_xy_to_3d(&gsc, scene, xy, &pt->x); | gp_point_xy_to_3d(&gsc, scene, xy, &pt->x); | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| /* Unapply parent corrections */ | /* Unapply parent corrections */ | ||||
| if (gpl->parent) { | if (mode != GP_REPROJECT_AXIS) { | ||||
| mul_m4_v3(inverse_diff_mat, &pt->x); | mul_m4_v3(inverse_diff_mat, &pt->x); | ||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| GP_EDITABLE_STROKES_END; | GP_EDITABLE_STROKES_END; | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| Context not available. | |||||
| void GPENCIL_OT_reproject(wmOperatorType *ot) | void GPENCIL_OT_reproject(wmOperatorType *ot) | ||||
| { | { | ||||
| static const EnumPropertyItem reproject_type[] = { | static const EnumPropertyItem reproject_type[] = { | ||||
| {GP_REPROJECT_PLANAR, "PLANAR", 0, "Planar", | { GP_REPROJECT_AXIS, "AXIS", 0, "Axis", | ||||
| "Reproject the strokes using the current lock axis configuration. This is the same projection using while" | |||||
| "drawing new strokes" }, | |||||
| {GP_REPROJECT_PLANAR, "PLANAR", 0, "Planar", | |||||
| "Reproject the strokes to end up on the same plane, as if drawn from the current viewpoint " | "Reproject the strokes to end up on the same plane, as if drawn from the current viewpoint " | ||||
| "using 'Cursor' Stroke Placement"}, | "using 'Cursor' Stroke Placement"}, | ||||
| {GP_REPROJECT_SURFACE, "SURFACE", 0, "Surface", | {GP_REPROJECT_SURFACE, "SURFACE", 0, "Surface", | ||||
| Context not available. | |||||
| /* callbacks */ | /* callbacks */ | ||||
| ot->invoke = WM_menu_invoke; | ot->invoke = WM_menu_invoke; | ||||
| ot->exec = gp_strokes_reproject_exec; | ot->exec = gp_strokes_reproject_exec; | ||||
| ot->poll = gp_strokes_reproject_poll; | ot->poll = gp_strokes_edit3d_poll; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| Context not available. | |||||
| if (pt->flag & GP_SPOINT_SELECT) { | if (pt->flag & GP_SPOINT_SELECT) { | ||||
| if (i + 1 < gps->totpoints) { | if (i + 1 < gps->totpoints) { | ||||
| if (gps->points[i + 1].flag & GP_SPOINT_SELECT) { | if (gps->points[i + 1].flag & GP_SPOINT_SELECT) { | ||||
| ++totnewpoints; | totnewpoints++; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| return totnewpoints; | return totnewpoints; | ||||
| } | } | ||||
| static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) | static int gp_stroke_subdivide_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| Context not available. | |||||
| pt_final->strength = pt->strength; | pt_final->strength = pt->strength; | ||||
| pt_final->time = pt->time; | pt_final->time = pt->time; | ||||
| pt_final->flag = pt->flag; | pt_final->flag = pt->flag; | ||||
| ++i2; | pt_final->totweight = 0; | ||||
| pt_final->weights = NULL; | |||||
| i2++; | |||||
| /* if next point is selected add a half way point */ | /* if next point is selected add a half way point */ | ||||
| if (pt->flag & GP_SPOINT_SELECT) { | if (pt->flag & GP_SPOINT_SELECT) { | ||||
| Context not available. | |||||
| CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); | CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); | ||||
| pt_final->time = interpf(pt->time, next->time, 0.5f); | pt_final->time = interpf(pt->time, next->time, 0.5f); | ||||
| pt_final->flag |= GP_SPOINT_SELECT; | pt_final->flag |= GP_SPOINT_SELECT; | ||||
| ++i2; | pt_final->totweight = 0; | ||||
| pt_final->weights = NULL; | |||||
| i2++; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| GP_EDITABLE_STROKES_END; | GP_EDITABLE_STROKES_END; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| Context not available. | |||||
| ot->poll = gp_active_layer_poll; | ot->poll = gp_active_layer_poll; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| /* properties */ | /* properties */ | ||||
| prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 10, "Number of Cuts", "", 1, 5); | prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 10, "Number of Cuts", "", 1, 5); | ||||
| Context not available. | |||||
| RNA_def_property_flag(prop, PROP_SKIP_SAVE); | RNA_def_property_flag(prop, PROP_SKIP_SAVE); | ||||
| } | } | ||||
| /* ** 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(C, gpl, gps) | |||||
| { | |||||
| if (gps->flag & GP_STROKE_SELECT) { | |||||
| /* simplify stroke using Ramer-Douglas-Peucker algorithm */ | |||||
| BKE_gpencil_simplify_stroke(gpl, gps, factor); | |||||
| } | |||||
| } | |||||
| GP_EDITABLE_STROKES_END; | |||||
| /* notifiers */ | |||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| 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 algorith *** */ | |||||
| 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(C, gpl, gps) | |||||
| { | |||||
| if (gps->flag & GP_STROKE_SELECT) { | |||||
| for (int i = 0; i < steps; i++) { | |||||
| BKE_gpencil_simplify_fixed(gpl, gps); | |||||
| } | |||||
| } | |||||
| } | |||||
| GP_EDITABLE_STROKES_END; | |||||
| /* notifiers */ | |||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| 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_dst = NULL; | |||||
| bGPdata *gpd_dst = NULL; | |||||
| bGPDlayer *gpl_dst = NULL; | |||||
| bGPDframe *gpf_dst = NULL; | |||||
| bGPDspoint *pt; | |||||
| int i; | |||||
| eGP_SeparateModes mode = RNA_enum_get(op->ptr, "mode"); | |||||
| /* sanity checks */ | |||||
| if (ELEM(NULL, gpd_src)) { | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| 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; | |||||
| /* create new grease pencil datablock and copy paletteslots */ | |||||
| // XXX: check usercounts | |||||
| gpd_dst = BKE_gpencil_data_addnew(bmain, "GPencil"); | |||||
| ob_dst->data = (bGPdata *)gpd_dst; | |||||
| BKE_gpencil_copy_palette_data(gpd_dst, gpd_src); | |||||
| /* 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(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); | |||||
| } | |||||
| /* selected points mode */ | |||||
| if (mode == GP_SEPARATE_POINT) { | |||||
| /* make copy of source stroke */ | |||||
| bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps); | |||||
| /* 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); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /* 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); | |||||
| } | |||||
| } | |||||
| BKE_gpencil_batch_cache_dirty(gpd_src); | |||||
| BKE_gpencil_batch_cache_dirty(gpd_dst); | |||||
| 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)) | |||||
| { | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| bGPDspoint *pt; | |||||
| int i; | |||||
| /* sanity checks */ | |||||
| if (ELEM(NULL, gpd)) { | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| 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(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; | |||||
| BKE_gpencil_batch_cache_dirty(gpd); | |||||
| 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; | |||||
| } | |||||
| Context not available. | |||||