diff options
Diffstat (limited to 'source/blender/editors/gpencil')
22 files changed, 1150 insertions, 125 deletions
diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index 9cb9e7ca1af..866df16f3d6 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -12,7 +12,6 @@ set(INC ../../makesdna ../../makesrna ../../windowmanager - ../../../../intern/glew-mx ../../../../intern/guardedalloc ../../bmesh # RNA_prototypes.h diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index d08d56a354a..ea34e6530fa 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -1715,7 +1715,7 @@ static void annotation_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_pt if (p->paintmode == GP_PAINTMODE_ERASER) { GPUVertFormat *format = immVertexFormat(); const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); @@ -1725,7 +1725,7 @@ static void annotation_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_pt immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -1782,7 +1782,7 @@ static void annotation_draw_stabilizer(bContext *C, int x, int y, void *p_ptr) GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); GPU_line_width(1.25f); @@ -2346,7 +2346,7 @@ static int annotation_draw_invoke(bContext *C, wmOperator *op, const wmEvent *ev return OPERATOR_RUNNING_MODAL; } -/* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ +/* gpencil modal operator stores area, which can be removed while using it (like full-screen). */ static bool annotation_area_exists(bContext *C, ScrArea *area_test) { bScreen *screen = CTX_wm_screen(C); @@ -2698,7 +2698,7 @@ static int annotation_draw_modal(bContext *C, wmOperator *op, const wmEvent *eve } } - /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ + /* gpencil modal operator stores area, which can be removed while using it (like full-screen). */ if (0 == annotation_area_exists(C, p->area)) { estate = OPERATOR_CANCELLED; } diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index ce38c261c1f..00066d5f2b8 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -823,6 +823,8 @@ static const ColorTemplate gp_monkey_pct_pupils = { void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4]) { + /* Original model created by Matias Mendiola. */ + Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); bGPdata *gpd = (bGPdata *)ob->data; diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c index 4687f9188fd..8522c81cb39 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -192,6 +192,8 @@ static const ColorTemplate gp_stroke_material_grey = { void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4]) { + /* Original design created by Daniel M. Lara and Matias Mendiola. */ + Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); bGPdata *gpd = (bGPdata *)ob->data; diff --git a/source/blender/editors/gpencil/gpencil_armature.c b/source/blender/editors/gpencil/gpencil_armature.c index d389f7eb5dd..5f5a4b41b27 100644 --- a/source/blender/editors/gpencil/gpencil_armature.c +++ b/source/blender/editors/gpencil/gpencil_armature.c @@ -29,6 +29,7 @@ #include "BKE_deform.h" #include "BKE_gpencil.h" #include "BKE_gpencil_modifier.h" +#include "BKE_layer.h" #include "BKE_main.h" #include "BKE_object_deform.h" #include "BKE_report.h" @@ -528,6 +529,7 @@ static bool gpencil_generate_weights_poll(bContext *C) return false; } + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); bGPdata *gpd = (bGPdata *)ob->data; @@ -536,7 +538,8 @@ static bool gpencil_generate_weights_poll(bContext *C) } /* need some armature in the view layer */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { if (base->object->type == OB_ARMATURE) { return true; } @@ -548,6 +551,7 @@ static bool gpencil_generate_weights_poll(bContext *C) static int gpencil_generate_weights_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = CTX_data_active_object(C); Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); @@ -566,7 +570,8 @@ static int gpencil_generate_weights_exec(bContext *C, wmOperator *op) /* get armature */ const int arm_idx = RNA_enum_get(op->ptr, "armature"); if (arm_idx > 0) { - Base *base = BLI_findlink(&view_layer->object_bases, arm_idx - 1); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BLI_findlink(BKE_view_layer_object_bases_get(view_layer), arm_idx - 1); ob_arm = base->object; } else { @@ -607,6 +612,7 @@ static const EnumPropertyItem *gpencil_armatures_enum_itemf(bContext *C, PropertyRNA *UNUSED(prop), bool *r_free) { + Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); EnumPropertyItem *item = NULL, item_tmp = {0}; int totitem = 0; @@ -623,7 +629,8 @@ static const EnumPropertyItem *gpencil_armatures_enum_itemf(bContext *C, RNA_enum_item_add(&item, &totitem, &item_tmp); i++; - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + BKE_view_layer_synced_ensure(scene, view_layer); + LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) { Object *ob = base->object; if (ob->type == OB_ARMATURE) { item_tmp.identifier = item_tmp.name = ob->id.name + 2; diff --git a/source/blender/editors/gpencil/gpencil_bake_animation.cc b/source/blender/editors/gpencil/gpencil_bake_animation.cc index e480852a9bb..28d4e1c6d42 100644 --- a/source/blender/editors/gpencil/gpencil_bake_animation.cc +++ b/source/blender/editors/gpencil/gpencil_bake_animation.cc @@ -327,7 +327,7 @@ static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op /* Reproject stroke. */ if (project_type != GP_REPROJECT_KEEP) { ED_gpencil_stroke_reproject( - depsgraph, &gsc, sctx, gpl_dst, gpf_dst, gps, project_type, false); + depsgraph, &gsc, sctx, gpl_dst, gpf_dst, gps, project_type, false, 0.0f); } else { BKE_gpencil_stroke_geometry_update(gpd_dst, gps); diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index e02a82f4555..bf78111a636 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -1303,6 +1303,7 @@ static void gpencil_layer_to_curve(bContext *C, ob = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, gpl->info); cu = ob->data = BKE_curve_add(bmain, gpl->info, OB_CURVES_LEGACY); BKE_collection_object_add(bmain, collection, ob); + BKE_view_layer_synced_ensure(scene, view_layer); base_new = BKE_view_layer_base_find(view_layer, ob); DEG_relations_tag_update(bmain); /* added object */ diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index b7ac73b9692..340288b2d74 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -2076,6 +2076,9 @@ static void gpencil_brush_delete_mode_brushes(Main *bmain, } BKE_brush_delete(bmain, brush); + if (brush == brush_active) { + brush_active = NULL; + } } } @@ -2109,8 +2112,8 @@ static int gpencil_brush_reset_all_exec(bContext *C, wmOperator *UNUSED(op)) char tool = '0'; if (paint) { - Brush *brush_active = paint->brush; - if (brush_active) { + if (paint->brush) { + Brush *brush_active = paint->brush; switch (mode) { case CTX_MODE_PAINT_GPENCIL: { tool = brush_active->gpencil_tool; diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index c05ab8c6b28..837a9390b6c 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -67,6 +67,7 @@ #include "ED_armature.h" #include "ED_gpencil.h" +#include "ED_keyframing.h" #include "ED_object.h" #include "ED_outliner.h" #include "ED_screen.h" @@ -1713,12 +1714,17 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) } } - /* 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, - * we are obliged to add a new frame if one - * doesn't exist already + * we reuse active frame or add a new frame if one + * doesn't exist already depending on REC button status. */ - gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW); + if (IS_AUTOKEY_ON(scene) || (gpl->actframe == NULL)) { + gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW); + } + else { + gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV); + } if (gpf) { /* Create new stroke */ bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true, true); @@ -3650,7 +3656,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) } elem = &strokes_list[i]; /* Join new_stroke and stroke B. */ - BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false); + BKE_gpencil_stroke_join(gps_new, elem->gps, leave_gaps, true, false, true); elem->used = true; } @@ -3791,6 +3797,101 @@ void GPENCIL_OT_stroke_flip(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Stroke Start Set Operator + * \{ */ + +static int gpencil_stroke_start_set_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + + /* sanity checks */ + if (ELEM(NULL, ob, gpd)) { + return OPERATOR_CANCELLED; + } + + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Curve Edit mode not supported"); + return OPERATOR_CANCELLED; + } + + bool changed = false; + /* Read all selected strokes. */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_SELECT) { + /* 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_material_editable(ob, gpl, gps) == false) { + continue; + } + + /* Only cyclic strokes. */ + if ((gps->flag & GP_STROKE_CYCLIC) == 0) { + continue; + } + + /* Find first selected point and set start. */ + bGPDspoint *pt; + for (int i = 0; i < gps->totpoints; i++) { + pt = &gps->points[i]; + if (pt->flag & GP_SPOINT_SELECT) { + BKE_gpencil_stroke_start_set(gps, i); + BKE_gpencil_stroke_geometry_update(gpd, gps); + changed = true; + break; + } + } + } + } + } + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; + } + } + } + CTX_DATA_END; + + if (changed) { + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_start_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Start Point"; + ot->idname = "GPENCIL_OT_stroke_start_set"; + ot->description = "Set start point for cyclic strokes"; + + /* api callbacks */ + ot->exec = gpencil_stroke_start_set_exec; + ot->poll = gpencil_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Stroke Re-project Operator * \{ */ @@ -3804,6 +3905,7 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) const bool keep_original = RNA_boolean_get(op->ptr, "keep_original"); const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const float offset = RNA_float_get(op->ptr, "offset"); /* Init snap context for geometry projection. */ SnapObjectContext *sctx = NULL; @@ -3843,7 +3945,8 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) BKE_scene_graph_update_for_newframe(depsgraph); } - ED_gpencil_stroke_reproject(depsgraph, &gsc, sctx, gpl, gpf, gps, mode, keep_original); + ED_gpencil_stroke_reproject( + depsgraph, &gsc, sctx, gpl, gpf, gps, mode, keep_original, offset); if (is_curve_edit && gps->editcurve != NULL) { BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); @@ -3883,8 +3986,29 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void gpencil_strokes_reproject_ui(bContext *UNUSED(C), wmOperator *op) +{ + uiLayout *layout = op->layout; + uiLayout *row; + + const eGP_ReprojectModes type = RNA_enum_get(op->ptr, "type"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + row = uiLayoutRow(layout, true); + uiItemR(row, op->ptr, "type", 0, NULL, ICON_NONE); + + if (type == GP_REPROJECT_SURFACE) { + row = uiLayoutRow(layout, true); + uiItemR(row, op->ptr, "offset", 0, NULL, ICON_NONE); + } + row = uiLayoutRow(layout, true); + uiItemR(row, op->ptr, "keep_original", 0, NULL, ICON_NONE); +} + void GPENCIL_OT_reproject(wmOperatorType *ot) { + PropertyRNA *prop; static const EnumPropertyItem reproject_type[] = { {GP_REPROJECT_FRONT, "FRONT", 0, "Front", "Reproject the strokes using the X-Z plane"}, {GP_REPROJECT_SIDE, "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"}, @@ -3922,6 +4046,7 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) ot->invoke = WM_menu_invoke; ot->exec = gpencil_strokes_reproject_exec; ot->poll = gpencil_strokes_edit3d_poll; + ot->ui = gpencil_strokes_reproject_ui; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -3930,12 +4055,15 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) ot->prop = RNA_def_enum( ot->srna, "type", reproject_type, GP_REPROJECT_VIEW, "Projection Type", ""); - RNA_def_boolean( + prop = RNA_def_boolean( ot->srna, "keep_original", 0, "Keep Original", "Keep original strokes and create a copy before reprojecting instead of reproject them"); + RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MOVIECLIP); + + RNA_def_float(ot->srna, "offset", 0.0f, 0.0f, 10.0f, "Surface Offset", "", 0.0f, 10.0f); } static int gpencil_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op)) @@ -3960,6 +4088,284 @@ static int gpencil_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_FINISHED; } +/* -------------------------------------------------------------------- */ +/** \name Stroke Perimeter from View Operator + * \{ */ + +enum { + GP_PERIMETER_VIEW = 0, + GP_PERIMETER_FRONT = 1, + GP_PERIMETER_SIDE = 2, + GP_PERIMETER_TOP = 3, + GP_PERIMETER_CAMERA = 4, +}; + +enum { + GP_STROKE_USE_ACTIVE_MATERIAL = 0, + GP_STROKE_USE_CURRENT_MATERIAL = 1, + GP_STROKE_USE_NEW_MATERIAL = 2, +}; + +static int gpencil_stroke_outline_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + RegionView3D *rv3d = CTX_wm_region_view3d(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + const int subdivisions = RNA_int_get(op->ptr, "subdivisions"); + const float length = RNA_float_get(op->ptr, "length"); + const bool keep = RNA_boolean_get(op->ptr, "keep"); + const int thickness = RNA_int_get(op->ptr, "thickness"); + + const int view_mode = RNA_enum_get(op->ptr, "view_mode"); + const int material_mode = RNA_enum_get(op->ptr, "material_mode"); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + + /* sanity checks */ + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + bool changed = false; + + float viewmat[4][4]; + copy_m4_m4(viewmat, rv3d->viewmat); + + switch (view_mode) { + case GP_PERIMETER_FRONT: + unit_m4(rv3d->viewmat); + viewmat[1][1] = 0.0f; + viewmat[1][2] = -1.0f; + + viewmat[2][1] = 1.0f; + viewmat[2][2] = 0.0f; + + viewmat[3][2] = -10.0f; + break; + case GP_PERIMETER_SIDE: + zero_m4(viewmat); + viewmat[0][2] = 1.0f; + viewmat[1][0] = 1.0f; + viewmat[2][1] = 1.0f; + viewmat[3][3] = 1.0f; + break; + case GP_PERIMETER_TOP: + unit_m4(viewmat); + break; + case GP_PERIMETER_CAMERA: { + Scene *scene = CTX_data_scene(C); + Object *cam_ob = scene->camera; + if (cam_ob != NULL) { + invert_m4_m4(viewmat, cam_ob->obmat); + } + break; + } + default: + break; + } + + /* Untag strokes to be sure nothing is pending. */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + gps->flag &= ~GP_STROKE_TAG; + } + } + } + /* Create a new material. */ + int mat_idx = 0; + if (material_mode == GP_STROKE_USE_NEW_MATERIAL) { + Material *ma = BKE_gpencil_object_material_new(bmain, ob, "Material", NULL); + MaterialGPencilStyle *gp_style = ma->gp_style; + + gp_style->flag |= GP_MATERIAL_FILL_SHOW; + mat_idx = ob->totcol - 1; + } + + /* loop all selected strokes */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->flag & GP_LAYER_HIDE) { + continue; + } + /* Prepare transform matrix. */ + float diff_mat[4][4]; + BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat); + + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } + + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + if ((gps->flag & GP_STROKE_SELECT) == 0) { + continue; + } + if (gps->totpoints == 0) { + continue; + } + if (!ED_gpencil_stroke_material_visible(ob, gps)) { + continue; + } + + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0); + + if (!is_stroke) { + continue; + } + + /* Duplicate the stroke to apply any layer thickness change. */ + bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false); + + /* Apply layer thickness change. */ + gps_duplicate->thickness += gpl->line_change; + /* Apply object scale to thickness. */ + gps_duplicate->thickness *= mat4_to_scale(ob->obmat); + CLAMP_MIN(gps_duplicate->thickness, 1.0f); + + /* Stroke. */ + const float ovr_thickness = keep ? thickness : 0.0f; + bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view( + viewmat, gpd, gpl, gps_duplicate, subdivisions, diff_mat, ovr_thickness); + gps_perimeter->flag &= ~GP_STROKE_SELECT; + /* Assign material. */ + switch (material_mode) { + case GP_STROKE_USE_ACTIVE_MATERIAL: { + if (ob->actcol - 1 < 0) { + gps_perimeter->mat_nr = 0; + } + else { + gps_perimeter->mat_nr = ob->actcol - 1; + } + break; + } + case GP_STROKE_USE_CURRENT_MATERIAL: + gps_perimeter->mat_nr = gps->mat_nr; + break; + case GP_STROKE_USE_NEW_MATERIAL: + gps_perimeter->mat_nr = mat_idx; + break; + default: + break; + } + + /* Sample stroke. */ + if (length > 0.0f) { + BKE_gpencil_stroke_sample(gpd, gps_perimeter, length, false, 0); + } + /* Set stroke thickness. */ + gps_perimeter->thickness = thickness; + + /* Set pressure constant. */ + bGPDspoint *pt; + for (int i = 0; i < gps_perimeter->totpoints; i++) { + pt = &gps_perimeter->points[i]; + pt->pressure = 1.0f; + } + + /* Add perimeter stroke to frame. */ + BLI_insertlinkafter(&gpf->strokes, gps, gps_perimeter); + + /* Tag original stroke to be removed. */ + gps->flag |= GP_STROKE_TAG; + + /* Free Temp stroke. */ + BKE_gpencil_free_stroke(gps_duplicate); + changed = true; + } + + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; + } + } + } + } + /* Free old strokes. */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_TAG) { + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + } + } + } + + if (changed) { + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_outline(wmOperatorType *ot) +{ + static const EnumPropertyItem view_mode[] = { + {GP_PERIMETER_VIEW, "VIEW", 0, "View", ""}, + {GP_PERIMETER_FRONT, "FRONT", 0, "Front", ""}, + {GP_PERIMETER_SIDE, "SIDE", 0, "Side", ""}, + {GP_PERIMETER_TOP, "TOP", 0, "Top", ""}, + {GP_PERIMETER_CAMERA, "CAMERA", 0, "Camera", ""}, + {0, NULL, 0, NULL, NULL}, + }; + static const EnumPropertyItem material_mode[] = { + {GP_STROKE_USE_ACTIVE_MATERIAL, "ACTIVE", 0, "Active Material", ""}, + {GP_STROKE_USE_CURRENT_MATERIAL, "KEEP", 0, "Keep Material", "Keep current stroke material"}, + {GP_STROKE_USE_NEW_MATERIAL, "NEW", 0, "New Material", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Convert Stroke to Outline"; + ot->idname = "GPENCIL_OT_stroke_outline"; + ot->description = "Convert stroke to perimeter"; + + /* api callbacks */ + ot->exec = gpencil_stroke_outline_exec; + ot->poll = gpencil_stroke_edit_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "view_mode", view_mode, GP_PERIMETER_VIEW, "View", ""); + RNA_def_enum(ot->srna, + "material_mode", + material_mode, + GP_STROKE_USE_ACTIVE_MATERIAL, + "Material Mode", + ""); + + RNA_def_int(ot->srna, + "thickness", + 1, + 1, + 1000, + "Thickness", + "Thickness of the stroke perimeter", + 1, + 1000); + RNA_def_boolean(ot->srna, + "keep", + true, + "Keep Shape", + "Try to keep global shape when the stroke thickness change"); + + RNA_def_int(ot->srna, "subdivisions", 3, 0, 10, "Subdivisions", "", 0, 10); + + RNA_def_float(ot->srna, "length", 0.0f, 0.0f, 100.0f, "Sample Length", "", 0.0f, 100.0f); +} + +/** \} */ + void GPENCIL_OT_recalc_geometry(wmOperatorType *ot) { /* identifiers */ @@ -4483,6 +4889,8 @@ void GPENCIL_OT_stroke_sample(wmOperatorType *ot) /* properties */ prop = RNA_def_float(ot->srna, "length", 0.1f, 0.0f, 100.0f, "Length", "", 0.0f, 100.0f); + prop = RNA_def_float( + ot->srna, "sharp_threshold", 0.1f, 0.0f, M_PI, "Sharp Threshold", "", 0.0f, M_PI); /* avoid re-using last var */ RNA_def_property_flag(prop, PROP_SKIP_SAVE); } diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 5305c764b3a..c173a30a736 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -67,6 +67,7 @@ #define LEAK_HORZ 0 #define LEAK_VERT 1 +#define FILL_LEAK 3.0f #define MIN_WINDOW_SIZE 128 /* Set to 1 to debug filling internal image. By default, the value must be 0. */ @@ -78,6 +79,20 @@ enum { GP_DRAWFILLS_ONLY3D = (1 << 1), /* only draw 3d-strokes */ }; +/* Temporary stroke data including stroke extensions. */ +typedef struct tStroke { + /* Referenced layer. */ + bGPDlayer *gpl; + /** Referenced frame. */ + bGPDframe *gpf; + /** Referenced stroke. */ + bGPDstroke *gps; + /** Extreme Stroke A. */ + bGPDstroke *gps_ext_a; + /** Extreme Stroke B. */ + bGPDstroke *gps_ext_b; +} tStroke; + /* Temporary fill operation data `op->customdata`. */ typedef struct tGPDfill { bContext *C; @@ -114,7 +129,7 @@ typedef struct tGPDfill { /** For operations that require occlusion testing. */ struct ViewDepths *depths; /** flags */ - short flag; + int flag; /** avoid too fast events */ short oldkey; /** send to back stroke */ @@ -140,6 +155,8 @@ typedef struct tGPDfill { int fill_simplylvl; /** boundary limits drawing mode */ int fill_draw_mode; + /** types of extensions **/ + int fill_extend_mode; /* scaling factor */ float fill_factor; @@ -157,7 +174,7 @@ typedef struct tGPDfill { Image *ima; /** temp points data */ BLI_Stack *stack; - /** handle for drawing strokes while operator is running 3d stuff */ + /** handle for drawing strokes while operator is running 3d stuff. */ void *draw_handle_3d; /* Temporary size x. */ @@ -174,12 +191,28 @@ typedef struct tGPDfill { /** Factor of extension. */ float fill_extend_fac; - + /** Size of stroke_array. */ + int stroke_array_num; + /** Temp strokes array to handle strokes and stroke extensions. */ + tStroke **stroke_array; } tGPDfill; bool skip_layer_check(short fill_layer_mode, int gpl_active_index, int gpl_index); static void gpencil_draw_boundary_lines(const struct bContext *UNUSED(C), struct tGPDfill *tgpf); +/* Free temp stroke array. */ +static void stroke_array_free(tGPDfill *tgpf) +{ + if (tgpf->stroke_array) { + for (int i = 0; i < tgpf->stroke_array_num; i++) { + tStroke *stroke = tgpf->stroke_array[i]; + MEM_freeN(stroke); + } + MEM_SAFE_FREE(tgpf->stroke_array); + } + tgpf->stroke_array_num = 0; +} + /* Delete any temporary stroke. */ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_frames) { @@ -197,7 +230,8 @@ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_ for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* free stroke */ - if ((gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG)) { + if ((gps->flag & GP_STROKE_NOFILL) && + (gps->flag & GP_STROKE_TAG || gps->flag & GP_STROKE_HELP)) { BLI_remlink(&gpf->strokes, gps); BKE_gpencil_free_stroke(gps); } @@ -209,6 +243,70 @@ static void gpencil_delete_temp_stroke_extension(tGPDfill *tgpf, const bool all_ } } +static bool extended_bbox_overlap( + float min1[3], float max1[3], float min2[3], float max2[3], float extend) +{ + for (int axis = 0; axis < 3; axis++) { + float intersection_min = max_ff(min1[axis], min2[axis]) - extend; + float intersection_max = min_ff(max1[axis], max2[axis]) + extend; + if (intersection_min > intersection_max) { + return false; + } + } + return true; +} + +static void add_stroke_extension(bGPDframe *gpf, bGPDstroke *gps, float p1[3], float p2[3]) +{ + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + bGPDspoint *pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, p1); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + copy_v3_v3(&pt->x, p2); + pt->strength = 1.0f; + pt->pressure = 1.0f; +} + +static void add_endpoint_radius_help(tGPDfill *tgpf, + bGPDframe *gpf, + bGPDstroke *gps, + const float endpoint[3], + const float radius, + const bool focused) +{ + float circumference = 2.0f * M_PI * radius; + float vertex_spacing = 0.005f; + int num_vertices = min_ii(max_ii((int)ceilf(circumference / vertex_spacing), 3), 40); + + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, num_vertices, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_CYCLIC | GP_STROKE_HELP; + if (focused) { + gps_new->flag |= GP_STROKE_TAG; + } + BLI_addtail(&gpf->strokes, gps_new); + + for (int i = 0; i < num_vertices; i++) { + float angle = ((float)i / (float)num_vertices) * 2.0f * M_PI; + bGPDspoint *pt = &gps_new->points[i]; + pt->x = endpoint[0] + radius * cosf(angle); + pt->y = endpoint[1]; + pt->z = endpoint[2] + radius * sinf(angle); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + /* Rotate to object rotation. */ + sub_v3_v3(&pt->x, endpoint); + mul_mat3_m4_v3(tgpf->ob->obmat, &pt->x); + add_v3_v3(&pt->x, endpoint); + } +} + static void extrapolate_points_by_length(bGPDspoint *a, bGPDspoint *b, float length, @@ -221,8 +319,42 @@ static void extrapolate_points_by_length(bGPDspoint *a, add_v3_v3v3(r_point, &b->x, ab); } -/* Loop all layers create stroke extensions. */ -static void gpencil_create_extensions(tGPDfill *tgpf) +/* Calculate the size of the array for strokes. */ +static void gpencil_strokes_array_size(tGPDfill *tgpf) +{ + bGPdata *gpd = tgpf->gpd; + Brush *brush = tgpf->brush; + BrushGpencilSettings *brush_settings = brush->gpencil_settings; + + bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd); + BLI_assert(gpl_active != NULL); + + const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active); + BLI_assert(gpl_active_index >= 0); + + tgpf->stroke_array_num = 0; + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->flag & GP_LAYER_HIDE) { + continue; + } + + /* Decide if the strokes of layers are included or not depending on the layer mode. */ + const int gpl_index = BLI_findindex(&gpd->layers, gpl); + bool skip = skip_layer_check(brush_settings->fill_layer_mode, gpl_active_index, gpl_index); + if (skip) { + continue; + } + + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, tgpf->active_cfra, GP_GETFRAME_USE_PREV); + if (gpf == NULL) { + continue; + } + tgpf->stroke_array_num += BLI_listbase_count(&gpf->strokes); + } +} + +/* Load all strokes to be procesed by extend lines. */ +static void gpencil_load_array_strokes(tGPDfill *tgpf) { Object *ob = tgpf->ob; bGPdata *gpd = tgpf->gpd; @@ -235,6 +367,14 @@ static void gpencil_create_extensions(tGPDfill *tgpf) const int gpl_active_index = BLI_findindex(&gpd->layers, gpl_active); BLI_assert(gpl_active_index >= 0); + /* Create array of strokes. */ + gpencil_strokes_array_size(tgpf); + if (tgpf->stroke_array_num == 0) { + return; + } + + tgpf->stroke_array = MEM_callocN(sizeof(tStroke *) * tgpf->stroke_array_num, __func__); + int idx = 0; LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { if (gpl->flag & GP_LAYER_HIDE) { continue; @@ -257,58 +397,379 @@ static void gpencil_create_extensions(tGPDfill *tgpf) if ((gps->points == NULL) || (gps->totpoints < 2)) { continue; } - if (gps->flag & (GP_STROKE_NOFILL | GP_STROKE_TAG)) { - continue; - } /* Check if the color is visible. */ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); if ((gp_style == NULL) || (gp_style->flag & GP_MATERIAL_HIDE)) { continue; } + /* Don't include temp strokes. */ + if ((gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG)) { + continue; + } + + tStroke *stroke = MEM_callocN(sizeof(tStroke), "temp stroke data"); + stroke->gpl = gpl; + stroke->gpf = gpf; + stroke->gps = gps; + + /* Create the extension strokes only for Lines. */ + if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { + /* Extend start. */ + bGPDspoint *pt0 = &gps->points[1]; + bGPDspoint *pt1 = &gps->points[0]; + stroke->gps_ext_a = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + stroke->gps_ext_a->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + stroke->gps_ext_a->fill_opacity_fac = FLT_MAX; + BLI_addtail(&gpf->strokes, stroke->gps_ext_a); + + bGPDspoint *pt = &stroke->gps_ext_a->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &stroke->gps_ext_a->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + + /* Extend end. */ + pt0 = &gps->points[gps->totpoints - 2]; + pt1 = &gps->points[gps->totpoints - 1]; + stroke->gps_ext_b = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + stroke->gps_ext_b->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + stroke->gps_ext_b->fill_opacity_fac = FLT_MAX; + BLI_addtail(&gpf->strokes, stroke->gps_ext_b); + + pt = &stroke->gps_ext_b->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &stroke->gps_ext_b->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + } + else { + stroke->gps_ext_a = NULL; + stroke->gps_ext_b = NULL; + } + + tgpf->stroke_array[idx] = stroke; + + idx++; + } + } + tgpf->stroke_array_num = idx; +} + +static void set_stroke_collide(bGPDstroke *gps_a, bGPDstroke *gps_b, const float connection_dist) +{ + gps_a->flag |= GP_STROKE_COLLIDE; + gps_b->flag |= GP_STROKE_COLLIDE; + + /* It uses `fill_opacity_fac` to store distance because this variable is never + * used by this type of strokes and can be used for these + * temp strokes without adding new variables to the bGPStroke struct. */ + gps_a->fill_opacity_fac = connection_dist; + gps_b->fill_opacity_fac = connection_dist; +} + +/* Cut the extended lines if collide. */ +static void gpencil_cut_extensions(tGPDfill *tgpf) +{ + const float connection_dist = tgpf->fill_extend_fac * 0.1f; + + bGPDlayer *gpl_prev = NULL; + bGPDframe *gpf_prev = NULL; + float diff_mat[4][4], inv_mat[4][4]; + + /* Allocate memory for all extend strokes. */ + bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * tgpf->stroke_array_num * 2, + __func__); + + for (int idx = 0; idx < tgpf->stroke_array_num; idx++) { + tStroke *stroke = tgpf->stroke_array[idx]; + bGPDframe *gpf = stroke->gpf; + if (stroke->gpl != gpl_prev) { + BKE_gpencil_layer_transform_matrix_get(tgpf->depsgraph, tgpf->ob, stroke->gpl, diff_mat); + invert_m4_m4(inv_mat, diff_mat); + gpl_prev = stroke->gpl; + } + + if (gpf == gpf_prev) { + continue; + } + gpf_prev = gpf; + + /* Store all frame extend strokes in an array. */ + int tot_idx = 0; + for (int i = 0; i < tgpf->stroke_array_num; i++) { + tStroke *s = tgpf->stroke_array[i]; + if (s->gpf != gpf) { + continue; + } + if ((s->gps_ext_a) && ((s->gps_ext_a->flag & GP_STROKE_COLLIDE) == 0)) { + gps_array[tot_idx] = s->gps_ext_a; + tot_idx++; + } + if ((s->gps_ext_b) && ((s->gps_ext_b->flag & GP_STROKE_COLLIDE) == 0)) { + gps_array[tot_idx] = s->gps_ext_b; + tot_idx++; + } + } + + /* Compare all strokes. */ + for (int i = 0; i < tot_idx; i++) { + bGPDstroke *gps_a = gps_array[i]; + + bGPDspoint pt2; + float a1xy[2], a2xy[2]; + float b1xy[2], b2xy[2]; + + /* First stroke. */ + bGPDspoint *pt = &gps_a->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a1xy[0], &a1xy[1]); + + pt = &gps_a->points[1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_a, &pt2, &a2xy[0], &a2xy[1]); + bGPDspoint *extreme_a = &gps_a->points[1]; + + /* Loop all other strokes and check the intersections. */ + for (int z = 0; z < tot_idx; z++) { + bGPDstroke *gps_b = gps_array[z]; + /* Don't check stroke with itself. */ + if (i == z) { + continue; + } + + /* Don't check strokes unless the bounding boxes of the strokes + * are close enough together that they can plausibly be connected. */ + if (!extended_bbox_overlap(gps_a->boundbox_min, + gps_a->boundbox_max, + gps_b->boundbox_min, + gps_b->boundbox_max, + connection_dist)) { + continue; + } + + pt = &gps_b->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b1xy[0], &b1xy[1]); + + pt = &gps_b->points[1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt2); + gpencil_point_to_xy_fl(&tgpf->gsc, gps_b, &pt2, &b2xy[0], &b2xy[1]); + bGPDspoint *extreme_b = &gps_b->points[1]; + + /* Check if extreme points are near. This case is when the + * extendend lines are colinear or parallel and close together. */ + const float gap_pixsize_sq = 25.0f; + float intersection3D[3]; + if (len_squared_v2v2(a2xy, b2xy) <= gap_pixsize_sq) { + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, b2xy, intersection3D); + mul_m4_v3(inv_mat, intersection3D); + copy_v3_v3(&extreme_a->x, intersection3D); + copy_v3_v3(&extreme_b->x, intersection3D); + set_stroke_collide(gps_a, gps_b, connection_dist); + continue; + } + /* Check if extensions cross. */ + if (isect_seg_seg_v2_simple(a1xy, a2xy, b1xy, b2xy)) { + float intersection[2]; + isect_line_line_v2_point(a1xy, a2xy, b1xy, b2xy, intersection); + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, intersection, intersection3D); + mul_m4_v3(inv_mat, intersection3D); + copy_v3_v3(&extreme_a->x, intersection3D); + copy_v3_v3(&extreme_b->x, intersection3D); + set_stroke_collide(gps_a, gps_b, connection_dist); + continue; + } + /* Check if extension extreme is near of the origin of any other extension. */ + if (len_squared_v2v2(a2xy, b1xy) <= gap_pixsize_sq) { + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, b1xy, &extreme_a->x); + mul_m4_v3(inv_mat, &extreme_a->x); + set_stroke_collide(gps_a, gps_b, connection_dist); + continue; + } + if (len_squared_v2v2(a1xy, b2xy) <= gap_pixsize_sq) { + gpencil_point_xy_to_3d(&tgpf->gsc, tgpf->scene, a1xy, &extreme_b->x); + mul_m4_v3(inv_mat, &extreme_b->x); + set_stroke_collide(gps_a, gps_b, connection_dist); + } + } + } + } + MEM_SAFE_FREE(gps_array); +} + +/* Loop all strokes and update stroke line extensions. */ +static void gpencil_update_extensions_line(tGPDfill *tgpf) +{ + float connection_dist = tgpf->fill_extend_fac * 0.1f; - /* Extend start. */ + for (int idx = 0; idx < tgpf->stroke_array_num; idx++) { + tStroke *stroke = tgpf->stroke_array[idx]; + bGPDstroke *gps = stroke->gps; + bGPDstroke *gps_a = stroke->gps_ext_a; + bGPDstroke *gps_b = stroke->gps_ext_b; + + /* Extend start. */ + if (((gps_a->flag & GP_STROKE_COLLIDE) == 0) || (gps_a->fill_opacity_fac > connection_dist)) { bGPDspoint *pt0 = &gps->points[1]; bGPDspoint *pt1 = &gps->points[0]; - bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); - gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_new); - - bGPDspoint *pt = &gps_new->points[0]; - copy_v3_v3(&pt->x, &pt1->x); - pt->strength = 1.0f; - pt->pressure = 1.0f; - - pt = &gps_new->points[1]; - pt->strength = 1.0f; - pt->pressure = 1.0f; - extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x); - - /* Extend end. */ - pt0 = &gps->points[gps->totpoints - 2]; - pt1 = &gps->points[gps->totpoints - 1]; - gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); - gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_new); - - pt = &gps_new->points[0]; - copy_v3_v3(&pt->x, &pt1->x); - pt->strength = 1.0f; - pt->pressure = 1.0f; - - pt = &gps_new->points[1]; - pt->strength = 1.0f; - pt->pressure = 1.0f; - extrapolate_points_by_length(pt0, pt1, tgpf->fill_extend_fac * 0.1f, &pt->x); + bGPDspoint *pt = &gps_a->points[1]; + extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x); + gps_a->flag &= ~GP_STROKE_COLLIDE; + } + + /* Extend end. */ + if (((gps_b->flag & GP_STROKE_COLLIDE) == 0) || (gps_b->fill_opacity_fac > connection_dist)) { + bGPDspoint *pt0 = &gps->points[gps->totpoints - 2]; + bGPDspoint *pt1 = &gps->points[gps->totpoints - 1]; + bGPDspoint *pt = &gps_b->points[1]; + extrapolate_points_by_length(pt0, pt1, connection_dist, &pt->x); + gps_b->flag &= ~GP_STROKE_COLLIDE; } } + + /* Cut overlength strokes. */ + gpencil_cut_extensions(tgpf); +} + +/* Loop all strokes and create stroke radius extensions. */ +static void gpencil_create_extensions_radius(tGPDfill *tgpf) +{ + float connection_dist = tgpf->fill_extend_fac * 0.1f; + GSet *connected_endpoints = BLI_gset_ptr_new(__func__); + + for (int idx = 0; idx < tgpf->stroke_array_num; idx++) { + tStroke *stroke = tgpf->stroke_array[idx]; + bGPDframe *gpf = stroke->gpf; + bGPDstroke *gps = stroke->gps; + + /* Find points of high curvature. */ + float tan1[3]; + float tan2[3]; + float d1; + float d2; + float total_length = 0.f; + for (int i = 1; i < gps->totpoints; i++) { + if (i > 1) { + copy_v3_v3(tan1, tan2); + d1 = d2; + } + bGPDspoint *pt1 = &gps->points[i - 1]; + bGPDspoint *pt2 = &gps->points[i]; + sub_v3_v3v3(tan2, &pt2->x, &pt1->x); + d2 = normalize_v3(tan2); + total_length += d2; + if (i > 1) { + float curvature[3]; + sub_v3_v3v3(curvature, tan2, tan1); + float k = normalize_v3(curvature); + k /= min_ff(d1, d2); + float radius = 1.f / k; + /* + * The smaller the radius of curvature, the sharper the corner. + * The thicker the line, the larger the radius of curvature it + * takes to be visually indistinguishable from an endpoint. + */ + float min_radius = gps->thickness * 0.0001f; + + if (radius < min_radius) { + /* Extend along direction of curvature. */ + bGPDstroke *gps_new = BKE_gpencil_stroke_new(gps->mat_nr, 2, gps->thickness); + gps_new->flag |= GP_STROKE_NOFILL | GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_new); + + bGPDspoint *pt = &gps_new->points[0]; + copy_v3_v3(&pt->x, &pt1->x); + pt->strength = 1.0f; + pt->pressure = 1.0f; + + pt = &gps_new->points[1]; + pt->strength = 1.0f; + pt->pressure = 1.0f; + mul_v3_fl(curvature, -connection_dist); + add_v3_v3v3(&pt->x, &pt1->x, curvature); + } + } + } + + /* Connect endpoints within a radius */ + float *stroke1_start = &gps->points[0].x; + float *stroke1_end = &gps->points[gps->totpoints - 1].x; + /* Connect the start of the stroke to its own end if the whole stroke + * isn't already so short that it's within that distance + */ + if (len_v3v3(stroke1_start, stroke1_end) < connection_dist && total_length > connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke1_end); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke1_end); + } + for (bGPDstroke *gps2 = (bGPDstroke *)(((Link *)gps)->next); gps2 != NULL; + gps2 = (bGPDstroke *)(((Link *)gps2)->next)) { + /* Don't check distance to temporary extensions. */ + if ((gps2->flag & GP_STROKE_NOFILL) && (gps2->flag & GP_STROKE_TAG)) { + continue; + } + + /* Don't check endpoint distances unless the bounding boxes of the strokes + are close enough together that they can plausibly be connected. */ + if (!extended_bbox_overlap(gps->boundbox_min, + gps->boundbox_max, + gps2->boundbox_min, + gps2->boundbox_max, + connection_dist)) { + continue; + } + + float *stroke2_start = &gps2->points[0].x; + float *stroke2_end = &gps2->points[gps2->totpoints - 1].x; + if (len_v3v3(stroke1_start, stroke2_start) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke2_start); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke2_start); + } + if (len_v3v3(stroke1_start, stroke2_end) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_start, stroke2_end); + BLI_gset_add(connected_endpoints, stroke1_start); + BLI_gset_add(connected_endpoints, stroke2_end); + } + if (len_v3v3(stroke1_end, stroke2_start) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_end, stroke2_start); + BLI_gset_add(connected_endpoints, stroke1_end); + BLI_gset_add(connected_endpoints, stroke2_start); + } + if (len_v3v3(stroke1_end, stroke2_end) < connection_dist) { + add_stroke_extension(gpf, gps, stroke1_end, stroke2_end); + BLI_gset_add(connected_endpoints, stroke1_end); + BLI_gset_add(connected_endpoints, stroke2_end); + } + } + + bool start_connected = BLI_gset_haskey(connected_endpoints, stroke1_start); + bool end_connected = BLI_gset_haskey(connected_endpoints, stroke1_end); + add_endpoint_radius_help(tgpf, gpf, gps, stroke1_start, connection_dist, start_connected); + add_endpoint_radius_help(tgpf, gpf, gps, stroke1_end, connection_dist, end_connected); + } + + BLI_gset_free(connected_endpoints, NULL); } static void gpencil_update_extend(tGPDfill *tgpf) { - gpencil_delete_temp_stroke_extension(tgpf, false); + if (tgpf->stroke_array == NULL) { + gpencil_load_array_strokes(tgpf); + } - if (tgpf->fill_extend_fac > 0.0f) { - gpencil_create_extensions(tgpf); + if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { + gpencil_update_extensions_line(tgpf); + } + else { + gpencil_delete_temp_stroke_extension(tgpf, false); + gpencil_create_extensions_radius(tgpf); } WM_event_add_notifier(tgpf->C, NC_GPENCIL | NA_EDITED, NULL); } @@ -322,15 +783,16 @@ static bool gpencil_stroke_is_drawable(tGPDfill *tgpf, bGPDstroke *gps) const bool show_help = (tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) != 0; const bool show_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) != 0; const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG); + const bool is_extend_help = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_HELP); if ((!show_help) && (show_extend)) { - if (!is_extend) { + if (!is_extend && !is_extend_help) { return false; } } if ((show_help) && (!show_extend)) { - if (is_extend) { + if (is_extend || is_extend_help) { return false; } } @@ -357,13 +819,29 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, float fpt[3]; float col[4]; const float extend_col[4] = {0.0f, 1.0f, 1.0f, 1.0f}; - const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG); + const float help_col[4] = {1.0f, 0.0f, 0.5f, 1.0f}; + const bool is_extend = (gps->flag & GP_STROKE_NOFILL) && (gps->flag & GP_STROKE_TAG) && + !(gps->flag & GP_STROKE_HELP); + const bool is_help = gps->flag & GP_STROKE_HELP; if (!gpencil_stroke_is_drawable(tgpf, gps)) { return; } - if ((is_extend) && (!tgpf->is_render)) { + if (is_help && tgpf->is_render) { + /* Help strokes are for display only and shouldn't render. */ + return; + } + else if (is_help) { + /* Color help strokes that won't affect fill or render separately from + * extended strokes, as they will affect them. */ + copy_v4_v4(col, help_col); + + /* If there is contact, hide the circles to avoid noise and keep the focus + * in the pending gaps. */ + col[3] = (gps->flag & GP_STROKE_TAG) ? 0.0f : 0.5f; + } + else if ((is_extend) && (!tgpf->is_render)) { copy_v4_v4(col, extend_col); } else { @@ -379,7 +857,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, immBindBuiltinProgram(GPU_SHADER_3D_FLAT_COLOR); /* draw stroke curve */ - GPU_line_width((!is_extend) ? thickness : thickness * 2.0f); + GPU_line_width((!is_extend && !is_help) ? thickness : thickness * 2.0f); immBeginAtMost(GPU_PRIM_LINE_STRIP, totpoints + cyclic_add); const bGPDspoint *pt = points; @@ -390,7 +868,7 @@ static void gpencil_draw_basic_stroke(tGPDfill *tgpf, CLAMP(alpha, 0.0f, 1.0f); col[3] = alpha <= thershold ? 0.0f : 1.0f; } - else { + else if (!is_help) { col[3] = 1.0f; } /* set point */ @@ -582,7 +1060,8 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) /* Normal strokes. */ if (ELEM(tgpf->fill_draw_mode, GP_FILL_DMODE_STROKE, GP_FILL_DMODE_BOTH)) { - if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0)) { + if (gpencil_stroke_is_drawable(tgpf, gps) && ((gps->flag & GP_STROKE_TAG) == 0) && + ((gps->flag & GP_STROKE_HELP) == 0)) { ED_gpencil_draw_fill(&tgpw); } } @@ -1677,7 +2156,8 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) /* Helper: Draw status message while the user is running the operator */ static void gpencil_fill_status_indicators(bContext *C) { - const char *status_str = TIP_("Fill: ESC/RMB cancel, LMB Fill, Shift Draw on Back"); + const char *status_str = TIP_( + "Fill: ESC/RMB cancel, LMB Fill, Shift Draw on Back, S: Switch Mode"); ED_workspace_status_text(C, status_str); } @@ -1777,13 +2257,17 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *op) tgpf->fill_threshold = brush->gpencil_settings->fill_threshold; tgpf->fill_simplylvl = brush->gpencil_settings->fill_simplylvl; tgpf->fill_draw_mode = brush->gpencil_settings->fill_draw_mode; + tgpf->fill_extend_mode = brush->gpencil_settings->fill_extend_mode; tgpf->fill_extend_fac = brush->gpencil_settings->fill_extend_fac; tgpf->fill_factor = max_ff(GPENCIL_MIN_FILL_FAC, min_ff(brush->gpencil_settings->fill_factor, GPENCIL_MAX_FILL_FAC)); - tgpf->fill_leak = (int)ceil((float)brush->gpencil_settings->fill_leak * tgpf->fill_factor); + tgpf->fill_leak = (int)ceil(FILL_LEAK * tgpf->fill_factor); int totcol = tgpf->ob->totcol; + /* Extensions array */ + tgpf->stroke_array = NULL; + /* get color info */ Material *ma = BKE_gpencil_object_material_ensure_from_active_input_brush( bmain, tgpf->ob, brush); @@ -1832,6 +2316,9 @@ static void gpencil_fill_exit(bContext *C, wmOperator *op) MEM_SAFE_FREE(tgpf->sbuffer); MEM_SAFE_FREE(tgpf->depth_arr); + /* Clean temp strokes. */ + stroke_array_free(tgpf); + /* Remove any temp stroke. */ gpencil_delete_temp_stroke_extension(tgpf, true); @@ -1928,9 +2415,8 @@ static int gpencil_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE tgpf = op->customdata; /* Enable custom drawing handlers to show help lines */ - const bool do_extend = (tgpf->fill_extend_fac > 0.0f); - const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || - ((tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) && (do_extend))); + const bool do_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES); + const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || (do_extend)); if (help_lines) { tgpf->draw_handle_3d = ED_region_draw_cb_activate( @@ -2179,9 +2665,8 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) const bool is_inverted = (is_brush_inv && (event->modifier & KM_CTRL) == 0) || (!is_brush_inv && (event->modifier & KM_CTRL) != 0); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(tgpf->gpd); - const bool do_extend = (tgpf->fill_extend_fac > 0.0f); - const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || - ((tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) && (do_extend))); + const bool do_extend = (tgpf->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES); + const bool help_lines = ((tgpf->flag & GP_BRUSH_FILL_SHOW_HELPLINES) || (do_extend)); int estate = OPERATOR_RUNNING_MODAL; switch (event->type) { @@ -2315,6 +2800,22 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) } tgpf->oldkey = event->type; break; + case EVT_SKEY: + if ((do_extend) && (event->val == KM_PRESS)) { + /* Clean temp strokes. */ + stroke_array_free(tgpf); + + /* Toogle mode */ + if (tgpf->fill_extend_mode == GP_FILL_EMODE_EXTEND) { + tgpf->fill_extend_mode = GP_FILL_EMODE_RADIUS; + } + else { + tgpf->fill_extend_mode = GP_FILL_EMODE_EXTEND; + } + gpencil_delete_temp_stroke_extension(tgpf, true); + gpencil_update_extend(tgpf); + } + break; case EVT_PAGEUPKEY: case WHEELUPMOUSE: if (tgpf->oldkey == 1) { diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index d656241c463..4d62f834d86 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -593,6 +593,7 @@ void GPENCIL_OT_stroke_cyclical_set(struct wmOperatorType *ot); */ void GPENCIL_OT_stroke_caps_set(struct wmOperatorType *ot); void GPENCIL_OT_stroke_join(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_start_set(struct wmOperatorType *ot); void GPENCIL_OT_stroke_flip(struct wmOperatorType *ot); void GPENCIL_OT_stroke_subdivide(struct wmOperatorType *ot); void GPENCIL_OT_stroke_simplify(struct wmOperatorType *ot); @@ -608,6 +609,7 @@ void GPENCIL_OT_stroke_merge_by_distance(struct wmOperatorType *ot); void GPENCIL_OT_stroke_merge_material(struct wmOperatorType *ot); void GPENCIL_OT_stroke_reset_vertex_color(struct wmOperatorType *ot); void GPENCIL_OT_stroke_normalize(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_outline(struct wmOperatorType *ot); void GPENCIL_OT_material_to_vertex_color(struct wmOperatorType *ot); void GPENCIL_OT_extract_palette_vertex(struct wmOperatorType *ot); diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index e7a4f2fe2dc..dc63acf5136 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -1483,7 +1483,8 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) */ static const EnumPropertyItem gpencil_interpolation_type_items[] = { /* Interpolation. */ - RNA_ENUM_ITEM_HEADING(N_("Interpolation"), "Standard transitions between keyframes"), + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Interpolation"), + N_("Standard transitions between keyframes")), {GP_IPO_LINEAR, "LINEAR", ICON_IPO_LINEAR, @@ -1496,9 +1497,9 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) "Custom interpolation defined using a curve map"}, /* Easing. */ - RNA_ENUM_ITEM_HEADING(N_("Easing (by strength)"), - "Predefined inertial transitions, useful for motion graphics " - "(from least to most \"dramatic\")"), + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Easing (by strength)"), + N_("Predefined inertial transitions, useful for motion graphics " + "(from least to most \"dramatic\")")), {GP_IPO_SINE, "SINE", ICON_IPO_SINE, @@ -1515,7 +1516,8 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) "Circular", "Circular easing (strongest and most dynamic)"}, - RNA_ENUM_ITEM_HEADING(N_("Dynamic Effects"), "Simple physics-inspired easing effects"), + RNA_ENUM_ITEM_HEADING(CTX_N_(BLT_I18NCONTEXT_ID_GPENCIL, "Dynamic Effects"), + N_("Simple physics-inspired easing effects")), {GP_IPO_BACK, "BACK", ICON_IPO_BACK, "Back", "Cubic easing with overshoot and settle"}, {GP_IPO_BOUNCE, "BOUNCE", @@ -1569,6 +1571,7 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) /* identifiers */ ot->name = "Interpolate Sequence"; ot->idname = "GPENCIL_OT_interpolate_sequence"; + ot->translation_context = BLT_I18NCONTEXT_ID_GPENCIL; ot->description = "Generate 'in-betweens' to smoothly interpolate between Grease Pencil frames"; /* api callbacks */ diff --git a/source/blender/editors/gpencil/gpencil_mesh.cc b/source/blender/editors/gpencil/gpencil_mesh.cc index b27e1c75746..739a1b319c3 100644 --- a/source/blender/editors/gpencil/gpencil_mesh.cc +++ b/source/blender/editors/gpencil/gpencil_mesh.cc @@ -213,7 +213,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op) bool newob = false; if (target == GP_TARGET_OB_SELECTED) { - ob_gpencil = BKE_view_layer_non_active_selected_object(CTX_data_view_layer(C), v3d); + ob_gpencil = BKE_view_layer_non_active_selected_object(scene, CTX_data_view_layer(C), v3d); if (ob_gpencil != nullptr) { if (ob_gpencil->type != OB_GPENCIL) { BKE_report(op->reports, RPT_WARNING, "Target object not a grease pencil, ignoring!"); @@ -315,7 +315,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op) LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if ((gps->flag & GP_STROKE_TAG) == 0) { ED_gpencil_stroke_reproject( - depsgraph, &gsc, sctx, gpl, gpf, gps, project_type, false); + depsgraph, &gsc, sctx, gpl, gpf, gps, project_type, false, 0.0f); gps->flag |= GP_STROKE_TAG; } } diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 99e28270c3e..85cc281ca90 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -621,6 +621,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_stroke_caps_set); WM_operatortype_append(GPENCIL_OT_stroke_join); WM_operatortype_append(GPENCIL_OT_stroke_flip); + WM_operatortype_append(GPENCIL_OT_stroke_start_set); WM_operatortype_append(GPENCIL_OT_stroke_subdivide); WM_operatortype_append(GPENCIL_OT_stroke_simplify); WM_operatortype_append(GPENCIL_OT_stroke_simplify_fixed); @@ -635,6 +636,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_stroke_merge_material); WM_operatortype_append(GPENCIL_OT_stroke_reset_vertex_color); WM_operatortype_append(GPENCIL_OT_stroke_normalize); + WM_operatortype_append(GPENCIL_OT_stroke_outline); WM_operatortype_append(GPENCIL_OT_material_to_vertex_color); WM_operatortype_append(GPENCIL_OT_extract_palette_vertex); diff --git a/source/blender/editors/gpencil/gpencil_ops_versioning.c b/source/blender/editors/gpencil/gpencil_ops_versioning.c index 8119646137c..50fbafff732 100644 --- a/source/blender/editors/gpencil/gpencil_ops_versioning.c +++ b/source/blender/editors/gpencil/gpencil_ops_versioning.c @@ -92,7 +92,7 @@ static int gpencil_convert_old_files_exec(bContext *C, wmOperator *op) if ((!is_annotation) && (view_layer != NULL)) { Object *ob; ob = BKE_object_add_for_data( - bmain, view_layer, OB_GPENCIL, "GP_Scene", &scene->gpd->id, false); + bmain, scene, view_layer, OB_GPENCIL, "GP_Scene", &scene->gpd->id, false); zero_v3(ob->loc); DEG_relations_tag_update(bmain); /* added object */ diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 13ea5179b23..7446c727a0c 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -918,6 +918,67 @@ static void gpencil_stroke_unselect(bGPdata *gpd, bGPDstroke *gps) } } +static bGPDstroke *gpencil_stroke_to_outline(tGPsdata *p, bGPDstroke *gps) +{ + bGPDlayer *gpl = p->gpl; + RegionView3D *rv3d = p->region->regiondata; + Brush *brush = p->brush; + BrushGpencilSettings *gpencil_settings = brush->gpencil_settings; + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(p->ob, gps->mat_nr + 1); + const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0); + + if (!is_stroke) { + return gps; + } + + /* Duplicate the stroke to apply any layer thickness change. */ + bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false); + + /* Apply layer thickness change. */ + gps_duplicate->thickness += gpl->line_change; + /* Apply object scale to thickness. */ + gps_duplicate->thickness *= mat4_to_scale(p->ob->obmat); + CLAMP_MIN(gps_duplicate->thickness, 1.0f); + + /* Stroke. */ + float diff_mat[4][4]; + unit_m4(diff_mat); + const float outline_thickness = (float)brush->size * gpencil_settings->outline_fac * 0.5f; + bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view( + rv3d->viewmat, p->gpd, gpl, gps_duplicate, 3, diff_mat, outline_thickness); + /* Assign material. */ + if (gpencil_settings->material_alt == NULL) { + gps_perimeter->mat_nr = gps->mat_nr; + } + else { + Material *ma = gpencil_settings->material_alt; + int mat_idx = BKE_gpencil_material_find_index_by_name_prefix(p->ob, ma->id.name + 2); + if (mat_idx > -1) { + gps_perimeter->mat_nr = mat_idx; + } + else { + gps_perimeter->mat_nr = gps->mat_nr; + } + } + + /* Set pressure constant. */ + gps_perimeter->thickness = max_ii((int)outline_thickness, 1); + + bGPDspoint *pt; + for (int i = 0; i < gps_perimeter->totpoints; i++) { + pt = &gps_perimeter->points[i]; + pt->pressure = 1.0f; + } + + /* Remove original stroke. */ + BKE_gpencil_free_stroke(gps); + + /* Free Temp stroke. */ + BKE_gpencil_free_stroke(gps_duplicate); + + return gps_perimeter; +} + /* make a new stroke from the buffer data */ static void gpencil_stroke_newfrombuffer(tGPsdata *p) { @@ -1221,6 +1282,23 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) BKE_gpencil_stroke_simplify_adaptive(gpd, gps, brush->gpencil_settings->simplify_f); } + /* Set material index. */ + gps->mat_nr = BKE_gpencil_object_material_get_index_from_brush(p->ob, p->brush); + if (gps->mat_nr < 0) { + if (p->ob->actcol - 1 < 0) { + gps->mat_nr = 0; + } + else { + gps->mat_nr = p->ob->actcol - 1; + } + } + + /* Convert to Outline. */ + if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && + (brush->gpencil_settings->flag & GP_BRUSH_OUTLINE_STROKE)) { + gps = gpencil_stroke_to_outline(p, gps); + } + /* reproject to plane (only in 3d space) */ gpencil_reproject_toplane(p, gps); /* change position relative to parent object */ @@ -1235,17 +1313,6 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) } } - /* Save material index */ - gps->mat_nr = BKE_gpencil_object_material_get_index_from_brush(p->ob, p->brush); - if (gps->mat_nr < 0) { - if (p->ob->actcol - 1 < 0) { - gps->mat_nr = 0; - } - else { - gps->mat_nr = p->ob->actcol - 1; - } - } - /* add stroke to frame, usually on tail of the listbase, but if on back is enabled the stroke * is added on listbase head because the drawing order is inverse and the head stroke is the * first to draw. This is very useful for artist when drawing the background. @@ -2343,7 +2410,7 @@ static void gpencil_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_ptr) if (p->paintmode == GP_PAINTMODE_ERASER) { GPUVertFormat *format = immVertexFormat(); const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); @@ -2353,7 +2420,7 @@ static void gpencil_draw_eraser(bContext *UNUSED(C), int x, int y, void *p_ptr) immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -3264,7 +3331,7 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event return OPERATOR_RUNNING_MODAL; } -/* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ +/* gpencil modal operator stores area, which can be removed while using it (like full-screen). */ static bool gpencil_area_exists(bContext *C, ScrArea *area_test) { bScreen *screen = CTX_wm_screen(C); @@ -3658,9 +3725,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } } - /* Exit painting mode (and/or end current stroke). - * - */ + /* Exit painting mode (and/or end current stroke). */ if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, EVT_ESCKEY, EVT_SPACEKEY)) { p->status = GP_STATUS_DONE; @@ -3823,7 +3888,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } } - /* gpencil modal operator stores area, which can be removed while using it (like fullscreen) */ + /* gpencil modal operator stores area, which can be removed while using it (like full-screen). */ if (0 == gpencil_area_exists(C, p->area)) { estate = OPERATOR_CANCELLED; } diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 70f12151fdd..4a4fffc9638 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -1024,8 +1024,10 @@ static void gpencil_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) gpd->runtime.sbuffer, &gpd->runtime.sbuffer_size, &gpd->runtime.sbuffer_used, false); /* add small offset to keep stroke over the surface */ - if ((depth_arr) && (gpd->zdepth_offset > 0.0f) && (depth_arr[i] != DEPTH_INVALID)) { - depth_arr[i] *= (1.0f - (gpd->zdepth_offset / 1000.0f)); + if (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_VIEW) { + if ((depth_arr) && (gpd->zdepth_offset > 0.0f) && (depth_arr[i] != DEPTH_INVALID)) { + depth_arr[i] *= (1.0f - (gpd->zdepth_offset / 1000.0f)); + } } /* convert screen-coordinates to 3D coordinates */ diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c index e27cd255217..52e6200978c 100644 --- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c +++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c @@ -516,7 +516,7 @@ static void gpencil_brush_grab_calc_dvec(tGP_BrushEditData *gso) float mval_f[2]; - /* convert from 2D screenspace to 3D... */ + /* Convert from 2D screen-space to 3D. */ mval_f[0] = (float)(gso->mval[0] - gso->mval_prev[0]); mval_f[1] = (float)(gso->mval[1] - gso->mval_prev[1]); @@ -700,8 +700,8 @@ static bool gpencil_brush_pinch_apply(tGP_BrushEditData *gso, /* ----------------------------------------------- */ /* Twist Brush - Rotate Around midpoint */ -/* Take the screenspace coordinates of the point, rotate this around the brush midpoint, - * convert the rotated point and convert it into "data" space +/* Take the screen-space coordinates of the point, rotate this around the brush midpoint, + * convert the rotated point and convert it into "data" space. */ static bool gpencil_brush_twist_apply(tGP_BrushEditData *gso, @@ -807,7 +807,7 @@ static bool gpencil_brush_randomize_apply(tGP_BrushEditData *gso, /* apply random to position */ if (gso->brush->gpencil_settings->sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) { /* Jitter is applied perpendicular to the mouse movement vector - * - We compute all effects in screenspace (since it's easier) + * - We compute all effects in screen-space (since it's easier) * and then project these to get the points/distances in * view-space as needed. */ @@ -989,8 +989,8 @@ static void gpencil_brush_clone_add(bContext *C, tGP_BrushEditData *gso) float delta[3]; size_t strokes_added = 0; - /* Compute amount to offset the points by */ - /* NOTE: This assumes that screenspace strokes are NOT used in the 3D view... */ + /* Compute amount to offset the points by. */ + /* NOTE: This assumes that screen-space strokes are NOT used in the 3D view. */ gpencil_brush_calc_midpoint(gso); /* this puts the cursor location into gso->dvec */ sub_v3_v3v3(delta, gso->dvec, data->buffer_midpoint); @@ -1063,7 +1063,7 @@ static void gpencil_brush_clone_adjust(tGP_BrushEditData *gso) /* For each of the stored strokes, apply the offset to each point */ /* NOTE: Again this assumes that in the 3D view, - * we only have 3d space and not screenspace strokes... */ + * we only have 3d space and not screen-space strokes. */ for (snum = 0; snum < data->totitems; snum++) { bGPDstroke *gps = data->new_strokes[snum]; bGPDspoint *pt; diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index a19265720e8..95f43733a36 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -2071,7 +2071,7 @@ static bool gpencil_generic_stroke_select(bContext *C, for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; - /* convert point coords to screenspace */ + /* Convert point coords to screen-space. */ const bool is_inside = is_inside_fn(gsc.region, gpstroke_iter.diff_mat, &pt->x, user_data); if (strokemode == false) { const bool is_select = (pt_active->flag & GP_SPOINT_SELECT) != 0; diff --git a/source/blender/editors/gpencil/gpencil_trace_ops.c b/source/blender/editors/gpencil/gpencil_trace_ops.c index f6e88e05d46..36165c6b7c0 100644 --- a/source/blender/editors/gpencil/gpencil_trace_ops.c +++ b/source/blender/editors/gpencil/gpencil_trace_ops.c @@ -71,6 +71,9 @@ typedef struct TraceJob { int32_t thickness; int32_t turnpolicy; int32_t mode; + /** Frame to render to be used by python API. Not exposed in UI. + * This feature is only used in Studios to run custom video trace for selected frames. */ + int32_t frame_num; bool success; bool was_canceled; @@ -212,7 +215,10 @@ static void trace_start_job(void *customdata, short *stop, short *do_update, flo (trace_job->mode == GPENCIL_TRACE_MODE_SINGLE)) { void *lock; ImageUser *iuser = trace_job->ob_active->iuser; - iuser->framenr = init_frame; + + iuser->framenr = ((trace_job->frame_num == 0) || (trace_job->frame_num > iuser->frames)) ? + init_frame : + trace_job->frame_num; ImBuf *ibuf = BKE_image_acquire_ibuf(trace_job->image, iuser, &lock); if (ibuf) { /* Create frame. */ @@ -300,9 +306,10 @@ static int gpencil_trace_image_exec(bContext *C, wmOperator *op) /* Create a new grease pencil object or reuse selected. */ eGP_TargetObjectMode target = RNA_enum_get(op->ptr, "target"); - job->ob_gpencil = (target == GP_TARGET_OB_SELECTED) ? BKE_view_layer_non_active_selected_object( - CTX_data_view_layer(C), job->v3d) : - NULL; + job->ob_gpencil = (target == GP_TARGET_OB_SELECTED) ? + BKE_view_layer_non_active_selected_object( + scene, CTX_data_view_layer(C), job->v3d) : + NULL; if (job->ob_gpencil != NULL) { if (job->ob_gpencil->type != OB_GPENCIL) { @@ -324,13 +331,14 @@ static int gpencil_trace_image_exec(bContext *C, wmOperator *op) job->thickness = RNA_int_get(op->ptr, "thickness"); job->turnpolicy = RNA_enum_get(op->ptr, "turnpolicy"); job->mode = RNA_enum_get(op->ptr, "mode"); + job->frame_num = RNA_int_get(op->ptr, "frame_number"); trace_initialize_job_data(job); /* Back to active base. */ ED_object_base_activate(job->C, job->base_active); - if (job->image->source == IMA_SRC_FILE) { + if ((job->image->source == IMA_SRC_FILE) || (job->frame_num > 0)) { short stop = 0, do_update = true; float progress; trace_start_job(job, &stop, &do_update, &progress); @@ -364,6 +372,8 @@ static int gpencil_trace_image_invoke(bContext *C, wmOperator *op, const wmEvent void GPENCIL_OT_trace_image(wmOperatorType *ot) { + PropertyRNA *prop; + static const EnumPropertyItem turnpolicy_type[] = { {POTRACE_TURNPOLICY_BLACK, "BLACK", @@ -475,4 +485,15 @@ void GPENCIL_OT_trace_image(wmOperatorType *ot) true, "Start At Current Frame", "Trace Image starting in current image frame"); + prop = RNA_def_int( + ot->srna, + "frame_number", + 0, + 0, + 9999, + "Trace Frame", + "Used to trace only one frame of the image sequence, set to zero to trace all", + 0, + 9999); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 7b659511aaa..729e8412684 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -1038,7 +1038,8 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, bGPDframe *gpf, bGPDstroke *gps, const eGP_ReprojectModes mode, - const bool keep_original) + const bool keep_original, + const float offset) { ToolSettings *ts = gsc->scene->toolsettings; ARegion *region = gsc->region; @@ -1156,7 +1157,13 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, &depth, &location[0], &normal[0])) { - copy_v3_v3(&pt->x, location); + /* Apply offset over surface. */ + float normal_vector[3]; + sub_v3_v3v3(normal_vector, ray_start, location); + normalize_v3(normal_vector); + mul_v3_fl(normal_vector, offset); + + add_v3_v3v3(&pt->x, location, normal_vector); } else { /* Default to planar */ @@ -1683,7 +1690,7 @@ void ED_gpencil_brush_draw_eraser(Brush *brush, int x, int y) GPUVertFormat *format = immVertexFormat(); const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); @@ -1693,7 +1700,7 @@ void ED_gpencil_brush_draw_eraser(Brush *brush, int x, int y) immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); @@ -1865,7 +1872,7 @@ static void gpencil_brush_cursor_draw(bContext *C, int x, int y, void *customdat /* draw icon */ GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); @@ -3189,7 +3196,7 @@ bGPDstroke *ED_gpencil_stroke_join_and_trim( /* Join both strokes. */ int totpoint = gps_final->totpoints; - BKE_gpencil_stroke_join(gps_final, gps, false, true, true); + BKE_gpencil_stroke_join(gps_final, gps, false, true, true, true); /* Select the join points and merge if the distance is very small. */ pt = &gps_final->points[totpoint - 1]; diff --git a/source/blender/editors/gpencil/gpencil_vertex_ops.c b/source/blender/editors/gpencil/gpencil_vertex_ops.c index 865c4e360b5..41f939813e4 100644 --- a/source/blender/editors/gpencil/gpencil_vertex_ops.c +++ b/source/blender/editors/gpencil/gpencil_vertex_ops.c @@ -131,7 +131,7 @@ static int gpencil_vertexpaint_brightness_contrast_exec(bContext *C, wmOperator /* * The algorithm is by Werner D. Streidt * (http://visca.com/ffactory/archives/5-99/msg00021.html) - * Extracted of OpenCV demhist.c + * Extracted of OpenCV `demhist.c`. */ if (contrast > 0) { gain = 1.0f - delta * 2.0f; |