Fix T66452: Convert Curve to Grease Pencil Strokes

This commit adds support to convert curves to Grease Pencil strokes and create the materials too.

Also, there is a new python API. This API is required by the modified SVG import addon to create strokes( see T67065).

All curves selected in one operation are converted in the same Grease Pencil object.
This commit is contained in:
Antonio Vazquez 2019-08-31 17:26:48 +02:00
parent a098bd094c
commit 505340202e
Notes: blender-bot 2023-02-14 07:39:44 +01:00
Referenced by issue #66452, GPencil: Modify Convert To operator to support Curve to Stroke
4 changed files with 444 additions and 2 deletions

View File

@ -237,6 +237,14 @@ void BKE_gpencil_get_range_selected(struct bGPDlayer *gpl, int *r_initframe, int
float BKE_gpencil_multiframe_falloff_calc(
struct bGPDframe *gpf, int actnum, int f_init, int f_end, struct CurveMapping *cur_falloff);
void BKE_gpencil_convert_curve(struct Main *bmain,
struct Scene *scene,
struct Object *ob_gp,
struct Object *ob_cu,
const bool gpencil_lines,
const bool use_collections,
const bool only_stroke);
extern void (*BKE_gpencil_batch_cache_dirty_tag_cb)(struct bGPdata *gpd);
extern void (*BKE_gpencil_batch_cache_free_cb)(struct bGPdata *gpd);

View File

@ -48,14 +48,18 @@
#include "BKE_action.h"
#include "BKE_animsys.h"
#include "BKE_curve.h"
#include "BKE_collection.h"
#include "BKE_colortools.h"
#include "BKE_deform.h"
#include "BKE_gpencil.h"
#include "BKE_colortools.h"
#include "BKE_icons.h"
#include "BKE_library.h"
#include "BKE_main.h"
#include "BKE_object.h"
#include "BKE_material.h"
#include "BKE_object.h"
#include "BLI_math_color.h"
#include "DEG_depsgraph.h"
@ -2572,3 +2576,373 @@ void BKE_gpencil_merge_distance_stroke(bGPDframe *gpf,
BKE_gpencil_dissolve_points(gpf, gps, GP_SPOINT_TAG);
}
}
/* Helper: Check materials with same color. */
static int gpencil_check_same_material_color(Object *ob_gp, float color[4], Material *r_mat)
{
Material *ma = NULL;
float color_cu[4];
linearrgb_to_srgb_v3_v3(color_cu, color);
float hsv1[4];
rgb_to_hsv_v(color_cu, hsv1);
hsv1[3] = color[3];
for (int i = 1; i <= ob_gp->totcol; i++) {
ma = give_current_material(ob_gp, i);
MaterialGPencilStyle *gp_style = ma->gp_style;
/* Check color with small tolerance (better in HSV). */
float hsv2[4];
rgb_to_hsv_v(gp_style->fill_rgba, hsv2);
hsv2[3] = gp_style->fill_rgba[3];
if ((gp_style->fill_style == GP_STYLE_FILL_STYLE_SOLID) && (compare_v4v4(hsv1, hsv2, 0.01f))) {
r_mat = ma;
return i - 1;
}
}
r_mat = NULL;
return -1;
}
/* Helper: Add gpencil material using curve material as base. */
static Material *gpencil_add_from_curve_material(Main *bmain,
Object *ob_gp,
float cu_color[4],
const bool gpencil_lines,
const bool fill,
int *r_idx)
{
Material *mat_gp = BKE_gpencil_object_material_new(
bmain, ob_gp, (fill) ? "Material" : "Unassigned", r_idx);
MaterialGPencilStyle *gp_style = mat_gp->gp_style;
/* Stroke color. */
if (gpencil_lines) {
ARRAY_SET_ITEMS(gp_style->stroke_rgba, 0.0f, 0.0f, 0.0f, 1.0f);
gp_style->flag |= GP_STYLE_STROKE_SHOW;
}
else {
linearrgb_to_srgb_v4(gp_style->stroke_rgba, cu_color);
gp_style->flag &= ~GP_STYLE_STROKE_SHOW;
}
/* Fill color. */
linearrgb_to_srgb_v4(gp_style->fill_rgba, cu_color);
/* Fill is false if the original curve hasn't material assigned, so enable it. */
if (fill) {
gp_style->flag |= GP_STYLE_FILL_SHOW;
}
/* Check at least one is enabled. */
if (((gp_style->flag & GP_STYLE_STROKE_SHOW) == 0) &&
((gp_style->flag & GP_STYLE_FILL_SHOW) == 0)) {
gp_style->flag |= GP_STYLE_STROKE_SHOW;
}
return mat_gp;
}
/* Helper: Create new stroke section. */
static void gpencil_add_new_points(bGPDstroke *gps,
float *coord_array,
float pressure,
int init,
int totpoints,
float init_co[3],
bool last)
{
for (int i = 0; i < totpoints; i++) {
bGPDspoint *pt = &gps->points[i + init];
copy_v3_v3(&pt->x, &coord_array[3 * i]);
/* Be sure the last point is not on top of the first point of the curve or
* the close of the stroke will produce glitches. */
if ((last) && (i > 0) && (i == totpoints - 1)) {
float dist = len_v3v3(init_co, &pt->x);
if (dist < 0.1f) {
/* Interpolate between previous point and current to back slightly. */
bGPDspoint *pt_prev = &gps->points[i + init - 1];
interp_v3_v3v3(&pt->x, &pt_prev->x, &pt->x, 0.95f);
}
}
pt->pressure = pressure;
pt->strength = 1.0f;
}
}
/* Helper: Get the first collection that includes the object. */
static Collection *gpencil_get_parent_collection(Scene *scene, Object *ob)
{
Collection *mycol = NULL;
FOREACH_SCENE_COLLECTION_BEGIN (scene, collection) {
for (CollectionObject *cob = collection->gobject.first; cob; cob = cob->next) {
if ((mycol == NULL) && (cob->ob == ob)) {
mycol = collection;
}
}
}
FOREACH_SCENE_COLLECTION_END;
return mycol;
}
/* Helper: Convert one spline to grease pencil stroke. */
static void gpencil_convert_spline(Main *bmain,
Scene *scene,
Object *ob_gp,
Object *ob_cu,
const bool gpencil_lines,
const bool use_collections,
const bool only_stroke,
bGPDframe *gpf,
Nurb *nu)
{
Curve *cu = (Curve *)ob_cu->data;
bool cyclic = true;
/* Create Stroke. */
bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "bGPDstroke");
gps->thickness = 1.0f;
gps->gradient_f = 1.0f;
ARRAY_SET_ITEMS(gps->gradient_s, 1.0f, 1.0f);
ARRAY_SET_ITEMS(gps->caps, GP_STROKE_CAP_ROUND, GP_STROKE_CAP_ROUND);
gps->inittime = 0.0f;
/* Enable recalculation flag by default. */
gps->flag |= GP_STROKE_RECALC_GEOMETRY;
gps->flag &= ~GP_STROKE_SELECT;
gps->flag |= GP_STROKE_3DSPACE;
gps->mat_nr = 0;
/* Count total points
* The total of points must consider that last point of each segment is equal to the first
* point of next segment.
*/
int totpoints = 0;
int segments = 0;
int resolu = nu->resolu + 1;
segments = nu->pntsu;
if (((nu->flagu & CU_NURB_CYCLIC) == 0) || (nu->pntsu == 2)) {
segments--;
cyclic = false;
}
totpoints = (resolu * segments) - (segments - 1);
/* Initialize triangle memory to dummy data. */
gps->tot_triangles = 0;
gps->triangles = NULL;
/* Materials
* Notice: The color of the material is the color of viewport and not the final shader color.
*/
Material *mat_gp = NULL;
bool fill = true;
/* Check if grease pencil has a material with same color.*/
float color[4];
if ((cu->mat) && (*cu->mat)) {
Material *mat_cu = *cu->mat;
copy_v4_v4(color, &mat_cu->r);
}
else {
/* Gray (unassigned from SVG add-on) */
zero_v4(color);
add_v3_fl(color, 0.6f);
color[3] = 1.0f;
fill = false;
}
/* Special case: If the color was created by the SVG add-on and the name contains '_stroke' and
* there is only one color, the stroke must not be closed, fill to false and use for
* stroke the fill color.
*/
bool do_stroke = false;
if (ob_cu->totcol == 1) {
Material *ma_stroke = give_current_material(ob_cu, 1);
if ((ma_stroke) && (strstr(ma_stroke->id.name, "_stroke") != NULL)) {
do_stroke = true;
}
}
int r_idx = gpencil_check_same_material_color(ob_gp, color, mat_gp);
if ((ob_cu->totcol > 0) && (r_idx < 0)) {
Material *mat_curve = give_current_material(ob_cu, 1);
mat_gp = gpencil_add_from_curve_material(bmain, ob_gp, color, gpencil_lines, fill, &r_idx);
if ((mat_curve) && (mat_curve->gp_style != NULL)) {
MaterialGPencilStyle *gp_style_cur = mat_curve->gp_style;
MaterialGPencilStyle *gp_style_gp = mat_gp->gp_style;
copy_v4_v4(gp_style_gp->mix_rgba, gp_style_cur->mix_rgba);
gp_style_gp->fill_style = gp_style_cur->fill_style;
gp_style_gp->mix_factor = gp_style_cur->mix_factor;
gp_style_gp->gradient_angle = gp_style_cur->gradient_angle;
}
/* If object has more than 1 material, use second material for stroke color. */
if ((!only_stroke) && (ob_cu->totcol > 1) && (give_current_material(ob_cu, 2))) {
mat_curve = give_current_material(ob_cu, 2);
linearrgb_to_srgb_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r);
mat_gp->gp_style->stroke_rgba[3] = mat_curve->a;
}
else if ((only_stroke) || (do_stroke)) {
/* Also use the first color if the fill is none for stroke color. */
if (ob_cu->totcol > 0) {
mat_curve = give_current_material(ob_cu, 1);
linearrgb_to_srgb_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r);
mat_gp->gp_style->stroke_rgba[3] = mat_curve->a;
/* Set stroke to on. */
mat_gp->gp_style->flag |= GP_STYLE_STROKE_SHOW;
/* Set fill to off. */
mat_gp->gp_style->flag &= ~GP_STYLE_FILL_SHOW;
}
}
}
CLAMP_MIN(r_idx, 0);
/* Assign material index to stroke. */
gps->mat_nr = r_idx;
/* Add stroke to frame.*/
BLI_addtail(&gpf->strokes, gps);
/* Read all segments of the curve. */
float *coord_array = NULL;
float init_co[3];
if (nu->type == CU_BEZIER) {
/* Allocate memory for storage points. */
gps->totpoints = totpoints;
gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
int init = 0;
resolu = nu->resolu + 1;
segments = nu->pntsu;
if (((nu->flagu & CU_NURB_CYCLIC) == 0) || (nu->pntsu == 2)) {
segments--;
}
/* Get all interpolated curve points of Beziert */
for (int s = 0; s < segments; s++) {
int inext = (s + 1) % nu->pntsu;
BezTriple *prevbezt = &nu->bezt[s];
BezTriple *bezt = &nu->bezt[inext];
bool last = (bool)(s == segments - 1);
coord_array = MEM_callocN((size_t)3 * resolu * sizeof(float), __func__);
for (int j = 0; j < 3; j++) {
BKE_curve_forward_diff_bezier(prevbezt->vec[1][j],
prevbezt->vec[2][j],
bezt->vec[0][j],
bezt->vec[1][j],
coord_array + j,
resolu - 1,
3 * sizeof(float));
}
/* Save first point coordinates. */
if (s == 0) {
copy_v3_v3(init_co, &coord_array[0]);
}
/* Add points to the stroke */
gpencil_add_new_points(gps, coord_array, bezt->radius, init, resolu, init_co, last);
/* Free memory. */
MEM_SAFE_FREE(coord_array);
/* As the last point of segment is the first point of next segment, back one array
* element to avoid duplicated points on the same location.
*/
init += resolu - 1;
}
}
else if (nu->type == CU_NURBS) {
if (nu->pntsv == 1) {
int nurb_points;
if (nu->flagu & CU_NURB_CYCLIC) {
resolu++;
nurb_points = nu->pntsu * resolu;
}
else {
nurb_points = (nu->pntsu - 1) * resolu;
}
/* Get all curve points. */
coord_array = MEM_callocN(sizeof(float[3]) * nurb_points, __func__);
BKE_nurb_makeCurve(nu, coord_array, NULL, NULL, NULL, resolu, sizeof(float[3]));
/* Allocate memory for storage points. */
gps->totpoints = nurb_points - 1;
gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
/* Add points. */
gpencil_add_new_points(gps, coord_array, 1.0f, 0, gps->totpoints, init_co, false);
MEM_SAFE_FREE(coord_array);
}
}
/* Cyclic curve, close stroke. */
if ((cyclic) && (!do_stroke)) {
BKE_gpencil_close_stroke(gps);
}
}
/* Convert a curve object to grease pencil stroke.
*
* \param bmain: Main thread pointer
* \param scene: Original scene.
* \param ob_gp: Grease pencil object to add strokes.
* \param ob_cu: Curve to convert.
* \param gpencil_lines: Use lines for strokes.
* \param use_collections: Create layers using collection names.
* \param only_stroke: The material must be only stroke without fill.
*/
void BKE_gpencil_convert_curve(Main *bmain,
Scene *scene,
Object *ob_gp,
Object *ob_cu,
const bool gpencil_lines,
const bool use_collections,
const bool only_stroke)
{
if (ELEM(NULL, ob_gp, ob_cu) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == NULL)) {
return;
}
Curve *cu = (Curve *)ob_cu->data;
bGPdata *gpd = (bGPdata *)ob_gp->data;
bGPDlayer *gpl = NULL;
/* If the curve is empty, cancel. */
if (cu->nurb.first == NULL) {
return;
}
/* Check if there is an active layer. */
if (use_collections) {
Collection *collection = gpencil_get_parent_collection(scene, ob_cu);
if (collection != NULL) {
gpl = BLI_findstring(&gpd->layers, collection->id.name + 2, offsetof(bGPDlayer, info));
if (gpl == NULL) {
gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true);
}
}
}
if (gpl == NULL) {
gpl = BKE_gpencil_layer_getactive(gpd);
if (gpl == NULL) {
gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
}
}
/* Check if there is an active frame and add if needed. */
bGPDframe *gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_COPY);
/* Read all splines of the curve and create a stroke for each. */
for (Nurb *nu = cu->nurb.first; nu; nu = nu->next) {
gpencil_convert_spline(
bmain, scene, ob_gp, ob_cu, gpencil_lines, use_collections, only_stroke, gpf, nu);
}
/* Tag for recalculation */
DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
}

View File

@ -2009,6 +2009,7 @@ void OBJECT_OT_duplicates_make_real(wmOperatorType *ot)
static const EnumPropertyItem convert_target_items[] = {
{OB_CURVE, "CURVE", ICON_OUTLINER_OB_CURVE, "Curve from Mesh/Text", ""},
{OB_MESH, "MESH", ICON_OUTLINER_OB_MESH, "Mesh from Curve/Meta/Surf/Text", ""},
{OB_GPENCIL, "GPENCIL", ICON_OUTLINER_OB_GREASEPENCIL, "Grease Pencil from Curve", ""},
{0, NULL, 0, NULL, NULL},
};
@ -2133,12 +2134,14 @@ static int convert_exec(bContext *C, wmOperator *op)
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
View3D *v3d = CTX_wm_view3d(C);
Base *basen = NULL, *basact = NULL;
Object *ob1, *obact = CTX_data_active_object(C);
Curve *cu;
Nurb *nu;
MetaBall *mb;
Mesh *me;
Object *gpencil_ob = NULL;
const short target = RNA_enum_get(op->ptr, "target");
bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
int a, mballConverted = 0;
@ -2380,6 +2383,24 @@ static int convert_exec(bContext *C, wmOperator *op)
/* meshes doesn't use displist */
BKE_object_free_curve_cache(newob);
}
else if (target == OB_GPENCIL) {
if (ob->type != OB_CURVE) {
BKE_report(
op->reports, RPT_ERROR, "Convert Surfaces to Grease Pencil is not supported.");
}
else {
/* Create a new grease pencil object only if it was not created before.
* All curves selected are converted as strokes of the same grease pencil object.
* Nurbs Surface are not supported.
*/
if (gpencil_ob == NULL) {
const float *cur = scene->cursor.location;
ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0;
gpencil_ob = ED_gpencil_add_object(C, scene, cur, local_view_bits);
}
BKE_gpencil_convert_curve(bmain, scene, gpencil_ob, ob, false, false, true);
}
}
}
else if (ob->type == OB_MBALL && target == OB_MESH) {
Object *baseob;

View File

@ -37,6 +37,7 @@
#include "DNA_object_types.h"
#include "BKE_layer.h"
#include "BKE_gpencil.h"
#include "DEG_depsgraph.h"
@ -685,6 +686,30 @@ static bool rna_Object_update_from_editmode(Object *ob, Main *bmain)
}
return result;
}
bool rna_Object_generate_gpencil_strokes(Object *ob,
bContext *C,
ReportList *reports,
Object *ob_gpencil,
bool gpencil_lines,
bool use_collections)
{
if (ob->type != OB_CURVE) {
BKE_reportf(reports,
RPT_ERROR,
"Object '%s' not valid for this operation! Only curves supported.",
ob->id.name + 2);
return false;
}
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
BKE_gpencil_convert_curve(bmain, scene, ob_gpencil, ob, gpencil_lines, use_collections, false);
WM_main_add_notifier(NC_GPENCIL | ND_DATA, NULL);
return true;
}
#else /* RNA_RUNTIME */
void RNA_api_object(StructRNA *srna)
@ -1128,6 +1153,20 @@ void RNA_api_object(StructRNA *srna)
RNA_def_function_ui_description(func,
"Release memory used by caches associated with this object. "
"Intended to be used by render engines only");
/* Convert curve object to gpencil strokes. */
func = RNA_def_function(srna, "generate_gpencil_strokes", "rna_Object_generate_gpencil_strokes");
RNA_def_function_ui_description(func, "Convert a curve object to grease pencil strokes.");
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
parm = RNA_def_pointer(
func, "ob_gpencil", "Object", "", "Grease Pencil object used to create new strokes");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
parm = RNA_def_boolean(func, "gpencil_lines", 0, "", "Create Lines");
parm = RNA_def_boolean(func, "use_collections", 1, "", "Use Collections");
parm = RNA_def_boolean(func, "result", 0, "", "Result");
RNA_def_function_return(func, parm);
}
#endif /* RNA_RUNTIME */