Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/gpencil/gpencil_edit.c
| Show First 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | |||||
| #include "BLI_ghash.h" | #include "BLI_ghash.h" | ||||
| #include "BLI_math.h" | #include "BLI_math.h" | ||||
| #include "BLI_string.h" | #include "BLI_string.h" | ||||
| #include "BLI_string_utils.h" | #include "BLI_string_utils.h" | ||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "BLT_translation.h" | #include "BLT_translation.h" | ||||
| #include "DNA_meshdata_types.h" | |||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_scene_types.h" | #include "DNA_scene_types.h" | ||||
| #include "DNA_screen_types.h" | #include "DNA_screen_types.h" | ||||
| #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 "BKE_main.h" | |||||
| #include "BKE_context.h" | #include "BKE_context.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_main.h" | #include "BKE_main.h" | ||||
| #include "BKE_material.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" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "WM_types.h" | #include "WM_types.h" | ||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "RNA_define.h" | #include "RNA_define.h" | ||||
| #include "RNA_enum_types.h" | #include "RNA_enum_types.h" | ||||
| #include "UI_view2d.h" | #include "UI_view2d.h" | ||||
| #include "ED_gpencil.h" | #include "ED_gpencil.h" | ||||
| #include "ED_object.h" | #include "ED_object.h" | ||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "ED_view3d.h" | #include "ED_view3d.h" | ||||
| #include "ED_space_api.h" | #include "ED_space_api.h" | ||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_build.h" | |||||
| #include "DEG_depsgraph_query.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_boolean_get(op->ptr, "back"); | |||||
| Depsgraph *depsgraph = CTX_data_depsgraph(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) { | ||||
| BKE_report(op->reports, RPT_ERROR, "No active GP data"); | |||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | |||||
| /* Just toggle editmode flag... */ | /* Just toggle editmode flag... */ | ||||
| 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(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, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| void GPENCIL_OT_editmode_toggle(wmOperatorType *ot) | void GPENCIL_OT_editmode_toggle(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Strokes Edit Mode Toggle"; | ot->name = "Strokes Edit Mode Toggle"; | ||||
| ot->idname = "GPENCIL_OT_editmode_toggle"; | ot->idname = "GPENCIL_OT_editmode_toggle"; | ||||
| ot->description = "Enter/Exit edit mode for Grease Pencil strokes"; | ot->description = "Enter/Exit edit mode for Grease Pencil strokes"; | ||||
| /* callbacks */ | /* callbacks */ | ||||
| ot->exec = gpencil_editmode_toggle_exec; | ot->exec = gpencil_editmode_toggle_exec; | ||||
| ot->poll = gpencil_editmode_toggle_poll; | ot->poll = gpencil_editmode_toggle_poll; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | ||||
| /* properties */ | |||||
| RNA_def_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); | |||||
| } | |||||
| /* 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 bool back = RNA_boolean_get(op->ptr, "back"); | |||||
| 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 ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0) && (back == 1)) { | |||||
| mode = ob->restore_mode; | |||||
| } | |||||
| ob->restore_mode = ob->mode; | |||||
| ob->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 */ | |||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); | |||||
| } | |||||
| /* 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 bool back = RNA_boolean_get(op->ptr, "back"); | |||||
| 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, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); | |||||
| } | |||||
| /* 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 bool back = RNA_boolean_get(op->ptr, "back"); | |||||
| 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, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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_boolean(ot->srna, "back", 0, "Return to Previous Mode", "Return to previous mode"); | |||||
| } | } | ||||
| /* ************************************************ */ | /* ************************************************ */ | ||||
| /* Stroke Editing Operators */ | /* Stroke Editing Operators */ | ||||
| /* poll callback for all stroke editing operators */ | /* poll callback for all stroke editing operators */ | ||||
| static int gp_stroke_edit_poll(bContext *C) | static int gp_stroke_edit_poll(bContext *C) | ||||
| { | { | ||||
| /* NOTE: this is a bit slower, but is the most accurate... */ | /* NOTE: this is a bit slower, but is the most accurate... */ | ||||
| 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)) | ||||
| { | { | ||||
| ToolSettings *ts = CTX_data_tool_settings(C); | ToolSettings *ts = CTX_data_tool_settings(C); | ||||
| if (ts == NULL) | if (ts == NULL) | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| Show All 23 Lines | void GPENCIL_OT_selection_opacity_toggle(wmOperatorType *ot) | ||||
| /* callbacks */ | /* callbacks */ | ||||
| ot->exec = gpencil_hideselect_toggle_exec; | ot->exec = gpencil_hideselect_toggle_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; | ||||
| } | } | ||||
| /* toggle multi edit strokes support */ | |||||
| static int gpencil_multiedit_toggle_exec(bContext *C, wmOperator *op) | |||||
| { | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| const bool lines = RNA_boolean_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_boolean(ot->srna, "toggle_visibility", 0, "Toggle Visibility Only", "Toggle visibility of edit lines only"); | |||||
| } | |||||
| /* ************** 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 */ | ||||
| static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes, const char *layername) | static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes, const char *layername) | ||||
| { | { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| int i; | int i; | ||||
| Show All 28 Lines | else { | ||||
| //printf("copying from %d to %d = %d\n", start_idx, i, len); | //printf("copying from %d to %d = %d\n", start_idx, i, len); | ||||
| /* make copies of the relevant data */ | /* make copies of the relevant data */ | ||||
| if (len) { | if (len) { | ||||
| bGPDstroke *gpsd; | bGPDstroke *gpsd; | ||||
| /* make a stupid copy first of the entire stroke (to get the flags too) */ | /* make a stupid copy first of the entire stroke (to get the flags too) */ | ||||
| gpsd = MEM_dupallocN(gps); | 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 */ | /* initialize triangle memory - will be calculated on next redraw */ | ||||
| gpsd->triangles = NULL; | gpsd->triangles = NULL; | ||||
| gpsd->flag |= GP_STROKE_RECALC_CACHES; | gpsd->flag |= GP_STROKE_RECALC_CACHES; | ||||
| gpsd->tot_triangles = 0; | gpsd->tot_triangles = 0; | ||||
| /* now, make a new points array, and copy of the relevant parts */ | /* now, make a new points array, and copy of the relevant parts */ | ||||
| gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy"); | gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy"); | ||||
| gpsd->dvert = MEM_callocN(sizeof(MDeformVert) * len, "gps stroke weights copy"); | |||||
| memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len); | memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len); | ||||
| memcpy(gpsd->dvert, gps->dvert + start_idx, sizeof(MDeformVert) * len); | |||||
| gpsd->totpoints = len; | gpsd->totpoints = 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 */ | /* add to temp buffer */ | ||||
| gpsd->next = gpsd->prev = NULL; | gpsd->next = gpsd->prev = NULL; | ||||
| BLI_addtail(new_strokes, gpsd); | BLI_addtail(new_strokes, gpsd); | ||||
| /* cleanup + reset for next */ | /* cleanup + reset for next */ | ||||
| start_idx = -1; | start_idx = -1; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| 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 | ||||
| * once all done | * once all done | ||||
| */ | */ | ||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| { | { | ||||
| ListBase new_strokes = {NULL, NULL}; | ListBase new_strokes = {NULL, NULL}; | ||||
| bGPDframe *gpf = gpl->actframe; | bGPDframe *gpf = gpl->actframe; | ||||
| Show All 11 Lines | for (gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| if (gps->flag & GP_STROKE_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| if (gps->totpoints == 1) { | if (gps->totpoints == 1) { | ||||
| /* Special Case: If there's just a single point in this stroke... */ | /* Special Case: If there's just a single point in this stroke... */ | ||||
| bGPDstroke *gpsd; | bGPDstroke *gpsd; | ||||
| /* make direct copies of the stroke and its points */ | /* make direct copies of the stroke and its points */ | ||||
| gpsd = MEM_dupallocN(gps); | 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); | gpsd->points = MEM_dupallocN(gps->points); | ||||
| gpsd->dvert = MEM_dupallocN(gps->dvert); | |||||
| 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; | ||||
| gpsd->triangles = NULL; | gpsd->triangles = NULL; | ||||
| /* 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); | ||||
| Show All 12 Lines | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| /* add all new strokes in temp buffer to the frame (preventing double-copies) */ | /* add all new strokes in temp buffer to the frame (preventing double-copies) */ | ||||
| BLI_movelisttolist(&gpf->strokes, &new_strokes); | BLI_movelisttolist(&gpf->strokes, &new_strokes); | ||||
| BLI_assert(new_strokes.first == NULL); | BLI_assert(new_strokes.first == NULL); | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* updates */ | /* updates */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_duplicate(wmOperatorType *ot) | void GPENCIL_OT_duplicate(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| Show All 17 Lines | |||||
| * to the active layer. This effectively flattens down the strokes | * to the active layer. This effectively flattens down the strokes | ||||
| * from several different layers into a single layer. | * from several different layers into a single layer. | ||||
| */ | */ | ||||
| /* list of bGPDstroke instances */ | /* list of bGPDstroke instances */ | ||||
| /* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */ | /* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */ | ||||
| ListBase gp_strokes_copypastebuf = {NULL, NULL}; | 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, | * 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). | * 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 = NULL; | ||||
| /* Free copy/paste buffer data */ | /* Free copy/paste buffer data */ | ||||
| void ED_gpencil_strokes_copybuf_free(void) | void ED_gpencil_strokes_copybuf_free(void) | ||||
| { | { | ||||
| bGPDstroke *gps, *gpsn; | bGPDstroke *gps, *gpsn; | ||||
| /* Free the palettes buffer | /* Free the colors buffer | ||||
| * NOTE: This is done before the strokes so that the name ptrs (keys) are still safe | * NOTE: This is done before the strokes so that the ptrs are still safe | ||||
| */ | */ | ||||
| if (gp_strokes_copypastebuf_colors) { | if (gp_strokes_copypastebuf_colors) { | ||||
| BLI_ghash_free(gp_strokes_copypastebuf_colors, NULL, MEM_freeN); | BLI_ghash_free(gp_strokes_copypastebuf_colors, NULL, NULL); | ||||
| gp_strokes_copypastebuf_colors = NULL; | gp_strokes_copypastebuf_colors = NULL; | ||||
| } | } | ||||
| /* Free the stroke buffer */ | /* Free the stroke buffer */ | ||||
| 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); | 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); | BLI_freelinkN(&gp_strokes_copypastebuf, gps); | ||||
| } | } | ||||
| gp_strokes_copypastebuf.first = gp_strokes_copypastebuf.last = NULL; | gp_strokes_copypastebuf.first = gp_strokes_copypastebuf.last = NULL; | ||||
| } | } | ||||
| /* 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) | ||||
| { | { | ||||
| Main *bmain = CTX_data_main(C); | |||||
| Object *ob = CTX_data_active_object(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; | ||||
| /* If there's no active palette yet (i.e. new datablock), add one */ | /* For each color, check if exist and add if not */ | ||||
| bGPDpalette *palette = BKE_gpencil_palette_getactive(gpd); | |||||
| if (palette == NULL) { | |||||
| palette = BKE_gpencil_palette_addnew(gpd, "Pasted Palette", true); | |||||
| } | |||||
| /* 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; | |||||
| 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); | int *key = BLI_ghashIterator_getKey(&gh_iter); | ||||
| BLI_addtail(&palette->colors, palcolor); | Material *ma = BLI_ghashIterator_getValue(&gh_iter); | ||||
| BLI_uniquename(&palette->colors, palcolor, DATA_("GP Color"), '.', offsetof(bGPDpalettecolor, info), sizeof(palcolor->info)); | if (BKE_object_material_slot_find_index(ob, ma) == 0) { | ||||
| BKE_object_material_slot_add(bmain, ob); | |||||
| assign_material(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_EXISTING); | |||||
| } | } | ||||
| /* Store this mapping (for use later when pasting) */ | /* Store this mapping (for use later when pasting) */ | ||||
| BLI_ghash_insert(new_colors, name, palcolor); | BLI_ghash_insert(new_colors, key, ma); | ||||
| } | } | ||||
| return new_colors; | return new_colors; | ||||
| } | } | ||||
| /* --------------------- */ | /* --------------------- */ | ||||
| /* Copy selected strokes */ | /* Copy selected strokes */ | ||||
| static int gp_strokes_copy_exec(bContext *C, wmOperator *op) | static int gp_strokes_copy_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| Object *ob = CTX_data_active_object(C); | |||||
| 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; | |||||
| } | |||||
| /* clear the buffer first */ | /* clear the buffer first */ | ||||
| ED_gpencil_strokes_copybuf_free(); | ED_gpencil_strokes_copybuf_free(); | ||||
| /* 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 | ||||
| * once all done | * once all done | ||||
| */ | */ | ||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| Show All 12 Lines | for (gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| if (gps->flag & GP_STROKE_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| if (gps->totpoints == 1) { | if (gps->totpoints == 1) { | ||||
| /* Special Case: If there's just a single point in this stroke... */ | /* Special Case: If there's just a single point in this stroke... */ | ||||
| bGPDstroke *gpsd; | bGPDstroke *gpsd; | ||||
| /* make direct copies of the stroke and its points */ | /* make direct copies of the stroke and its points */ | ||||
| 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->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); /* saves original layer name */ | ||||
| gpsd->points = MEM_dupallocN(gps->points); | gpsd->points = MEM_dupallocN(gps->points); | ||||
| gpsd->dvert = MEM_dupallocN(gps->dvert); | |||||
| 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; | ||||
| gpsd->triangles = NULL; | gpsd->triangles = NULL; | ||||
| /* add to temp buffer */ | /* add to temp buffer */ | ||||
| gpsd->next = gpsd->prev = NULL; | gpsd->next = gpsd->prev = NULL; | ||||
| BLI_addtail(&gp_strokes_copypastebuf, gpsd); | BLI_addtail(&gp_strokes_copypastebuf, gpsd); | ||||
| } | } | ||||
| else { | else { | ||||
| /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ | /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ | ||||
| gp_duplicate_points(gps, &gp_strokes_copypastebuf, gpl->info); | gp_duplicate_points(gps, &gp_strokes_copypastebuf, gpl->info); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | 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) { | if (gp_strokes_copypastebuf.first) { | ||||
| gp_strokes_copypastebuf_colors = BLI_ghash_str_new("GPencil CopyBuf Colors"); | gp_strokes_copypastebuf_colors = BLI_ghash_str_new("GPencil CopyBuf Colors"); | ||||
| Material *ma = NULL; | |||||
| 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) { | ma = give_current_material(ob, gps->mat_nr + 1); | ||||
| bGPDpalettecolor *color = MEM_dupallocN(gps->palcolor); | if (BLI_ghash_haskey(gp_strokes_copypastebuf_colors, &gps->mat_nr) == false) | ||||
| { | |||||
| BLI_ghash_insert(gp_strokes_copypastebuf_colors, gps->colorname, color); | BLI_ghash_insert(gp_strokes_copypastebuf_colors, &gps->mat_nr, ma); | ||||
| gps->palcolor = color; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* updates (to ensure operator buttons are refreshed, when used via hotkeys) */ | /* updates (to ensure operator buttons are refreshed, when used via hotkeys) */ | ||||
| WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL); // XXX? | WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL); // XXX? | ||||
| Show All 31 Lines | |||||
| typedef enum eGP_PasteMode { | typedef enum eGP_PasteMode { | ||||
| GP_COPY_ONLY = -1, | GP_COPY_ONLY = -1, | ||||
| GP_COPY_MERGE = 1 | GP_COPY_MERGE = 1 | ||||
| } eGP_PasteMode; | } eGP_PasteMode; | ||||
| static int gp_strokes_paste_exec(bContext *C, wmOperator *op) | 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); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); /* only use active for copy merge */ | 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; | bGPDframe *gpf; | ||||
| eGP_PasteMode type = RNA_enum_get(op->ptr, "type"); | eGP_PasteMode type = RNA_enum_get(op->ptr, "type"); | ||||
| GHash *new_colors; | GHash *new_colors; | ||||
| /* check for various error conditions */ | /* check for various error conditions */ | ||||
| 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; | ||||
| } | } | ||||
| 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; | ||||
| } | } | ||||
| else if (gpl == NULL) { | else if (gpl == NULL) { | ||||
| /* no active layer - let's just create one */ | /* no active layer - let's just create one */ | ||||
| gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); | gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); | ||||
| } | } | ||||
| Show All 36 Lines | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| pt->flag &= ~GP_SPOINT_SELECT; | pt->flag &= ~GP_SPOINT_SELECT; | ||||
| } | } | ||||
| gps->flag &= ~GP_STROKE_SELECT; | gps->flag &= ~GP_STROKE_SELECT; | ||||
| } | } | ||||
| 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) { | ||||
| if (ED_gpencil_stroke_can_use(C, gps)) { | if (ED_gpencil_stroke_can_use(C, gps)) { | ||||
| /* Need to verify if layer exists */ | /* Need to verify if layer exists */ | ||||
| if (type != GP_COPY_MERGE) { | 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) { | if (gpl == NULL) { | ||||
| /* no layer - use active (only if layer deleted before paste) */ | /* no layer - use active (only if layer deleted before paste) */ | ||||
| gpl = CTX_data_active_gpencil_layer(C); | gpl = CTX_data_active_gpencil_layer(C); | ||||
| } | } | ||||
| } | } | ||||
| /* Ensure we have a frame to draw into | /* Ensure we have a frame to draw into | ||||
| * NOTE: Since this is an op which creates strokes, | * NOTE: Since this is an op which creates strokes, | ||||
| * we are obliged to add a new frame if one | * we are obliged to add a new frame if one | ||||
| * doesn't exist already | * doesn't exist already | ||||
| */ | */ | ||||
| gpf = BKE_gpencil_layer_getframe(gpl, CFRA, true); | gpf = BKE_gpencil_layer_getframe(gpl, cfra_eval, true); | ||||
| if (gpf) { | if (gpf) { | ||||
| /* Create new stroke */ | /* Create new stroke */ | ||||
| bGPDstroke *new_stroke = MEM_dupallocN(gps); | 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); | new_stroke->points = MEM_dupallocN(gps->points); | ||||
| new_stroke->dvert = MEM_dupallocN(gps->dvert); | |||||
| 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; | ||||
| new_stroke->next = new_stroke->prev = NULL; | new_stroke->next = new_stroke->prev = NULL; | ||||
| BLI_addtail(&gpf->strokes, new_stroke); | BLI_addtail(&gpf->strokes, new_stroke); | ||||
| /* Fix color references */ | /* Remap material */ | ||||
| BLI_assert(new_stroke->colorname[0] != '\0'); | Material *ma = BLI_ghash_lookup(new_colors, &new_stroke->mat_nr); | ||||
| new_stroke->palcolor = BLI_ghash_lookup(new_colors, new_stroke->colorname); | if ((ma) && (BKE_object_material_slot_find_index(ob, ma) > 0)) { | ||||
| gps->mat_nr = BKE_object_material_slot_find_index(ob, ma) - 1; | |||||
| BLI_assert(new_stroke->palcolor != NULL); | CLAMP_MIN(gps->mat_nr, 0); | ||||
| BLI_strncpy(new_stroke->colorname, new_stroke->palcolor->info, sizeof(new_stroke->colorname)); | } | ||||
| else { | |||||
| gps->mat_nr = 0; /* only if the color is not found */ | |||||
| } | |||||
| /*new_stroke->flag |= GP_STROKE_RECALC_COLOR; */ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* free temp data */ | /* free temp data */ | ||||
| BLI_ghash_free(new_colors, NULL, NULL); | BLI_ghash_free(new_colors, NULL, NULL); | ||||
| /* updates */ | /* updates */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_paste(wmOperatorType *ot) | void GPENCIL_OT_paste(wmOperatorType *ot) | ||||
| { | { | ||||
| static const EnumPropertyItem copy_type[] = { | static const EnumPropertyItem copy_type[] = { | ||||
| Show All 33 Lines | static int gp_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt)) | ||||
| return OPERATOR_INTERFACE; | return OPERATOR_INTERFACE; | ||||
| } | } | ||||
| // FIXME: allow moving partial strokes | // FIXME: allow moving partial strokes | ||||
| static int gp_move_to_layer_exec(bContext *C, wmOperator *op) | static int gp_move_to_layer_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| bGPdata *gpd = CTX_data_gpencil_data(C); | 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; | 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 */ | ||||
| target_layer = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); | target_layer = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); | ||||
| } | } | ||||
| else { | else { | ||||
| /* Try to get layer */ | /* Try to get layer */ | ||||
| target_layer = BLI_findlink(&gpd->layers, layer_num); | target_layer = BLI_findlink(&gpd->layers, layer_num); | ||||
| Show All 31 Lines | for (gps = gpf->strokes.first; gps; gps = gpsn) { | ||||
| BLI_addtail(&strokes, gps); | BLI_addtail(&strokes, gps); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* Paste them all in one go */ | /* Paste them all in one go */ | ||||
| if (strokes.first) { | if (strokes.first) { | ||||
| Scene *scene = CTX_data_scene(C); | bGPDframe *gpf = BKE_gpencil_layer_getframe(target_layer, cfra_eval, true); | ||||
| bGPDframe *gpf = BKE_gpencil_layer_getframe(target_layer, CFRA, true); | |||||
| BLI_movelisttolist(&gpf->strokes, &strokes); | BLI_movelisttolist(&gpf->strokes, &strokes); | ||||
| BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL)); | BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL)); | ||||
| } | } | ||||
| /* updates */ | /* updates */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_move_to_layer(wmOperatorType *ot) | void GPENCIL_OT_move_to_layer(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| Show All 32 Lines | else { | ||||
| CTX_wm_operator_poll_msg_set(C, "Active region not set"); | CTX_wm_operator_poll_msg_set(C, "Active region not set"); | ||||
| } | } | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| static int gp_blank_frame_add_exec(bContext *C, wmOperator *op) | 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); | 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); | bGPDlayer *active_gpl = BKE_gpencil_layer_getactive(gpd); | ||||
| const bool all_layers = RNA_boolean_get(op->ptr, "all_layers"); | const bool all_layers = RNA_boolean_get(op->ptr, "all_layers"); | ||||
| /* Initialise datablock and an active layer if nothing exists yet */ | /* Initialise datablock and an active layer if nothing exists yet */ | ||||
| if (ELEM(NULL, gpd, active_gpl)) { | if (ELEM(NULL, gpd, active_gpl)) { | ||||
| /* let's just be lazy, and call the "Add New Layer" operator, which sets everything up as required */ | /* let's just be lazy, and call the "Add New Layer" operator, which sets everything up as required */ | ||||
| WM_operator_name_call(C, "GPENCIL_OT_layer_add", WM_OP_EXEC_DEFAULT, NULL); | WM_operator_name_call(C, "GPENCIL_OT_layer_add", WM_OP_EXEC_DEFAULT, NULL); | ||||
| } | } | ||||
| /* Go through each layer, adding a frame after the active one | /* Go through each layer, adding a frame after the active one | ||||
| * and/or shunting all the others out of the way | * and/or shunting all the others out of the way | ||||
| */ | */ | ||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| { | { | ||||
| if ((all_layers == false) && (gpl != active_gpl)) { | if ((all_layers == false) && (gpl != active_gpl)) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| /* 1) Check for an existing frame on the current frame */ | /* 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) { | if (gpf) { | ||||
| /* Shunt all frames after (and including) the existing one later by 1-frame */ | /* Shunt all frames after (and including) the existing one later by 1-frame */ | ||||
| for (; gpf; gpf = gpf->next) { | for (; gpf; gpf = gpf->next) { | ||||
| gpf->framenum += 1; | gpf->framenum += 1; | ||||
| } | } | ||||
| } | } | ||||
| /* 2) Now add a new frame, with nothing in it */ | /* 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; | CTX_DATA_END; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_blank_frame_add(wmOperatorType *ot) | void GPENCIL_OT_blank_frame_add(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| Show All 20 Lines | static int gp_actframe_delete_poll(bContext *C) | ||||
| /* only if there's an active layer with an active frame */ | /* only if there's an active layer with an active frame */ | ||||
| return (gpl && gpl->actframe); | return (gpl && gpl->actframe); | ||||
| } | } | ||||
| /* delete active frame - wrapper around API calls */ | /* delete active frame - wrapper around API calls */ | ||||
| static int gp_actframe_delete_exec(bContext *C, wmOperator *op) | static int gp_actframe_delete_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| Scene *scene = CTX_data_scene(C); | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); | 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, 0); | |||||
| /* if there's no existing Grease-Pencil data there, add some */ | /* if there's no existing Grease-Pencil data there, add some */ | ||||
| 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 (ELEM(NULL, gpl, gpf)) { | if (ELEM(NULL, gpl, gpf)) { | ||||
| BKE_report(op->reports, RPT_ERROR, "No active frame to delete"); | BKE_report(op->reports, RPT_ERROR, "No active frame to delete"); | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| /* delete it... */ | /* delete it... */ | ||||
| BKE_gpencil_layer_delframe(gpl, gpf); | BKE_gpencil_layer_delframe(gpl, gpf); | ||||
| /* notifiers */ | /* notifiers */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_active_frame_delete(wmOperatorType *ot) | void GPENCIL_OT_active_frame_delete(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| Show All 17 Lines | static int gp_actframe_delete_all_poll(bContext *C) | ||||
| /* 1) There must be grease pencil data | /* 1) There must be grease pencil data | ||||
| * 2) Hopefully some of the layers have stuff we can use | * 2) Hopefully some of the layers have stuff we can use | ||||
| */ | */ | ||||
| return (gpd && gpd->layers.first); | return (gpd && gpd->layers.first); | ||||
| } | } | ||||
| static int gp_actframe_delete_all_exec(bContext *C, wmOperator *op) | 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; | bool success = false; | ||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| { | { | ||||
| /* try to get the "active" frame - but only if it actually occurs on this frame */ | /* 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, 0); | ||||
| if (gpf == NULL) | if (gpf == NULL) | ||||
| continue; | continue; | ||||
| /* delete it... */ | /* delete it... */ | ||||
| BKE_gpencil_layer_delframe(gpl, gpf); | BKE_gpencil_layer_delframe(gpl, gpf); | ||||
| /* we successfully modified something */ | /* we successfully modified something */ | ||||
| success = true; | success = true; | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* updates */ | /* updates */ | ||||
| if (success) { | if (success) { | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| else { | else { | ||||
| BKE_report(op->reports, RPT_ERROR, "No active frame(s) to delete"); | BKE_report(op->reports, RPT_ERROR, "No active frame(s) to delete"); | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| } | } | ||||
| Show All 18 Lines | typedef enum eGP_DeleteMode { | ||||
| /* delete selected stroke points */ | /* delete selected stroke points */ | ||||
| GP_DELETEOP_POINTS = 0, | GP_DELETEOP_POINTS = 0, | ||||
| /* delete selected strokes */ | /* delete selected strokes */ | ||||
| GP_DELETEOP_STROKES = 1, | GP_DELETEOP_STROKES = 1, | ||||
| /* delete active frame */ | /* delete active frame */ | ||||
| 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; | ||||
| 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; | bGPDstroke *gps, *gpsn; | ||||
| if (gpf == NULL) | if (gpf == NULL) | ||||
| continue; | continue; | ||||
| /* simply delete strokes which are selected */ | /* simply delete strokes which are selected */ | ||||
| for (gps = gpf->strokes.first; gps; gps = gpsn) { | for (gps = gpf->strokes.first; gps; gps = gpsn) { | ||||
| gpsn = gps->next; | gpsn = gps->next; | ||||
| /* skip strokes that are invalid for current view */ | /* skip strokes that are invalid for current view */ | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | if (ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| continue; | continue; | ||||
| /* free stroke if selected */ | /* free stroke if selected */ | ||||
| if (gps->flag & GP_STROKE_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| /* free stroke memory arrays, then stroke itself */ | /* free stroke memory arrays, then stroke itself */ | ||||
| if (gps->points) MEM_freeN(gps->points); | if (gps->points) { | ||||
| if (gps->triangles) MEM_freeN(gps->triangles); | 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); | BLI_freelinkN(&gpf->strokes, gps); | ||||
| changed = true; | changed = true; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| } | |||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| if (changed) { | if (changed) { | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| else { | else { | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| } | } | ||||
| /* ----------------------------------- */ | /* ----------------------------------- */ | ||||
| /* 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) | ||||
| { | { | ||||
| Object *ob = CTX_data_active_object(C); | |||||
| 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; | ||||
| 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; | bGPDstroke *gps, *gpsn; | ||||
| if (gpf == NULL) | if (gpf == NULL) | ||||
| continue; | continue; | ||||
| /* simply delete points from selected strokes | /* simply delete points from selected strokes | ||||
| * NOTE: we may still have to remove the stroke if it ends up having no points! | * NOTE: we may still have to remove the stroke if it ends up having no points! | ||||
| */ | */ | ||||
| for (gps = gpf->strokes.first; gps; gps = gpsn) { | for (gps = gpf->strokes.first; gps; gps = gpsn) { | ||||
| gpsn = gps->next; | gpsn = gps->next; | ||||
| /* skip strokes that are invalid for current view */ | /* skip strokes that are invalid for current view */ | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | if (ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| continue; | continue; | ||||
| /* check if the color is editable */ | /* 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; | continue; | ||||
| /* the stroke must have at least one point selected for any operator */ | |||||
| if (gps->flag & GP_STROKE_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| MDeformVert *dvert = NULL; | |||||
| int i; | int i; | ||||
| int tot = gps->totpoints; /* number of points in new buffer */ | int tot = gps->totpoints; /* number of points in new buffer */ | ||||
| /* First Pass: Count how many points are selected (i.e. how many to remove) */ | /* 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++) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| if (pt->flag & GP_SPOINT_SELECT) { | if (pt->flag & GP_SPOINT_SELECT) { | ||||
| /* selected point - one of the points to remove */ | /* selected point - one of the points to remove */ | ||||
| tot--; | 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; | |||||
| } | |||||
| /* if no points are left, we simply delete the entire stroke */ | /* if no points are left, we simply delete the entire stroke */ | ||||
| if (tot <= 0) { | if (tot <= 0) { | ||||
| /* remove the entire stroke */ | /* remove the entire stroke */ | ||||
| if (gps->points) { | |||||
| MEM_freeN(gps->points); | MEM_freeN(gps->points); | ||||
| } | |||||
| if (gps->dvert) { | |||||
| BKE_gpencil_free_stroke_weights(gps); | |||||
| MEM_freeN(gps->dvert); | |||||
| } | |||||
| if (gps->triangles) { | if (gps->triangles) { | ||||
| MEM_freeN(gps->triangles); | MEM_freeN(gps->triangles); | ||||
| } | } | ||||
| BLI_freelinkN(&gpf->strokes, gps); | BLI_freelinkN(&gpf->strokes, gps); | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| } | } | ||||
| else { | else { | ||||
| /* just copy all unselected into a smaller buffer */ | /* just copy all points to keep into a smaller buffer */ | ||||
| bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); | bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); | ||||
| bGPDspoint *npt = new_points; | bGPDspoint *npt = new_points; | ||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | MDeformVert *new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy"); | ||||
| MDeformVert *ndvert = new_dvert; | |||||
| switch (mode) { | |||||
| case GP_DISSOLVE_POINTS: | |||||
| for (i = 0, pt = gps->points, dvert = gps->dvert; i < gps->totpoints; i++, pt++, dvert++) { | |||||
| if ((pt->flag & GP_SPOINT_SELECT) == 0) { | if ((pt->flag & GP_SPOINT_SELECT) == 0) { | ||||
| *npt = *pt; | *npt = *pt; | ||||
| *ndvert = *dvert; | |||||
| ndvert->dw = MEM_dupallocN(dvert->dw); | |||||
| npt++; | npt++; | ||||
| ndvert++; | |||||
| } | } | ||||
| } | } | ||||
| break; | |||||
| case GP_DISSOLVE_BETWEEN: | |||||
| /* copy first segment */ | |||||
| for (i = 0, pt = gps->points, dvert = gps->dvert; i < first; i++, pt++, dvert++) { | |||||
| *npt = *pt; | |||||
| *ndvert = *dvert; | |||||
| ndvert->dw = MEM_dupallocN(dvert->dw); | |||||
| npt++; | |||||
| ndvert++; | |||||
| } | |||||
| /* copy segment (selected points) */ | |||||
| for (i = first, pt = gps->points + first, dvert = gps->dvert + first; i < last; i++, pt++, dvert++) { | |||||
| if (pt->flag & GP_SPOINT_SELECT) { | |||||
| *npt = *pt; | |||||
| *ndvert = *dvert; | |||||
| ndvert->dw = MEM_dupallocN(dvert->dw); | |||||
| npt++; | |||||
| ndvert++; | |||||
| } | |||||
| } | |||||
| /* copy last segment */ | |||||
| for (i = last, pt = gps->points + last, dvert = gps->dvert + last; i < gps->totpoints; i++, pt++, dvert++) { | |||||
| *npt = *pt; | |||||
| *ndvert = *dvert; | |||||
| ndvert->dw = MEM_dupallocN(dvert->dw); | |||||
| npt++; | |||||
| ndvert++; | |||||
| } | |||||
| break; | |||||
| case GP_DISSOLVE_UNSELECT: | |||||
| /* copy any selected point */ | |||||
| for (i = 0, pt = gps->points, dvert = gps->dvert; i < gps->totpoints; i++, pt++, dvert++) { | |||||
| if (pt->flag & GP_SPOINT_SELECT) { | |||||
| *npt = *pt; | |||||
| *ndvert = *dvert; | |||||
| ndvert->dw = MEM_dupallocN(dvert->dw); | |||||
| npt++; | |||||
| ndvert++; | |||||
| } | |||||
| } | |||||
| break; | |||||
| } | |||||
| /* free the old buffer */ | /* free the old buffer */ | ||||
| if (gps->points) { | |||||
| MEM_freeN(gps->points); | MEM_freeN(gps->points); | ||||
| } | |||||
| if (gps->dvert) { | |||||
| BKE_gpencil_free_stroke_weights(gps); | |||||
| MEM_freeN(gps->dvert); | |||||
| } | |||||
| /* save the new buffer */ | /* save the new buffer */ | ||||
| gps->points = new_points; | gps->points = new_points; | ||||
| gps->dvert = new_dvert; | |||||
| gps->totpoints = tot; | gps->totpoints = tot; | ||||
| /* triangles cache needs to be recalculated */ | /* triangles cache needs to be recalculated */ | ||||
| gps->flag |= GP_STROKE_RECALC_CACHES; | gps->flag |= GP_STROKE_RECALC_CACHES; | ||||
| gps->tot_triangles = 0; | gps->tot_triangles = 0; | ||||
| /* deselect the stroke, since none of its selected points will still be selected */ | /* deselect the stroke, since none of its selected points will still be selected */ | ||||
| gps->flag &= ~GP_STROKE_SELECT; | 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; | CTX_DATA_END; | ||||
| if (changed) { | if (changed) { | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| else { | else { | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| } | } | ||||
| Show All 17 Lines | |||||
| * 1) We firstly identify the number of "islands" of non-tagged points | * 1) We firstly identify the number of "islands" of non-tagged points | ||||
| * which will all end up being in new strokes. | * which will all end up being in new strokes. | ||||
| * - In the most extreme case (i.e. every other vert is a 1-vert island), | * - In the most extreme case (i.e. every other vert is a 1-vert island), | ||||
| * we have at most n / 2 islands | * we have at most n / 2 islands | ||||
| * - Once we start having larger islands than that, the number required | * - Once we start having larger islands than that, the number required | ||||
| * 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; | ||||
| } | } | ||||
| else { | else { | ||||
| /* unselected - start of a new island? */ | /* unselected - start of a new island? */ | ||||
| int idx; | int idx; | ||||
| Show All 26 Lines | for (idx = 0; idx < num_islands; idx++) { | ||||
| /* initialize triangle memory - to be calculated on next redraw */ | /* initialize triangle memory - to be calculated on next redraw */ | ||||
| new_stroke->triangles = NULL; | new_stroke->triangles = NULL; | ||||
| new_stroke->flag |= GP_STROKE_RECALC_CACHES; | new_stroke->flag |= GP_STROKE_RECALC_CACHES; | ||||
| new_stroke->tot_triangles = 0; | new_stroke->tot_triangles = 0; | ||||
| /* 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"); | ||||
| new_stroke->dvert = MEM_callocN(sizeof(MDeformVert) * new_stroke->totpoints, "gp delete stroke fragment weight"); | |||||
| /* 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); | ||||
| 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 | /* Each island corresponds to a new stroke. We must adjust the | ||||
| * timings of these new strokes: | * timings of these new strokes: | ||||
| * | * | ||||
| * Each point's timing data is a delta from stroke's inittime, so as we erase some points from | * Each point's timing data is a delta from stroke's inittime, so as we erase some points from | ||||
| * the start of the stroke, we have to offset this inittime and all remaining points' delta values. | * the start of the stroke, we have to offset this inittime and all remaining points' delta values. | ||||
| * This way we get a new stroke with exactly the same timing as if user had started drawing from | * This way we get a new stroke with exactly the same timing as if user had started drawing from | ||||
| * the first non-removed point... | * the first non-removed point... | ||||
| */ | */ | ||||
| { | { | ||||
| bGPDspoint *pts; | bGPDspoint *pts; | ||||
| float delta = gps->points[island->start_idx].time; | float delta = gps->points[island->start_idx].time; | ||||
| int j; | int j; | ||||
| new_stroke->inittime += (double)delta; | new_stroke->inittime += (double)delta; | ||||
| 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; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| /* Add new stroke to the frame */ | /* Add new stroke to the frame */ | ||||
| if (next_stroke) { | if (next_stroke) { | ||||
| BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); | BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); | ||||
| } | } | ||||
| else { | else { | ||||
| BLI_addtail(&gpf->strokes, new_stroke); | BLI_addtail(&gpf->strokes, new_stroke); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* free islands */ | /* free islands */ | ||||
| MEM_freeN(islands); | MEM_freeN(islands); | ||||
| /* Delete the old stroke */ | /* Delete the old stroke */ | ||||
| if (gps->points) { | |||||
| MEM_freeN(gps->points); | MEM_freeN(gps->points); | ||||
| } | |||||
| if (gps->dvert) { | |||||
| BKE_gpencil_free_stroke_weights(gps); | |||||
| MEM_freeN(gps->dvert); | |||||
| } | |||||
| 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) | ||||
| { | { | ||||
| Object *ob = CTX_data_active_object(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; | ||||
| 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; | bGPDstroke *gps, *gpsn; | ||||
| if (gpf == NULL) | if (gpf == NULL) | ||||
| continue; | continue; | ||||
| /* simply delete strokes which are selected */ | /* simply delete strokes which are selected */ | ||||
| for (gps = gpf->strokes.first; gps; gps = gpsn) { | for (gps = gpf->strokes.first; gps; gps = gpsn) { | ||||
| gpsn = gps->next; | gpsn = gps->next; | ||||
| /* skip strokes that are invalid for current view */ | /* skip strokes that are invalid for current view */ | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | if (ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| continue; | continue; | ||||
| /* check if the color is editable */ | /* 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; | continue; | ||||
| if (gps->flag & GP_STROKE_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| /* deselect old stroke, since it will be used as template for the new strokes */ | /* deselect old stroke, since it will be used as template for the new strokes */ | ||||
| gps->flag &= ~GP_STROKE_SELECT; | gps->flag &= ~GP_STROKE_SELECT; | ||||
| /* delete unwanted points by splitting stroke into several smaller ones */ | /* delete unwanted points by splitting stroke into several smaller ones */ | ||||
| gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT); | gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false); | ||||
| changed = true; | changed = true; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| } | |||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| if (changed) { | if (changed) { | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| else { | else { | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| } | } | ||||
| /* 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) | ||||
| { | { | ||||
| eGP_DeleteMode mode = RNA_enum_get(op->ptr, "type"); | eGP_DeleteMode mode = RNA_enum_get(op->ptr, "type"); | ||||
| int result = OPERATOR_CANCELLED; | int result = OPERATOR_CANCELLED; | ||||
| switch (mode) { | switch (mode) { | ||||
| Show All 34 Lines | void GPENCIL_OT_delete(wmOperatorType *ot) | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; | ||||
| /* props */ | /* props */ | ||||
| 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 ************************ */ | ||||
| /* Poll callback for snap operators */ | /* Poll callback for snap operators */ | ||||
| /* NOTE: For now, we only allow these in the 3D view, as other editors do not | /* NOTE: For now, we only allow these in the 3D view, as other editors do not | ||||
| * define a cursor or gridstep which can be used | * define a cursor or gridstep which can be used | ||||
| */ | */ | ||||
| static int gp_snap_poll(bContext *C) | static int gp_snap_poll(bContext *C) | ||||
| { | { | ||||
| bGPdata *gpd = CTX_data_gpencil_data(C); | bGPdata *gpd = CTX_data_gpencil_data(C); | ||||
| ScrArea *sa = CTX_wm_area(C); | ScrArea *sa = CTX_wm_area(C); | ||||
| return (gpd != NULL) && ((sa != NULL) && (sa->spacetype == SPACE_VIEW3D)); | return (gpd != NULL) && ((sa != NULL) && (sa->spacetype == SPACE_VIEW3D)); | ||||
| } | } | ||||
| /* --------------------------------- */ | /* --------------------------------- */ | ||||
| static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) | static int gp_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) | ||||
| { | { | ||||
| 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); | ||||
| Depsgraph *depsgraph = CTX_data_depsgraph(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) { | ||||
| /* only editable and visible layers are considered */ | /* only editable and visible layers are considered */ | ||||
| if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { | if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { | ||||
| 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(depsgraph, 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; | ||||
| int i; | int i; | ||||
| /* skip strokes that are invalid for current view */ | /* skip strokes that are invalid for current view */ | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | if (ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| continue; | continue; | ||||
| /* check if the color is editable */ | /* 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; | continue; | ||||
| // TODO: if entire stroke is selected, offset entire stroke by same amount? | // TODO: if entire stroke is selected, offset entire stroke by same amount? | ||||
| 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) { | |||||
| 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 */ | /* apply parent transformations */ | ||||
| float fpt[3]; | float fpt[3]; | ||||
| mul_v3_m4v3(fpt, diff_mat, &pt->x); | mul_v3_m4v3(fpt, diff_mat, &pt->x); | ||||
| fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); | fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); | ||||
| fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); | fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); | ||||
| fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); | fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); | ||||
| /* return data */ | /* return data */ | ||||
| copy_v3_v3(&pt->x, fpt); | copy_v3_v3(&pt->x, fpt); | ||||
| gp_apply_parent_point(gpl, pt); | gp_apply_parent_point(depsgraph, obact, gpd, gpl, pt); | ||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_snap_to_grid(wmOperatorType *ot) | void GPENCIL_OT_snap_to_grid(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Snap Selection to Grid"; | ot->name = "Snap Selection to Grid"; | ||||
| Show All 11 Lines | |||||
| /* ------------------------------- */ | /* ------------------------------- */ | ||||
| static int gp_snap_to_cursor(bContext *C, wmOperator *op) | static int gp_snap_to_cursor(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| View3D *v3d = CTX_wm_view3d(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 bool use_offset = RNA_boolean_get(op->ptr, "use_offset"); | ||||
| const float *cursor_global = ED_view3d_cursor3d_get(scene, v3d)->location; | const float *cursor_global = ED_view3d_cursor3d_get(scene, v3d)->location; | ||||
| for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { | for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { | ||||
| /* only editable and visible layers are considered */ | /* only editable and visible layers are considered */ | ||||
| if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { | if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { | ||||
| 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(depsgraph, 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; | ||||
| int i; | int i; | ||||
| /* skip strokes that are invalid for current view */ | /* skip strokes that are invalid for current view */ | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | if (ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| continue; | continue; | ||||
| /* check if the color is editable */ | /* 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; | continue; | ||||
| /* only continue if this stroke is selected (editable doesn't guarantee this)... */ | /* only continue if this stroke is selected (editable doesn't guarantee this)... */ | ||||
| if ((gps->flag & GP_STROKE_SELECT) == 0) | if ((gps->flag & GP_STROKE_SELECT) == 0) | ||||
| continue; | continue; | ||||
| if (use_offset) { | if (use_offset) { | ||||
| float offset[3]; | float offset[3]; | ||||
| /* compute offset from first point of stroke to cursor */ | /* compute offset from first point of stroke to cursor */ | ||||
| /* TODO: Allow using midpoint instead? */ | /* TODO: Allow using midpoint instead? */ | ||||
| sub_v3_v3v3(offset, cursor_global, &gps->points->x); | sub_v3_v3v3(offset, cursor_global, &gps->points->x); | ||||
| /* apply offset to all points in the stroke */ | /* apply offset to all points in the stroke */ | ||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| add_v3_v3(&pt->x, offset); | add_v3_v3(&pt->x, offset); | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| /* affect each selected point */ | /* affect each selected point */ | ||||
| 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(depsgraph, obact, gpd, gpl, pt); | ||||
| gp_apply_parent_point(gpl, pt); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot) | void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Snap Selection to Cursor"; | ot->name = "Snap Selection to Cursor"; | ||||
| Show All 15 Lines | |||||
| /* ------------------------------- */ | /* ------------------------------- */ | ||||
| static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) | static int gp_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| View3D *v3d = CTX_wm_view3d(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)->location; | float *cursor = ED_view3d_cursor3d_get(scene, v3d)->location; | ||||
| float centroid[3] = {0.0f}; | float centroid[3] = {0.0f}; | ||||
| float min[3], max[3]; | float min[3], max[3]; | ||||
| size_t count = 0; | size_t count = 0; | ||||
| INIT_MINMAX(min, max); | INIT_MINMAX(min, max); | ||||
| /* calculate midpoints from selected points */ | /* calculate midpoints from selected points */ | ||||
| for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { | for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { | ||||
| /* only editable and visible layers are considered */ | /* only editable and visible layers are considered */ | ||||
| if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { | if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { | ||||
| 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(depsgraph, 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; | ||||
| int i; | int i; | ||||
| /* skip strokes that are invalid for current view */ | /* skip strokes that are invalid for current view */ | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) | if (ED_gpencil_stroke_can_use(C, gps) == false) | ||||
| continue; | continue; | ||||
| /* check if the color is editable */ | /* 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; | continue; | ||||
| /* only continue if this stroke is selected (editable doesn't guarantee this)... */ | /* only continue if this stroke is selected (editable doesn't guarantee this)... */ | ||||
| if ((gps->flag & GP_STROKE_SELECT) == 0) | if ((gps->flag & GP_STROKE_SELECT) == 0) | ||||
| continue; | continue; | ||||
| 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) { | |||||
| add_v3_v3(centroid, &pt->x); | |||||
| minmax_v3v3_v3(min, max, &pt->x); | |||||
| } | |||||
| else { | |||||
| /* apply parent transformations */ | /* apply parent transformations */ | ||||
| float fpt[3]; | float fpt[3]; | ||||
| mul_v3_m4v3(fpt, diff_mat, &pt->x); | 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++; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_MEAN && count) { | if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_MEAN && count) { | ||||
| mul_v3_fl(centroid, 1.0f / (float)count); | mul_v3_fl(centroid, 1.0f / (float)count); | ||||
| copy_v3_v3(cursor, centroid); | copy_v3_v3(cursor, centroid); | ||||
| } | } | ||||
| else { | else { | ||||
| mid_v3_v3v3(cursor, min, max); | mid_v3_v3v3(cursor, min, max); | ||||
| } | } | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot) | void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Snap Cursor to Selected Points"; | ot->name = "Snap Cursor to Selected Points"; | ||||
| Show All 25 Lines | for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| /* Apply thickness */ | /* Apply thickness */ | ||||
| gps->thickness = gps->thickness + gpl->thickness; | gps->thickness = gps->thickness + gpl->thickness; | ||||
| } | } | ||||
| } | } | ||||
| /* clear value */ | /* clear value */ | ||||
| gpl->thickness = 0.0f; | gpl->thickness = 0.0f; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_stroke_apply_thickness(wmOperatorType *ot) | void GPENCIL_OT_stroke_apply_thickness(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| Show All 12 Lines | enum { | ||||
| GP_STROKE_CYCLIC_CLOSE = 1, | GP_STROKE_CYCLIC_CLOSE = 1, | ||||
| GP_STROKE_CYCLIC_OPEN = 2, | GP_STROKE_CYCLIC_OPEN = 2, | ||||
| GP_STROKE_CYCLIC_TOGGLE = 3 | GP_STROKE_CYCLIC_TOGGLE = 3 | ||||
| }; | }; | ||||
| static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) | static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| Object *ob = CTX_data_active_object(C); | |||||
| const int type = RNA_enum_get(op->ptr, "type"); | const int type = RNA_enum_get(op->ptr, "type"); | ||||
| /* sanity checks */ | /* sanity checks */ | ||||
| if (ELEM(NULL, gpd)) | if (ELEM(NULL, gpd)) | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| /* loop all selected strokes */ | /* loop all selected strokes */ | ||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| { | { | ||||
| if (gpl->actframe == NULL) | if (gpl->actframe == NULL) | ||||
| 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; | MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1); | ||||
| /* 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) | ||||
| continue; | continue; | ||||
| /* skip hidden or locked colors */ | /* 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; | continue; | ||||
| switch (type) { | switch (type) { | ||||
| case GP_STROKE_CYCLIC_CLOSE: | case GP_STROKE_CYCLIC_CLOSE: | ||||
| /* Close all (enable) */ | /* Close all (enable) */ | ||||
| gps->flag |= GP_STROKE_CYCLIC; | gps->flag |= GP_STROKE_CYCLIC; | ||||
| break; | break; | ||||
| case GP_STROKE_CYCLIC_OPEN: | case GP_STROKE_CYCLIC_OPEN: | ||||
| /* Open all (disable) */ | /* Open all (disable) */ | ||||
| gps->flag &= ~GP_STROKE_CYCLIC; | gps->flag &= ~GP_STROKE_CYCLIC; | ||||
| break; | break; | ||||
| case GP_STROKE_CYCLIC_TOGGLE: | case GP_STROKE_CYCLIC_TOGGLE: | ||||
| /* Just toggle flag... */ | /* Just toggle flag... */ | ||||
| gps->flag ^= GP_STROKE_CYCLIC; | gps->flag ^= GP_STROKE_CYCLIC; | ||||
| break; | break; | ||||
| default: | default: | ||||
| BLI_assert(0); | BLI_assert(0); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| /** | /** | ||||
| * Similar to #CURVE_OT_cyclic_toggle or #MASK_OT_cyclic_toggle, but with | * Similar to #CURVE_OT_cyclic_toggle or #MASK_OT_cyclic_toggle, but with | ||||
| * option to force opened/closed strokes instead of just toggle behavior. | * option to force opened/closed strokes instead of just toggle behavior. | ||||
| ▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | for (int i = 0; i < gps->totpoints / 2; i++) { | ||||
| point->strength = pt.strength; | point->strength = pt.strength; | ||||
| point->time = pt.time; | point->time = pt.time; | ||||
| end--; | end--; | ||||
| } | } | ||||
| } | } | ||||
| /* Helper: copy point between strokes */ | /* Helper: copy point between strokes */ | ||||
| static void gpencil_stroke_copy_point(bGPDstroke *gps, bGPDspoint *point, float delta[3], | static void gpencil_stroke_copy_point(bGPDstroke *gps, bGPDspoint *point, int idx, float delta[3], | ||||
| float pressure, float strength, float deltatime) | float pressure, float strength, float deltatime) | ||||
| { | { | ||||
| bGPDspoint *newpoint; | bGPDspoint *newpoint; | ||||
| MDeformVert *dvert, *newdvert; | |||||
| gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); | gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); | ||||
| gps->dvert = MEM_reallocN(gps->dvert, sizeof(MDeformVert) * (gps->totpoints + 1)); | |||||
| gps->totpoints++; | gps->totpoints++; | ||||
| dvert = &gps->dvert[idx]; | |||||
| newpoint = &gps->points[gps->totpoints - 1]; | newpoint = &gps->points[gps->totpoints - 1]; | ||||
| newdvert = &gps->dvert[gps->totpoints - 1]; | |||||
| newpoint->x = point->x * delta[0]; | newpoint->x = point->x * delta[0]; | ||||
| newpoint->y = point->y * delta[1]; | newpoint->y = point->y * delta[1]; | ||||
| newpoint->z = point->z * delta[2]; | newpoint->z = point->z * delta[2]; | ||||
| newpoint->flag = point->flag; | newpoint->flag = point->flag; | ||||
| newpoint->pressure = pressure; | newpoint->pressure = pressure; | ||||
| newpoint->strength = strength; | newpoint->strength = strength; | ||||
| newpoint->time = point->time + deltatime; | newpoint->time = point->time + deltatime; | ||||
| newdvert->totweight = dvert->totweight; | |||||
| newdvert->dw = MEM_dupallocN(dvert->dw); | |||||
| } | } | ||||
| /* Helper: join two strokes using the shortest distance (reorder stroke if necessary ) */ | /* Helper: join two strokes using the shortest distance (reorder stroke if necessary ) */ | ||||
| static void gpencil_stroke_join_strokes(bGPDstroke *gps_a, bGPDstroke *gps_b, const bool leave_gaps) | static void gpencil_stroke_join_strokes(bGPDstroke *gps_a, bGPDstroke *gps_b, const bool leave_gaps) | ||||
| { | { | ||||
| bGPDspoint point; | bGPDspoint point; | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| int i; | int i; | ||||
| Show All 29 Lines | if (ea_eb < ea_sb) { | ||||
| gpencil_flip_stroke(gps_b); | gpencil_flip_stroke(gps_b); | ||||
| } | } | ||||
| /* don't visibly link the first and last points? */ | /* don't visibly link the first and last points? */ | ||||
| if (leave_gaps) { | if (leave_gaps) { | ||||
| /* 1st: add one tail point to start invisible area */ | /* 1st: add one tail point to start invisible area */ | ||||
| point = gps_a->points[gps_a->totpoints - 1]; | point = gps_a->points[gps_a->totpoints - 1]; | ||||
| deltatime = point.time; | 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 */ | /* 2nd: add one head point to finish invisible area */ | ||||
| point = gps_b->points[0]; | 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 */ | /* 3rd: add all points */ | ||||
| for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) { | for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) { | ||||
| /* check if still room in buffer */ | /* check if still room in buffer */ | ||||
| if (gps_a->totpoints <= GP_STROKE_BUFFER_MAX - 2) { | 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); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| static int gp_stroke_join_exec(bContext *C, wmOperator *op) | static int gp_stroke_join_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| 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); | Object *ob = CTX_data_active_object(C); | ||||
| bGPDpalettecolor *palcolor = BKE_gpencil_palettecolor_getactive(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; | ||||
| bGPDstroke *new_stroke = NULL; | bGPDstroke *new_stroke = NULL; | ||||
| const int type = RNA_enum_get(op->ptr, "type"); | const int type = RNA_enum_get(op->ptr, "type"); | ||||
| const bool leave_gaps = RNA_boolean_get(op->ptr, "leave_gaps"); | const bool leave_gaps = RNA_boolean_get(op->ptr, "leave_gaps"); | ||||
| Show All 19 Lines | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| for (gps = gpf->strokes.first; gps; gps = gpsn) { | for (gps = gpf->strokes.first; gps; gps = gpsn) { | ||||
| gpsn = gps->next; | gpsn = gps->next; | ||||
| if (gps->flag & GP_STROKE_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| /* skip strokes that are invalid for current view */ | /* skip strokes that are invalid for current view */ | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) { | if (ED_gpencil_stroke_can_use(C, gps) == false) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| /* check if the color is editable */ | /* 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; | continue; | ||||
| } | } | ||||
| /* to join strokes, cyclic must be disabled */ | /* to join strokes, cyclic must be disabled */ | ||||
| gps->flag &= ~GP_STROKE_CYCLIC; | gps->flag &= ~GP_STROKE_CYCLIC; | ||||
| /* saves first frame and stroke */ | /* saves first frame and stroke */ | ||||
| if (!first) { | if (!first) { | ||||
| first = true; | first = true; | ||||
| gpf_a = gpf; | gpf_a = gpf; | ||||
| stroke_a = gps; | stroke_a = gps; | ||||
| } | } | ||||
| else { | else { | ||||
| stroke_b = gps; | stroke_b = gps; | ||||
| /* create a new stroke if was not created before (only created if something to join) */ | /* create a new stroke if was not created before (only created if something to join) */ | ||||
| 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); | ||||
| new_stroke->dvert = MEM_dupallocN(stroke_a->dvert); | |||||
| 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; | ||||
| /* if new, set current color */ | /* if new, set current color */ | ||||
| if (type == GP_STROKE_JOINCOPY) { | if (type == GP_STROKE_JOINCOPY) { | ||||
| new_stroke->palcolor = palcolor; | new_stroke->mat_nr = stroke_a->mat_nr; | ||||
| BLI_strncpy(new_stroke->colorname, palcolor->info, sizeof(new_stroke->colorname)); | |||||
| new_stroke->flag |= GP_STROKE_RECALC_COLOR; | |||||
| } | } | ||||
| } | } | ||||
| /* join new_stroke and stroke B. New stroke will contain all the previous data */ | /* join new_stroke and stroke B. New stroke will contain all the previous data */ | ||||
| gpencil_stroke_join_strokes(new_stroke, stroke_b, leave_gaps); | gpencil_stroke_join_strokes(new_stroke, stroke_b, leave_gaps); | ||||
| /* if join only, delete old strokes */ | /* if join only, delete old strokes */ | ||||
| if (type == GP_STROKE_JOIN) { | if (type == GP_STROKE_JOIN) { | ||||
| Show All 22 Lines | if (new_stroke) { | ||||
| if (activegpl->actframe == NULL) | if (activegpl->actframe == NULL) | ||||
| activegpl->actframe = BKE_gpencil_frame_addnew(activegpl, gpf_a->framenum); | activegpl->actframe = BKE_gpencil_frame_addnew(activegpl, gpf_a->framenum); | ||||
| BLI_addtail(&activegpl->actframe->strokes, new_stroke); | BLI_addtail(&activegpl->actframe->strokes, new_stroke); | ||||
| } | } | ||||
| } | } | ||||
| /* notifiers */ | /* notifiers */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_stroke_join(wmOperatorType *ot) | void GPENCIL_OT_stroke_join(wmOperatorType *ot) | ||||
| { | { | ||||
| static const EnumPropertyItem join_type[] = { | static const EnumPropertyItem join_type[] = { | ||||
| Show All 19 Lines | void GPENCIL_OT_stroke_join(wmOperatorType *ot) | ||||
| RNA_def_boolean(ot->srna, "leave_gaps", false, "Leave Gaps", "Leave gaps between joined strokes instead of linking them"); | RNA_def_boolean(ot->srna, "leave_gaps", false, "Leave Gaps", "Leave gaps between joined strokes instead of linking them"); | ||||
| } | } | ||||
| /* ******************* Stroke flip ************************** */ | /* ******************* Stroke flip ************************** */ | ||||
| static int gp_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) | static int gp_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) | ||||
| { | { | ||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | bGPdata *gpd = ED_gpencil_data_get_active(C); | ||||
| Object *ob = CTX_data_active_object(C); | |||||
| /* sanity checks */ | /* sanity checks */ | ||||
| if (ELEM(NULL, gpd)) | if (ELEM(NULL, gpd)) | ||||
| return OPERATOR_CANCELLED; | return OPERATOR_CANCELLED; | ||||
| /* read all selected strokes */ | /* read all selected strokes */ | ||||
| CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) | ||||
| { | { | ||||
| bGPDframe *gpf = gpl->actframe; | bGPDframe *gpf = gpl->actframe; | ||||
| if (gpf == NULL) | if (gpf == NULL) | ||||
| continue; | continue; | ||||
| for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| if (gps->flag & GP_STROKE_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| /* skip strokes that are invalid for current view */ | /* skip strokes that are invalid for current view */ | ||||
| if (ED_gpencil_stroke_can_use(C, gps) == false) { | if (ED_gpencil_stroke_can_use(C, gps) == false) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| /* check if the color is editable */ | /* 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; | continue; | ||||
| } | } | ||||
| /* flip stroke */ | /* flip stroke */ | ||||
| gpencil_flip_stroke(gps); | gpencil_flip_stroke(gps); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| CTX_DATA_END; | CTX_DATA_END; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_stroke_flip(wmOperatorType *ot) | void GPENCIL_OT_stroke_flip(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Flip Stroke"; | ot->name = "Flip Stroke"; | ||||
| ot->idname = "GPENCIL_OT_stroke_flip"; | ot->idname = "GPENCIL_OT_stroke_flip"; | ||||
| ot->description = "Change direction of the points of the selected strokes"; | ot->description = "Change direction of the points of the selected strokes"; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->exec = gp_stroke_flip_exec; | ot->exec = gp_stroke_flip_exec; | ||||
| ot->poll = gp_active_layer_poll; | ot->poll = gp_active_layer_poll; | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| } | } | ||||
| /* ***************** 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); | |||||
| Depsgraph *depsgraph = CTX_data_depsgraph(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) { | ||||
| struct Depsgraph *depsgraph = CTX_data_depsgraph(C); | |||||
| view3d_region_operator_needs_opengl(CTX_wm_window(C), gsc.ar); | view3d_region_operator_needs_opengl(CTX_wm_window(C), gsc.ar); | ||||
| ED_view3d_autodist_init(depsgraph, 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? | // TODO: For deforming geometry workflow, create new frames? | ||||
| /* Go through each editable + selected stroke, adjusting each of its points one by one... */ | /* 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(C, gpl, gps) | ||||
| { | { | ||||
| if (gps->flag & GP_STROKE_SELECT) { | if (gps->flag & GP_STROKE_SELECT) { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| int i; | int i; | ||||
| float inverse_diff_mat[4][4]; | float inverse_diff_mat[4][4]; | ||||
| /* 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]; | ||||
| /* 3D to Screenspace */ | /* 3D to Screenspace */ | ||||
| /* Note: We can't use gp_point_to_xy() here because that uses ints for the screenspace | /* Note: We can't use gp_point_to_xy() here because that uses ints for the screenspace | ||||
| * 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) { | |||||
| gp_point_to_xy_fl(&gsc, gps, pt, &xy[0], &xy[1]); | |||||
| } | |||||
| else { | |||||
| bGPDspoint pt2; | bGPDspoint pt2; | ||||
| gp_point_to_parent_space(pt, diff_mat, &pt2); | gp_point_to_parent_space(pt, diff_mat, &pt2); | ||||
| gp_point_to_xy_fl(&gsc, gps, &pt2, &xy[0], &xy[1]); | 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(depsgraph, 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); | ||||
| } | } | ||||
| else { | else { | ||||
| /* Geometry - Snap to surfaces of visible geometry */ | /* Geometry - Snap to surfaces of visible geometry */ | ||||
| /* XXX: There will be precision loss (possible stairstep artifacts) from this conversion to satisfy the API's */ | /* XXX: There will be precision loss (possible stairstep artifacts) from this conversion to satisfy the API's */ | ||||
| const int screen_co[2] = {(int)xy[0], (int)xy[1]}; | const int screen_co[2] = {(int)xy[0], (int)xy[1]}; | ||||
| int depth_margin = 0; // XXX: 4 for strokes, 0 for normal | int depth_margin = 0; // XXX: 4 for strokes, 0 for normal | ||||
| float depth; | float depth; | ||||
| /* XXX: The proper procedure computes the depths into an array, to have smooth transitions when all else fails... */ | /* XXX: The proper procedure computes the depths into an array, to have smooth transitions when all else fails... */ | ||||
| if (ED_view3d_autodist_depth(gsc.ar, screen_co, depth_margin, &depth)) { | if (ED_view3d_autodist_depth(gsc.ar, screen_co, depth_margin, &depth)) { | ||||
| ED_view3d_autodist_simple(gsc.ar, screen_co, &pt->x, 0, &depth); | ED_view3d_autodist_simple(gsc.ar, screen_co, &pt->x, 0, &depth); | ||||
| } | } | ||||
| else { | else { | ||||
| /* Default to planar */ | /* Default to planar */ | ||||
| gp_point_xy_to_3d(&gsc, scene, xy, &pt->x); | gp_point_xy_to_3d(&gsc, scene, xy, &pt->x); | ||||
| } | } | ||||
| } | } | ||||
| /* 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); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| GP_EDITABLE_STROKES_END; | GP_EDITABLE_STROKES_END; | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_reproject(wmOperatorType *ot) | void GPENCIL_OT_reproject(wmOperatorType *ot) | ||||
| { | { | ||||
| static const EnumPropertyItem reproject_type[] = { | static const EnumPropertyItem reproject_type[] = { | ||||
| { 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", | {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", | ||||
| "Reproject the strokes on to the scene geometry, as if drawn using 'Surface' placement"}, | "Reproject the strokes on to the scene geometry, as if drawn using 'Surface' placement"}, | ||||
| {0, NULL, 0, NULL, NULL} | {0, NULL, 0, NULL, NULL} | ||||
| }; | }; | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Reproject Strokes"; | ot->name = "Reproject Strokes"; | ||||
| ot->idname = "GPENCIL_OT_reproject"; | ot->idname = "GPENCIL_OT_reproject"; | ||||
| ot->description = "Reproject the selected strokes from the current viewpoint as if they had been newly drawn " | 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, " | "(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, " | ||||
| "or for matching deforming geometry)"; | "or for matching deforming geometry)"; | ||||
| /* 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; | ||||
| /* properties */ | /* 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_PLANAR, "Projection Type", ""); | ||||
| } | } | ||||
| /* ******************* Stroke subdivide ************************** */ | /* ******************* Stroke subdivide ************************** */ | ||||
| /* helper: Count how many points need to be inserted */ | /* helper: Count how many points need to be inserted */ | ||||
| static int gp_count_subdivision_cuts(bGPDstroke *gps) | static int gp_count_subdivision_cuts(bGPDstroke *gps) | ||||
| { | { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| int i; | int i; | ||||
| int totnewpoints = 0; | int totnewpoints = 0; | ||||
| for (i = 0, pt = gps->points; i < gps->totpoints && pt; i++, pt++) { | for (i = 0, pt = gps->points; i < gps->totpoints && pt; i++, pt++) { | ||||
| 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++; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| 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); | ||||
| bGPDspoint *temp_points; | bGPDspoint *temp_points; | ||||
| const int cuts = RNA_int_get(op->ptr, "number_cuts"); | const int cuts = RNA_int_get(op->ptr, "number_cuts"); | ||||
| int totnewpoints, oldtotpoints; | int totnewpoints, oldtotpoints; | ||||
| int i2; | int i2; | ||||
| Show All 14 Lines | if (gps->flag & GP_STROKE_SELECT) { | ||||
| } | } | ||||
| /* duplicate points in a temp area */ | /* duplicate points in a temp area */ | ||||
| temp_points = MEM_dupallocN(gps->points); | temp_points = MEM_dupallocN(gps->points); | ||||
| oldtotpoints = gps->totpoints; | oldtotpoints = gps->totpoints; | ||||
| /* resize the points arrys */ | /* resize the points arrys */ | ||||
| gps->totpoints += totnewpoints; | gps->totpoints += totnewpoints; | ||||
| gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); | gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); | ||||
| gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); | |||||
| gps->flag |= GP_STROKE_RECALC_CACHES; | gps->flag |= GP_STROKE_RECALC_CACHES; | ||||
| /* loop and interpolate */ | /* loop and interpolate */ | ||||
| i2 = 0; | i2 = 0; | ||||
| for (int i = 0; i < oldtotpoints; i++) { | for (int i = 0; i < oldtotpoints; i++) { | ||||
| bGPDspoint *pt = &temp_points[i]; | bGPDspoint *pt = &temp_points[i]; | ||||
| bGPDspoint *pt_final = &gps->points[i2]; | bGPDspoint *pt_final = &gps->points[i2]; | ||||
| MDeformVert *dvert_final = &gps->dvert[i2]; | |||||
| /* copy current point */ | /* copy current point */ | ||||
| copy_v3_v3(&pt_final->x, &pt->x); | copy_v3_v3(&pt_final->x, &pt->x); | ||||
| pt_final->pressure = pt->pressure; | pt_final->pressure = pt->pressure; | ||||
| 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; | |||||
| dvert_final->totweight = 0; | |||||
| dvert_final->dw = 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) { | ||||
| if (i + 1 < oldtotpoints) { | if (i + 1 < oldtotpoints) { | ||||
| if (temp_points[i + 1].flag & GP_SPOINT_SELECT) { | if (temp_points[i + 1].flag & GP_SPOINT_SELECT) { | ||||
| pt_final = &gps->points[i2]; | pt_final = &gps->points[i2]; | ||||
| dvert_final = &gps->dvert[i2]; | |||||
| /* Interpolate all values */ | /* Interpolate all values */ | ||||
| bGPDspoint *next = &temp_points[i + 1]; | bGPDspoint *next = &temp_points[i + 1]; | ||||
| interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); | interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); | ||||
| pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); | pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); | ||||
| pt_final->strength = interpf(pt->strength, next->strength, 0.5f); | pt_final->strength = interpf(pt->strength, next->strength, 0.5f); | ||||
| 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; | |||||
| dvert_final->totweight = 0; | |||||
| dvert_final->dw = NULL; | |||||
| i2++; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* free temp memory */ | /* free temp memory */ | ||||
| MEM_freeN(temp_points); | MEM_freeN(temp_points); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| GP_EDITABLE_STROKES_END; | GP_EDITABLE_STROKES_END; | ||||
| /* notifiers */ | /* notifiers */ | ||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | ||||
| } | } | ||||
| void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) | void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) | ||||
| { | { | ||||
| PropertyRNA *prop; | PropertyRNA *prop; | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Subdivide Stroke"; | ot->name = "Subdivide Stroke"; | ||||
| ot->idname = "GPENCIL_OT_stroke_subdivide"; | ot->idname = "GPENCIL_OT_stroke_subdivide"; | ||||
| ot->description = "Subdivide between continuous selected points of the stroke adding a point half way between them"; | ot->description = "Subdivide between continuous selected points of the stroke adding a point half way between them"; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->exec = gp_stroke_subdivide_exec; | ot->exec = gp_stroke_subdivide_exec; | ||||
| 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); | ||||
| /* avoid re-using last var because it can cause _very_ high value and annoy users */ | /* avoid re-using last var because it can cause _very_ high value and annoy users */ | ||||
| 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(gps, factor); | |||||
| } | |||||
| } | |||||
| GP_EDITABLE_STROKES_END; | |||||
| /* notifiers */ | |||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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(gps); | |||||
| } | |||||
| } | |||||
| } | |||||
| GP_EDITABLE_STROKES_END; | |||||
| /* notifiers */ | |||||
| DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | |||||
| } | |||||
| 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 */ | |||||
| // XXX: check usercounts | |||||
| gpd_dst = BKE_gpencil_data_addnew(bmain, "GPencil"); | |||||
| 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_object_material_slot_find_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_EXISTING); | |||||
| 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, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| DEG_id_tag_update(&gpd_dst->id, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | |||||
| } | |||||
| 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, OB_RECALC_OB | OB_RECALC_DATA); | |||||
| 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; | |||||
| } | |||||