This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/gpencil_curve.c
Julian Eisel 65166e145b Cleanup: Remove scene frame macros (CFRA et al.)
Removes the following macros for scene/render frame values:
- `CFRA`
- `SUBFRA`
- `SFRA`
- `EFRA`

These macros don't add much, other than saving a few characters when typing.
It's not immediately clear what they refer to, they just hide what they
actually access. Just be explicit and clear about that.
Plus these macros gave read and write access to the variables, so eyesores like
this would be done (eyesore because it looks like assigning to a constant):
```
CFRA = some_frame_nbr;
```

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D15311
2022-06-30 18:38:44 +02:00

1373 lines
44 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2008 Blender Foundation. */
/** \file
* \ingroup bke
*/
#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CLG_log.h"
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_math_vector.h"
#include "BLT_translation.h"
#include "DNA_collection_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_material_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_scene_types.h"
#include "BKE_collection.h"
#include "BKE_context.h"
#include "BKE_curve.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_curve.h"
#include "BKE_gpencil_geom.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_object.h"
#include "curve_fit_nd.h"
#include "DEG_depsgraph_query.h"
#define COORD_FITTING_INFLUENCE 20.0f
/* -------------------------------------------------------------------- */
/** \name Convert to curve object
* \{ */
/* Helper: Check materials with same color. */
static int gpencil_check_same_material_color(Object *ob_gp,
const float color_stroke[4],
const float color_fill[4],
const bool do_stroke,
const bool do_fill,
Material **r_mat)
{
int index = -1;
Material *ma = NULL;
*r_mat = NULL;
float color_cu[4];
float hsv_stroke[4], hsv_fill[4];
copy_v4_v4(color_cu, color_stroke);
zero_v3(hsv_stroke);
rgb_to_hsv_v(color_cu, hsv_stroke);
hsv_stroke[3] = color_stroke[3];
copy_v4_v4(color_cu, color_fill);
zero_v3(hsv_fill);
rgb_to_hsv_v(color_cu, hsv_fill);
hsv_fill[3] = color_fill[3];
bool match_stroke = false;
bool match_fill = false;
for (int i = 1; i <= ob_gp->totcol; i++) {
ma = BKE_object_material_get(ob_gp, i);
MaterialGPencilStyle *gp_style = ma->gp_style;
const bool fill = (gp_style->fill_style == GP_MATERIAL_FILL_STYLE_SOLID);
const bool stroke = (gp_style->fill_style == GP_MATERIAL_STROKE_STYLE_SOLID);
if (do_fill && !fill) {
continue;
}
if (do_stroke && !stroke) {
continue;
}
/* Check color with small tolerance (better result in HSV). */
float hsv2[4];
if (do_fill) {
zero_v3(hsv2);
rgb_to_hsv_v(gp_style->fill_rgba, hsv2);
hsv2[3] = gp_style->fill_rgba[3];
if (compare_v4v4(hsv_fill, hsv2, 0.01f)) {
*r_mat = ma;
index = i - 1;
match_fill = true;
}
}
else {
match_fill = true;
}
if (do_stroke) {
zero_v3(hsv2);
rgb_to_hsv_v(gp_style->stroke_rgba, hsv2);
hsv2[3] = gp_style->stroke_rgba[3];
if (compare_v4v4(hsv_stroke, hsv2, 0.01f)) {
*r_mat = ma;
index = i - 1;
match_stroke = true;
}
}
else {
match_stroke = true;
}
/* If match, don't look for more. */
if (match_stroke || match_fill) {
break;
}
}
if (!match_stroke || !match_fill) {
*r_mat = NULL;
index = -1;
}
return index;
}
/* Helper: Add gpencil material using curve material as base. */
static Material *gpencil_add_from_curve_material(Main *bmain,
Object *ob_gp,
const float stroke_color[4],
const float fill_color[4],
const bool stroke,
const bool fill,
int *r_idx)
{
Material *mat_gp = BKE_gpencil_object_material_new(bmain, ob_gp, "Material", r_idx);
MaterialGPencilStyle *gp_style = mat_gp->gp_style;
/* Stroke color. */
if (stroke) {
copy_v4_v4(mat_gp->gp_style->stroke_rgba, stroke_color);
gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
}
/* Fill color. */
if (fill) {
copy_v4_v4(mat_gp->gp_style->fill_rgba, fill_color);
gp_style->flag |= GP_MATERIAL_FILL_SHOW;
}
/* Check at least one is enabled. */
if (((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0) &&
((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) {
gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
}
return mat_gp;
}
/* Helper: Create new stroke section. */
static void gpencil_add_new_points(bGPDstroke *gps,
const float *coord_array,
const float pressure_start,
const float pressure_end,
const int init,
const int totpoints,
const float init_co[3],
const bool last)
{
BLI_assert(totpoints > 0);
const float step = 1.0f / ((float)totpoints - 1.0f);
float factor = 0.0f;
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->strength = 1.0f;
pt->pressure = interpf(pressure_end, pressure_start, factor);
factor += step;
}
}
/* 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) {
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
if ((mycol == NULL) && (cob->ob == ob)) {
mycol = collection;
}
}
}
FOREACH_SCENE_COLLECTION_END;
return mycol;
}
static int gpencil_get_stroke_material_fromcurve(
Main *bmain, Object *ob_gp, Object *ob_cu, bool *do_stroke, bool *do_fill)
{
Curve *cu = (Curve *)ob_cu->data;
Material *mat_gp = NULL;
Material *mat_curve_stroke = NULL;
Material *mat_curve_fill = NULL;
float color_stroke[4] = {0.0f, 0.0f, 0.0f, 0.0f};
float color_fill[4] = {0.0f, 0.0f, 0.0f, 0.0f};
/* If the curve has 2 materials, the first is considered as Fill and the second as Stroke.
* If the has only one material, if the name contains "_stroke",
* it's used as a stroke, otherwise as fill. */
if (ob_cu->totcol >= 2) {
*do_stroke = true;
*do_fill = true;
mat_curve_fill = BKE_object_material_get(ob_cu, 1);
mat_curve_stroke = BKE_object_material_get(ob_cu, 2);
}
else if (ob_cu->totcol == 1) {
mat_curve_stroke = BKE_object_material_get(ob_cu, 1);
if ((mat_curve_stroke) && (strstr(mat_curve_stroke->id.name, "_stroke") != NULL)) {
*do_stroke = true;
*do_fill = false;
mat_curve_fill = NULL;
}
else {
*do_stroke = false;
*do_fill = true;
/* Invert materials. */
mat_curve_fill = mat_curve_stroke;
mat_curve_stroke = NULL;
}
}
else {
/* No materials in the curve. */
*do_fill = false;
return -1;
}
if (mat_curve_stroke) {
copy_v4_v4(color_stroke, &mat_curve_stroke->r);
}
if (mat_curve_fill) {
copy_v4_v4(color_fill, &mat_curve_fill->r);
}
int r_idx = gpencil_check_same_material_color(
ob_gp, color_stroke, color_fill, *do_stroke, *do_fill, &mat_gp);
if ((ob_gp->totcol < r_idx) || (r_idx < 0)) {
mat_gp = gpencil_add_from_curve_material(
bmain, ob_gp, color_stroke, color_fill, *do_stroke, *do_fill, &r_idx);
}
/* Set fill and stroke depending of curve type (3D or 2D). */
if ((cu->flag & CU_3D) || ((cu->flag & (CU_FRONT | CU_BACK)) == 0)) {
mat_gp->gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
mat_gp->gp_style->flag &= ~GP_MATERIAL_FILL_SHOW;
}
else {
mat_gp->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
mat_gp->gp_style->flag |= GP_MATERIAL_FILL_SHOW;
}
return r_idx;
}
/* Helper: Convert one spline to grease pencil stroke. */
static void gpencil_convert_spline(Main *bmain,
Object *ob_gp,
Object *ob_cu,
const float scale_thickness,
const float sample,
bGPDframe *gpf,
Nurb *nu)
{
bGPdata *gpd = (bGPdata *)ob_gp->data;
bool cyclic = true;
/* Create Stroke. */
bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "bGPDstroke");
gps->thickness = 1.0f;
gps->fill_opacity_fac = 1.0f;
gps->hardeness = 1.0f;
gps->uv_scale = 1.0f;
ARRAY_SET_ITEMS(gps->aspect_ratio, 1.0f, 1.0f);
ARRAY_SET_ITEMS(gps->caps, GP_STROKE_CAP_ROUND, GP_STROKE_CAP_ROUND);
gps->inittime = 0.0f;
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) {
segments--;
cyclic = false;
}
totpoints = (resolu * segments) - (segments - 1);
/* Materials
* Notice: The color of the material is the color of viewport and not the final shader color.
*/
bool do_stroke, do_fill;
int r_idx = gpencil_get_stroke_material_fromcurve(bmain, ob_gp, ob_cu, &do_stroke, &do_fill);
CLAMP_MIN(r_idx, 0);
/* Assign material index to stroke. */
gps->mat_nr = r_idx;
/* Add stroke to frame. */
BLI_addtail(&gpf->strokes, gps);
float *coord_array = NULL;
float init_co[3];
switch (nu->type) {
case CU_POLY: {
/* Allocate memory for storage points. */
gps->totpoints = nu->pntsu;
gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
/* Increase thickness for this type. */
gps->thickness = 10.0f;
/* Get all curve points */
for (int s = 0; s < gps->totpoints; s++) {
BPoint *bp = &nu->bp[s];
bGPDspoint *pt = &gps->points[s];
copy_v3_v3(&pt->x, bp->vec);
pt->pressure = bp->radius;
pt->strength = 1.0f;
}
break;
}
case 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) {
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,
sizeof(float[3]));
}
/* Save first point coordinates. */
if (s == 0) {
copy_v3_v3(init_co, &coord_array[0]);
}
/* Add points to the stroke */
float radius_start = prevbezt->radius * scale_thickness;
float radius_end = bezt->radius * scale_thickness;
gpencil_add_new_points(
gps, coord_array, radius_start, radius_end, 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;
}
break;
}
case 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;
gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
/* Add points. */
gpencil_add_new_points(gps, coord_array, 1.0f, 1.0f, 0, gps->totpoints, init_co, false);
MEM_SAFE_FREE(coord_array);
}
break;
}
default: {
break;
}
}
/* Cyclic curve, close stroke. */
if (cyclic) {
BKE_gpencil_stroke_close(gps);
}
if (sample > 0.0f) {
BKE_gpencil_stroke_sample(gpd, gps, sample, false, 0);
}
/* Recalc fill geometry. */
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
static void gpencil_editstroke_deselect_all(bGPDcurve *gpc)
{
for (int i = 0; i < gpc->tot_curve_points; i++) {
bGPDcurve_point *gpc_pt = &gpc->curve_points[i];
BezTriple *bezt = &gpc_pt->bezt;
gpc_pt->flag &= ~GP_CURVE_POINT_SELECT;
BEZT_DESEL_ALL(bezt);
}
gpc->flag &= ~GP_CURVE_SELECT;
}
void BKE_gpencil_convert_curve(Main *bmain,
Scene *scene,
Object *ob_gp,
Object *ob_cu,
const bool use_collections,
const float scale_thickness,
const float sample)
{
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 = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2);
if (gpl == NULL) {
gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true, false);
}
}
}
if (gpl == NULL) {
gpl = BKE_gpencil_layer_active_get(gpd);
if (gpl == NULL) {
gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false);
}
}
/* Check if there is an active frame and add if needed. */
bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_COPY);
/* Read all splines of the curve and create a stroke for each. */
LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
gpencil_convert_spline(bmain, ob_gp, ob_cu, scale_thickness, sample, gpf, nu);
}
/* Merge any similar material. */
int removed = 0;
BKE_gpencil_merge_materials(ob_gp, 0.001f, 0.001f, 0.001f, &removed);
/* Remove any unused slot. */
int actcol = ob_gp->actcol;
for (int slot = 1; slot <= ob_gp->totcol; slot++) {
while (slot <= ob_gp->totcol && !BKE_object_material_slot_used(ob_gp, slot)) {
ob_gp->actcol = slot;
BKE_object_material_slot_remove(bmain, ob_gp);
if (actcol >= slot) {
actcol--;
}
}
}
ob_gp->actcol = actcol;
/* Tag for recalculation */
DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
DEG_id_tag_update(&ob_gp->id, ID_RECALC_GEOMETRY);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edit-Curve Kernel Functions
* \{ */
static bGPDcurve *gpencil_stroke_editcurve_generate_edgecases(bGPDstroke *gps,
const float stroke_radius)
{
BLI_assert(gps->totpoints < 3);
if (gps->totpoints == 1) {
bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_new(1);
bGPDspoint *pt = &gps->points[0];
bGPDcurve_point *cpt = &editcurve->curve_points[0];
BezTriple *bezt = &cpt->bezt;
/* Handles are twice as long as the radius of the point. */
float offset = (pt->pressure * stroke_radius) * 2.0f;
float tmp_vec[3];
for (int j = 0; j < 3; j++) {
copy_v3_v3(tmp_vec, &pt->x);
/* Move handles along the x-axis away from the control point */
tmp_vec[0] += (float)(j - 1) * offset;
copy_v3_v3(bezt->vec[j], tmp_vec);
}
cpt->pressure = pt->pressure;
cpt->strength = pt->strength;
copy_v4_v4(cpt->vert_color, pt->vert_color);
/* default handle type */
bezt->h1 = HD_FREE;
bezt->h2 = HD_FREE;
cpt->point_index = 0;
return editcurve;
}
if (gps->totpoints == 2) {
bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_new(2);
bGPDspoint *first_pt = &gps->points[0];
bGPDspoint *last_pt = &gps->points[1];
float length = len_v3v3(&first_pt->x, &last_pt->x);
float offset = length / 3;
float dir[3];
sub_v3_v3v3(dir, &last_pt->x, &first_pt->x);
for (int i = 0; i < 2; i++) {
bGPDspoint *pt = &gps->points[i];
bGPDcurve_point *cpt = &editcurve->curve_points[i];
BezTriple *bezt = &cpt->bezt;
float tmp_vec[3];
for (int j = 0; j < 3; j++) {
copy_v3_v3(tmp_vec, dir);
normalize_v3_length(tmp_vec, (float)(j - 1) * offset);
add_v3_v3v3(bezt->vec[j], &pt->x, tmp_vec);
}
cpt->pressure = pt->pressure;
cpt->strength = pt->strength;
copy_v4_v4(cpt->vert_color, pt->vert_color);
/* default handle type */
bezt->h1 = HD_VECT;
bezt->h2 = HD_VECT;
cpt->point_index = 0;
}
return editcurve;
}
return NULL;
}
bGPDcurve *BKE_gpencil_stroke_editcurve_generate(bGPDstroke *gps,
const float error_threshold,
const float corner_angle,
const float stroke_radius)
{
if (gps->totpoints < 3) {
return gpencil_stroke_editcurve_generate_edgecases(gps, stroke_radius);
}
#define POINT_DIM 9
float *points = MEM_callocN(sizeof(float) * gps->totpoints * POINT_DIM, __func__);
float diag_length = len_v3v3(gps->boundbox_min, gps->boundbox_max);
float tmp_vec[3];
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
int row = i * POINT_DIM;
/* normalize coordinate to 0..1 */
sub_v3_v3v3(tmp_vec, &pt->x, gps->boundbox_min);
mul_v3_v3fl(&points[row], tmp_vec, COORD_FITTING_INFLUENCE / diag_length);
points[row + 3] = pt->pressure / diag_length;
/* strength and color are already normalized */
points[row + 4] = pt->strength / diag_length;
mul_v4_v4fl(&points[row + 5], pt->vert_color, 1.0f / diag_length);
}
uint calc_flag = CURVE_FIT_CALC_HIGH_QUALIY;
if (gps->totpoints > 2 && gps->flag & GP_STROKE_CYCLIC) {
calc_flag |= CURVE_FIT_CALC_CYCLIC;
}
float *r_cubic_array = NULL;
unsigned int r_cubic_array_len = 0;
unsigned int *r_cubic_orig_index = NULL;
unsigned int *r_corners_index_array = NULL;
unsigned int r_corners_index_len = 0;
int r = curve_fit_cubic_to_points_refit_fl(points,
gps->totpoints,
POINT_DIM,
error_threshold,
calc_flag,
NULL,
0,
corner_angle,
&r_cubic_array,
&r_cubic_array_len,
&r_cubic_orig_index,
&r_corners_index_array,
&r_corners_index_len);
if (r != 0 || r_cubic_array_len < 1) {
return NULL;
}
uint curve_point_size = 3 * POINT_DIM;
bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_new(r_cubic_array_len);
for (int i = 0; i < r_cubic_array_len; i++) {
bGPDcurve_point *cpt = &editcurve->curve_points[i];
BezTriple *bezt = &cpt->bezt;
float *curve_point = &r_cubic_array[i * curve_point_size];
for (int j = 0; j < 3; j++) {
float *bez = &curve_point[j * POINT_DIM];
madd_v3_v3v3fl(bezt->vec[j], gps->boundbox_min, bez, diag_length / COORD_FITTING_INFLUENCE);
}
float *ctrl_point = &curve_point[1 * POINT_DIM];
cpt->pressure = ctrl_point[3] * diag_length;
cpt->strength = ctrl_point[4] * diag_length;
mul_v4_v4fl(cpt->vert_color, &ctrl_point[5], diag_length);
/* default handle type */
bezt->h1 = HD_ALIGN;
bezt->h2 = HD_ALIGN;
cpt->point_index = r_cubic_orig_index[i];
}
if (r_corners_index_len > 0 && r_corners_index_array != NULL) {
int start = 0, end = r_corners_index_len;
if ((r_corners_index_len > 1) && (calc_flag & CURVE_FIT_CALC_CYCLIC) == 0) {
start = 1;
end = r_corners_index_len - 1;
}
for (int i = start; i < end; i++) {
bGPDcurve_point *cpt = &editcurve->curve_points[r_corners_index_array[i]];
BezTriple *bezt = &cpt->bezt;
bezt->h1 = HD_FREE;
bezt->h2 = HD_FREE;
}
}
MEM_freeN(points);
if (r_cubic_array) {
free(r_cubic_array);
}
if (r_corners_index_array) {
free(r_corners_index_array);
}
if (r_cubic_orig_index) {
free(r_cubic_orig_index);
}
#undef POINT_DIM
return editcurve;
}
void BKE_gpencil_stroke_editcurve_update(bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps)
{
if (gps == NULL || gps->totpoints < 0) {
return;
}
if (gps->editcurve != NULL) {
BKE_gpencil_free_stroke_editcurve(gps);
}
float defaultpixsize = 1000.0f / gpd->pixfactor;
float stroke_radius = ((gps->thickness + gpl->line_change) / defaultpixsize) / 2.0f;
bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_generate(
gps, gpd->curve_edit_threshold, gpd->curve_edit_corner_angle, stroke_radius);
if (editcurve == NULL) {
return;
}
gps->editcurve = editcurve;
}
void BKE_gpencil_editcurve_stroke_sync_selection(bGPdata *UNUSED(gpd),
bGPDstroke *gps,
bGPDcurve *gpc)
{
if (gps->flag & GP_STROKE_SELECT) {
gpc->flag |= GP_CURVE_SELECT;
for (int i = 0; i < gpc->tot_curve_points; i++) {
bGPDcurve_point *gpc_pt = &gpc->curve_points[i];
bGPDspoint *pt = &gps->points[gpc_pt->point_index];
if (pt->flag & GP_SPOINT_SELECT) {
gpc_pt->flag |= GP_CURVE_POINT_SELECT;
BEZT_SEL_ALL(&gpc_pt->bezt);
}
else {
gpc_pt->flag &= ~GP_CURVE_POINT_SELECT;
BEZT_DESEL_ALL(&gpc_pt->bezt);
}
}
}
else {
gpc->flag &= ~GP_CURVE_SELECT;
gpencil_editstroke_deselect_all(gpc);
}
}
void BKE_gpencil_stroke_editcurve_sync_selection(bGPdata *gpd, bGPDstroke *gps, bGPDcurve *gpc)
{
if (gpc->flag & GP_CURVE_SELECT) {
gps->flag |= GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_set(gpd, gps);
for (int i = 0; i < gpc->tot_curve_points - 1; i++) {
bGPDcurve_point *gpc_pt = &gpc->curve_points[i];
bGPDspoint *pt = &gps->points[gpc_pt->point_index];
bGPDcurve_point *gpc_pt_next = &gpc->curve_points[i + 1];
if (gpc_pt->flag & GP_CURVE_POINT_SELECT) {
pt->flag |= GP_SPOINT_SELECT;
if (gpc_pt_next->flag & GP_CURVE_POINT_SELECT) {
/* select all the points after */
for (int j = gpc_pt->point_index + 1; j < gpc_pt_next->point_index; j++) {
bGPDspoint *pt_next = &gps->points[j];
pt_next->flag |= GP_SPOINT_SELECT;
}
}
}
else {
pt->flag &= ~GP_SPOINT_SELECT;
/* deselect all points after */
for (int j = gpc_pt->point_index + 1; j < gpc_pt_next->point_index; j++) {
bGPDspoint *pt_next = &gps->points[j];
pt_next->flag &= ~GP_SPOINT_SELECT;
}
}
}
bGPDcurve_point *gpc_first = &gpc->curve_points[0];
bGPDcurve_point *gpc_last = &gpc->curve_points[gpc->tot_curve_points - 1];
bGPDspoint *last_pt = &gps->points[gpc_last->point_index];
if (gpc_last->flag & GP_CURVE_POINT_SELECT) {
last_pt->flag |= GP_SPOINT_SELECT;
}
else {
last_pt->flag &= ~GP_SPOINT_SELECT;
}
if (gps->flag & GP_STROKE_CYCLIC) {
if (gpc_first->flag & GP_CURVE_POINT_SELECT && gpc_last->flag & GP_CURVE_POINT_SELECT) {
for (int i = gpc_last->point_index + 1; i < gps->totpoints; i++) {
bGPDspoint *pt_next = &gps->points[i];
pt_next->flag |= GP_SPOINT_SELECT;
}
}
else {
for (int i = gpc_last->point_index + 1; i < gps->totpoints; i++) {
bGPDspoint *pt_next = &gps->points[i];
pt_next->flag &= ~GP_SPOINT_SELECT;
}
}
}
}
else {
gps->flag &= ~GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_reset(gps);
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
pt->flag &= ~GP_SPOINT_SELECT;
}
}
}
static void gpencil_interpolate_fl_from_to(
float from, float to, float *point_offset, int it, int stride)
{
/* smooth interpolation */
float *r = point_offset;
for (int i = 0; i <= it; i++) {
float fac = (float)i / (float)it;
fac = 3.0f * fac * fac - 2.0f * fac * fac * fac; /* Smooth. */
*r = interpf(to, from, fac);
r = POINTER_OFFSET(r, stride);
}
}
static void gpencil_interpolate_v4_from_to(
float from[4], float to[4], float *point_offset, int it, int stride)
{
/* smooth interpolation */
float *r = point_offset;
for (int i = 0; i <= it; i++) {
float fac = (float)i / (float)it;
fac = 3.0f * fac * fac - 2.0f * fac * fac * fac; /* Smooth. */
interp_v4_v4v4(r, from, to, fac);
r = POINTER_OFFSET(r, stride);
}
}
static float gpencil_approximate_curve_segment_arclength(bGPDcurve_point *cpt_start,
bGPDcurve_point *cpt_end)
{
BezTriple *bezt_start = &cpt_start->bezt;
BezTriple *bezt_end = &cpt_end->bezt;
float chord_len = len_v3v3(bezt_start->vec[1], bezt_end->vec[1]);
float net_len = len_v3v3(bezt_start->vec[1], bezt_start->vec[2]);
net_len += len_v3v3(bezt_start->vec[2], bezt_end->vec[0]);
net_len += len_v3v3(bezt_end->vec[0], bezt_end->vec[1]);
return (chord_len + net_len) / 2.0f;
}
static void gpencil_calculate_stroke_points_curve_segment(
bGPDcurve_point *cpt, bGPDcurve_point *cpt_next, float *points_offset, int resolu, int stride)
{
/* sample points on all 3 axis between two curve points */
for (uint axis = 0; axis < 3; axis++) {
BKE_curve_forward_diff_bezier(cpt->bezt.vec[1][axis],
cpt->bezt.vec[2][axis],
cpt_next->bezt.vec[0][axis],
cpt_next->bezt.vec[1][axis],
POINTER_OFFSET(points_offset, sizeof(float) * axis),
(int)resolu,
stride);
}
/* interpolate other attributes */
gpencil_interpolate_fl_from_to(cpt->pressure,
cpt_next->pressure,
POINTER_OFFSET(points_offset, sizeof(float) * 3),
resolu,
stride);
gpencil_interpolate_fl_from_to(cpt->strength,
cpt_next->strength,
POINTER_OFFSET(points_offset, sizeof(float) * 4),
resolu,
stride);
gpencil_interpolate_v4_from_to(cpt->vert_color,
cpt_next->vert_color,
POINTER_OFFSET(points_offset, sizeof(float) * 5),
resolu,
stride);
}
static float *gpencil_stroke_points_from_editcurve_adaptive_resolu(
bGPDcurve_point *curve_point_array,
int curve_point_array_len,
int resolution,
bool is_cyclic,
int *r_points_len)
{
/* One stride contains: x, y, z, pressure, strength, Vr, Vg, Vb, Vmix_factor */
const uint stride = sizeof(float[9]);
const uint cpt_last = curve_point_array_len - 1;
const uint num_segments = (is_cyclic) ? curve_point_array_len : curve_point_array_len - 1;
int *segment_point_lengths = MEM_callocN(sizeof(int) * num_segments, __func__);
uint points_len = 1;
for (int i = 0; i < cpt_last; i++) {
bGPDcurve_point *cpt = &curve_point_array[i];
bGPDcurve_point *cpt_next = &curve_point_array[i + 1];
float arclen = gpencil_approximate_curve_segment_arclength(cpt, cpt_next);
int segment_resolu = (int)floorf(arclen * resolution);
CLAMP_MIN(segment_resolu, 1);
segment_point_lengths[i] = segment_resolu;
points_len += segment_resolu;
}
if (is_cyclic) {
bGPDcurve_point *cpt = &curve_point_array[cpt_last];
bGPDcurve_point *cpt_next = &curve_point_array[0];
float arclen = gpencil_approximate_curve_segment_arclength(cpt, cpt_next);
int segment_resolu = (int)floorf(arclen * resolution);
CLAMP_MIN(segment_resolu, 1);
segment_point_lengths[cpt_last] = segment_resolu;
points_len += segment_resolu;
}
float(*r_points)[9] = MEM_callocN((stride * points_len * (is_cyclic ? 2 : 1)), __func__);
float *points_offset = &r_points[0][0];
int point_index = 0;
for (int i = 0; i < cpt_last; i++) {
bGPDcurve_point *cpt_curr = &curve_point_array[i];
bGPDcurve_point *cpt_next = &curve_point_array[i + 1];
int segment_resolu = segment_point_lengths[i];
gpencil_calculate_stroke_points_curve_segment(
cpt_curr, cpt_next, points_offset, segment_resolu, stride);
/* update the index */
cpt_curr->point_index = point_index;
point_index += segment_resolu;
points_offset = POINTER_OFFSET(points_offset, segment_resolu * stride);
}
bGPDcurve_point *cpt_curr = &curve_point_array[cpt_last];
cpt_curr->point_index = point_index;
if (is_cyclic) {
bGPDcurve_point *cpt_next = &curve_point_array[0];
int segment_resolu = segment_point_lengths[cpt_last];
gpencil_calculate_stroke_points_curve_segment(
cpt_curr, cpt_next, points_offset, segment_resolu, stride);
}
MEM_freeN(segment_point_lengths);
*r_points_len = points_len;
return (float(*))r_points;
}
/**
* Helper: calculate the points on a curve with a fixed resolution.
*/
static float *gpencil_stroke_points_from_editcurve_fixed_resolu(bGPDcurve_point *curve_point_array,
int curve_point_array_len,
int resolution,
bool is_cyclic,
int *r_points_len)
{
/* One stride contains: x, y, z, pressure, strength, Vr, Vg, Vb, Vmix_factor */
const uint stride = sizeof(float[9]);
const uint array_last = curve_point_array_len - 1;
const uint resolu_stride = resolution * stride;
const uint points_len = BKE_curve_calc_coords_axis_len(
curve_point_array_len, resolution, is_cyclic, false);
float(*r_points)[9] = MEM_callocN((stride * points_len * (is_cyclic ? 2 : 1)), __func__);
float *points_offset = &r_points[0][0];
for (unsigned int i = 0; i < array_last; i++) {
bGPDcurve_point *cpt_curr = &curve_point_array[i];
bGPDcurve_point *cpt_next = &curve_point_array[i + 1];
gpencil_calculate_stroke_points_curve_segment(
cpt_curr, cpt_next, points_offset, resolution, stride);
/* update the index */
cpt_curr->point_index = i * resolution;
points_offset = POINTER_OFFSET(points_offset, resolu_stride);
}
bGPDcurve_point *cpt_curr = &curve_point_array[array_last];
cpt_curr->point_index = array_last * resolution;
if (is_cyclic) {
bGPDcurve_point *cpt_next = &curve_point_array[0];
gpencil_calculate_stroke_points_curve_segment(
cpt_curr, cpt_next, points_offset, resolution, stride);
}
*r_points_len = points_len;
return (float(*))r_points;
}
void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps,
const uint resolution,
const bool adaptive)
{
if (gps == NULL || gps->editcurve == NULL) {
return;
}
bGPDcurve *editcurve = gps->editcurve;
bGPDcurve_point *curve_point_array = editcurve->curve_points;
int curve_point_array_len = editcurve->tot_curve_points;
if (curve_point_array_len == 0) {
return;
}
/* Handle case for single curve point. */
if (curve_point_array_len == 1) {
bGPDcurve_point *cpt = &curve_point_array[0];
/* resize stroke point array */
gps->totpoints = 1;
gps->points = MEM_recallocN(gps->points, sizeof(bGPDspoint) * gps->totpoints);
if (gps->dvert != NULL) {
gps->dvert = MEM_recallocN(gps->dvert, sizeof(MDeformVert) * gps->totpoints);
}
bGPDspoint *pt = &gps->points[0];
copy_v3_v3(&pt->x, cpt->bezt.vec[1]);
pt->pressure = cpt->pressure;
pt->strength = cpt->strength;
copy_v4_v4(pt->vert_color, cpt->vert_color);
/* deselect */
pt->flag &= ~GP_SPOINT_SELECT;
gps->flag &= ~GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_reset(gps);
return;
}
bool is_cyclic = gps->flag & GP_STROKE_CYCLIC;
int points_len = 0;
float(*points)[9] = NULL;
if (adaptive) {
points = (float(*)[9])gpencil_stroke_points_from_editcurve_adaptive_resolu(
curve_point_array, curve_point_array_len, resolution, is_cyclic, &points_len);
}
else {
points = (float(*)[9])gpencil_stroke_points_from_editcurve_fixed_resolu(
curve_point_array, curve_point_array_len, resolution, is_cyclic, &points_len);
}
if (points == NULL || points_len == 0) {
return;
}
/* resize stroke point array */
gps->totpoints = points_len;
gps->points = MEM_recallocN(gps->points, sizeof(bGPDspoint) * gps->totpoints);
if (gps->dvert != NULL) {
gps->dvert = MEM_recallocN(gps->dvert, sizeof(MDeformVert) * gps->totpoints);
}
/* write new data to stroke point array */
for (int i = 0; i < points_len; i++) {
bGPDspoint *pt = &gps->points[i];
copy_v3_v3(&pt->x, &points[i][0]);
pt->pressure = points[i][3];
pt->strength = points[i][4];
copy_v4_v4(pt->vert_color, &points[i][5]);
/* deselect points */
pt->flag &= ~GP_SPOINT_SELECT;
}
gps->flag &= ~GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_reset(gps);
/* free temp data */
MEM_freeN(points);
}
void BKE_gpencil_editcurve_recalculate_handles(bGPDstroke *gps)
{
if (gps == NULL || gps->editcurve == NULL) {
return;
}
bool changed = false;
bGPDcurve *gpc = gps->editcurve;
if (gpc->tot_curve_points < 2) {
return;
}
if (gpc->tot_curve_points == 1) {
BKE_nurb_handle_calc(
&(gpc->curve_points[0].bezt), NULL, &(gpc->curve_points[0].bezt), false, 0);
gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE;
}
for (int i = 1; i < gpc->tot_curve_points - 1; i++) {
bGPDcurve_point *gpc_pt = &gpc->curve_points[i];
bGPDcurve_point *gpc_pt_prev = &gpc->curve_points[i - 1];
bGPDcurve_point *gpc_pt_next = &gpc->curve_points[i + 1];
/* update handle if point or neighbor is selected */
if (gpc_pt->flag & GP_CURVE_POINT_SELECT || gpc_pt_prev->flag & GP_CURVE_POINT_SELECT ||
gpc_pt_next->flag & GP_CURVE_POINT_SELECT) {
BezTriple *bezt = &gpc_pt->bezt;
BezTriple *bezt_prev = &gpc_pt_prev->bezt;
BezTriple *bezt_next = &gpc_pt_next->bezt;
BKE_nurb_handle_calc(bezt, bezt_prev, bezt_next, false, 0);
changed = true;
}
}
bGPDcurve_point *gpc_first = &gpc->curve_points[0];
bGPDcurve_point *gpc_last = &gpc->curve_points[gpc->tot_curve_points - 1];
bGPDcurve_point *gpc_first_next = &gpc->curve_points[1];
bGPDcurve_point *gpc_last_prev = &gpc->curve_points[gpc->tot_curve_points - 2];
if (gps->flag & GP_STROKE_CYCLIC) {
if (gpc_first->flag & GP_CURVE_POINT_SELECT || gpc_last->flag & GP_CURVE_POINT_SELECT) {
BezTriple *bezt_first = &gpc_first->bezt;
BezTriple *bezt_last = &gpc_last->bezt;
BezTriple *bezt_first_next = &gpc_first_next->bezt;
BezTriple *bezt_last_prev = &gpc_last_prev->bezt;
BKE_nurb_handle_calc(bezt_first, bezt_last, bezt_first_next, false, 0);
BKE_nurb_handle_calc(bezt_last, bezt_last_prev, bezt_first, false, 0);
changed = true;
}
}
else {
if (gpc_first->flag & GP_CURVE_POINT_SELECT || gpc_last->flag & GP_CURVE_POINT_SELECT) {
BezTriple *bezt_first = &gpc_first->bezt;
BezTriple *bezt_last = &gpc_last->bezt;
BezTriple *bezt_first_next = &gpc_first_next->bezt;
BezTriple *bezt_last_prev = &gpc_last_prev->bezt;
BKE_nurb_handle_calc(bezt_first, NULL, bezt_first_next, false, 0);
BKE_nurb_handle_calc(bezt_last, bezt_last_prev, NULL, false, 0);
changed = true;
}
}
if (changed) {
gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE;
}
}
/* Helper: count how many new curve points must be generated. */
static int gpencil_editcurve_subdivide_count(bGPDcurve *gpc, bool is_cyclic)
{
int count = 0;
for (int i = 0; i < gpc->tot_curve_points - 1; i++) {
bGPDcurve_point *cpt = &gpc->curve_points[i];
bGPDcurve_point *cpt_next = &gpc->curve_points[i + 1];
if (cpt->flag & GP_CURVE_POINT_SELECT && cpt_next->flag & GP_CURVE_POINT_SELECT) {
count++;
}
}
if (is_cyclic) {
bGPDcurve_point *cpt = &gpc->curve_points[0];
bGPDcurve_point *cpt_next = &gpc->curve_points[gpc->tot_curve_points - 1];
if (cpt->flag & GP_CURVE_POINT_SELECT && cpt_next->flag & GP_CURVE_POINT_SELECT) {
count++;
}
}
return count;
}
static void gpencil_editcurve_subdivide_curve_segment(bGPDcurve_point *cpt_start,
bGPDcurve_point *cpt_end,
bGPDcurve_point *cpt_new)
{
BezTriple *bezt_start = &cpt_start->bezt;
BezTriple *bezt_end = &cpt_end->bezt;
BezTriple *bezt_new = &cpt_new->bezt;
for (int axis = 0; axis < 3; axis++) {
float p0, p1, p2, p3, m0, m1, q0, q1, b;
p0 = bezt_start->vec[1][axis];
p1 = bezt_start->vec[2][axis];
p2 = bezt_end->vec[0][axis];
p3 = bezt_end->vec[1][axis];
m0 = (p0 + p1) / 2;
q0 = (p0 + 2 * p1 + p2) / 4;
b = (p0 + 3 * p1 + 3 * p2 + p3) / 8;
q1 = (p1 + 2 * p2 + p3) / 4;
m1 = (p2 + p3) / 2;
bezt_new->vec[0][axis] = q0;
bezt_new->vec[2][axis] = q1;
bezt_new->vec[1][axis] = b;
bezt_start->vec[2][axis] = m0;
bezt_end->vec[0][axis] = m1;
}
cpt_new->pressure = interpf(cpt_end->pressure, cpt_start->pressure, 0.5f);
cpt_new->strength = interpf(cpt_end->strength, cpt_start->strength, 0.5f);
interp_v4_v4v4(cpt_new->vert_color, cpt_start->vert_color, cpt_end->vert_color, 0.5f);
}
void BKE_gpencil_editcurve_subdivide(bGPDstroke *gps, const int cuts)
{
bGPDcurve *gpc = gps->editcurve;
if (gpc == NULL || gpc->tot_curve_points < 2) {
return;
}
bool is_cyclic = gps->flag & GP_STROKE_CYCLIC;
/* repeat for number of cuts */
for (int s = 0; s < cuts; s++) {
int old_tot_curve_points = gpc->tot_curve_points;
int new_num_curve_points = gpencil_editcurve_subdivide_count(gpc, is_cyclic);
if (new_num_curve_points == 0) {
break;
}
int new_tot_curve_points = old_tot_curve_points + new_num_curve_points;
bGPDcurve_point *temp_curve_points = (bGPDcurve_point *)MEM_callocN(
sizeof(bGPDcurve_point) * new_tot_curve_points, __func__);
bool prev_subdivided = false;
int j = 0;
for (int i = 0; i < old_tot_curve_points - 1; i++, j++) {
bGPDcurve_point *cpt = &gpc->curve_points[i];
bGPDcurve_point *cpt_next = &gpc->curve_points[i + 1];
if (cpt->flag & GP_CURVE_POINT_SELECT && cpt_next->flag & GP_CURVE_POINT_SELECT) {
bGPDcurve_point *cpt_new = &temp_curve_points[j + 1];
gpencil_editcurve_subdivide_curve_segment(cpt, cpt_next, cpt_new);
memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point));
memcpy(&temp_curve_points[j + 2], cpt_next, sizeof(bGPDcurve_point));
cpt_new->flag |= GP_CURVE_POINT_SELECT;
cpt_new->bezt.h1 = HD_ALIGN;
cpt_new->bezt.h2 = HD_ALIGN;
BEZT_SEL_ALL(&cpt_new->bezt);
prev_subdivided = true;
j++;
}
else if (!prev_subdivided) {
memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point));
prev_subdivided = false;
}
else {
prev_subdivided = false;
}
}
if (is_cyclic) {
bGPDcurve_point *cpt = &gpc->curve_points[old_tot_curve_points - 1];
bGPDcurve_point *cpt_next = &gpc->curve_points[0];
if (cpt->flag & GP_CURVE_POINT_SELECT && cpt_next->flag & GP_CURVE_POINT_SELECT) {
bGPDcurve_point *cpt_new = &temp_curve_points[j + 1];
gpencil_editcurve_subdivide_curve_segment(cpt, cpt_next, cpt_new);
memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point));
memcpy(&temp_curve_points[0], cpt_next, sizeof(bGPDcurve_point));
cpt_new->flag |= GP_CURVE_POINT_SELECT;
cpt_new->bezt.h1 = HD_ALIGN;
cpt_new->bezt.h2 = HD_ALIGN;
BEZT_SEL_ALL(&cpt_new->bezt);
}
else if (!prev_subdivided) {
memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point));
}
}
else {
bGPDcurve_point *cpt = &gpc->curve_points[old_tot_curve_points - 1];
memcpy(&temp_curve_points[j], cpt, sizeof(bGPDcurve_point));
}
MEM_freeN(gpc->curve_points);
gpc->curve_points = temp_curve_points;
gpc->tot_curve_points = new_tot_curve_points;
}
}
void BKE_gpencil_strokes_selected_update_editcurve(bGPdata *gpd)
{
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
/* For all selected strokes, update edit curve. */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
if (!BKE_gpencil_layer_is_editable(gpl)) {
continue;
}
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)) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* skip deselected stroke */
if (!(gps->flag & GP_STROKE_SELECT)) {
continue;
}
/* Generate the curve if there is none or the stroke was changed */
if (gps->editcurve == NULL) {
BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps);
/* Continue if curve could not be generated. */
if (gps->editcurve == NULL) {
continue;
}
}
else if (gps->editcurve->flag & GP_CURVE_NEEDS_STROKE_UPDATE) {
BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps);
}
/* Update the selection from the stroke to the curve. */
BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve);
gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE;
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
}
}
}
}
void BKE_gpencil_strokes_selected_sync_selection_editcurve(bGPdata *gpd)
{
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
/* Sync selection for all strokes with editcurve. */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
if (!BKE_gpencil_layer_is_editable(gpl)) {
continue;
}
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)) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
bGPDcurve *gpc = gps->editcurve;
if (gpc != NULL) {
/* Update the selection of every stroke that has an editcurve */
BKE_gpencil_stroke_editcurve_sync_selection(gpd, gps, gpc);
}
}
}
}
}
}
/** \} */