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