From 0be88c7d15d2ad1af284c6283370173647ae74eb Mon Sep 17 00:00:00 2001 From: Falk David Date: Fri, 13 Nov 2020 21:43:00 +0100 Subject: GPencil: Merge GSoC curve edit mode Differential Revision: https://developer.blender.org/D8660 This patch is the result of the GSoC 2020 "Editing Grease Pencil Strokes Using Curves" project. It adds a submode to greasepencil edit mode that allows for the transformation of greasepencil strokes using bezier curves. More information about the project can be found here: https://wiki.blender.org/wiki/User:Filedescriptor/GSoC_2020. --- source/blender/blenkernel/BKE_gpencil.h | 11 +- source/blender/blenkernel/BKE_gpencil_curve.h | 21 + source/blender/blenkernel/BKE_gpencil_geom.h | 31 +- source/blender/blenkernel/intern/gpencil.c | 112 +- source/blender/blenkernel/intern/gpencil_curve.c | 874 ++++++++++- source/blender/blenkernel/intern/gpencil_geom.c | 84 +- .../blender/blenkernel/intern/gpencil_modifier.c | 8 +- source/blender/blenloader/intern/versioning_280.c | 2 +- source/blender/blenloader/intern/versioning_290.c | 14 + .../blender/draw/engines/overlay/overlay_gpencil.c | 61 +- .../blender/draw/engines/overlay/overlay_private.h | 3 + .../overlay/shaders/edit_curve_handle_geom.glsl | 6 + .../overlay/shaders/edit_curve_point_vert.glsl | 5 +- source/blender/draw/intern/draw_cache.h | 2 + .../blender/draw/intern/draw_cache_impl_gpencil.c | 180 +++ source/blender/editors/gpencil/CMakeLists.txt | 1 + source/blender/editors/gpencil/annotate_paint.c | 6 +- .../blender/editors/gpencil/editaction_gpencil.c | 2 +- .../blender/editors/gpencil/gpencil_add_monkey.c | 56 +- .../blender/editors/gpencil/gpencil_add_stroke.c | 2 +- source/blender/editors/gpencil/gpencil_convert.c | 4 +- source/blender/editors/gpencil/gpencil_data.c | 51 +- source/blender/editors/gpencil/gpencil_edit.c | 1600 ++++++++++++++------ .../blender/editors/gpencil/gpencil_edit_curve.c | 214 +++ source/blender/editors/gpencil/gpencil_fill.c | 4 +- source/blender/editors/gpencil/gpencil_intern.h | 59 +- .../blender/editors/gpencil/gpencil_interpolate.c | 14 +- source/blender/editors/gpencil/gpencil_merge.c | 7 +- source/blender/editors/gpencil/gpencil_ops.c | 22 + source/blender/editors/gpencil/gpencil_paint.c | 10 +- source/blender/editors/gpencil/gpencil_primitive.c | 4 +- .../blender/editors/gpencil/gpencil_sculpt_paint.c | 7 +- source/blender/editors/gpencil/gpencil_select.c | 1577 ++++++++++++++----- .../blender/editors/gpencil/gpencil_trace_utils.c | 5 +- source/blender/editors/gpencil/gpencil_utils.c | 101 +- source/blender/editors/gpencil/gpencil_uv.c | 8 +- .../blender/editors/gpencil/gpencil_vertex_paint.c | 25 +- source/blender/editors/include/ED_gpencil.h | 4 +- source/blender/editors/screen/area.c | 4 + .../editors/transform/transform_convert_gpencil.c | 436 +++++- .../blender/editors/transform/transform_generics.c | 2 +- .../blender/editors/transform/transform_gizmo_3d.c | 42 +- .../editors/transform/transform_mode_gpopacity.c | 14 + .../transform/transform_mode_gpshrinkfatten.c | 14 + .../gpencil_modifiers/intern/MOD_gpencilarmature.c | 3 +- .../gpencil_modifiers/intern/MOD_gpencilarray.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencilbuild.c | 51 +- .../gpencil_modifiers/intern/MOD_gpencilhook.c | 3 +- .../gpencil_modifiers/intern/MOD_gpencillattice.c | 3 +- .../gpencil_modifiers/intern/MOD_gpencilmirror.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencilmultiply.c | 5 +- .../gpencil_modifiers/intern/MOD_gpenciloffset.c | 3 +- .../gpencil_modifiers/intern/MOD_gpencilsimplify.c | 10 +- .../gpencil_modifiers/intern/MOD_gpencilsubdiv.c | 3 +- .../gpencil_modifiers/intern/MOD_gpenciltexture.c | 3 +- source/blender/makesdna/DNA_curve_types.h | 47 + source/blender/makesdna/DNA_gpencil_types.h | 84 +- source/blender/makesdna/DNA_scene_types.h | 2 +- source/blender/makesrna/intern/rna_gpencil.c | 398 ++++- 59 files changed, 5270 insertions(+), 1058 deletions(-) create mode 100644 source/blender/editors/gpencil/gpencil_edit_curve.c (limited to 'source/blender') diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 7d50599a8f0..6dc8d1ef06e 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -48,6 +48,7 @@ struct bGPDlayer; struct bGPDlayer_Mask; struct bGPDspoint; struct bGPDstroke; +struct bGPDcurve; struct bGPdata; #define GPENCIL_SIMPLIFY(scene) ((scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_ENABLE)) @@ -89,6 +90,7 @@ struct bGPdata; void BKE_gpencil_free_point_weights(struct MDeformVert *dvert); void BKE_gpencil_free_stroke_weights(struct bGPDstroke *gps); +void BKE_gpencil_free_stroke_editcurve(struct bGPDstroke *gps); void BKE_gpencil_free_stroke(struct bGPDstroke *gps); bool BKE_gpencil_free_strokes(struct bGPDframe *gpf); void BKE_gpencil_free_frames(struct bGPDlayer *gpl); @@ -102,6 +104,7 @@ void BKE_gpencil_batch_cache_dirty_tag(struct bGPdata *gpd); void BKE_gpencil_batch_cache_free(struct bGPdata *gpd); void BKE_gpencil_stroke_sync_selection(struct bGPDstroke *gps); +void BKE_gpencil_curve_sync_selection(struct bGPDstroke *gps); struct bGPDframe *BKE_gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe); struct bGPDframe *BKE_gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe); @@ -111,7 +114,10 @@ struct bGPdata *BKE_gpencil_data_addnew(struct Main *bmain, const char name[]); struct bGPDframe *BKE_gpencil_frame_duplicate(const struct bGPDframe *gpf_src); struct bGPDlayer *BKE_gpencil_layer_duplicate(const struct bGPDlayer *gpl_src); void BKE_gpencil_frame_copy_strokes(struct bGPDframe *gpf_src, struct bGPDframe *gpf_dst); -struct bGPDstroke *BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, const bool dup_points); +struct bGPDcurve *BKE_gpencil_stroke_curve_duplicate(struct bGPDcurve *gpc_src); +struct bGPDstroke *BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, + const bool dup_points, + const bool dup_curve); struct bGPdata *BKE_gpencil_data_duplicate(struct Main *bmain, const struct bGPdata *gpd, @@ -160,6 +166,8 @@ struct bGPDstroke *BKE_gpencil_stroke_add_existing_style(struct bGPDframe *gpf, int totpoints, short thickness); +struct bGPDcurve *BKE_gpencil_stroke_editcurve_new(const int tot_curve_points); + /* Stroke and Fill - Alpha Visibility Threshold */ #define GPENCIL_ALPHA_OPACITY_THRESH 0.001f #define GPENCIL_STRENGTH_MIN 0.003f @@ -247,6 +255,7 @@ float BKE_gpencil_multiframe_falloff_calc( void BKE_gpencil_palette_ensure(struct Main *bmain, struct Scene *scene); bool BKE_gpencil_from_image(struct SpaceImage *sima, + struct bGPdata *gpd, struct bGPDframe *gpf, const float size, const bool mask); diff --git a/source/blender/blenkernel/BKE_gpencil_curve.h b/source/blender/blenkernel/BKE_gpencil_curve.h index c61427c6c4a..1821972469c 100644 --- a/source/blender/blenkernel/BKE_gpencil_curve.h +++ b/source/blender/blenkernel/BKE_gpencil_curve.h @@ -30,6 +30,10 @@ extern "C" { struct Main; struct Object; struct Scene; +struct bGPdata; +struct bGPDlayer; +struct bGPDstroke; +struct bGPDcurve; void BKE_gpencil_convert_curve(struct Main *bmain, struct Scene *scene, @@ -39,6 +43,23 @@ void BKE_gpencil_convert_curve(struct Main *bmain, const float scale_thickness, const float sample); +struct bGPDcurve *BKE_gpencil_stroke_editcurve_generate(struct bGPDstroke *gps, + const float error_threshold, + const float corner_angle, + const float stroke_radius); +void BKE_gpencil_stroke_editcurve_update(struct bGPdata *gpd, + struct bGPDlayer *gpl, + struct bGPDstroke *gps); +void BKE_gpencil_editcurve_stroke_sync_selection(struct bGPDstroke *gps, struct bGPDcurve *gpc); +void BKE_gpencil_stroke_editcurve_sync_selection(struct bGPDstroke *gps, struct bGPDcurve *gpc); +void BKE_gpencil_strokes_selected_update_editcurve(struct bGPdata *gpd); +void BKE_gpencil_strokes_selected_sync_selection_editcurve(struct bGPdata *gpd); +void BKE_gpencil_stroke_update_geometry_from_editcurve(struct bGPDstroke *gps, + const uint resolution, + const bool is_adaptive); +void BKE_gpencil_editcurve_recalculate_handles(struct bGPDstroke *gps); +void BKE_gpencil_editcurve_subdivide(struct bGPDstroke *gps, const int cuts); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index 404cbbe1741..6917a62053c 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -51,11 +51,17 @@ void BKE_gpencil_stroke_boundingbox_calc(struct bGPDstroke *gps); /* stroke geometry utilities */ void BKE_gpencil_stroke_normal(const struct bGPDstroke *gps, float r_normal[3]); -void BKE_gpencil_stroke_simplify_adaptive(struct bGPDstroke *gps, float epsilon); -void BKE_gpencil_stroke_simplify_fixed(struct bGPDstroke *gps); -void BKE_gpencil_stroke_subdivide(struct bGPDstroke *gps, int level, int type); -bool BKE_gpencil_stroke_trim(struct bGPDstroke *gps); -void BKE_gpencil_stroke_merge_distance(struct bGPDframe *gpf, +void BKE_gpencil_stroke_simplify_adaptive(struct bGPdata *gpd, + struct bGPDstroke *gps, + float epsilon); +void BKE_gpencil_stroke_simplify_fixed(struct bGPdata *gpd, struct bGPDstroke *gps); +void BKE_gpencil_stroke_subdivide(struct bGPdata *gpd, + struct bGPDstroke *gps, + int level, + int type); +bool BKE_gpencil_stroke_trim(struct bGPdata *gpd, struct bGPDstroke *gps); +void BKE_gpencil_stroke_merge_distance(struct bGPdata *gpd, + struct bGPDframe *gpf, struct bGPDstroke *gps, const float threshold, const bool use_unselected); @@ -72,7 +78,7 @@ void BKE_gpencil_stroke_2d_flat_ref(const struct bGPDspoint *ref_points, const float scale, int *r_direction); void BKE_gpencil_stroke_fill_triangulate(struct bGPDstroke *gps); -void BKE_gpencil_stroke_geometry_update(struct bGPDstroke *gps); +void BKE_gpencil_stroke_geometry_update(struct bGPdata *gpd, struct bGPDstroke *gps); void BKE_gpencil_stroke_uv_update(struct bGPDstroke *gps); void BKE_gpencil_transform(struct bGPdata *gpd, const float mat[4][4]); @@ -91,19 +97,26 @@ void BKE_gpencil_point_coords_apply_with_mat4(struct bGPdata *gpd, const GPencilPointCoordinates *elem_data, const float mat[4][4]); -bool BKE_gpencil_stroke_sample(struct bGPDstroke *gps, const float dist, const bool select); +bool BKE_gpencil_stroke_sample(struct bGPdata *gpd, + struct bGPDstroke *gps, + const float dist, + const bool select); bool BKE_gpencil_stroke_smooth(struct bGPDstroke *gps, int i, float inf); bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence); bool BKE_gpencil_stroke_close(struct bGPDstroke *gps); -void BKE_gpencil_dissolve_points(struct bGPDframe *gpf, struct bGPDstroke *gps, const short tag); +void BKE_gpencil_dissolve_points(struct bGPdata *gpd, + struct bGPDframe *gpf, + struct bGPDstroke *gps, + const short tag); bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps, const float dist, const float tip_length); bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps, const int index_from, const int index_to); -bool BKE_gpencil_stroke_split(struct bGPDframe *gpf, +bool BKE_gpencil_stroke_split(struct bGPdata *gpd, + struct bGPDframe *gpf, struct bGPDstroke *gps, const int before_index, struct bGPDstroke **remaining_gps); diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 4cf8e365cf6..9d3582b2df2 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -155,6 +155,11 @@ static void greasepencil_blend_write(BlendWriter *writer, ID *id, const void *id BLO_write_struct_array(writer, bGPDspoint, gps->totpoints, gps->points); BLO_write_struct_array(writer, bGPDtriangle, gps->tot_triangles, gps->triangles); BKE_defvert_blend_write(writer, gps->totpoints, gps->dvert); + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + BLO_write_struct(writer, bGPDcurve, gpc); + BLO_write_struct_array(writer, bGPDcurve_point, gpc->tot_curve_points, gpc->curve_points); + } } } } @@ -222,6 +227,13 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd) /* Relink geometry*/ BLO_read_data_address(reader, &gps->triangles); + /* relink stroke edit curve. */ + BLO_read_data_address(reader, &gps->editcurve); + if (gps->editcurve != NULL) { + /* relink curve point array */ + BLO_read_data_address(reader, &gps->editcurve->curve_points); + } + /* relink weight data */ if (gps->dvert) { BLO_read_data_address(reader, &gps->dvert); @@ -341,6 +353,20 @@ void BKE_gpencil_free_stroke_weights(bGPDstroke *gps) } } +void BKE_gpencil_free_stroke_editcurve(bGPDstroke *gps) +{ + if (gps == NULL) { + return; + } + bGPDcurve *editcurve = gps->editcurve; + if (editcurve == NULL) { + return; + } + MEM_freeN(editcurve->curve_points); + MEM_freeN(editcurve); + gps->editcurve = NULL; +} + /* free stroke, doesn't unlink from any listbase */ void BKE_gpencil_free_stroke(bGPDstroke *gps) { @@ -358,6 +384,9 @@ void BKE_gpencil_free_stroke(bGPDstroke *gps) if (gps->triangles) { MEM_freeN(gps->triangles); } + if (gps->editcurve != NULL) { + BKE_gpencil_free_stroke_editcurve(gps); + } MEM_freeN(gps); } @@ -688,6 +717,13 @@ bGPdata *BKE_gpencil_data_addnew(Main *bmain, const char name[]) gpd->pixfactor = GP_DEFAULT_PIX_FACTOR; + gpd->curve_edit_resolution = GP_DEFAULT_CURVE_RESOLUTION; + gpd->curve_edit_threshold = GP_DEFAULT_CURVE_ERROR; + gpd->curve_edit_corner_angle = GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE; + + /* use adaptive curve resolution by default */ + gpd->flag |= GP_DATA_CURVE_ADAPTIVE_RESOLUTION; + gpd->zdepth_offset = 0.150f; /* grid settings */ @@ -776,6 +812,8 @@ bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness) gps->mat_nr = mat_idx; + gps->editcurve = NULL; + return gps; } @@ -827,6 +865,16 @@ bGPDstroke *BKE_gpencil_stroke_add_existing_style( return gps; } +bGPDcurve *BKE_gpencil_stroke_editcurve_new(const int tot_curve_points) +{ + bGPDcurve *new_gp_curve = (bGPDcurve *)MEM_callocN(sizeof(bGPDcurve), __func__); + new_gp_curve->tot_curve_points = tot_curve_points; + new_gp_curve->curve_points = (bGPDcurve_point *)MEM_callocN( + sizeof(bGPDcurve_point) * tot_curve_points, __func__); + + return new_gp_curve; +} + /* ************************************************** */ /* Data Duplication */ @@ -845,13 +893,28 @@ void BKE_gpencil_stroke_weights_duplicate(bGPDstroke *gps_src, bGPDstroke *gps_d BKE_defvert_array_copy(gps_dst->dvert, gps_src->dvert, gps_src->totpoints); } +/* Make a copy of a given gpencil stroke editcurve */ +bGPDcurve *BKE_gpencil_stroke_curve_duplicate(bGPDcurve *gpc_src) +{ + bGPDcurve *gpc_dst = MEM_dupallocN(gpc_src); + + if (gpc_src->curve_points != NULL) { + gpc_dst->curve_points = MEM_dupallocN(gpc_src->curve_points); + } + + return gpc_dst; +} + /** * Make a copy of a given grease-pencil stroke. * \param gps_src: Source grease pencil strokes. * \param dup_points: Duplicate points data. + * \param dup_curve: Duplicate curve data. * \return Pointer to new stroke. */ -bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, const bool dup_points) +bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, + const bool dup_points, + const bool dup_curve) { bGPDstroke *gps_dst = NULL; @@ -871,6 +934,10 @@ bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, const bool dup_poi } } + if (dup_curve && gps_src->editcurve != NULL) { + gps_dst->editcurve = BKE_gpencil_stroke_curve_duplicate(gps_src->editcurve); + } + /* return new stroke */ return gps_dst; } @@ -898,7 +965,7 @@ bGPDframe *BKE_gpencil_frame_duplicate(const bGPDframe *gpf_src) BLI_listbase_clear(&gpf_dst->strokes); LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { /* make copy of source stroke */ - gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); BLI_addtail(&gpf_dst->strokes, gps_dst); } @@ -923,7 +990,7 @@ void BKE_gpencil_frame_copy_strokes(bGPDframe *gpf_src, struct bGPDframe *gpf_ds BLI_listbase_clear(&gpf_dst->strokes); LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { /* make copy of source stroke */ - gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); BLI_addtail(&gpf_dst->strokes, gps_dst); } } @@ -1037,6 +1104,39 @@ void BKE_gpencil_stroke_sync_selection(bGPDstroke *gps) } } +void BKE_gpencil_curve_sync_selection(bGPDstroke *gps) +{ + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL) { + return; + } + + gps->flag &= ~GP_STROKE_SELECT; + gpc->flag &= ~GP_CURVE_SELECT; + + bool is_selected = false; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (BEZT_ISSEL_ANY(bezt)) { + gpc_pt->flag |= GP_SPOINT_SELECT; + } + else { + gpc_pt->flag &= ~GP_SPOINT_SELECT; + } + + if (gpc_pt->flag & GP_SPOINT_SELECT) { + is_selected = true; + } + } + + if (is_selected) { + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + } +} + /* ************************************************** */ /* GP Frame API */ @@ -2304,12 +2404,14 @@ void BKE_gpencil_palette_ensure(Main *bmain, Scene *scene) /** * Create grease pencil strokes from image * \param sima: Image + * \param gpd: Grease pencil data-block * \param gpf: Grease pencil frame * \param size: Size * \param mask: Mask * \return True if done */ -bool BKE_gpencil_from_image(SpaceImage *sima, bGPDframe *gpf, const float size, const bool mask) +bool BKE_gpencil_from_image( + SpaceImage *sima, bGPdata *gpd, bGPDframe *gpf, const float size, const bool mask) { Image *image = sima->image; bool done = false; @@ -2357,7 +2459,7 @@ bool BKE_gpencil_from_image(SpaceImage *sima, bGPDframe *gpf, const float size, pt->flag |= GP_SPOINT_SELECT; } } - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 6b3f752120a..59c251197eb 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -37,8 +37,10 @@ #include "BLT_translation.h" #include "DNA_gpencil_types.h" +#include "DNA_meshdata_types.h" #include "BKE_collection.h" +#include "BKE_context.h" #include "BKE_curve.h" #include "BKE_gpencil.h" #include "BKE_gpencil_curve.h" @@ -47,8 +49,16 @@ #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], @@ -295,6 +305,7 @@ static void gpencil_convert_spline(Main *bmain, bGPDframe *gpf, Nurb *nu) { + bGPdata *gpd = (bGPdata *)ob_gp->data; bool cyclic = true; /* Create Stroke. */ @@ -445,11 +456,22 @@ static void gpencil_convert_spline(Main *bmain, } if (sample > 0.0f) { - BKE_gpencil_stroke_sample(gps, sample, false); + BKE_gpencil_stroke_sample(gpd, gps, sample, false); } /* Recalc fill geometry. */ - BKE_gpencil_stroke_geometry_update(gps); + 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; } /** @@ -536,3 +558,851 @@ void BKE_gpencil_convert_curve(Main *bmain, } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Editcurve 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; +} + +/** + * Creates a bGPDcurve by doing a cubic curve fitting on the grease pencil stroke points. + */ +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; +} + +/** + * Updates the editcurve for a stroke. Frees the old curve if one exists and generates a new one. + */ +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; +} + +/** + * Sync the selection from stroke to editcurve + */ +void BKE_gpencil_editcurve_stroke_sync_selection(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); + } +} + +/** + * Sync the selection from editcurve to stroke + */ +void BKE_gpencil_stroke_editcurve_sync_selection(bGPDstroke *gps, bGPDcurve *gpc) +{ + if (gpc->flag & GP_CURVE_SELECT) { + gps->flag |= GP_STROKE_SELECT; + + 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; + 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; +} + +/** + * Recalculate stroke points with the editcurve of the stroke. + */ +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; + + 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; + + /* free temp data */ + MEM_freeN(points); +} + +/** + * Recalculate the handles of the edit curve of a grease pencil stroke + */ +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 neighbour 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(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(gps, gpc); + } + } + } + } + } +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index ff7dde27db8..d2cfb36cb15 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -45,8 +45,11 @@ #include "DNA_meshdata_types.h" #include "DNA_scene_types.h" +#include "BLT_translation.h" + #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" @@ -415,10 +418,11 @@ static void stroke_interpolate_deform_weights( /** * Resample a stroke + * \param gpd: Grease pencil data-block * \param gps: Stroke to sample * \param dist: Distance of one segment */ -bool BKE_gpencil_stroke_sample(bGPDstroke *gps, const float dist, const bool select) +bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, const bool select) { bGPDspoint *pt = gps->points; bGPDspoint *pt1 = NULL; @@ -515,7 +519,7 @@ bool BKE_gpencil_stroke_sample(bGPDstroke *gps, const float dist, const bool sel gps->totpoints = i; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); return true; } @@ -628,13 +632,15 @@ bool BKE_gpencil_stroke_trim_points(bGPDstroke *gps, const int index_from, const /** * Split stroke. + * \param gpd: Grease pencil data-block * \param gpf: Grease pencil frame * \param gps: Grease pencil original stroke * \param before_index: Position of the point to split * \param remaining_gps: Secondary stroke after split. * \return True if the split was done */ -bool BKE_gpencil_stroke_split(bGPDframe *gpf, +bool BKE_gpencil_stroke_split(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps, const int before_index, bGPDstroke **remaining_gps) @@ -684,7 +690,7 @@ bool BKE_gpencil_stroke_split(bGPDframe *gpf, * Keep the end point. */ BKE_gpencil_stroke_trim_points(gps, 0, old_count); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); return true; } @@ -1273,14 +1279,31 @@ void BKE_gpencil_stroke_uv_update(bGPDstroke *gps) /** * Recalc all internal geometry data for the stroke + * \param gpd: Grease pencil data-block * \param gps: Grease pencil stroke */ -void BKE_gpencil_stroke_geometry_update(bGPDstroke *gps) +void BKE_gpencil_stroke_geometry_update(bGPdata *gpd, bGPDstroke *gps) { if (gps == NULL) { return; } + if (gps->editcurve != NULL) { + if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + /* curve geometry was updated: stroke needs recalculation */ + if (gps->flag & GP_STROKE_NEEDS_CURVE_UPDATE) { + bool is_adaptive = gpd->flag & GP_DATA_CURVE_ADAPTIVE_RESOLUTION; + BKE_gpencil_stroke_update_geometry_from_editcurve( + gps, gpd->curve_edit_resolution, is_adaptive); + gps->flag &= ~GP_STROKE_NEEDS_CURVE_UPDATE; + } + } + else { + /* stroke geometry was updated: editcurve needs recalculation */ + gps->editcurve->flag |= GP_CURVE_NEEDS_STROKE_UPDATE; + } + } + if (gps->totpoints > 2) { BKE_gpencil_stroke_fill_triangulate(gps); } @@ -1326,7 +1349,7 @@ float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d) * Trim stroke to the first intersection or loop. * \param gps: Stroke data */ -bool BKE_gpencil_stroke_trim(bGPDstroke *gps) +bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps) { if (gps->totpoints < 4) { return false; @@ -1413,7 +1436,7 @@ bool BKE_gpencil_stroke_trim(bGPDstroke *gps) MEM_SAFE_FREE(old_dvert); } - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); return intersect; } @@ -1509,11 +1532,12 @@ bool BKE_gpencil_stroke_close(bGPDstroke *gps) /** * Dissolve points in stroke. + * \param gpd: Grease pencil data-block * \param gpf: Grease pencil frame * \param gps: Grease pencil stroke * \param tag: Type of tag for point */ -void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short tag) +void BKE_gpencil_dissolve_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, const short tag) { bGPDspoint *pt; MDeformVert *dvert = NULL; @@ -1589,7 +1613,7 @@ void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short ta gps->totpoints = tot; /* triangles cache needs to be recalculated */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } @@ -1635,10 +1659,11 @@ void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3]) * * Ramer - Douglas - Peucker algorithm * by http ://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm + * \param gpd: Grease pencil data-block * \param gps: Grease pencil stroke * \param epsilon: Epsilon value to define precision of the algorithm */ -void BKE_gpencil_stroke_simplify_adaptive(bGPDstroke *gps, float epsilon) +void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float epsilon) { bGPDspoint *old_points = MEM_dupallocN(gps->points); int totpoints = gps->totpoints; @@ -1735,7 +1760,7 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPDstroke *gps, float epsilon) gps->totpoints = j; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); MEM_SAFE_FREE(old_points); MEM_SAFE_FREE(old_dvert); @@ -1744,9 +1769,10 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPDstroke *gps, float epsilon) /** * Simplify alternate vertex of stroke except extremes. + * \param gpd: Grease pencil data-block * \param gps: Grease pencil stroke */ -void BKE_gpencil_stroke_simplify_fixed(bGPDstroke *gps) +void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps) { if (gps->totpoints < 5) { return; @@ -1800,19 +1826,20 @@ void BKE_gpencil_stroke_simplify_fixed(bGPDstroke *gps) gps->totpoints = j; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); MEM_SAFE_FREE(old_points); MEM_SAFE_FREE(old_dvert); } /** - * Subdivide grease pencil stroke. - * \param gps: Grease pencil stroke + * Subdivide a stroke + * \param gpd: Grease pencil data-block + * \param gps: Stroke * \param level: Level of subdivision * \param type: Type of subdivision */ -void BKE_gpencil_stroke_subdivide(bGPDstroke *gps, int level, int type) +void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int type) { bGPDspoint *temp_points; MDeformVert *temp_dverts = NULL; @@ -1921,7 +1948,7 @@ void BKE_gpencil_stroke_subdivide(bGPDstroke *gps, int level, int type) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } /* Merge by distance ------------------------------------- */ @@ -1930,12 +1957,14 @@ void BKE_gpencil_stroke_subdivide(bGPDstroke *gps, int level, int type) * Reduce a series of points when the distance is below a threshold. * Special case for first and last points (both are keeped) for other points, * the merge point always is at first point. + * \param gpd: Grease pencil data-block * \param gpf: Grease Pencil frame * \param gps: Grease Pencil stroke * \param threshold: Distance between points * \param use_unselected: Set to true to analyze all stroke and not only selected points */ -void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf, +void BKE_gpencil_stroke_merge_distance(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps, const float threshold, const bool use_unselected) @@ -2000,11 +2029,11 @@ void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf, /* Dissolve tagged points */ if (tagged) { - BKE_gpencil_dissolve_points(gpf, gps, GP_SPOINT_TAG); + BKE_gpencil_dissolve_points(gpd, gpf, gps, GP_SPOINT_TAG); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } typedef struct GpEdge { @@ -2093,6 +2122,7 @@ static int gpencil_walk_edge(GHash *v_table, } static void gpencil_generate_edgeloops(Object *ob, + bGPdata *gpd, bGPDframe *gpf_stroke, int stroke_mat_index, const float angle, @@ -2218,7 +2248,7 @@ static void gpencil_generate_edgeloops(Object *ob, pt->strength = 1.0f; } - BKE_gpencil_stroke_geometry_update(gps_stroke); + BKE_gpencil_stroke_geometry_update(gpd, gps_stroke); } /* Free memory. */ @@ -2397,10 +2427,10 @@ bool BKE_gpencil_convert_mesh(Main *bmain, } /* If has only 3 points subdivide. */ if (mp->totloop == 3) { - BKE_gpencil_stroke_subdivide(gps_fill, 1, GP_SUBDIV_SIMPLE); + BKE_gpencil_stroke_subdivide(gpd, gps_fill, 1, GP_SUBDIV_SIMPLE); } - BKE_gpencil_stroke_geometry_update(gps_fill); + BKE_gpencil_stroke_geometry_update(gpd, gps_fill); } } } @@ -2417,7 +2447,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); gpencil_generate_edgeloops( - ob_eval, gpf_stroke, stroke_mat_index, angle, thickness, offset, matrix, use_seams); + ob_eval, gpd, gpf_stroke, stroke_mat_index, angle, thickness, offset, matrix, use_seams); /* Tag for recalculation */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); @@ -2457,7 +2487,7 @@ void BKE_gpencil_transform(bGPdata *gpd, const float mat[4][4]) } /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } @@ -2549,7 +2579,7 @@ void BKE_gpencil_point_coords_apply(bGPdata *gpd, const GPencilPointCoordinates } /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } @@ -2586,7 +2616,7 @@ void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd, } /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index be06638ab64..09f9e9e891c 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -698,7 +698,9 @@ void BKE_gpencil_prepare_eval_data(Depsgraph *depsgraph, Scene *scene, Object *o } const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_eval); - const bool do_modifiers = (bool)((!is_multiedit) && (ob->greasepencil_modifiers.first != NULL) && + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd_eval); + const bool do_modifiers = (bool)((!is_multiedit) && (!is_curve_edit) && + (ob->greasepencil_modifiers.first != NULL) && (!GPENCIL_SIMPLIFY_MODIF(scene))); if ((!do_modifiers) && (!do_parent)) { return; @@ -734,8 +736,10 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) bGPdata *gpd = (bGPdata *)ob->data; const bool is_edit = GPENCIL_ANY_EDIT_MODE(gpd); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_render = (bool)(DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); - const bool do_modifiers = (bool)((!is_multiedit) && (ob->greasepencil_modifiers.first != NULL) && + const bool do_modifiers = (bool)((!is_multiedit) && (!is_curve_edit) && + (ob->greasepencil_modifiers.first != NULL) && (!GPENCIL_SIMPLIFY_MODIF(scene))); if (!do_modifiers) { return; diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 0b7830c922a..c2bedc54690 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -4871,7 +4871,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) gps->fill_opacity_fac = 1.0f; /* Calc geometry data because in old versions this data was not saved. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); srgb_to_linearrgb_v4(gps->vert_color_fill, gps->vert_color_fill); int i; diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index 1574fe9b548..ec39113c4d0 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -1067,6 +1067,20 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) part->phystype = PART_PHYS_NO; } } + /* Init grease pencil default curve resolution. */ + if (!DNA_struct_elem_find(fd->filesdna, "bGPdata", "int", "curve_edit_resolution")) { + LISTBASE_FOREACH (bGPdata *, gpd, &bmain->gpencils) { + gpd->curve_edit_resolution = GP_DEFAULT_CURVE_RESOLUTION; + gpd->flag |= GP_DATA_CURVE_ADAPTIVE_RESOLUTION; + } + } + /* Init grease pencil curve editing error threshold. */ + if (!DNA_struct_elem_find(fd->filesdna, "bGPdata", "float", "curve_edit_threshold")) { + LISTBASE_FOREACH (bGPdata *, gpd, &bmain->gpencils) { + gpd->curve_edit_threshold = GP_DEFAULT_CURVE_ERROR; + gpd->curve_edit_corner_angle = GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE; + } + } } if (!MAIN_VERSION_ATLEAST(bmain, 291, 9)) { diff --git a/source/blender/draw/engines/overlay/overlay_gpencil.c b/source/blender/draw/engines/overlay/overlay_gpencil.c index ccc914e0422..7f9290a6c3a 100644 --- a/source/blender/draw/engines/overlay/overlay_gpencil.c +++ b/source/blender/draw/engines/overlay/overlay_gpencil.c @@ -49,6 +49,10 @@ void OVERLAY_edit_gpencil_cache_init(OVERLAY_Data *vedata) pd->edit_gpencil_wires_grp = NULL; psl->edit_gpencil_ps = NULL; + pd->edit_gpencil_curve_handle_grp = NULL; + pd->edit_gpencil_curve_points_grp = NULL; + psl->edit_gpencil_curve_ps = NULL; + const DRWContextState *draw_ctx = DRW_context_state_get(); View3D *v3d = draw_ctx->v3d; Object *ob = draw_ctx->obact; @@ -105,7 +109,8 @@ void OVERLAY_edit_gpencil_cache_init(OVERLAY_Data *vedata) (GPENCIL_EDIT_MODE(gpd) && (ts->gpencil_selectmode_edit != GP_SELECTMODE_STROKE)); - if ((!GPENCIL_VERTEX_MODE(gpd) && !GPENCIL_PAINT_MODE(gpd)) || use_vertex_mask) { + if ((!GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) && + ((!GPENCIL_VERTEX_MODE(gpd) && !GPENCIL_PAINT_MODE(gpd)) || use_vertex_mask)) { DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_BLEND_ALPHA; DRW_PASS_CREATE(psl->edit_gpencil_ps, state | pd->clipping_state); @@ -132,6 +137,37 @@ void OVERLAY_edit_gpencil_cache_init(OVERLAY_Data *vedata) } } + /* Handles and curve point for Curve Edit submode. */ + if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + DRWState state = DRW_STATE_WRITE_COLOR; + DRW_PASS_CREATE(psl->edit_gpencil_curve_ps, state | pd->clipping_state); + + /* Edit lines. */ + if (show_lines) { + sh = OVERLAY_shader_edit_gpencil_wire(); + pd->edit_gpencil_wires_grp = grp = DRW_shgroup_create(sh, psl->edit_gpencil_curve_ps); + DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo); + DRW_shgroup_uniform_bool_copy(grp, "doMultiframe", show_multi_edit_lines); + DRW_shgroup_uniform_bool_copy(grp, "doWeightColor", is_weight_paint); + DRW_shgroup_uniform_bool_copy(grp, "hideSelect", hide_select); + DRW_shgroup_uniform_float_copy(grp, "gpEditOpacity", v3d->vertex_opacity); + DRW_shgroup_uniform_texture(grp, "weightTex", G_draw.weight_ramp); + } + + sh = OVERLAY_shader_edit_curve_handle(); + pd->edit_gpencil_curve_handle_grp = grp = DRW_shgroup_create(sh, psl->edit_gpencil_curve_ps); + DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo); + DRW_shgroup_uniform_bool_copy(grp, "showCurveHandles", pd->edit_curve.show_handles); + DRW_shgroup_uniform_int_copy(grp, "curveHandleDisplay", pd->edit_curve.handle_display); + DRW_shgroup_state_enable(grp, DRW_STATE_BLEND_ALPHA); + + sh = OVERLAY_shader_edit_curve_point(); + pd->edit_gpencil_curve_points_grp = grp = DRW_shgroup_create(sh, psl->edit_gpencil_curve_ps); + DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo); + DRW_shgroup_uniform_bool_copy(grp, "showCurveHandles", pd->edit_curve.show_handles); + DRW_shgroup_uniform_int_copy(grp, "curveHandleDisplay", pd->edit_curve.handle_display); + } + /* control points for primitives and speed guide */ const bool is_cppoint = (gpd->runtime.tot_cp_points > 0); const bool is_speed_guide = (ts->gp_sculpt.guide.use_guide && @@ -182,6 +218,7 @@ void OVERLAY_edit_gpencil_cache_init(OVERLAY_Data *vedata) void OVERLAY_gpencil_cache_init(OVERLAY_Data *vedata) { OVERLAY_PassList *psl = vedata->psl; + OVERLAY_PrivateData *pd = vedata->stl->pd; struct GPUShader *sh; DRWShadingGroup *grp; @@ -196,6 +233,9 @@ void OVERLAY_gpencil_cache_init(OVERLAY_Data *vedata) ToolSettings *ts = scene->toolsettings; const View3DCursor *cursor = &scene->cursor; + pd->edit_curve.show_handles = v3d->overlay.handle_display != CURVE_HANDLE_NONE; + pd->edit_curve.handle_display = v3d->overlay.handle_display; + if (gpd == NULL || ob->type != OB_GPENCIL) { return; } @@ -303,6 +343,20 @@ static void OVERLAY_edit_gpencil_cache_populate(OVERLAY_Data *vedata, Object *ob struct GPUBatch *geom = DRW_cache_gpencil_edit_points_get(ob, pd->cfra); DRW_shgroup_call_no_cull(grp, geom, ob); } + + if (pd->edit_gpencil_curve_handle_grp) { + struct GPUBatch *geom = DRW_cache_gpencil_edit_curve_handles_get(ob, pd->cfra); + if (geom) { + DRW_shgroup_call_no_cull(pd->edit_gpencil_curve_handle_grp, geom, ob); + } + } + + if (pd->edit_gpencil_curve_points_grp) { + struct GPUBatch *geom = DRW_cache_gpencil_edit_curve_points_get(ob, pd->cfra); + if (geom) { + DRW_shgroup_call_no_cull(pd->edit_gpencil_curve_points_grp, geom, ob); + } + } } static void overlay_gpencil_draw_stroke_color_name(bGPDlayer *UNUSED(gpl), @@ -407,4 +461,9 @@ void OVERLAY_edit_gpencil_draw(OVERLAY_Data *vedata) if (psl->edit_gpencil_ps) { DRW_draw_pass(psl->edit_gpencil_ps); } + + /* Curve edit handles. */ + if (psl->edit_gpencil_curve_ps) { + DRW_draw_pass(psl->edit_gpencil_curve_ps); + } } diff --git a/source/blender/draw/engines/overlay/overlay_private.h b/source/blender/draw/engines/overlay/overlay_private.h index c259d9351f3..b9d591a5642 100644 --- a/source/blender/draw/engines/overlay/overlay_private.h +++ b/source/blender/draw/engines/overlay/overlay_private.h @@ -77,6 +77,7 @@ typedef struct OVERLAY_PassList { DRWPass *edit_curve_handle_ps; DRWPass *edit_gpencil_ps; DRWPass *edit_gpencil_gizmos_ps; + DRWPass *edit_gpencil_curve_ps; DRWPass *edit_lattice_ps; DRWPass *edit_mesh_depth_ps[2]; DRWPass *edit_mesh_verts_ps[2]; @@ -252,6 +253,8 @@ typedef struct OVERLAY_PrivateData { DRWShadingGroup *edit_lattice_wires_grp; DRWShadingGroup *edit_gpencil_points_grp; DRWShadingGroup *edit_gpencil_wires_grp; + DRWShadingGroup *edit_gpencil_curve_handle_grp; + DRWShadingGroup *edit_gpencil_curve_points_grp; DRWShadingGroup *edit_mesh_depth_grp[2]; DRWShadingGroup *edit_mesh_faces_grp[2]; DRWShadingGroup *edit_mesh_faces_cage_grp[2]; diff --git a/source/blender/draw/engines/overlay/shaders/edit_curve_handle_geom.glsl b/source/blender/draw/engines/overlay/shaders/edit_curve_handle_geom.glsl index 9311542a79e..442f69aec7e 100644 --- a/source/blender/draw/engines/overlay/shaders/edit_curve_handle_geom.glsl +++ b/source/blender/draw/engines/overlay/shaders/edit_curve_handle_geom.glsl @@ -53,6 +53,9 @@ void main() bool edge_selected = (((vertFlag[1] | vertFlag[0]) & VERT_SELECTED) != 0); bool handle_selected = (showCurveHandles && (((vertFlag[1] | vertFlag[0]) & VERT_SELECTED_BEZT_HANDLE) != 0)); + /* It reuses freestyle flag because the flag is 8 bits and all are already used and this + * flag is not used in this context. */ + bool is_gpencil = ((vertFlag[1] & EDGE_FREESTYLE) != 0); /* If handle type is only selected and the edge is not selected, don't show. */ if ((curveHandleDisplay != CURVE_HANDLE_ALL) && (!handle_selected)) { @@ -61,6 +64,9 @@ void main() if ((!is_u_segment) && (color_id <= 4)) { return; } + if (is_gpencil) { + return; + } } vec4 inner_color; diff --git a/source/blender/draw/engines/overlay/shaders/edit_curve_point_vert.glsl b/source/blender/draw/engines/overlay/shaders/edit_curve_point_vert.glsl index a811fcca0d4..6b4edbfe578 100644 --- a/source/blender/draw/engines/overlay/shaders/edit_curve_point_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/edit_curve_point_vert.glsl @@ -17,16 +17,17 @@ void main() { GPU_INTEL_VERTEX_SHADER_WORKAROUND + /* Reuse the FREESTYLE flag to determine is GPencil. */ if ((data & VERT_SELECTED) != 0) { if ((data & VERT_ACTIVE) != 0) { finalColor = colorEditMeshActive; } else { - finalColor = colorVertexSelect; + finalColor = ((data & EDGE_FREESTYLE) == 0) ? colorVertexSelect : colorGpencilVertexSelect; } } else { - finalColor = colorVertex; + finalColor = ((data & EDGE_FREESTYLE) == 0) ? colorVertex : colorGpencilVertex; } vec3 world_pos = point_object_to_world(pos); diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index afbf9903dbc..9fd7ffd4692 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -253,6 +253,8 @@ struct GPUBatch *DRW_cache_gpencil_strokes_get(struct Object *ob, int cfra); struct GPUBatch *DRW_cache_gpencil_fills_get(struct Object *ob, int cfra); struct GPUBatch *DRW_cache_gpencil_edit_lines_get(struct Object *ob, int cfra); struct GPUBatch *DRW_cache_gpencil_edit_points_get(struct Object *ob, int cfra); +struct GPUBatch *DRW_cache_gpencil_edit_curve_handles_get(struct Object *ob, int cfra); +struct GPUBatch *DRW_cache_gpencil_edit_curve_points_get(struct Object *ob, int cfra); struct GPUBatch *DRW_cache_gpencil_sbuffer_stroke_get(struct Object *ob); struct GPUBatch *DRW_cache_gpencil_sbuffer_fill_get(struct Object *ob); diff --git a/source/blender/draw/intern/draw_cache_impl_gpencil.c b/source/blender/draw/intern/draw_cache_impl_gpencil.c index 220c7041c7f..0a80f7df877 100644 --- a/source/blender/draw/intern/draw_cache_impl_gpencil.c +++ b/source/blender/draw/intern/draw_cache_impl_gpencil.c @@ -21,6 +21,7 @@ * \ingroup draw */ +#include "DNA_curve_types.h" #include "DNA_gpencil_types.h" #include "DNA_meshdata_types.h" #include "DNA_screen_types.h" @@ -43,6 +44,9 @@ #include "draw_cache.h" #include "draw_cache_impl.h" +#define BEZIER_HANDLE 1 << 3 +#define COLOR_SHIFT 5 + /* ---------------------------------------------------------------------- */ typedef struct GpencilBatchCache { /** Instancing Data */ @@ -59,6 +63,10 @@ typedef struct GpencilBatchCache { GPUVertBuf *edit_vbo; GPUBatch *edit_lines_batch; GPUBatch *edit_points_batch; + /** Edit Curve Mode */ + GPUVertBuf *edit_curve_vbo; + GPUBatch *edit_curve_handles_batch; + GPUBatch *edit_curve_points_batch; /** Cache is dirty */ bool is_dirty; @@ -123,6 +131,10 @@ static void gpencil_batch_cache_clear(GpencilBatchCache *cache) GPU_BATCH_DISCARD_SAFE(cache->edit_points_batch); GPU_VERTBUF_DISCARD_SAFE(cache->edit_vbo); + GPU_BATCH_DISCARD_SAFE(cache->edit_curve_handles_batch); + GPU_BATCH_DISCARD_SAFE(cache->edit_curve_points_batch); + GPU_VERTBUF_DISCARD_SAFE(cache->edit_curve_vbo); + cache->is_dirty = true; } @@ -196,6 +208,23 @@ static GPUVertFormat *gpencil_edit_stroke_format(void) return &format; } +/* MUST match the format below. */ +typedef struct gpEditCurveVert { + float pos[3]; + int data; +} gpEditCurveVert; + +static GPUVertFormat *gpencil_edit_curve_format(void) +{ + static GPUVertFormat format = {0}; + if (format.attr_len == 0) { + /* initialize vertex formats */ + GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + GPU_vertformat_attr_add(&format, "data", GPU_COMP_U8, 1, GPU_FETCH_INT); + } + return &format; +} + /* MUST match the format below. */ typedef struct gpColorVert { float vcol[4]; /* Vertex color */ @@ -228,6 +257,7 @@ typedef struct gpIterData { GPUIndexBufBuilder ibo; int vert_len; int tri_len; + int curve_len; } gpIterData; static GPUVertBuf *gpencil_dummy_buffer_get(void) @@ -383,6 +413,7 @@ static void gpencil_batches_ensure(Object *ob, GpencilBatchCache *cache, int cfr .ibo = {0}, .vert_len = 1, /* Start at 1 for the gl_InstanceID trick to work (see vert shader). */ .tri_len = 0, + .curve_len = 0, }; BKE_gpencil_visible_stroke_iter( NULL, ob, NULL, gpencil_object_verts_count_cb, &iter, do_onion, cfra); @@ -653,6 +684,11 @@ typedef struct gpEditIterData { int vgindex; } gpEditIterData; +typedef struct gpEditCurveIterData { + gpEditCurveVert *verts; + int vgindex; +} gpEditCurveIterData; + static uint32_t gpencil_point_edit_flag(const bool layer_lock, const bGPDspoint *pt, int v, @@ -698,6 +734,92 @@ static void gpencil_edit_stroke_iter_cb(bGPDlayer *gpl, vert_ptr->weight = gpencil_point_edit_weight(dvert, 0, iter->vgindex); } +static void gpencil_edit_curve_stroke_count_cb(bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps, + void *thunk) +{ + if (gpl->flag & GP_LAYER_LOCKED) { + return; + } + + gpIterData *iter = (gpIterData *)thunk; + + if (gps->editcurve == NULL) { + return; + } + + /* Store first index offset */ + gps->runtime.curve_start = iter->curve_len; + iter->curve_len += gps->editcurve->tot_curve_points * 4; +} + +static char gpencil_beztriple_vflag_get(char flag, + char col_id, + bool handle_point, + const bool handle_selected) +{ + char vflag = 0; + SET_FLAG_FROM_TEST(vflag, (flag & SELECT), VFLAG_VERT_SELECTED); + SET_FLAG_FROM_TEST(vflag, handle_point, BEZIER_HANDLE); + SET_FLAG_FROM_TEST(vflag, handle_selected, VFLAG_VERT_SELECTED_BEZT_HANDLE); + /* Reuse flag of Freestyle to indicate is GPencil data. */ + vflag |= VFLAG_EDGE_FREESTYLE; + + /* Handle color id. */ + vflag |= col_id << COLOR_SHIFT; + return vflag; +} + +static void gpencil_edit_curve_stroke_iter_cb(bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps, + void *thunk) +{ + if (gpl->flag & GP_LAYER_LOCKED) { + return; + } + + if (gps->editcurve == NULL) { + return; + } + bGPDcurve *editcurve = gps->editcurve; + gpEditCurveIterData *iter = (gpEditCurveIterData *)thunk; + const int v = gps->runtime.curve_start; + gpEditCurveVert *vert_ptr = iter->verts + v; + /* Hide points when the curve is unselected. Passing the control point + * as handle produces the point shader skip it if you are not in ALL mode. */ + const bool hide = !(editcurve->flag & GP_CURVE_SELECT); + + for (int i = 0; i < editcurve->tot_curve_points; i++) { + BezTriple *bezt = &editcurve->curve_points[i].bezt; + const bool handle_selected = BEZT_ISSEL_ANY(bezt); + const char vflag[3] = { + gpencil_beztriple_vflag_get(bezt->f1, bezt->h1, true, handle_selected), + gpencil_beztriple_vflag_get(bezt->f2, bezt->h1, hide, handle_selected), + gpencil_beztriple_vflag_get(bezt->f3, bezt->h2, true, handle_selected), + }; + + /* First segment. */ + copy_v3_v3(vert_ptr->pos, bezt->vec[0]); + vert_ptr->data = vflag[0]; + vert_ptr++; + + copy_v3_v3(vert_ptr->pos, bezt->vec[1]); + vert_ptr->data = vflag[1]; + vert_ptr++; + + /* Second segment. */ + copy_v3_v3(vert_ptr->pos, bezt->vec[1]); + vert_ptr->data = vflag[1]; + vert_ptr++; + + copy_v3_v3(vert_ptr->pos, bezt->vec[2]); + vert_ptr->data = vflag[2]; + vert_ptr++; + } +} + static void gpencil_edit_batches_ensure(Object *ob, GpencilBatchCache *cache, int cfra) { bGPdata *gpd = (bGPdata *)ob->data; @@ -737,6 +859,46 @@ static void gpencil_edit_batches_ensure(Object *ob, GpencilBatchCache *cache, in cache->edit_lines_batch = GPU_batch_create(GPU_PRIM_LINE_STRIP, cache->vbo, NULL); GPU_batch_vertbuf_add(cache->edit_lines_batch, cache->edit_vbo); + } + + /* Curve Handles and Points for Editing. */ + if (cache->edit_curve_vbo == NULL) { + gpIterData iterdata = { + .gpd = gpd, + .verts = NULL, + .ibo = {0}, + .vert_len = 0, + .tri_len = 0, + .curve_len = 0, + }; + + /* Create VBO. */ + GPUVertFormat *format = gpencil_edit_curve_format(); + cache->edit_curve_vbo = GPU_vertbuf_create_with_format(format); + + /* Count data. */ + BKE_gpencil_visible_stroke_iter( + NULL, ob, NULL, gpencil_edit_curve_stroke_count_cb, &iterdata, false, cfra); + + gpEditCurveIterData iter; + int vert_len = iterdata.curve_len; + if (vert_len > 0) { + + GPU_vertbuf_data_alloc(cache->edit_curve_vbo, vert_len); + iter.verts = (gpEditCurveVert *)GPU_vertbuf_get_data(cache->edit_curve_vbo); + + /* Fill buffers with data. */ + BKE_gpencil_visible_stroke_iter( + NULL, ob, NULL, gpencil_edit_curve_stroke_iter_cb, &iter, false, cfra); + + cache->edit_curve_handles_batch = GPU_batch_create( + GPU_PRIM_LINES, cache->edit_curve_vbo, NULL); + GPU_batch_vertbuf_add(cache->edit_curve_handles_batch, cache->edit_curve_vbo); + + cache->edit_curve_points_batch = GPU_batch_create( + GPU_PRIM_POINTS, cache->edit_curve_vbo, NULL); + GPU_batch_vertbuf_add(cache->edit_curve_points_batch, cache->edit_curve_vbo); + } gpd->flag &= ~GP_DATA_CACHE_IS_DIRTY; cache->is_dirty = false; @@ -761,4 +923,22 @@ GPUBatch *DRW_cache_gpencil_edit_points_get(Object *ob, int cfra) return cache->edit_points_batch; } +GPUBatch *DRW_cache_gpencil_edit_curve_handles_get(Object *ob, int cfra) +{ + GpencilBatchCache *cache = gpencil_batch_cache_get(ob, cfra); + gpencil_batches_ensure(ob, cache, cfra); + gpencil_edit_batches_ensure(ob, cache, cfra); + + return cache->edit_curve_handles_batch; +} + +GPUBatch *DRW_cache_gpencil_edit_curve_points_get(Object *ob, int cfra) +{ + GpencilBatchCache *cache = gpencil_batch_cache_get(ob, cfra); + gpencil_batches_ensure(ob, cache, cfra); + gpencil_edit_batches_ensure(ob, cache, cfra); + + return cache->edit_curve_points_batch; +} + /** \} */ diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index b2c618073f4..1e91edbb3c0 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -42,6 +42,7 @@ set(SRC gpencil_convert.c gpencil_data.c gpencil_edit.c + gpencil_edit_curve.c gpencil_fill.c gpencil_interpolate.c gpencil_merge.c diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index a9f9625db7a..a73bf8154d9 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -910,7 +910,7 @@ static void annotation_stroke_newfrombuffer(tGPsdata *p) int totarrowpoints = runtime.arrow_end_style; /* Setting up arrow stroke. */ - bGPDstroke *e_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false); + bGPDstroke *e_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false, false); annotation_stroke_arrow_allocate(e_arrow_gps, totarrowpoints); /* Set pointer to first non-initialized point. */ @@ -931,7 +931,7 @@ static void annotation_stroke_newfrombuffer(tGPsdata *p) int totarrowpoints = runtime.arrow_start_style; /* Setting up arrow stroke. */ - bGPDstroke *s_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false); + bGPDstroke *s_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false, false); annotation_stroke_arrow_allocate(s_arrow_gps, totarrowpoints); /* Set pointer to first non-initialized point. */ @@ -1198,7 +1198,7 @@ static void annotation_stroke_eraser_dostroke(tGPsdata *p, /* Second Pass: Remove any points that are tagged */ if (do_cull) { - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + gpencil_stroke_delete_tagged_points(p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } } } diff --git a/source/blender/editors/gpencil/editaction_gpencil.c b/source/blender/editors/gpencil/editaction_gpencil.c index d4f3c4d7f5e..aae31b11025 100644 --- a/source/blender/editors/gpencil/editaction_gpencil.c +++ b/source/blender/editors/gpencil/editaction_gpencil.c @@ -486,7 +486,7 @@ bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) */ for (gps = gpfs->strokes.first; gps; gps = gps->next) { /* make a copy of stroke, then of its points array */ - gpsn = BKE_gpencil_stroke_duplicate(gps, true); + gpsn = BKE_gpencil_stroke_duplicate(gps, true, true); /* append stroke to frame */ BLI_addtail(&gpf->strokes, gpsn); diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index 23ca5241866..315b3c281da 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -861,115 +861,115 @@ void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4]) /* generate strokes */ gps = BKE_gpencil_stroke_add(frameFills, color_Skin, 270, 75, false); BKE_gpencil_stroke_add_points(gps, data0, 270, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data1, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 60, false); BKE_gpencil_stroke_add_points(gps, data2, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data3, 64, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data4, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data5, 64, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data6, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Light, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data7, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Eyes, 49, 60, false); BKE_gpencil_stroke_add_points(gps, data8, 49, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data9, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Eyes, 49, 60, false); BKE_gpencil_stroke_add_points(gps, data10, 49, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data11, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameFills, color_Skin_Shadow, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data12, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data13, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data14, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 65, 60, false); BKE_gpencil_stroke_add_points(gps, data15, 65, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 34, 60, false); BKE_gpencil_stroke_add_points(gps, data16, 34, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data17, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 40, false); BKE_gpencil_stroke_add_points(gps, data18, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 34, 40, false); BKE_gpencil_stroke_add_points(gps, data19, 34, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data20, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 64, 60, false); BKE_gpencil_stroke_add_points(gps, data21, 64, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Pupils, 26, 60, false); BKE_gpencil_stroke_add_points(gps, data22, 26, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Pupils, 26, 60, false); BKE_gpencil_stroke_add_points(gps, data23, 26, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data24, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data25, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 18, 40, false); BKE_gpencil_stroke_add_points(gps, data26, 18, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps = BKE_gpencil_stroke_add(frameLines, color_Black, 33, 60, false); BKE_gpencil_stroke_add_points(gps, data27, 33, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* update depsgraph */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c index 39a2d594c13..f26fd936d40 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -249,7 +249,7 @@ void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4]) /* generate stroke */ gps = BKE_gpencil_stroke_add(frame_lines, color_black, 175, 75, false); BKE_gpencil_stroke_add_points(gps, data0, 175, mat); - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* update depsgraph */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 237b5839c42..293ce370a0b 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -1855,12 +1855,12 @@ static int image_to_gpencil_exec(bContext *C, wmOperator *op) bGPdata *gpd = (bGPdata *)ob->data; bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true); bGPDframe *gpf = BKE_gpencil_frame_addnew(gpl, CFRA); - done = BKE_gpencil_from_image(sima, gpf, size, is_mask); + done = BKE_gpencil_from_image(sima, gpd, gpf, size, is_mask); if (done) { /* Delete any selected point. */ LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } BKE_reportf(op->reports, RPT_INFO, "Object created"); diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index 2cee06c36ad..a963632a01b 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -580,7 +580,7 @@ static int gpencil_layer_duplicate_object_exec(bContext *C, wmOperator *op) LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { /* Make copy of source stroke. */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); /* Check if material is in destination object, * otherwise add the slot with the material. */ @@ -1531,6 +1531,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) const int direction = RNA_enum_get(op->ptr, "direction"); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { /* temp listbase to store selected strokes */ ListBase selected = {NULL}; @@ -1589,7 +1590,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) break; /* Bring Forward */ case GP_STROKE_MOVE_UP: - for (LinkData *link = selected.last; link; link = link->prev) { + LISTBASE_FOREACH_BACKWARD (LinkData *, link, &selected) { gps = link->data; BLI_listbase_link_move(&gpf->strokes, gps, 1); } @@ -1603,7 +1604,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) break; /* Send to Back */ case GP_STROKE_MOVE_BOTTOM: - for (LinkData *link = selected.last; link; link = link->prev) { + LISTBASE_FOREACH_BACKWARD (LinkData *, link, &selected) { gps = link->data; BLI_remlink(&gpf->strokes, gps); BLI_addhead(&gpf->strokes, gps); @@ -1612,6 +1613,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) default: BLI_assert(0); break; + changed = true; } } BLI_freelistN(&selected); @@ -1625,9 +1627,11 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - /* 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); + 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; } @@ -1693,6 +1697,7 @@ static int gpencil_stroke_change_color_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; /* loop all strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -1717,6 +1722,8 @@ static int gpencil_stroke_change_color_exec(bContext *C, wmOperator *op) /* assign new color */ gps->mat_nr = idx; + + changed = true; } } } @@ -1728,9 +1735,11 @@ static int gpencil_stroke_change_color_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - /* 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); + 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; } @@ -1757,9 +1766,7 @@ void GPENCIL_OT_stroke_change_color(wmOperatorType *ot) static int gpencil_material_lock_unsused_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); - Object *ob = CTX_data_active_object(C); - short *totcol = BKE_object_material_len_p(ob); /* sanity checks */ @@ -1776,6 +1783,7 @@ static int gpencil_material_lock_unsused_exec(bContext *C, wmOperator *UNUSED(op } } + bool changed = false; /* loop all selected strokes and unlock any color */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ @@ -1793,19 +1801,24 @@ static int gpencil_material_lock_unsused_exec(bContext *C, wmOperator *UNUSED(op tmp_ma->gp_style->flag &= ~GP_MATERIAL_LOCKED; DEG_id_tag_update(&tmp_ma->id, ID_RECALC_COPY_ON_WRITE); } + + changed = true; } } } } - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* 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); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + + /* 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; } @@ -3476,7 +3489,6 @@ static int gpencil_set_active_material_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); - bool changed = false; /* Sanity checks. */ if (gpd == NULL) { @@ -3484,6 +3496,7 @@ static int gpencil_set_active_material_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; /* Loop all selected strokes. */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 4ba75bcd604..f2c3804a067 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -52,6 +52,7 @@ #include "BKE_context.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "BKE_layer.h" #include "BKE_lib_id.h" @@ -145,6 +146,18 @@ static bool gpencil_editmode_toggle_poll(bContext *C) return ED_gpencil_data_get_active(C) != NULL; } +static bool gpencil_stroke_not_in_curve_edit_mode(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + bGPdata *gpd = (bGPdata *)ob->data; + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + + return (gpl != NULL && !GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -195,6 +208,22 @@ static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *op) ob->mode = mode; } + /* Recalculate editcurves for strokes where the geometry/vertex colors have changed */ + if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->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(gps, gps->editcurve); + + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + GP_EDITABLE_CURVES_END(gps_iter); + } + /* setup other modes */ ED_gpencil_setup_modes(C, gpd, mode); /* set cache as dirty */ @@ -813,7 +842,8 @@ void GPENCIL_OT_selection_opacity_toggle(wmOperatorType *ot) * \{ */ /* Make copies of selected point segments in a selected stroke */ -static void gpencil_duplicate_points(const bGPDstroke *gps, +static void gpencil_duplicate_points(bGPdata *gpd, + const bGPDstroke *gps, ListBase *new_strokes, const char *layername) { @@ -853,7 +883,7 @@ static void gpencil_duplicate_points(const bGPDstroke *gps, bGPDstroke *gpsd; /* make a stupid copy first of the entire stroke (to get the flags too) */ - gpsd = BKE_gpencil_stroke_duplicate((bGPDstroke *)gps, false); + gpsd = BKE_gpencil_stroke_duplicate((bGPDstroke *)gps, false, true); /* saves original layer name */ BLI_strncpy(gpsd->runtime.tmp_layerinfo, layername, sizeof(gpsd->runtime.tmp_layerinfo)); @@ -877,7 +907,7 @@ static void gpencil_duplicate_points(const bGPDstroke *gps, } } - BKE_gpencil_stroke_geometry_update(gpsd); + BKE_gpencil_stroke_geometry_update(gpd, gpsd); /* add to temp buffer */ gpsd->next = gpsd->prev = NULL; @@ -894,6 +924,7 @@ static void gpencil_duplicate_points(const bGPDstroke *gps, static int gpencil_duplicate_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -905,69 +936,80 @@ static int gpencil_duplicate_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* for each visible (and editable) layer's selected strokes, - * copy the strokes into a temporary buffer, then append - * once all done - */ - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - ListBase new_strokes = {NULL, NULL}; - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps; - - if (gpf == NULL) { - continue; - } + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* for each visible (and editable) layer's selected strokes, + * copy the strokes into a temporary buffer, then append + * once all done + */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + ListBase new_strokes = {NULL, NULL}; + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps; - /* make copies of selected strokes, and deselect these once we're done */ - for (gps = gpf->strokes.first; gps; gps = gps->next) { - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { + if (gpf == NULL) { continue; } - if (gps->flag & GP_STROKE_SELECT) { - if (gps->totpoints == 1) { - /* Special Case: If there's just a single point in this stroke... */ - bGPDstroke *gpsd; + /* make copies of selected strokes, and deselect these once we're done */ + for (gps = gpf->strokes.first; gps; gps = gps->next) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } - /* make direct copies of the stroke and its points */ - gpsd = BKE_gpencil_stroke_duplicate(gps, true); + if (gps->flag & GP_STROKE_SELECT) { + if (gps->totpoints == 1) { + /* Special Case: If there's just a single point in this stroke... */ + bGPDstroke *gpsd; - BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); + /* make direct copies of the stroke and its points */ + gpsd = BKE_gpencil_stroke_duplicate(gps, true, true); - /* Initialize triangle information. */ - BKE_gpencil_stroke_geometry_update(gpsd); + BLI_strncpy( + gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); - /* add to temp buffer */ - gpsd->next = gpsd->prev = NULL; - BLI_addtail(&new_strokes, gpsd); - } - else { - /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ - gpencil_duplicate_points(gps, &new_strokes, gpl->info); - } + /* Initialize triangle information. */ + BKE_gpencil_stroke_geometry_update(gpd, gpsd); - /* deselect original stroke, or else the originals get moved too - * (when using the copy + move macro) - */ - bGPDspoint *pt; - int i; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + /* add to temp buffer */ + gpsd->next = gpsd->prev = NULL; + BLI_addtail(&new_strokes, gpsd); + } + else { + /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ + gpencil_duplicate_points(gpd, gps, &new_strokes, gpl->info); + } + + /* deselect original stroke, or else the originals get moved too + * (when using the copy + move macro) + */ + bGPDspoint *pt; + int i; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } + gps->flag &= ~GP_STROKE_SELECT; + + changed = true; } - gps->flag &= ~GP_STROKE_SELECT; } - } - /* add all new strokes in temp buffer to the frame (preventing double-copies) */ - BLI_movelisttolist(&gpf->strokes, &new_strokes); - BLI_assert(new_strokes.first == NULL); + /* add all new strokes in temp buffer to the frame (preventing double-copies) */ + BLI_movelisttolist(&gpf->strokes, &new_strokes); + BLI_assert(new_strokes.first == NULL); + } + CTX_DATA_END; } - CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + /* updates */ + 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; } @@ -1028,7 +1070,7 @@ static void gpencil_copy_move_point(bGPDstroke *gps, } } -static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) +static void gpencil_add_move_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps) { bGPDspoint *temp_points = NULL; MDeformVert *temp_dverts = NULL; @@ -1049,7 +1091,7 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) pt = &gps->points[i]; if (pt->flag == GP_SPOINT_SELECT) { /* duplicate original stroke data */ - bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, false); + bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, false, true); gps_new->prev = gps_new->next = NULL; /* add new points array */ @@ -1067,8 +1109,8 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) gpencil_copy_move_point(gps_new, gps->points, gps->dvert, i, 0, true); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); - BKE_gpencil_stroke_geometry_update(gps_new); + BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps_new); /* deselect orinal point */ pt->flag &= ~GP_SPOINT_SELECT; @@ -1143,7 +1185,7 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); MEM_SAFE_FREE(temp_points); MEM_SAFE_FREE(temp_dverts); @@ -1155,10 +1197,100 @@ static void gpencil_add_move_points(bGPDframe *gpf, bGPDstroke *gps) } } +static void gpencil_curve_extrude_points(bGPdata *gpd, + bGPDframe *gpf, + bGPDstroke *gps, + bGPDcurve *gpc) +{ + const int old_num_points = gpc->tot_curve_points; + const bool first_select = gpc->curve_points[0].flag & GP_CURVE_POINT_SELECT; + bool last_select = gpc->curve_points[old_num_points - 1].flag & GP_CURVE_POINT_SELECT; + + /* iterate over middle points */ + for (int i = 1; i < gpc->tot_curve_points - 1; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + + /* Create new stroke if selected point */ + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(gps, false, false); + gps_new->points = NULL; + gps_new->flag &= ~GP_STROKE_CYCLIC; + gps_new->prev = gps_new->next = NULL; + + gps_new->editcurve = BKE_gpencil_stroke_editcurve_new(2); + bGPDcurve *new_gpc = gps_new->editcurve; + for (int j = 0; j < new_gpc->tot_curve_points; j++) { + bGPDcurve_point *gpc_pt_new = &new_gpc->curve_points[j]; + memcpy(gpc_pt_new, gpc_pt, sizeof(bGPDcurve_point)); + gpc_pt_new->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt_new->bezt); + } + + /* select last point */ + bGPDcurve_point *gpc_pt_last = &new_gpc->curve_points[1]; + gpc_pt_last->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(&gpc_pt_last->bezt, 1); + gps_new->editcurve->flag |= GP_CURVE_SELECT; + + BLI_insertlinkafter(&gpf->strokes, gps, gps_new); + + gps_new->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps_new); + + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + } + + /* Edgcase for single curve point. */ + if (gpc->tot_curve_points == 1) { + last_select = false; + } + + if (first_select || last_select) { + int new_num_points = old_num_points; + + if (first_select) { + new_num_points++; + } + if (last_select) { + new_num_points++; + } + + /* Grow the array */ + gpc->tot_curve_points = new_num_points; + gpc->curve_points = MEM_recallocN(gpc->curve_points, sizeof(bGPDcurve_point) * new_num_points); + + if (first_select) { + /* shift points by one */ + memmove( + &gpc->curve_points[1], &gpc->curve_points[0], sizeof(bGPDcurve_point) * old_num_points); + + bGPDcurve_point *old_first = &gpc->curve_points[1]; + + old_first->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&old_first->bezt); + } + + if (last_select) { + bGPDcurve_point *old_last = &gpc->curve_points[gpc->tot_curve_points - 2]; + bGPDcurve_point *new_last = &gpc->curve_points[gpc->tot_curve_points - 1]; + memcpy(new_last, old_last, sizeof(bGPDcurve_point)); + + old_last->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&old_last->bezt); + } + + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } +} + static int gpencil_extrude_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); bGPDstroke *gps = NULL; @@ -1167,6 +1299,7 @@ static int gpencil_extrude_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -1182,9 +1315,22 @@ static int gpencil_extrude_exec(bContext *C, wmOperator *op) continue; } - if (gps->flag & GP_STROKE_SELECT) { - gpencil_add_move_points(gpf, gps); + if (is_curve_edit) { + if (gps->editcurve == NULL) { + continue; + } + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + gpencil_curve_extrude_points(gpd, gpf, gps, gpc); + } + } + else { + if (gps->flag & GP_STROKE_SELECT) { + gpencil_add_move_points(gpd, gpf, gps); + } } + + changed = true; } /* if not multiedit, exit loop*/ if (!is_multiedit) { @@ -1195,10 +1341,13 @@ static int gpencil_extrude_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - /* 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); + 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; } @@ -1352,6 +1501,7 @@ static int gpencil_strokes_copy_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -1366,56 +1516,62 @@ static int gpencil_strokes_copy_exec(bContext *C, wmOperator *op) /* clear the buffer first */ ED_gpencil_strokes_copybuf_free(); - /* for each visible (and editable) layer's selected strokes, - * copy the strokes into a temporary buffer, then append - * once all done - */ - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = gpl->actframe; - bGPDstroke *gps; - - if (gpf == NULL) { - continue; - } + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* for each visible (and editable) layer's selected strokes, + * copy the strokes into a temporary buffer, then append + * once all done + */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + bGPDframe *gpf = gpl->actframe; + bGPDstroke *gps; - /* make copies of selected strokes, and deselect these once we're done */ - for (gps = gpf->strokes.first; gps; gps = gps->next) { - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { + if (gpf == NULL) { continue; } - if (gps->flag & GP_STROKE_SELECT) { - if (gps->totpoints == 1) { - /* Special Case: If there's just a single point in this stroke... */ - bGPDstroke *gpsd; - - /* make direct copies of the stroke and its points */ - gpsd = BKE_gpencil_stroke_duplicate(gps, false); - - /* saves original layer name */ - BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); - gpsd->points = MEM_dupallocN(gps->points); - if (gps->dvert != NULL) { - gpsd->dvert = MEM_dupallocN(gps->dvert); - BKE_gpencil_stroke_weights_duplicate(gps, gpsd); - } + /* make copies of selected strokes, and deselect these once we're done */ + for (gps = gpf->strokes.first; gps; gps = gps->next) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpsd); + if (gps->flag & GP_STROKE_SELECT) { + if (gps->totpoints == 1) { + /* Special Case: If there's just a single point in this stroke... */ + bGPDstroke *gpsd; + + /* make direct copies of the stroke and its points */ + gpsd = BKE_gpencil_stroke_duplicate(gps, false, true); + + /* saves original layer name */ + BLI_strncpy( + gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); + gpsd->points = MEM_dupallocN(gps->points); + if (gps->dvert != NULL) { + gpsd->dvert = MEM_dupallocN(gps->dvert); + BKE_gpencil_stroke_weights_duplicate(gps, gpsd); + } - /* add to temp buffer */ - gpsd->next = gpsd->prev = NULL; - BLI_addtail(&gpencil_strokes_copypastebuf, gpsd); - } - else { - /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ - gpencil_duplicate_points(gps, &gpencil_strokes_copypastebuf, gpl->info); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gpsd); + + /* add to temp buffer */ + gpsd->next = gpsd->prev = NULL; + BLI_addtail(&gpencil_strokes_copypastebuf, gpsd); + } + else { + /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */ + gpencil_duplicate_points(gpd, gps, &gpencil_strokes_copypastebuf, gpl->info); + } } } } + CTX_DATA_END; } - CTX_DATA_END; /* Build up hash of material colors used in these strokes */ if (gpencil_strokes_copypastebuf.first) { @@ -1493,6 +1649,7 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = (bGPdata *)ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); /* only use active for copy merge */ Scene *scene = CTX_data_scene(C); bGPDframe *gpf; @@ -1556,46 +1713,52 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) /* Ensure that all the necessary colors exist */ new_colors = gpencil_copybuf_validate_colormap(C); - /* Copy over the strokes from the buffer (and adjust the colors) */ - bGPDstroke *gps_init = (!on_back) ? gpencil_strokes_copypastebuf.first : - gpencil_strokes_copypastebuf.last; - for (bGPDstroke *gps = gps_init; gps; gps = (!on_back) ? gps->next : gps->prev) { - if (ED_gpencil_stroke_can_use(C, gps)) { - /* Need to verify if layer exists */ - if (type != GP_COPY_TO_ACTIVE) { - gpl = BLI_findstring(&gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info)); - if (gpl == NULL) { - /* no layer - use active (only if layer deleted before paste) */ - gpl = BKE_gpencil_layer_active_get(gpd); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* Copy over the strokes from the buffer (and adjust the colors) */ + bGPDstroke *gps_init = (!on_back) ? gpencil_strokes_copypastebuf.first : + gpencil_strokes_copypastebuf.last; + for (bGPDstroke *gps = gps_init; gps; gps = (!on_back) ? gps->next : gps->prev) { + if (ED_gpencil_stroke_can_use(C, gps)) { + /* Need to verify if layer exists */ + if (type != GP_COPY_TO_ACTIVE) { + gpl = BLI_findstring( + &gpd->layers, gps->runtime.tmp_layerinfo, offsetof(bGPDlayer, info)); + if (gpl == NULL) { + /* no layer - use active (only if layer deleted before paste) */ + gpl = BKE_gpencil_layer_active_get(gpd); + } } - } - /* Ensure we have a frame to draw into - * NOTE: Since this is an op which creates strokes, - * we are obliged to add a new frame if one - * doesn't exist already - */ - gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); - if (gpf) { - /* Create new stroke */ - bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true); - new_stroke->runtime.tmp_layerinfo[0] = '\0'; - new_stroke->next = new_stroke->prev = NULL; + /* Ensure we have a frame to draw into + * NOTE: Since this is an op which creates strokes, + * we are obliged to add a new frame if one + * doesn't exist already + */ + gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); + if (gpf) { + /* Create new stroke */ + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true, true); + new_stroke->runtime.tmp_layerinfo[0] = '\0'; + new_stroke->next = new_stroke->prev = NULL; - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(new_stroke); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); - if (on_back) { - BLI_addhead(&gpf->strokes, new_stroke); - } - else { - BLI_addtail(&gpf->strokes, new_stroke); - } + if (on_back) { + BLI_addhead(&gpf->strokes, new_stroke); + } + else { + BLI_addtail(&gpf->strokes, new_stroke); + } - /* Remap material */ - Material *ma = BLI_ghash_lookup(new_colors, POINTER_FROM_INT(new_stroke->mat_nr)); - new_stroke->mat_nr = BKE_gpencil_object_material_index_get(ob, ma); - CLAMP_MIN(new_stroke->mat_nr, 0); + /* Remap material */ + Material *ma = BLI_ghash_lookup(new_colors, POINTER_FROM_INT(new_stroke->mat_nr)); + new_stroke->mat_nr = BKE_gpencil_object_material_index_get(ob, ma); + CLAMP_MIN(new_stroke->mat_nr, 0); + } } } } @@ -2018,10 +2181,10 @@ typedef enum eGP_DissolveMode { /* Delete selected strokes */ static int gpencil_delete_selected_strokes(bContext *C) { - bool changed = false; bGPdata *gpd = ED_gpencil_data_get_active(C); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -2042,16 +2205,9 @@ static int gpencil_delete_selected_strokes(bContext *C) /* free stroke if selected */ if (gps->flag & GP_STROKE_SELECT) { + BLI_remlink(&gpf->strokes, gps); /* free stroke memory arrays, then stroke itself */ - if (gps->points) { - MEM_freeN(gps->points); - } - if (gps->dvert) { - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->dvert); - } - MEM_SAFE_FREE(gps->triangles); - BLI_freelinkN(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); changed = true; } @@ -2071,11 +2227,144 @@ static int gpencil_delete_selected_strokes(bContext *C) /* ----------------------------------- */ -/* Delete selected points but keep the stroke */ -static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) +static bool gpencil_dissolve_selected_curve_points(bContext *C, + bGPdata *gpd, + eGP_DissolveMode mode) +{ + bool changed = false; + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_SELECT) { + int first = 0, last = 0; + int num_points_remaining = gpc->tot_curve_points; + + switch (mode) { + case GP_DISSOLVE_POINTS: + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + num_points_remaining--; + } + } + break; + case GP_DISSOLVE_BETWEEN: + first = -1; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + if (first < 0) { + first = i; + } + last = i; + } + } + + for (int i = first + 1; i < last; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + num_points_remaining--; + } + } + break; + case GP_DISSOLVE_UNSELECT: + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + num_points_remaining--; + } + } + break; + default: + return false; + break; + } + + if (num_points_remaining < 1) { + /* Delete stroke */ + BLI_remlink(&gpf_->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + else { + bGPDcurve_point *new_points = MEM_callocN(sizeof(bGPDcurve_point) * num_points_remaining, + __func__); + + int idx = 0; + switch (mode) { + case GP_DISSOLVE_POINTS: + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + *new_cpt = *cpt; + idx++; + } + } + break; + case GP_DISSOLVE_BETWEEN: + for (int i = 0; i < first; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + + *new_cpt = *cpt; + idx++; + } + + for (int i = first; i < last; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + *new_cpt = *cpt; + idx++; + } + } + + for (int i = last; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + + *new_cpt = *cpt; + idx++; + } + break; + case GP_DISSOLVE_UNSELECT: + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[idx]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + *new_cpt = *cpt; + idx++; + } + } + break; + default: + return false; + break; + } + + if (gpc->curve_points != NULL) { + MEM_freeN(gpc->curve_points); + } + + gpc->curve_points = new_points; + gpc->tot_curve_points = num_points_remaining; + + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + changed = true; + } + } + GP_EDITABLE_CURVES_END(gps_iter); + + return changed; +} + +static bool gpencil_dissolve_selected_stroke_points(bContext *C, + bGPdata *gpd, + eGP_DissolveMode mode) { - Object *ob = CTX_data_active_object(C); - bGPdata *gpd = (bGPdata *)ob->data; bool changed = false; int first = 0; int last = 0; @@ -2135,18 +2424,8 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) /* if no points are left, we simply delete the entire stroke */ if (tot <= 0) { /* remove the entire stroke */ - if (gps->points) { - MEM_freeN(gps->points); - } - if (gps->dvert) { - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->dvert); - } - if (gps->triangles) { - MEM_freeN(gps->triangles); - } - BLI_freelinkN(&gpf_->strokes, gps); - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + BLI_remlink(&gpf_->strokes, gps); + BKE_gpencil_free_stroke(gps); } else { /* just copy all points to keep into a smaller buffer */ @@ -2263,7 +2542,7 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) gps->totpoints = tot; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* deselect the stroke, since none of its selected points will still be selected */ gps->flag &= ~GP_STROKE_SELECT; @@ -2277,6 +2556,24 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) } GP_EDITABLE_STROKES_END(gpstroke_iter); + return changed; +} + +/* Delete selected points but keep the stroke */ +static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = (bGPdata *)ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; + + if (is_curve_edit) { + changed = gpencil_dissolve_selected_curve_points(C, gpd, mode); + } + else { + changed = gpencil_dissolve_selected_stroke_points(C, gpd, mode); + } + 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); @@ -2289,14 +2586,15 @@ static int gpencil_dissolve_selected_points(bContext *C, eGP_DissolveMode mode) /* Temp data for storing information about an "island" of points * that should be kept when splitting up a stroke. Used in: - * gp_stroke_delete_tagged_points() + * gpencil_stroke_delete_tagged_points() */ typedef struct tGPDeleteIsland { int start_idx; int end_idx; } tGPDeleteIsland; -static void gpencil_stroke_join_islands(bGPDframe *gpf, +static void gpencil_stroke_join_islands(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps_first, bGPDstroke *gps_last) { @@ -2305,7 +2603,7 @@ static void gpencil_stroke_join_islands(bGPDframe *gpf, const int totpoints = gps_first->totpoints + gps_last->totpoints; /* create new stroke */ - bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false); + bGPDstroke *join_stroke = BKE_gpencil_stroke_duplicate(gps_first, false, true); join_stroke->points = MEM_callocN(sizeof(bGPDspoint) * totpoints, __func__); join_stroke->totpoints = totpoints; @@ -2373,7 +2671,7 @@ static void gpencil_stroke_join_islands(bGPDframe *gpf, /* add new stroke at head */ BLI_addhead(&gpf->strokes, join_stroke); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(join_stroke); + BKE_gpencil_stroke_geometry_update(gpd, join_stroke); /* remove first stroke */ BLI_remlink(&gpf->strokes, gps_first); @@ -2398,7 +2696,8 @@ static void gpencil_stroke_join_islands(bGPDframe *gpf, * 2) Each island gets converted to a new stroke * If the number of points is <= limit, the stroke is deleted */ -void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, +void gpencil_stroke_delete_tagged_points(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags, @@ -2450,7 +2749,7 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, /* Create each new stroke... */ for (idx = 0; idx < num_islands; idx++) { tGPDeleteIsland *island = &islands[idx]; - new_stroke = BKE_gpencil_stroke_duplicate(gps, false); + new_stroke = BKE_gpencil_stroke_duplicate(gps, false, true); /* if cyclic and first stroke, save to join later */ if ((is_cyclic) && (gps_first == NULL)) { @@ -2521,7 +2820,7 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, } else { /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); if (next_stroke) { BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); @@ -2533,7 +2832,7 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, } /* if cyclic, need to join last stroke with first stroke */ if ((is_cyclic) && (gps_first != NULL) && (gps_first != new_stroke)) { - gpencil_stroke_join_islands(gpf, gps_first, new_stroke); + gpencil_stroke_join_islands(gpd, gpf, gps_first, new_stroke); } } @@ -2545,13 +2844,120 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, BKE_gpencil_free_stroke(gps); } -/* Split selected strokes into segments, splitting on selected points */ -static int gpencil_delete_selected_points(bContext *C) +static void gpencil_curve_delete_tagged_points(bGPdata *gpd, + bGPDframe *gpf, + bGPDstroke *gps, + bGPDstroke *next_stroke, + bGPDcurve *gpc, + int tag_flags) { - Object *ob = CTX_data_active_object(C); - bGPdata *gpd = ED_gpencil_data_get_active(C); - const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); - bool changed = false; + if (gpc == NULL) { + return; + } + const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + const int idx_last = gpc->tot_curve_points - 1; + bGPDstroke *gps_first = NULL; + bGPDstroke *gps_last = NULL; + + int idx_start = 0; + int idx_end = 0; + bool prev_selected = gpc->curve_points[0].flag & tag_flags; + for (int i = 1; i < gpc->tot_curve_points; i++) { + bool selected = gpc->curve_points[i].flag & tag_flags; + if (prev_selected == true && selected == false) { + idx_start = i; + } + /* Island ends if the current point is selected or if we reached the end of the stroke */ + if ((prev_selected == false && selected == true) || (selected == false && i == idx_last)) { + + idx_end = selected ? i - 1 : i; + int island_length = idx_end - idx_start + 1; + + /* If an island has only a single curve point, there is no curve segment, so skip island */ + if (island_length == 1) { + if (is_cyclic) { + if (idx_start > 0 && idx_end < idx_last) { + prev_selected = selected; + continue; + } + } + else { + prev_selected = selected; + continue; + } + } + + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, false, false); + new_stroke->points = NULL; + new_stroke->flag &= ~GP_STROKE_CYCLIC; + new_stroke->editcurve = BKE_gpencil_stroke_editcurve_new(island_length); + + if (gps_first == NULL) { + gps_first = new_stroke; + } + + bGPDcurve *new_gpc = new_stroke->editcurve; + memcpy(new_gpc->curve_points, + gpc->curve_points + idx_start, + sizeof(bGPDcurve_point) * island_length); + + BKE_gpencil_editcurve_recalculate_handles(new_stroke); + new_stroke->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + + if (next_stroke) { + BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); + } + else { + BLI_addtail(&gpf->strokes, new_stroke); + } + + gps_last = new_stroke; + } + prev_selected = selected; + } + + /* join first and last stroke if cyclic */ + if (is_cyclic && gps_first != NULL && gps_last != NULL && gps_first != gps_last) { + bGPDcurve *gpc_first = gps_first->editcurve; + bGPDcurve *gpc_last = gps_last->editcurve; + int first_tot_points = gpc_first->tot_curve_points; + int old_tot_points = gpc_last->tot_curve_points; + + gpc_last->tot_curve_points = first_tot_points + old_tot_points; + gpc_last->curve_points = MEM_recallocN(gpc_last->curve_points, + sizeof(bGPDcurve_point) * gpc_last->tot_curve_points); + /* copy data from first to last */ + memcpy(gpc_last->curve_points + old_tot_points, + gpc_first->curve_points, + sizeof(bGPDcurve_point) * first_tot_points); + + BKE_gpencil_editcurve_recalculate_handles(gps_last); + gps_last->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps_last); + + /* remove first one */ + BLI_remlink(&gpf->strokes, gps_first); + BKE_gpencil_free_stroke(gps_first); + } + + /* Delete the old stroke */ + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); +} + +/* Split selected strokes into segments, splitting on selected points */ +static int gpencil_delete_selected_points(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -2579,8 +2985,16 @@ static int gpencil_delete_selected_points(bContext *C) /* deselect old stroke, since it will be used as template for the new strokes */ gps->flag &= ~GP_STROKE_SELECT; - /* delete unwanted points by splitting stroke into several smaller ones */ - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + if (is_curve_edit) { + bGPDcurve *gpc = gps->editcurve; + gpencil_curve_delete_tagged_points( + gpd, gpf, gps, gps->next, gpc, GP_CURVE_POINT_SELECT); + } + else { + /* delete unwanted points by splitting stroke into several smaller ones */ + gpencil_stroke_delete_tagged_points( + gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + } changed = true; } @@ -2743,7 +3157,9 @@ static int gpencil_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *obact = CTX_data_active_object(C); const float gridf = ED_view3d_grid_view_scale(scene, v3d, region, NULL); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { @@ -2754,9 +3170,6 @@ static int gpencil_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - bGPDspoint *pt; - int i; - /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; @@ -2766,30 +3179,82 @@ static int gpencil_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) continue; } - /* TODO: if entire stroke is selected, offset entire stroke by same amount? */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - /* only if point is selected */ - if (pt->flag & GP_SPOINT_SELECT) { - /* apply parent transformations */ - float fpt[3]; - mul_v3_m4v3(fpt, diff_mat, &pt->x); + if (is_curve_edit) { + if (gps->editcurve == NULL) { + continue; + } + float inv_diff_mat[4][4]; + invert_m4_m4_safe(inv_diff_mat, diff_mat); + + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + float tmp0[3], tmp1[3], tmp2[3], offset[3]; + mul_v3_m4v3(tmp0, diff_mat, bezt->vec[0]); + mul_v3_m4v3(tmp1, diff_mat, bezt->vec[1]); + mul_v3_m4v3(tmp2, diff_mat, bezt->vec[2]); + + /* calculate the offset vector */ + offset[0] = gridf * floorf(0.5f + tmp1[0] / gridf) - tmp1[0]; + offset[1] = gridf * floorf(0.5f + tmp1[1] / gridf) - tmp1[1]; + offset[2] = gridf * floorf(0.5f + tmp1[2] / gridf) - tmp1[2]; + + /* shift bezTriple */ + add_v3_v3(bezt->vec[0], offset); + add_v3_v3(bezt->vec[1], offset); + add_v3_v3(bezt->vec[2], offset); + + mul_v3_m4v3(tmp0, inv_diff_mat, bezt->vec[0]); + mul_v3_m4v3(tmp1, inv_diff_mat, bezt->vec[1]); + mul_v3_m4v3(tmp2, inv_diff_mat, bezt->vec[2]); + copy_v3_v3(bezt->vec[0], tmp0); + copy_v3_v3(bezt->vec[1], tmp1); + copy_v3_v3(bezt->vec[2], tmp2); + + changed = true; + } + } - fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); - fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); - fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); + if (changed) { + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + else { + /* TODO: if entire stroke is selected, offset entire stroke by same amount? */ + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + /* only if point is selected */ + if (pt->flag & GP_SPOINT_SELECT) { + /* apply parent transformations */ + float fpt[3]; + mul_v3_m4v3(fpt, diff_mat, &pt->x); + + fpt[0] = gridf * floorf(0.5f + fpt[0] / gridf); + fpt[1] = gridf * floorf(0.5f + fpt[1] / gridf); + fpt[2] = gridf * floorf(0.5f + fpt[2] / gridf); + + /* return data */ + copy_v3_v3(&pt->x, fpt); + gpencil_apply_parent_point(depsgraph, obact, gpl, pt); - /* return data */ - copy_v3_v3(&pt->x, fpt); - gpencil_apply_parent_point(depsgraph, obact, gpl, pt); + changed = true; + } } } } } } - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + 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; } @@ -2817,7 +3282,7 @@ void GPENCIL_OT_snap_to_grid(wmOperatorType *ot) static int gpencil_snap_to_cursor(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); - + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *obact = CTX_data_active_object(C); @@ -2825,50 +3290,60 @@ static int gpencil_snap_to_cursor(bContext *C, wmOperator *op) const bool use_offset = RNA_boolean_get(op->ptr, "use_offset"); const float *cursor_global = scene->cursor.location; - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - /* only editable and visible layers are considered */ - if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { - bGPDframe *gpf = gpl->actframe; - float diff_mat[4][4]; + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* only editable and visible layers are considered */ + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + bGPDframe *gpf = gpl->actframe; + float diff_mat[4][4]; - /* calculate difference matrix */ - BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); + /* calculate difference matrix */ + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - bGPDspoint *pt; - int i; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + bGPDspoint *pt; + int i; - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { - continue; - } - /* check if the color is editable */ - if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { - continue; - } - /* only continue if this stroke is selected (editable doesn't guarantee this)... */ - if ((gps->flag & GP_STROKE_SELECT) == 0) { - continue; - } + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { + continue; + } + /* only continue if this stroke is selected (editable doesn't guarantee this)... */ + if ((gps->flag & GP_STROKE_SELECT) == 0) { + continue; + } - if (use_offset) { - float offset[3]; + if (use_offset) { + float offset[3]; - /* compute offset from first point of stroke to cursor */ - /* TODO: Allow using midpoint instead? */ - sub_v3_v3v3(offset, cursor_global, &gps->points->x); + /* compute offset from first point of stroke to cursor */ + /* TODO: Allow using midpoint instead? */ + sub_v3_v3v3(offset, cursor_global, &gps->points->x); - /* apply offset to all points in the stroke */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - add_v3_v3(&pt->x, offset); + /* apply offset to all points in the stroke */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + add_v3_v3(&pt->x, offset); + } + + changed = true; } - } - else { - /* affect each selected point */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - copy_v3_v3(&pt->x, cursor_global); - gpencil_apply_parent_point(depsgraph, obact, gpl, pt); + else { + /* affect each selected point */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + copy_v3_v3(&pt->x, cursor_global); + gpencil_apply_parent_point(depsgraph, obact, gpl, pt); + + changed = true; + } } } } @@ -2876,9 +3351,12 @@ static int gpencil_snap_to_cursor(bContext *C, wmOperator *op) } } - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - DEG_id_tag_update(&obact->id, ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + 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; } @@ -2910,23 +3388,16 @@ void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot) /** \name Snapping Cursor to Selection Operator * \{ */ -static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) +static bool gpencil_stroke_points_centroid(Depsgraph *depsgraph, + bContext *C, + Object *obact, + bGPdata *gpd, + float r_centroid[3], + float r_min[3], + float r_max[3], + size_t *count) { - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Object *obact = CTX_data_active_object(C); - Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); - bGPdata *gpd = (bGPdata *)ob_eval->data; - - Scene *scene = CTX_data_scene(C); - View3D *v3d = CTX_wm_view3d(C); - - float *cursor = scene->cursor.location; - float centroid[3] = {0.0f}; - float min[3], max[3]; - size_t count = 0; - - INIT_MINMAX(min, max); - + bool changed = false; /* calculate midpoints from selected points */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { /* only editable and visible layers are considered */ @@ -2960,29 +3431,61 @@ static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(op)) float fpt[3]; mul_v3_m4v3(fpt, diff_mat, &pt->x); - add_v3_v3(centroid, fpt); - minmax_v3v3_v3(min, max, fpt); + add_v3_v3(r_centroid, fpt); + minmax_v3v3_v3(r_min, r_max, fpt); - count++; + (*count)++; } } + + changed = true; } } } - if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_BOUNDS) { - mid_v3_v3v3(cursor, min, max); + return changed; +} + +static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *op) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *obact = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + Scene *scene = CTX_data_scene(C); + View3D *v3d = CTX_wm_view3d(C); + + float *cursor = scene->cursor.location; + float centroid[3] = {0.0f}; + float min[3], max[3]; + size_t count = 0; + + INIT_MINMAX(min, max); + + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); } - else { /* #V3D_AROUND_CENTER_MEDIAN. */ - zero_v3(cursor); - if (count) { - mul_v3_fl(centroid, 1.0f / (float)count); - copy_v3_v3(cursor, centroid); - } + else { + changed = gpencil_stroke_points_centroid(depsgraph, C, obact, gpd, centroid, min, max, &count); } - DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); + if (changed) { + if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_BOUNDS) { + mid_v3_v3v3(cursor, min, max); + } + else { /* #V3D_AROUND_CENTER_MEDIAN. */ + zero_v3(cursor); + if (count) { + mul_v3_fl(centroid, 1.0f / (float)count); + copy_v3_v3(cursor, centroid); + } + } + + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d); + } return OPERATOR_FINISHED; } @@ -3030,6 +3533,7 @@ static int gpencil_stroke_apply_thickness_exec(bContext *C, wmOperator *UNUSED(o } } } + /* clear value */ gpl->thickness = 0.0f; gpl->line_change = 0; @@ -3073,6 +3577,7 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) const int type = RNA_enum_get(op->ptr, "type"); const bool geometry = RNA_boolean_get(op->ptr, "geometry"); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); bGPDstroke *gps = NULL; /* sanity checks */ @@ -3080,6 +3585,7 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; /* loop all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -3103,6 +3609,7 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) continue; } + bool before = (bool)(gps->flag & GP_STROKE_CYCLIC); switch (type) { case GP_STROKE_CYCLIC_CLOSE: /* Close all (enable) */ @@ -3121,9 +3628,19 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) break; } - /* Create new geometry. */ - if ((gps->flag & GP_STROKE_CYCLIC) && (geometry)) { - BKE_gpencil_stroke_close(gps); + if (before != (gps->flag & GP_STROKE_CYCLIC)) { + /* Create new geometry. */ + if (is_curve_edit) { + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + else if ((gps->flag & GP_STROKE_CYCLIC) && geometry) { + BKE_gpencil_stroke_close(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + changed = true; } } @@ -3136,13 +3653,31 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - /* 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); + 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; } +static bool gpencil_cyclical_set_curve_edit_poll_property(const bContext *C, + wmOperator *UNUSED(op), + const PropertyRNA *prop) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + if (gpd != NULL && GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + const char *prop_id = RNA_property_identifier(prop); + /* Only show type in curve edit mode */ + if (!STREQ(prop_id, "type")) { + return false; + } + } + + return true; +} + /** * Similar to #CURVE_OT_cyclic_toggle or #MASK_OT_cyclic_toggle, but with * option to force opened/closed strokes instead of just toggle behavior. @@ -3166,6 +3701,7 @@ void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_stroke_cyclical_set_exec; ot->poll = gpencil_active_layer_poll; + ot->poll_property = gpencil_cyclical_set_curve_edit_poll_property; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -3194,7 +3730,6 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); Object *ob = CTX_data_active_object(C); - const int type = RNA_enum_get(op->ptr, "type"); /* sanity checks */ @@ -3202,6 +3737,7 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; /* loop all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { if (gpl->actframe == NULL) { @@ -3221,6 +3757,9 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) continue; } + short prev_first = gps->caps[0]; + short prev_last = gps->caps[1]; + if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_START)) { ++gps->caps[0]; if (gps->caps[0] >= GP_STROKE_CAP_MAX) { @@ -3237,13 +3776,19 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) gps->caps[0] = GP_STROKE_CAP_ROUND; gps->caps[1] = GP_STROKE_CAP_ROUND; } + + if (prev_first != gps->caps[0] || prev_last != gps->caps[1]) { + changed = true; + } } } CTX_DATA_END; - /* 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); + 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; } @@ -3539,6 +4084,11 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + if (is_curve_edit) { + return OPERATOR_CANCELLED; + } + if (activegpl->flag & GP_LAYER_LOCKED) { return OPERATOR_CANCELLED; } @@ -3597,7 +4147,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) elem->used = true; /* Create a new stroke. */ - bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(elem->gps, true); + bGPDstroke *gps_new = BKE_gpencil_stroke_duplicate(elem->gps, true, true); gps_new->flag &= ~GP_STROKE_CYCLIC; BLI_insertlinkbefore(&elem->gpf->strokes, elem->gps, gps_new); @@ -3614,7 +4164,7 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) } /* Calc geometry data for new stroke. */ - BKE_gpencil_stroke_geometry_update(gps_new); + BKE_gpencil_stroke_geometry_update(gpd, gps_new); /* If join only, delete old strokes. */ if (type == GP_STROKE_JOIN) { @@ -3670,7 +4220,7 @@ void GPENCIL_OT_stroke_join(wmOperatorType *ot) /** \name Stroke Flip Operator * \{ */ -static int gpencil_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) +static int gpencil_stroke_flip_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); Object *ob = CTX_data_active_object(C); @@ -3680,6 +4230,8 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; /* read all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *gpf = gpl->actframe; @@ -3698,16 +4250,25 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) continue; } - /* flip stroke */ - gpencil_flip_stroke(gps); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* flip stroke */ + gpencil_flip_stroke(gps); + } + + changed = true; } } } CTX_DATA_END; - /* 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); + 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; } @@ -3742,19 +4303,26 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) int oldframe = (int)DEG_get_ctime(depsgraph); const eGP_ReprojectModes mode = RNA_enum_get(op->ptr, "type"); const bool keep_original = RNA_boolean_get(op->ptr, "keep_original"); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); - /* Init space conversion stuff. */ - GP_SpaceConversion gsc = {NULL}; - SnapObjectContext *sctx = NULL; - gpencil_point_conversion_init(C, &gsc); /* Init snap context for geometry projection. */ + SnapObjectContext *sctx = NULL; sctx = ED_transform_snap_object_context_create_view3d(scene, 0, region, CTX_wm_view3d(C)); + bool changed = false; + /* Init space conversion stuff. */ + GP_SpaceConversion gsc = {NULL}; + gpencil_point_conversion_init(C, &gsc); int cfra_prv = INT_MIN; /* Go through each editable + selected stroke, adjusting each of its points one by one... */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { + bool curve_select = false; + if (is_curve_edit && gps->editcurve != NULL) { + curve_select = gps->editcurve->flag & GP_CURVE_SELECT; + } + + if (gps->flag & GP_STROKE_SELECT || curve_select) { /* update frame to get the new location of objects */ if ((mode == GP_REPROJECT_SURFACE) && (cfra_prv != gpf_->framenum)) { @@ -3764,6 +4332,17 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) } ED_gpencil_stroke_reproject(depsgraph, &gsc, sctx, gpl, gpf_, gps, mode, keep_original); + + if (is_curve_edit && gps->editcurve != NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + /* Update the selection from the stroke to the curve. */ + BKE_gpencil_editcurve_stroke_sync_selection(gps, gps->editcurve); + + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + changed = true; } } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -3776,9 +4355,12 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) ED_transform_snap_object_context_destroy(sctx); } - /* update changed data */ - DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + if (changed) { + /* update changed data */ + 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; } @@ -3846,10 +4428,11 @@ static int gpencil_recalc_geometry_exec(bContext *C, wmOperator *UNUSED(op)) LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } } + /* update changed data */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -3943,134 +4526,176 @@ static int gpencil_count_subdivision_cuts(bGPDstroke *gps) return totnewpoints; } -static int gpencil_stroke_subdivide_exec(bContext *C, wmOperator *op) +static void gpencil_stroke_subdivide(bGPDstroke *gps, const int cuts) { - bGPdata *gpd = ED_gpencil_data_get_active(C); bGPDspoint *temp_points; - const int cuts = RNA_int_get(op->ptr, "number_cuts"); - int totnewpoints, oldtotpoints; int i2; + /* loop as many times as cuts */ + for (int s = 0; s < cuts; s++) { + totnewpoints = gpencil_count_subdivision_cuts(gps); + if (totnewpoints == 0) { + continue; + } + /* duplicate points in a temp area */ + temp_points = MEM_dupallocN(gps->points); + oldtotpoints = gps->totpoints; - /* sanity checks */ - if (ELEM(NULL, gpd)) { - return OPERATOR_CANCELLED; - } + MDeformVert *temp_dverts = NULL; + MDeformVert *dvert_final = NULL; + MDeformVert *dvert = NULL; + MDeformVert *dvert_next = NULL; + if (gps->dvert != NULL) { + temp_dverts = MEM_dupallocN(gps->dvert); + } - /* Go through each editable + selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { - /* loop as many times as cuts */ - for (int s = 0; s < cuts; s++) { - totnewpoints = gpencil_count_subdivision_cuts(gps); - if (totnewpoints == 0) { - continue; - } - /* duplicate points in a temp area */ - temp_points = MEM_dupallocN(gps->points); - oldtotpoints = gps->totpoints; - - MDeformVert *temp_dverts = NULL; - MDeformVert *dvert_final = NULL; - MDeformVert *dvert = NULL; - MDeformVert *dvert_next = NULL; - if (gps->dvert != NULL) { - temp_dverts = MEM_dupallocN(gps->dvert); - } + /* resize the points arrays */ + gps->totpoints += totnewpoints; + gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); + if (gps->dvert != NULL) { + gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); + } - /* resize the points arrays */ - gps->totpoints += totnewpoints; - gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); - if (gps->dvert != NULL) { - gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); - } + /* loop and interpolate */ + i2 = 0; + for (int i = 0; i < oldtotpoints; i++) { + bGPDspoint *pt = &temp_points[i]; + bGPDspoint *pt_final = &gps->points[i2]; - /* loop and interpolate */ - i2 = 0; - for (int i = 0; i < oldtotpoints; i++) { - bGPDspoint *pt = &temp_points[i]; - bGPDspoint *pt_final = &gps->points[i2]; - - /* copy current point */ - copy_v3_v3(&pt_final->x, &pt->x); - pt_final->pressure = pt->pressure; - pt_final->strength = pt->strength; - pt_final->time = pt->time; - pt_final->flag = pt->flag; - copy_v4_v4(pt_final->vert_color, pt->vert_color); - - if (gps->dvert != NULL) { - dvert = &temp_dverts[i]; - dvert_final = &gps->dvert[i2]; - dvert_final->totweight = dvert->totweight; - dvert_final->dw = dvert->dw; - } - i2++; + /* copy current point */ + copy_v3_v3(&pt_final->x, &pt->x); + pt_final->pressure = pt->pressure; + pt_final->strength = pt->strength; + pt_final->time = pt->time; + pt_final->flag = pt->flag; + copy_v4_v4(pt_final->vert_color, pt->vert_color); - /* if next point is selected add a half way point */ - if (pt->flag & GP_SPOINT_SELECT) { - if (i + 1 < oldtotpoints) { - if (temp_points[i + 1].flag & GP_SPOINT_SELECT) { - pt_final = &gps->points[i2]; - if (gps->dvert != NULL) { - dvert_final = &gps->dvert[i2]; - } - /* Interpolate all values */ - bGPDspoint *next = &temp_points[i + 1]; - interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); - pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); - pt_final->strength = interpf(pt->strength, next->strength, 0.5f); - CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); - interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f); - pt_final->time = interpf(pt->time, next->time, 0.5f); - pt_final->flag |= GP_SPOINT_SELECT; - - /* interpolate weights */ - if (gps->dvert != NULL) { - dvert = &temp_dverts[i]; - dvert_next = &temp_dverts[i + 1]; - dvert_final = &gps->dvert[i2]; - - dvert_final->totweight = dvert->totweight; - dvert_final->dw = MEM_dupallocN(dvert->dw); - - /* interpolate weight values */ - for (int d = 0; d < dvert->totweight; d++) { - MDeformWeight *dw_a = &dvert->dw[d]; - if (dvert_next->totweight > d) { - MDeformWeight *dw_b = &dvert_next->dw[d]; - MDeformWeight *dw_final = &dvert_final->dw[d]; - dw_final->weight = interpf(dw_a->weight, dw_b->weight, 0.5f); - } - } - } + if (gps->dvert != NULL) { + dvert = &temp_dverts[i]; + dvert_final = &gps->dvert[i2]; + dvert_final->totweight = dvert->totweight; + dvert_final->dw = dvert->dw; + } + i2++; - i2++; + /* if next point is selected add a half way point */ + if (pt->flag & GP_SPOINT_SELECT) { + if (i + 1 < oldtotpoints) { + if (temp_points[i + 1].flag & GP_SPOINT_SELECT) { + pt_final = &gps->points[i2]; + if (gps->dvert != NULL) { + dvert_final = &gps->dvert[i2]; + } + /* Interpolate all values */ + bGPDspoint *next = &temp_points[i + 1]; + interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); + pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); + pt_final->strength = interpf(pt->strength, next->strength, 0.5f); + CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); + interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f); + pt_final->time = interpf(pt->time, next->time, 0.5f); + pt_final->flag |= GP_SPOINT_SELECT; + + /* interpolate weights */ + if (gps->dvert != NULL) { + dvert = &temp_dverts[i]; + dvert_next = &temp_dverts[i + 1]; + dvert_final = &gps->dvert[i2]; + + dvert_final->totweight = dvert->totweight; + dvert_final->dw = MEM_dupallocN(dvert->dw); + + /* interpolate weight values */ + for (int d = 0; d < dvert->totweight; d++) { + MDeformWeight *dw_a = &dvert->dw[d]; + if (dvert_next->totweight > d) { + MDeformWeight *dw_b = &dvert_next->dw[d]; + MDeformWeight *dw_final = &dvert_final->dw[d]; + dw_final->weight = interpf(dw_a->weight, dw_b->weight, 0.5f); + } } } + + i2++; } } - /* free temp memory */ - MEM_SAFE_FREE(temp_points); - MEM_SAFE_FREE(temp_dverts); } + } + /* free temp memory */ + MEM_SAFE_FREE(temp_points); + MEM_SAFE_FREE(temp_dverts); + } +} - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); +static int gpencil_stroke_subdivide_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + const int cuts = RNA_int_get(op->ptr, "number_cuts"); + + /* sanity checks */ + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + bool changed = false; + if (is_curve_edit) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_SELECT) { + BKE_gpencil_editcurve_subdivide(gps, cuts); + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + changed = true; + } } + GP_EDITABLE_CURVES_END(gps_iter); } - GP_EDITABLE_STROKES_END(gpstroke_iter); + else { + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + gpencil_stroke_subdivide(gps, cuts); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps); + changed = true; + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); - /* smooth stroke */ - gpencil_smooth_stroke(C, op); + if (changed) { + /* smooth stroke */ + gpencil_smooth_stroke(C, op); + } + } - /* 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); + 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; } +static bool gpencil_subdivide_curve_edit_poll_property(const bContext *C, + wmOperator *UNUSED(op), + const PropertyRNA *prop) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + if (gpd != NULL && GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + const char *prop_id = RNA_property_identifier(prop); + /* Only show number_cuts in curve edit mode */ + if (!STREQ(prop_id, "number_cuts")) { + return false; + } + } + + return true; +} + void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) { PropertyRNA *prop; @@ -4085,6 +4710,7 @@ void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_stroke_subdivide_exec; ot->poll = gpencil_active_layer_poll; + ot->poll_property = gpencil_subdivide_curve_edit_poll_property; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -4120,18 +4746,29 @@ static int gpencil_stroke_simplify_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Go through each editable + selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { - /* simplify stroke using Ramer-Douglas-Peucker algorithm */ - BKE_gpencil_stroke_simplify_adaptive(gps, factor); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + /* simplify stroke using Ramer-Douglas-Peucker algorithm */ + BKE_gpencil_stroke_simplify_adaptive(gpd, gps, factor); + changed = true; + } } + GP_EDITABLE_STROKES_END(gpstroke_iter); } - GP_EDITABLE_STROKES_END(gpstroke_iter); - /* 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); + 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; } @@ -4169,19 +4806,30 @@ static int gpencil_stroke_simplify_fixed_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Go through each editable + selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { - for (int i = 0; i < steps; i++) { - BKE_gpencil_stroke_simplify_fixed(gps); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + bool changed = false; + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + changed |= true; + for (int i = 0; i < steps; i++) { + BKE_gpencil_stroke_simplify_fixed(gpd, gps); + } } } + GP_EDITABLE_STROKES_END(gpstroke_iter); } - GP_EDITABLE_STROKES_END(gpstroke_iter); - /* 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); + 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; } @@ -4223,7 +4871,7 @@ static int gpencil_stroke_sample_exec(bContext *C, wmOperator *op) /* Go through each editable + selected stroke */ GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_stroke_sample(gps, length, true); + BKE_gpencil_stroke_sample(gpd, gps, length, true); } } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -4246,7 +4894,7 @@ void GPENCIL_OT_stroke_sample(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_stroke_sample_exec; - ot->poll = gpencil_active_layer_poll; + ot->poll = gpencil_stroke_not_in_curve_edit_mode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -4263,7 +4911,7 @@ void GPENCIL_OT_stroke_sample(wmOperatorType *ot) /** \name Stroke Trim Operator * \{ */ -static int gpencil_stroke_trim_exec(bContext *C, wmOperator *UNUSED(op)) +static int gpencil_stroke_trim_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); @@ -4274,6 +4922,7 @@ static int gpencil_stroke_trim_exec(bContext *C, wmOperator *UNUSED(op)) /* Go through each editable + selected stroke */ const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; @@ -4293,7 +4942,12 @@ static int gpencil_stroke_trim_exec(bContext *C, wmOperator *UNUSED(op)) } if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_stroke_trim(gps); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + BKE_gpencil_stroke_trim(gpd, gps); + } } } /* if not multiedit, exit loop*/ @@ -4373,6 +5027,7 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) } const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_src); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd_src); /* Create a new object. */ /* Take into account user preferences for duplicating actions. */ @@ -4434,27 +5089,32 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) /* selected points mode */ if (mode == GP_SEPARATE_POINT) { - /* make copy of source stroke */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* make copy of source stroke */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true, true); - /* Reassign material. */ - gps_dst->mat_nr = idx; + /* Reassign material. */ + gps_dst->mat_nr = idx; - /* link to destination frame */ - BLI_addtail(&gpf_dst->strokes, gps_dst); + /* link to destination frame */ + BLI_addtail(&gpf_dst->strokes, gps_dst); - /* Invert selection status of all points in destination stroke */ - for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { - pt->flag ^= GP_SPOINT_SELECT; - } + /* Invert selection status of all points in destination stroke */ + for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { + pt->flag ^= GP_SPOINT_SELECT; + } - /* delete selected points from destination stroke */ - gpencil_stroke_delete_tagged_points( - gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); + /* delete selected points from destination stroke */ + gpencil_stroke_delete_tagged_points( + gpd_dst, gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); - /* delete selected points from origin stroke */ - gpencil_stroke_delete_tagged_points( - gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + /* delete selected points from origin stroke */ + gpencil_stroke_delete_tagged_points( + gpd_src, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + } } /* selected strokes mode */ else if (mode == GP_SEPARATE_STROKE) { @@ -4574,7 +5234,7 @@ void GPENCIL_OT_stroke_separate(wmOperatorType *ot) /** \name Stroke Split Operator * \{ */ -static int gpencil_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) +static int gpencil_stroke_split_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); @@ -4586,6 +5246,7 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); /* loop strokes and split parts */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { @@ -4610,22 +5271,29 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) } /* Split selected strokes. */ if (gps->flag & GP_STROKE_SELECT) { - /* make copy of source stroke */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* make copy of source stroke */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true, true); - /* link to same frame */ - BLI_addtail(&gpf->strokes, gps_dst); + /* link to same frame */ + BLI_addtail(&gpf->strokes, gps_dst); - /* invert selection status of all points in destination stroke */ - for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { - pt->flag ^= GP_SPOINT_SELECT; - } + /* invert selection status of all points in destination stroke */ + for (i = 0, pt = gps_dst->points; i < gps_dst->totpoints; i++, pt++) { + pt->flag ^= GP_SPOINT_SELECT; + } - /* delete selected points from destination stroke */ - gpencil_stroke_delete_tagged_points(gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0); + /* delete selected points from destination stroke */ + gpencil_stroke_delete_tagged_points( + gpd, gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0); - /* delete selected points from origin stroke */ - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + /* delete selected points from origin stroke */ + gpencil_stroke_delete_tagged_points( + gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + } } } /* select again tagged points */ @@ -4705,7 +5373,7 @@ void GPENCIL_OT_stroke_smooth(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_stroke_smooth_exec; - ot->poll = gpencil_active_layer_poll; + ot->poll = gpencil_stroke_not_in_curve_edit_mode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -4761,7 +5429,8 @@ typedef bool (*GPencilTestFn)(bGPDstroke *gps, const float diff_mat[4][4], void *user_data); -static void gpencil_cutter_dissolve(bGPDlayer *hit_layer, +static void gpencil_cutter_dissolve(bGPdata *gpd, + bGPDlayer *hit_layer, bGPDstroke *hit_stroke, const bool flat_caps) { @@ -4819,7 +5488,7 @@ static void gpencil_cutter_dissolve(bGPDlayer *hit_layer, } gpencil_stroke_delete_tagged_points( - hit_layer->actframe, hit_stroke, gpsn, GP_SPOINT_TAG, false, 1); + gpd, hit_layer->actframe, hit_stroke, gpsn, GP_SPOINT_TAG, false, 1); } } @@ -4877,7 +5546,7 @@ static int gpencil_cutter_lasso_select(bContext *C, gps->flag |= GP_STROKE_SELECT; float r_hita[3], r_hitb[3]; if (gps->totpoints > 1) { - ED_gpencil_select_stroke_segment(gpl, gps, pt, true, true, scale, r_hita, r_hitb); + ED_gpencil_select_stroke_segment(gpd, gpl, gps, pt, true, true, scale, r_hita, r_hitb); } /* avoid infinite loops */ if (gps->totpoints > oldtot) { @@ -4907,7 +5576,7 @@ static int gpencil_cutter_lasso_select(bContext *C, } LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_SELECT) { - gpencil_cutter_dissolve(gpl, gps, flat_caps); + gpencil_cutter_dissolve(gpd, gpl, gps, flat_caps); } } } @@ -5039,13 +5708,20 @@ static int gpencil_merge_by_distance_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Go through each editable selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { - BKE_gpencil_stroke_merge_distance(gpf_, gps, threshold, unselected); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + if (is_curve_edit) { + /* TODO: merge curve points by distance */ + } + else { + /* Go through each editable selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (gps->flag & GP_STROKE_SELECT) { + BKE_gpencil_stroke_merge_distance(gpd, gpf_, gps, threshold, unselected); + } } + GP_EDITABLE_STROKES_END(gpstroke_iter); } - GP_EDITABLE_STROKES_END(gpstroke_iter); /* notifiers */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_edit_curve.c b/source/blender/editors/gpencil/gpencil_edit_curve.c new file mode 100644 index 00000000000..60d1d2169b4 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_edit_curve.c @@ -0,0 +1,214 @@ +/* + * 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) 2008, Blender Foundation + * This is a new part of Blender + * Operators for editing Grease Pencil strokes + */ + +/** \file + * \ingroup edgpencil + */ + +#include +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_gpencil_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" +#include "BKE_gpencil_geom.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_gpencil.h" + +#include "DEG_depsgraph.h" + +#include "gpencil_intern.h" + +/* Poll callback for checking if there is an active layer and we are in curve edit mode. */ +static bool gpencil_curve_edit_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_CURVE_EDIT_SESSIONS_ON(gpd)) { + return false; + } + + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + return (gpl != NULL); +} + +static int gpencil_stroke_enter_editcurve_mode_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + + float error_threshold = RNA_float_get(op->ptr, "error_threshold"); + gpd->curve_edit_threshold = error_threshold; + + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + if (gpf == gpl->actframe) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* only allow selected and non-converted strokes to be transformed */ + if ((gps->flag & GP_STROKE_SELECT && gps->editcurve == NULL) || + (gps->editcurve != NULL && 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(gps, gps->editcurve); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + } + } + } + + gpd->flag |= GP_DATA_CURVE_EDIT_MODE; + + /* 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_enter_editcurve_mode(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Enter curve edit mode"; + ot->idname = "GPENCIL_OT_stroke_enter_editcurve_mode"; + ot->description = "Called to transform a stroke into a curve"; + + /* api callbacks */ + ot->exec = gpencil_stroke_enter_editcurve_mode_exec; + ot->poll = gpencil_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_float(ot->srna, + "error_threshold", + 0.1f, + FLT_MIN, + 100.0f, + "Error Threshold", + "Threshold on the maximum deviation from the actual stroke", + FLT_MIN, + 10.f); + RNA_def_property_ui_range(prop, FLT_MIN, 10.0f, 0.1f, 5); +} + +static int gpencil_editcurve_set_handle_type_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + const int handle_type = RNA_enum_get(op->ptr, "type"); + + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + BezTriple *bezt = &gpc_pt->bezt; + + if (bezt->f2 & SELECT) { + bezt->h1 = handle_type; + bezt->h2 = handle_type; + } + else { + if (bezt->f1 & SELECT) { + bezt->h1 = handle_type; + } + if (bezt->f3 & SELECT) { + bezt->h2 = handle_type; + } + } + } + } + + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + GP_EDITABLE_CURVES_END(gps_iter); + + /* 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_editcurve_set_handle_type(wmOperatorType *ot) +{ + static const EnumPropertyItem editcurve_handle_type_items[] = { + {HD_FREE, "FREE", 0, "Free", ""}, + {HD_AUTO, "AUTOMATIC", 0, "Automatic", ""}, + {HD_VECT, "VECTOR", 0, "Vector", ""}, + {HD_ALIGN, "ALIGNED", 0, "Aligned", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Set handle type"; + ot->idname = "GPENCIL_OT_stroke_editcurve_set_handle_type"; + ot->description = "Set the type of a edit curve handle"; + + /* api callbacks */ + ot->invoke = WM_menu_invoke; + ot->exec = gpencil_editcurve_set_handle_type_exec; + ot->poll = gpencil_curve_edit_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "type", editcurve_handle_type_items, 1, "Type", "Spline type"); +} + +/** \} */ diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index f06a1d6b6c8..93941ea3766 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -1299,11 +1299,11 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) /* simplify stroke */ for (int b = 0; b < tgpf->fill_simplylvl; b++) { - BKE_gpencil_stroke_simplify_fixed(gps); + BKE_gpencil_stroke_simplify_fixed(tgpf->gpd, gps); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(tgpf->gpd, gps); } /* ----------------------- */ diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index e3e2199f8a3..e4e9a3ae0ab 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -343,7 +343,8 @@ struct GHash *gpencil_copybuf_validate_colormap(struct bContext *C); /* Stroke Editing ------------------------------------ */ -void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, +void gpencil_stroke_delete_tagged_points(bGPdata *gpd, + bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags, @@ -351,7 +352,7 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf, int limit); int gpencil_delete_selected_point_wrap(bContext *C); -void gpencil_subdivide_stroke(bGPDstroke *gps, const int subdivide); +void gpencil_subdivide_stroke(bGPdata *gpd, bGPDstroke *gps, const int subdivide); /* Layers Enums -------------------------------------- */ @@ -447,6 +448,11 @@ void GPENCIL_OT_snap_cursor_to_selected(struct wmOperatorType *ot); void GPENCIL_OT_reproject(struct wmOperatorType *ot); void GPENCIL_OT_recalc_geometry(struct wmOperatorType *ot); +/* stroke editcurve */ + +void GPENCIL_OT_stroke_enter_editcurve_mode(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_editcurve_set_handle_type(struct wmOperatorType *ot); + /* stroke sculpting -- */ void GPENCIL_OT_sculpt_paint(struct wmOperatorType *ot); @@ -692,6 +698,55 @@ struct GP_EditableStrokes_Iter { } \ (void)0 +/** + * Iterate over all editable editcurves in the current context, + * stopping on each usable layer + stroke + curve pair (i.e. gpl, gps and gpc) + * to perform some operations on the curve. + * + * \param gpl: The identifier to use for the layer of the stroke being processed. + * Choose a suitable value to avoid name clashes. + * \param gps: The identifier to use for current stroke being processed. + * Choose a suitable value to avoid name clashes. + * \param gpc: The identifier to use for current editcurve being processed. + * Choose a suitable value to avoid name clashes. + */ +#define GP_EDITABLE_CURVES_BEGIN(gpstroke_iter, C, gpl, gps, gpc) \ + { \ + struct GP_EditableStrokes_Iter gpstroke_iter = {{{0}}}; \ + Depsgraph *depsgraph_ = CTX_data_ensure_evaluated_depsgraph(C); \ + Object *obact_ = CTX_data_active_object(C); \ + bGPdata *gpd_ = CTX_data_gpencil_data(C); \ + const bool is_multiedit_ = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_); \ + 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_)) { \ + BKE_gpencil_parent_matrix_get(depsgraph_, obact_, gpl, gpstroke_iter.diff_mat); \ + invert_m4_m4(gpstroke_iter.inverse_diff_mat, gpstroke_iter.diff_mat); \ + /* loop over strokes */ \ + bGPDstroke *gpsn_; \ + for (bGPDstroke *gps = gpf_->strokes.first; gps; gps = gpsn_) { \ + gpsn_ = gps->next; \ + /* skip strokes that are invalid for current view */ \ + if (ED_gpencil_stroke_can_use(C, gps) == false) \ + continue; \ + if (gps->editcurve == NULL) \ + continue; \ + bGPDcurve *gpc = gps->editcurve; \ + /* ... Do Stuff With Strokes ... */ + +#define GP_EDITABLE_CURVES_END(gpstroke_iter) \ + } \ + } \ + if (!is_multiedit_) { \ + break; \ + } \ + } \ + } \ + CTX_DATA_END; \ + } \ + (void)0 + /** * Iterate over all editable strokes using evaluated data in the current context, * stopping on each usable layer + stroke pair (i.e. gpl and gps) diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index 3a3a9bde38b..3617f20763e 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -184,7 +184,7 @@ static void gpencil_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgp /* Add temp strokes. */ if (gpf) { - bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true); + bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true, true); gps_eval->flag |= GP_STROKE_TAG; BLI_addtail(&gpf->strokes, gps_eval); } @@ -327,7 +327,7 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) } /* create new stroke */ - new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true); + new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); if (valid) { /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ @@ -353,7 +353,7 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); /* add to strokes */ BLI_addtail(&tgpil->interFrame->strokes, new_stroke); } @@ -608,11 +608,11 @@ static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent } /* make copy of source stroke, then adjust pointer to points too */ - gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true); + gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); gps_dst->flag &= ~GP_STROKE_TAG; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps_dst); + BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps_dst); BLI_addtail(&gpf_dst->strokes, gps_dst); } @@ -1050,7 +1050,7 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) } /* create new stroke */ - bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true); + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ if (gps_from->totpoints > gps_to->totpoints) { @@ -1075,7 +1075,7 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); /* add to strokes */ BLI_addtail(&interFrame->strokes, new_stroke); diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c index 938f4ab26af..f795ed01bb8 100644 --- a/source/blender/editors/gpencil/gpencil_merge.c +++ b/source/blender/editors/gpencil/gpencil_merge.c @@ -172,6 +172,9 @@ static void gpencil_get_elements_len(bContext *C, int *totstrokes, int *totpoint static void gpencil_dissolve_points(bContext *C) { + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *gpf = gpl->actframe; if (gpf == NULL) { @@ -179,7 +182,7 @@ static void gpencil_dissolve_points(bContext *C) } LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + gpencil_stroke_delete_tagged_points(gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } } CTX_DATA_END; @@ -519,7 +522,7 @@ static int gpencil_stroke_merge_exec(bContext *C, wmOperator *op) gpencil_dissolve_points(C); } - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* free memory */ MEM_SAFE_FREE(original_array); diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 9f7725e01f5..33ac0c5bbbf 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -69,6 +69,13 @@ static bool gpencil_stroke_editmode_poll(bContext *C) return (gpd && (gpd->flag & GP_DATA_STROKE_EDITMODE)); } +/* Poll callback for stroke curve editing mode */ +static bool gpencil_stroke_editmode_curve_poll(bContext *C) +{ + bGPdata *gpd = CTX_data_gpencil_data(C); + return (GPENCIL_EDIT_MODE(gpd) && GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)); +} + /* Poll callback for stroke painting mode */ static bool gpencil_stroke_paintmode_poll(bContext *C) { @@ -315,6 +322,15 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf) keymap->poll = gpencil_stroke_editmode_poll; } +/* Stroke Curve Editing Keymap - Only when editmode is enabled and in curve edit mode */ +static void ed_keymap_gpencil_curve_editing(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Curve Edit Mode", 0, 0); + + /* set poll callback - so that this keymap only gets enabled when curve editmode is enabled */ + keymap->poll = gpencil_stroke_editmode_curve_poll; +} + /* keys for draw with a drawing brush (no fill) */ static void ed_keymap_gpencil_painting_draw(wmKeyConfig *keyconf) { @@ -471,6 +487,7 @@ static void ed_keymap_gpencil_weightpainting_draw(wmKeyConfig *keyconf) void ED_keymap_gpencil(wmKeyConfig *keyconf) { ed_keymap_gpencil_general(keyconf); + ed_keymap_gpencil_curve_editing(keyconf); ed_keymap_gpencil_editing(keyconf); ed_keymap_gpencil_painting(keyconf); ed_keymap_gpencil_painting_draw(keyconf); @@ -568,6 +585,11 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_sculpt_paint); WM_operatortype_append(GPENCIL_OT_weight_paint); + /* Edit stroke editcurve */ + + WM_operatortype_append(GPENCIL_OT_stroke_enter_editcurve_mode); + WM_operatortype_append(GPENCIL_OT_stroke_editcurve_set_handle_type); + /* Editing (Buttons) ------------ */ WM_operatortype_append(GPENCIL_OT_annotation_add); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 82c30dea91d..e544093cd1d 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1195,7 +1195,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) /* subdivide and smooth the stroke */ if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (subdivide > 0)) { - gpencil_subdivide_stroke(gps, subdivide); + gpencil_subdivide_stroke(gpd, gps, subdivide); } /* Smooth stroke after subdiv - only if there's something to do for each iteration, @@ -1226,7 +1226,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) /* Simplify adaptive */ if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (brush->gpencil_settings->simplify_f > 0.0f)) { - BKE_gpencil_stroke_simplify_adaptive(gps, brush->gpencil_settings->simplify_f); + BKE_gpencil_stroke_simplify_adaptive(gpd, gps, brush->gpencil_settings->simplify_f); } /* reproject to plane (only in 3d space) */ @@ -1279,11 +1279,11 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) /* post process stroke */ if ((p->brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && p->brush->gpencil_settings->flag & GP_BRUSH_TRIM_STROKE) { - BKE_gpencil_stroke_trim(gps); + BKE_gpencil_stroke_trim(gpd, gps); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gpencil_stroke_added_enable(p); } @@ -1652,7 +1652,7 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, gpencil_stroke_soft_refine(gps); } - gpencil_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); + gpencil_stroke_delete_tagged_points(p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } gpencil_update_cache(p->gpd); } diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 55180885c5d..801dacb3e6b 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -1082,7 +1082,7 @@ static void gpencil_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); /* Update evaluated data. */ ED_gpencil_sbuffer_update_eval(tgpi->gpd, tgpi->ob_eval); @@ -1323,7 +1323,7 @@ static void gpencil_primitive_interaction_end(bContext *C, copy_v2_v2(gps->aspect_ratio, brush_settings->aspect_ratio); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps); } /* transfer stroke from temporary buffer to the actual frame */ diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c index 1d500223ab8..ed18c2eed5d 100644 --- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c +++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c @@ -303,7 +303,7 @@ static void gpencil_update_geometry(bGPdata *gpd) LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_TAG) { - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); gps->flag &= ~GP_STROKE_TAG; } } @@ -1021,7 +1021,7 @@ static void gpencil_brush_clone_add(bContext *C, tGP_BrushEditData *gso) bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); /* Make a new stroke */ - new_stroke = BKE_gpencil_stroke_duplicate(gps, true); + new_stroke = BKE_gpencil_stroke_duplicate(gps, true, true); new_stroke->next = new_stroke->prev = NULL; BLI_addtail(&gpf->strokes, new_stroke); @@ -1574,6 +1574,7 @@ static bool gpencil_sculpt_brush_do_frame(bContext *C, bool changed = false; bool redo_geom = false; Object *ob = gso->object; + bGPdata *gpd = ob->data; char tool = gso->brush->gpencil_sculpt_tool; LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { @@ -1672,7 +1673,7 @@ static bool gpencil_sculpt_brush_do_frame(bContext *C, MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); /* Update active frame now, only if material has fill. */ if (gp_style->flag & GP_MATERIAL_FILL_SHOW) { - BKE_gpencil_stroke_geometry_update(gps_active); + BKE_gpencil_stroke_geometry_update(gpd, gps_active); } else { gpencil_recalc_geometry_tag(gps_active); diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index 82d5eedf576..ddd8a11d9b5 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -43,10 +43,13 @@ #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" +#include "BKE_gpencil_geom.h" #include "BKE_material.h" #include "BKE_report.h" #include "UI_interface.h" +#include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" @@ -58,6 +61,7 @@ #include "ED_gpencil.h" #include "ED_select_utils.h" +#include "ED_view3d.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -126,6 +130,86 @@ static bool gpencil_select_poll(bContext *C) return false; } +static bool gpencil_3d_point_to_screen_space(ARegion *region, + const float diff_mat[4][4], + const float co[3], + int r_co[2]) +{ + float parent_co[3]; + mul_v3_m4v3(parent_co, diff_mat, co); + int screen_co[2]; + if (ED_view3d_project_int_global( + region, parent_co, screen_co, V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == + V3D_PROJ_RET_OK) { + if (!ELEM(V2D_IS_CLIPPED, screen_co[0], screen_co[1])) { + copy_v2_v2_int(r_co, screen_co); + return true; + } + } + r_co[0] = V2D_IS_CLIPPED; + r_co[1] = V2D_IS_CLIPPED; + return false; +} + +/* helper to deselect all selected strokes/points */ +static void deselect_all_selected(bContext *C) +{ + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + /* deselect stroke and its points if selected */ + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + + /* deselect points */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } + + /* deselect stroke itself too */ + gps->flag &= ~GP_STROKE_SELECT; + } + + /* deselect curve and curve points */ + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + for (int j = 0; j < gpc->tot_curve_points; j++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[j]; + BezTriple *bezt = &gpc_pt->bezt; + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + + gpc->flag &= ~GP_CURVE_SELECT; + } + } + CTX_DATA_END; +} + +static void select_all_curve_points(bGPDstroke *gps, bGPDcurve *gpc, bool deselect) +{ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (deselect == false) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + } + + if (deselect == false) { + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + gps->flag &= ~GP_STROKE_SELECT; + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -149,6 +233,7 @@ static int gpencil_select_all_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); int action = RNA_enum_get(op->ptr, "action"); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -168,7 +253,12 @@ static int gpencil_select_all_exec(bContext *C, wmOperator *op) } } - ED_gpencil_select_toggle_all(C, action); + if (is_curve_edit) { + ED_gpencil_select_curve_toggle_all(C, action); + } + else { + ED_gpencil_select_toggle_all(C, action); + } /* updates */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); @@ -207,6 +297,7 @@ void GPENCIL_OT_select_all(wmOperatorType *ot) static int gpencil_select_linked_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -218,18 +309,34 @@ static int gpencil_select_linked_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* select all points in selected strokes */ - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; + if (is_curve_edit) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_SELECT) { + 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_SEL_ALL(bezt); + } + } + } + GP_EDITABLE_CURVES_END(gps_iter); + } + else { + /* select all points in selected strokes */ + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag |= GP_SPOINT_SELECT; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag |= GP_SPOINT_SELECT; + } } } + CTX_DATA_END; } - CTX_DATA_END; /* updates */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); @@ -265,8 +372,9 @@ void GPENCIL_OT_select_linked(wmOperatorType *ot) static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) { - const bool unselect_ends = RNA_boolean_get(op->ptr, "unselect_ends"); bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool unselect_ends = RNA_boolean_get(op->ptr, "unselect_ends"); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -278,47 +386,94 @@ static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* select all points in selected strokes */ - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if ((gps->flag & GP_STROKE_SELECT) && (gps->totpoints > 1)) { - bGPDspoint *pt; - int row = 0; - int start = 0; - if (unselect_ends) { - start = 1; - } + bool changed = false; + if (is_curve_edit) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if ((gps->flag & GP_STROKE_SELECT) && (gps->totpoints > 1)) { + int idx = 0; + int start = 0; + if (unselect_ends) { + start = 1; + } - for (int i = start; i < gps->totpoints; i++) { - pt = &gps->points[i]; - if ((row % 2) == 0) { - pt->flag |= GP_SPOINT_SELECT; + for (int i = start; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + if ((idx % 2) == 0) { + gpc_pt->flag |= GP_SPOINT_SELECT; + BEZT_SEL_ALL(&gpc_pt->bezt); + } + else { + gpc_pt->flag &= ~GP_SPOINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + idx++; } - else { - pt->flag &= ~GP_SPOINT_SELECT; + + if (unselect_ends) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[0]; + gpc_pt->flag &= ~GP_SPOINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + + gpc_pt = &gpc->curve_points[gpc->tot_curve_points - 1]; + gpc_pt->flag &= ~GP_SPOINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); } - row++; + + BKE_gpencil_curve_sync_selection(gps); + changed = true; } + } + GP_EDITABLE_CURVES_END(gps_iter); + } + else { + /* select all points in selected strokes */ + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if ((gps->flag & GP_STROKE_SELECT) && (gps->totpoints > 1)) { + bGPDspoint *pt; + int row = 0; + int start = 0; + if (unselect_ends) { + start = 1; + } - /* unselect start and end points */ - if (unselect_ends) { - pt = &gps->points[0]; - pt->flag &= ~GP_SPOINT_SELECT; + for (int i = start; i < gps->totpoints; i++) { + pt = &gps->points[i]; + if ((row % 2) == 0) { + pt->flag |= GP_SPOINT_SELECT; + } + else { + pt->flag &= ~GP_SPOINT_SELECT; + } + row++; + } - pt = &gps->points[gps->totpoints - 1]; - pt->flag &= ~GP_SPOINT_SELECT; + /* unselect start and end points */ + if (unselect_ends) { + pt = &gps->points[0]; + pt->flag &= ~GP_SPOINT_SELECT; + + pt = &gps->points[gps->totpoints - 1]; + pt->flag &= ~GP_SPOINT_SELECT; + } + + changed = true; } } + CTX_DATA_END; } - CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + + WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -365,10 +520,13 @@ typedef enum eGP_SelectGrouped { /* ----------------------------------- */ /* On each visible layer, check for selected strokes - if found, select all others */ -static void gpencil_select_same_layer(bContext *C) +static bool gpencil_select_same_layer(bContext *C) { Scene *scene = CTX_data_scene(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV); bGPDstroke *gps; @@ -390,29 +548,55 @@ static void gpencil_select_same_layer(bContext *C) /* Select all if found */ if (found) { - for (gps = gpf->strokes.first; gps; gps = gps->next) { - if (ED_gpencil_stroke_can_use(C, gps)) { - bGPDspoint *pt; - int i; - - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag |= GP_SPOINT_SELECT; + if (is_curve_edit) { + for (gps = gpf->strokes.first; gps; gps = gps->next) { + if (gps->editcurve != NULL && ED_gpencil_stroke_can_use(C, gps)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc_pt->bezt); + } + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + + changed = true; } + } + } + else { + for (gps = gpf->strokes.first; gps; gps = gps->next) { + if (ED_gpencil_stroke_can_use(C, gps)) { + bGPDspoint *pt; + int i; + + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag |= GP_SPOINT_SELECT; + } - gps->flag |= GP_STROKE_SELECT; + gps->flag |= GP_STROKE_SELECT; + + changed = true; + } } } } } CTX_DATA_END; + + return changed; } /* Select all strokes with same colors as selected ones */ -static void gpencil_select_same_material(bContext *C) +static bool gpencil_select_same_material(bContext *C) { + bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); /* First, build set containing all the colors of selected strokes */ GSet *selected_colors = BLI_gset_str_new("GP Selected Colors"); + bool changed = false; + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { if (gps->flag & GP_STROKE_SELECT) { /* add instead of insert here, otherwise the uniqueness check gets skipped, @@ -424,25 +608,48 @@ static void gpencil_select_same_material(bContext *C) CTX_DATA_END; /* Second, select any visible stroke that uses these colors */ - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (BLI_gset_haskey(selected_colors, &gps->mat_nr)) { - /* select this stroke */ - bGPDspoint *pt; - int i; + if (is_curve_edit) { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (gps->editcurve != NULL && BLI_gset_haskey(selected_colors, &gps->mat_nr)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc_pt->bezt); + } + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag |= GP_SPOINT_SELECT; + changed = true; } + } + CTX_DATA_END; + } + else { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (BLI_gset_haskey(selected_colors, &gps->mat_nr)) { + /* select this stroke */ + bGPDspoint *pt; + int i; - gps->flag |= GP_STROKE_SELECT; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag |= GP_SPOINT_SELECT; + } + + gps->flag |= GP_STROKE_SELECT; + + changed = true; + } } + CTX_DATA_END; } - CTX_DATA_END; /* free memomy */ if (selected_colors != NULL) { BLI_gset_free(selected_colors, NULL); } + + return changed; } /* ----------------------------------- */ @@ -456,12 +663,14 @@ static int gpencil_select_grouped_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + bool changed = false; + switch (mode) { case GP_SEL_SAME_LAYER: - gpencil_select_same_layer(C); + changed = gpencil_select_same_layer(C); break; case GP_SEL_SAME_MATERIAL: - gpencil_select_same_material(C); + changed = gpencil_select_same_material(C); break; default: @@ -469,14 +678,16 @@ static int gpencil_select_grouped_exec(bContext *C, wmOperator *op) break; } - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + } return OPERATOR_FINISHED; } @@ -515,6 +726,8 @@ void GPENCIL_OT_select_grouped(wmOperatorType *ot) static int gpencil_select_first_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; @@ -523,6 +736,7 @@ static int gpencil_select_first_exec(bContext *C, wmOperator *op) const bool only_selected = RNA_boolean_get(op->ptr, "only_selected_strokes"); const bool extend = RNA_boolean_get(op->ptr, "extend"); + bool changed = false; CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { /* skip stroke if we're only manipulating selected strokes */ if (only_selected && !(gps->flag & GP_STROKE_SELECT)) { @@ -532,30 +746,53 @@ static int gpencil_select_first_exec(bContext *C, wmOperator *op) /* select first point */ BLI_assert(gps->totpoints >= 1); - gps->points->flag |= GP_SPOINT_SELECT; - gps->flag |= GP_STROKE_SELECT; + if (is_curve_edit) { + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + gpc->curve_points[0].flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc->curve_points[0].bezt); + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + if ((extend == false) && (gps->totpoints > 1)) { + for (int i = 1; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + } + changed = true; + } + } + else { + gps->points->flag |= GP_SPOINT_SELECT; + gps->flag |= GP_STROKE_SELECT; - /* deselect rest? */ - if ((extend == false) && (gps->totpoints > 1)) { - /* start from index 1, to skip the first point that we'd just selected... */ - bGPDspoint *pt = &gps->points[1]; - int i = 1; + /* deselect rest? */ + if ((extend == false) && (gps->totpoints > 1)) { + /* start from index 1, to skip the first point that we'd just selected... */ + bGPDspoint *pt = &gps->points[1]; + int i = 1; - for (; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + for (; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } } + changed = true; } } CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + + WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -590,12 +827,14 @@ void GPENCIL_OT_select_first(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Select First +/** \name Select Last * \{ */ static int gpencil_select_last_exec(bContext *C, wmOperator *op) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; @@ -604,6 +843,7 @@ static int gpencil_select_last_exec(bContext *C, wmOperator *op) const bool only_selected = RNA_boolean_get(op->ptr, "only_selected_strokes"); const bool extend = RNA_boolean_get(op->ptr, "extend"); + bool changed = false; CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { /* skip stroke if we're only manipulating selected strokes */ if (only_selected && !(gps->flag & GP_STROKE_SELECT)) { @@ -613,30 +853,54 @@ static int gpencil_select_last_exec(bContext *C, wmOperator *op) /* select last point */ BLI_assert(gps->totpoints >= 1); - gps->points[gps->totpoints - 1].flag |= GP_SPOINT_SELECT; - gps->flag |= GP_STROKE_SELECT; + if (is_curve_edit) { + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + gpc->curve_points[gpc->tot_curve_points - 1].flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc->curve_points[gpc->tot_curve_points - 1].bezt); + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + if ((extend == false) && (gps->totpoints > 1)) { + for (int i = 0; i < gpc->tot_curve_points - 1; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&gpc_pt->bezt); + } + } + changed = true; + } + } + else { + gps->points[gps->totpoints - 1].flag |= GP_SPOINT_SELECT; + gps->flag |= GP_STROKE_SELECT; - /* deselect rest? */ - if ((extend == false) && (gps->totpoints > 1)) { - /* don't include the last point... */ - bGPDspoint *pt = gps->points; - int i = 1; + /* deselect rest? */ + if ((extend == false) && (gps->totpoints > 1)) { + /* don't include the last point... */ + bGPDspoint *pt = gps->points; + int i = 0; - for (; i < gps->totpoints - 1; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + for (; i < gps->totpoints - 1; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } } + + changed = true; } } CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + + WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -677,64 +941,118 @@ void GPENCIL_OT_select_last(wmOperatorType *ot) static int gpencil_select_more_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; } - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; - bool prev_sel; - - /* First Pass: Go in forward order, - * expanding selection if previous was selected (pre changes). - * - This pass covers the "after" edges of selection islands - */ - prev_sel = false; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - /* selected point - just set flag for next point */ - prev_sel = true; + bool changed = false; + if (is_curve_edit) { + GP_EDITABLE_STROKES_BEGIN (gp_iter, C, gpl, gps) { + if (gps->editcurve != NULL && gps->flag & GP_STROKE_SELECT) { + bGPDcurve *editcurve = gps->editcurve; + + bool prev_sel = false; + for (int i = 0; i < editcurve->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &editcurve->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + /* selected point - just set flag for next point */ + prev_sel = true; + } + else { + /* unselected point - expand selection if previous was selected... */ + if (prev_sel) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + changed = true; + } + prev_sel = false; + } } - else { - /* unselected point - expand selection if previous was selected... */ - if (prev_sel) { - pt->flag |= GP_SPOINT_SELECT; + + prev_sel = false; + for (int i = editcurve->tot_curve_points - 1; i >= 0; i--) { + bGPDcurve_point *gpc_pt = &editcurve->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + prev_sel = true; + } + else { + /* unselected point - expand selection if previous was selected... */ + if (prev_sel) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + changed = true; + } + prev_sel = false; } - prev_sel = false; } } + } + GP_EDITABLE_STROKES_END(gp_iter); + } + else { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + bool prev_sel; - /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) - * - This pass covers the "before" edges of selection islands - */ - prev_sel = false; - for (pt -= 1; i > 0; i--, pt--) { - if (pt->flag & GP_SPOINT_SELECT) { - prev_sel = true; + /* First Pass: Go in forward order, + * expanding selection if previous was selected (pre changes). + * - This pass covers the "after" edges of selection islands + */ + prev_sel = false; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + /* selected point - just set flag for next point */ + prev_sel = true; + } + else { + /* unselected point - expand selection if previous was selected... */ + if (prev_sel) { + pt->flag |= GP_SPOINT_SELECT; + changed = true; + } + prev_sel = false; + } } - else { - /* unselected point - expand selection if previous was selected... */ - if (prev_sel) { - pt->flag |= GP_SPOINT_SELECT; + + /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) + * - This pass covers the "before" edges of selection islands + */ + prev_sel = false; + for (pt -= 1; i > 0; i--, pt--) { + if (pt->flag & GP_SPOINT_SELECT) { + prev_sel = true; + } + else { + /* unselected point - expand selection if previous was selected... */ + if (prev_sel) { + pt->flag |= GP_SPOINT_SELECT; + changed = true; + } + prev_sel = false; } - prev_sel = false; } } } + CTX_DATA_END; } - CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + + WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -762,65 +1080,125 @@ void GPENCIL_OT_select_more(wmOperatorType *ot) static int gpencil_select_less_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; } - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; - bool prev_sel; + bool changed = false; + if (is_curve_edit) { + GP_EDITABLE_STROKES_BEGIN (gp_iter, C, gpl, gps) { + if (gps->editcurve != NULL && gps->flag & GP_STROKE_SELECT) { + bGPDcurve *editcurve = gps->editcurve; + int i; - /* First Pass: Go in forward order, shrinking selection - * if previous was not selected (pre changes). - * - This pass covers the "after" edges of selection islands - */ - prev_sel = false; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - /* shrink if previous wasn't selected */ - if (prev_sel == false) { - pt->flag &= ~GP_SPOINT_SELECT; + bool prev_sel = false; + for (i = 0; i < editcurve->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &editcurve->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + changed = true; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; } - prev_sel = true; } - else { - /* mark previous as being unselected - and hence, is trigger for shrinking */ - prev_sel = false; + + /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) + * - This pass covers the "before" edges of selection islands + */ + prev_sel = false; + for (i = editcurve->tot_curve_points - 1; i > 0; i--) { + bGPDcurve_point *gpc_pt = &editcurve->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + changed = true; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; + } } } + } + GP_EDITABLE_STROKES_END(gp_iter); + } + else { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + bool prev_sel; - /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) - * - This pass covers the "before" edges of selection islands - */ - prev_sel = false; - for (pt -= 1; i > 0; i--, pt--) { - if (pt->flag & GP_SPOINT_SELECT) { - /* shrink if previous wasn't selected */ - if (prev_sel == false) { - pt->flag &= ~GP_SPOINT_SELECT; + /* First Pass: Go in forward order, shrinking selection + * if previous was not selected (pre changes). + * - This pass covers the "after" edges of selection islands + */ + prev_sel = false; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + pt->flag &= ~GP_SPOINT_SELECT; + changed = true; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; } - prev_sel = true; } - else { - /* mark previous as being unselected - and hence, is trigger for shrinking */ - prev_sel = false; + + /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) + * - This pass covers the "before" edges of selection islands + */ + prev_sel = false; + for (pt -= 1; i > 0; i--, pt--) { + if (pt->flag & GP_SPOINT_SELECT) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + pt->flag &= ~GP_SPOINT_SELECT; + changed = true; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; + } } } } + CTX_DATA_END; } - CTX_DATA_END; - /* updates */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); + if (changed) { + /* updates */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - /* copy on write tag is needed, or else no refresh happens */ - DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + /* copy on write tag is needed, or else no refresh happens */ + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + + WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); + } - WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL); return OPERATOR_FINISHED; } @@ -852,7 +1230,7 @@ void GPENCIL_OT_select_less(wmOperatorType *ot) * from gpencil_paint.c #gpencil_stroke_eraser_dostroke(). * It would be great to de-duplicate the logic here sometime, but that can wait. */ -static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd), +static bool gpencil_stroke_do_circle_sel(bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps, GP_SpaceConversion *gsc, @@ -863,7 +1241,8 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd), rcti *rect, const float diff_mat[4][4], const int selectmode, - const float scale) + const float scale, + const bool is_curve_edit) { bGPDspoint *pt = NULL; int x0 = 0, y0 = 0; @@ -906,7 +1285,7 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd), float r_hita[3], r_hitb[3]; bool hit_select = (bool)(pt_active->flag & GP_SPOINT_SELECT); ED_gpencil_select_stroke_segment( - gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); + gpd, gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); } } } @@ -927,10 +1306,111 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd), } } - /* Ensure that stroke selection is in sync with its points. */ - BKE_gpencil_stroke_sync_selection(gps_active); + /* If curve edit mode, generate the curve. */ + if (is_curve_edit && hit && gps_active->editcurve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps_active); + gps_active->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + /* Select all curve points. */ + select_all_curve_points(gps_active, gps_active->editcurve, false); + BKE_gpencil_stroke_geometry_update(gpd, gps_active); + changed = true; + } + + /* Ensure that stroke selection is in sync with its points. */ + BKE_gpencil_stroke_sync_selection(gps_active); + + return changed; +} + +static bool gpencil_do_curve_circle_sel(bContext *C, + bGPDstroke *gps, + bGPDcurve *gpc, + const int mx, + const int my, + const int radius, + const bool select, + rcti *rect, + const float diff_mat[4][4], + const int selectmode) +{ + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + const bool only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); + + bool hit = false; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (bezt->hide == 1) { + continue; + } + + const bool handles_visible = (v3d->overlay.handle_display != CURVE_HANDLE_NONE) && + (!only_selected || BEZT_ISSEL_ANY(bezt)); + + /* if the handles are not visible only check ctrl point (vec[1])*/ + int from = (!handles_visible) ? 1 : 0; + int to = (!handles_visible) ? 2 : 3; + + for (int j = from; j < to; j++) { + float parent_co[3]; + mul_v3_m4v3(parent_co, diff_mat, bezt->vec[j]); + int screen_co[2]; + /* do 2d projection */ + if (ED_view3d_project_int_global( + region, parent_co, screen_co, V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) != + V3D_PROJ_RET_OK) { + continue; + } + + /* view and bounding box test */ + if (ELEM(V2D_IS_CLIPPED, screen_co[0], screen_co[1]) && + !BLI_rcti_isect_pt(rect, screen_co[0], screen_co[1])) { + continue; + } + + /* test inside circle */ + int dist_x = screen_co[0] - mx; + int dist_y = screen_co[1] - my; + int dist = dist_x * dist_x + dist_y * dist_y; + if (dist <= radius * radius) { + hit = true; + /* change selection */ + if (select) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(bezt, j); + } + else { + BEZT_DESEL_IDX(bezt, j); + if (!BEZT_ISSEL_ANY(bezt)) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + } + } + } + } + } + + /* select the entire curve */ + if (hit && (selectmode == GP_SELECTMODE_STROKE)) { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (select) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + } + } + + BKE_gpencil_curve_sync_selection(gps); - return changed; + return hit; } static int gpencil_circle_select_exec(bContext *C, wmOperator *op) @@ -938,6 +1418,7 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) bGPdata *gpd = ED_gpencil_data_get_active(C); ToolSettings *ts = CTX_data_tool_settings(C); Object *ob = CTX_data_active_object(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); int selectmode; if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { @@ -963,12 +1444,6 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) const int my = RNA_int_get(op->ptr, "y"); const int radius = RNA_int_get(op->ptr, "radius"); - GP_SpaceConversion gsc = {NULL}; - /* for bounding rect around circle (for quicky intersection testing) */ - rcti rect = {0}; - - bool changed = false; - /* sanity checks */ if (area == NULL) { BKE_report(op->reports, RPT_ERROR, "No active area"); @@ -978,36 +1453,57 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) const eSelectOp sel_op = ED_select_op_modal(RNA_enum_get(op->ptr, "mode"), WM_gesture_is_modal_first(op->customdata)); const bool select = (sel_op != SEL_OP_SUB); - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - ED_gpencil_select_toggle_all(C, SEL_DESELECT); - changed = true; - } - /* init space conversion stuff */ - gpencil_point_conversion_init(C, &gsc); - - /* rect is rectangle of selection circle */ + bool changed = false; + /* for bounding rect around circle (for quicky intersection testing) */ + rcti rect = {0}; rect.xmin = mx - radius; rect.ymin = my - radius; rect.xmax = mx + radius; rect.ymax = my + radius; - /* find visible strokes, and select if hit */ - GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - changed |= gpencil_stroke_do_circle_sel(gpd, - gpl, - gps, - &gsc, - mx, - my, - radius, - select, - &rect, - gpstroke_iter.diff_mat, - selectmode, - scale); + if (is_curve_edit) { + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + ED_gpencil_select_curve_toggle_all(C, SEL_DESELECT); + changed = true; + } + + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + changed |= gpencil_do_curve_circle_sel( + C, gps, gpc, mx, my, radius, select, &rect, gps_iter.diff_mat, selectmode); + } + GP_EDITABLE_CURVES_END(gps_iter); + } + + if (changed == false) { + GP_SpaceConversion gsc = {NULL}; + /* init space conversion stuff */ + gpencil_point_conversion_init(C, &gsc); + + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + ED_gpencil_select_toggle_all(C, SEL_DESELECT); + changed = true; + } + + /* find visible strokes, and select if hit */ + GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + changed |= gpencil_stroke_do_circle_sel(gpd, + gpl, + gps, + &gsc, + mx, + my, + radius, + select, + &rect, + gpstroke_iter.diff_mat, + selectmode, + scale, + is_curve_edit); + } + GP_EVALUATED_STROKES_END(gpstroke_iter); } - GP_EVALUATED_STROKES_END(gpstroke_iter); /* updates */ if (changed) { @@ -1054,49 +1550,245 @@ void GPENCIL_OT_select_circle(wmOperatorType *ot) * * \{ */ -typedef bool (*GPencilTestFn)(bGPDstroke *gps, - bGPDspoint *pt, - const GP_SpaceConversion *gsc, - const float diff_mat[4][4], - void *user_data); +typedef struct GP_SelectUserData { + int mx, my, radius; + /* Bounding box rect */ + rcti rect; + const int (*lasso_coords)[2]; + int lasso_coords_len; +} GP_SelectUserData; -static int gpencil_generic_select_exec( - bContext *C, wmOperator *op, GPencilTestFn is_inside_fn, rcti box, void *user_data) +typedef bool (*GPencilTestFn)(ARegion *region, + const float diff_mat[4][4], + const float pt[3], + GP_SelectUserData *user_data); + +#if 0 +static bool gpencil_stroke_fill_isect_rect(ARegion *region, + bGPDstroke *gps, + const float diff_mat[4][4], + rcti rect) { - Object *ob = CTX_data_active_object(C); - bGPdata *gpd = ED_gpencil_data_get_active(C); - ToolSettings *ts = CTX_data_tool_settings(C); - ScrArea *area = CTX_wm_area(C); + int min[2] = {-INT_MAX, -INT_MAX}; + int max[2] = {INT_MAX, INT_MAX}; - int selectmode; - if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { - selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); - } - else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { - selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); - } - else { - selectmode = ts->gpencil_selectmode_edit; + int(*points2d)[2] = MEM_callocN(sizeof(int[2]) * gps->totpoints, __func__); + + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + int *pt2d = points2d[i]; + + int screen_co[2]; + gpencil_3d_point_to_screen_space(region, diff_mat, &pt->x, screen_co); + DO_MINMAX2(screen_co, min, max); + + copy_v2_v2_int(pt2d, screen_co); } - const bool strokemode = ((selectmode == GP_SELECTMODE_STROKE) && - ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); - const bool segmentmode = ((selectmode == GP_SELECTMODE_SEGMENT) && - ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); + bool hit = false; + /* check bounding box */ + rcti bb = {min[0], max[0], min[1], max[1]}; + if (BLI_rcti_isect(&rect, &bb, NULL)) { + for (int i = 0; i < gps->tot_triangles; i++) { + bGPDtriangle *tri = &gps->triangles[i]; + int pt1[2], pt2[2], pt3[2]; + int tri_min[2] = {-INT_MAX, -INT_MAX}; + int tri_max[2] = {INT_MAX, INT_MAX}; + + copy_v2_v2_int(pt1, points2d[tri->verts[0]]); + copy_v2_v2_int(pt2, points2d[tri->verts[1]]); + copy_v2_v2_int(pt3, points2d[tri->verts[2]]); + + DO_MINMAX2(pt1, tri_min, tri_max); + DO_MINMAX2(pt2, tri_min, tri_max); + DO_MINMAX2(pt3, tri_min, tri_max); + + rcti tri_bb = {tri_min[0], tri_max[0], tri_min[1], tri_max[1]}; + /* Case 1: triangle is entirely inside box selection */ + /* (XXX: Can this even happen with no point inside the box?) */ + if (BLI_rcti_inside_rcti(&tri_bb, &rect)) { + hit = true; + break; + } - const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); - const float scale = ts->gp_sculpt.isect_threshold; + /* Case 2: rectangle intersects sides of triangle */ + if (BLI_rcti_isect_segment(&rect, pt1, pt2) || BLI_rcti_isect_segment(&rect, pt2, pt3) || + BLI_rcti_isect_segment(&rect, pt3, pt1)) { + hit = true; + break; + } - GP_SpaceConversion gsc = {NULL}; + /* TODO: Case 3: rectangle is inside the triangle */ + } + } + + MEM_freeN(points2d); + return hit; +} +#endif + +static bool gpencil_generic_curve_select(bContext *C, + Object *UNUSED(ob), + GPencilTestFn is_inside_fn, + rcti UNUSED(box), + GP_SelectUserData *user_data, + const bool strokemode, + const eSelectOp sel_op) +{ + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + const bool handle_only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); + const bool handle_all = (v3d->overlay.handle_display == CURVE_HANDLE_ALL); + bool hit = false; bool changed = false; + bool whole = false; - /* sanity checks */ - if (area == NULL) { - BKE_report(op->reports, RPT_ERROR, "No active area"); - return OPERATOR_CANCELLED; + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + bool any_select = false; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (bezt->hide == 1) { + continue; + } + + const bool handles_visible = (handle_all || (handle_only_selected && + (gpc_pt->flag & GP_CURVE_POINT_SELECT))); + + if (handles_visible) { + for (int j = 0; j < 3; j++) { + const bool is_select = BEZT_ISSEL_IDX(bezt, j); + bool is_inside = is_inside_fn(region, gps_iter.diff_mat, bezt->vec[j], user_data); + if (strokemode) { + if (is_inside) { + hit = true; + any_select = true; + break; + } + } + else { + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + if (sel_op_result) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(bezt, j); + any_select = true; + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_IDX(bezt, j); + } + changed = true; + hit = true; + } + else { + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_IDX(bezt, j); + } + } + } + } + } + /* if the handles are not visible only check ctrl point (vec[1])*/ + else { + const bool is_select = bezt->f2; + bool is_inside = is_inside_fn(region, gps_iter.diff_mat, bezt->vec[1], user_data); + if (strokemode) { + if (is_inside) { + hit = true; + any_select = true; + } + } + else { + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + if (sel_op_result) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + bezt->f2 |= SELECT; + any_select = true; + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + bezt->f2 &= ~SELECT; + } + changed = true; + hit = true; + } + else { + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + bezt->f2 &= ~SELECT; + } + } + } + } + } + + /* TODO: Fix selection for filled in curves. */ +#if 0 + if (!hit) { + /* check if we selected the inside of a filled curve */ + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + if ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0) { + continue; + } + + whole = gpencil_stroke_fill_isect_rect(region, gps, gps_iter.diff_mat, box); + } +#endif + /* select the entire curve */ + if (strokemode || whole) { + const int sel_op_result = ED_select_op_action_deselected(sel_op, any_select, hit || whole); + if (sel_op_result != -1) { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (sel_op_result) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + } + + if (sel_op_result) { + gpc->flag |= GP_CURVE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + } + changed = true; + } + } + + BKE_gpencil_curve_sync_selection(gps); } + GP_EDITABLE_CURVES_END(gps_iter); + return changed; +} + +static bool gpencil_generic_stroke_select(bContext *C, + Object *ob, + bGPdata *gpd, + GPencilTestFn is_inside_fn, + rcti box, + GP_SelectUserData *user_data, + const bool strokemode, + const bool segmentmode, + const eSelectOp sel_op, + const float scale, + const bool is_curve_edit) +{ + GP_SpaceConversion gsc = {NULL}; + bool changed = false; /* init space conversion stuff */ gpencil_point_conversion_init(C, &gsc); @@ -1130,7 +1822,7 @@ static int gpencil_generic_select_exec( bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; /* convert point coords to screenspace */ - const bool is_inside = is_inside_fn(gps, pt, &gsc, gpstroke_iter.diff_mat, user_data); + const bool is_inside = is_inside_fn(gsc.region, gpstroke_iter.diff_mat, &pt->x, user_data); if (strokemode == false) { const bool is_select = (pt_active->flag & GP_SPOINT_SELECT) != 0; const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); @@ -1144,7 +1836,7 @@ static int gpencil_generic_select_exec( bool hit_select = (bool)(pt_active->flag & GP_SPOINT_SELECT); float r_hita[3], r_hitb[3]; ED_gpencil_select_stroke_segment( - gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); + gpd, gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); } } } @@ -1191,11 +1883,82 @@ static int gpencil_generic_select_exec( } } + /* If curve edit mode, generate the curve. */ + if (is_curve_edit && (hit || whole) && gps_active->editcurve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps_active); + gps_active->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + /* Select all curve points. */ + select_all_curve_points(gps_active, gps_active->editcurve, false); + BKE_gpencil_stroke_geometry_update(gpd, gps_active); + changed = true; + } + /* Ensure that stroke selection is in sync with its points */ BKE_gpencil_stroke_sync_selection(gps_active); } GP_EVALUATED_STROKES_END(gpstroke_iter); + return changed; +} + +static int gpencil_generic_select_exec(bContext *C, + wmOperator *op, + GPencilTestFn is_inside_fn, + rcti box, + GP_SelectUserData *user_data) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + ToolSettings *ts = CTX_data_tool_settings(C); + ScrArea *area = CTX_wm_area(C); + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + int selectmode; + if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { + selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); + } + else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { + selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + } + else { + selectmode = ts->gpencil_selectmode_edit; + } + + const bool strokemode = ((selectmode == GP_SELECTMODE_STROKE) && + ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); + const bool segmentmode = ((selectmode == GP_SELECTMODE_SEGMENT) && + ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); + + const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); + const float scale = ts->gp_sculpt.isect_threshold; + + bool changed = false; + + /* sanity checks */ + if (area == NULL) { + BKE_report(op->reports, RPT_ERROR, "No active area"); + return OPERATOR_CANCELLED; + } + + if (is_curve_edit) { + changed = gpencil_generic_curve_select( + C, ob, is_inside_fn, box, user_data, strokemode, sel_op); + } + + if (changed == false) { + changed = gpencil_generic_stroke_select(C, + ob, + gpd, + is_inside_fn, + box, + user_data, + strokemode, + segmentmode, + sel_op, + scale, + is_curve_edit); + } + /* if paint mode,delete selected points */ if (GPENCIL_PAINT_MODE(gpd)) { gpencil_delete_selected_point_wrap(C); @@ -1222,27 +1985,21 @@ static int gpencil_generic_select_exec( /** \name Box Select Operator * \{ */ -struct GP_SelectBoxUserData { - rcti rect; -}; - -static bool gpencil_test_box(bGPDstroke *gps, - bGPDspoint *pt, - const GP_SpaceConversion *gsc, +static bool gpencil_test_box(ARegion *region, const float diff_mat[4][4], - void *user_data) + const float pt[3], + GP_SelectUserData *user_data) { - const struct GP_SelectBoxUserData *data = user_data; - bGPDspoint pt2; - int x0, y0; - gpencil_point_to_parent_space(pt, diff_mat, &pt2); - gpencil_point_to_xy(gsc, gps, &pt2, &x0, &y0); - return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&data->rect, x0, y0)); + int co[2] = {0}; + if (gpencil_3d_point_to_screen_space(region, diff_mat, pt, co)) { + return BLI_rcti_isect_pt(&user_data->rect, co[0], co[1]); + } + return false; } static int gpencil_box_select_exec(bContext *C, wmOperator *op) { - struct GP_SelectBoxUserData data = {0}; + GP_SelectUserData data = {0}; WM_operator_properties_border_to_rcti(op, &data.rect); rcti rect = data.rect; return gpencil_generic_select_exec(C, op, gpencil_test_box, rect, &data); @@ -1277,45 +2034,38 @@ void GPENCIL_OT_select_box(wmOperatorType *ot) /** \name Lasso Select Operator * \{ */ -struct GP_SelectLassoUserData { - rcti rect; - const int (*mcoords)[2]; - int mcoords_len; -}; - -static bool gpencil_test_lasso(bGPDstroke *gps, - bGPDspoint *pt, - const GP_SpaceConversion *gsc, +static bool gpencil_test_lasso(ARegion *region, const float diff_mat[4][4], - void *user_data) + const float pt[3], + GP_SelectUserData *user_data) { - const struct GP_SelectLassoUserData *data = user_data; - bGPDspoint pt2; - int x0, y0; - gpencil_point_to_parent_space(pt, diff_mat, &pt2); - gpencil_point_to_xy(gsc, gps, &pt2, &x0, &y0); - /* test if in lasso boundbox + within the lasso noose */ - return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&data->rect, x0, y0) && - BLI_lasso_is_point_inside(data->mcoords, data->mcoords_len, x0, y0, INT_MAX)); + int co[2] = {0}; + if (gpencil_3d_point_to_screen_space(region, diff_mat, pt, co)) { + /* test if in lasso boundbox + within the lasso noose */ + return (BLI_rcti_isect_pt(&user_data->rect, co[0], co[1]) && + BLI_lasso_is_point_inside( + user_data->lasso_coords, user_data->lasso_coords_len, co[0], co[1], INT_MAX)); + } + return false; } static int gpencil_lasso_select_exec(bContext *C, wmOperator *op) { - struct GP_SelectLassoUserData data = {0}; - data.mcoords = WM_gesture_lasso_path_to_array(C, op, &data.mcoords_len); + struct GP_SelectUserData data = {0}; + data.lasso_coords = WM_gesture_lasso_path_to_array(C, op, &data.lasso_coords_len); /* Sanity check. */ - if (data.mcoords == NULL) { + if (data.lasso_coords == NULL) { return OPERATOR_PASS_THROUGH; } /* Compute boundbox of lasso (for faster testing later). */ - BLI_lasso_boundbox(&data.rect, data.mcoords, data.mcoords_len); + BLI_lasso_boundbox(&data.rect, data.lasso_coords, data.lasso_coords_len); rcti rect = data.rect; int ret = gpencil_generic_select_exec(C, op, gpencil_test_lasso, rect, &data); - MEM_freeN((void *)data.mcoords); + MEM_freeN((void *)data.lasso_coords); return ret; } @@ -1346,25 +2096,56 @@ void GPENCIL_OT_select_lasso(wmOperatorType *ot) /** \name Mouse Pick Select Operator * \{ */ -/* helper to deselect all selected strokes/points */ -static void deselect_all_selected(bContext *C) +static void gpencil_select_curve_point(bContext *C, + const int mval[2], + const int radius_squared, + bGPDlayer **r_gpl, + bGPDstroke **r_gps, + bGPDcurve **r_gpc, + bGPDcurve_point **r_pt, + char *handle) { - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - /* deselect stroke and its points if selected */ - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + const bool only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); - /* deselect points */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + int hit_distance = radius_squared; + + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + + if (bezt->hide == 1) { + continue; } - /* deselect stroke itself too */ - gps->flag &= ~GP_STROKE_SELECT; + const bool handles_visible = (v3d->overlay.handle_display != CURVE_HANDLE_NONE) && + (!only_selected || BEZT_ISSEL_ANY(bezt)); + + /* if the handles are not visible only check ctrl point (vec[1])*/ + int from = (!handles_visible) ? 1 : 0; + int to = (!handles_visible) ? 2 : 3; + + for (int j = from; j < to; j++) { + int screen_co[2]; + if (gpencil_3d_point_to_screen_space(region, gps_iter.diff_mat, bezt->vec[j], screen_co)) { + const int pt_distance = len_manhattan_v2v2_int(mval, screen_co); + + if (pt_distance <= radius_squared && pt_distance < hit_distance) { + *r_gpl = gpl; + *r_gps = gps; + *r_gpc = gpc; + *r_pt = gpc_pt; + *handle = j; + hit_distance = pt_distance; + } + } + } } } - CTX_DATA_END; + GP_EDITABLE_CURVES_END(gps_iter); } static int gpencil_select_exec(bContext *C, wmOperator *op) @@ -1374,6 +2155,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) bGPdata *gpd = ED_gpencil_data_get_active(C); ToolSettings *ts = CTX_data_tool_settings(C); const float scale = ts->gp_sculpt.isect_threshold; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); /* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */ const float radius = 0.4f * U.widget_unit; @@ -1387,12 +2169,17 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all") && !use_shift_extend; int mval[2] = {0}; + /* get mouse location */ + RNA_int_get_array(op->ptr, "location", mval); GP_SpaceConversion gsc = {NULL}; bGPDlayer *hit_layer = NULL; bGPDstroke *hit_stroke = NULL; bGPDspoint *hit_point = NULL; + bGPDcurve *hit_curve = NULL; + bGPDcurve_point *hit_curve_point = NULL; + char hit_curve_handle = 0; int hit_distance = radius_squared; /* sanity checks */ @@ -1414,47 +2201,57 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) whole = (bool)(ts->gpencil_selectmode_edit == GP_SELECTMODE_STROKE); } - /* init space conversion stuff */ - gpencil_point_conversion_init(C, &gsc); + if (is_curve_edit) { + gpencil_select_curve_point(C, + mval, + radius_squared, + &hit_layer, + &hit_stroke, + &hit_curve, + &hit_curve_point, + &hit_curve_handle); + } - /* get mouse location */ - RNA_int_get_array(op->ptr, "location", mval); + if (hit_curve == NULL) { + /* init space conversion stuff */ + gpencil_point_conversion_init(C, &gsc); - /* First Pass: Find stroke point which gets hit */ - GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; - bGPDspoint *pt; - int i; + /* First Pass: Find stroke point which gets hit */ + GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + bGPDspoint *pt; + int i; - /* firstly, check for hit-point */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - int xy[2]; - - bGPDspoint pt2; - gpencil_point_to_parent_space(pt, gpstroke_iter.diff_mat, &pt2); - gpencil_point_to_xy(&gsc, gps, &pt2, &xy[0], &xy[1]); - - /* do boundbox check first */ - if (!ELEM(V2D_IS_CLIPPED, xy[0], xy[1])) { - const int pt_distance = len_manhattan_v2v2_int(mval, xy); - - /* check if point is inside */ - if (pt_distance <= radius_squared) { - /* only use this point if it is a better match than the current hit - T44685 */ - if (pt_distance < hit_distance) { - hit_layer = gpl; - hit_stroke = gps_active; - hit_point = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; - hit_distance = pt_distance; + /* firstly, check for hit-point */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + int xy[2]; + + bGPDspoint pt2; + gpencil_point_to_parent_space(pt, gpstroke_iter.diff_mat, &pt2); + gpencil_point_to_xy(&gsc, gps, &pt2, &xy[0], &xy[1]); + + /* do boundbox check first */ + if (!ELEM(V2D_IS_CLIPPED, xy[0], xy[1])) { + const int pt_distance = len_manhattan_v2v2_int(mval, xy); + + /* check if point is inside */ + if (pt_distance <= radius_squared) { + /* only use this point if it is a better match than the current hit - T44685 */ + if (pt_distance < hit_distance) { + hit_layer = gpl; + hit_stroke = gps_active; + hit_point = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + hit_distance = pt_distance; + } } } } } + GP_EVALUATED_STROKES_END(gpstroke_iter); } - GP_EVALUATED_STROKES_END(gpstroke_iter); /* Abort if nothing hit... */ - if (ELEM(NULL, hit_stroke, hit_point)) { + if (!hit_curve && !hit_curve_point && !hit_point && !hit_stroke) { if (deselect_all) { /* since left mouse select change, deselect all if click outside any hit */ @@ -1472,9 +2269,26 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + /* select all handles if the click was on the curve but not on a handle */ + if (is_curve_edit && hit_point != NULL) { + whole = true; + hit_curve = hit_stroke->editcurve; + } + /* adjust selection behavior - for toggle option */ if (toggle) { - deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0; + if (hit_curve_point != NULL) { + BezTriple *bezt = &hit_curve_point->bezt; + if (bezt->f1 & SELECT && hit_curve_handle == 0) + deselect = true; + if (bezt->f2 & SELECT && hit_curve_handle == 1) + deselect = true; + if (bezt->f3 & SELECT && hit_curve_handle == 2) + deselect = true; + } + else { + deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0; + } } /* If not extending selection, deselect everything else */ @@ -1484,64 +2298,94 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) /* Perform selection operations... */ if (whole) { - bGPDspoint *pt; - int i; + /* Generate editcurve if it does not exist */ + if (is_curve_edit && hit_curve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, hit_layer, hit_stroke); + hit_stroke->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, hit_stroke); + hit_curve = hit_stroke->editcurve; + } + /* select all curve points */ + if (hit_curve != NULL) { + select_all_curve_points(hit_stroke, hit_curve, deselect); + } + else { + bGPDspoint *pt; + int i; + + /* entire stroke's points */ + for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) { + if (deselect == false) { + pt->flag |= GP_SPOINT_SELECT; + } + else { + pt->flag &= ~GP_SPOINT_SELECT; + } + } - /* entire stroke's points */ - for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) { + /* stroke too... */ if (deselect == false) { - pt->flag |= GP_SPOINT_SELECT; + hit_stroke->flag |= GP_STROKE_SELECT; } else { - pt->flag &= ~GP_SPOINT_SELECT; + hit_stroke->flag &= ~GP_STROKE_SELECT; } } - - /* stroke too... */ - if (deselect == false) { - hit_stroke->flag |= GP_STROKE_SELECT; - } - else { - hit_stroke->flag &= ~GP_STROKE_SELECT; - } } else { /* just the point (and the stroke) */ if (deselect == false) { - /* we're adding selection, so selection must be true */ - hit_point->flag |= GP_SPOINT_SELECT; - hit_stroke->flag |= GP_STROKE_SELECT; - - /* expand selection to segment */ - int selectmode; - if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { - selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); - } - else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { - selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + if (hit_curve_point != NULL) { + hit_curve_point->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(&hit_curve_point->bezt, hit_curve_handle); + hit_curve->flag |= GP_CURVE_SELECT; + hit_stroke->flag |= GP_STROKE_SELECT; } else { - selectmode = ts->gpencil_selectmode_edit; - } + /* we're adding selection, so selection must be true */ + hit_point->flag |= GP_SPOINT_SELECT; + hit_stroke->flag |= GP_STROKE_SELECT; + + /* expand selection to segment */ + int selectmode; + if (ob && ob->mode == OB_MODE_SCULPT_GPENCIL) { + selectmode = gpencil_select_mode_from_sculpt(ts->gpencil_selectmode_sculpt); + } + else if (ob && ob->mode == OB_MODE_VERTEX_GPENCIL) { + selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); + } + else { + selectmode = ts->gpencil_selectmode_edit; + } - if (selectmode == GP_SELECTMODE_SEGMENT) { - float r_hita[3], r_hitb[3]; - bool hit_select = (bool)(hit_point->flag & GP_SPOINT_SELECT); - ED_gpencil_select_stroke_segment( - hit_layer, hit_stroke, hit_point, hit_select, false, scale, r_hita, r_hitb); + if (selectmode == GP_SELECTMODE_SEGMENT) { + float r_hita[3], r_hitb[3]; + bool hit_select = (bool)(hit_point->flag & GP_SPOINT_SELECT); + ED_gpencil_select_stroke_segment( + gpd, hit_layer, hit_stroke, hit_point, hit_select, false, scale, r_hita, r_hitb); + } } } else { - /* deselect point */ - hit_point->flag &= ~GP_SPOINT_SELECT; + if (hit_curve_point != NULL) { + BEZT_DESEL_IDX(&hit_curve_point->bezt, hit_curve_handle); + if (!BEZT_ISSEL_ANY(&hit_curve_point->bezt)) { + hit_curve_point->flag &= ~GP_CURVE_POINT_SELECT; + } + BKE_gpencil_curve_sync_selection(hit_stroke); + } + else { + /* deselect point */ + hit_point->flag &= ~GP_SPOINT_SELECT; - /* ensure that stroke is selected correctly */ - BKE_gpencil_stroke_sync_selection(hit_stroke); + /* ensure that stroke is selected correctly */ + BKE_gpencil_stroke_sync_selection(hit_stroke); + } } } /* updates */ - if (hit_point != NULL) { + if (hit_curve_point != NULL || hit_point != NULL) { DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); /* copy on write tag is needed, or else no refresh happens */ @@ -1679,13 +2523,13 @@ static int gpencil_select_vertex_color_exec(bContext *C, wmOperator *op) { ToolSettings *ts = CTX_data_tool_settings(C); Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); const float threshold = RNA_int_get(op->ptr, "threshold"); const int selectmode = gpencil_select_mode_from_vertex(ts->gpencil_selectmode_vertex); - bGPdata *gpd = (bGPdata *)ob->data; const float range = pow(10, 5 - threshold); - bool done = false; + bool changed = false; /* Create a hash table with all selected colors. */ GHash *hue_table = BLI_ghash_int_new(__func__); @@ -1722,7 +2566,6 @@ static int gpencil_select_vertex_color_exec(bContext *C, wmOperator *op) if (gps_selected) { gps->flag |= GP_STROKE_SELECT; - done = true; /* Extend stroke selection. */ if (selectmode == GP_SELECTMODE_STROKE) { @@ -1736,7 +2579,7 @@ static int gpencil_select_vertex_color_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - if (done) { + if (changed) { /* updates */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_trace_utils.c b/source/blender/editors/gpencil/gpencil_trace_utils.c index 544cb4fef1e..ada777d43f3 100644 --- a/source/blender/editors/gpencil/gpencil_trace_utils.c +++ b/source/blender/editors/gpencil/gpencil_trace_utils.c @@ -352,13 +352,14 @@ void ED_gpencil_trace_data_to_strokes(Main *bmain, * long stroke. Here the length is checked and removed if the length is too big. */ float length = BKE_gpencil_stroke_length(gps, true); if (length <= MAX_LENGTH) { + bGPdata *gpd = ob->data; if (sample > 0.0f) { /* Resample stroke. Don't need to call to BKE_gpencil_stroke_geometry_update() because * the sample function already call that. */ - BKE_gpencil_stroke_sample(gps, sample, false); + BKE_gpencil_stroke_sample(gpd, gps, sample, false); } else { - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } else { diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 0fdd70c55bc..a1c2ff12866 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -55,6 +55,7 @@ #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" @@ -1144,7 +1145,7 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, bGPDstroke *gps_active = gps; /* if duplicate, deselect all points. */ if (keep_original) { - gps_active = BKE_gpencil_stroke_duplicate(gps, true); + gps_active = BKE_gpencil_stroke_duplicate(gps, true, true); gps_active->flag &= ~GP_STROKE_SELECT; for (i = 0, pt = gps_active->points; i < gps_active->totpoints; i++, pt++) { pt->flag &= ~GP_SPOINT_SELECT; @@ -1320,10 +1321,11 @@ void ED_gpencil_project_point_to_plane(const Scene *scene, /** * Subdivide a stroke once, by adding a point half way between each pair of existing points + * \param gpd: Datablock * \param gps: Stroke data * \param subdivide: Number of times to subdivide */ -void gpencil_subdivide_stroke(bGPDstroke *gps, const int subdivide) +void gpencil_subdivide_stroke(bGPdata *gpd, bGPDstroke *gps, const int subdivide) { bGPDspoint *temp_points; int totnewpoints, oldtotpoints; @@ -1413,7 +1415,7 @@ void gpencil_subdivide_stroke(bGPDstroke *gps, const int subdivide) MEM_SAFE_FREE(temp_points); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } /* Reset parent matrix for all layers. */ @@ -2243,8 +2245,12 @@ static void gpencil_copy_points( } } -static void gpencil_insert_point( - bGPDstroke *gps, bGPDspoint *a_pt, bGPDspoint *b_pt, const float co_a[3], const float co_b[3]) +static void gpencil_insert_point(bGPdata *gpd, + bGPDstroke *gps, + bGPDspoint *a_pt, + bGPDspoint *b_pt, + const float co_a[3], + float co_b[3]) { bGPDspoint *temp_points; int totnewpoints, oldtotpoints; @@ -2303,8 +2309,8 @@ static void gpencil_insert_point( i2++; } - /* Calculate geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps); MEM_SAFE_FREE(temp_points); } @@ -2328,7 +2334,8 @@ static float gpencil_calc_factor(const float p2d_a1[2], } /* extend selection to stroke intersections */ -int ED_gpencil_select_stroke_segment(bGPDlayer *gpl, +int ED_gpencil_select_stroke_segment(bGPdata *gpd, + bGPDlayer *gpl, bGPDstroke *gps, bGPDspoint *pt, bool select, @@ -2483,7 +2490,7 @@ int ED_gpencil_select_stroke_segment(bGPDlayer *gpl, /* insert new point in the collision points */ if (insert) { - gpencil_insert_point(gps, hit_pointa, hit_pointb, r_hita, r_hitb); + gpencil_insert_point(gpd, gps, hit_pointa, hit_pointb, r_hita, r_hitb); } /* free memory */ @@ -2611,6 +2618,82 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) } } +void ED_gpencil_select_curve_toggle_all(bContext *C, int action) +{ + /* if toggle, check if we need to select or deselect */ + if (action == SEL_TOGGLE) { + action = SEL_SELECT; + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (gpc->flag & GP_CURVE_SELECT) { + action = SEL_DESELECT; + } + } + GP_EDITABLE_CURVES_END(gps_iter); + } + + if (action == SEL_DESELECT) { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, 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; + gps->flag &= ~GP_STROKE_SELECT; + } + GP_EDITABLE_CURVES_END(gps_iter); + } + else { + GP_EDITABLE_STROKES_BEGIN(gps_iter, C, gpl, gps){ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + bool selected = false; + + /* Make sure stroke has an editcurve */ + if (gps->editcurve == NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + switch (action) { + case SEL_SELECT: + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + break; + case SEL_INVERT: + gpc_pt->flag ^= GP_CURVE_POINT_SELECT; + BEZT_SEL_INVERT(bezt); + break; + default: + break; + } + + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + selected = true; + } + } + + if (selected) { + gpc->flag |= GP_CURVE_SELECT; + gps->flag |= GP_STROKE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + gps->flag &= ~GP_STROKE_SELECT; + } + } + GP_EDITABLE_STROKES_END(gps_iter); + } +} + /** * Ensure the #tGPspoint buffer (while drawing stroke) * size is enough to save all points of the stroke. diff --git a/source/blender/editors/gpencil/gpencil_uv.c b/source/blender/editors/gpencil/gpencil_uv.c index 3bd2c3e6be6..1d1bc72aaec 100644 --- a/source/blender/editors/gpencil/gpencil_uv.c +++ b/source/blender/editors/gpencil/gpencil_uv.c @@ -273,7 +273,7 @@ static bool gpencil_uv_transform_calc(bContext *C, wmOperator *op) changed = true; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); i++; } } @@ -291,7 +291,7 @@ static bool gpencil_uv_transform_calc(bContext *C, wmOperator *op) gps->uv_rotation = opdata->array_rot[i] - uv_rotation; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); i++; } } @@ -316,7 +316,7 @@ static bool gpencil_uv_transform_calc(bContext *C, wmOperator *op) if (gps->flag & GP_STROKE_SELECT) { gps->uv_scale = opdata->array_scale[i] + scale; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); i++; } } @@ -512,7 +512,7 @@ static int gpencil_reset_transform_fill_exec(bContext *C, wmOperator *op) gps->uv_scale = 1.0f; } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); changed = true; } } diff --git a/source/blender/editors/gpencil/gpencil_vertex_paint.c b/source/blender/editors/gpencil/gpencil_vertex_paint.c index c6ee30ad6e3..a4dc677f0dc 100644 --- a/source/blender/editors/gpencil/gpencil_vertex_paint.c +++ b/source/blender/editors/gpencil/gpencil_vertex_paint.c @@ -819,8 +819,9 @@ static void gpencil_save_selected_point(tGP_BrushVertexpaintData *gso, gso->pbuffer_used++; } -/* Select points in this stroke and add to an array to be used later. */ -static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, +/* Select points in this stroke and add to an array to be used later. + * Returns true if any point was hit and got saved */ +static bool gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, bGPDstroke *gps, const char tool, const float diff_mat[4][4]) @@ -841,9 +842,11 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, int index; bool include_last = false; + bool saved = false; + /* Check if the stroke collide with brush. */ if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, diff_mat)) { - return; + return false; } if (gps->totpoints == 1) { @@ -862,6 +865,7 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, /* apply operation to this point */ if (pt_active != NULL) { gpencil_save_selected_point(gso, gps_active, 0, pc1); + saved = true; } } } @@ -913,6 +917,7 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, } hit = true; gpencil_save_selected_point(gso, gps_active, index, pc1); + saved = true; } /* Only do the second point if this is the last segment, @@ -931,6 +936,7 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, hit = true; gpencil_save_selected_point(gso, gps_active, index, pc2); include_last = false; + saved = true; } } else { @@ -949,8 +955,8 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, if (pt_active != NULL) { hit = true; gpencil_save_selected_point(gso, gps_active, index, pc1); - include_last = false; + saved = true; } } } @@ -970,10 +976,13 @@ static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, for (int repeat = 0; repeat < 50; repeat++) { gpencil_save_selected_point(gso, gps_active, -1, NULL); } + saved = true; } } } } + + return saved; } /* Apply vertex paint brushes to strokes in the given frame. */ @@ -1008,7 +1017,13 @@ static bool gpencil_vertexpaint_brush_do_frame(bContext *C, } /* Check points below the brush. */ - gpencil_vertexpaint_select_stroke(gso, gps, tool, diff_mat); + bool hit = gpencil_vertexpaint_select_stroke(gso, gps, tool, diff_mat); + + /* If stroke was hit and has an editcurve the curve needs an update. */ + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + if (gps_active->editcurve != NULL && hit) { + gps_active->editcurve->flag |= GP_CURVE_NEEDS_STROKE_UPDATE; + } } /* For Average tool, need calculate the average resulting color from all colors diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 48d323c5d57..17aa407bd76 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -314,7 +314,8 @@ void ED_gpencil_update_color_uv(struct Main *bmain, struct Material *mat); * 2 - Hit in point B * 3 - Hit in point A and B */ -int ED_gpencil_select_stroke_segment(struct bGPDlayer *gpl, +int ED_gpencil_select_stroke_segment(struct bGPdata *gpd, + struct bGPDlayer *gpl, struct bGPDstroke *gps, struct bGPDspoint *pt, bool select, @@ -324,6 +325,7 @@ int ED_gpencil_select_stroke_segment(struct bGPDlayer *gpl, float r_hitb[3]); void ED_gpencil_select_toggle_all(struct bContext *C, int action); +void ED_gpencil_select_curve_toggle_all(struct bContext *C, int action); /* Ensure stroke sbuffer size is enough */ struct tGPspoint *ED_gpencil_sbuffer_ensure(struct tGPspoint *buffer_array, diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index ce049cfcaac..690d6c8055a 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1738,6 +1738,10 @@ static void ed_default_handlers( wmKeyMap *keymap_general = WM_keymap_ensure(wm->defaultconf, "Grease Pencil", 0, 0); WM_event_add_keymap_handler(handlers, keymap_general); + wmKeyMap *keymap_curve_edit = WM_keymap_ensure( + wm->defaultconf, "Grease Pencil Stroke Curve Edit Mode", 0, 0); + WM_event_add_keymap_handler(handlers, keymap_curve_edit); + wmKeyMap *keymap_edit = WM_keymap_ensure( wm->defaultconf, "Grease Pencil Stroke Edit Mode", 0, 0); WM_event_add_keymap_handler(handlers, keymap_edit); diff --git a/source/blender/editors/transform/transform_convert_gpencil.c b/source/blender/editors/transform/transform_convert_gpencil.c index 84885dd8c49..0a742ec4470 100644 --- a/source/blender/editors/transform/transform_convert_gpencil.c +++ b/source/blender/editors/transform/transform_convert_gpencil.c @@ -31,7 +31,9 @@ #include "BKE_colortools.h" #include "BKE_context.h" +#include "BKE_curve.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "ED_gpencil.h" @@ -63,33 +65,351 @@ static void createTransGPencil_center_get(bGPDstroke *gps, float r_center[3]) } } -void createTransGPencil(bContext *C, TransInfo *t) +static short get_bezt_sel_triple_flag(BezTriple *bezt, const bool handles_visible) { - if (t->data_container_len == 0) { +#define SEL_F1 (1 << 0) +#define SEL_F2 (1 << 1) +#define SEL_F3 (1 << 2) +#define SEL_ALL ((1 << 0) | (1 << 1) | (1 << 2)) + + short flag = 0; + + if (handles_visible) { + flag = ((bezt->f1 & SELECT) ? SEL_F1 : 0) | ((bezt->f2 & SELECT) ? SEL_F2 : 0) | + ((bezt->f3 & SELECT) ? SEL_F3 : 0); + } + else { + if (bezt->f2 & SELECT) { + flag = SEL_ALL; + } + } + + /* Special case for auto & aligned handles */ + if (flag != SEL_ALL && flag & SEL_F2) { + if (ELEM(bezt->h1, HD_AUTO, HD_ALIGN) && ELEM(bezt->h2, HD_AUTO, HD_ALIGN)) { + flag = SEL_ALL; + } + } + +#undef SEL_F1 +#undef SEL_F2 +#undef SEL_F3 + return flag; +} + +static void createTransGPencil_curves(bContext *C, + TransInfo *t, + Depsgraph *depsgraph, + ToolSettings *ts, + Object *obact, + bGPdata *gpd, + const int cfra_scene, + const bool is_multiedit, + const bool use_multiframe_falloff, + const bool is_prop_edit, + const bool is_prop_edit_connected, + const bool is_scale_thickness) +{ +#define SEL_F1 (1 << 0) +#define SEL_F2 (1 << 1) +#define SEL_F3 (1 << 2) + + View3D *v3d = t->view; + const bool handle_only_selected_visible = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); + const bool handle_all_visible = (v3d->overlay.handle_display == CURVE_HANDLE_ALL); + + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + tc->data_len = 0; + + /* Number of selected curve points */ + uint32_t tot_curve_points = 0, tot_sel_curve_points = 0, tot_points = 0, tot_sel_points = 0; + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* Only editable and visible layers are considered. */ + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + 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 strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* Check if the color is editable. */ + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { + continue; + } + /* Check if stroke has an editcurve */ + if (gps->editcurve == NULL) { + continue; + } + + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (bezt->hide) { + continue; + } + + const bool handles_visible = (handle_all_visible || + (handle_only_selected_visible && + (gpc_pt->flag & GP_CURVE_POINT_SELECT))); + + const short sel_flag = get_bezt_sel_triple_flag(bezt, handles_visible); + if (sel_flag & (SEL_F1 | SEL_F2 | SEL_F3)) { + if (sel_flag & SEL_F1) { + tot_sel_points++; + } + if (sel_flag & SEL_F2) { + tot_sel_points++; + } + if (sel_flag & SEL_F3) { + tot_sel_points++; + } + tot_sel_curve_points++; + } + + if (is_prop_edit) { + tot_points += 3; + tot_curve_points++; + } + } + } + } + + /* If not multiedit out of loop. */ + if (!is_multiedit) { + break; + } + } + } + } + + if (((is_prop_edit && !is_prop_edit_connected) ? tot_curve_points : tot_sel_points) == 0) { + tc->data_len = 0; return; } - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - bGPdata *gpd = ED_gpencil_data_get_active(C); - ToolSettings *ts = CTX_data_tool_settings(C); + int data_len_pt = 0; - bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); - bool use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0; + if (is_prop_edit) { + tc->data_len = tot_points; + data_len_pt = tot_curve_points; + } + else { + tc->data_len = tot_sel_points; + data_len_pt = tot_sel_curve_points; + } - Object *obact = CTX_data_active_object(C); - TransData *td = NULL; - float mtx[3][3], smtx[3][3]; + if (tc->data_len == 0) { + return; + } - const Scene *scene = CTX_data_scene(C); - const int cfra_scene = CFRA; + transform_around_single_fallback_ex(t, data_len_pt); - const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; - const bool is_prop_edit_connected = (t->flag & T_PROP_CONNECTED) != 0; - const bool is_scale_thickness = ((t->mode == TFM_GPENCIL_SHRINKFATTEN) || - (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_SCALE_THICKNESS)); + tc->data = MEM_callocN(tc->data_len * sizeof(TransData), __func__); + TransData *td = tc->data; - TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + const bool use_around_origins_for_handles_test = ((t->around == V3D_AROUND_LOCAL_ORIGINS) && + transform_mode_use_local_origins(t)); + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* Only editable and visible layers are considered. */ + if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { + const int cfra = (gpl->flag & GP_LAYER_FRAMELOCK) ? gpl->actframe->framenum : cfra_scene; + bGPDframe *gpf = gpl->actframe; + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; + float diff_mat[4][4], mtx[3][3]; + float smtx[3][3]; + + /* Init multiframe falloff options. */ + int f_init = 0; + int f_end = 0; + + if (use_multiframe_falloff) { + BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end); + } + + if ((gpf->framenum != cfra) && (!is_multiedit)) { + gpf = BKE_gpencil_frame_addcopy(gpl, cfra); + /* in some weird situations (framelock enabled) return NULL */ + if (gpf == NULL) { + continue; + } + if (!is_multiedit) { + init_gpf = gpf; + } + } + + /* Calculate difference matrix. */ + BKE_gpencil_parent_matrix_get(depsgraph, obact, gpl, diff_mat); + copy_m3_m4(mtx, diff_mat); + pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); + + for (gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + /* If multi-frame and falloff, recalculate and save value. */ + float falloff = 1.0f; /* by default no falloff */ + if ((is_multiedit) && (use_multiframe_falloff)) { + /* Falloff depends on distance to active frame + * (relative to the overall frame range). */ + falloff = BKE_gpencil_multiframe_falloff_calc( + gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff); + } + + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* Check if the color is editable. */ + if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { + continue; + } + /* Check if stroke has an editcurve */ + if (gps->editcurve == NULL) { + continue; + } + TransData *head, *tail; + head = tail = td; + + gps->runtime.multi_frame_falloff = falloff; + bool need_handle_recalc = false; + + bGPDcurve *gpc = gps->editcurve; + const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (bezt->hide) { + continue; + } + + TransDataCurveHandleFlags *hdata = NULL; + bool bezt_use = false; + const bool handles_visible = (handle_all_visible || + (handle_only_selected_visible && + (gpc_pt->flag & GP_CURVE_POINT_SELECT))); + const short sel_flag = get_bezt_sel_triple_flag(bezt, handles_visible); + /* Iterate over bezier triple */ + for (int j = 0; j < 3; j++) { + bool is_ctrl_point = (j == 1); + bool sel = sel_flag & (1 << j); + + if (is_prop_edit || sel) { + copy_v3_v3(td->iloc, bezt->vec[j]); + td->loc = bezt->vec[j]; + bool rotate_around_ctrl = !handles_visible || + (t->around == V3D_AROUND_LOCAL_ORIGINS) || + (bezt->f2 & SELECT); + copy_v3_v3(td->center, bezt->vec[rotate_around_ctrl ? 1 : j]); + + if (!handles_visible || is_ctrl_point) { + if (bezt->f2 & SELECT) { + td->flag = TD_SELECTED; + } + else { + td->flag = 0; + } + } + else if (handles_visible) { + if (BEZT_ISSEL_IDX(bezt, j)) { + td->flag = TD_SELECTED; + } + else { + td->flag = 0; + } + } + + td->ext = NULL; + if (is_ctrl_point) { + if (t->mode != TFM_MIRROR) { + if (t->mode != TFM_GPENCIL_OPACITY) { + if (is_scale_thickness) { + td->val = &(gpc_pt->pressure); + td->ival = gpc_pt->pressure; + } + } + else { + td->val = &(gpc_pt->strength); + td->ival = gpc_pt->strength; + } + } + } + else { + td->val = NULL; + } + + if (hdata == NULL) { + if (is_ctrl_point && ((sel_flag & SEL_F1 & SEL_F3) == 0)) { + hdata = initTransDataCurveHandles(td, bezt); + } + else if (!is_ctrl_point) { + hdata = initTransDataCurveHandles(td, bezt); + } + } + + td->extra = gps; + td->ob = obact; + + copy_m3_m3(td->smtx, smtx); + copy_m3_m3(td->mtx, mtx); + copy_m3_m3(td->axismtx, mtx); + + td++; + tail++; + } + + bezt_use |= sel; + } + + /* Update the handle types so transformation is possible */ + if (bezt_use && !ELEM(t->mode, TFM_GPENCIL_OPACITY, TFM_GPENCIL_SHRINKFATTEN)) { + BKE_nurb_bezt_handle_test( + bezt, SELECT, handles_visible, use_around_origins_for_handles_test); + need_handle_recalc = true; + } + } + if (is_prop_edit && (head != tail)) { + calc_distanceCurveVerts(head, tail - 1, is_cyclic); + } + + if (need_handle_recalc) { + BKE_gpencil_editcurve_recalculate_handles(gps); + } + } + } + + /* If not multiedit out of loop. */ + if (!is_multiedit) { + break; + } + } + } + } +#undef SEL_F1 +#undef SEL_F2 +#undef SEL_F3 +} + +static void createTransGPencil_strokes(bContext *C, + TransInfo *t, + Depsgraph *depsgraph, + ToolSettings *ts, + Object *obact, + bGPdata *gpd, + const int cfra_scene, + const bool is_multiedit, + const bool use_multiframe_falloff, + const bool is_prop_edit, + const bool is_prop_edit_connected, + const bool is_scale_thickness) +{ + TransData *td = NULL; + float mtx[3][3], smtx[3][3]; + + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); /* == Grease Pencil Strokes to Transform Data == * Grease Pencil stroke points can be a mixture of 2D (screen-space), * or 3D coordinates. However, they're always saved as 3D points. @@ -98,15 +418,6 @@ void createTransGPencil(bContext *C, TransInfo *t) */ tc->data_len = 0; - if (gpd == NULL) { - return; - } - - /* initialize falloff curve */ - if (is_multiedit) { - BKE_curvemapping_init(ts->gp_sculpt.cur_falloff); - } - /* First Pass: Count the number of data-points required for the strokes, * (and additional info about the configuration - e.g. 2D/3D?). */ @@ -368,6 +679,71 @@ void createTransGPencil(bContext *C, TransInfo *t) } } +void createTransGPencil(bContext *C, TransInfo *t) +{ + if (t->data_container_len == 0) { + return; + } + + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + const Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + Object *obact = CTX_data_active_object(C); + bGPdata *gpd = obact->data; + BLI_assert(gpd != NULL); + + const int cfra_scene = CFRA; + + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != + 0; + + const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; + const bool is_prop_edit_connected = (t->flag & T_PROP_CONNECTED) != 0; + const bool is_scale_thickness = ((t->mode == TFM_GPENCIL_SHRINKFATTEN) || + (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_SCALE_THICKNESS)); + + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + + /* initialize falloff curve */ + if (is_multiedit) { + BKE_curvemapping_init(ts->gp_sculpt.cur_falloff); + } + + if (gpd == NULL) { + return; + } + + if (is_curve_edit) { + createTransGPencil_curves(C, + t, + depsgraph, + ts, + obact, + gpd, + cfra_scene, + is_multiedit, + use_multiframe_falloff, + is_prop_edit, + is_prop_edit_connected, + is_scale_thickness); + } + else { + createTransGPencil_strokes(C, + t, + depsgraph, + ts, + obact, + gpd, + cfra_scene, + is_multiedit, + use_multiframe_falloff, + is_prop_edit, + is_prop_edit_connected, + is_scale_thickness); + } +} + /* force recalculation of triangles during transformation */ void recalcData_gpencil_strokes(TransInfo *t) { @@ -375,13 +751,19 @@ void recalcData_gpencil_strokes(TransInfo *t) GHash *strokes = BLI_ghash_ptr_new(__func__); TransData *td = tc->data; + bGPdata *gpd = td->ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); for (int i = 0; i < tc->data_len; i++, td++) { bGPDstroke *gps = td->extra; if ((gps != NULL) && (!BLI_ghash_haskey(strokes, gps))) { BLI_ghash_insert(strokes, gps, gps); + if (is_curve_edit && gps->editcurve != NULL) { + BKE_gpencil_editcurve_recalculate_handles(gps); + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } } BLI_ghash_free(strokes, NULL, NULL); diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index 60848eb5678..8a7ec7a99e9 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -799,7 +799,7 @@ void postTrans(bContext *C, TransInfo *t) if (t->data_len_all != 0) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { /* free data malloced per trans-data */ - if (ELEM(t->obedit_type, OB_CURVE, OB_SURF) || (t->spacetype == SPACE_GRAPH)) { + if (ELEM(t->obedit_type, OB_CURVE, OB_SURF, OB_GPENCIL) || (t->spacetype == SPACE_GRAPH)) { TransData *td = tc->data; for (int a = 0; a < tc->data_len; a++, td++) { if (td->flag & TD_BEZTRIPLE) { diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index a71915d8122..d5c6ea2dfdd 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -653,6 +653,7 @@ int ED_transform_calc_gizmo_stats(const bContext *C, Object *ob = OBACT(view_layer); bGPdata *gpd = CTX_data_gpencil_data(C); const bool is_gp_edit = GPENCIL_ANY_MODE(gpd); + const bool is_curve_edit = GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); int a, totsel = 0; const int pivot_point = scene->toolsettings->transform_pivot_point; @@ -711,16 +712,39 @@ int ED_transform_calc_gizmo_stats(const bContext *C, continue; } - /* we're only interested in selected points here... */ - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; + if (is_curve_edit) { + if (gps->editcurve == NULL) { + continue; + } - /* Change selection status of all points, then make the stroke match */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - calc_tw_center_with_matrix(tbounds, &pt->x, use_mat_local, diff_mat); - totsel++; + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + for (uint32_t i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + for (uint32_t j = 0; j < 3; j++) { + if (BEZT_ISSEL_IDX(bezt, j)) { + calc_tw_center_with_matrix(tbounds, bezt->vec[j], use_mat_local, diff_mat); + totsel++; + } + } + } + } + } + } + else { + /* we're only interested in selected points here... */ + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i; + + /* Change selection status of all points, then make the stroke match */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + calc_tw_center_with_matrix(tbounds, &pt->x, use_mat_local, diff_mat); + totsel++; + } } } } diff --git a/source/blender/editors/transform/transform_mode_gpopacity.c b/source/blender/editors/transform/transform_mode_gpopacity.c index 103eb17c273..e67c6c03481 100644 --- a/source/blender/editors/transform/transform_mode_gpopacity.c +++ b/source/blender/editors/transform/transform_mode_gpopacity.c @@ -29,6 +29,8 @@ #include "BKE_context.h" #include "BKE_unit.h" +#include "DNA_gpencil_types.h" + #include "ED_screen.h" #include "UI_interface.h" @@ -68,8 +70,16 @@ static void applyGPOpacity(TransInfo *t, const int UNUSED(mval[2])) BLI_snprintf(str, sizeof(str), TIP_("Opacity: %3f"), ratio); } + bool recalc = false; FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData *td = tc->data; + bGPdata *gpd = td->ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* Only recalculate data when in curve edit mode. */ + if (is_curve_edit) { + recalc = true; + } + for (i = 0; i < tc->data_len; i++, td++) { if (td->flag & TD_SKIP) { continue; @@ -84,6 +94,10 @@ static void applyGPOpacity(TransInfo *t, const int UNUSED(mval[2])) } } + if (recalc) { + recalcData(t); + } + ED_area_status_text(t->area, str); } diff --git a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c index 14e7c1df4f4..95e3d89d2b7 100644 --- a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c @@ -29,6 +29,8 @@ #include "BKE_context.h" #include "BKE_unit.h" +#include "DNA_gpencil_types.h" + #include "ED_screen.h" #include "UI_interface.h" @@ -68,8 +70,16 @@ static void applyGPShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) BLI_snprintf(str, sizeof(str), TIP_("Shrink/Fatten: %3f"), ratio); } + bool recalc = false; FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData *td = tc->data; + bGPdata *gpd = td->ob->data; + const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + /* Only recalculate data when in curve edit mode. */ + if (is_curve_edit) { + recalc = true; + } + for (i = 0; i < tc->data_len; i++, td++) { if (td->flag & TD_SKIP) { continue; @@ -86,6 +96,10 @@ static void applyGPShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) } } + if (recalc) { + recalcData(t); + } + ED_area_status_text(t->area, str); } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c index c67d622ffec..cc8eae64300 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c @@ -125,10 +125,11 @@ static void deformStroke(GpencilModifierData *md, if (!mmd->object) { return; } + bGPdata *gpd = ob->data; gpencil_deform_verts(mmd, ob, gps); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } static void bakeModifier(Main *UNUSED(bmain), diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c index 628c5bc0476..aa21bf192c4 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c @@ -255,7 +255,7 @@ static void generate_geometry(GpencilModifierData *md, /* Duplicate original strokes to create this instance. */ LISTBASE_FOREACH_BACKWARD (tmpStrokes *, iter, &stroke_cache) { /* Duplicate stroke */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(iter->gps, true); + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(iter->gps, true, true); /* Move points */ for (int i = 0; i < iter->gps->totpoints; i++) { diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c index 5b5cc61bedc..8221c9288d4 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c @@ -115,7 +115,8 @@ static void gpf_clear_all_strokes(bGPDframe *gpf) * * Note: This won't be called if all points are present/removed */ -static void reduce_stroke_points(bGPDstroke *gps, +static void reduce_stroke_points(bGPdata *gpd, + bGPDstroke *gps, const int num_points, const eBuildGpencil_Transition transition) { @@ -180,7 +181,7 @@ static void reduce_stroke_points(bGPDstroke *gps, gps->totpoints = num_points; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } /* --------------------------------------------- */ @@ -197,7 +198,10 @@ typedef struct tStrokeBuildDetails { } tStrokeBuildDetails; /* Sequential - Show strokes one after the other */ -static void build_sequential(BuildGpencilModifierData *mmd, bGPDframe *gpf, float fac) +static void build_sequential(BuildGpencilModifierData *mmd, + bGPdata *gpd, + bGPDframe *gpf, + float fac) { const size_t tot_strokes = BLI_listbase_count(&gpf->strokes); bGPDstroke *gps; @@ -236,25 +240,25 @@ static void build_sequential(BuildGpencilModifierData *mmd, bGPDframe *gpf, floa size_t last_visible = 0; switch (mmd->transition) { - /* Show in forward order - * - As fac increases, the number of visible points increases - */ + /* Show in forward order + * - As fac increases, the number of visible points increases + */ case GP_BUILD_TRANSITION_GROW: first_visible = 0; /* always visible */ last_visible = (size_t)roundf(totpoints * fac); break; - /* Hide in reverse order - * - As fac increases, the number of points visible at the end decreases - */ + /* Hide in reverse order + * - As fac increases, the number of points visible at the end decreases + */ case GP_BUILD_TRANSITION_SHRINK: first_visible = 0; /* always visible (until last point removed) */ last_visible = (size_t)(totpoints * (1.0f - fac)); break; - /* Hide in forward order - * - As fac increases, the early points start getting hidden - */ + /* Hide in forward order + * - As fac increases, the early points start getting hidden + */ case GP_BUILD_TRANSITION_FADE: first_visible = (size_t)(totpoints * fac); last_visible = totpoints; /* i.e. visible until the end, unless first overlaps this */ @@ -278,12 +282,12 @@ static void build_sequential(BuildGpencilModifierData *mmd, bGPDframe *gpf, floa else if (first_visible > cell->start_idx) { /* Starts partway through this stroke */ int num_points = cell->end_idx - first_visible; - reduce_stroke_points(cell->gps, num_points, mmd->transition); + reduce_stroke_points(gpd, cell->gps, num_points, mmd->transition); } else { /* Ends partway through this stroke */ int num_points = last_visible - cell->start_idx; - reduce_stroke_points(cell->gps, num_points, mmd->transition); + reduce_stroke_points(gpd, cell->gps, num_points, mmd->transition); } } } @@ -295,7 +299,10 @@ static void build_sequential(BuildGpencilModifierData *mmd, bGPDframe *gpf, floa /* --------------------------------------------- */ /* Concurrent - Show multiple strokes at once */ -static void build_concurrent(BuildGpencilModifierData *mmd, bGPDframe *gpf, float fac) +static void build_concurrent(BuildGpencilModifierData *mmd, + bGPdata *gpd, + bGPDframe *gpf, + float fac) { bGPDstroke *gps, *gps_next; int max_points = 0; @@ -390,16 +397,14 @@ static void build_concurrent(BuildGpencilModifierData *mmd, bGPDframe *gpf, floa } else if (num_points < gps->totpoints) { /* Remove some points */ - reduce_stroke_points(gps, num_points, mmd->transition); + reduce_stroke_points(gpd, gps, num_points, mmd->transition); } } } /* --------------------------------------------- */ -static void generate_geometry(GpencilModifierData *md, - Depsgraph *depsgraph, - bGPDlayer *gpl, - bGPDframe *gpf) +static void generate_geometry( + GpencilModifierData *md, Depsgraph *depsgraph, bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf) { BuildGpencilModifierData *mmd = (BuildGpencilModifierData *)md; const bool reverse = (mmd->transition != GP_BUILD_TRANSITION_GROW); @@ -504,11 +509,11 @@ static void generate_geometry(GpencilModifierData *md, /* Time management mode */ switch (mmd->mode) { case GP_BUILD_MODE_SEQUENTIAL: - build_sequential(mmd, gpf, fac); + build_sequential(mmd, gpd, gpf, fac); break; case GP_BUILD_MODE_CONCURRENT: - build_concurrent(mmd, gpf, fac); + build_concurrent(mmd, gpd, gpf, fac); break; default: @@ -530,7 +535,7 @@ static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Objec if (gpf == NULL) { continue; } - generate_geometry(md, depsgraph, gpl, gpf); + generate_geometry(md, depsgraph, gpd, gpl, gpf); } } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c index f0838e4522d..09cce3f1ab4 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c @@ -228,6 +228,7 @@ static void deformStroke(GpencilModifierData *md, mmd->flag & GP_HOOK_INVERT_MATERIAL)) { return; } + bGPdata *gpd = ob->data; /* init struct */ tData.curfalloff = mmd->curfalloff; @@ -273,7 +274,7 @@ static void deformStroke(GpencilModifierData *md, gpencil_hook_co_apply(&tData, weight, pt); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } /* FIXME: Ideally we be doing this on a copy of the main depsgraph diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c index 098a2edf04a..b2a83e83c9e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c @@ -85,6 +85,7 @@ static void deformStroke(GpencilModifierData *md, bGPDframe *UNUSED(gpf), bGPDstroke *gps) { + bGPdata *gpd = ob->data; LatticeGpencilModifierData *mmd = (LatticeGpencilModifierData *)md; const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); @@ -121,7 +122,7 @@ static void deformStroke(GpencilModifierData *md, (struct LatticeDeformData *)mmd->cache_data, &pt->x, mmd->strength * weight); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } /* FIXME: Ideally we be doing this on a copy of the main depsgraph diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c index a5457c86ec0..111c60436bf 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c @@ -151,7 +151,7 @@ static void generate_geometry(GpencilModifierData *md, Object *ob, bGPDlayer *gp mmd->flag & GP_MIRROR_INVERT_PASS, mmd->flag & GP_MIRROR_INVERT_LAYERPASS, mmd->flag & GP_MIRROR_INVERT_MATERIAL)) { - gps_new = BKE_gpencil_stroke_duplicate(gps, true); + gps_new = BKE_gpencil_stroke_duplicate(gps, true, true); update_position(ob, mmd, gps_new, xi); BLI_addtail(&gpf->strokes, gps_new); } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c index 7d0e3ce53fe..445f677e616 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c @@ -129,6 +129,7 @@ static void duplicateStroke(Object *ob, float fading_thickness, float fading_opacity) { + bGPdata *gpd = ob->data; int i; bGPDstroke *new_gps = NULL; float stroke_normal[3]; @@ -172,7 +173,7 @@ static void duplicateStroke(Object *ob, * to be processed, since we duplicate its data. */ for (i = count - 1; i >= 0; i--) { if (i != 0) { - new_gps = BKE_gpencil_stroke_duplicate(gps, true); + new_gps = BKE_gpencil_stroke_duplicate(gps, true, true); BLI_addtail(results, new_gps); } else { @@ -199,7 +200,7 @@ static void duplicateStroke(Object *ob, } /* Calc geometry data. */ if (new_gps != NULL) { - BKE_gpencil_stroke_geometry_update(new_gps); + BKE_gpencil_stroke_geometry_update(gpd, new_gps); } MEM_freeN(t1_array); MEM_freeN(t2_array); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c index 634aac00777..4e009e25ce3 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c @@ -100,6 +100,7 @@ static void deformStroke(GpencilModifierData *md, mmd->flag & GP_OFFSET_INVERT_MATERIAL)) { return; } + bGPdata *gpd = ob->data; for (int i = 0; i < gps->totpoints; i++) { bGPDspoint *pt = &gps->points[i]; @@ -125,7 +126,7 @@ static void deformStroke(GpencilModifierData *md, mul_m4_v3(mat, &pt->x); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } static void bakeModifier(struct Main *UNUSED(bmain), diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c index 7052c59ae40..8093be2f560 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c @@ -92,26 +92,26 @@ static void deformStroke(GpencilModifierData *md, mmd->flag & GP_SIMPLIFY_INVERT_MATERIAL)) { return; } - + bGPdata *gpd = ob->data; /* Select simplification mode. */ switch (mmd->mode) { case GP_SIMPLIFY_FIXED: { for (int i = 0; i < mmd->step; i++) { - BKE_gpencil_stroke_simplify_fixed(gps); + BKE_gpencil_stroke_simplify_fixed(gpd, gps); } break; } case GP_SIMPLIFY_ADAPTIVE: { /* simplify stroke using Ramer-Douglas-Peucker algorithm */ - BKE_gpencil_stroke_simplify_adaptive(gps, mmd->factor); + BKE_gpencil_stroke_simplify_adaptive(gpd, gps, mmd->factor); break; } case GP_SIMPLIFY_SAMPLE: { - BKE_gpencil_stroke_sample(gps, mmd->length, false); + BKE_gpencil_stroke_sample(gpd, gps, mmd->length, false); break; } case GP_SIMPLIFY_MERGE: { - BKE_gpencil_stroke_merge_distance(gpf, gps, mmd->distance, true); + BKE_gpencil_stroke_merge_distance(gpd, gpf, gps, mmd->distance, true); break; } default: diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c index 8f17be97710..c91bb1cef92 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c @@ -80,6 +80,7 @@ static void deformStroke(GpencilModifierData *md, bGPDstroke *gps) { SubdivGpencilModifierData *mmd = (SubdivGpencilModifierData *)md; + bGPdata *gpd = ob->data; /* It makes sense when adding points to a straight line */ /* e.g. for creating thickness variation in later modifiers. */ @@ -100,7 +101,7 @@ static void deformStroke(GpencilModifierData *md, return; } - BKE_gpencil_stroke_subdivide(gps, mmd->level, mmd->type); + BKE_gpencil_stroke_subdivide(gpd, gps, mmd->level, mmd->type); /* If the stroke is cyclic, must generate the closing geometry. */ if (gps->flag & GP_STROKE_CYCLIC) { diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c index 6a630cbf978..c396dffad27 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c @@ -82,6 +82,7 @@ static void deformStroke(GpencilModifierData *md, { TextureGpencilModifierData *mmd = (TextureGpencilModifierData *)md; const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + bGPdata *gpd = ob->data; if (!is_stroke_affected_by_modifier(ob, mmd->layername, @@ -102,7 +103,7 @@ static void deformStroke(GpencilModifierData *md, gps->uv_translation[0] += mmd->fill_offset[0]; gps->uv_translation[1] += mmd->fill_offset[1]; gps->uv_scale *= mmd->fill_scale; - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); } if (ELEM(mmd->mode, STROKE, STROKE_AND_FILL)) { diff --git a/source/blender/makesdna/DNA_curve_types.h b/source/blender/makesdna/DNA_curve_types.h index 2f362034b78..4eca81c27cc 100644 --- a/source/blender/makesdna/DNA_curve_types.h +++ b/source/blender/makesdna/DNA_curve_types.h @@ -519,6 +519,10 @@ typedef enum eBezTriple_KeyframeType { (bezt)->f2 & SELECT : \ BEZT_ISSEL_ANY(bezt)) +#define BEZT_ISSEL_IDX(bezt, i) \ + ((i == 0 && (bezt)->f1 & SELECT) || (i == 1 && (bezt)->f2 & SELECT) || \ + (i == 2 && (bezt)->f3 & SELECT)) + #define BEZT_SEL_ALL(bezt) \ { \ (bezt)->f1 |= SELECT; \ @@ -533,6 +537,49 @@ typedef enum eBezTriple_KeyframeType { (bezt)->f3 &= ~SELECT; \ } \ ((void)0) +#define BEZT_SEL_INVERT(bezt) \ + { \ + (bezt)->f1 ^= SELECT; \ + (bezt)->f2 ^= SELECT; \ + (bezt)->f3 ^= SELECT; \ + } \ + ((void)0) + +#define BEZT_SEL_IDX(bezt, i) \ + { \ + switch (i) { \ + case 0: \ + (bezt)->f1 |= SELECT; \ + break; \ + case 1: \ + (bezt)->f2 |= SELECT; \ + break; \ + case 2: \ + (bezt)->f3 |= SELECT; \ + break; \ + default: \ + break; \ + } \ + } \ + ((void)0) + +#define BEZT_DESEL_IDX(bezt, i) \ + { \ + switch (i) { \ + case 0: \ + (bezt)->f1 &= ~SELECT; \ + break; \ + case 1: \ + (bezt)->f2 &= ~SELECT; \ + break; \ + case 2: \ + (bezt)->f3 &= ~SELECT; \ + break; \ + default: \ + break; \ + } \ + } \ + ((void)0) #define BEZT_IS_AUTOH(bezt) \ (ELEM((bezt)->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM((bezt)->h2, HD_AUTO, HD_AUTO_ANIM)) diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 222b716a502..94b75642fd6 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -29,6 +29,7 @@ struct AnimData; struct MDeformVert; +struct Curve; #define GP_DEFAULT_PIX_FACTOR 1.0f #define GP_DEFAULT_GRID_LINES 4 @@ -36,6 +37,10 @@ struct MDeformVert; #define GP_MATERIAL_BUFFER_LEN 256 +#define GP_DEFAULT_CURVE_RESOLUTION 32 +#define GP_DEFAULT_CURVE_ERROR 0.1f +#define GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE M_PI_2 + /* ***************************************** */ /* GP Stroke Points */ @@ -165,6 +170,61 @@ typedef enum eGPDpalette_Flag { PL_PALETTE_ACTIVE = (1 << 0), } eGPDpalette_Flag; +/* ***************************************** */ +/* GP Curve Point */ + +typedef struct bGPDcurve_point { + /** Bezier Triple for the handles and control points. */ + BezTriple bezt; + /** Pressure of input device (from 0 to 1) at this point. */ + float pressure; + /** Color strength (used for alpha factor). */ + float strength; + /** Index of corresponding point in gps->points. */ + int point_index; + + /** Additional options. */ + int flag; + + /** Factor of uv along the stroke. */ + float uv_fac; + /** Uv rotation for dot mode. */ + float uv_rot; + /** Uv for fill mode. */ + float uv_fill[2]; + + /** Vertex Color RGBA (A=mix factor). */ + float vert_color[4]; + char _pad[4]; +} bGPDcurve_point; + +/* bGPDcurve_point->flag */ +typedef enum eGPDcurve_point_Flag { + GP_CURVE_POINT_SELECT = (1 << 0), +} eGPDcurve_point_Flag; + +/* ***************************************** */ +/* GP Curve */ + +/* Curve for Bezier Editing. */ +typedef struct bGPDcurve { + /** Array of BezTriple. */ + bGPDcurve_point *curve_points; + /** Total number of curve points. */ + int tot_curve_points; + /** General flag. */ + short flag; + char _pad[2]; +} bGPDcurve; + +/* bGPDcurve_Flag->flag */ +typedef enum bGPDcurve_Flag { + /* Flag to indicated that the stroke data has been changed and the curve needs to be refitted */ + GP_CURVE_NEEDS_STROKE_UPDATE = (1 << 0), + /* Curve is selected */ + GP_CURVE_SELECT = (1 << 1), +} bGPDcurve_Flag; + /* ***************************************** */ /* GP Strokes */ @@ -180,7 +240,8 @@ typedef struct bGPDstroke_Runtime { int stroke_start; /** Triangle offset in the ibo where this fill starts. */ int fill_start; - int _pad[1]; + /** Curve Handles offset in the ibo where this handle starts. */ + int curve_start; /** Original stroke (used to dereference evaluated data) */ struct bGPDstroke *gps_orig; @@ -245,6 +306,9 @@ typedef struct bGPDstroke { /** Vertex Color for Fill (one for all stroke, A=mix factor). */ float vert_color_fill[4]; + /** Curve used to edit the stroke using Bezier handlers. */ + struct bGPDcurve *editcurve; + bGPDstroke_Runtime runtime; } bGPDstroke; @@ -263,6 +327,9 @@ typedef enum eGPDstroke_Flag { /* Flag used to indicate that stroke is used for fill close and must use * fill color for stroke and no fill area */ GP_STROKE_NOFILL = (1 << 8), + /* Flag to indicated that the editcurve has been changed and the stroke needs to be updated with + * the curve data */ + GP_STROKE_NEEDS_CURVE_UPDATE = (1 << 9), /* only for use with stroke-buffer (while drawing arrows) */ GP_STROKE_USE_ARROW_START = (1 << 12), /* only for use with stroke-buffer (while drawing arrows) */ @@ -562,7 +629,12 @@ typedef struct bGPdata { ListBase layers; /** Settings for this data-block. */ int flag; - char _pad1[4]; + /** Default resolution for generated curves using curve editing method. */ + int curve_edit_resolution; + /** Curve Editing error threshold. */ + float curve_edit_threshold; + /** Curve Editing corner angle (less or equal is treated as corner). */ + float curve_edit_corner_angle; /* Palettes */ /** List of bGPDpalette's - Deprecated (2.78 - 2.79 only). */ @@ -680,6 +752,11 @@ typedef enum eGPdata_Flag { /* Autolock not active layers */ GP_DATA_AUTOLOCK_LAYERS = (1 << 20), + + /* Enable Bezier Editing Curve (a submode of Edit mode). */ + GP_DATA_CURVE_EDIT_MODE = (1 << 21), + /* Use adaptive curve resolution */ + GP_DATA_CURVE_ADAPTIVE_RESOLUTION = (1 << 22), } eGPdata_Flag; /* gpd->onion_flag */ @@ -725,6 +802,9 @@ typedef enum eGP_DrawMode { GP_DATA_STROKE_WEIGHTMODE | GP_DATA_STROKE_VERTEXMODE)) && \ ((gpd)->flag & GP_DATA_STROKE_MULTIEDIT)) +#define GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd) \ + ((gpd) && ((gpd)->flag & (GP_DATA_STROKE_EDITMODE)) && ((gpd)->flag & GP_DATA_CURVE_EDIT_MODE)) + /* Macros to check grease pencil modes */ #define GPENCIL_ANY_MODE(gpd) \ ((gpd) && ((gpd)->flag & \ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 028e150d904..f65f8d32e97 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1090,7 +1090,7 @@ typedef struct GP_Sculpt_Settings { int lock_axis; /** Threshold for intersections */ float isect_threshold; - char _pad_[4]; + char _pad[4]; /** Multiframe edit falloff effect by frame. */ struct CurveMapping *cur_falloff; /** Curve used for primitive tools. */ diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index f8bc5c06963..603bd51b2d9 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -23,6 +23,7 @@ #include "BLI_math.h" #include "DNA_brush_types.h" +#include "DNA_curve_types.h" #include "DNA_gpencil_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" @@ -160,6 +161,7 @@ static const EnumPropertyItem rna_enum_gpencil_caps_modes_items[] = { #ifdef RNA_RUNTIME # include "BLI_ghash.h" +# include "BLI_listbase.h" # include "BLI_string_utils.h" # include "WM_api.h" @@ -167,6 +169,7 @@ static const EnumPropertyItem rna_enum_gpencil_caps_modes_items[] = { # include "BKE_action.h" # include "BKE_animsys.h" # include "BKE_gpencil.h" +# include "BKE_gpencil_curve.h" # include "BKE_gpencil_geom.h" # include "BKE_icons.h" @@ -179,6 +182,71 @@ static void rna_GPencil_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Pointe WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL); } +static void rna_GPencil_curve_edit_mode_toggle(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + ToolSettings *ts = scene->toolsettings; + bGPdata *gpd = (bGPdata *)ptr->owner_id; + + /* Curve edit mode is turned on. */ + if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + /* If the current select mode is segment and the Bezier mode is on, change + * to Point because segment is not supported. */ + if (ts->gpencil_selectmode_edit == GP_SELECTMODE_SEGMENT) { + ts->gpencil_selectmode_edit = GP_SELECTMODE_POINT; + } + + BKE_gpencil_strokes_selected_update_editcurve(gpd); + } + /* Curve edit mode is turned off. */ + else { + BKE_gpencil_strokes_selected_sync_selection_editcurve(gpd); + } + + /* Standard update. */ + rna_GPencil_update(bmain, scene, ptr); +} + +static void rna_GPencil_stroke_curve_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + bGPdata *gpd = (bGPdata *)ptr->owner_id; + + if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->actframe != NULL) { + bGPDframe *gpf = gpl->actframe; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->editcurve != NULL) { + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + } + } + } + + rna_GPencil_update(bmain, scene, ptr); +} + +static void rna_GPencil_stroke_curve_resolution_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + bGPdata *gpd = (bGPdata *)ptr->owner_id; + + if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->actframe != NULL) { + bGPDframe *gpf = gpl->actframe; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->editcurve != NULL) { + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + } + } + } + } + rna_GPencil_update(bmain, scene, ptr); +} + static void rna_GPencil_dependency_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) { DEG_id_tag_update(ptr->owner_id, ID_RECALC_TRANSFORM); @@ -191,11 +259,12 @@ static void rna_GPencil_dependency_update(Main *bmain, Scene *UNUSED(scene), Poi static void rna_GPencil_uv_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { + bGPdata *gpd = (bGPdata *)ptr->owner_id; /* Force to recalc the UVs. */ bGPDstroke *gps = (bGPDstroke *)ptr->data; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps); DEG_id_tag_update(ptr->owner_id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL); @@ -669,7 +738,7 @@ static void rna_GPencil_stroke_point_add( stroke->totpoints += count; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(stroke); + BKE_gpencil_stroke_geometry_update(gpd, stroke); DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); @@ -730,7 +799,7 @@ static void rna_GPencil_stroke_point_pop(ID *id, } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(stroke); + BKE_gpencil_stroke_geometry_update(gpd, stroke); DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); @@ -808,6 +877,32 @@ static void rna_GPencil_stroke_select_set(PointerRNA *ptr, const bool value) } } +static void rna_GPencil_curve_select_set(PointerRNA *ptr, const bool value) +{ + bGPDcurve *gpc = ptr->data; + + /* Set new value. */ + if (value) { + gpc->flag |= GP_CURVE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + } + /* Ensure that the curves's points are selected in the same way. */ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; + if (value) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + else { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + } +} + static bGPDframe *rna_GPencil_frame_new(bGPDlayer *layer, ReportList *reports, int frame_number, @@ -969,6 +1064,100 @@ static char *rna_GreasePencilGrid_path(PointerRNA *UNUSED(ptr)) return BLI_strdup("grid"); } +static void rna_GpencilCurvePoint_BezTriple_handle1_get(PointerRNA *ptr, float *values) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + copy_v3_v3(values, cpt->bezt.vec[0]); +} + +static void rna_GpencilCurvePoint_BezTriple_handle1_set(PointerRNA *ptr, const float *values) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + copy_v3_v3(cpt->bezt.vec[0], values); +} + +static bool rna_GpencilCurvePoint_BezTriple_handle1_select_get(PointerRNA *ptr) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + return cpt->bezt.f1; +} + +static void rna_GpencilCurvePoint_BezTriple_handle1_select_set(PointerRNA *ptr, const bool value) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + cpt->bezt.f1 = value; +} + +static void rna_GpencilCurvePoint_BezTriple_handle2_get(PointerRNA *ptr, float *values) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + copy_v3_v3(values, cpt->bezt.vec[2]); +} + +static void rna_GpencilCurvePoint_BezTriple_handle2_set(PointerRNA *ptr, const float *values) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + copy_v3_v3(cpt->bezt.vec[2], values); +} + +static bool rna_GpencilCurvePoint_BezTriple_handle2_select_get(PointerRNA *ptr) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + return cpt->bezt.f3; +} + +static void rna_GpencilCurvePoint_BezTriple_handle2_select_set(PointerRNA *ptr, const bool value) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + cpt->bezt.f3 = value; +} + +static void rna_GpencilCurvePoint_BezTriple_ctrlpoint_get(PointerRNA *ptr, float *values) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + copy_v3_v3(values, cpt->bezt.vec[1]); +} + +static void rna_GpencilCurvePoint_BezTriple_ctrlpoint_set(PointerRNA *ptr, const float *values) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + copy_v3_v3(cpt->bezt.vec[1], values); +} + +static bool rna_GpencilCurvePoint_BezTriple_ctrlpoint_select_get(PointerRNA *ptr) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + return cpt->bezt.f2; +} + +static void rna_GpencilCurvePoint_BezTriple_ctrlpoint_select_set(PointerRNA *ptr, const bool value) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + cpt->bezt.f2 = value; +} + +static bool rna_GpencilCurvePoint_BezTriple_hide_get(PointerRNA *ptr) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + return (bool)cpt->bezt.hide; +} + +static void rna_GpencilCurvePoint_BezTriple_hide_set(PointerRNA *ptr, const bool value) +{ + bGPDcurve_point *cpt = (bGPDcurve_point *)ptr->data; + cpt->bezt.hide = value; +} + +static bool rna_stroke_has_edit_curve_get(PointerRNA *ptr) +{ + bGPDstroke *gps = (bGPDstroke *)ptr->data; + if (gps->editcurve != NULL) { + return true; + } + + return false; +} + #else static void rna_def_gpencil_stroke_point(BlenderRNA *brna) @@ -1106,6 +1295,149 @@ static void rna_def_gpencil_triangle(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); } +static void rna_def_gpencil_curve_point(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "GPencilEditCurvePoint", NULL); + RNA_def_struct_sdna(srna, "bGPDcurve_point"); + RNA_def_struct_ui_text(srna, "Bezier Curve Point", "Bezier curve point with two handles"); + + /* Boolean values */ + prop = RNA_def_property(srna, "select_left_handle", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_funcs(prop, + "rna_GpencilCurvePoint_BezTriple_handle1_select_get", + "rna_GpencilCurvePoint_BezTriple_handle1_select_set"); + RNA_def_property_ui_text(prop, "Handle 1 selected", "Handle 1 selection status"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + + prop = RNA_def_property(srna, "select_right_handle", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_funcs(prop, + "rna_GpencilCurvePoint_BezTriple_handle2_select_get", + "rna_GpencilCurvePoint_BezTriple_handle2_select_set"); + RNA_def_property_ui_text(prop, "Handle 2 selected", "Handle 2 selection status"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + + prop = RNA_def_property(srna, "select_control_point", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_funcs(prop, + "rna_GpencilCurvePoint_BezTriple_ctrlpoint_select_get", + "rna_GpencilCurvePoint_BezTriple_ctrlpoint_select_set"); + RNA_def_property_ui_text(prop, "Control Point selected", "Control point selection status"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + + prop = RNA_def_property(srna, "hide", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_funcs(prop, + "rna_GpencilCurvePoint_BezTriple_hide_get", + "rna_GpencilCurvePoint_BezTriple_hide_set"); + RNA_def_property_ui_text(prop, "Hide", "Visibility status"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + + /* Vector values */ + prop = RNA_def_property(srna, "handle_left", PROP_FLOAT, PROP_TRANSLATION); + RNA_def_property_array(prop, 3); + RNA_def_property_float_funcs(prop, + "rna_GpencilCurvePoint_BezTriple_handle1_get", + "rna_GpencilCurvePoint_BezTriple_handle1_set", + NULL); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Handle 1", "Coordinates of the first handle"); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); + + prop = RNA_def_property(srna, "co", PROP_FLOAT, PROP_TRANSLATION); + RNA_def_property_array(prop, 3); + RNA_def_property_float_funcs(prop, + "rna_GpencilCurvePoint_BezTriple_ctrlpoint_get", + "rna_GpencilCurvePoint_BezTriple_ctrlpoint_set", + NULL); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Control Point", "Coordinates of the control point"); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); + + prop = RNA_def_property(srna, "handle_right", PROP_FLOAT, PROP_TRANSLATION); + RNA_def_property_array(prop, 3); + RNA_def_property_float_funcs(prop, + "rna_GpencilCurvePoint_BezTriple_handle2_get", + "rna_GpencilCurvePoint_BezTriple_handle2_set", + NULL); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Handle 2", "Coordinates of the second handle"); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); + + /* Pressure */ + prop = RNA_def_property(srna, "pressure", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "pressure"); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Pressure", "Pressure of the grease pencil stroke point"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); + + /* Strength */ + prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "strength"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text( + prop, "Strength", "Color intensity (alpha factor) of the grease pencil stroke point"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); + + /* read-only index */ + prop = RNA_def_property(srna, "point_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, NULL, "point_index"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text( + prop, "Point Index", "Index of the corresponding grease pencil stroke point"); + + prop = RNA_def_property(srna, "uv_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "uv_fac"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "UV Factor", "Internal UV factor"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); + + prop = RNA_def_property(srna, "uv_rotation", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, NULL, "uv_rot"); + RNA_def_property_range(prop, -M_PI_2, M_PI_2); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "UV Rotation", "Internal UV factor for dot mode"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); + + prop = RNA_def_property(srna, "vertex_color", PROP_FLOAT, PROP_COLOR); + RNA_def_property_float_sdna(prop, NULL, "vert_color"); + RNA_def_property_array(prop, 4); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Vertex Color", "Vertex color of the grease pencil stroke point"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); +} + +/* Editing Curve data. */ +static void rna_def_gpencil_curve(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "GPencilEditCurve", NULL); + RNA_def_struct_sdna(srna, "bGPDcurve"); + RNA_def_struct_ui_text(srna, "Edit Curve", "Edition Curve"); + + prop = RNA_def_property(srna, "curve_points", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "curve_points", "tot_curve_points"); + RNA_def_property_struct_type(prop, "GPencilEditCurvePoint"); + RNA_def_property_ui_text(prop, "Curve Points", "Curve data points"); + + prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_CURVE_SELECT); + RNA_def_property_boolean_funcs(prop, NULL, "rna_GPencil_curve_select_set"); + RNA_def_property_ui_text(prop, "Select", "Curve is selected for viewport editing"); + RNA_def_property_update(prop, 0, "rna_GPencil_update"); +} + static void rna_def_gpencil_mvert_group(BlenderRNA *brna) { StructRNA *srna; @@ -1180,6 +1512,12 @@ static void rna_def_gpencil_stroke(BlenderRNA *brna) RNA_def_property_struct_type(prop, "GPencilTriangle"); RNA_def_property_ui_text(prop, "Triangles", "Triangulation data for HQ fill"); + /* Edit Curve. */ + prop = RNA_def_property(srna, "edit_curve", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "editcurve"); + RNA_def_property_struct_type(prop, "GPencilEditCurve"); + RNA_def_property_ui_text(prop, "Edit Curve", "Temporary data for Edit Curve"); + /* Material Index */ prop = RNA_def_property(srna, "material_index", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "mat_nr"); @@ -1205,6 +1543,12 @@ static void rna_def_gpencil_stroke(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Cyclic", "Enable cyclic drawing, closing the stroke"); RNA_def_property_update(prop, 0, "rna_GPencil_update"); + /* The stroke has Curve Edit data. */ + prop = RNA_def_property(srna, "has_edit_curve", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_funcs(prop, "rna_stroke_has_edit_curve_get", NULL); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Has Curve Data", "Stroke has Curve data to edit shape"); + /* Caps mode */ prop = RNA_def_property(srna, "start_cap_mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "caps[0]"); @@ -2017,6 +2361,47 @@ static void rna_def_gpencil_data(BlenderRNA *brna) "Scale conversion factor for pixel size (use larger values for thicker lines)"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + prop = RNA_def_property(srna, "edit_curve_resolution", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "curve_edit_resolution"); + RNA_def_property_range(prop, 1, 256); + RNA_def_property_ui_range(prop, 1, 64, 1, 1); + RNA_def_property_int_default(prop, GP_DEFAULT_CURVE_RESOLUTION); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_ui_text( + prop, + "Curve Resolution", + "Number of segments generated between control points when editing strokes in curve mode"); + RNA_def_property_update( + prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_resolution_update"); + + prop = RNA_def_property(srna, "use_adaptive_curve_resolution", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_CURVE_ADAPTIVE_RESOLUTION); + RNA_def_property_boolean_default(prop, true); + RNA_def_property_ui_text(prop, + "Adaptive Resolution", + "Set the resolution of each editcurve segment dynamically depending on " + "the length of the segment. The resolution is the number of points " + "generated per unit distance"); + RNA_def_property_update( + prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_resolution_update"); + + /* Curve editing error threshold. */ + prop = RNA_def_property(srna, "curve_edit_threshold", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "curve_edit_threshold"); + RNA_def_property_range(prop, FLT_MIN, 10.0); + RNA_def_property_float_default(prop, GP_DEFAULT_CURVE_ERROR); + RNA_def_property_ui_text(prop, "Threshold", "Curve conversion error threshold"); + RNA_def_property_ui_range(prop, FLT_MIN, 10.0, 2, 5); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + /* Curve editing corner angle. */ + prop = RNA_def_property(srna, "curve_edit_corner_angle", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, NULL, "curve_edit_corner_angle"); + RNA_def_property_range(prop, 0.0f, DEG2RADF(180.0f)); + RNA_def_property_float_default(prop, DEG2RADF(90.0f)); + RNA_def_property_ui_text(prop, "Corner Angle", "Angle threshold to be treated as corners"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "use_multiedit", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_STROKE_MULTIEDIT); RNA_def_property_ui_text(prop, @@ -2025,6 +2410,11 @@ static void rna_def_gpencil_data(BlenderRNA *brna) "(keyframes must be selected to be included)"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + prop = RNA_def_property(srna, "use_curve_edit", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_CURVE_EDIT_MODE); + RNA_def_property_ui_text(prop, "Curve Editing", "Edit strokes using curve handles"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_curve_edit_mode_toggle"); + prop = RNA_def_property(srna, "use_autolock_layers", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_AUTOLOCK_LAYERS); RNA_def_property_ui_text( @@ -2176,6 +2566,8 @@ void RNA_def_gpencil(BlenderRNA *brna) rna_def_gpencil_stroke(brna); rna_def_gpencil_stroke_point(brna); rna_def_gpencil_triangle(brna); + rna_def_gpencil_curve(brna); + rna_def_gpencil_curve_point(brna); rna_def_gpencil_mvert_group(brna); } -- cgit v1.2.3