GPencil: New operator to convert strokes to perimeter.

This operator converts any stroke of gpencil with a center line into a stroke with the perimeter.

It's possible to assign the active material, keep current or create a new material for all perimeters.

The conversion is only done for strokes with a material using `Stroke`. Only `Fill` strokes are not converted.

Known issues: As the perimter has not boolean implementation, some perimeters can be overlaped. This could be solved in the future when a new 2D boolean library will be developed.

Reviewed By: mendio, pepeland, frogstomp

Differential Revision: https://developer.blender.org/D15664
This commit is contained in:
Antonio Vazquez 2022-08-18 16:19:03 +02:00
parent 4d4a84bbeb
commit aa7b2f1dd9
4 changed files with 268 additions and 0 deletions

View File

@ -5178,6 +5178,9 @@ class VIEW3D_MT_edit_gpencil_stroke(Menu):
layout.separator()
layout.operator("gpencil.reset_transform_fill", text="Reset Fill Transform")
layout.separator()
layout.operator("gpencil.stroke_outline", text="Outline")
class VIEW3D_MT_edit_gpencil_point(Menu):
bl_label = "Point"

View File

@ -3960,6 +3960,269 @@ 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 int view_mode = RNA_enum_get(op->ptr, "view_mode");
const int mode = RNA_enum_get(op->ptr, "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], viewinv[4][4];
copy_m4_m4(viewmat, rv3d->viewmat);
copy_m4_m4(viewinv, rv3d->viewinv);
switch (view_mode) {
case GP_PERIMETER_FRONT:
unit_m4(rv3d->viewmat);
rv3d->viewmat[1][1] = 0.0f;
rv3d->viewmat[1][2] = -1.0f;
rv3d->viewmat[2][1] = 1.0f;
rv3d->viewmat[2][2] = 0.0f;
rv3d->viewmat[3][2] = -10.0f;
invert_m4_m4(rv3d->viewinv, rv3d->viewmat);
break;
case GP_PERIMETER_SIDE:
zero_m4(rv3d->viewmat);
rv3d->viewmat[0][2] = 1.0f;
rv3d->viewmat[1][0] = 1.0f;
rv3d->viewmat[2][1] = 1.0f;
rv3d->viewmat[3][3] = 1.0f;
invert_m4_m4(rv3d->viewinv, rv3d->viewmat);
break;
case GP_PERIMETER_TOP:
unit_m4(rv3d->viewmat);
unit_m4(rv3d->viewinv);
break;
case GP_PERIMETER_CAMERA: {
Scene *scene = CTX_data_scene(C);
Object *cam_ob = scene->camera;
if (cam_ob != NULL) {
invert_m4_m4(rv3d->viewmat, cam_ob->obmat);
copy_m4_m4(rv3d->viewinv, 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 (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. */
bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
rv3d, gpd, gpl, gps_duplicate, subdivisions, diff_mat);
gps_perimeter->flag &= ~GP_STROKE_SELECT;
/* Assign material. */
switch (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_perimeter->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 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);
}
}
}
}
/* Back to view matrix. */
copy_m4_m4(rv3d->viewmat, viewmat);
copy_m4_m4(rv3d->viewinv, viewinv);
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, "mode", material_mode, GP_STROKE_USE_ACTIVE_MATERIAL, "Material Mode", "");
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 */

View File

@ -608,6 +608,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);

View File

@ -635,6 +635,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);