Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFalk David <falkdavid@gmx.de>2020-11-13 23:43:00 +0300
committerFalk David <falkdavid@gmx.de>2020-11-13 23:43:00 +0300
commit0be88c7d15d2ad1af284c6283370173647ae74eb (patch)
tree5fff573c512e284547ebe0c921ecffdae2c377c4
parent9d28353b525ecfbcca1501be72e4276dfb2bbc2a (diff)
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.
-rw-r--r--release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py1
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py18
-rw-r--r--release/scripts/startup/bl_ui/properties_data_gpencil.py2
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py45
-rw-r--r--source/blender/blenkernel/BKE_gpencil.h11
-rw-r--r--source/blender/blenkernel/BKE_gpencil_curve.h21
-rw-r--r--source/blender/blenkernel/BKE_gpencil_geom.h31
-rw-r--r--source/blender/blenkernel/intern/gpencil.c112
-rw-r--r--source/blender/blenkernel/intern/gpencil_curve.c874
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.c84
-rw-r--r--source/blender/blenkernel/intern/gpencil_modifier.c8
-rw-r--r--source/blender/blenloader/intern/versioning_280.c2
-rw-r--r--source/blender/blenloader/intern/versioning_290.c14
-rw-r--r--source/blender/draw/engines/overlay/overlay_gpencil.c61
-rw-r--r--source/blender/draw/engines/overlay/overlay_private.h3
-rw-r--r--source/blender/draw/engines/overlay/shaders/edit_curve_handle_geom.glsl6
-rw-r--r--source/blender/draw/engines/overlay/shaders/edit_curve_point_vert.glsl5
-rw-r--r--source/blender/draw/intern/draw_cache.h2
-rw-r--r--source/blender/draw/intern/draw_cache_impl_gpencil.c180
-rw-r--r--source/blender/editors/gpencil/CMakeLists.txt1
-rw-r--r--source/blender/editors/gpencil/annotate_paint.c6
-rw-r--r--source/blender/editors/gpencil/editaction_gpencil.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_add_monkey.c56
-rw-r--r--source/blender/editors/gpencil/gpencil_add_stroke.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_convert.c4
-rw-r--r--source/blender/editors/gpencil/gpencil_data.c51
-rw-r--r--source/blender/editors/gpencil/gpencil_edit.c1588
-rw-r--r--source/blender/editors/gpencil/gpencil_edit_curve.c214
-rw-r--r--source/blender/editors/gpencil/gpencil_fill.c4
-rw-r--r--source/blender/editors/gpencil/gpencil_intern.h59
-rw-r--r--source/blender/editors/gpencil/gpencil_interpolate.c14
-rw-r--r--source/blender/editors/gpencil/gpencil_merge.c7
-rw-r--r--source/blender/editors/gpencil/gpencil_ops.c22
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c10
-rw-r--r--source/blender/editors/gpencil/gpencil_primitive.c4
-rw-r--r--source/blender/editors/gpencil/gpencil_sculpt_paint.c7
-rw-r--r--source/blender/editors/gpencil/gpencil_select.c1571
-rw-r--r--source/blender/editors/gpencil/gpencil_trace_utils.c5
-rw-r--r--source/blender/editors/gpencil/gpencil_utils.c101
-rw-r--r--source/blender/editors/gpencil/gpencil_uv.c8
-rw-r--r--source/blender/editors/gpencil/gpencil_vertex_paint.c25
-rw-r--r--source/blender/editors/include/ED_gpencil.h4
-rw-r--r--source/blender/editors/screen/area.c4
-rw-r--r--source/blender/editors/transform/transform_convert_gpencil.c436
-rw-r--r--source/blender/editors/transform/transform_generics.c2
-rw-r--r--source/blender/editors/transform/transform_gizmo_3d.c42
-rw-r--r--source/blender/editors/transform/transform_mode_gpopacity.c14
-rw-r--r--source/blender/editors/transform/transform_mode_gpshrinkfatten.c14
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c3
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c2
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c51
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c3
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c3
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c2
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c5
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c3
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c10
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c3
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c3
-rw-r--r--source/blender/makesdna/DNA_curve_types.h47
-rw-r--r--source/blender/makesdna/DNA_gpencil_types.h84
-rw-r--r--source/blender/makesdna/DNA_scene_types.h2
-rw-r--r--source/blender/makesrna/intern/rna_gpencil.c398
63 files changed, 5325 insertions, 1051 deletions
diff --git a/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py b/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
index 52b9f1601d5..f92a478fb66 100644
--- a/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
+++ b/release/scripts/modules/bl_keymap_utils/keymap_hierarchy.py
@@ -191,6 +191,7 @@ _km_hierarchy = [
]),
('Grease Pencil', 'EMPTY', 'WINDOW', [ # grease pencil stuff (per region)
+ ('Grease Pencil Stroke Curve Edit Mode', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Edit Mode', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Paint (Draw brush)', 'EMPTY', 'WINDOW', []),
('Grease Pencil Stroke Paint (Fill)', 'EMPTY', 'WINDOW', []),
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 174ab6fd28b..d66d7c45dd4 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -3226,6 +3226,9 @@ def km_grease_pencil_stroke_edit_mode(params):
{"properties": [("mode", 'GPENCIL_OPACITY')]}),
# Proportional editing.
*_template_items_proportional_editing(connected=True),
+ # Curve edit mode toggle.
+ ("wm.context_toggle", {"type": 'U', "value": 'PRESS'},
+ {"properties": [("data_path", 'gpencil_data.use_curve_edit')]}),
# Add menu
("object.gpencil_add", {"type": 'A', "value": 'PRESS', "shift": True}, None),
# Vertex group menu
@@ -3253,6 +3256,20 @@ def km_grease_pencil_stroke_edit_mode(params):
return keymap
+def km_grease_pencil_stroke_curve_edit_mode(params):
+ items = []
+ keymap = (
+ "Grease Pencil Stroke Curve Edit Mode",
+ {"space_type": 'EMPTY', "region_type": 'WINDOW'},
+ {"items": items},
+ )
+
+ items.extend([
+ # Set handle type
+ ("gpencil.stroke_editcurve_set_handle_type", {"type": 'V', "value": 'PRESS'}, None),
+ ])
+
+ return keymap
def km_grease_pencil_stroke_paint_mode(params):
items = []
@@ -6890,6 +6907,7 @@ def generate_keymaps(params=None):
# Modes.
km_grease_pencil(params),
+ km_grease_pencil_stroke_curve_edit_mode(params),
km_grease_pencil_stroke_edit_mode(params),
km_grease_pencil_stroke_paint_mode(params),
km_grease_pencil_stroke_paint_draw_brush(params),
diff --git a/release/scripts/startup/bl_ui/properties_data_gpencil.py b/release/scripts/startup/bl_ui/properties_data_gpencil.py
index affdba6f693..398bf60c493 100644
--- a/release/scripts/startup/bl_ui/properties_data_gpencil.py
+++ b/release/scripts/startup/bl_ui/properties_data_gpencil.py
@@ -369,6 +369,8 @@ class DATA_PT_gpencil_strokes(DataButtonsPanel, Panel):
sub.active = gpd.stroke_thickness_space == 'WORLDSPACE'
sub.prop(gpd, "pixel_factor", text="Thickness Scale")
+ col.prop(gpd, "edit_curve_resolution")
+
class DATA_PT_gpencil_display(DataButtonsPanel, Panel):
bl_label = "Viewport Display"
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 1b5a7da61b0..9dbb56e7e45 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -661,7 +661,23 @@ class VIEW3D_HT_header(Header):
# Select mode for Editing
if gpd.use_stroke_edit_mode:
row = layout.row(align=True)
- row.prop(tool_settings, "gpencil_selectmode_edit", text="", expand=True)
+ row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='POINT')
+ row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='STROKE')
+
+ subrow = row.row(align=True)
+ subrow.enabled = not gpd.use_curve_edit
+ subrow.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='SEGMENT')
+
+ # Curve edit submode
+ row = layout.row(align=True)
+ row.prop(gpd, "use_curve_edit", text="",
+ icon='IPO_BEZIER')
+ sub = row.row(align=True)
+ sub.active = gpd.use_curve_edit
+ sub.popover(
+ panel="VIEW3D_PT_gpencil_curve_edit",
+ text="Curve Editing",
+ )
# Select mode for Sculpt
if gpd.is_stroke_sculpt_mode:
@@ -687,7 +703,7 @@ class VIEW3D_HT_header(Header):
row.prop(gpd, "use_multiedit", text="", icon='GP_MULTIFRAME_EDITING')
sub = row.row(align=True)
- sub.active = gpd.use_multiedit
+ sub.enabled = gpd.use_multiedit
sub.popover(
panel="VIEW3D_PT_gpencil_multi_frame",
text="Multiframe",
@@ -6802,6 +6818,12 @@ class VIEW3D_PT_overlay_gpencil_options(Panel):
layout.prop(overlay, "vertex_opacity", text="Vertex Opacity", slider=True)
+ # Handles for Curve Edit
+ if context.object.mode == 'EDIT_GPENCIL':
+ gpd = context.object.data
+ if gpd.use_curve_edit:
+ layout.prop(overlay, "display_handle", text="Handles")
+
if context.object.mode in {'PAINT_GPENCIL', 'VERTEX_GPENCIL'}:
layout.label(text="Vertex Paint")
row = layout.row()
@@ -6965,6 +6987,24 @@ class VIEW3D_PT_gpencil_multi_frame(Panel):
layout.template_curve_mapping(settings, "multiframe_falloff_curve", brush=True)
+# Grease Pencil Object - Curve Editing tools
+class VIEW3D_PT_gpencil_curve_edit(Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'HEADER'
+ bl_label = "Curve Editing"
+
+ def draw(self, context):
+ gpd = context.gpencil_data
+ settings = context.tool_settings.gpencil_sculpt
+
+ layout = self.layout
+ col = layout.column(align=True)
+ col.prop(gpd, "edit_curve_resolution")
+ col.prop(gpd, "curve_edit_threshold")
+ col.prop(gpd, "curve_edit_corner_angle")
+ col.prop(gpd, "use_adaptive_curve_resolution")
+
+
class VIEW3D_MT_gpencil_edit_context_menu(Menu):
bl_label = ""
@@ -7606,6 +7646,7 @@ classes = (
VIEW3D_PT_grease_pencil,
VIEW3D_PT_annotation_onion,
VIEW3D_PT_gpencil_multi_frame,
+ VIEW3D_PT_gpencil_curve_edit,
VIEW3D_PT_quad_view,
VIEW3D_PT_view3d_stereo,
VIEW3D_PT_shading,
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;
}
@@ -197,6 +209,23 @@ static GPUVertFormat *gpencil_edit_stroke_format(void)
}
/* 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 */
float fcol[4]; /* Fill 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,11 +2844,118 @@ void gpencil_stroke_delete_tagged_points(bGPDframe *gpf,
BKE_gpencil_free_stroke(gps);
}
+static void gpencil_curve_delete_tagged_points(bGPdata *gpd,
+ bGPDframe *gpf,
+ bGPDstroke *gps,
+ bGPDstroke *next_stroke,
+ bGPDcurve *gpc,
+ int tag_flags)
+{
+ 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;
@@ -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;
+ }
+ }
+
+ 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);
- 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 <math.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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);
@@ -693,6 +699,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)
* to perform some operations on the stroke.
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,17 +1306,119 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *UNUSED(gpd),
}
}
+ /* 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 hit;
+}
+
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++) {
+ /* 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;
+ }
+ }
+
+ /* 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 */
@@ -166,6 +171,61 @@ typedef enum eGPDpalette_Flag {
} 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 */
/* Runtime temp data for bGPDstroke */
@@ -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);
}