forked from bartvdbraak/blender
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:
parent
cb8614e398
commit
65de468396
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user