diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 440916c209a..c888ce4402a 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -3008,6 +3008,9 @@ def km_grease_pencil_stroke_edit_mode(params): ("gpencil.stroke_join", {"type": 'J', "value": 'PRESS', "ctrl": True}, None), ("gpencil.stroke_join", {"type": 'J', "value": 'PRESS', "shift": True, "ctrl": True}, {"properties": [("type", 'JOINCOPY')]}), + # Close strokes + ("gpencil.stroke_cyclical_set", {"type": 'F', "value": 'PRESS'}, + {"properties": [("type", 'CLOSE'), ("geometry", True)]}), # Copy + paset ("gpencil.copy", {"type": 'C', "value": 'PRESS', "ctrl": True}, None), ("gpencil.paste", {"type": 'V', "value": 'PRESS', "ctrl": True}, None), diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 10bd910db99..8630fe49696 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -4542,6 +4542,9 @@ class VIEW3D_MT_edit_gpencil(Menu): layout.operator_menu_enum("gpencil.stroke_separate", "mode", text="Separate...") layout.operator("gpencil.stroke_split", text="Split") layout.operator("gpencil.stroke_merge", text="Merge") + op = layout.operator("gpencil.stroke_cyclical_set", text="Close") + op.type = 'CLOSE' + op.geometry = True layout.operator_menu_enum("gpencil.stroke_join", "type", text="Join...") layout.operator("gpencil.stroke_flip", text="Flip Direction") @@ -6295,7 +6298,10 @@ class VIEW3D_MT_gpencil_edit_context_menu(Menu): layout.operator("gpencil.stroke_join", text="Join & Copy").type = 'JOINCOPY' layout.menu("GPENCIL_MT_separate", text="Separate") layout.operator("gpencil.stroke_split", text="Split") - + op = layout.operator("gpencil.stroke_cyclical_set", text="Close") + op.type = 'CLOSE' + op.geometry = True + layout.separator() layout.menu("VIEW3D_MT_mirror") diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 0e3b0181fb9..ae1000d1b99 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -215,6 +215,7 @@ bool BKE_gpencil_smooth_stroke(struct bGPDstroke *gps, int i, float inf); bool BKE_gpencil_smooth_stroke_strength(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_smooth_stroke_thickness(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_smooth_stroke_uv(struct bGPDstroke *gps, int point_index, float influence); +bool BKE_gpencil_close_stroke(struct bGPDstroke *gps); void BKE_gpencil_get_range_selected(struct bGPDlayer *gpl, int *r_initframe, int *r_endframe); float BKE_gpencil_multiframe_falloff_calc( diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 842fa85bf76..ff638eaf082 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -1958,3 +1958,83 @@ bool BKE_gpencil_trim_stroke(bGPDstroke *gps) } return intersect; } + +/** + * Close stroke + * \param gps: Stroke to close + */ +bool BKE_gpencil_close_stroke(bGPDstroke *gps) +{ + bGPDspoint *pt1 = NULL; + bGPDspoint *pt2 = NULL; + + /* Only can close a stroke with 3 points or more. */ + if (gps->totpoints < 3) { + return false; + } + + /* Calc average distance between points to get same level of sampling. */ + float dist_tot = 0.0f; + for (int i = 0; i < gps->totpoints - 1; i++) { + pt1 = &gps->points[i]; + pt2 = &gps->points[i + 1]; + dist_tot += len_v3v3(&pt1->x, &pt2->x); + } + /* Calc the average distance. */ + float dist_avg = dist_tot / (gps->totpoints - 1); + + /* Calc distance between last and first point. */ + pt1 = &gps->points[gps->totpoints - 1]; + pt2 = &gps->points[0]; + float dist_close = len_v3v3(&pt1->x, &pt2->x); + + /* Calc number of points required using the average distance. */ + int tot_newpoints = MAX2(dist_close / dist_avg, 1); + + /* Resize stroke array. */ + int old_tot = gps->totpoints; + gps->totpoints += tot_newpoints; + gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); + if (gps->dvert != NULL) { + gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); + } + + /* Generate new points */ + pt1 = &gps->points[old_tot - 1]; + pt2 = &gps->points[0]; + bGPDspoint *pt = &gps->points[old_tot]; + for (int i = 1; i < tot_newpoints + 1; i++, pt++) { + float step = ((float)i / (float)tot_newpoints); + /* Clamp last point to be near, but not on top of first point. */ + CLAMP(step, 0.0f, 0.99f); + + /* Average point. */ + interp_v3_v3v3(&pt->x, &pt1->x, &pt2->x, step); + pt->pressure = interpf(pt2->pressure, pt1->pressure, step); + pt->strength = interpf(pt2->strength, pt1->strength, step); + pt->flag = 0; + + /* Set weights. */ + if (gps->dvert != NULL) { + MDeformVert *dvert1 = &gps->dvert[old_tot - 1]; + MDeformWeight *dw1 = defvert_verify_index(dvert1, 0); + float weight_1 = dw1 ? dw1->weight : 0.0f; + + MDeformVert *dvert2 = &gps->dvert[0]; + MDeformWeight *dw2 = defvert_verify_index(dvert2, 0); + float weight_2 = dw2 ? dw2->weight : 0.0f; + + MDeformVert *dvert_final = &gps->dvert[old_tot + i - 1]; + dvert_final->totweight = 0; + MDeformWeight *dw = defvert_verify_index(dvert_final, 0); + if (dvert_final->dw) { + dw->weight = interpf(weight_2, weight_1, step); + } + } + } + + /* Enable cyclic flag. */ + gps->flag |= GP_STROKE_CYCLIC; + + return true; +} diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 5459cd09e53..44f4728adcd 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -2804,6 +2804,9 @@ static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) Object *ob = CTX_data_active_object(C); const int type = RNA_enum_get(op->ptr, "type"); + const bool geometry = RNA_boolean_get(op->ptr, "geometry"); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + bGPDstroke *gps = NULL; /* sanity checks */ if (ELEM(NULL, gpd)) { @@ -2812,39 +2815,55 @@ static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) /* loop all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - if (gpl->actframe == NULL) { - continue; - } + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { - MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1); + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } - /* skip strokes that are not selected or invalid for current view */ - if (((gps->flag & GP_STROKE_SELECT) == 0) || ED_gpencil_stroke_can_use(C, gps) == false) { - continue; - } - /* skip hidden or locked colors */ - if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || - (gp_style->flag & GP_STYLE_COLOR_LOCKED)) { - continue; - } + for (gps = gpf->strokes.first; gps; gps = gps->next) { + MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(ob, gps->mat_nr + 1); + /* skip strokes that are not selected or invalid for current view */ + if (((gps->flag & GP_STROKE_SELECT) == 0) || + ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* skip hidden or locked colors */ + if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || + (gp_style->flag & GP_STYLE_COLOR_LOCKED)) { + continue; + } - switch (type) { - case GP_STROKE_CYCLIC_CLOSE: - /* Close all (enable) */ - gps->flag |= GP_STROKE_CYCLIC; - break; - case GP_STROKE_CYCLIC_OPEN: - /* Open all (disable) */ - gps->flag &= ~GP_STROKE_CYCLIC; - break; - case GP_STROKE_CYCLIC_TOGGLE: - /* Just toggle flag... */ - gps->flag ^= GP_STROKE_CYCLIC; - break; - default: - BLI_assert(0); + switch (type) { + case GP_STROKE_CYCLIC_CLOSE: + /* Close all (enable) */ + gps->flag |= GP_STROKE_CYCLIC; + break; + case GP_STROKE_CYCLIC_OPEN: + /* Open all (disable) */ + gps->flag &= ~GP_STROKE_CYCLIC; + break; + case GP_STROKE_CYCLIC_TOGGLE: + /* Just toggle flag... */ + gps->flag ^= GP_STROKE_CYCLIC; + break; + default: + BLI_assert(0); + break; + } + + /* Create new geometry. */ + if ((gps->flag & GP_STROKE_CYCLIC) && (geometry)) { + BKE_gpencil_close_stroke(gps); + } + } + + /* if not multiedit, exit loop*/ + if (!is_multiedit) { break; + } } } } @@ -2863,6 +2882,8 @@ static int gp_stroke_cyclical_set_exec(bContext *C, wmOperator *op) */ void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot) { + PropertyRNA *prop; + static const EnumPropertyItem cyclic_type[] = { {GP_STROKE_CYCLIC_CLOSE, "CLOSE", 0, "Close all", ""}, {GP_STROKE_CYCLIC_OPEN, "OPEN", 0, "Open all", ""}, @@ -2884,6 +2905,9 @@ void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot) /* properties */ ot->prop = RNA_def_enum(ot->srna, "type", cyclic_type, GP_STROKE_CYCLIC_TOGGLE, "Type", ""); + prop = RNA_def_boolean( + ot->srna, "geometry", false, "Create Geometry", "Create new geometry for closing stroke"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /* ******************* Flat Stroke Caps ************************** */