GPencil: Improve Cyclic operator to generate geometry

Now, when close a geometry with cyclic is possible generate new geometry for the gap.

The cyclic operator now supports multiframe edition too. Before only worked with active frame.

Also added the corresponding missing menu options and the new F keymap. All these features were missing, pending of the fix of the alpha glitches in stroke already done.
This commit is contained in:
Antonio Vazquez 2019-06-30 21:03:24 +02:00
parent 741967079c
commit 789c6e94fb
5 changed files with 144 additions and 30 deletions

View File

@ -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),

View File

@ -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")

View File

@ -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(

View File

@ -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;
}

View File

@ -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 ************************** */