This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/gpencil/gpencil_vertex_ops.c
Campbell Barton bdb42c2c2d Cleanup: remove redundant headers in source/blender/editors/
Remove redundant headers using
`./source/tools/utils_maintenance/code_clean.py`

Reviewed By: jmonteath

Ref D10364
2021-02-10 09:38:24 +11:00

1162 lines
35 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2015, Blender Foundation
* This is a new part of Blender
* Brush based operators for editing Grease Pencil strokes
*/
/** \file
* \ingroup edgpencil
*/
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_math.h"
#include "DNA_brush_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_material_types.h"
#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_paint.h"
#include "BKE_report.h"
#include "WM_api.h"
#include "WM_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "ED_gpencil.h"
#include "ED_screen.h"
#include "DEG_depsgraph.h"
#include "gpencil_intern.h"
static const EnumPropertyItem gpencil_modesEnumPropertyItem_mode[] = {
{GPPAINT_MODE_STROKE, "STROKE", 0, "Stroke", ""},
{GPPAINT_MODE_FILL, "FILL", 0, "Fill", ""},
{GPPAINT_MODE_BOTH, "BOTH", 0, "Stroke and Fill", ""},
{0, NULL, 0, NULL, NULL},
};
/* Helper: Check if any stroke is selected. */
static bool is_any_stroke_selected(bContext *C, const bool is_multiedit, const bool is_curve_edit)
{
bool is_selected = false;
/* If not enabled any mask mode, the strokes are considered as not selected. */
ToolSettings *ts = CTX_data_tool_settings(C);
if (!GPENCIL_ANY_VERTEX_MASK(ts->gpencil_selectmode_vertex)) {
return false;
}
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
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;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if (is_curve_edit) {
if (gps->editcurve == NULL) {
continue;
}
bGPDcurve *gpc = gps->editcurve;
if (gpc->flag & GP_CURVE_SELECT) {
is_selected = true;
break;
}
}
else {
if (gps->flag & GP_STROKE_SELECT) {
is_selected = true;
break;
}
}
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
CTX_DATA_END;
return is_selected;
}
/* Poll callback for stroke vertex paint operator. */
static bool gpencil_vertexpaint_mode_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
return false;
}
bGPdata *gpd = (bGPdata *)ob->data;
if (GPENCIL_VERTEX_MODE(gpd)) {
/* Any data to use. */
if (gpd->layers.first) {
return true;
}
}
return false;
}
static int gpencil_vertexpaint_brightness_contrast_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = (bGPdata *)ob->data;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode");
const bool any_selected = is_any_stroke_selected(C, is_multiedit, false);
float gain, offset;
{
float brightness = RNA_float_get(op->ptr, "brightness");
float contrast = RNA_float_get(op->ptr, "contrast");
brightness /= 100.0f;
float delta = contrast / 200.0f;
/*
* The algorithm is by Werner D. Streidt
* (http://visca.com/ffactory/archives/5-99/msg00021.html)
* Extracted of OpenCV demhist.c
*/
if (contrast > 0) {
gain = 1.0f - delta * 2.0f;
gain = 1.0f / max_ff(gain, FLT_EPSILON);
offset = gain * (brightness - delta);
}
else {
delta *= -1;
gain = max_ff(1.0f - delta * 2.0f, 0.0f);
offset = gain * brightness + delta;
}
}
/* Loop all selected strokes. */
bool changed = false;
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
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;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if ((!any_selected) || (gps->flag & GP_STROKE_SELECT)) {
/* Fill color. */
if (mode != GPPAINT_MODE_STROKE) {
if (gps->vert_color_fill[3] > 0.0f) {
changed = true;
for (int i2 = 0; i2 < 3; i2++) {
gps->vert_color_fill[i2] = gain * gps->vert_color_fill[i2] + offset;
}
}
}
/* Stroke points. */
if (mode != GPPAINT_MODE_FILL) {
changed = true;
int i;
bGPDspoint *pt;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (((!any_selected) || (pt->flag & GP_SPOINT_SELECT)) &&
(pt->vert_color[3] > 0.0f)) {
for (int i2 = 0; i2 < 3; i2++) {
pt->vert_color[i2] = gain * pt->vert_color[i2] + offset;
}
}
}
}
}
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
CTX_DATA_END;
/* notifiers */
if (changed) {
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_vertex_color_brightness_contrast(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Vertex Paint Brightness/Contrast";
ot->idname = "GPENCIL_OT_vertex_color_brightness_contrast";
ot->description = "Adjust vertex color brightness/contrast";
/* api callbacks */
ot->exec = gpencil_vertexpaint_brightness_contrast_exec;
ot->poll = gpencil_vertexpaint_mode_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* params */
ot->prop = RNA_def_enum(
ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, GPPAINT_MODE_BOTH, "Mode", "");
const float min = -100, max = +100;
prop = RNA_def_float(ot->srna, "brightness", 0.0f, min, max, "Brightness", "", min, max);
prop = RNA_def_float(ot->srna, "contrast", 0.0f, min, max, "Contrast", "", min, max);
RNA_def_property_ui_range(prop, min, max, 1, 1);
}
static int gpencil_vertexpaint_hsv_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = (bGPdata *)ob->data;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode");
const bool any_selected = is_any_stroke_selected(C, is_multiedit, false);
float hue = RNA_float_get(op->ptr, "h");
float sat = RNA_float_get(op->ptr, "s");
float val = RNA_float_get(op->ptr, "v");
bool changed = false;
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
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;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if ((!any_selected) || (gps->flag & GP_STROKE_SELECT)) {
float hsv[3];
/* Fill color. */
if (mode != GPPAINT_MODE_STROKE) {
if (gps->vert_color_fill[3] > 0.0f) {
changed = true;
rgb_to_hsv_v(gps->vert_color_fill, hsv);
hsv[0] += (hue - 0.5f);
if (hsv[0] > 1.0f) {
hsv[0] -= 1.0f;
}
else if (hsv[0] < 0.0f) {
hsv[0] += 1.0f;
}
hsv[1] *= sat;
hsv[2] *= val;
hsv_to_rgb_v(hsv, gps->vert_color_fill);
}
}
/* Stroke points. */
if (mode != GPPAINT_MODE_FILL) {
changed = true;
int i;
bGPDspoint *pt;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (((!any_selected) || (pt->flag & GP_SPOINT_SELECT)) &&
(pt->vert_color[3] > 0.0f)) {
rgb_to_hsv_v(pt->vert_color, hsv);
hsv[0] += (hue - 0.5f);
if (hsv[0] > 1.0f) {
hsv[0] -= 1.0f;
}
else if (hsv[0] < 0.0f) {
hsv[0] += 1.0f;
}
hsv[1] *= sat;
hsv[2] *= val;
hsv_to_rgb_v(hsv, pt->vert_color);
}
}
}
}
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
CTX_DATA_END;
/* notifiers */
if (changed) {
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_vertex_color_hsv(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Vertex Paint Hue Saturation Value";
ot->idname = "GPENCIL_OT_vertex_color_hsv";
ot->description = "Adjust vertex color HSV values";
/* api callbacks */
ot->exec = gpencil_vertexpaint_hsv_exec;
ot->poll = gpencil_vertexpaint_mode_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* params */
ot->prop = RNA_def_enum(
ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, GPPAINT_MODE_BOTH, "Mode", "");
RNA_def_float(ot->srna, "h", 0.5f, 0.0f, 1.0f, "Hue", "", 0.0f, 1.0f);
RNA_def_float(ot->srna, "s", 1.0f, 0.0f, 2.0f, "Saturation", "", 0.0f, 2.0f);
RNA_def_float(ot->srna, "v", 1.0f, 0.0f, 2.0f, "Value", "", 0.0f, 2.0f);
}
static int gpencil_vertexpaint_invert_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = (bGPdata *)ob->data;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode");
const bool any_selected = is_any_stroke_selected(C, is_multiedit, false);
bool changed = false;
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
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;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if ((!any_selected) || (gps->flag & GP_STROKE_SELECT)) {
/* Fill color. */
if (mode != GPPAINT_MODE_STROKE) {
if (gps->vert_color_fill[3] > 0.0f) {
changed = true;
for (int i2 = 0; i2 < 3; i2++) {
gps->vert_color_fill[i2] = 1.0f - gps->vert_color_fill[i2];
}
}
}
/* Stroke points. */
if (mode != GPPAINT_MODE_FILL) {
changed = true;
int i;
bGPDspoint *pt;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (((!any_selected) || (pt->flag & GP_SPOINT_SELECT)) &&
(pt->vert_color[3] > 0.0f)) {
for (int i2 = 0; i2 < 3; i2++) {
pt->vert_color[i2] = 1.0f - pt->vert_color[i2];
}
}
}
}
}
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
CTX_DATA_END;
/* notifiers */
if (changed) {
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_vertex_color_invert(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Vertex Paint Invert";
ot->idname = "GPENCIL_OT_vertex_color_invert";
ot->description = "Invert RGB values";
/* api callbacks */
ot->exec = gpencil_vertexpaint_invert_exec;
ot->poll = gpencil_vertexpaint_mode_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* params */
ot->prop = RNA_def_enum(
ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, GPPAINT_MODE_BOTH, "Mode", "");
}
static int gpencil_vertexpaint_levels_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = (bGPdata *)ob->data;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode");
const bool any_selected = is_any_stroke_selected(C, is_multiedit, false);
float gain = RNA_float_get(op->ptr, "gain");
float offset = RNA_float_get(op->ptr, "offset");
bool changed = false;
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
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;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if ((!any_selected) || (gps->flag & GP_STROKE_SELECT)) {
/* Fill color. */
if (mode != GPPAINT_MODE_STROKE) {
if (gps->vert_color_fill[3] > 0.0f) {
changed = true;
for (int i2 = 0; i2 < 3; i2++) {
gps->vert_color_fill[i2] = gain * (gps->vert_color_fill[i2] + offset);
}
}
}
/* Stroke points. */
if (mode != GPPAINT_MODE_FILL) {
changed = true;
int i;
bGPDspoint *pt;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (((!any_selected) || (pt->flag & GP_SPOINT_SELECT)) &&
(pt->vert_color[3] > 0.0f)) {
for (int i2 = 0; i2 < 3; i2++) {
pt->vert_color[i2] = gain * (pt->vert_color[i2] + offset);
}
}
}
}
}
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
CTX_DATA_END;
/* notifiers */
if (changed) {
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_vertex_color_levels(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Vertex Paint Levels";
ot->idname = "GPENCIL_OT_vertex_color_levels";
ot->description = "Adjust levels of vertex colors";
/* api callbacks */
ot->exec = gpencil_vertexpaint_levels_exec;
ot->poll = gpencil_vertexpaint_mode_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* params */
ot->prop = RNA_def_enum(
ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, GPPAINT_MODE_BOTH, "Mode", "");
RNA_def_float(
ot->srna, "offset", 0.0f, -1.0f, 1.0f, "Offset", "Value to add to colors", -1.0f, 1.0f);
RNA_def_float(
ot->srna, "gain", 1.0f, 0.0f, FLT_MAX, "Gain", "Value to multiply colors by", 0.0f, 10.0f);
}
static int gpencil_vertexpaint_set_exec(bContext *C, wmOperator *op)
{
ToolSettings *ts = CTX_data_tool_settings(C);
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = (bGPdata *)ob->data;
Paint *paint = &ts->gp_vertexpaint->paint;
Brush *brush = paint->brush;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode");
const bool any_selected = is_any_stroke_selected(C, is_multiedit, false);
float factor = RNA_float_get(op->ptr, "factor");
bool changed = false;
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
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;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if ((!any_selected) || (gps->flag & GP_STROKE_SELECT)) {
/* Fill color. */
if (mode != GPPAINT_MODE_STROKE) {
changed = true;
copy_v3_v3(gps->vert_color_fill, brush->rgb);
gps->vert_color_fill[3] = factor;
}
/* Stroke points. */
if (mode != GPPAINT_MODE_FILL) {
changed = true;
int i;
bGPDspoint *pt;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if ((!any_selected) || (pt->flag & GP_SPOINT_SELECT)) {
copy_v3_v3(pt->vert_color, brush->rgb);
pt->vert_color[3] = factor;
}
}
}
}
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
CTX_DATA_END;
/* notifiers */
if (changed) {
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_vertex_color_set(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Vertex Paint Set Color";
ot->idname = "GPENCIL_OT_vertex_color_set";
ot->description = "Set active color to all selected vertex";
/* api callbacks */
ot->exec = gpencil_vertexpaint_set_exec;
ot->poll = gpencil_vertexpaint_mode_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* params */
ot->prop = RNA_def_enum(
ot->srna, "mode", gpencil_modesEnumPropertyItem_mode, GPPAINT_MODE_BOTH, "Mode", "");
RNA_def_float(ot->srna, "factor", 1.0f, 0.001f, 1.0f, "Factor", "Mix Factor", 0.001f, 1.0f);
}
/* Helper to extract color from vertex color to create a palette. */
static bool gpencil_extract_palette_from_vertex(bContext *C,
const bool selected,
const int threshold)
{
Main *bmain = CTX_data_main(C);
Object *ob = CTX_data_active_object(C);
bool done = false;
const float range = pow(10.0f, threshold);
float col[3];
GHash *color_table = BLI_ghash_int_new(__func__);
/* Extract all colors. */
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) {
continue;
}
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
if (gp_style == NULL) {
continue;
}
if ((selected) && ((gps->flag & GP_STROKE_SELECT) == 0)) {
continue;
}
bool use_stroke = (gp_style->flag & GP_MATERIAL_STROKE_SHOW);
bool use_fill = (gp_style->flag & GP_MATERIAL_FILL_SHOW);
/* Material is disabled. */
if ((!use_fill) && (!use_stroke)) {
continue;
}
/* Only solid strokes or stencil. */
if ((use_stroke) && ((gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE) &&
((gp_style->flag & GP_MATERIAL_STROKE_PATTERN) == 0))) {
continue;
}
/* Only solid fill. */
if ((use_fill) && (gp_style->fill_style != GP_MATERIAL_FILL_STYLE_SOLID)) {
continue;
}
/* Fill color. */
if (gps->vert_color_fill[3] > 0.0f) {
col[0] = truncf(gps->vert_color_fill[0] * range) / range;
col[1] = truncf(gps->vert_color_fill[1] * range) / range;
col[2] = truncf(gps->vert_color_fill[2] * range) / range;
uint key = rgb_to_cpack(col[0], col[1], col[2]);
if (!BLI_ghash_haskey(color_table, POINTER_FROM_INT(key))) {
BLI_ghash_insert(color_table, POINTER_FROM_INT(key), POINTER_FROM_INT(key));
}
}
/* Read all points to get all colors. */
bGPDspoint *pt;
int i;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
col[0] = truncf(pt->vert_color[0] * range) / range;
col[1] = truncf(pt->vert_color[1] * range) / range;
col[2] = truncf(pt->vert_color[2] * range) / range;
uint key = rgb_to_cpack(col[0], col[1], col[2]);
if (!BLI_ghash_haskey(color_table, POINTER_FROM_INT(key))) {
BLI_ghash_insert(color_table, POINTER_FROM_INT(key), POINTER_FROM_INT(key));
}
}
}
}
}
CTX_DATA_END;
/* Create the Palette. */
done = BKE_palette_from_hash(bmain, color_table, ob->id.name + 2, true);
/* Free memory. */
BLI_ghash_free(color_table, NULL, NULL);
return done;
}
/* Convert Materials to Vertex Color. */
typedef struct GPMatArray {
uint key;
Material *ma;
int index;
} GPMatArray;
static uint get_material_type(MaterialGPencilStyle *gp_style,
bool use_stroke,
bool use_fill,
char *name)
{
uint r_i = 0;
if ((use_stroke) && (use_fill)) {
switch (gp_style->mode) {
case GP_MATERIAL_MODE_LINE: {
r_i = 1;
strcpy(name, "Line Stroke-Fill");
break;
}
case GP_MATERIAL_MODE_DOT: {
r_i = 2;
strcpy(name, "Dots Stroke-Fill");
break;
}
case GP_MATERIAL_MODE_SQUARE: {
r_i = 3;
strcpy(name, "Squares Stroke-Fill");
break;
}
default:
break;
}
}
else if (use_stroke) {
switch (gp_style->mode) {
case GP_MATERIAL_MODE_LINE: {
r_i = 4;
strcpy(name, "Line Stroke");
break;
}
case GP_MATERIAL_MODE_DOT: {
r_i = 5;
strcpy(name, "Dots Stroke");
break;
}
case GP_MATERIAL_MODE_SQUARE: {
r_i = 6;
strcpy(name, "Squares Stroke");
break;
}
default:
break;
}
}
else {
r_i = 7;
strcpy(name, "Solid Fill");
}
/* Create key TSSSSFFFF (T: Type S: Stroke Alpha F: Fill Alpha) */
r_i *= 1e8;
if (use_stroke) {
r_i += gp_style->stroke_rgba[3] * 1e7;
}
if (use_fill) {
r_i += gp_style->fill_rgba[3] * 1e3;
}
return r_i;
}
static bool gpencil_material_to_vertex_poll(bContext *C)
{
/* only supported with grease pencil objects */
Object *ob = CTX_data_active_object(C);
if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
return false;
}
return true;
}
static int gpencil_material_to_vertex_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = (bGPdata *)ob->data;
const bool remove = RNA_boolean_get(op->ptr, "remove");
const bool palette = RNA_boolean_get(op->ptr, "palette");
const bool selected = RNA_boolean_get(op->ptr, "selected");
char name[32] = "";
Material *ma = NULL;
GPMatArray *mat_elm = NULL;
bool changed = false;
short *totcol = BKE_object_material_len_p(ob);
if (totcol == 0) {
return OPERATOR_CANCELLED;
}
/* These arrays hold all materials and index in the material slots for all combinations. */
int totmat = *totcol;
GPMatArray *mat_table = MEM_calloc_arrayN(totmat, sizeof(GPMatArray), __func__);
/* Update stroke material index. */
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) {
continue;
}
if ((selected) && ((gps->flag & GP_STROKE_SELECT) == 0)) {
continue;
}
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
if (gp_style == NULL) {
continue;
}
bool use_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) &&
(gp_style->stroke_rgba[3] > 0.0f));
bool use_fill = ((gp_style->flag & GP_MATERIAL_FILL_SHOW) &&
(gp_style->fill_rgba[3] > 0.0f));
bool is_stencil = ((gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE) &&
(gp_style->flag & GP_MATERIAL_STROKE_PATTERN));
/* Material is disabled. */
if ((!use_fill) && (!use_stroke)) {
continue;
}
/* Only solid strokes or stencil. */
if ((use_stroke) && ((gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE) &&
((gp_style->flag & GP_MATERIAL_STROKE_PATTERN) == 0))) {
continue;
}
/* Only solid fill. */
if ((use_fill) && (gp_style->fill_style != GP_MATERIAL_FILL_STYLE_SOLID)) {
continue;
}
/* Only for no Stencil materials. */
if (!is_stencil) {
/* Create material type unique key by type and alpha. */
uint key = get_material_type(gp_style, use_stroke, use_fill, name);
/* Check if material exist. */
bool found = false;
int i;
for (i = 0; i < totmat; i++) {
mat_elm = &mat_table[i];
if (mat_elm->ma == NULL) {
break;
}
if (key == mat_elm->key) {
found = true;
break;
}
}
/* If not found create a new material. */
if (!found) {
ma = BKE_gpencil_material_add(bmain, name);
if (use_stroke) {
ma->gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
}
else {
ma->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
}
if (use_fill) {
ma->gp_style->flag |= GP_MATERIAL_FILL_SHOW;
}
else {
ma->gp_style->flag &= ~GP_MATERIAL_FILL_SHOW;
}
ma->gp_style->stroke_rgba[3] = gp_style->stroke_rgba[3];
ma->gp_style->fill_rgba[3] = gp_style->fill_rgba[3];
BKE_object_material_slot_add(bmain, ob);
BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
mat_elm->key = key;
mat_elm->ma = ma;
mat_elm->index = ob->totcol - 1;
}
else {
mat_elm = &mat_table[i];
}
/* Update stroke */
gps->mat_nr = mat_elm->index;
}
changed = true;
copy_v3_v3(gps->vert_color_fill, gp_style->fill_rgba);
gps->vert_color_fill[3] = 1.0f;
/* Update all points. */
bGPDspoint *pt;
int i;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
copy_v3_v3(pt->vert_color, gp_style->stroke_rgba);
pt->vert_color[3] = 1.0f;
}
}
}
}
CTX_DATA_END;
/* notifiers */
if (changed) {
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
}
/* Free memory. */
MEM_SAFE_FREE(mat_table);
/* Generate a Palette. */
if (palette) {
gpencil_extract_palette_from_vertex(C, selected, 1);
}
/* Clean unused materials. */
if (remove) {
WM_operator_name_call(
C, "OBJECT_OT_material_slot_remove_unused", WM_OP_INVOKE_REGION_WIN, NULL);
}
return OPERATOR_FINISHED;
}
void GPENCIL_OT_material_to_vertex_color(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Convert Stroke Materials to Vertex Color";
ot->idname = "GPENCIL_OT_material_to_vertex_color";
ot->description = "Replace materials in strokes with Vertex Color";
/* api callbacks */
ot->exec = gpencil_material_to_vertex_exec;
ot->poll = gpencil_material_to_vertex_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_boolean(ot->srna,
"remove",
true,
"Remove Unused Materials",
"Remove any unused material after the conversion");
RNA_def_boolean(ot->srna, "palette", true, "Create Palette", "Create a new palette with colors");
RNA_def_boolean(ot->srna, "selected", false, "Only Selected", "Convert only selected strokes");
RNA_def_int(ot->srna, "threshold", 3, 1, 4, "Threshold", "", 1, 4);
}
/* Extract Palette from Vertex Color. */
static bool gpencil_extract_palette_vertex_poll(bContext *C)
{
/* only supported with grease pencil objects */
Object *ob = CTX_data_active_object(C);
if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
return false;
}
return true;
}
static int gpencil_extract_palette_vertex_exec(bContext *C, wmOperator *op)
{
const bool selected = RNA_boolean_get(op->ptr, "selected");
const int threshold = RNA_int_get(op->ptr, "threshold");
if (gpencil_extract_palette_from_vertex(C, selected, threshold)) {
BKE_reportf(op->reports, RPT_INFO, "Palette created");
}
else {
BKE_reportf(op->reports, RPT_ERROR, "Unable to find Vertex Information to create palette");
}
return OPERATOR_FINISHED;
}
void GPENCIL_OT_extract_palette_vertex(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Extract Palette from Vertex Color";
ot->idname = "GPENCIL_OT_extract_palette_vertex";
ot->description = "Extract all colors used in Grease Pencil Vertex and create a Palette";
/* api callbacks */
ot->exec = gpencil_extract_palette_vertex_exec;
ot->poll = gpencil_extract_palette_vertex_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_boolean(
ot->srna, "selected", false, "Only Selected", "Convert only selected strokes");
RNA_def_int(ot->srna, "threshold", 1, 1, 4, "Threshold", "", 1, 4);
}
/* -------------------------------------------------------------------- */
/** \name Reset Stroke Vertex Color Operator
* \{ */
static void gpencil_reset_vertex(bGPDstroke *gps, eGp_Vertex_Mode mode)
{
if (mode != GPPAINT_MODE_STROKE) {
zero_v4(gps->vert_color_fill);
}
if (mode != GPPAINT_MODE_FILL) {
bGPDspoint *pt;
for (int i = 0; i < gps->totpoints; i++) {
pt = &gps->points[i];
zero_v4(pt->vert_color);
}
}
}
static int gpencil_stroke_reset_vertex_color_exec(bContext *C, wmOperator *op)
{
Object *obact = CTX_data_active_object(C);
bGPdata *gpd = (bGPdata *)obact->data;
const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd);
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode");
/* First need to check if there are something selected. If not, apply to all strokes. */
const bool any_selected = is_any_stroke_selected(C, is_multiedit, is_curve_edit);
/* Reset Vertex colors. */
bool changed = false;
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
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;
}
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if (is_curve_edit) {
if (gps->editcurve == NULL) {
continue;
}
bGPDcurve *gpc = gps->editcurve;
if ((!any_selected) || (gpc->flag & GP_CURVE_SELECT)) {
gpencil_reset_vertex(gps, mode);
}
}
else {
if ((!any_selected) || (gps->flag & GP_STROKE_SELECT)) {
gpencil_reset_vertex(gps, mode);
}
}
changed = true;
}
/* if not multiedit, exit loop*/
if (!is_multiedit) {
break;
}
}
}
}
CTX_DATA_END;
if (changed) {
/* updates */
DEG_id_tag_update(&gpd->id,
ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
}
return OPERATOR_FINISHED;
}
void GPENCIL_OT_stroke_reset_vertex_color(wmOperatorType *ot)
{
static EnumPropertyItem mode_types_items[] = {
{GPPAINT_MODE_STROKE, "STROKE", 0, "Stroke", "Reset Vertex Color to Stroke only"},
{GPPAINT_MODE_FILL, "FILL", 0, "Fill", "Reset Vertex Color to Fill only"},
{GPPAINT_MODE_BOTH, "BOTH", 0, "Stroke and Fill", "Reset Vertex Color to Stroke and Fill"},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Reset Vertex Color";
ot->idname = "GPENCIL_OT_stroke_reset_vertex_color";
ot->description = "Reset vertex color for all or selected strokes";
/* callbacks */
ot->exec = gpencil_stroke_reset_vertex_color_exec;
ot->poll = gpencil_vertexpaint_mode_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_enum(ot->srna, "mode", mode_types_items, GPPAINT_MODE_BOTH, "Mode", "");
}
/** \} */