GP: Draw: Stroke Trim

New edit mode operator and post-processing brush option.

Trim works on a single GP stroke. It removes trailing points before and after the first intersection (or loop) nearest to the start of the stroke.
This commit is contained in:
Charlie Jolly 2019-02-26 16:04:27 +00:00
parent cb8614e398
commit 65de468396
12 changed files with 190 additions and 3 deletions

@ -673,6 +673,7 @@ class AddPresetGpencilBrush(AddPresetBase, Operator):
"settings.uv_random",
"settings.pen_jitter",
"settings.use_jitter_pressure",
"settings.trim",
]
preset_subdir = "gpencil_brush"

@ -215,6 +215,7 @@ class GreasePencilStrokeEditPanel:
row = col.row(align=True)
row.operator("gpencil.stroke_simplify_fixed", text="Simplify")
row.operator("gpencil.stroke_simplify", text="Adaptive")
row.operator("gpencil.stroke_trim", text="Trim")
col.separator()

@ -4007,6 +4007,7 @@ class VIEW3D_MT_edit_gpencil(Menu):
layout.operator("gpencil.stroke_smooth", text="Smooth")
layout.operator("gpencil.stroke_subdivide", text="Subdivide")
layout.menu("VIEW3D_MT_gpencil_simplify")
layout.operator("gpencil.stroke_trim", text="Trim")
layout.separator()
@ -5674,6 +5675,7 @@ class VIEW3D_MT_gpencil_edit_specials(Menu):
layout.operator("gpencil.stroke_subdivide", text="Subdivide")
layout.operator("gpencil.stroke_simplify_fixed", text="Simplify")
layout.operator("gpencil.stroke_simplify", text="Simplify Adaptive")
layout.operator("gpencil.stroke_trim", text="Trim")
layout.separator()
layout.menu("GPENCIL_MT_separate", text="Separate")

@ -1599,6 +1599,9 @@ class VIEW3D_PT_tools_grease_pencil_brush_settings(View3DPanel, Panel):
col.prop(gp_settings, "pen_subdivision_steps")
col.prop(gp_settings, "random_subdiv", text="Randomness", slider=True)
col = layout.column(align=True)
col.prop(gp_settings, "trim")
class VIEW3D_PT_tools_grease_pencil_brush_random(View3DPanel, Panel):
bl_context = ".greasepencil_paint"

@ -160,6 +160,7 @@ void BKE_gpencil_stroke_normal(const struct bGPDstroke *gps, float r_normal[3]);
void BKE_gpencil_simplify_stroke(struct bGPDstroke *gps, float factor);
void BKE_gpencil_simplify_fixed(struct bGPDstroke *gps);
void BKE_gpencil_subdivide(struct bGPDstroke *gps, int level, int flag);
bool BKE_gpencil_trim_stroke(struct bGPDstroke *gps);
void BKE_gpencil_stroke_2d_flat(
const struct bGPDspoint *points, int totpoints, float(*points2d)[2], int *r_direction);

@ -1700,3 +1700,98 @@ void BKE_gpencil_stroke_2d_flat_ref(
/* Concave (-1), Convex (1), or Autodetect (0)? */
*r_direction = (int)locy[2];
}
/**
* Trim stroke to the first intersection or loop
* \param gps: Stroke data
*/
bool BKE_gpencil_trim_stroke(bGPDstroke *gps)
{
if (gps->totpoints < 4) {
return false;
}
bool intersect = false;
int start, end;
float point[3];
/* loop segments from start until we have an intersection */
for (int i = 0; i < gps->totpoints - 2; i++) {
start = i;
bGPDspoint *a = &gps->points[start];
bGPDspoint *b = &gps->points[start + 1];
for (int j = start + 2; j < gps->totpoints - 1; j++) {
end = j + 1;
bGPDspoint *c = &gps->points[j];
bGPDspoint *d = &gps->points[end];
float pointb[3];
/* get intersection */
if (isect_line_line_v3(&a->x, &b->x, &c->x, &d->x, point, pointb)) {
if (len_v3(point) > 0.0f) {
float closest[3];
/* check intersection is on both lines */
float lambda = closest_to_line_v3(closest, point, &a->x, &b->x);
if ((lambda <= 0.0f) || (lambda >= 1.0f)) {
continue;
}
lambda = closest_to_line_v3(closest, point, &c->x, &d->x);
if ((lambda <= 0.0f) || (lambda >= 1.0f)) {
continue;
}
else {
intersect = true;
break;
}
}
}
}
if (intersect) {
break;
}
}
/* trim unwanted points */
if (intersect) {
/* save points */
bGPDspoint *old_points = MEM_dupallocN(gps->points);
MDeformVert *old_dvert = NULL;
MDeformVert *dvert_src = NULL;
if (gps->dvert != NULL) {
old_dvert = MEM_dupallocN(gps->dvert);
}
/* resize gps */
int newtot = end - start + 1;
gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
if (gps->dvert != NULL) {
gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
}
for (int i = 0; i < newtot; i++) {
int idx = start + i;
bGPDspoint *pt_src = &old_points[idx];
bGPDspoint *pt_new = &gps->points[i];
memcpy(pt_new, pt_src, sizeof(bGPDspoint));
if (gps->dvert != NULL) {
dvert_src = &old_dvert[idx];
MDeformVert *dvert = &gps->dvert[i];
memcpy(dvert, dvert_src, sizeof(MDeformVert));
if (dvert_src->dw) {
memcpy(dvert->dw, dvert_src->dw, sizeof(MDeformWeight));
}
}
if (idx == start || idx == end) {
copy_v3_v3(&pt_new->x, point);
}
}
gps->flag |= GP_STROKE_RECALC_GEOMETRY;
gps->tot_triangles = 0;
gps->totpoints = newtot;
MEM_SAFE_FREE(old_points);
MEM_SAFE_FREE(old_dvert);
}
return intersect;
}

@ -3562,6 +3562,74 @@ void GPENCIL_OT_stroke_simplify_fixed(wmOperatorType *ot)
}
/* ******************* Stroke trim ************************** */
static int gp_stroke_trim_exec(bContext *C, wmOperator *op)
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
/* sanity checks */
if (ELEM(NULL, gpd))
return OPERATOR_CANCELLED;
/* Go through each editable + selected stroke */
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
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;
if (gps->flag & GP_STROKE_SELECT) {
BKE_gpencil_trim_stroke(gps);
}
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
CTX_DATA_END;
/* 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_trim(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Trim Stroke";
ot->idname = "GPENCIL_OT_stroke_trim";
ot->description = "Trim selected stroke to first loop or intersection";
/* api callbacks */
ot->exec = gp_stroke_trim_exec;
ot->poll = gp_active_layer_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ***************** Separate Strokes ********************** */
typedef enum eGP_SeparateModes {
/* Points */

@ -467,6 +467,7 @@ void GPENCIL_OT_stroke_split(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_smooth(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_merge(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_cutter(struct wmOperatorType *ot);
void GPENCIL_OT_stroke_trim(struct wmOperatorType *ot);
void GPENCIL_OT_brush_presets_create(struct wmOperatorType *ot);

@ -310,6 +310,7 @@ void ED_operatortypes_gpencil(void)
WM_operatortype_append(GPENCIL_OT_stroke_smooth);
WM_operatortype_append(GPENCIL_OT_stroke_merge);
WM_operatortype_append(GPENCIL_OT_stroke_cutter);
WM_operatortype_append(GPENCIL_OT_stroke_trim);
WM_operatortype_append(GPENCIL_OT_brush_presets_create);

@ -1242,6 +1242,12 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
}
}
/* post process stroke */
if ((p->brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) &&
p->brush->gpencil_settings->flag & GP_BRUSH_TRIM_STROKE) {
BKE_gpencil_trim_stroke(gps);
}
gp_stroke_added_enable(p);
}

@ -71,8 +71,7 @@ typedef struct BrushGpencilSettings {
short draw_smoothlvl;
/** Number of times to subdivide new strokes. */
short draw_subdivide;
/** Internal grease pencil drawing flags. */
short flag;
short _pad;
/** Number of times to apply thickness smooth factor to new strokes. */
short thick_smoothlvl;
@ -106,7 +105,8 @@ typedef struct BrushGpencilSettings {
float era_strength_f;
/** Factor to apply to thickness for soft eraser. */
float era_thickness_f;
char pad_2[4];
/** Internal grease pencil drawing flags. */
int flag;
struct CurveMapping *curve_sensitivity;
struct CurveMapping *curve_strength;
@ -147,6 +147,8 @@ typedef enum eGPDbrush_Flag {
GP_BRUSH_DISSABLE_LASSO = (1 << 14),
/* Do not erase strokes oLcluded */
GP_BRUSH_OCCLUDE_ERASER = (1 << 15),
/* Post process trim stroke */
GP_BRUSH_TRIM_STROKE = (1 << 16),
} eGPDbrush_Flag;
/* BrushGpencilSettings->gp_fill_draw_mode */

@ -1256,6 +1256,12 @@ static void rna_def_gpencil_options(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Mode", "Mode to draw boundary limits");
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
prop = RNA_def_property(srna, "trim", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_TRIM_STROKE);
RNA_def_property_boolean_default(prop, false);
RNA_def_property_ui_text(prop, "Trim Stroke Ends", "Trim intersecting stroke ends");
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
/* Material */
prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "Material");