diff options
25 files changed, 993 insertions, 492 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index fd9d699ed01..69582849308 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -6757,6 +6757,15 @@ def km_3d_view_tool_paint_gpencil_eyedropper(params): ]}, ) +def km_3d_view_tool_paint_gpencil_interpolate(params): + return ( + "3D View Tool: Paint Gpencil, Interpolate", + {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, + {"items": [ + ("gpencil.interpolate", {"type": params.tool_tweak, "value": 'ANY'}, + {"properties": [("release_confirm", True)]}), + ]}, + ) def km_3d_view_tool_edit_gpencil_select(params): return ( @@ -6858,6 +6867,17 @@ def km_3d_view_tool_edit_gpencil_transform_fill(params): ) +def km_3d_view_tool_edit_gpencil_interpolate(params): + return ( + "3D View Tool: Edit Gpencil, Interpolate", + {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, + {"items": [ + ("gpencil.interpolate", {"type": params.tool_tweak, "value": 'ANY'}, + {"properties": [("release_confirm", True)]}), + ]}, + ) + + def km_3d_view_tool_sculpt_gpencil_select(params): return ( "3D View Tool: Sculpt Gpencil, Tweak", @@ -7173,6 +7193,7 @@ def generate_keymaps(params=None): km_3d_view_tool_paint_gpencil_curve(params), km_3d_view_tool_paint_gpencil_cutter(params), km_3d_view_tool_paint_gpencil_eyedropper(params), + km_3d_view_tool_paint_gpencil_interpolate(params), km_3d_view_tool_edit_gpencil_select(params), km_3d_view_tool_edit_gpencil_select_box(params), km_3d_view_tool_edit_gpencil_select_circle(params), @@ -7183,6 +7204,7 @@ def generate_keymaps(params=None): km_3d_view_tool_edit_gpencil_shear(params), km_3d_view_tool_edit_gpencil_to_sphere(params), km_3d_view_tool_edit_gpencil_transform_fill(params), + km_3d_view_tool_edit_gpencil_interpolate(params), km_3d_view_tool_sculpt_gpencil_select(params), km_3d_view_tool_sculpt_gpencil_select_box(params), km_3d_view_tool_sculpt_gpencil_select_circle(params), diff --git a/release/scripts/startup/bl_ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py index aa98e4292f4..39d232b2871 100644 --- a/release/scripts/startup/bl_ui/space_dopesheet.py +++ b/release/scripts/startup/bl_ui/space_dopesheet.py @@ -322,7 +322,7 @@ class DOPESHEET_MT_editor_menus(Menu): if st.mode != 'GPENCIL': layout.menu("DOPESHEET_MT_key") else: - layout.menu("DOPESHEET_MT_gpencil_frame") + layout.menu("DOPESHEET_MT_gpencil_key") class DOPESHEET_MT_view(Menu): @@ -558,8 +558,8 @@ class DOPESHEET_MT_gpencil_channel(Menu): layout.operator_menu_enum("anim.channels_move", "direction", text="Move...") -class DOPESHEET_MT_gpencil_frame(Menu): - bl_label = "Frame" +class DOPESHEET_MT_gpencil_key(Menu): + bl_label = "Key" def draw(self, _context): layout = self.layout @@ -569,15 +569,14 @@ class DOPESHEET_MT_gpencil_frame(Menu): layout.operator_menu_enum("action.mirror", "type", text="Mirror") layout.separator() - layout.operator("action.duplicate") - layout.operator("action.delete") + layout.operator("action.keyframe_insert") layout.separator() - layout.operator("action.keyframe_type") + layout.operator("action.delete") + layout.operator("gpencil.interpolate_reverse") - # layout.separator() - # layout.operator("action.copy") - # layout.operator("action.paste") + layout.separator() + layout.operator("action.keyframe_type", text="Keyframe Type") class DOPESHEET_MT_delete(Menu): @@ -761,7 +760,7 @@ classes = ( DOPESHEET_MT_key, DOPESHEET_MT_key_transform, DOPESHEET_MT_gpencil_channel, - DOPESHEET_MT_gpencil_frame, + DOPESHEET_MT_gpencil_key, DOPESHEET_MT_delete, DOPESHEET_MT_context_menu, DOPESHEET_MT_channel_context_menu, diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 68cded82ce3..75bca09a044 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2035,6 +2035,26 @@ class _defs_gpencil_paint: draw_settings=draw_settings, ) + @ToolDef.from_fn + def interpolate(): + def draw_settings(context, layout, tool): + props = tool.operator_properties("gpencil.interpolate") + row = layout.row() + row.prop(props, "layers") + row.prop(props, "flip") + row.prop(props, "smooth_factor") + row.prop(props, "smooth_steps") + + return dict( + idname="builtin.interpolate", + label="Interpolate", + icon="ops.pose.breakdowner", + cursor='DEFAULT', + widget=None, + keymap=(), + draw_settings=draw_settings, + ) + class _defs_gpencil_edit: def is_segment(context): @@ -2198,6 +2218,27 @@ class _defs_gpencil_edit: draw_settings=draw_settings, ) + @ToolDef.from_fn + def interpolate(): + def draw_settings(context, layout, tool): + props = tool.operator_properties("gpencil.interpolate") + row = layout.row() + row.prop(props, "layers") + row.prop(props, "interpolate_selected_only") + row.prop(props, "flip") + row.prop(props, "smooth_factor") + row.prop(props, "smooth_steps") + + return dict( + idname="builtin.interpolate", + label="Interpolate", + icon="ops.pose.breakdowner", + cursor='DEFAULT', + widget=None, + keymap=(), + draw_settings=draw_settings, + ) + class _defs_gpencil_sculpt: @@ -2877,6 +2918,8 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): _defs_gpencil_paint.box, _defs_gpencil_paint.circle, None, + _defs_gpencil_paint.interpolate, + None, *_tools_annotate, ], 'EDIT_GPENCIL': [ @@ -2892,9 +2935,10 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): _defs_gpencil_edit.shear, _defs_gpencil_edit.tosphere, ), - None, _defs_gpencil_edit.transform_fill, None, + _defs_gpencil_edit.interpolate, + None, *_tools_annotate, ], 'SCULPT_GPENCIL': [ diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 0fe16720d81..43866add7dd 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -715,13 +715,6 @@ class VIEW3D_HT_header(Header): text="Multiframe", ) - if gpd.use_stroke_edit_mode or gpd.is_stroke_paint_mode: - row = layout.row(align=True) - row.popover( - panel="VIEW3D_PT_tools_grease_pencil_interpolate", - text="Interpolate", - ) - overlay = view.overlay VIEW3D_MT_editor_menus.draw_collapsible(context, layout) diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index f93a6f3346b..257b9edf4e8 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1693,58 +1693,6 @@ class VIEW3D_PT_tools_grease_pencil_brush_paint_falloff(GreasePencilBrushFalloff return (settings and settings.brush and settings.brush.curve and tool == 'TINT') -# Grease Pencil stroke interpolation tools -class VIEW3D_PT_tools_grease_pencil_interpolate(Panel): - bl_space_type = 'VIEW_3D' - bl_region_type = 'HEADER' - bl_label = "Interpolate" - - @classmethod - def poll(cls, context): - if context.gpencil_data is None: - return False - - gpd = context.gpencil_data - valid_mode = bool(gpd.use_stroke_edit_mode or gpd.is_stroke_paint_mode) - return bool(context.editable_gpencil_strokes) and valid_mode - - def draw(self, context): - layout = self.layout - settings = context.tool_settings.gpencil_interpolate - - col = layout.column(align=True) - col.label(text="Interpolate Strokes") - col.operator("gpencil.interpolate", text="Interpolate") - col.operator("gpencil.interpolate_sequence", text="Sequence") - col.operator("gpencil.interpolate_reverse", text="Remove Breakdowns") - - col = layout.column(align=True) - col.label(text="Options:") - col.prop(settings, "interpolate_all_layers") - - gpd = context.gpencil_data - if gpd.use_stroke_edit_mode: - col.prop(settings, "interpolate_selected_only") - - col = layout.column(align=True) - col.label(text="Sequence Options:") - col.prop(settings, "step") - col.prop(settings, "type") - if settings.type == 'CUSTOM': - # TODO: Options for loading/saving curve presets? - col.template_curve_mapping(settings, "interpolation_curve", brush=True, - use_negative_slope=True) - elif settings.type != 'LINEAR': - col.prop(settings, "easing") - - if settings.type == 'BACK': - layout.prop(settings, "back") - elif settings.type == 'ELASTIC': - sub = layout.column(align=True) - sub.prop(settings, "amplitude") - sub.prop(settings, "period") - - # Grease Pencil stroke sculpting tools class GreasePencilSculptPanel: bl_context = ".greasepencil_sculpt" @@ -2266,7 +2214,6 @@ classes = ( VIEW3D_PT_tools_grease_pencil_vertex_paint_select, VIEW3D_PT_tools_grease_pencil_vertex_paint_settings, VIEW3D_PT_tools_grease_pencil_vertex_appearance, - VIEW3D_PT_tools_grease_pencil_interpolate, VIEW3D_PT_tools_grease_pencil_brush_mixcolor, VIEW3D_PT_tools_grease_pencil_brush_mix_palette, diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index a8caf317467..b9dba991d64 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -101,8 +101,11 @@ void BKE_gpencil_tag(struct bGPdata *gpd); 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); +void BKE_gpencil_stroke_sync_selection(struct bGPdata *gpd, struct bGPDstroke *gps); +void BKE_gpencil_curve_sync_selection(struct bGPdata *gpd, struct bGPDstroke *gps); +void BKE_gpencil_stroke_select_index_set(struct bGPdata *gpd, + struct bGPDstroke *gps, + const bool reset); struct bGPDframe *BKE_gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe); struct bGPDframe *BKE_gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe); diff --git a/source/blender/blenkernel/BKE_gpencil_curve.h b/source/blender/blenkernel/BKE_gpencil_curve.h index 2d42bb36949..9cbe67af9c1 100644 --- a/source/blender/blenkernel/BKE_gpencil_curve.h +++ b/source/blender/blenkernel/BKE_gpencil_curve.h @@ -50,8 +50,12 @@ struct bGPDcurve *BKE_gpencil_stroke_editcurve_generate(struct bGPDstroke *gps, 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_editcurve_stroke_sync_selection(struct bGPdata *gpd, + struct bGPDstroke *gps, + struct bGPDcurve *gpc); +void BKE_gpencil_stroke_editcurve_sync_selection(struct bGPdata *gpd, + 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, diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 1aec1ad296d..4167f60e880 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -1134,7 +1134,7 @@ bGPdata *BKE_gpencil_data_duplicate(Main *bmain, const bGPdata *gpd_src, bool in * Ensure selection status of stroke is in sync with its points. * \param gps: Grease pencil stroke */ -void BKE_gpencil_stroke_sync_selection(bGPDstroke *gps) +void BKE_gpencil_stroke_sync_selection(bGPdata *gpd, bGPDstroke *gps) { bGPDspoint *pt; int i; @@ -1148,6 +1148,7 @@ void BKE_gpencil_stroke_sync_selection(bGPDstroke *gps) * so initially, we must deselect */ gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (pt->flag & GP_SPOINT_SELECT) { @@ -1155,9 +1156,13 @@ void BKE_gpencil_stroke_sync_selection(bGPDstroke *gps) break; } } + + if (gps->flag & GP_STROKE_SELECT) { + BKE_gpencil_stroke_select_index_set(gpd, gps, false); + } } -void BKE_gpencil_curve_sync_selection(bGPDstroke *gps) +void BKE_gpencil_curve_sync_selection(bGPdata *gpd, bGPDstroke *gps) { bGPDcurve *gpc = gps->editcurve; if (gpc == NULL) { @@ -1165,6 +1170,7 @@ void BKE_gpencil_curve_sync_selection(bGPDstroke *gps) } gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); gpc->flag &= ~GP_CURVE_SELECT; bool is_selected = false; @@ -1187,6 +1193,19 @@ void BKE_gpencil_curve_sync_selection(bGPDstroke *gps) if (is_selected) { gpc->flag |= GP_CURVE_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); + } +} + +/* Assign unique stroke ID for selection. */ +void BKE_gpencil_stroke_select_index_set(bGPdata *gpd, bGPDstroke *gps, const bool reset) +{ + if (!reset) { + gpd->select_last_index++; + gps->select_index = gpd->select_last_index; + } + else { + gps->select_index = 0; } } @@ -2512,6 +2531,11 @@ bool BKE_gpencil_from_image( pt->flag |= GP_SPOINT_SELECT; } } + + if (gps->flag & GP_STROKE_SELECT) { + BKE_gpencil_stroke_select_index_set(gpd, gps, false); + } + 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 be14d74de7a..6707fb53aa3 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -781,7 +781,7 @@ void BKE_gpencil_stroke_editcurve_update(bGPdata *gpd, bGPDlayer *gpl, bGPDstrok /** * Sync the selection from stroke to editcurve */ -void BKE_gpencil_editcurve_stroke_sync_selection(bGPDstroke *gps, bGPDcurve *gpc) +void BKE_gpencil_editcurve_stroke_sync_selection(bGPdata *gpd, bGPDstroke *gps, bGPDcurve *gpc) { if (gps->flag & GP_STROKE_SELECT) { gpc->flag |= GP_CURVE_SELECT; @@ -808,10 +808,11 @@ void BKE_gpencil_editcurve_stroke_sync_selection(bGPDstroke *gps, bGPDcurve *gpc /** * Sync the selection from editcurve to stroke */ -void BKE_gpencil_stroke_editcurve_sync_selection(bGPDstroke *gps, bGPDcurve *gpc) +void BKE_gpencil_stroke_editcurve_sync_selection(bGPdata *gpd, bGPDstroke *gps, bGPDcurve *gpc) { if (gpc->flag & GP_CURVE_SELECT) { gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); for (int i = 0; i < gpc->tot_curve_points - 1; i++) { bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; @@ -865,6 +866,7 @@ void BKE_gpencil_stroke_editcurve_sync_selection(bGPDstroke *gps, bGPDcurve *gpc } else { gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); for (int i = 0; i < gps->totpoints; i++) { bGPDspoint *pt = &gps->points[i]; pt->flag &= ~GP_SPOINT_SELECT; @@ -1089,6 +1091,7 @@ void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps, /* deselect */ pt->flag &= ~GP_SPOINT_SELECT; gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); return; } @@ -1131,6 +1134,7 @@ void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps, pt->flag &= ~GP_SPOINT_SELECT; } gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); /* free temp data */ MEM_freeN(points); @@ -1375,7 +1379,7 @@ void BKE_gpencil_strokes_selected_update_editcurve(bGPdata *gpd) 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); + BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve); gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; BKE_gpencil_stroke_geometry_update(gpd, gps); @@ -1400,7 +1404,7 @@ void BKE_gpencil_strokes_selected_sync_selection_editcurve(bGPdata *gpd) 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); + BKE_gpencil_stroke_editcurve_sync_selection(gpd, gps, gpc); } } } diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index 2ef85439a46..2d10645a467 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -3444,6 +3444,7 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd, if (select) { gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); } /* Free the sample points. Important to use the mutable loop here because we are erasing the list diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 25951fa3e6f..c7279dbc815 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -167,8 +167,6 @@ static void scene_init_data(ID *id) &gp_primitive_curve->clipr, CURVE_PRESET_BELL, CURVEMAP_SLOPE_POSITIVE); - /* Grease pencil interpolate. */ - scene->toolsettings->gp_interpolate.step = 1; scene->unit.system = USER_UNIT_METRIC; scene->unit.scale_length = 1.0f; diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index 728235e84bf..29041f4ae9d 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -1061,14 +1061,6 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) } } - /* Set the minimum sequence interpolate for grease pencil. */ - if (!DNA_struct_elem_find(fd->filesdna, "GP_Interpolate_Settings", "int", "step")) { - LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { - ToolSettings *ts = scene->toolsettings; - ts->gp_interpolate.step = 1; - } - } - /* Hair and PointCloud attributes. */ for (Hair *hair = bmain->hairs.first; hair != NULL; hair = hair->id.next) { do_versions_point_attributes(&hair->pdata); diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index 4c9bde77103..0597de445f4 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -3418,9 +3418,11 @@ static int gpencil_material_select_exec(bContext *C, wmOperator *op) if (!deselected) { gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); } else { gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (!deselected) { diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index bf1aff5a34a..aec593b97ea 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -214,7 +214,7 @@ static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *op) 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); + BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve); gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; BKE_gpencil_stroke_geometry_update(gpd, gps); @@ -992,6 +992,7 @@ static int gpencil_duplicate_exec(bContext *C, wmOperator *op) pt->flag &= ~GP_SPOINT_SELECT; } gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); changed = true; } @@ -1193,6 +1194,7 @@ static void gpencil_add_move_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gp /* if the stroke is not reused, deselect */ if (!do_stroke) { gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } } @@ -1709,6 +1711,7 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) } gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } CTX_DATA_END; @@ -2548,6 +2551,7 @@ static bool gpencil_dissolve_selected_stroke_points(bContext *C, /* deselect the stroke, since none of its selected points will still be selected */ gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { pt->flag &= ~GP_SPOINT_SELECT; } @@ -2620,6 +2624,7 @@ static int gpencil_delete_selected_points(bContext *C) if (gps->flag & GP_STROKE_SELECT) { /* deselect old stroke, since it will be used as template for the new strokes */ gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); if (is_curve_edit) { bGPDcurve *gpc = gps->editcurve; @@ -3790,7 +3795,7 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) 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); + BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve); gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; BKE_gpencil_stroke_geometry_update(gpd, gps); @@ -4574,6 +4579,7 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) else if (mode == GP_SEPARATE_STROKE) { /* deselect old stroke */ gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); /* unlink from source frame */ BLI_remlink(&gpf->strokes, gps); gps->prev = gps->next = NULL; @@ -4982,6 +4988,7 @@ static int gpencil_cutter_lasso_select(bContext *C, } gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } CTX_DATA_END; @@ -5022,6 +5029,7 @@ static int gpencil_cutter_lasso_select(bContext *C, changed = true; pt->flag |= GP_SPOINT_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); float r_hita[3], r_hitb[3]; if (gps->totpoints > 1) { ED_gpencil_select_stroke_segment( diff --git a/source/blender/editors/gpencil/gpencil_edit_curve.c b/source/blender/editors/gpencil/gpencil_edit_curve.c index 031bbd61173..0f9a8c93df9 100644 --- a/source/blender/editors/gpencil/gpencil_edit_curve.c +++ b/source/blender/editors/gpencil/gpencil_edit_curve.c @@ -88,7 +88,7 @@ static int gpencil_stroke_enter_editcurve_mode_exec(bContext *C, wmOperator *op) (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); + BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve); gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; BKE_gpencil_stroke_geometry_update(gpd, gps); } diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 454f8d21590..c6f74c39beb 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -103,55 +103,6 @@ typedef struct tGPDdraw { float diff_mat[4][4]; /* matrix */ } tGPDdraw; -/* Temporary interpolate operation data */ -typedef struct tGPDinterpolate_layer { - struct tGPDinterpolate_layer *next, *prev; - - /** layer */ - struct bGPDlayer *gpl; - /** frame before current frame (interpolate-from) */ - struct bGPDframe *prevFrame; - /** frame after current frame (interpolate-to) */ - struct bGPDframe *nextFrame; - /** interpolated frame */ - struct bGPDframe *interFrame; - /** interpolate factor */ - float factor; - -} tGPDinterpolate_layer; - -typedef struct tGPDinterpolate { - /** Current depsgraph from context */ - struct Depsgraph *depsgraph; - /** current scene from context */ - struct Scene *scene; - /** area where painting originated */ - struct ScrArea *area; - /** region where painting originated */ - struct ARegion *region; - /** current GP datablock */ - struct bGPdata *gpd; - /** current material */ - struct Material *mat; - - /** current frame number */ - int cframe; - /** (tGPDinterpolate_layer) layers to be interpolated */ - ListBase ilayers; - /** value for determining the displacement influence */ - float shift; - /** initial interpolation factor for active layer */ - float init_factor; - /** shift low limit (-100%) */ - float low_limit; - /** shift upper limit (200%) */ - float high_limit; - /** flag from toolsettings */ - int flag; - - NumInput num; /* numeric input */ -} tGPDinterpolate; - /* Modal Operator Drawing Callbacks ------------------------ */ void ED_gpencil_draw_fill(struct tGPDdraw *tgpw); diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index ecd243ed595..6a2eae934a2 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -32,6 +32,7 @@ #include "BLI_blenlib.h" #include "BLI_easing.h" +#include "BLI_ghash.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -53,6 +54,7 @@ #include "BKE_report.h" #include "UI_interface.h" +#include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" @@ -67,6 +69,78 @@ #include "gpencil_intern.h" +/* Temporary interpolate operation data */ +typedef struct tGPDinterpolate_layer { + struct tGPDinterpolate_layer *next, *prev; + + /** layer */ + struct bGPDlayer *gpl; + /** frame before current frame (interpolate-from) */ + struct bGPDframe *prevFrame; + /** frame after current frame (interpolate-to) */ + struct bGPDframe *nextFrame; + /** interpolated frame */ + struct bGPDframe *interFrame; + /** interpolate factor */ + float factor; + + /* Hash tablets to create temp relationship between strokes. */ + struct GHash *used_strokes; + struct GHash *pair_strokes; + +} tGPDinterpolate_layer; + +typedef struct tGPDinterpolate { + /** Current depsgraph from context */ + struct Depsgraph *depsgraph; + /** current scene from context */ + struct Scene *scene; + /** area where painting originated */ + struct ScrArea *area; + /** region where painting originated */ + struct ARegion *region; + /** current object */ + struct Object *ob; + /** current GP datablock */ + struct bGPdata *gpd; + /** current material */ + struct Material *mat; + /* Space Conversion Data */ + struct GP_SpaceConversion gsc; + + /** current frame number */ + int cframe; + /** (tGPDinterpolate_layer) layers to be interpolated */ + ListBase ilayers; + /** value for determining the displacement influence */ + float shift; + /** initial interpolation factor for active layer */ + float init_factor; + /** shift low limit (-100%) */ + float low_limit; + /** shift upper limit (200%) */ + float high_limit; + /** flag from toolsettings */ + int flag; + /** Flip mode. */ + int flipmode; + /** smooth factor */ + float smooth_factor; + /** smooth iterations */ + int smooth_steps; + + NumInput num; /* numeric input */ +} tGPDinterpolate; + +typedef enum eGP_InterpolateFlipMode { + /* No flip. */ + GP_INTERPOLATE_NOFLIP = 0, + /* Flip always. */ + GP_INTERPOLATE_FLIP = 1, + /* Flip if needed. */ + GP_INTERPOLATE_FLIPAUTO = 2, +} eGP_InterpolateFlipMode; + /* ************************************************ */ /* Core/Shared Utilities */ @@ -79,17 +153,150 @@ static bool gpencil_view3d_poll(bContext *C) /* only 3D view */ ScrArea *area = CTX_wm_area(C); if (area && area->spacetype != SPACE_VIEW3D) { - return 0; + return false; } /* need data to interpolate */ if (ELEM(NULL, gpd, gpl)) { - return 0; + return false; } - return 1; + return true; +} + +/* Return if the stroke must be flipped or not. The logic of the calculation + * is to check if the lines from extremes crossed. All is done in 2D. */ +static bool gpencil_stroke_need_flip(Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + GP_SpaceConversion *gsc, + bGPDstroke *gps_from, + bGPDstroke *gps_to) +{ + float diff_mat[4][4]; + /* calculate parent matrix */ + BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat); + bGPDspoint *pt, pt_dummy_ps; + float v1a[2], v1b[2], v2a[2], v2b[2]; + + /* Line from start of strokes. */ + pt = &gps_from->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v1a[0], &v1a[1]); + + pt = &gps_to->points[0]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v1b[0], &v1b[1]); + + /* Line from end of strokes. */ + pt = &gps_from->points[gps_from->totpoints - 1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v2a[0], &v2a[1]); + + pt = &gps_to->points[gps_to->totpoints - 1]; + gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps); + gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v2b[0], &v2b[1]); + + if (isect_seg_seg_v2(v1a, v1b, v2a, v2b) == ISECT_LINE_LINE_CROSS) { + return true; + } + + return false; +} + +/* Return the stroke related to the selection index, returning the stroke with + * the smallest selection index greater than reference index. */ +static bGPDstroke *gpencil_stroke_get_related(GHash *used_strokes, + bGPDframe *gpf, + const int reference_index) +{ + bGPDstroke *gps_found = NULL; + int lower_index = INT_MAX; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->select_index > reference_index) { + if (!BLI_ghash_haskey(used_strokes, gps)) { + if (gps->select_index < lower_index) { + lower_index = gps->select_index; + gps_found = gps; + } + } + } + } + + /* Set as used. */ + if (gps_found) { + BLI_ghash_insert(used_strokes, gps_found, gps_found); + } + + return gps_found; +} + +/* Load a Hash with the relationship between strokes. */ +static void gpencil_stroke_pair_table(bContext *C, + tGPDinterpolate *tgpi, + tGPDinterpolate_layer *tgpil) +{ + bGPdata *gpd = tgpi->gpd; + const bool only_selected = ((GPENCIL_EDIT_MODE(gpd)) && + ((tgpi->flag & GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED) != 0)); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + + /* Create hash tablets with relationship between strokes. */ + tgpil->used_strokes = BLI_ghash_ptr_new(__func__); + tgpil->pair_strokes = BLI_ghash_ptr_new(__func__); + + /* Create a table with source and target pair of strokes. */ + LISTBASE_FOREACH (bGPDstroke *, gps_from, &tgpil->prevFrame->strokes) { + bGPDstroke *gps_to = NULL; + /* only selected */ + if ((GPENCIL_EDIT_MODE(gpd)) && (only_selected) && + ((gps_from->flag & GP_STROKE_SELECT) == 0)) { + continue; + } + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps_from) == false) { + continue; + } + /* Check if the material is editable. */ + if (ED_gpencil_stroke_material_editable(tgpi->ob, tgpil->gpl, gps_from) == false) { + continue; + } + /* Try to get the related stroke. */ + if ((is_multiedit) && (gps_from->select_index > 0)) { + gps_to = gpencil_stroke_get_related( + tgpil->used_strokes, tgpil->nextFrame, gps_from->select_index); + } + /* If not found, get final stroke to interpolate using position in the array. */ + if (gps_to == NULL) { + int fFrame = BLI_findindex(&tgpil->prevFrame->strokes, gps_from); + gps_to = BLI_findlink(&tgpil->nextFrame->strokes, fFrame); + } + + if (ELEM(NULL, gps_from, gps_to)) { + continue; + } + /* Insert the pair entry in the hash table. */ + BLI_ghash_insert(tgpil->pair_strokes, gps_from, gps_to); + } } +static void gpencil_interpolate_smooth_stroke(bGPDstroke *gps, + float smooth_factor, + int smooth_steps) +{ + if (smooth_factor == 0.0f) { + return; + } + + float reduce = 0.0f; + for (int r = 0; r < smooth_steps; r++) { + for (int i = 0; i < gps->totpoints - 1; i++) { + BKE_gpencil_stroke_smooth(gps, i, smooth_factor - reduce); + BKE_gpencil_stroke_smooth_strength(gps, i, smooth_factor); + } + reduce += 0.25f; /* reduce the factor */ + } +} /* Perform interpolation */ static void gpencil_interpolate_update_points(const bGPDstroke *gps_from, const bGPDstroke *gps_to, @@ -112,7 +319,7 @@ static void gpencil_interpolate_update_points(const bGPDstroke *gps_from, /* ****************** Interpolate Interactive *********************** */ /* Helper: free all temp strokes for display. */ -static void gpencil_interpolate_free_temp_strokes(bGPDframe *gpf) +static void gpencil_interpolate_free_tagged_strokes(bGPDframe *gpf) { if (gpf == NULL) { return; @@ -152,33 +359,34 @@ static void gpencil_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgp const float factor = tgpil->factor + shift; bGPDframe *gpf = tgpil->gpl->actframe; - /* Free temp strokes. */ - gpencil_interpolate_free_temp_strokes(gpf); + /* Free temp strokes used for display. */ + gpencil_interpolate_free_tagged_strokes(gpf); - LISTBASE_FOREACH (bGPDstroke *, new_stroke, &tgpil->interFrame->strokes) { - bGPDstroke *gps_from, *gps_to; - int stroke_idx; + /* Clear previous interpolations. */ + gpencil_interpolate_free_tagged_strokes(tgpil->interFrame); - if (new_stroke->totpoints == 0) { - continue; - } + GHashIterator gh_iter; + GHASH_ITER (gh_iter, tgpil->pair_strokes) { + bGPDstroke *gps_from = (bGPDstroke *)BLI_ghashIterator_getKey(&gh_iter); + bGPDstroke *gps_to = (bGPDstroke *)BLI_ghashIterator_getValue(&gh_iter); + /* Create new stroke. */ + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); + new_stroke->flag |= GP_STROKE_TAG; + new_stroke->select_index = 0; - /* get strokes to interpolate */ - stroke_idx = BLI_findindex(&tgpil->interFrame->strokes, new_stroke); + /* Update points position. */ + gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); - gps_from = BLI_findlink(&tgpil->prevFrame->strokes, stroke_idx); - gps_to = BLI_findlink(&tgpil->nextFrame->strokes, stroke_idx); - - /* update points position */ - if ((gps_from) && (gps_to)) { - gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + /* Add to strokes. */ + BLI_addtail(&tgpil->interFrame->strokes, new_stroke); - /* Add temp strokes. */ - if (gpf) { - bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true, true); - gps_eval->flag |= GP_STROKE_TAG; - BLI_addtail(&gpf->strokes, gps_eval); - } + /* Add temp strokes to display. */ + if (gpf) { + bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true, true); + gps_eval->flag |= GP_STROKE_TAG; + BLI_addtail(&gpf->strokes, gps_eval); } } } @@ -187,65 +395,50 @@ static void gpencil_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgp WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); } -/* Helper: Verify valid strokes for interpolation */ -static bool gpencil_interpolate_check_todo(bContext *C, bGPdata *gpd) +/* Helper: Get previous keyframe. */ +static bGPDframe *gpencil_get_previous_keyframe(bGPDlayer *gpl, int cfra) { - Object *ob = CTX_data_active_object(C); - ToolSettings *ts = CTX_data_tool_settings(C); - eGP_Interpolate_SettingsFlag flag = ts->gp_interpolate.flag; + if (gpl->actframe != NULL && gpl->actframe->framenum < cfra && + gpl->actframe->key_type == BEZT_KEYTYPE_KEYFRAME) { + return gpl->actframe; + } - /* get layers */ - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - /* all layers or only active */ - if (!(flag & GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS) && !(gpl->flag & GP_LAYER_ACTIVE)) { + LISTBASE_FOREACH_BACKWARD (bGPDframe *, gpf, &gpl->frames) { + if (gpf->key_type != BEZT_KEYTYPE_KEYFRAME) { continue; } - /* only editable and visible layers are considered */ - if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { + if (gpf->framenum >= cfra) { continue; } + return gpf; + } - /* read strokes */ - LISTBASE_FOREACH (bGPDstroke *, gps_from, &gpl->actframe->strokes) { - bGPDstroke *gps_to; - int fFrame; - - /* only selected */ - if ((GPENCIL_EDIT_MODE(gpd)) && (flag & GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED) && - ((gps_from->flag & GP_STROKE_SELECT) == 0)) { - continue; - } - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps_from) == false) { - continue; - } - /* check if the color is editable */ - if (ED_gpencil_stroke_material_editable(ob, gpl, gps_from) == false) { - continue; - } - - /* get final stroke to interpolate */ - fFrame = BLI_findindex(&gpl->actframe->strokes, gps_from); - gps_to = (gpl->actframe->next != NULL) ? - BLI_findlink(&gpl->actframe->next->strokes, fFrame) : - NULL; - if (gps_to == NULL) { - continue; - } + return NULL; +} - return true; +/* Helper: Get next keyframe. */ +static bGPDframe *gpencil_get_next_keyframe(bGPDlayer *gpl, int cfra) +{ + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + if (gpf->key_type != BEZT_KEYTYPE_KEYFRAME) { + continue; + } + if (gpf->framenum <= cfra) { + continue; } + return gpf; } - return false; + + return NULL; } /* Helper: Create internal strokes interpolated */ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) { + Scene *scene = tgpi->scene; bGPdata *gpd = tgpi->gpd; bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C); bGPDframe *actframe = active_gpl->actframe; - Object *ob = CTX_data_active_object(C); /* save initial factor for active layer to define shift limits */ tgpi->init_factor = (float)(tgpi->cframe - actframe->framenum) / @@ -274,12 +467,15 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) tgpil = MEM_callocN(sizeof(tGPDinterpolate_layer), "GPencil Interpolate Layer"); tgpil->gpl = gpl; - tgpil->prevFrame = BKE_gpencil_frame_duplicate(gpl->actframe, true); - tgpil->nextFrame = BKE_gpencil_frame_duplicate(gpl->actframe->next, true); + bGPDframe *gpf = gpencil_get_previous_keyframe(gpl, CFRA); + tgpil->prevFrame = BKE_gpencil_frame_duplicate(gpf, true); + + gpf = gpencil_get_next_keyframe(gpl, CFRA); + tgpil->nextFrame = BKE_gpencil_frame_duplicate(gpf, true); BLI_addtail(&tgpi->ilayers, tgpil); - /* create a new temporary frame */ + /* Create a new temporary frame. */ tgpil->interFrame = MEM_callocN(sizeof(bGPDframe), "bGPDframe"); tgpil->interFrame->framenum = tgpi->cframe; @@ -287,62 +483,42 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) tgpil->factor = (float)(tgpi->cframe - tgpil->prevFrame->framenum) / (tgpil->nextFrame->framenum - tgpil->prevFrame->framenum + 1); - /* create new strokes data with interpolated points reading original stroke */ - LISTBASE_FOREACH (bGPDstroke *, gps_from, &tgpil->prevFrame->strokes) { - bGPDstroke *gps_to; - int fFrame; + /* Load the relationship between frames. */ + gpencil_stroke_pair_table(C, tgpi, tgpil); - bGPDstroke *new_stroke = NULL; - bool valid = true; + /* Create new strokes data with interpolated points reading original stroke. */ + GHashIterator gh_iter; + GHASH_ITER (gh_iter, tgpil->pair_strokes) { + bGPDstroke *gps_from = (bGPDstroke *)BLI_ghashIterator_getKey(&gh_iter); + bGPDstroke *gps_to = (bGPDstroke *)BLI_ghashIterator_getValue(&gh_iter); - /* only selected */ - if ((GPENCIL_EDIT_MODE(gpd)) && (tgpi->flag & GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED) && - ((gps_from->flag & GP_STROKE_SELECT) == 0)) { - valid = false; + /* If destination stroke is smaller, resize new_stroke to size of gps_to stroke. */ + if (gps_from->totpoints > gps_to->totpoints) { + BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true); } - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps_from) == false) { - valid = false; + if (gps_to->totpoints > gps_from->totpoints) { + BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true); } - /* check if the color is editable */ - if (ED_gpencil_stroke_material_editable(ob, tgpil->gpl, gps_from) == false) { - valid = false; + /* Flip stroke. */ + if (tgpi->flipmode == GP_INTERPOLATE_FLIP) { + BKE_gpencil_stroke_flip(gps_to); } - - /* get final stroke to interpolate */ - fFrame = BLI_findindex(&tgpil->prevFrame->strokes, gps_from); - gps_to = BLI_findlink(&tgpil->nextFrame->strokes, fFrame); - if (gps_to == NULL) { - valid = false; - } - - if (valid) { - /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ - if (gps_from->totpoints > gps_to->totpoints) { - BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true); + else if (tgpi->flipmode == GP_INTERPOLATE_FLIPAUTO) { + if (gpencil_stroke_need_flip( + tgpi->depsgraph, tgpi->ob, gpl, &tgpi->gsc, gps_from, gps_to)) { + BKE_gpencil_stroke_flip(gps_to); } - if (gps_to->totpoints > gps_from->totpoints) { - BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true); - } - - /* Create new stroke. */ - new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); - - /* Update points position. */ - gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor); } - else { - /* Create new stroke. */ - new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); - /* need an empty stroke to keep index correct for lookup, but resize to smallest size */ - new_stroke->totpoints = 0; - new_stroke->points = MEM_recallocN(new_stroke->points, sizeof(*new_stroke->points)); - if (new_stroke->dvert != NULL) { - new_stroke->dvert = MEM_recallocN(new_stroke->dvert, sizeof(*new_stroke->dvert)); - } - } + /* Create new stroke. */ + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); + new_stroke->flag |= GP_STROKE_TAG; + new_stroke->select_index = 0; + + /* Update points position. */ + gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor); + gpencil_interpolate_smooth_stroke(new_stroke, tgpi->smooth_factor, tgpi->smooth_steps); /* Calc geometry data. */ BKE_gpencil_stroke_geometry_update(gpd, new_stroke); @@ -429,7 +605,7 @@ static void gpencil_interpolate_exit(bContext *C, wmOperator *op) /* Clear any temp stroke. */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - gpencil_interpolate_free_temp_strokes(gpf); + gpencil_interpolate_free_tagged_strokes(gpf); } } @@ -441,9 +617,18 @@ static void gpencil_interpolate_exit(bContext *C, wmOperator *op) MEM_SAFE_FREE(tgpil->prevFrame); MEM_SAFE_FREE(tgpil->nextFrame); MEM_SAFE_FREE(tgpil->interFrame); + + /* Free Hash tablets. */ + if (tgpil->used_strokes != NULL) { + BLI_ghash_free(tgpil->used_strokes, NULL, NULL); + } + if (tgpil->pair_strokes != NULL) { + BLI_ghash_free(tgpil->pair_strokes, NULL, NULL); + } } BLI_freelistN(&tgpi->ilayers); + MEM_SAFE_FREE(tgpi); } DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -456,24 +641,33 @@ static void gpencil_interpolate_exit(bContext *C, wmOperator *op) /* Init new temporary interpolation data */ static bool gpencil_interpolate_set_init_values(bContext *C, wmOperator *op, tGPDinterpolate *tgpi) { - ToolSettings *ts = CTX_data_tool_settings(C); - bGPdata *gpd = CTX_data_gpencil_data(C); - /* set current scene and window */ tgpi->depsgraph = CTX_data_ensure_evaluated_depsgraph(C); tgpi->scene = CTX_data_scene(C); tgpi->area = CTX_wm_area(C); tgpi->region = CTX_wm_region(C); - tgpi->flag = ts->gp_interpolate.flag; + tgpi->ob = CTX_data_active_object(C); + /* Setup space conversions. */ + gpencil_point_conversion_init(C, &tgpi->gsc); /* set current frame number */ tgpi->cframe = tgpi->scene->r.cfra; /* set GP datablock */ - tgpi->gpd = gpd; - + tgpi->gpd = tgpi->ob->data; /* set interpolation weight */ tgpi->shift = RNA_float_get(op->ptr, "shift"); + SET_FLAG_FROM_TEST( + tgpi->flag, (RNA_enum_get(op->ptr, "layers") == 1), GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS); + SET_FLAG_FROM_TEST( + tgpi->flag, + ((GPENCIL_EDIT_MODE(tgpi->gpd)) && (RNA_boolean_get(op->ptr, "interpolate_selected_only"))), + GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED); + + tgpi->flipmode = RNA_enum_get(op->ptr, "flip"); + + tgpi->smooth_factor = RNA_float_get(op->ptr, "smooth_factor"); + tgpi->smooth_steps = RNA_int_get(op->ptr, "smooth_steps"); /* Untag strokes to be sure nothing is pending due any canceled process. */ LISTBASE_FOREACH (bGPDlayer *, gpl, &tgpi->gpd->layers) { @@ -524,12 +718,13 @@ static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent bGPdata *gpd = CTX_data_gpencil_data(C); bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); Scene *scene = CTX_data_scene(C); - int cfra = CFRA; - bGPDframe *actframe = gpl->actframe; tGPDinterpolate *tgpi = NULL; /* cannot interpolate if not between 2 frames */ - if (ELEM(NULL, actframe, actframe->next)) { + int cfra = CFRA; + bGPDframe *gpf_prv = gpencil_get_previous_keyframe(gpl, cfra); + bGPDframe *gpf_next = gpencil_get_next_keyframe(gpl, cfra); + if (ELEM(NULL, gpf_prv, gpf_next)) { BKE_report( op->reports, RPT_ERROR, @@ -537,25 +732,10 @@ static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent return OPERATOR_CANCELLED; } - /* cannot interpolate in extremes */ - if (ELEM(cfra, actframe->framenum, actframe->next->framenum)) { - BKE_report(op->reports, - RPT_ERROR, - "Cannot interpolate as current frame already has existing grease pencil frames"); - return OPERATOR_CANCELLED; - } - if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { BKE_report(op->reports, RPT_ERROR, "Cannot interpolate in curve edit mode"); return OPERATOR_CANCELLED; } - - /* need editable strokes */ - if (!gpencil_interpolate_check_todo(C, gpd)) { - BKE_report(op->reports, RPT_ERROR, "Interpolation requires some editable strokes"); - return OPERATOR_CANCELLED; - } - /* try to initialize context data needed */ if (!gpencil_interpolate_init(C, op)) { if (op->customdata) { @@ -602,8 +782,7 @@ static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent gpf_dst = BKE_gpencil_layer_frame_get(tgpil->gpl, tgpi->cframe, GP_GETFRAME_ADD_NEW); gpf_dst->key_type = BEZT_KEYTYPE_BREAKDOWN; - /* copy strokes */ - BLI_listbase_clear(&gpf_dst->strokes); + /* Copy strokes. */ LISTBASE_FOREACH (bGPDstroke *, gps_src, &tgpil->interFrame->strokes) { if (gps_src->totpoints == 0) { continue; @@ -710,6 +889,15 @@ static void gpencil_interpolate_cancel(bContext *C, wmOperator *op) void GPENCIL_OT_interpolate(wmOperatorType *ot) { + static const EnumPropertyItem flip_modes[] = { + {GP_INTERPOLATE_NOFLIP, "NOFLIP", 0, "No Flip", ""}, + {GP_INTERPOLATE_FLIP, "FLIP", 0, "Flip", ""}, + {GP_INTERPOLATE_FLIPAUTO, "AUTO", 0, "Automatic", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + /* identifiers */ ot->name = "Grease Pencil Interpolation"; ot->idname = "GPENCIL_OT_interpolate"; @@ -724,6 +912,12 @@ void GPENCIL_OT_interpolate(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; + static const EnumPropertyItem gpencil_interpolation_layer_items[] = { + {0, "ACTIVE", 0, "Active", ""}, + {1, "ALL", 0, "All Layers", ""}, + {0, NULL, 0, NULL, NULL}, + }; + /* properties */ RNA_def_float_factor( ot->srna, @@ -735,25 +929,68 @@ void GPENCIL_OT_interpolate(wmOperatorType *ot) "Bias factor for which frame has more influence on the interpolated strokes", -0.9f, 0.9f); + + RNA_def_enum(ot->srna, + "layers", + gpencil_interpolation_layer_items, + 0, + "Layer", + "Layers included in the interpolation"); + + RNA_def_boolean(ot->srna, + "interpolate_selected_only", + 0, + "Only Selected", + "Interpolate only selected strokes"); + + RNA_def_enum(ot->srna, + "flip", + flip_modes, + GP_INTERPOLATE_FLIPAUTO, + "Flip Mode", + "Invert destination stroke to match start and end with source stroke"); + + RNA_def_int(ot->srna, + "smooth_steps", + 1, + 1, + 3, + "Iterations", + "Number of times to smooth newly created strokes", + 1, + 3); + + RNA_def_float(ot->srna, + "smooth_factor", + 0.0f, + 0.0f, + 2.0f, + "Smooth", + "Amount of smoothing to apply to interpolated strokes, to reduce jitter/noise", + 0.0f, + 2.0f); + + prop = RNA_def_boolean(ot->srna, "release_confirm", 0, "Confirm on Release", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /* ****************** Interpolate Sequence *********************** */ /* Helper: Perform easing equation calculations for GP interpolation operator */ -static float gpencil_interpolate_seq_easing_calc(GP_Interpolate_Settings *ipo_settings, float time) +static float gpencil_interpolate_seq_easing_calc(wmOperator *op, float time) { const float begin = 0.0f; const float change = 1.0f; const float duration = 1.0f; - const float back = ipo_settings->back; - const float amplitude = ipo_settings->amplitude; - const float period = ipo_settings->period; - - eBezTriple_Easing easing = ipo_settings->easing; + const float back = RNA_float_get(op->ptr, "back"); + const float amplitude = RNA_float_get(op->ptr, "amplitude"); + const float period = RNA_float_get(op->ptr, "period"); + const eBezTriple_Easing easing = RNA_enum_get(op->ptr, "easing"); + const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type"); float result = time; - switch (ipo_settings->type) { + switch (type) { case GP_IPO_BACK: switch (easing) { case BEZT_IPO_EASE_IN: @@ -936,7 +1173,7 @@ static float gpencil_interpolate_seq_easing_calc(GP_Interpolate_Settings *ipo_se break; default: - printf("%s: Unknown interpolation type - %d\n", __func__, ipo_settings->type); + printf("%s: Unknown interpolation type\n", __func__); break; } @@ -945,34 +1182,47 @@ static float gpencil_interpolate_seq_easing_calc(GP_Interpolate_Settings *ipo_se static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) { - bGPdata *gpd = CTX_data_gpencil_data(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = CTX_data_tool_settings(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C); - bGPDframe *actframe = active_gpl->actframe; + /* Setup space conversions. */ + GP_SpaceConversion gsc; + gpencil_point_conversion_init(C, &gsc); - Object *ob = CTX_data_active_object(C); - ToolSettings *ts = CTX_data_tool_settings(C); - Scene *scene = CTX_data_scene(C); int cfra = CFRA; GP_Interpolate_Settings *ipo_settings = &ts->gp_interpolate; - eGP_Interpolate_SettingsFlag flag = ipo_settings->flag; - const int step = ipo_settings->step; + const int step = RNA_int_get(op->ptr, "step"); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + const bool all_layers = (bool)(RNA_enum_get(op->ptr, "layers") == 1); + const bool only_selected = ((GPENCIL_EDIT_MODE(gpd)) && + (RNA_boolean_get(op->ptr, "interpolate_selected_only") != 0)); - /* cannot interpolate if not between 2 frames */ - if (ELEM(NULL, actframe, actframe->next)) { + eGP_InterpolateFlipMode flipmode = RNA_enum_get(op->ptr, "flip"); + + const float smooth_factor = RNA_float_get(op->ptr, "smooth_factor"); + const int smooth_steps = RNA_int_get(op->ptr, "smooth_steps"); + + const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type"); + + if (ipo_settings->custom_ipo == NULL) { + ipo_settings->custom_ipo = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + } + BKE_curvemapping_init(ipo_settings->custom_ipo); + + /* Cannot interpolate if not between 2 frames. */ + bGPDframe *gpf_prv = gpencil_get_previous_keyframe(active_gpl, cfra); + bGPDframe *gpf_next = gpencil_get_next_keyframe(active_gpl, cfra); + if (ELEM(NULL, gpf_prv, gpf_next)) { BKE_report( op->reports, RPT_ERROR, "Cannot find a pair of grease pencil frames to interpolate between in active layer"); return OPERATOR_CANCELLED; } - /* cannot interpolate in extremes */ - if (ELEM(cfra, actframe->framenum, actframe->next->framenum)) { - BKE_report(op->reports, - RPT_ERROR, - "Cannot interpolate as current frame already has existing grease pencil frames"); - return OPERATOR_CANCELLED; - } if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { BKE_report(op->reports, RPT_ERROR, "Cannot interpolate in curve edit mode"); @@ -981,103 +1231,137 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) /* loop all layer to check if need interpolation */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - bGPDframe *prevFrame, *nextFrame; - bGPDstroke *gps_from, *gps_to; - int cframe, fFrame; - - /* Need a set of frames to interpolate. */ - if ((gpl->actframe == NULL) || (gpl->actframe->next == NULL)) { - continue; - } /* all layers or only active */ - if (((flag & GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS) == 0) && (gpl != active_gpl)) { + if ((!all_layers) && (gpl != active_gpl)) { continue; } /* only editable and visible layers are considered */ if (!BKE_gpencil_layer_is_editable(gpl)) { continue; } + gpf_prv = gpencil_get_previous_keyframe(gpl, cfra); + gpf_next = gpencil_get_next_keyframe(gpl, cfra); + + /* Need a set of frames to interpolate. */ + if ((gpf_prv == NULL) || (gpf_next == NULL)) { + continue; + } + + /* Store extremes. */ + bGPDframe *prevFrame = BKE_gpencil_frame_duplicate(gpf_prv, true); + bGPDframe *nextFrame = BKE_gpencil_frame_duplicate(gpf_next, true); + + /* Create a table with source and target pair of strokes. */ + GHash *used_strokes = BLI_ghash_ptr_new(__func__); + GHash *pair_strokes = BLI_ghash_ptr_new(__func__); + + LISTBASE_FOREACH (bGPDstroke *, gps_from, &prevFrame->strokes) { + bGPDstroke *gps_to = NULL; + /* Only selected. */ + if ((GPENCIL_EDIT_MODE(gpd)) && (only_selected) && + ((gps_from->flag & GP_STROKE_SELECT) == 0)) { + continue; + } + /* Skip strokes that are invalid for current view. */ + if (ED_gpencil_stroke_can_use(C, gps_from) == false) { + continue; + } + /* Check if the material is editable. */ + if (ED_gpencil_stroke_material_editable(ob, gpl, gps_from) == false) { + continue; + } + /* Try to get the related stroke. */ + if ((is_multiedit) && (gps_from->select_index > 0)) { + gps_to = gpencil_stroke_get_related(used_strokes, nextFrame, gps_from->select_index); + } + /* If not found, get final stroke to interpolate using position in the array. */ + if (gps_to == NULL) { + int fFrame = BLI_findindex(&prevFrame->strokes, gps_from); + gps_to = BLI_findlink(&nextFrame->strokes, fFrame); + } + + if (ELEM(NULL, gps_from, gps_to)) { + continue; + } + + /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ + if (gps_from->totpoints > gps_to->totpoints) { + BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true); + } + if (gps_to->totpoints > gps_from->totpoints) { + BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true); + } - /* store extremes */ - prevFrame = BKE_gpencil_frame_duplicate(gpl->actframe, true); - nextFrame = BKE_gpencil_frame_duplicate(gpl->actframe->next, true); + /* Flip stroke. */ + if (flipmode == GP_INTERPOLATE_FLIP) { + BKE_gpencil_stroke_flip(gps_to); + } + else if (flipmode == GP_INTERPOLATE_FLIPAUTO) { + if (gpencil_stroke_need_flip(depsgraph, ob, gpl, &gsc, gps_from, gps_to)) { + BKE_gpencil_stroke_flip(gps_to); + } + } - /* Loop over intermediary frames and create the interpolation */ - for (cframe = prevFrame->framenum + step; cframe < nextFrame->framenum; cframe += step) { - bGPDframe *interFrame = NULL; - float factor; + /* Insert the pair entry in the hash table. */ + BLI_ghash_insert(pair_strokes, gps_from, gps_to); + } - /* get interpolation factor */ + /* Loop over intermediary frames and create the interpolation. */ + for (int cframe = prevFrame->framenum + step; cframe < nextFrame->framenum; cframe += step) { + /* Get interpolation factor. */ float framerange = nextFrame->framenum - prevFrame->framenum; CLAMP_MIN(framerange, 1.0f); - factor = (float)(cframe - prevFrame->framenum) / framerange; + float factor = (float)(cframe - prevFrame->framenum) / framerange; - if (ipo_settings->type == GP_IPO_CURVEMAP) { + if (type == GP_IPO_CURVEMAP) { /* custom curvemap */ if (ipo_settings->custom_ipo) { factor = BKE_curvemapping_evaluateF(ipo_settings->custom_ipo, 0, factor); } else { BKE_report(op->reports, RPT_ERROR, "Custom interpolation curve does not exist"); + continue; } } - else if (ipo_settings->type >= GP_IPO_BACK) { + else if (type >= GP_IPO_BACK) { /* easing equation... */ - factor = gpencil_interpolate_seq_easing_calc(ipo_settings, factor); + factor = gpencil_interpolate_seq_easing_calc(op, factor); } - /* create new strokes data with interpolated points reading original stroke */ - for (gps_from = prevFrame->strokes.first; gps_from; gps_from = gps_from->next) { - - /* only selected */ - if ((GPENCIL_EDIT_MODE(gpd)) && (flag & GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED) && - ((gps_from->flag & GP_STROKE_SELECT) == 0)) { - continue; - } - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps_from) == false) { - continue; - } - /* check if the color is editable */ - if (ED_gpencil_stroke_material_editable(ob, gpl, gps_from) == false) { - continue; - } - - /* get final stroke to interpolate */ - fFrame = BLI_findindex(&prevFrame->strokes, gps_from); - gps_to = BLI_findlink(&nextFrame->strokes, fFrame); - if (gps_to == NULL) { - continue; - } - - /* create a new frame if needed */ - if (interFrame == NULL) { - interFrame = BKE_gpencil_layer_frame_get(gpl, cframe, GP_GETFRAME_ADD_NEW); - interFrame->key_type = BEZT_KEYTYPE_BREAKDOWN; - } - - /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ - if (gps_from->totpoints > gps_to->totpoints) { - BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true); - } - if (gps_to->totpoints > gps_from->totpoints) { - BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true); - } + /* Apply the factor to all pair of strokes. */ + GHashIterator gh_iter; + GHASH_ITER (gh_iter, pair_strokes) { + bGPDstroke *gps_from = (bGPDstroke *)BLI_ghashIterator_getKey(&gh_iter); + bGPDstroke *gps_to = (bGPDstroke *)BLI_ghashIterator_getValue(&gh_iter); - /* create new stroke */ + /* Create new stroke. */ bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true); + new_stroke->flag |= GP_STROKE_TAG; + new_stroke->select_index = 0; - /* update points position */ + /* Update points position. */ gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); + gpencil_interpolate_smooth_stroke(new_stroke, smooth_factor, smooth_steps); /* Calc geometry data. */ BKE_gpencil_stroke_geometry_update(gpd, new_stroke); - /* add to strokes */ + /* Add strokes to frame. */ + bGPDframe *interFrame = BKE_gpencil_layer_frame_get(gpl, cframe, GP_GETFRAME_ADD_NEW); + interFrame->key_type = BEZT_KEYTYPE_BREAKDOWN; + BLI_addtail(&interFrame->strokes, new_stroke); } } + /* Free Hash tablets. */ + if (used_strokes != NULL) { + BLI_ghash_free(used_strokes, NULL, NULL); + } + if (pair_strokes != NULL) { + BLI_ghash_free(pair_strokes, NULL, NULL); + } + BKE_gpencil_free_strokes(prevFrame); BKE_gpencil_free_strokes(nextFrame); MEM_SAFE_FREE(prevFrame); @@ -1091,8 +1375,148 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void gpencil_interpolate_seq_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + uiLayout *col, *row; + PointerRNA ptr; + + RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr); + + const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + col = uiLayoutColumn(layout, true); + uiItemR(col, &ptr, "step", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "layers", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "interpolate_selected_only", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "flip", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "smooth_factor", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "smooth_steps", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "type", 0, NULL, ICON_NONE); + + if (type == GP_IPO_CURVEMAP) { + /* Get an RNA pointer to ToolSettings to give to the custom curve. */ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + PointerRNA gpsettings_ptr; + RNA_pointer_create( + &scene->id, &RNA_GPencilInterpolateSettings, &ts->gp_interpolate, &gpsettings_ptr); + uiTemplateCurveMapping( + layout, &gpsettings_ptr, "interpolation_curve", 0, false, true, true, false); + } + else if (type != GP_IPO_LINEAR) { + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "easing", 0, NULL, ICON_NONE); + if (type == GP_IPO_BACK) { + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "back", 0, NULL, ICON_NONE); + } + else if (type == GP_IPO_ELASTIC) { + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "amplitude", 0, NULL, ICON_NONE); + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "period", 0, NULL, ICON_NONE); + } + } +} + void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) { + static const EnumPropertyItem gpencil_interpolation_layer_items[] = { + {0, "ACTIVE", 0, "Active", ""}, + {1, "ALL", 0, "All Layers", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem gpencil_interpolation_type_items[] = { + /* interpolation */ + {0, "", 0, N_("Interpolation"), "Standard transitions between keyframes"}, + {GP_IPO_LINEAR, + "LINEAR", + ICON_IPO_LINEAR, + "Linear", + "Straight-line interpolation between A and B (i.e. no ease in/out)"}, + {GP_IPO_CURVEMAP, + "CUSTOM", + ICON_IPO_BEZIER, + "Custom", + "Custom interpolation defined using a curve map"}, + + /* easing */ + {0, + "", + 0, + N_("Easing (by strength)"), + "Predefined inertial transitions, useful for motion graphics (from least to most " + "''dramatic'')"}, + {GP_IPO_SINE, + "SINE", + ICON_IPO_SINE, + "Sinusoidal", + "Sinusoidal easing (weakest, almost linear but with a slight curvature)"}, + {GP_IPO_QUAD, "QUAD", ICON_IPO_QUAD, "Quadratic", "Quadratic easing"}, + {GP_IPO_CUBIC, "CUBIC", ICON_IPO_CUBIC, "Cubic", "Cubic easing"}, + {GP_IPO_QUART, "QUART", ICON_IPO_QUART, "Quartic", "Quartic easing"}, + {GP_IPO_QUINT, "QUINT", ICON_IPO_QUINT, "Quintic", "Quintic easing"}, + {GP_IPO_EXPO, "EXPO", ICON_IPO_EXPO, "Exponential", "Exponential easing (dramatic)"}, + {GP_IPO_CIRC, + "CIRC", + ICON_IPO_CIRC, + "Circular", + "Circular easing (strongest and most dynamic)"}, + + {0, "", 0, N_("Dynamic Effects"), "Simple physics-inspired easing effects"}, + {GP_IPO_BACK, "BACK", ICON_IPO_BACK, "Back", "Cubic easing with overshoot and settle"}, + {GP_IPO_BOUNCE, + "BOUNCE", + ICON_IPO_BOUNCE, + "Bounce", + "Exponentially decaying parabolic bounce, like when objects collide"}, + {GP_IPO_ELASTIC, + "ELASTIC", + ICON_IPO_ELASTIC, + "Elastic", + "Exponentially decaying sine wave, like an elastic band"}, + + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem gpencil_interpolation_easing_items[] = { + {BEZT_IPO_EASE_AUTO, + "AUTO", + ICON_IPO_EASE_IN_OUT, + "Automatic Easing", + "Easing type is chosen automatically based on what the type of interpolation used " + "(e.g. 'Ease In' for transitional types, and 'Ease Out' for dynamic effects)"}, + + {BEZT_IPO_EASE_IN, + "EASE_IN", + ICON_IPO_EASE_IN, + "Ease In", + "Only on the end closest to the next keyframe"}, + {BEZT_IPO_EASE_OUT, + "EASE_OUT", + ICON_IPO_EASE_OUT, + "Ease Out", + "Only on the end closest to the first keyframe"}, + {BEZT_IPO_EASE_IN_OUT, + "EASE_IN_OUT", + ICON_IPO_EASE_IN_OUT, + "Ease In and Out", + "Segment between both keyframes"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem flip_modes[] = { + {GP_INTERPOLATE_NOFLIP, "NOFLIP", 0, "No Flip", ""}, + {GP_INTERPOLATE_FLIP, "FLIP", 0, "Flip", ""}, + {GP_INTERPOLATE_FLIPAUTO, "AUTO", 0, "Automatic", ""}, + {0, NULL, 0, NULL, NULL}, + }; + /* identifiers */ ot->name = "Interpolate Sequence"; ot->idname = "GPENCIL_OT_interpolate_sequence"; @@ -1101,6 +1525,103 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) /* api callbacks */ ot->exec = gpencil_interpolate_seq_exec; ot->poll = gpencil_view3d_poll; + ot->ui = gpencil_interpolate_seq_ui; + + RNA_def_int(ot->srna, + "step", + 1, + 1, + MAXFRAME, + "Step", + "Number of frames between generated interpolated frames", + 1, + MAXFRAME); + + RNA_def_enum(ot->srna, + "layers", + gpencil_interpolation_layer_items, + 0, + "Layer", + "Layers included in the interpolation"); + + RNA_def_boolean(ot->srna, + "interpolate_selected_only", + 0, + "Only Selected", + "Interpolate only selected strokes"); + + RNA_def_enum(ot->srna, + "flip", + flip_modes, + GP_INTERPOLATE_FLIPAUTO, + "Flip Mode", + "Invert destination stroke to match start and end with source stroke"); + + RNA_def_int(ot->srna, + "smooth_steps", + 1, + 1, + 3, + "Iterations", + "Number of times to smooth newly created strokes", + 1, + 3); + + RNA_def_float(ot->srna, + "smooth_factor", + 0.0f, + 0.0f, + 2.0f, + "Smooth", + "Amount of smoothing to apply to interpolated strokes, to reduce jitter/noise", + 0.0f, + 2.0f); + + RNA_def_enum(ot->srna, + "type", + gpencil_interpolation_type_items, + 0, + "Type", + "Interpolation method to use the next time 'Interpolate Sequence' is run"); + + RNA_def_enum( + ot->srna, + "easing", + gpencil_interpolation_easing_items, + 0, + "Easing", + "Which ends of the segment between the preceding and following grease pencil frames " + "easing interpolation is applied to"); + + RNA_def_float(ot->srna, + "back", + 1.702f, + 0.0f, + FLT_MAX, + "Back", + "Amount of overshoot for 'back' easing", + 0.0f, + FLT_MAX); + + RNA_def_float(ot->srna, + "amplitude", + 0.15f, + 0.0f, + FLT_MAX, + "Amplitude", + "Amount to boost elastic bounces for 'elastic' easing", + 0.0f, + FLT_MAX); + + RNA_def_float(ot->srna, + "period", + 0.15f, + -FLT_MAX, + FLT_MAX, + "Period", + "Time between bounces for elastic easing", + -FLT_MAX, + FLT_MAX); /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -1110,19 +1631,30 @@ void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot) static bool gpencil_interpolate_reverse_poll(bContext *C) { - if (!gpencil_view3d_poll(C)) { - return 0; + ScrArea *area = CTX_wm_area(C); + if (area == NULL) { + return false; + } + if ((area->spacetype != SPACE_VIEW3D) && (area->spacetype != SPACE_ACTION)) { + return false; } - bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); + bGPdata *gpd = ED_gpencil_data_get_active(C); + if (gpd == NULL) { + return false; + } + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + if (gpl == NULL) { + return false; + } /* need to be on a breakdown frame */ if ((gpl->actframe == NULL) || (gpl->actframe->key_type != BEZT_KEYTYPE_BREAKDOWN)) { CTX_wm_operator_poll_msg_set(C, "Expected current frame to be a breakdown"); - return 0; + return false; } - return 1; + return true; } static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op)) @@ -1132,7 +1664,11 @@ static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op)) /* Go through each layer, deleting the breakdowns around the current frame, * but only if there is a keyframe nearby to stop at */ - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* only editable and visible layers are considered */ + if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { + continue; + } bGPDframe *start_key = NULL; bGPDframe *end_key = NULL; bGPDframe *gpf, *gpfn; @@ -1193,7 +1729,6 @@ static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op)) BLI_freelinkN(&gpl->frames, end_key); } } - CTX_DATA_END; /* notifiers */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -1205,7 +1740,7 @@ static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op)) void GPENCIL_OT_interpolate_reverse(wmOperatorType *ot) { /* identifiers */ - ot->name = "Remove Breakdowns"; + ot->name = "Delete Breakdowns"; ot->idname = "GPENCIL_OT_interpolate_reverse"; ot->description = "Remove breakdown frames generated by interpolating between two Grease Pencil frames"; @@ -1217,5 +1752,3 @@ void GPENCIL_OT_interpolate_reverse(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } - -/* *************************************************************** */ diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c index 435bff34998..8d8734dfd1f 100644 --- a/source/blender/editors/gpencil/gpencil_merge.c +++ b/source/blender/editors/gpencil/gpencil_merge.c @@ -104,6 +104,7 @@ static bGPDstroke *gpencil_prepare_stroke(bContext *C, wmOperator *op, int totpo Main *bmain = CTX_data_main(C); ToolSettings *ts = CTX_data_tool_settings(C); Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); Scene *scene = CTX_data_scene(C); @@ -133,6 +134,7 @@ static bGPDstroke *gpencil_prepare_stroke(bContext *C, wmOperator *op, int totpo /* stroke */ bGPDstroke *gps = BKE_gpencil_stroke_new(MAX2(ob->actcol - 1, 0), totpoints, brush->size); gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); if (cyclic) { gps->flag |= GP_STROKE_CYCLIC; @@ -241,6 +243,7 @@ static void gpencil_calc_points_factor(bContext *C, } } gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } } } diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index f347a4fe6b9..4eec4c7d00e 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -338,6 +338,7 @@ static void gpencil_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) ED_gpencil_fill_vertex_color_set(ts, brush, gps); gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); /* the polygon must be closed, so enabled cyclic */ if (ELEM(tgpi->type, GP_STROKE_BOX, GP_STROKE_CIRCLE)) { gps->flag |= GP_STROKE_CYCLIC; diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index d1d8abd6775..fd5c5bb5346 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -156,6 +156,11 @@ static bool gpencil_3d_point_to_screen_space(ARegion *region, /* helper to deselect all selected strokes/points */ static void deselect_all_selected(bContext *C) { + /* Set selection index to 0. */ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + gpd->select_last_index = 0; + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { /* deselect stroke and its points if selected */ if (gps->flag & GP_STROKE_SELECT) { @@ -169,6 +174,7 @@ static void deselect_all_selected(bContext *C) /* deselect stroke itself too */ gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } /* deselect curve and curve points */ @@ -187,7 +193,7 @@ static void deselect_all_selected(bContext *C) CTX_DATA_END; } -static void select_all_curve_points(bGPDstroke *gps, bGPDcurve *gpc, bool deselect) +static void select_all_curve_points(bGPdata *gpd, bGPDstroke *gps, bGPDcurve *gpc, bool deselect) { for (int i = 0; i < gpc->tot_curve_points; i++) { bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; @@ -205,10 +211,12 @@ static void select_all_curve_points(bGPDstroke *gps, bGPDcurve *gpc, bool desele if (deselect == false) { gpc->flag |= GP_CURVE_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); } else { gpc->flag &= ~GP_CURVE_SELECT; gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } } @@ -423,7 +431,7 @@ static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) BEZT_DESEL_ALL(&gpc_pt->bezt); } - BKE_gpencil_curve_sync_selection(gps); + BKE_gpencil_curve_sync_selection(gpd, gps); changed = true; } } @@ -562,6 +570,7 @@ static bool gpencil_select_same_layer(bContext *C) } gpc->flag |= GP_CURVE_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); changed = true; } @@ -578,6 +587,7 @@ static bool gpencil_select_same_layer(bContext *C) } gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); changed = true; } @@ -622,6 +632,7 @@ static bool gpencil_select_same_material(bContext *C) } gpc->flag |= GP_CURVE_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); changed = true; } @@ -640,6 +651,7 @@ static bool gpencil_select_same_material(bContext *C) } gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); changed = true; } @@ -756,6 +768,8 @@ static int gpencil_select_first_exec(bContext *C, wmOperator *op) BEZT_SEL_ALL(&gpc->curve_points[0].bezt); gpc->flag |= GP_CURVE_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); + if ((extend == false) && (gps->totpoints > 1)) { for (int i = 1; i < gpc->tot_curve_points; i++) { bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; @@ -769,6 +783,7 @@ static int gpencil_select_first_exec(bContext *C, wmOperator *op) else { gps->points->flag |= GP_SPOINT_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); /* deselect rest? */ if ((extend == false) && (gps->totpoints > 1)) { @@ -863,6 +878,7 @@ static int gpencil_select_last_exec(bContext *C, wmOperator *op) BEZT_SEL_ALL(&gpc->curve_points[gpc->tot_curve_points - 1].bezt); gpc->flag |= GP_CURVE_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); 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]; @@ -876,6 +892,7 @@ static int gpencil_select_last_exec(bContext *C, wmOperator *op) else { gps->points[gps->totpoints - 1].flag |= GP_SPOINT_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); /* deselect rest? */ if ((extend == false) && (gps->totpoints > 1)) { @@ -1272,10 +1289,12 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *gpd, if (select) { pt_active->flag |= GP_SPOINT_SELECT; gps_active->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps_active, false); } else { pt_active->flag &= ~GP_SPOINT_SELECT; gps_active->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps_active, true); } changed = true; /* if stroke mode, don't check more points */ @@ -1314,13 +1333,13 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *gpd, 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); + select_all_curve_points(gpd, 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); + BKE_gpencil_stroke_sync_selection(gpd, gps_active); return changed; } @@ -1338,6 +1357,9 @@ static bool gpencil_do_curve_circle_sel(bContext *C, { ARegion *region = CTX_wm_region(C); View3D *v3d = CTX_wm_view3d(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + const bool only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); bool hit = false; @@ -1411,7 +1433,7 @@ static bool gpencil_do_curve_circle_sel(bContext *C, } } - BKE_gpencil_curve_sync_selection(gps); + BKE_gpencil_curve_sync_selection(gpd, gps); return hit; } @@ -1631,7 +1653,7 @@ static bool gpencil_stroke_fill_isect_rect(ARegion *region, #endif static bool gpencil_generic_curve_select(bContext *C, - Object *UNUSED(ob), + Object *ob, GPencilTestFn is_inside_fn, rcti UNUSED(box), GP_SelectUserData *user_data, @@ -1640,6 +1662,7 @@ static bool gpencil_generic_curve_select(bContext *C, { ARegion *region = CTX_wm_region(C); View3D *v3d = CTX_wm_view3d(C); + bGPdata *gpd = ob->data; const bool handle_only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); const bool handle_all = (v3d->overlay.handle_display == CURVE_HANDLE_ALL); @@ -1771,7 +1794,7 @@ static bool gpencil_generic_curve_select(bContext *C, } } - BKE_gpencil_curve_sync_selection(gps); + BKE_gpencil_curve_sync_selection(gpd, gps); } GP_EDITABLE_CURVES_END(gps_iter); @@ -1797,6 +1820,8 @@ static bool gpencil_generic_stroke_select(bContext *C, /* deselect all strokes first? */ if (SEL_OP_USE_PRE_DESELECT(sel_op) || (GPENCIL_PAINT_MODE(gpd))) { + /* Set selection index to 0. */ + gpd->select_last_index = 0; CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { bGPDspoint *pt; @@ -1807,6 +1832,7 @@ static bool gpencil_generic_stroke_select(bContext *C, } gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } CTX_DATA_END; @@ -1891,13 +1917,13 @@ static bool gpencil_generic_stroke_select(bContext *C, 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); + select_all_curve_points(gpd, 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); + BKE_gpencil_stroke_sync_selection(gpd, gps_active); } GP_EVALUATED_STROKES_END(gpstroke_iter); @@ -2313,7 +2339,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) } /* select all curve points */ if (hit_curve != NULL) { - select_all_curve_points(hit_stroke, hit_curve, deselect); + select_all_curve_points(gpd, hit_stroke, hit_curve, deselect); } else { bGPDspoint *pt; @@ -2332,9 +2358,11 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) /* stroke too... */ if (deselect == false) { hit_stroke->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, hit_stroke, false); } else { hit_stroke->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, hit_stroke, true); } } } @@ -2346,11 +2374,13 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) BEZT_SEL_IDX(&hit_curve_point->bezt, hit_curve_handle); hit_curve->flag |= GP_CURVE_SELECT; hit_stroke->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, hit_stroke, false); } else { /* we're adding selection, so selection must be true */ hit_point->flag |= GP_SPOINT_SELECT; hit_stroke->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, hit_stroke, false); /* expand selection to segment */ int selectmode; @@ -2378,14 +2408,14 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) if (!BEZT_ISSEL_ANY(&hit_curve_point->bezt)) { hit_curve_point->flag &= ~GP_CURVE_POINT_SELECT; } - BKE_gpencil_curve_sync_selection(hit_stroke); + BKE_gpencil_curve_sync_selection(gpd, hit_stroke); } else { /* deselect point */ hit_point->flag &= ~GP_SPOINT_SELECT; /* ensure that stroke is selected correctly */ - BKE_gpencil_stroke_sync_selection(hit_stroke); + BKE_gpencil_stroke_sync_selection(gpd, hit_stroke); } } } @@ -2572,6 +2602,7 @@ static int gpencil_select_vertex_color_exec(bContext *C, wmOperator *op) if (gps_selected) { gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); /* Extend stroke selection. */ if (selectmode == GP_SELECTMODE_STROKE) { diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 9b12772bc9b..f9242c5a1a8 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -1167,6 +1167,7 @@ void ED_gpencil_stroke_reproject(Depsgraph *depsgraph, if (keep_original) { gps_active = BKE_gpencil_stroke_duplicate(gps, true, true); gps_active->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps_active, true); for (i = 0, pt = gps_active->points; i < gps_active->totpoints; i++, pt++) { pt->flag &= ~GP_SPOINT_SELECT; } @@ -1689,6 +1690,10 @@ void ED_gpencil_vgroup_select(bContext *C, Object *ob) gps->flag |= GP_STROKE_SELECT; } } + + if (gps->flag & GP_STROKE_SELECT) { + BKE_gpencil_stroke_select_index_set(gpd, gps, false); + } } } @@ -2564,6 +2569,9 @@ int ED_gpencil_select_stroke_segment(bGPdata *gpd, void ED_gpencil_select_toggle_all(bContext *C, int action) { + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + /* for "toggle", test for existing selected strokes */ if (action == SEL_TOGGLE) { action = SEL_SELECT; @@ -2588,6 +2596,9 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) * NOTE: we limit ourselves to editable layers, since once a layer is "locked/hidden * nothing should be able to touch it */ + /* Set selection index to 0. */ + gpd->select_last_index = 0; + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { /* deselect all strokes on all frames */ @@ -2605,6 +2616,7 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) } gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } } } @@ -2642,9 +2654,11 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) /* Change status of stroke */ if (selected) { gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); } else { gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } } CTX_DATA_END; @@ -2666,6 +2680,11 @@ void ED_gpencil_select_curve_toggle_all(bContext *C, int action) } if (action == SEL_DESELECT) { + /* Set selection index to 0. */ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + gpd->select_last_index = 0; + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) { for (int i = 0; i < gpc->tot_curve_points; i++) { @@ -2676,6 +2695,7 @@ void ED_gpencil_select_curve_toggle_all(bContext *C, int action) } gpc->flag &= ~GP_CURVE_SELECT; gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } GP_EDITABLE_CURVES_END(gps_iter); } @@ -2717,10 +2737,12 @@ void ED_gpencil_select_curve_toggle_all(bContext *C, int action) if (selected) { gpc->flag |= GP_CURVE_SELECT; gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); } else { gpc->flag &= ~GP_CURVE_SELECT; gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } } GP_EDITABLE_STROKES_END(gps_iter); diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 835761bf6e9..9d969a29add 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -306,6 +306,10 @@ typedef struct bGPDstroke { float uv_translation[2]; float uv_scale; + /** Stroke selection index.*/ + int select_index; + char _pad4[4]; + /** Vertex weight data. */ struct MDeformVert *dvert; void *_pad3; @@ -701,6 +705,10 @@ typedef struct bGPdata { /** Keyframe type for onion filter (eBezTriple_KeyframeType plus All option) */ short onion_keytype; + /** Stroke selection last index. Used to generate a unique selection index. */ + int select_last_index; + char _pad3[4]; + bGPgrid grid; bGPdata_Runtime runtime; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 2362686cfce..c7f7e610a1a 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1132,22 +1132,6 @@ typedef enum eGP_vertex_SelectMaskFlag { /* Settings for GP Interpolation Operators */ typedef struct GP_Interpolate_Settings { - /** #eGP_Interpolate_SettingsFlag. */ - short flag; - - /** #eGP_Interpolate_Type - Interpolation Mode. */ - char type; - /** #eBezTriple_Easing - Easing mode (if easing equation used). */ - char easing; - - /** BEZT_IPO_BACK. */ - float back; - /** BEZT_IPO_ELASTIC. */ - float amplitude, period; - /* Step between sequence interpolated frames. */ - int step; - char _pad[4]; - /** Custom interpolation curve (for use with GP_IPO_CURVEMAP). */ struct CurveMapping *custom_ipo; } GP_Interpolate_Settings; diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index 5437c93ef61..631ab1c4671 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -717,7 +717,7 @@ static void rna_GPencil_stroke_point_select_set(PointerRNA *ptr, const bool valu } /* Check if the stroke should be selected or not... */ - BKE_gpencil_stroke_sync_selection(gps); + BKE_gpencil_stroke_sync_selection(gpd, gps); } } @@ -933,6 +933,7 @@ static void rna_GPencil_stroke_close(ID *id, static void rna_GPencil_stroke_select_set(PointerRNA *ptr, const bool value) { + bGPdata *gpd = (bGPdata *)ptr->owner_id; bGPDstroke *gps = ptr->data; bGPDspoint *pt; int i; @@ -940,9 +941,11 @@ static void rna_GPencil_stroke_select_set(PointerRNA *ptr, const bool value) /* set new value */ if (value) { gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps, false); } else { gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(NULL, gps, true); } /* ensure that the stroke's points are selected in the same way */ @@ -1744,6 +1747,11 @@ static void rna_def_gpencil_stroke(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Vertex Fill Color", "Color used to mix with fill color to get final color"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + + /* Selection Index */ + prop = RNA_def_property(srna, "select_index", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "select_index"); + RNA_def_property_ui_text(prop, "Select Index", "Index of selection used for interpolation"); } static void rna_def_gpencil_strokes_api(BlenderRNA *brna, PropertyRNA *cprop) diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 9479d15c2cc..8c01b28df5a 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -735,27 +735,6 @@ static void rna_GPencil_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UN ED_gpencil_tag_scene_gpencil(scene); } -/* Grease Pencil Interpolation settings */ -static char *rna_GPencilInterpolateSettings_path(PointerRNA *UNUSED(ptr)) -{ - return BLI_strdup("tool_settings.gpencil_interpolate"); -} - -static void rna_GPencilInterpolateSettings_type_set(PointerRNA *ptr, int value) -{ - GP_Interpolate_Settings *settings = (GP_Interpolate_Settings *)ptr->data; - - /* NOTE: This cast should be fine, as we have a small + finite set of values - * (#eGP_Interpolate_Type) that should fit well within a char. - */ - settings->type = (char)value; - - /* init custom interpolation curve here now the first time it's used */ - if ((settings->type == GP_IPO_CURVEMAP) && (settings->custom_ipo == NULL)) { - settings->custom_ipo = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - } -} - static void rna_Gpencil_extend_selection(bContext *C, PointerRNA *UNUSED(ptr)) { /* Extend selection to all points in all selected strokes. */ @@ -2688,69 +2667,10 @@ static void rna_def_gpencil_interpolate(BlenderRNA *brna) srna = RNA_def_struct(brna, "GPencilInterpolateSettings", NULL); RNA_def_struct_sdna(srna, "GP_Interpolate_Settings"); - RNA_def_struct_path_func(srna, "rna_GPencilInterpolateSettings_path"); RNA_def_struct_ui_text(srna, "Grease Pencil Interpolate Settings", "Settings for Grease Pencil interpolation tools"); - /* flags */ - prop = RNA_def_property(srna, "interpolate_all_layers", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS); - RNA_def_property_ui_text( - prop, "Interpolate All Layers", "Interpolate all layers, not only active"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - - prop = RNA_def_property(srna, "interpolate_selected_only", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED); - RNA_def_property_ui_text(prop, - "Interpolate Selected Strokes", - "Interpolate only selected strokes in the original frame"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - - /* interpolation type */ - prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_sdna(prop, NULL, "type"); - RNA_def_property_enum_items(prop, rna_enum_gpencil_interpolation_mode_items); - RNA_def_property_enum_funcs(prop, NULL, "rna_GPencilInterpolateSettings_type_set", NULL); - RNA_def_property_ui_text( - prop, "Type", "Interpolation method to use the next time 'Interpolate Sequence' is run"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - - prop = RNA_def_property(srna, "step", PROP_INT, PROP_NONE); - RNA_def_property_range(prop, 1, MAXFRAME); - RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_ui_text(prop, "Step", "Number of frames between generated interpolated frames"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - - /* easing */ - prop = RNA_def_property(srna, "easing", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_sdna(prop, NULL, "easing"); - RNA_def_property_enum_items(prop, rna_enum_beztriple_interpolation_easing_items); - RNA_def_property_ui_text( - prop, - "Easing", - "Which ends of the segment between the preceding and following grease pencil frames " - "easing interpolation is applied to"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - - /* easing options */ - prop = RNA_def_property(srna, "back", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "back"); - RNA_def_property_ui_text(prop, "Back", "Amount of overshoot for 'back' easing"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - - prop = RNA_def_property(srna, "amplitude", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "amplitude"); - RNA_def_property_range(prop, 0.0f, FLT_MAX); /* only positive values... */ - RNA_def_property_ui_text( - prop, "Amplitude", "Amount to boost elastic bounces for 'elastic' easing"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - - prop = RNA_def_property(srna, "period", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "period"); - RNA_def_property_ui_text(prop, "Period", "Time between bounces for elastic easing"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - /* custom curvemap */ prop = RNA_def_property(srna, "interpolation_curve", PROP_POINTER, PROP_NONE); RNA_def_property_pointer_sdna(prop, NULL, "custom_ipo"); @@ -2759,7 +2679,6 @@ static void rna_def_gpencil_interpolate(BlenderRNA *brna) prop, "Interpolation Curve", "Custom curve to control 'sequence' interpolation between Grease Pencil frames"); - RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); } static void rna_def_transform_orientation(BlenderRNA *brna) |