diff options
85 files changed, 6801 insertions, 3280 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 0af7493ed47..b76afc6f35f 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -3316,7 +3316,7 @@ def km_grease_pencil_stroke_edit_mode(params): # Separate ("gpencil.stroke_separate", {"type": 'P', "value": 'PRESS'}, None), # Split and joint strokes - ("gpencil.stroke_split", {"type": 'V', "value": 'PRESS'}, None), + ("gpencil.stroke_split", {"type": 'V', "value": 'PRESS', "shift": True}, None), ("gpencil.stroke_join", {"type": 'J', "value": 'PRESS', "ctrl": True}, None), ("gpencil.stroke_join", {"type": 'J', "value": 'PRESS', "shift": True, "ctrl": True}, {"properties": [("type", 'JOINCOPY')]}), @@ -3361,9 +3361,6 @@ def km_grease_pencil_stroke_edit_mode(params): # Proportional editing. *_template_items_proportional_editing( params, connected=True, toggle_data_path='tool_settings.use_proportional_edit'), - # 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 @@ -3381,6 +3378,8 @@ def km_grease_pencil_stroke_edit_mode(params): op_menu("VIEW3D_MT_gpencil_animation", {"type": 'I', "value": 'PRESS'}), # Context menu *_template_items_context_menu("VIEW3D_MT_gpencil_edit_context_menu", params.context_menu_event), + # Set handle type + ("gpencil.stroke_editcurve_set_handle_type", {"type": 'V', "value": 'PRESS'}, None), ]) if params.legacy: @@ -3391,21 +3390,6 @@ 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 = [] keymap = ( @@ -5747,6 +5731,33 @@ def km_generic_gizmo_tweak_modal_map(_params): return keymap +def km_gpencil_curve_draw_modal_map(_params): + items = [] + keymap = ( + "Curve Draw Tool Modal Map", + {"space_type": 'EMPTY', "region_type": 'WINDOW', "modal": True}, + {"items": items}, + ) + + items.extend([ + ("CANCEL", {"type": 'ESC', "value": 'PRESS', "any": True}, None), + ("CANCEL", {"type": 'RIGHTMOUSE', "value": 'PRESS', "any": True}, None), + ("CONFIRM", {"type": 'SPACE', "value": 'PRESS', "any": True}, None), + ("CONFIRM", {"type": 'RET', "value": 'PRESS', "any": True}, None), + ("CONFIRM", {"type": 'NUMPAD_ENTER', "value": 'PRESS', "any": True}, None), + ("CONFIRM", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "any": True}, None), + ("FREE_HANDLE_ON", {"type": 'RIGHT_ALT', "value": 'PRESS', "any": True}, None), + ("FREE_HANDLE_OFF", {"type": 'RIGHT_ALT', "value": 'RELEASE', "any": True}, None), + ("FREE_HANDLE_ON", {"type": 'LEFT_ALT', "value": 'PRESS', "any": True}, None), + ("FREE_HANDLE_OFF", {"type": 'LEFT_ALT', "value": 'RELEASE', "any": True}, None), + ("CYCLIC_TOGGLE", {"type": 'C', "value": 'PRESS', "any": True}, None), + ("DELETE_LAST", {"type": 'X', "value": 'PRESS', "any": True}, None), + ("SET_THICKNESS", {"type": 'F', "value": 'PRESS', "any": True}, None), + ]) + + return keymap + + # ------------------------------------------------------------------------------ # Popup Keymaps @@ -6854,6 +6865,15 @@ def km_3d_view_tool_paint_gpencil_eyedropper(params): ]}, ) +def km_3d_view_tool_paint_gpencil_curve_pen(params): + return ( + "3D View Tool: Paint Gpencil, Bézier Pen", + {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, + {"items": [ + ("gpencil.draw_curve", {"type": params.tool_mouse, "value": 'PRESS'}, {"properties":[]}), + ]}, + ) + def km_3d_view_tool_paint_gpencil_interpolate(params): return ( "3D View Tool: Paint Gpencil, Interpolate", @@ -6864,6 +6884,7 @@ def km_3d_view_tool_paint_gpencil_interpolate(params): ]}, ) + def km_3d_view_tool_edit_gpencil_select(params): return ( "3D View Tool: Edit Gpencil, Tweak", @@ -7119,7 +7140,6 @@ 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), @@ -7184,6 +7204,7 @@ def generate_keymaps(params=None): km_view3d_dolly_modal(params), km_paint_stroke_modal(params), km_sculpt_expand_modal(params), + km_gpencil_curve_draw_modal_map(params), # Gizmos. km_generic_gizmo(params), @@ -7292,6 +7313,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_curve_pen(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), diff --git a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py index 40dd0729fec..bcaea40e99c 100644 --- a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py +++ b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py @@ -59,6 +59,8 @@ def load_handler(_): scene = bpy.data.scenes[0] if scene: scene.tool_settings.use_keyframe_insert_auto = True + scene.tool_settings.gpencil_curve_fit_threshold = 0.1 + scene.tool_settings.gpencil_curve_fit_corner_angle = 1.57079632679489661923 for ob in scene.objects: if ob.type == 'GPENCIL': gpd = ob.data diff --git a/release/scripts/startup/bl_ui/properties_data_gpencil.py b/release/scripts/startup/bl_ui/properties_data_gpencil.py index b273eee4e19..549f50f1300 100644 --- a/release/scripts/startup/bl_ui/properties_data_gpencil.py +++ b/release/scripts/startup/bl_ui/properties_data_gpencil.py @@ -380,7 +380,10 @@ 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") + col.separator() + + col.prop(gpd, "edit_curve_resolution", text="Bézier Stroke Resolution") + col.prop(gpd, "use_adaptive_curve_resolution", text="Adaptive") class DATA_PT_gpencil_display(DataButtonsPanel, Panel): diff --git a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py index de743033036..3e85bf2adfd 100644 --- a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -365,6 +365,14 @@ class GPENCIL_MT_cleanup(Menu): if ob.mode != 'PAINT_GPENCIL': layout.operator("gpencil.reproject") +class GPENCIL_MT_stroke_type(Menu): + bl_label = "Set Stroke Type" + + def draw(self, contect): + layout = self.layout + layout.operator("gpencil.stroke_set_type", text="Poly", icon='OUTLINER_DATA_GREASEPENCIL').type = 'POLY' + layout.operator("gpencil.stroke_set_type", text="Bézier", icon='HANDLE_ALIGNED').type = 'BEZIER' + class GPENCIL_UL_annotation_layer(UIList): def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): @@ -923,6 +931,7 @@ classes = ( GPENCIL_MT_move_to_layer, GPENCIL_MT_layer_active, GPENCIL_MT_material_active, + GPENCIL_MT_stroke_type, GPENCIL_MT_gpencil_draw_delete, GPENCIL_MT_layer_mask_menu, diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index c55f637f8b2..dc6ce05371a 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2042,6 +2042,21 @@ class _defs_gpencil_paint: keymap=(), draw_settings=draw_settings, ) + + @ToolDef.from_fn + def curve_pen(): + def draw_settings(context, layout, tool): + pass + + return dict( + idname="builtin.curve_pen", + label="Bézier Pen", + icon="ops.curve.draw", + cursor='DOT', + widget=None, + keymap=(), + draw_settings=draw_settings, + ) @ToolDef.from_fn def interpolate(): @@ -2057,9 +2072,6 @@ class _defs_gpencil_paint: label="Interpolate", icon="ops.pose.breakdowner", cursor='DEFAULT', - widget=None, - keymap=(), - draw_settings=draw_settings, ) @@ -2910,6 +2922,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): _defs_view3d_generic.cursor, None, _defs_gpencil_paint.generate_from_brushes, + _defs_gpencil_paint.curve_pen, _defs_gpencil_paint.cutter, None, _defs_gpencil_paint.eyedropper, diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index df41445ee6f..98033cc2608 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -660,26 +660,24 @@ class VIEW3D_HT_header(Header): sub.separator(factor=0.4) sub.prop(tool_settings, "use_gpencil_draw_additive", text="", icon='FREEZE') + # Curve fit + row = layout.row(align=True) + row.prop(tool_settings, "use_gpencil_bezier_mode", text="", + icon='IPO_BEZIER') + sub = row.row(align=True) + sub.active = tool_settings.use_gpencil_bezier_mode + sub.popover( + panel="VIEW3D_PT_gpencil_curve_fit", + text="Curve Fit", + ) + # Select mode for Editing if gpd.use_stroke_edit_mode: row = layout.row(align=True) row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='POINT') row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='STROKE') + row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='SEGMENT') - 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: @@ -5009,6 +5007,7 @@ class VIEW3D_MT_edit_gpencil_stroke(Menu): layout.operator_menu_enum("gpencil.stroke_caps_set", text="Toggle Caps", property="type") layout.operator("gpencil.stroke_flip", text="Switch Direction") layout.prop(settings, "use_scale_thickness", text="Scale Thickness") + layout.menu("GPENCIL_MT_stroke_type") layout.separator() layout.operator("gpencil.stroke_normalize", text="Normalize Thickness").mode = 'THICKNESS' @@ -6830,8 +6829,7 @@ class VIEW3D_PT_overlay_gpencil_options(Panel): # 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") + layout.prop(overlay, "display_handle", text="Handles") if context.object.mode in {'PAINT_GPENCIL', 'VERTEX_GPENCIL'}: layout.label(text="Vertex Paint") @@ -6996,21 +6994,19 @@ 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): +# Grease Pencil Object - Curve Editing settings +class VIEW3D_PT_gpencil_curve_fit(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'HEADER' - bl_label = "Curve Editing" + bl_label = "Curve Fit" def draw(self, context): layout = self.layout - gpd = context.gpencil_data + tool_settings = context.tool_settings 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") + col.prop(tool_settings, "gpencil_curve_fit_threshold") + col.prop(tool_settings, "gpencil_curve_fit_corner_angle") class VIEW3D_MT_gpencil_edit_context_menu(Menu): @@ -7109,6 +7105,10 @@ class VIEW3D_MT_gpencil_edit_context_menu(Menu): col.separator() + col.menu("GPENCIL_MT_stroke_type") + + col.separator() + # Duplicate operators col.operator("gpencil.duplicate_move", text="Duplicate") col.operator("gpencil.copy", text="Copy", icon='COPYDOWN') @@ -7606,6 +7606,7 @@ classes = ( VIEW3D_MT_gpencil_animation, VIEW3D_MT_gpencil_simplify, VIEW3D_MT_gpencil_autoweights, + VIEW3D_PT_gpencil_curve_fit, VIEW3D_MT_gpencil_edit_context_menu, VIEW3D_MT_edit_curve, VIEW3D_MT_edit_curve_ctrlpoints, @@ -7653,7 +7654,6 @@ 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/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 46fed79332d..c8f81940cfe 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1464,6 +1464,9 @@ class VIEW3D_PT_tools_grease_pencil_brush_advanced(View3DPanel, Panel): subcol.enabled = False subcol.prop(gp_settings, "aspect") + col.separator() + col.prop(gp_settings, "use_curve_data") + elif brush.gpencil_tool == 'FILL': row = col.row(align=True) row.prop(gp_settings, "fill_draw_mode", text="Boundary") diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index c11c34cb312..449750e2944 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -266,6 +266,7 @@ bool BKE_gpencil_stroke_select_check(const struct bGPDstroke *gps); void BKE_gpencil_dvert_ensure(struct bGPDstroke *gps); void BKE_gpencil_vgroup_remove(struct Object *ob, struct bDeformGroup *defgroup); void BKE_gpencil_stroke_weights_duplicate(struct bGPDstroke *gps_src, struct bGPDstroke *gps_dst); +void BKE_gpencil_editcurve_weights_duplicate(struct bGPDcurve *gpc_src, struct bGPDcurve *gpc_dst); /* Set active frame by layer. */ void BKE_gpencil_frame_active_set(struct Depsgraph *depsgraph, struct bGPdata *gpd); diff --git a/source/blender/blenkernel/BKE_gpencil_curve.h b/source/blender/blenkernel/BKE_gpencil_curve.h index 9cbe67af9c1..5672185086f 100644 --- a/source/blender/blenkernel/BKE_gpencil_curve.h +++ b/source/blender/blenkernel/BKE_gpencil_curve.h @@ -33,7 +33,10 @@ struct Scene; struct bGPDcurve; struct bGPDlayer; struct bGPDstroke; +struct bGPDcurve; +struct bGPDcurve_point; struct bGPdata; +enum eGPStrokeGeoUpdateFlag; void BKE_gpencil_convert_curve(struct Main *bmain, struct Scene *scene, @@ -45,25 +48,63 @@ void BKE_gpencil_convert_curve(struct Main *bmain, 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); + const float corner_angle); +struct bGPDcurve *BKE_gpencil_stroke_editcurve_tagged_segments_update(struct bGPDstroke *gps, + const float error_threshold, + const float corner_angle); +void BKE_gpencil_stroke_editcurve_regenerate_single(struct bGPDstroke *gps, + uint32_t start_idx, + uint32_t end_idx, + const float error_threshold); +void BKE_gpencil_stroke_refit_curve(struct bGPDstroke *gps, + const float threshold, + const float corner_angle, + const enum eGPStrokeGeoUpdateFlag flag); 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, const uint resolution, - const bool is_adaptive); -void BKE_gpencil_editcurve_recalculate_handles(struct bGPDstroke *gps); + const bool is_adaptive, + const enum eGPStrokeGeoUpdateFlag flag); +bool BKE_gpencil_editcurve_recalculate_handles(struct bGPDstroke *gps); void BKE_gpencil_editcurve_subdivide(struct bGPDstroke *gps, const int cuts); - +int BKE_gpencil_editcurve_dissolve(struct bGPDstroke *gps, + const uint flag, + const bool refit_segments, + const float error_threshold); +void BKE_gpencil_editcurve_simplify_adaptive(struct bGPDstroke *gps, const float threshold); +void BKE_gpencil_editcurve_simplify_fixed(struct bGPDstroke *gps, const int count); +void BKE_gpencil_editcurve_smooth_ex(struct bGPDstroke *gps, + const float factor, + const uint step_size, + const uint repeat, + const bool only_selected, + const bool affect_endpoints, + const bool use_vertex_groups, + const bool invert_weights, + const int deform_group, + const CurveMapping *curve_mapping, + const bool do_positions, + const bool do_pressure, + const bool do_strength); +void BKE_gpencil_editcurve_smooth(struct bGPDstroke *gps, + const float factor, + const uint step_size, + const uint repeat, + const bool only_selected, + const bool affect_endpoints, + const bool do_positions, + const bool do_pressure, + const bool do_strength); +bool BKE_gpencil_editcurve_merge_distance(struct bGPDstroke *gps, + const float threshold, + const bool use_unselected, + const bool refit_segments, + const float error_threshold); #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index c1ccae7a437..1e9c9d8ba01 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -38,6 +38,71 @@ struct bGPDspoint; struct bGPDstroke; struct bGPdata; +typedef enum eGPStrokeGeoUpdateFlag { + /* Default geometry update. Triangulate the stroke, update UVs and bounding box. If the stroke + type is bezier, regenerate the polyline first (GP_GEO_UPDATE_POLYLINE_REGENERATE_ALL). */ + GP_GEO_UPDATE_DEFAULT = 0, + + /* == Curve refitting flags == */ + /* On a stroke geometry update, if the stroke is of type bézier, there is the option to use the + points in the polyline to do a curve fitting. This is useful when an operation writes to the + polyline and the shape of the curve is out of sync and needs to be refitted. These flags + control what attributes the curve should be fitted to. */ + + /* Refit the curve point positions. */ + GP_GEO_UPDATE_CURVE_REFIT_POSITION = (1 << 1), + /* Refit the curve point pressures. */ + GP_GEO_UPDATE_CURVE_REFIT_PRESSURE = (1 << 2), + /* Refit the curve point strengths. */ + GP_GEO_UPDATE_CURVE_REFIT_STRENGTH = (1 << 3), + /* Refit the curve point vertex colors. */ + GP_GEO_UPDATE_CURVE_REFIT_COLOR = (1 << 4), + /* Refit the curve point weights. */ + GP_GEO_UPDATE_CURVE_REFIT_WEIGHT = (1 << 5), + /* Do a partial refit. Uses the `GP_SPOINT_TAG` point flag to determin what curve segments need + to be refitted. Only affected curve segments will be updated. */ + GP_GEO_UPDATE_CURVE_PARTIAL_REFIT = (1 << 6), + + /* == Polyline regeneration flags == */ + /* The polyline is regenerated when the curve geometry is updated. This is because the polyline + is used for rendering instead of the actual curve data. These flag control what attributes + should be regenerated when the curve was updated. */ + + /* Regenerate the polyline positions from the curve data. */ + GP_GEO_UPDATE_POLYLINE_POSITION = (1 << 7), + /* Regenerate the polyline point pressure from the curve data. */ + GP_GEO_UPDATE_POLYLINE_PRESSURE = (1 << 8), + /* Regenerate the polyline point strength from the curve data. */ + GP_GEO_UPDATE_POLYLINE_STRENGTH = (1 << 9), + /* Regenerate the polyline vertex colors from the curve data. */ + GP_GEO_UPDATE_POLYLINE_COLOR = (1 << 10), + /* Regenerate the polyline weights from the curve data. */ + GP_GEO_UPDATE_POLYLINE_WEIGHT = (1 << 11), + + /* Add additional flags here: (1 << 12), (2 << 12), ... */ + /* GP_GEO_UPDATE_XXX = (1 << 12), */ +} eGPStrokeGeoUpdateFlag; + +/* Refit all attributes. */ +#define GP_GEO_UPDATE_CURVE_REFIT_ALL \ + (GP_GEO_UPDATE_CURVE_REFIT_POSITION | GP_GEO_UPDATE_CURVE_REFIT_PRESSURE | \ + GP_GEO_UPDATE_CURVE_REFIT_STRENGTH | GP_GEO_UPDATE_CURVE_REFIT_COLOR | \ + GP_GEO_UPDATE_CURVE_REFIT_WEIGHT) + +/* Check if any curve refitting is done. */ +#define GP_GEO_UPDATE_CURVE_REFIT_ANY(flag) (flag & GP_GEO_UPDATE_CURVE_REFIT_ALL) + +/* Regenerate all attributes of the polyline from the curve data. */ +#define GP_GEO_UPDATE_POLYLINE_REGENERATE_ALL \ + (GP_GEO_UPDATE_POLYLINE_POSITION | GP_GEO_UPDATE_POLYLINE_PRESSURE | \ + GP_GEO_UPDATE_POLYLINE_STRENGTH | GP_GEO_UPDATE_POLYLINE_COLOR | \ + GP_GEO_UPDATE_POLYLINE_WEIGHT) + +/* Check if any atttributes of the polyline need to be regenerated. Note that we update all + * attributes by default (GP_GEO_UPDATE_DEFAULT). */ +#define GP_GEO_UPDATE_POLYLINE_REGENERATE_ANY(flag) \ + ((flag & GP_GEO_UPDATE_POLYLINE_REGENERATE_ALL) || flag == GP_GEO_UPDATE_DEFAULT) + /* Object boundbox. */ bool BKE_gpencil_data_minmax(const struct bGPdata *gpd, float r_min[3], float r_max[3]); bool BKE_gpencil_stroke_minmax(const struct bGPDstroke *gps, @@ -78,7 +143,10 @@ 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 bGPdata *gpd, struct bGPDstroke *gps); + +void BKE_gpencil_stroke_geometry_update(struct bGPdata *gpd, + struct bGPDstroke *gps, + const eGPStrokeGeoUpdateFlag flag); void BKE_gpencil_stroke_uv_update(struct bGPDstroke *gps); void BKE_gpencil_transform(struct bGPdata *gpd, const float mat[4][4]); diff --git a/source/blender/blenkernel/BKE_gpencil_modifier.h b/source/blender/blenkernel/BKE_gpencil_modifier.h index 8fbc2112c77..ac8766374bb 100644 --- a/source/blender/blenkernel/BKE_gpencil_modifier.h +++ b/source/blender/blenkernel/BKE_gpencil_modifier.h @@ -119,7 +119,7 @@ typedef struct GpencilModifierTypeInfo { void (*copyData)(const struct GpencilModifierData *md, struct GpencilModifierData *target); /** - * Callback for GP "stroke" modifiers that operate on the + * Callback for GP "polyline stroke" modifiers that operate on the * shape and parameters of the provided strokes (e.g. Thickness, Noise, etc.) * * The gpl parameter contains the GP layer that the strokes come from. @@ -130,7 +130,18 @@ typedef struct GpencilModifierTypeInfo { * The gps parameter contains the GP stroke to operate on. This is usually a copy * of the original (unmodified and saved to files) stroke data. */ - void (*deformStroke)(struct GpencilModifierData *md, + void (*deformPolyline)(struct GpencilModifierData *md, + struct Depsgraph *depsgraph, + struct Object *ob, + struct bGPDlayer *gpl, + struct bGPDframe *gpf, + struct bGPDstroke *gps); + + /** + * Callback for GP "bezier stroke" modifiers that operate on the + * shape and parameters of the provided strokes (e.g. Thickness, Noise, etc.) + */ + void (*deformBezier)(struct GpencilModifierData *md, struct Depsgraph *depsgraph, struct Object *ob, struct bGPDlayer *gpl, diff --git a/source/blender/blenkernel/intern/armature_deform.c b/source/blender/blenkernel/intern/armature_deform.c index bca5503c8d2..770a90b7190 100644 --- a/source/blender/blenkernel/intern/armature_deform.c +++ b/source/blender/blenkernel/intern/armature_deform.c @@ -49,6 +49,7 @@ #include "BKE_armature.h" #include "BKE_deform.h" #include "BKE_editmesh.h" +#include "BKE_gpencil.h" #include "BKE_lattice.h" #include "DEG_depsgraph_build.h" @@ -487,6 +488,7 @@ static void armature_deform_coords_impl(const Object *ob_arm, bool use_dverts = false; int armature_def_nr; int cd_dvert_offset = -1; + MDeformVert *temp_dverts = NULL; /* in editmode, or not an armature */ if (arm->edbo || (ob_arm->pose == NULL)) { @@ -523,9 +525,33 @@ static void armature_deform_coords_impl(const Object *ob_arm, } } else if (ob_target->type == OB_GPENCIL) { - dverts = gps_target->dvert; - if (dverts) { - dverts_len = gps_target->totpoints; + if (GPENCIL_STROKE_TYPE_BEZIER(gps_target)) { + bGPDcurve *gpc = gps_target->editcurve; + dverts = gpc->dvert; + if (dverts != NULL) { + dverts_len = gpc->tot_curve_points * 3; + temp_dverts = MEM_mallocN(sizeof(MDeformVert) * dverts_len, __func__); + for (i = 0; i < gpc->tot_curve_points; i++) { + MDeformVert *dvert_src = &gpc->dvert[i]; + int idx = i * 3; + for (int w = 0; w < 3; w++) { + MDeformVert *dvert_dst = &temp_dverts[idx + w]; + memcpy(dvert_dst, dvert_src, sizeof(MDeformVert)); + if (dvert_src->dw) { + dvert_dst->dw = MEM_mallocN(sizeof(MDeformWeight) * dvert_src->totweight, + __func__); + memcpy(dvert_dst->dw, dvert_src->dw, sizeof(MDeformWeight) * dvert_src->totweight); + } + } + } + dverts = temp_dverts; + } + } + else { + dverts = gps_target->dvert; + if (dverts) { + dverts_len = gps_target->totpoints; + } } } } @@ -619,6 +645,15 @@ static void armature_deform_coords_impl(const Object *ob_arm, if (pchan_from_defbase) { MEM_freeN(pchan_from_defbase); } + + if (temp_dverts != NULL) { + bGPDcurve *gpc = gps_target->editcurve; + for (i = 0; i < gpc->tot_curve_points * 3; i++) { + MDeformVert *dvert = &temp_dverts[i]; + MEM_SAFE_FREE(dvert->dw); + } + MEM_freeN(temp_dverts); + } } void BKE_armature_deform_coords_with_gpencil_stroke(const Object *ob_arm, diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index abf7bab7612..4a7049e10db 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -1057,6 +1057,27 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) zero_v3(brush->secondary_rgb); break; } +#if 0 + case GP_BRUSH_PRESET_INK_CURVE: { + brush->size = 25.0f; + brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 1.0f; + brush->gpencil_settings->flag &= ~GP_BRUSH_USE_STRENGTH_PRESSURE; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->draw_angle = 0.0f; + brush->gpencil_settings->draw_angle_factor = 0.0f; + brush->gpencil_settings->hardeness = 1.0f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PEN; + brush->gpencil_tool = GPAINT_TOOL_CURVE; + + zero_v3(brush->secondary_rgb); + break; + } +#endif case GP_BRUSH_PRESET_VERTEX_DRAW: { brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_DRAW; brush->gpencil_vertex_tool = GPVERTEX_TOOL_DRAW; @@ -1399,6 +1420,14 @@ void BKE_brush_gpencil_paint_presets(Main *bmain, ToolSettings *ts, const bool r BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_TINT); } +#if 0 + /* Curve pen. */ + brush = gpencil_brush_ensure(bmain, ts, "Curve pen", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GPAINT_TOOL_CURVE); + } +#endif + /* Set default Draw brush. */ if ((reset == false) && (brush_prev != NULL)) { BKE_paint_brush_set(paint, brush_prev); diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 647db970d09..d1a7c95fe8d 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -186,6 +186,7 @@ static void greasepencil_blend_write(BlendWriter *writer, ID *id, const void *id BLO_write_struct(writer, bGPDcurve, gpc); BLO_write_struct_array( writer, bGPDcurve_point, gpc->tot_curve_points, gpc->curve_points); + BKE_defvert_blend_write(writer, gpc->tot_curve_points, gpc->dvert); } } } @@ -259,6 +260,11 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd) if (gps->editcurve != NULL) { /* Relink curve point array. */ BLO_read_data_address(reader, &gps->editcurve->curve_points); + if (gps->editcurve->dvert != NULL) { + BLO_read_data_address(reader, &gps->editcurve->dvert); + BKE_defvert_blend_read( + reader, gps->editcurve->tot_curve_points, gps->editcurve->dvert); + } } /* Relink weight data. */ @@ -388,12 +394,19 @@ void BKE_gpencil_free_stroke_editcurve(bGPDstroke *gps) if (gps == NULL) { return; } - bGPDcurve *editcurve = gps->editcurve; - if (editcurve == NULL) { + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL) { return; } - MEM_freeN(editcurve->curve_points); - MEM_freeN(editcurve); + MEM_freeN(gpc->curve_points); + if (gpc->dvert != NULL) { + for (int i = 0; i < gpc->tot_curve_points; i++) { + MDeformVert *dvert = &gpc->dvert[i]; + BKE_gpencil_free_point_weights(dvert); + } + MEM_freeN(gpc->dvert); + } + MEM_freeN(gpc); gps->editcurve = NULL; } @@ -407,8 +420,9 @@ void BKE_gpencil_free_stroke(bGPDstroke *gps) if (gps->points) { MEM_freeN(gps->points); } + + BKE_gpencil_free_stroke_weights(gps); if (gps->dvert) { - BKE_gpencil_free_stroke_weights(gps); MEM_freeN(gps->dvert); } if (gps->triangles) { @@ -766,8 +780,6 @@ 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; @@ -942,6 +954,16 @@ 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); } +void BKE_gpencil_editcurve_weights_duplicate(bGPDcurve *gpc_src, bGPDcurve *gpc_dst) +{ + if (gpc_src == NULL) { + return; + } + BLI_assert(gpc_src->tot_curve_points == gpc_dst->tot_curve_points); + + BKE_defvert_array_copy(gpc_dst->dvert, gpc_src->dvert, gpc_src->tot_curve_points); +} + /* Make a copy of a given gpencil stroke editcurve */ bGPDcurve *BKE_gpencil_stroke_curve_duplicate(bGPDcurve *gpc_src) { @@ -951,6 +973,14 @@ bGPDcurve *BKE_gpencil_stroke_curve_duplicate(bGPDcurve *gpc_src) gpc_dst->curve_points = MEM_dupallocN(gpc_src->curve_points); } + if (gpc_src->dvert != NULL) { + gpc_dst->dvert = MEM_dupallocN(gpc_src->dvert); + BKE_gpencil_editcurve_weights_duplicate(gpc_src, gpc_dst); + } + else { + gpc_dst->dvert = NULL; + } + return gpc_dst; } @@ -1197,7 +1227,6 @@ void BKE_gpencil_curve_sync_selection(bGPdata *gpd, bGPDstroke *gps) return; } - gps->flag &= ~GP_STROKE_SELECT; BKE_gpencil_stroke_select_index_reset(gps); gpc->flag &= ~GP_CURVE_SELECT; @@ -1220,7 +1249,6 @@ void BKE_gpencil_curve_sync_selection(bGPdata *gpd, bGPDstroke *gps) if (is_selected) { gpc->flag |= GP_CURVE_SELECT; - gps->flag |= GP_STROKE_SELECT; BKE_gpencil_stroke_select_index_set(gpd, gps); } } @@ -1257,14 +1285,7 @@ void BKE_gpencil_frame_delete_laststroke(bGPDlayer *gpl, bGPDframe *gpf) } /* free the stroke and its data */ - if (gps->points) { - MEM_freeN(gps->points); - } - if (gps->dvert) { - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->dvert); - } - MEM_freeN(gps->triangles); + BKE_gpencil_free_stroke(gps); BLI_freelinkN(&gpf->strokes, gps); /* if frame has no strokes after this, delete it */ @@ -2078,6 +2099,26 @@ bool BKE_gpencil_stroke_select_check(const bGPDstroke *gps) /* ************************************************** */ /* GP Object - Vertex Groups */ +/* Helper to remove a dvert from a group. */ +static void gpencil_remove_dvert_ex(MDeformVert *dvert, const int def_nr, const int tot_groups) +{ + if (dvert == NULL) { + return; + } + + MDeformWeight *dw = BKE_defvert_find_index(dvert, def_nr); + if (dw != NULL) { + BKE_defvert_remove_group(dvert, dw); + } + /* Reorganize weights for other groups after deleted one. */ + for (int g = 0; g < tot_groups; g++) { + dw = BKE_defvert_find_index(dvert, g); + if ((dw != NULL) && (dw->def_nr > def_nr)) { + dw->def_nr--; + } + } +} + /** * Remove a vertex group. * \param ob: Grease pencil object @@ -2086,7 +2127,6 @@ bool BKE_gpencil_stroke_select_check(const bGPDstroke *gps) void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup) { bGPdata *gpd = ob->data; - MDeformVert *dvert = NULL; const int def_nr = BLI_findindex(&ob->defbase, defgroup); const int totgrp = BLI_listbase_count(&ob->defbase); @@ -2095,20 +2135,17 @@ void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup) LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - if (gps->dvert != NULL) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + MDeformVert *dvert = &gpc->dvert[i]; + gpencil_remove_dvert_ex(dvert, def_nr, totgrp); + } + } + else if (gps->dvert != NULL) { for (int i = 0; i < gps->totpoints; i++) { - dvert = &gps->dvert[i]; - MDeformWeight *dw = BKE_defvert_find_index(dvert, def_nr); - if (dw != NULL) { - BKE_defvert_remove_group(dvert, dw); - } - /* Reorganize weights for other groups after deleted one. */ - for (int g = 0; g < totgrp; g++) { - dw = BKE_defvert_find_index(dvert, g); - if ((dw != NULL) && (dw->def_nr > def_nr)) { - dw->def_nr--; - } - } + MDeformVert *dvert = &gps->dvert[i]; + gpencil_remove_dvert_ex(dvert, def_nr, totgrp); } } } @@ -2127,8 +2164,12 @@ void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup) */ void BKE_gpencil_dvert_ensure(bGPDstroke *gps) { - if (gps->dvert == NULL) { - gps->dvert = MEM_callocN(sizeof(MDeformVert) * gps->totpoints, "gp_stroke_weights"); + if (GPENCIL_STROKE_TYPE_BEZIER(gps) && gps->editcurve->dvert == NULL) { + bGPDcurve *gpc = gps->editcurve; + gpc->dvert = MEM_callocN(sizeof(MDeformVert) * gpc->tot_curve_points, __func__); + } + else if (gps->dvert == NULL) { + gps->dvert = MEM_callocN(sizeof(MDeformVert) * gps->totpoints, __func__); } } @@ -2654,7 +2695,7 @@ bool BKE_gpencil_from_image( BKE_gpencil_stroke_select_index_set(gpd, gps); } - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } @@ -2949,19 +2990,37 @@ void BKE_gpencil_frame_original_pointers_update(const struct bGPDframe *gpf_orig /* Assign original stroke pointer. */ if (gps_eval != NULL) { gps_eval->runtime.gps_orig = gps_orig; + if (GPENCIL_STROKE_TYPE_BEZIER(gps_orig)) { + bGPDcurve *gpc_orig = gps_orig->editcurve; + bGPDcurve *gpc_eval = gps_eval->editcurve; - /* Assign original point pointer. */ - for (int i = 0; i < gps_orig->totpoints; i++) { - if (i > gps_eval->totpoints - 1) { - break; + for (int i = 0; i < gpc_orig->tot_curve_points; i++) { + if (i > gpc_eval->tot_curve_points - 1) { + break; + } + bGPDcurve_point *gpc_pt_orig = &gpc_orig->curve_points[i]; + bGPDcurve_point *gpc_pt_eval = &gpc_eval->curve_points[i]; + gpc_pt_orig->runtime.gpc_pt_orig = NULL; + gpc_pt_orig->runtime.idx_orig = i; + gpc_pt_eval->runtime.gpc_pt_orig = gpc_pt_orig; + gpc_pt_eval->runtime.idx_orig = i; } - bGPDspoint *pt_orig = &gps_orig->points[i]; - bGPDspoint *pt_eval = &gps_eval->points[i]; - pt_orig->runtime.pt_orig = NULL; - pt_orig->runtime.idx_orig = i; - pt_eval->runtime.pt_orig = pt_orig; - pt_eval->runtime.idx_orig = i; } + else { + /* Assign original point pointer. */ + for (int i = 0; i < gps_orig->totpoints; i++) { + if (i > gps_eval->totpoints - 1) { + break; + } + bGPDspoint *pt_orig = &gps_orig->points[i]; + bGPDspoint *pt_eval = &gps_eval->points[i]; + pt_orig->runtime.pt_orig = NULL; + pt_orig->runtime.idx_orig = i; + pt_eval->runtime.pt_orig = pt_orig; + pt_eval->runtime.idx_orig = i; + } + } + /* Increase pointer. */ gps_eval = gps_eval->next; } diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 344be7bc0f5..01a57b01f4a 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -37,14 +37,17 @@ #include "BLT_translation.h" #include "DNA_collection_types.h" +#include "DNA_color_types.h" #include "DNA_gpencil_types.h" #include "DNA_material_types.h" #include "DNA_meshdata_types.h" #include "DNA_scene_types.h" #include "BKE_collection.h" +#include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_curve.h" +#include "BKE_deform.h" #include "BKE_gpencil.h" #include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" @@ -56,8 +59,6 @@ #include "DEG_depsgraph_query.h" -#define COORD_FITTING_INFLUENCE 20.0f - /* -------------------------------------------------------------------- */ /** \name Convert to curve object * \{ */ @@ -330,7 +331,6 @@ static void gpencil_convert_spline(Main *bmain, * The total of points must consider that last point of each segment is equal to the first * point of next segment. */ - int totpoints = 0; int segments = 0; int resolu = nu->resolu + 1; segments = nu->pntsu; @@ -338,7 +338,6 @@ static void gpencil_convert_spline(Main *bmain, segments--; cyclic = false; } - totpoints = (resolu * segments) - (segments - 1); /* Materials * Notice: The color of the material is the color of viewport and not the final shader color. @@ -358,69 +357,43 @@ static void gpencil_convert_spline(Main *bmain, switch (nu->type) { case CU_POLY: { - /* Allocate memory for storage points. */ - gps->totpoints = nu->pntsu; - gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + bGPDcurve *gpc = BKE_gpencil_stroke_editcurve_new(nu->pntsu); + /* Increase thickness for this type. */ gps->thickness = 10.0f; - /* Get all curve points */ - for (int s = 0; s < gps->totpoints; s++) { - BPoint *bp = &nu->bp[s]; - bGPDspoint *pt = &gps->points[s]; - copy_v3_v3(&pt->x, bp->vec); - pt->pressure = bp->radius; - pt->strength = 1.0f; + for (int i = 0; i < nu->pntsu; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + BPoint *bp = &nu->bp[i]; + copy_v3_v3(cpt->bezt.vec[1], bp->vec); + cpt->pressure = bp->radius; + cpt->strength = 1.0f; + cpt->bezt.h1 = cpt->bezt.h2 = HD_VECT; } + + gps->editcurve = gpc; + BKE_gpencil_editcurve_recalculate_handles(gps); break; } case CU_BEZIER: { - /* Allocate memory for storage points. */ - gps->totpoints = totpoints; - gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); - - int init = 0; - resolu = nu->resolu + 1; - segments = nu->pntsu; - if ((nu->flagu & CU_NURB_CYCLIC) == 0) { - segments--; - } - /* Get all interpolated curve points of Beziert */ - for (int s = 0; s < segments; s++) { - int inext = (s + 1) % nu->pntsu; - BezTriple *prevbezt = &nu->bezt[s]; - BezTriple *bezt = &nu->bezt[inext]; - bool last = (bool)(s == segments - 1); - - coord_array = MEM_callocN((size_t)3 * resolu * sizeof(float), __func__); + /* Create new grease pencil editcurve. */ + bGPDcurve *gpc = BKE_gpencil_stroke_editcurve_new(nu->pntsu); + for (int i = 0; i < nu->pntsu; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + BezTriple *bezt = &nu->bezt[i]; for (int j = 0; j < 3; j++) { - BKE_curve_forward_diff_bezier(prevbezt->vec[1][j], - prevbezt->vec[2][j], - bezt->vec[0][j], - bezt->vec[1][j], - coord_array + j, - resolu - 1, - sizeof(float[3])); - } - /* Save first point coordinates. */ - if (s == 0) { - copy_v3_v3(init_co, &coord_array[0]); + copy_v3_v3(cpt->bezt.vec[j], bezt->vec[j]); } - /* Add points to the stroke */ - float radius_start = prevbezt->radius * scale_thickness; - float radius_end = bezt->radius * scale_thickness; - gpencil_add_new_points( - gps, coord_array, radius_start, radius_end, init, resolu, init_co, last); - /* Free memory. */ - MEM_SAFE_FREE(coord_array); - - /* As the last point of segment is the first point of next segment, back one array - * element to avoid duplicated points on the same location. - */ - init += resolu - 1; + cpt->pressure = bezt->radius * scale_thickness; + cpt->strength = 1.0f; + cpt->bezt.h1 = bezt->h1; + cpt->bezt.h2 = bezt->h2; } + + gps->editcurve = gpc; + BKE_gpencil_editcurve_recalculate_handles(gps); break; } case CU_NURBS: { @@ -453,9 +426,10 @@ static void gpencil_convert_spline(Main *bmain, break; } } - /* Cyclic curve, close stroke. */ + + /* Cyclic curve. */ if (cyclic) { - BKE_gpencil_stroke_close(gps); + gps->flag |= GP_STROKE_CYCLIC; } if (sample > 0.0f) { @@ -463,7 +437,7 @@ static void gpencil_convert_spline(Main *bmain, } /* Recalc fill geometry. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } static void gpencil_editstroke_deselect_all(bGPDcurve *gpc) @@ -566,8 +540,182 @@ void BKE_gpencil_convert_curve(Main *bmain, /** \name Edit-Curve Kernel Functions * \{ */ -static bGPDcurve *gpencil_stroke_editcurve_generate_edgecases(bGPDstroke *gps, - const float stroke_radius) +typedef struct tCurveFitPoint { + float x, y, z; + float pressure; + float strength; + float color[4]; +} tCurveFitPoint; + +#define CURVE_FIT_POINT_DIM 9 + +/* We treat pressure, strength and vertex colors as dimensions in the curve fitting like the + * position. But this means that e.g. a high pressure will change the shape of the + * higher-dimensional fitted curve which will in turn change the shape of the projected + * 3-dimensional curve. But we don't really want the pressure or something else than the position + * to influence the shape. So this COORD_FITTING_INFLUENCE will "dampen" the effect of the other + * attributes affecting the shape. Ideally, we fit the other attributes separate from the position. + */ +#define COORD_FITTING_INFLUENCE 20.0f + +typedef struct tGPCurveSegment { + struct tGPCurveSegment *next, *prev; + + float *point_array; + uint32_t point_array_len; + + bGPDcurve_point *curve_points; + float *cubic_array; + uint32_t cubic_array_len; + + uint32_t *cubic_orig_index; + uint32_t *corners_index_array; + uint32_t corners_index_len; + +} tGPCurveSegment; + +/** + * Find the stationary points of a cubic bezier curve. + * Calcualtes the t-values (factor along curve) of the stationary points of a cubic bezier curve by + * finding the roots of the first derivative. If no roots where found the function returns false. + * Otherwise if one of the roots was found, one of r_t1 or r_t2 will be NaN and the other will + * contain the t-value of the root found. If both roots were found, both r_t1 and r_t2 will contain + * a value. + * \param p1, p2, p3, p4: Points of the cubic bezier curve. + * \param r_t1, r_t2: Return t-values (factor along curve). + */ +static bool find_cubic_bezier_stationary_points( + const float p1, const float p2, const float p3, const float p4, float *r_t1, float *r_t2) +{ + float a = 6.0f * (-p1 + 3.0f * p2 - 3.0f * p3 + p4); + if (IS_EQF(a, 0.0f)) { + /* Special edge-case that we have to handle seperately.*/ + if ((p1 == p4) && (p2 == p3)) { + if (p1 == p2) { + *r_t1 = NAN; + *r_t2 = NAN; + return false; + } + *r_t1 = 0.5f; + *r_t2 = NAN; + return true; + } + /* Denominator is zero. No roots. */ + return false; + } + + float x = p4 * (p1 - p2) - p3 * (p1 + p2) + p2 * p2 + p3 * p3; + if (x < 0.0f) { + /* Negative number under square root. No real roots. */ + return false; + } + + float s = 6.0f * sqrtf(x); + float b = 6.0f * (p1 - 2.0f * p2 + p3); + + float t1 = (s - b) / a; + float t2 = -(s + b) / a; + + /* Discard root outside of limits. */ + *r_t1 = IN_RANGE_INCL(t1, 0.0f, 1.0f) ? t1 : NAN; + *r_t2 = IN_RANGE_INCL(t2, 0.0f, 1.0f) ? t2 : NAN; + + return !(*r_t1 == NAN) || !(*r_t2 == NAN); +} + +static void gpencil_free_curve_segment(tGPCurveSegment *tcs) +{ + if (tcs == NULL) { + return; + } + + if (tcs->point_array != NULL && tcs->point_array_len > 0) { + MEM_freeN(tcs->point_array); + } + if (tcs->curve_points != NULL && tcs->cubic_array_len > 0) { + MEM_freeN(tcs->curve_points); + } + if (tcs->cubic_array != NULL && tcs->cubic_array_len > 0) { + free(tcs->cubic_array); + } + if (tcs->cubic_orig_index != NULL && tcs->cubic_array_len > 0) { + free(tcs->cubic_orig_index); + } + if (tcs->corners_index_array != NULL && tcs->corners_index_len > 0) { + free(tcs->corners_index_array); + } + + MEM_freeN(tcs); +} + +static bool gpencil_is_segment_tagged(bGPDstroke *gps, const uint32_t cpt_start) +{ + bGPDcurve *gpc = gps->editcurve; + const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC); + const uint32_t cpt_end = (cpt_start + 1) % gpc->tot_curve_points; + + const uint32_t start_pt_idx = gpc->curve_points[cpt_start].point_index; + const uint32_t end_pt_idx = gpc->curve_points[cpt_end].point_index; + + if (!is_cyclic && (cpt_start == gpc->tot_curve_points - 1)) { + return false; + } + + /* Check endpoints of segment frist. */ + if ((gps->points[start_pt_idx].flag & GP_SPOINT_TAG) || + (gps->points[end_pt_idx].flag & GP_SPOINT_TAG)) { + return true; + } + + /* Iterate over all stroke points in between and check if any point is tagged. */ + if (is_cyclic && (cpt_start == gpc->tot_curve_points - 1)) { + for (int i = start_pt_idx + 1; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + if ((pt->flag & GP_SPOINT_TAG)) { + return true; + } + } + } + else { + for (int i = start_pt_idx + 1; i < end_pt_idx - 1; i++) { + bGPDspoint *pt = &gps->points[i]; + if ((pt->flag & GP_SPOINT_TAG)) { + return true; + } + } + } + + return false; +} + +static int gpencil_count_tagged_curve_segments(bGPDstroke *gps) +{ + bGPDcurve *gpc = gps->editcurve; + /* Handle single point edgecase. */ + if (gpc->tot_curve_points == 1) { + return (gps->points[0].flag & GP_SPOINT_TAG) ? 1 : 0; + } + + /* Iterate over points and check if segment is tagged. */ + int count = 0; + for (int i = 0; i < gpc->tot_curve_points; i++) { + if (gpencil_is_segment_tagged(gps, i)) { + count++; + } + } + + return count; +} + +static void gpencil_clear_point_tag(bGPDstroke *gps) +{ + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + pt->flag &= ~GP_SPOINT_TAG; + } +} + +static bGPDcurve *gpencil_stroke_editcurve_generate_edgecases(bGPDstroke *gps) { BLI_assert(gps->totpoints < 3); @@ -577,6 +725,8 @@ static bGPDcurve *gpencil_stroke_editcurve_generate_edgecases(bGPDstroke *gps, bGPDcurve_point *cpt = &editcurve->curve_points[0]; BezTriple *bezt = &cpt->bezt; + /* This is not the actual radius, but it is good enough for what we need. */ + float stroke_radius = gps->thickness / 1000.0f; /* Handles are twice as long as the radius of the point. */ float offset = (pt->pressure * stroke_radius) * 2.0f; @@ -639,154 +789,465 @@ static bGPDcurve *gpencil_stroke_editcurve_generate_edgecases(bGPDstroke *gps, 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) +/* Helper: Generates a tGPCurveSegment using the stroke points in gps from the start index up to + * the end index. */ +static tGPCurveSegment *gpencil_fit_curve_to_points_ex(bGPDstroke *gps, + const float diag_length, + const float error_threshold, + const float corner_angle, + const uint32_t start_idx, + const uint32_t end_idx, + const bool is_cyclic) { - if (gps->totpoints < 3) { - return gpencil_stroke_editcurve_generate_edgecases(gps, stroke_radius); - } -#define POINT_DIM 9 + const uint32_t length = end_idx - start_idx + 1; - 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; + tGPCurveSegment *tcs = MEM_callocN(sizeof(tGPCurveSegment), __func__); + tcs->point_array_len = length * CURVE_FIT_POINT_DIM; + tcs->point_array = MEM_callocN(sizeof(float) * tcs->point_array_len, __func__); + float tmp_vec[3]; + for (int i = 0; i < length; i++) { + bGPDspoint *pt = &gps->points[i + start_idx]; + int row = i * CURVE_FIT_POINT_DIM; + tCurveFitPoint *cfpt = (tCurveFitPoint *)&tcs->point_array[row]; /* 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; + mul_v3_v3fl(&cfpt->x, tmp_vec, COORD_FITTING_INFLUENCE / diag_length); + cfpt->pressure = 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); + cfpt->strength = pt->strength / diag_length; + mul_v4_v4fl(cfpt->color, pt->vert_color, 1.0f / diag_length); } - uint calc_flag = CURVE_FIT_CALC_HIGH_QUALIY; - if (gps->totpoints > 2 && gps->flag & GP_STROKE_CYCLIC) { + int calc_flag = CURVE_FIT_CALC_HIGH_QUALIY; + if (is_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, + int r = curve_fit_cubic_to_points_refit_fl(tcs->point_array, + length, + CURVE_FIT_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) { + &tcs->cubic_array, + &tcs->cubic_array_len, + &tcs->cubic_orig_index, + &tcs->corners_index_array, + &tcs->corners_index_len); + + if (r != 0 || tcs->cubic_array_len < 1) { + gpencil_free_curve_segment(tcs); return NULL; } - uint curve_point_size = 3 * POINT_DIM; + tcs->curve_points = MEM_callocN(sizeof(bGPDcurve_point) * tcs->cubic_array_len, __func__); - 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]; + for (int i = 0; i < tcs->cubic_array_len; i++) { + bGPDcurve_point *cpt = &tcs->curve_points[i]; BezTriple *bezt = &cpt->bezt; - float *curve_point = &r_cubic_array[i * curve_point_size]; + tCurveFitPoint *curve_point = (tCurveFitPoint *)&tcs->cubic_array[i * 3 * CURVE_FIT_POINT_DIM]; + /* Loop over bezier triple and fill coordinates. */ for (int j = 0; j < 3; j++) { - float *bez = &curve_point[j * POINT_DIM]; + float *bez = &curve_point[j].x; 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); + tCurveFitPoint *ctrl_point = &curve_point[1]; + cpt->pressure = ctrl_point->pressure * diag_length; + cpt->strength = ctrl_point->strength * diag_length; + mul_v4_v4fl(cpt->vert_color, ctrl_point->color, diag_length); - /* default handle type */ + /* Default handle type */ bezt->h1 = HD_ALIGN; bezt->h2 = HD_ALIGN; - cpt->point_index = r_cubic_orig_index[i]; + /* Make sure to add the start index. */ + cpt->point_index = tcs->cubic_orig_index[i] + start_idx; + + /* Select the curve point if the original stroke point was selected. */ + if (gps->points[cpt->point_index].flag & GP_SPOINT_SELECT) { + cpt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } } - 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; + /* Set handle type to HD_FREE for corner handles. Ignore first and last. */ + if (tcs->corners_index_array != NULL) { + uint i_start = 0, i_end = tcs->corners_index_len; + + if ((tcs->corners_index_len >= 2) && (calc_flag & CURVE_FIT_CALC_CYCLIC) == 0) { + i_start += 1; + i_end -= 1; } - for (int i = start; i < end; i++) { - bGPDcurve_point *cpt = &editcurve->curve_points[r_corners_index_array[i]]; + + for (uint i = i_start; i < i_end; i++) { + bGPDcurve_point *cpt = &tcs->curve_points[tcs->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); + return tcs; +} + +static float get_point_weight(MDeformVert *dvert, bool inverse, int def_nr) +{ + float weight = 1.0f; + + if ((dvert != NULL) && (def_nr != -1)) { + MDeformWeight *dw = BKE_defvert_find_index(dvert, def_nr); + weight = dw ? dw->weight : -1.0f; + if ((weight >= 0.0f) && (inverse == 1)) { + return -1.0f; + } + + if ((weight < 0.0f) && (inverse == 0)) { + return -1.0f; + } + + /* if inverse, weight is always 1 */ + if ((weight < 0.0f) && (inverse == 1)) { + return 1.0f; + } } - if (r_corners_index_array) { - free(r_corners_index_array); + + /* handle special empty groups */ + if ((dvert == NULL) && (def_nr != -1)) { + if (inverse == 1) { + return 1.0f; + } + + return -1.0f; } - if (r_cubic_orig_index) { - free(r_cubic_orig_index); + + return weight; +} + +/** + * 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) +{ + if (gps->totpoints < 3) { + return gpencil_stroke_editcurve_generate_edgecases(gps); } + const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC); + const float diag_length = len_v3v3(gps->boundbox_min, gps->boundbox_max); + tGPCurveSegment *tcs = gpencil_fit_curve_to_points_ex( + gps, diag_length, error_threshold, corner_angle, 0, gps->totpoints - 1, is_cyclic); + + bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_new(tcs->cubic_array_len); + memcpy( + editcurve->curve_points, tcs->curve_points, sizeof(bGPDcurve_point) * tcs->cubic_array_len); + gpencil_free_curve_segment(tcs); -#undef POINT_DIM return editcurve; } /** - * Updates the editcurve for a stroke. Frees the old curve if one exists and generates a new one. + * Creates a bGPDcurve by doing a cubic curve fitting on the tagged segments (parts of the curve + * with at least one bGPDspoint with GP_SPOINT_TAG). + */ +bGPDcurve *BKE_gpencil_stroke_editcurve_tagged_segments_update(bGPDstroke *gps, + const float error_threshold, + const float corner_angle) +{ + if (gps->totpoints < 3) { + BKE_gpencil_free_stroke_editcurve(gps); + return gpencil_stroke_editcurve_generate_edgecases(gps); + } + + /* The reason why we cant just take all the bad segments and regenerate them is because that + * will leave the control points at their original spot (if a control point moved, we should no + * longer treat it as a control point). Therefor the only option is to find */ + + bGPDcurve *gpc = gps->editcurve; + const int num_segments = gpc->tot_curve_points - 1; + float diag_length = len_v3v3(gps->boundbox_min, gps->boundbox_max); + + ListBase curve_segments = {NULL, NULL}; + + /* TODO: If the stroke is cyclic we need to move the start of the curve to the last curve point + * that is not tagged. */ + + tGPCurveSegment *tcs; + int j = 0, new_num_segments = 0; + for (int i = 0; i < num_segments; i = j) { + int island_length = 1; + int start_idx = gpc->curve_points[i].point_index; + bool is_tagged = gpencil_is_segment_tagged(gps, i); + + /* Find the end of this island (tagged or un-tagged). */ + int end_idx; + bGPDspoint *end_pt; + for (j = i + 1; j < gpc->tot_curve_points; j++, island_length++) { + end_idx = gpc->curve_points[j].point_index; + end_pt = &gps->points[end_idx]; + if (!(is_tagged && (end_pt->flag & GP_SPOINT_TAG)) && + (gpencil_is_segment_tagged(gps, j) != is_tagged)) { + break; + } + } + + if (is_tagged) { + /* Regenerate this segment. */ + tcs = gpencil_fit_curve_to_points_ex( + gps, diag_length, error_threshold, corner_angle, start_idx, end_idx, false); + } + else { + /* Save this segment. */ + tcs = MEM_callocN(sizeof(tGPCurveSegment), __func__); + tcs->cubic_array_len = island_length; + tcs->point_array_len = end_idx - start_idx + 1; + tcs->curve_points = MEM_callocN(sizeof(bGPDcurve_point) * tcs->cubic_array_len, __func__); + memcpy(tcs->curve_points, + &gpc->curve_points[i], + sizeof(bGPDcurve_point) * tcs->cubic_array_len); + } + + new_num_segments += (tcs->cubic_array_len - 1); + BLI_addtail(&curve_segments, tcs); + } + + /* TODO: If the stroke is cyclic we need to check if the last segment needs to be regenerated or + * saved. */ + bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_new(new_num_segments + 1); + + /* Copy first point. */ + tGPCurveSegment *frist_cs = (tGPCurveSegment *)&curve_segments.first; + memcpy(&editcurve->curve_points[0], &frist_cs->curve_points[0], sizeof(bGPDcurve_point)); + + /* Combine listbase curve segments to gpencil curve. */ + int offset = 0; + LISTBASE_FOREACH_MUTABLE (tGPCurveSegment *, cs, &curve_segments) { + /* Copy second handle of first point */ + bGPDcurve_point *first_dst = &editcurve->curve_points[offset]; + bGPDcurve_point *first_src = &cs->curve_points[0]; + copy_v3_v3(first_dst->bezt.vec[2], first_src->bezt.vec[2]); + + /* Copy the rest of the points. */ + memcpy(&editcurve->curve_points[offset + 1], + &cs->curve_points[1], + sizeof(bGPDcurve_point) * (cs->cubic_array_len - 1)); + + offset += (cs->cubic_array_len - 1); + gpencil_free_curve_segment(cs); + } + + /* Free the old curve. */ + BKE_gpencil_free_stroke_editcurve(gps); + return editcurve; +} + +static void gpencil_stroke_editcurve_regenerate_single_ex(bGPDcurve_point *start, + bGPDcurve_point *end, + bGPDspoint *points, + const int totpoints, + const float boundbox_min[3], + const float diag_length, + const bool is_cyclic, + const float error_threshold, + float *r_error_sq) +{ +#define POINT_DIM 3 + BezTriple *bezt_prev = &start->bezt; + BezTriple *bezt_next = &end->bezt; + + const uint32_t start_pt_idx = start->point_index; + const uint32_t end_pt_idx = end->point_index; + + uint32_t length = 0; + if (start_pt_idx > end_pt_idx) { + if (!is_cyclic) { + return; + } + length = (totpoints - start_pt_idx) + end_pt_idx + 1; + } + else { + length = (end_pt_idx - start_pt_idx) + 1; + } + + uint32_t point_array_len = length * POINT_DIM; + float *point_array = MEM_callocN(sizeof(float) * point_array_len, __func__); + + float tmp_vec[3]; + for (int i = 0; i < length; i++) { + int idx = (i + start_pt_idx) % totpoints; + bGPDspoint *pt = &points[idx]; + float *point = &point_array[i * POINT_DIM]; + /* normalize coordinate to 0..1 */ + sub_v3_v3v3(tmp_vec, &pt->x, boundbox_min); + mul_v3_v3fl(point, tmp_vec, 1.0f / diag_length); + } + + float tan_l[3], tan_r[3]; + + sub_v3_v3v3(tan_l, bezt_prev->vec[1], bezt_prev->vec[2]); + normalize_v3(tan_l); + sub_v3_v3v3(tan_r, bezt_next->vec[0], bezt_next->vec[1]); + normalize_v3(tan_r); + + float error_sq; + uint error_index; + int r = curve_fit_cubic_to_points_single_fl(point_array, + length, + NULL, + POINT_DIM, + error_threshold, + tan_l, + tan_r, + bezt_prev->vec[2], + bezt_next->vec[0], + &error_sq, + &error_index); + if (r != 0) { + MEM_freeN(point_array); + return; + } + + madd_v3_v3v3fl(bezt_prev->vec[2], boundbox_min, bezt_prev->vec[2], diag_length); + madd_v3_v3v3fl(bezt_next->vec[0], boundbox_min, bezt_next->vec[0], diag_length); + + if (!ELEM(bezt_prev->h2, HD_FREE, HD_ALIGN)) { + bezt_prev->h2 = (bezt_prev->h2 == HD_VECT) ? HD_FREE : HD_ALIGN; + } + if (!ELEM(bezt_next->h1, HD_FREE, HD_ALIGN)) { + bezt_next->h1 = (bezt_next->h1 == HD_VECT) ? HD_FREE : HD_ALIGN; + } + + MEM_freeN(point_array); + + if (r_error_sq != NULL) { + *r_error_sq = error_sq; + } +#undef POINT_DIM +} + +/** + * Regenerate the handles for the segment from `start_idx` to `end_idx` by refitting the curve to + * the points. As an example, this is used by the dissolve operator to fit the curve to a segment + * where the curve poitns have been deleted. + * Note: `start_idx` can be greater than `end_idx`. If this is the case and the stroke is cyclic, + * then the fitting will use the points from the end and beginning of the stroke. + */ +void BKE_gpencil_stroke_editcurve_regenerate_single(bGPDstroke *gps, + uint32_t start_idx, + uint32_t end_idx, + const float error_threshold) +{ + if (!GPENCIL_STROKE_TYPE_BEZIER(gps) || (start_idx == end_idx)) { + return; + } + bGPDcurve *gpc = gps->editcurve; + const float diag_length = len_v3v3(gps->boundbox_min, gps->boundbox_max); + + BLI_assert(start_idx < gpc->tot_curve_points && end_idx < gpc->tot_curve_points); + + bGPDcurve_point *start = &gpc->curve_points[start_idx]; + bGPDcurve_point *end = &gpc->curve_points[end_idx]; + + gpencil_stroke_editcurve_regenerate_single_ex(start, + end, + gps->points, + gps->totpoints, + gps->boundbox_min, + diag_length, + gps->flag & GP_STROKE_CYCLIC, + error_threshold, + NULL); +} + +/** + * Updates the editcurve for a stroke. + * \param gps: The stroke. + * \param threshold: Fitting threshold. The gernerated curve should not deviate more than this + * amount from the stroke. + * \param corner_angle: If angles greater than this amount are detected during fitting, they will + * be sharp (non-aligned handles). + * \param flag: Flag for refitting options (see eGPStrokeGeoUpdateFlag). */ -void BKE_gpencil_stroke_editcurve_update(bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps) +void BKE_gpencil_stroke_refit_curve(bGPDstroke *gps, + const float threshold, + const float corner_angle, + const eGPStrokeGeoUpdateFlag flag) { if (gps == NULL || gps->totpoints < 0) { return; } + bGPDcurve *editcurve = NULL; + short prev_flag = 0; + + /* If editcurve exists save the selection to the stroke points (only for syncing later). */ if (gps->editcurve != NULL) { - BKE_gpencil_free_stroke_editcurve(gps); + BKE_gpencil_stroke_editcurve_sync_selection(NULL, gps, gps->editcurve); + } + + /* Do a partial update by only updating the curve segments that contain tagged points. */ + if ((flag & GP_GEO_UPDATE_CURVE_PARTIAL_REFIT) && gps->editcurve != NULL) { + prev_flag = gps->editcurve->flag; + /* Find the segments that need an update, then update them. */ + const int tot_num_segments = (gps->flag & GP_STROKE_CYCLIC) ? + gps->editcurve->tot_curve_points : + gps->editcurve->tot_curve_points - 1; + const int num_sel_segments = gpencil_count_tagged_curve_segments(gps); + if (num_sel_segments == 0) { + /* No update needed. */ + return; + } + else if (num_sel_segments == tot_num_segments) { + /* All segments need to be updated. Do a full update. */ + BKE_gpencil_free_stroke_editcurve(gps); + editcurve = BKE_gpencil_stroke_editcurve_generate(gps, threshold, corner_angle); + } + else { + /* Some segments are unchanged. Do a partial update. */ + editcurve = BKE_gpencil_stroke_editcurve_tagged_segments_update( + gps, threshold, corner_angle); + gpencil_clear_point_tag(gps); + } } + else { + /* Do a full update. Delete the old curve and generate a new one. */ + if (gps->editcurve != NULL) { + prev_flag = gps->editcurve->flag; + BKE_gpencil_free_stroke_editcurve(gps); + } - float defaultpixsize = 1000.0f / gpd->pixfactor; - float stroke_radius = ((gps->thickness + gpl->line_change) / defaultpixsize) / 2.0f; + editcurve = BKE_gpencil_stroke_editcurve_generate(gps, threshold, corner_angle); + } - bGPDcurve *editcurve = BKE_gpencil_stroke_editcurve_generate( - gps, gpd->curve_edit_threshold, gpd->curve_edit_corner_angle, stroke_radius); if (editcurve == NULL) { + /* This should not happen. Maybe add an assert here? */ return; } + /* Assign pointer. This makes the stroke a bezier stroke now. */ gps->editcurve = editcurve; + if (prev_flag) { + gps->editcurve->flag = prev_flag; + } + + if ((flag & GP_GEO_UPDATE_CURVE_PARTIAL_REFIT)) { + BKE_gpencil_editcurve_recalculate_handles(gps); + } } /** * Sync the selection from stroke to editcurve */ -void BKE_gpencil_editcurve_stroke_sync_selection(bGPdata *UNUSED(gpd), - 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; + BKE_gpencil_stroke_select_index_set(gpd, gps); for (int i = 0; i < gpc->tot_curve_points; i++) { bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; @@ -804,6 +1265,7 @@ void BKE_gpencil_editcurve_stroke_sync_selection(bGPdata *UNUSED(gpd), else { gpc->flag &= ~GP_CURVE_SELECT; gpencil_editstroke_deselect_all(gpc); + BKE_gpencil_stroke_select_index_reset(gps); } } @@ -814,7 +1276,9 @@ void BKE_gpencil_stroke_editcurve_sync_selection(bGPdata *gpd, bGPDstroke *gps, { if (gpc->flag & GP_CURVE_SELECT) { gps->flag |= GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_set(gpd, gps); + if (gpd != NULL) { + BKE_gpencil_stroke_select_index_set(gpd, gps); + } for (int i = 0; i < gpc->tot_curve_points - 1; i++) { bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; @@ -868,7 +1332,9 @@ void BKE_gpencil_stroke_editcurve_sync_selection(bGPdata *gpd, bGPDstroke *gps, } else { gps->flag &= ~GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_reset(gps); + if (gpd != NULL) { + BKE_gpencil_stroke_select_index_reset(gps); + } for (int i = 0; i < gps->totpoints; i++) { bGPDspoint *pt = &gps->points[i]; pt->flag &= ~GP_SPOINT_SELECT; @@ -876,28 +1342,36 @@ void BKE_gpencil_stroke_editcurve_sync_selection(bGPdata *gpd, bGPDstroke *gps, } } -static void gpencil_interpolate_fl_from_to( +static float smooth_interpf(const float target, const float origin, const float t) +{ + float fac = 3.0f * t * t - 2.0f * t * t * t; // smooth + return interpf(target, origin, fac); +} + +static void smooth_interp_v4_v4v4(float *r, const float *a, const float *b, const float t) +{ + float fac = 3.0f * t * t - 2.0f * t * t * t; // smooth + interp_v4_v4v4(r, a, b, fac); +} + +static void gpencil_interp_stride_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 = smooth_interpf(to, from, (float)i / (float)it); r = POINTER_OFFSET(r, stride); } } -static void gpencil_interpolate_v4_from_to( +static void gpencil_interp_stride_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); + smooth_interp_v4_v4v4(r, from, to, (float)i / (float)it); r = POINTER_OFFSET(r, stride); } } @@ -916,49 +1390,66 @@ static float gpencil_approximate_curve_segment_arclength(bGPDcurve_point *cpt_st 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) +/* Helper: Interpolate curve point attributes from curve point pair into point array. */ +static void gpencil_calculate_stroke_points_curve_segment(bGPDcurve_point *cpt, + bGPDcurve_point *cpt_next, + float *points_offset, + int resolu, + int stride, + const eGPStrokeGeoUpdateFlag flag) { - /* 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); + const bool update_all_attributes = (flag == GP_GEO_UPDATE_DEFAULT); + + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_POSITION)) { + /* 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); + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_PRESSURE)) { + gpencil_interp_stride_fl_from_to(cpt->pressure, + cpt_next->pressure, + POINTER_OFFSET(points_offset, sizeof(float) * 3), + resolu, + stride); + } + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_STRENGTH)) { + gpencil_interp_stride_fl_from_to(cpt->strength, + cpt_next->strength, + POINTER_OFFSET(points_offset, sizeof(float) * 4), + resolu, + stride); + } + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_COLOR)) { + gpencil_interp_stride_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, + const uint num_segments, bool is_cyclic, - int *r_points_len) + const eGPStrokeGeoUpdateFlag flag, + int *r_points_len, + int **r_segment_lengths) { /* 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; @@ -992,7 +1483,7 @@ static float *gpencil_stroke_points_from_editcurve_adaptive_resolu( 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); + cpt_curr, cpt_next, points_offset, segment_resolu, stride, flag); /* update the index */ cpt_curr->point_index = point_index; point_index += segment_resolu; @@ -1005,12 +1496,11 @@ static float *gpencil_stroke_points_from_editcurve_adaptive_resolu( 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); + cpt_curr, cpt_next, points_offset, segment_resolu, stride, flag); } - MEM_freeN(segment_point_lengths); - *r_points_len = points_len; + *r_segment_lengths = segment_point_lengths; return (float(*))r_points; } @@ -1021,6 +1511,7 @@ static float *gpencil_stroke_points_from_editcurve_fixed_resolu(bGPDcurve_point int curve_point_array_len, int resolution, bool is_cyclic, + const eGPStrokeGeoUpdateFlag flag, int *r_points_len) { /* One stride contains: x, y, z, pressure, strength, Vr, Vg, Vb, Vmix_factor */ @@ -1037,7 +1528,7 @@ static float *gpencil_stroke_points_from_editcurve_fixed_resolu(bGPDcurve_point bGPDcurve_point *cpt_next = &curve_point_array[i + 1]; gpencil_calculate_stroke_points_curve_segment( - cpt_curr, cpt_next, points_offset, resolution, stride); + cpt_curr, cpt_next, points_offset, resolution, stride, flag); /* update the index */ cpt_curr->point_index = i * resolution; points_offset = POINTER_OFFSET(points_offset, resolu_stride); @@ -1048,7 +1539,7 @@ static float *gpencil_stroke_points_from_editcurve_fixed_resolu(bGPDcurve_point 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); + cpt_curr, cpt_next, points_offset, resolution, stride, flag); } *r_points_len = points_len; @@ -1060,15 +1551,17 @@ static float *gpencil_stroke_points_from_editcurve_fixed_resolu(bGPDcurve_point */ void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps, const uint resolution, - const bool adaptive) + const bool adaptive, + const eGPStrokeGeoUpdateFlag flag) { if (gps == NULL || gps->editcurve == NULL) { return; } + const bool update_all_attributes = (flag == GP_GEO_UPDATE_DEFAULT); - bGPDcurve *editcurve = gps->editcurve; - bGPDcurve_point *curve_point_array = editcurve->curve_points; - int curve_point_array_len = editcurve->tot_curve_points; + bGPDcurve *gpc = gps->editcurve; + bGPDcurve_point *curve_point_array = gpc->curve_points; + int curve_point_array_len = gpc->tot_curve_points; if (curve_point_array_len == 0) { return; } @@ -1078,17 +1571,30 @@ void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps, /* 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]); + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_POSITION)) { + copy_v3_v3(&pt->x, cpt->bezt.vec[1]); + } + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_PRESSURE)) { + pt->pressure = cpt->pressure; + } + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_STRENGTH)) { + pt->strength = cpt->strength; + } + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_COLOR)) { + copy_v4_v4(pt->vert_color, cpt->vert_color); + } - pt->pressure = cpt->pressure; - pt->strength = cpt->strength; + if (gpc->dvert != NULL && (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_WEIGHT))) { + gps->dvert = MEM_recallocN(gps->dvert, sizeof(MDeformVert) * gps->totpoints); - copy_v4_v4(pt->vert_color, cpt->vert_color); + /* Copy the weights of the curve point. */ + MDeformVert *dv_first = &gpc->dvert[0]; + MDeformVert *dvert = &gps->dvert[0]; + BKE_defvert_copy(dvert, dv_first); + BKE_defvert_sync(dvert, dv_first, true); + } /* deselect */ pt->flag &= ~GP_SPOINT_SELECT; @@ -1102,65 +1608,161 @@ void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps, int points_len = 0; float(*points)[9] = NULL; + int *segment_length_cache = NULL; + const uint num_segments = (is_cyclic) ? curve_point_array_len : curve_point_array_len - 1; if (adaptive) { points = (float(*)[9])gpencil_stroke_points_from_editcurve_adaptive_resolu( - curve_point_array, curve_point_array_len, resolution, is_cyclic, &points_len); + curve_point_array, + curve_point_array_len, + resolution, + num_segments, + is_cyclic, + flag, + &points_len, + &segment_length_cache); } else { points = (float(*)[9])gpencil_stroke_points_from_editcurve_fixed_resolu( - curve_point_array, curve_point_array_len, resolution, is_cyclic, &points_len); + curve_point_array, curve_point_array_len, resolution, is_cyclic, flag, &points_len); } if (points == NULL || points_len == 0) { return; } + /* We have to free all of the old weight data and replace it completely. */ + BKE_gpencil_free_stroke_weights(gps); + /* 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); - } + int idx = 0; /* write new data to stroke point array */ for (int i = 0; i < points_len; i++) { + bGPDcurve_point *gpc_pt = &curve_point_array[idx % curve_point_array_len]; 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]); + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_POSITION)) { + copy_v3_v3(&pt->x, &points[i][0]); + } + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_PRESSURE)) { + pt->pressure = points[i][3]; + } + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_STRENGTH)) { + pt->strength = points[i][4]; + } + if (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_COLOR)) { + copy_v4_v4(pt->vert_color, &points[i][5]); + } /* deselect points */ pt->flag &= ~GP_SPOINT_SELECT; + + if (gpc_pt->point_index == i) { + pt->flag |= GP_SPOINT_IS_BEZT_CONTROL; + idx++; + } } gps->flag &= ~GP_STROKE_SELECT; BKE_gpencil_stroke_select_index_reset(gps); + /* Interpolate weights. */ + if (gpc->dvert != NULL && (update_all_attributes || (flag & GP_GEO_UPDATE_POLYLINE_WEIGHT))) { + gps->dvert = MEM_callocN(sizeof(MDeformVert) * gps->totpoints, __func__); + + idx = 0; + for (int i = 0; i < gpc->tot_curve_points - 1; i++) { + MDeformVert *dv_curr = &gpc->dvert[i]; + MDeformVert *dv_next = &gpc->dvert[i + 1]; + int segment_length = (adaptive) ? segment_length_cache[i] : resolution; + int totweight = max_ii(dv_curr->totweight, dv_next->totweight); + + if (totweight == 0) { + idx += segment_length; + continue; + } + + for (int j = 0; j < segment_length; j++) { + MDeformVert *dvert = &gps->dvert[idx + j]; + BKE_defvert_copy(dvert, dv_curr); + float t = (float)j / (float)segment_length; + for (int d = 0; d < totweight; d++) { + MDeformWeight *dw_a = BKE_defvert_ensure_index(dv_curr, d); + MDeformWeight *dw_b = BKE_defvert_ensure_index(dv_next, d); + MDeformWeight *dw_final = BKE_defvert_ensure_index(dvert, d); + + if (dw_a->weight == dw_b->weight) { + dw_final->weight = dw_a->weight; + } + else { + dw_final->weight = smooth_interpf(dw_b->weight, dw_a->weight, t); + } + } + } + + idx += segment_length; + } + + if (is_cyclic) { + /* Interpolate weights between last and first curve point. */ + MDeformVert *dv_curr = &gpc->dvert[gpc->tot_curve_points - 1]; + MDeformVert *dv_next = &gpc->dvert[0]; + int segment_length = (adaptive) ? segment_length_cache[gpc->tot_curve_points - 1] : + resolution; + int totweight = max_ii(dv_curr->totweight, dv_next->totweight); + + if (totweight) { + for (int j = 0; j < segment_length; j++) { + MDeformVert *dvert = &gps->dvert[idx + j]; + BKE_defvert_copy(dvert, dv_curr); + float t = (float)j / (float)segment_length; + for (int d = 0; d < totweight; d++) { + MDeformWeight *dw_a = BKE_defvert_ensure_index(dv_curr, d); + MDeformWeight *dw_b = BKE_defvert_ensure_index(dv_next, d); + MDeformWeight *dw_final = BKE_defvert_ensure_index(dvert, d); + + if (dw_a->weight == dw_b->weight) { + dw_final->weight = dw_a->weight; + } + else { + dw_final->weight = smooth_interpf(dw_b->weight, dw_a->weight, t); + } + } + } + } + } + else { + /* Copy the weights of the last curve point. */ + MDeformVert *dv_last = &gpc->dvert[gpc->tot_curve_points - 1]; + MDeformVert *dvert = &gps->dvert[gps->totpoints - 1]; + BKE_defvert_copy(dvert, dv_last); + BKE_defvert_sync(dvert, dv_last, true); + } + } + /* free temp data */ MEM_freeN(points); + MEM_SAFE_FREE(segment_length_cache); } /** * Recalculate the handles of the edit curve of a grease pencil stroke */ -void BKE_gpencil_editcurve_recalculate_handles(bGPDstroke *gps) +bool BKE_gpencil_editcurve_recalculate_handles(bGPDstroke *gps) { if (gps == NULL || gps->editcurve == NULL) { - return; + return false; } bool changed = false; bGPDcurve *gpc = gps->editcurve; if (gpc->tot_curve_points < 2) { - return; + return false; } 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++) { @@ -1183,34 +1785,26 @@ void BKE_gpencil_editcurve_recalculate_handles(bGPDstroke *gps) 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; + 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; + + if (gps->flag & GP_STROKE_CYCLIC) { 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; - + else { 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; + changed = true; } + + return changed; } /* Helper: count how many new curve points must be generated. */ @@ -1238,6 +1832,46 @@ static int gpencil_editcurve_subdivide_count(bGPDcurve *gpc, bool is_cyclic) return count; } +/* Helper: Divide the curve segment at t, where t is a value between 0 and 1. */ +static void gpencil_editcurve_subdivide_curve_segment_factor(bGPDcurve_point *cpt_start, + bGPDcurve_point *cpt_end, + bGPDcurve_point *cpt_new, + const float t) +{ + BLI_assert(t >= 0.0f && t <= 1.0f); + + BezTriple *bezt_start = &cpt_start->bezt; + BezTriple *bezt_end = &cpt_end->bezt; + BezTriple *bezt_new = &cpt_new->bezt; + const float z = 1 - t, tt = t * t, zz = z * z, ttt = t * t * t, zzz = z * z * z; + + 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 = (t * p1) + (z * p0); + q0 = (tt * p2) + 2 * (t * z * p1) + (zz * p0); + b = (ttt * p3) + 3 * (tt * z * p2) + 3 * (t * zz * p1) + (zzz * p0); + q1 = (tt * p3) + 2 * (t * z * p2) + (zz * p1); + m1 = (t * p3) + (z * p2); + + bezt_new->vec[0][axis] = q0; + bezt_new->vec[1][axis] = b; + bezt_new->vec[2][axis] = q1; + + bezt_start->vec[2][axis] = m0; + bezt_end->vec[0][axis] = m1; + } + + cpt_new->pressure = interpf(cpt_end->pressure, cpt_start->pressure, t); + cpt_new->strength = interpf(cpt_end->strength, cpt_start->strength, t); + interp_v4_v4v4(cpt_new->vert_color, cpt_start->vert_color, cpt_end->vert_color, t); + /* TODO: interpolate weights */ +} + static void gpencil_editcurve_subdivide_curve_segment(bGPDcurve_point *cpt_start, bGPDcurve_point *cpt_end, bGPDcurve_point *cpt_new) @@ -1280,6 +1914,9 @@ void BKE_gpencil_editcurve_subdivide(bGPDstroke *gps, const int cuts) bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; /* repeat for number of cuts */ + /* TODO: Ideally the subdivisions should match how it works in the rest of Blender. Here we get + * 1->1, 2->3, 3->7 etc. because we keep supdividing already subdivided segments. But it would be + * better to get the exact number of cuts per segment.*/ 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); @@ -1352,66 +1989,510 @@ void BKE_gpencil_editcurve_subdivide(bGPDstroke *gps, const int cuts) } } -void BKE_gpencil_strokes_selected_update_editcurve(bGPdata *gpd) +static void gpencil_refit_single_from_to(bGPDstroke *gps, + bGPDcurve *gpc, + bGPDcurve_point *new_points, + int from_start, + int from_end, + int to_start, + int to_end, + float error_threshold) { - 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; + bGPDcurve_point *cpt_start = &gpc->curve_points[from_start]; + bGPDcurve_point *cpt_end = &gpc->curve_points[from_end]; + bGPDcurve_point *new_cpt_start = &new_points[to_start]; + bGPDcurve_point *new_cpt_end = &new_points[to_end]; + + BKE_gpencil_stroke_editcurve_regenerate_single(gps, from_start, from_end, error_threshold); + + memcpy(new_cpt_start, cpt_start, sizeof(bGPDcurve_point)); + memcpy(new_cpt_end, cpt_end, sizeof(bGPDcurve_point)); +} + +/** + * Dissolves the curve points tagged with `flag`. + * \param gps: The stroke. + * \param flag: flag (see eGPDcurve_point_Flag). + * \param refit_segments: Do a refit of the segments where points have been dissolved. + * \param error_threshold: Refit threshold when refitting. + * + * \returns The number of points that remain after dissolving. Can be 0 in which case the caller + * should delete the stroke. + */ +int BKE_gpencil_editcurve_dissolve(bGPDstroke *gps, + const uint flag, + const bool refit_segments, + const float error_threshold) +{ + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL || gpc->tot_curve_points < 2) { + return gpc->tot_curve_points; + } + const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC); + + int first = -1, last = 0; + int num_points_remaining = gpc->tot_curve_points; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if ((cpt->flag & flag)) { + num_points_remaining--; } - 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; + else { + if (first < 0) { + first = i; + } + last = i; + } + } + + /* All points will be deleted. */ + if (num_points_remaining < 1) { + return 0; + } + /* All points remain. */ + else if (num_points_remaining == gpc->tot_curve_points) { + return gpc->tot_curve_points; + } + + bGPDcurve_point *new_points = MEM_callocN(sizeof(bGPDcurve_point) * num_points_remaining, + __func__); + + int new_idx = 0, start = first, end = first; + for (int i = first; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + bGPDcurve_point *new_cpt = &new_points[new_idx]; + if ((cpt->flag & flag) == 0) { + memcpy(new_cpt, cpt, sizeof(bGPDcurve_point)); + /* Check that the indices are in bounds. */ + if (refit_segments && (start > 0) && IN_RANGE(end, start, gpc->tot_curve_points)) { + /* Refit this segment. */ + gpencil_refit_single_from_to( + gps, gpc, new_points, start - 1, end, new_idx - 1, new_idx, error_threshold); + start = end = i; + } + new_idx++; + start++; + } + end++; + } + + if (is_cyclic && refit_segments) { + /* Make sure that there is at least one segment that needs updating. */ + if (last != first && first >= 0 && last >= 0 && + !(first == 0 && last == (gpc->tot_curve_points - 1))) { + + /* Refit this segment. */ + gpencil_refit_single_from_to( + gps, gpc, new_points, last, first, num_points_remaining - 1, 0, error_threshold); + } + } + + /* Recreate array. */ + 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); + + return num_points_remaining; +} + +/* Helper: Dissolve the curve point with the lowest re-fit error. Dissolve only if error is under + * `threshold`. Returns true if a point was dissolved. */ +static bool gpencil_editcurve_dissolve_smallest_error_curve_point(bGPDstroke *gps, + bGPDcurve *gpc, + const float diag_length, + const bool is_cyclic, + const float threshold) +{ + bool changed = false; + /* Duplicate the curve point array. */ + bGPDcurve_point *curve_point_array = MEM_dupallocN(gpc->curve_points); + + float lowest_error = FLT_MAX; + int lowest_error_idx = 0; + /* Loop over control point pairs with one control point in between. + * Find the control point that produces the lowest error when removed. */ + for (int i = 0; i < gpc->tot_curve_points - 2; i++) { + bGPDcurve_point *cpt_prev = &curve_point_array[i]; + bGPDcurve_point *cpt_next = &curve_point_array[i + 2]; + + float error_sq; + /* Regenerate the handles between cpt_prev and cpt_next as if the point in the middle didn't + * exist. Get the re-fit error. */ + gpencil_stroke_editcurve_regenerate_single_ex(cpt_prev, + cpt_next, + gps->points, + gps->totpoints, + gps->boundbox_min, + diag_length, + is_cyclic, + 0.0f, + &error_sq); + + if (error_sq < lowest_error) { + lowest_error = error_sq; + lowest_error_idx = i + 1; + } + } + + /* Dissolve the control point with the lowest error found. */ + if (sqrtf(lowest_error) < threshold) { + int new_tot_curve_points = gpc->tot_curve_points - 1; + bGPDcurve_point *new_points = MEM_callocN(sizeof(bGPDcurve_point) * new_tot_curve_points, + __func__); + /* Copy all other points. Skip the point that will be dissolved. */ + memcpy(new_points, gpc->curve_points, lowest_error_idx * sizeof(bGPDcurve_point)); + memcpy(new_points + lowest_error_idx, + gpc->curve_points + lowest_error_idx + 1, + (new_tot_curve_points - lowest_error_idx) * sizeof(bGPDcurve_point)); + + /* Get the start and end points of the segment. */ + bGPDcurve_point *cpt_start = &curve_point_array[lowest_error_idx - 1]; + bGPDcurve_point *cpt_end = &curve_point_array[lowest_error_idx + 1]; + bGPDcurve_point *new_cpt_start = &new_points[lowest_error_idx - 1]; + bGPDcurve_point *new_cpt_end = &new_points[lowest_error_idx]; + + /* Write the new handles. */ + copy_v3_v3(new_cpt_start->bezt.vec[2], cpt_start->bezt.vec[2]); + copy_v3_v3(new_cpt_end->bezt.vec[0], cpt_end->bezt.vec[0]); + + /* Overwrite curve points. */ + MEM_freeN(gpc->curve_points); + gpc->curve_points = new_points; + gpc->tot_curve_points = new_tot_curve_points; + + changed = true; + } + + MEM_freeN(curve_point_array); + + return changed; +} + +/** + * Simplify Adaptive + * Dissolves all the curve points with a refit-error that is less than threshold. + */ +void BKE_gpencil_editcurve_simplify_adaptive(bGPDstroke *gps, const float threshold) +{ + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL || gpc->tot_curve_points < 3) { + return; + } + const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + const float diag_length = len_v3v3(gps->boundbox_min, gps->boundbox_max); + + bool changed = true; + /* Loop until we have removed all points that causes an error less than `threshold`. */ + while (gpc->tot_curve_points > 2 && changed) { + changed = gpencil_editcurve_dissolve_smallest_error_curve_point( + gps, gpc, diag_length, is_cyclic, threshold); + } +} + +/** + * Simplify Fixed + * Dissolves `count` curve points with the lowest refit-error. + */ +void BKE_gpencil_editcurve_simplify_fixed(bGPDstroke *gps, const int count) +{ + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL || gpc->tot_curve_points < 3 || count < 1) { + return; + } + const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + const float diag_length = len_v3v3(gps->boundbox_min, gps->boundbox_max); + + /* Loop until we have removed all points that causes an error less than `threshold`. */ + for (int i = count; i >= 0 && gpc->tot_curve_points > 2; i--) { + gpencil_editcurve_dissolve_smallest_error_curve_point( + gps, gpc, diag_length, is_cyclic, FLT_MAX); + } +} + +/** + * Smooth curve extra + * \param gps: The grease pencil stroke. + * \param factor: Smoothing factor. + * \param step_size: Number of adjacent points to consider when smoothing. + * \param repeat: Number of times to repeat the smoothing process. + * \param only_selected: Only smooth selected points. + * \param affect_endpoints: Include end-points when smoothing (only used when stroke is + * non-cyclic). + * \param use_vertex_groups: Use vertex group to mask the smoothing effect. + * \param invert_weights: Invert the effect when using vertex groups. + * \param deform_group: The vertex group. + * \param curve_mapping: If not NULL use the curve mapping along the stroke to weigh the influence + * of the smoothing effect. + * \param do_positions: Smooth positions. + * \param do_pressure: Smooth pressure. + * \param do_strength: Smooth strength. + */ +void BKE_gpencil_editcurve_smooth_ex(bGPDstroke *gps, + const float factor, + const uint step_size, + const uint repeat, + const bool only_selected, + const bool affect_endpoints, + const bool use_vertex_groups, + const bool invert_weights, + const int deform_group, + const CurveMapping *curve_mapping, + const bool do_positions, + const bool do_pressure, + const bool do_strength) +{ + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL || gpc->tot_curve_points < 2 || factor == 0.0f || repeat < 1) { + return; + } + + if (!(do_positions || do_pressure || do_strength)) { + return; + } + + const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + const bool use_curve_mapping = (curve_mapping != NULL); + + /* Total number of cubic points. */ + const uint tot_points = gpc->tot_curve_points * 3; + for (uint r = 0; r < repeat; r++) { + uint start_idx = (affect_endpoints || is_cyclic) ? 0 : 2; + uint end_idx = (affect_endpoints || is_cyclic) ? tot_points : tot_points - 2; + + for (uint i = start_idx; i < end_idx; i++) { + /* bGPDcurve_point index */ + uint pt_idx = i / 3; + /* BezTriple handle index */ + uint hd_idx = i % 3; + + bGPDcurve_point *gpc_pt = &gpc->curve_points[pt_idx]; + MDeformVert *dvert = &gpc->dvert[pt_idx]; + if ((gpc_pt->flag & GP_CURVE_POINT_SELECT) == 0 && only_selected) { + continue; + } + + float weight = 1.0f; + if (use_vertex_groups) { + weight = get_point_weight(dvert, invert_weights, deform_group); + if (weight < 0.0f) { + continue; + } + } + + if (curve_mapping != NULL) { + float value = (float)i / (float)(tot_points - 1); + weight *= BKE_curvemapping_evaluateF(curve_mapping, 0, value); + if (weight < 0.0f) { + continue; + } + } + + BezTriple *bezt = &gpc_pt->bezt; + float sco[3] = {0.0f}; + float smoothed_pressure = 0.0f; + float smoothed_strength = 0.0f; + + float average_fac = 1.0f / (float)(step_size * 2 + 1); + float weighted_factor = factor; + if (use_vertex_groups || use_curve_mapping) { + weighted_factor *= weight; + } + + if (do_positions) { + /* Include the current point. */ + madd_v3_v3fl(sco, bezt->vec[hd_idx], average_fac); + + for (uint step = 1; step <= step_size; step++) { + int prev_idx = i - step; + int next_idx = i + step; + + if (is_cyclic) { + prev_idx = mod_i(prev_idx, tot_points); + next_idx = next_idx % tot_points; + } + else { + CLAMP_MIN(prev_idx, 0); + CLAMP_MAX(next_idx, tot_points - 1); } - /* 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; - } + uint prev_pt_idx = prev_idx / 3; + uint prev_hd_idx = prev_idx % 3; + uint next_pt_idx = next_idx / 3; + uint next_hd_idx = next_idx % 3; + + bGPDcurve_point *gpc_prev_pt = &gpc->curve_points[prev_pt_idx]; + bGPDcurve_point *gpc_next_pt = &gpc->curve_points[next_pt_idx]; + BezTriple *prev_bezt = &gpc_prev_pt->bezt; + BezTriple *next_bezt = &gpc_next_pt->bezt; + + madd_v3_v3fl(sco, prev_bezt->vec[prev_hd_idx], average_fac); + madd_v3_v3fl(sco, next_bezt->vec[next_hd_idx], average_fac); + } + + interp_v3_v3v3(bezt->vec[hd_idx], bezt->vec[hd_idx], sco, weighted_factor); + } + + if (do_pressure && hd_idx == 1) { + /* Include the current point. */ + smoothed_pressure += gpc_pt->pressure * average_fac; + + for (uint step = 1; step <= step_size; step++) { + int prev_idx = pt_idx - step; + int next_idx = pt_idx + step; + + if (is_cyclic) { + prev_idx = mod_i(prev_idx, gpc->tot_curve_points); + next_idx = next_idx % gpc->tot_curve_points; + } + else { + CLAMP_MIN(prev_idx, 0); + CLAMP_MAX(next_idx, gpc->tot_curve_points - 1); + } + + bGPDcurve_point *gpc_prev_pt = &gpc->curve_points[prev_idx]; + bGPDcurve_point *gpc_next_pt = &gpc->curve_points[next_idx]; + smoothed_pressure += gpc_prev_pt->pressure * average_fac; + smoothed_pressure += gpc_next_pt->pressure * average_fac; + } + + gpc_pt->pressure = interpf(smoothed_pressure, gpc_pt->pressure, weighted_factor); + } + + if (do_strength && hd_idx == 1) { + /* Include the current point. */ + smoothed_strength += gpc_pt->strength * average_fac; + + for (uint step = 1; step <= step_size; step++) { + int prev_idx = pt_idx - step; + int next_idx = pt_idx + step; + + if (is_cyclic) { + prev_idx = mod_i(prev_idx, gpc->tot_curve_points); + next_idx = next_idx % gpc->tot_curve_points; } - else if (gps->editcurve->flag & GP_CURVE_NEEDS_STROKE_UPDATE) { - BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + else { + CLAMP_MIN(prev_idx, 0); + CLAMP_MAX(next_idx, gpc->tot_curve_points - 1); } - /* Update the selection from the stroke to the curve. */ - BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve); - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps); + bGPDcurve_point *gpc_prev_pt = &gpc->curve_points[prev_idx]; + bGPDcurve_point *gpc_next_pt = &gpc->curve_points[next_idx]; + smoothed_strength += gpc_prev_pt->strength * average_fac; + smoothed_strength += gpc_next_pt->strength * average_fac; } + + gpc_pt->strength = interpf(smoothed_strength, gpc_pt->strength, weighted_factor); } } } } -void BKE_gpencil_strokes_selected_sync_selection_editcurve(bGPdata *gpd) +/** + * Smooth curve + * \param gps: The grease pencil stroke. + * \param factor: Smoothing factor. + * \param step_size: Number of adjacent points to consider when smoothing. + * \param repeat: Number of times to repeat the smoothing process. + * \param only_selected: Only smooth selected points. + * \param affect_endpoints: Include end-points when smoothing (only used when stroke is + * non-cyclic). + * \param do_positions: Smooth positions. + * \param do_pressure: Smooth pressure. + * \param do_strength: Smooth strength. + */ +void BKE_gpencil_editcurve_smooth(bGPDstroke *gps, + const float factor, + const uint step_size, + const uint repeat, + const bool only_selected, + const bool affect_endpoints, + const bool do_positions, + const bool do_pressure, + const bool do_strength) +{ + BKE_gpencil_editcurve_smooth_ex(gps, + factor, + step_size, + repeat, + only_selected, + affect_endpoints, + false, + false, + 0, + NULL, + do_positions, + do_pressure, + do_strength); +} + +/** + * Curve Merge by Distance + * Merge the control points by distance. Merging will always occur on the first point. + * The first and last point are never merged. Note: The caller is resposible for the geometry + * update of the stroke. + * \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. + * \param refit_segments: Set to refit segments where points were dissolved. + * \param error_threshold: Error threshold for refitting (only used when refit_segments = true). + * \returns True if any point was merged and the geometry changed. + */ +bool BKE_gpencil_editcurve_merge_distance(bGPDstroke *gps, + const float threshold, + const bool use_unselected, + const bool refit_segments, + const float error_threshold) { - 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)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc == NULL || gpc->tot_curve_points < 2 || threshold == 0.0f) { + return false; + } + const float th_square = threshold * threshold; + + bool tagged = false; + int i = 0, step = 1; + while ((i < gpc->tot_curve_points - 1) && (i + step < gpc->tot_curve_points)) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + bGPDcurve_point *gpc_pt_next = &gpc->curve_points[i + step]; + if ((!use_unselected && ((gpc_pt->flag & GP_CURVE_POINT_SELECT) == 0 || + (gpc_pt_next->flag & GP_CURVE_POINT_SELECT) == 0)) || + (gpc_pt->flag & GP_CURVE_POINT_TAG)) { + i++; + step = 1; continue; } - bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { - if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && is_multiedit)) { - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - bGPDcurve *gpc = gps->editcurve; - if (gpc != NULL) { - /* Update the selection of every stroke that has an editcurve */ - BKE_gpencil_stroke_editcurve_sync_selection(gpd, gps, gpc); - } - } - } + if (gpc_pt_next->flag & GP_CURVE_POINT_TAG) { + step++; + continue; } + BezTriple *bezt = &gpc_pt->bezt; + BezTriple *bezt_next = &gpc_pt_next->bezt; + + float len_square = len_squared_v3v3(bezt->vec[1], bezt_next->vec[1]); + if (len_square <= th_square) { + gpc_pt_next->flag |= GP_CURVE_POINT_TAG; + tagged = true; + step++; + } + else { + i++; + step = 1; + } + } + + gpc->curve_points[0].flag &= ~GP_CURVE_POINT_TAG; + gpc->curve_points[gpc->tot_curve_points - 1].flag &= ~GP_CURVE_POINT_TAG; + + int old_num_points = gpc->tot_curve_points; + if (tagged) { + return BKE_gpencil_editcurve_dissolve( + gps, GP_CURVE_POINT_TAG, refit_segments, error_threshold) != old_num_points; } + return false; } /** \} */ diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index 4dcd94fdeec..be5a148f6f9 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -523,7 +523,7 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, gps->totpoints = i; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); return true; } @@ -708,7 +708,7 @@ bool BKE_gpencil_stroke_split(bGPdata *gpd, * Keep the end point. */ BKE_gpencil_stroke_trim_points(gps, 0, old_count); - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); return true; } @@ -1376,29 +1376,25 @@ 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 + * \param flag: eGPStrokeGeoUpdateFlag flag (use GP_GEO_UPDATE_DEFAULT=0 for the default) */ -void BKE_gpencil_stroke_geometry_update(bGPdata *gpd, bGPDstroke *gps) +void BKE_gpencil_stroke_geometry_update(bGPdata *gpd, + bGPDstroke *gps, + const eGPStrokeGeoUpdateFlag flag) { 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; - } + /* Update curve points first if it's a bezier stroke. */ + if (GPENCIL_STROKE_TYPE_BEZIER(gps) && GP_GEO_UPDATE_POLYLINE_REGENERATE_ANY(flag)) { + /* Regenerate the polyline points from the curve data. */ + const uint resolution = gpd->curve_edit_resolution; + const bool is_adaptive = gpd->flag & GP_DATA_CURVE_ADAPTIVE_RESOLUTION; + BKE_gpencil_stroke_update_geometry_from_editcurve(gps, resolution, is_adaptive, flag); } + /* Triangulate the stroke. */ if (gps->totpoints > 2) { BKE_gpencil_stroke_fill_triangulate(gps); } @@ -1407,7 +1403,7 @@ void BKE_gpencil_stroke_geometry_update(bGPdata *gpd, bGPDstroke *gps) MEM_SAFE_FREE(gps->triangles); } - /* calc uv data along the stroke */ + /* Calc UV data along the stroke. */ BKE_gpencil_stroke_uv_update(gps); /* Calc stroke bounding box. */ @@ -1559,7 +1555,7 @@ bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps) MEM_SAFE_FREE(old_dvert); } - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); return intersect; } @@ -1736,7 +1732,7 @@ void BKE_gpencil_dissolve_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, gps->totpoints = tot; /* triangles cache needs to be recalculated */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } @@ -1884,7 +1880,7 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float e gps->totpoints = j; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); MEM_SAFE_FREE(old_points); MEM_SAFE_FREE(old_dvert); @@ -1950,7 +1946,7 @@ void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps) gps->totpoints = j; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); MEM_SAFE_FREE(old_points); MEM_SAFE_FREE(old_dvert); @@ -2072,7 +2068,7 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* Merge by distance ------------------------------------- */ @@ -2157,7 +2153,7 @@ void BKE_gpencil_stroke_merge_distance(bGPdata *gpd, } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } typedef struct GpEdge { @@ -2372,7 +2368,7 @@ static void gpencil_generate_edgeloops(Object *ob, pt->strength = 1.0f; } - BKE_gpencil_stroke_geometry_update(gpd, gps_stroke); + BKE_gpencil_stroke_geometry_update(gpd, gps_stroke, GP_GEO_UPDATE_DEFAULT); } /* Free memory. */ @@ -2557,7 +2553,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, BKE_gpencil_stroke_subdivide(gpd, gps_fill, 1, GP_SUBDIV_SIMPLE); } - BKE_gpencil_stroke_geometry_update(gpd, gps_fill); + BKE_gpencil_stroke_geometry_update(gpd, gps_fill, GP_GEO_UPDATE_DEFAULT); } } } @@ -2614,7 +2610,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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } } @@ -2706,7 +2702,7 @@ void BKE_gpencil_point_coords_apply(bGPdata *gpd, const GPencilPointCoordinates } /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } } @@ -2743,7 +2739,7 @@ void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd, } /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } } @@ -2768,49 +2764,108 @@ void BKE_gpencil_stroke_set_random_color(bGPDstroke *gps) } } +static void gpencil_flip_beztriple(BezTriple *bezt) +{ + /* Flip handle position. */ + float tmp[3]; + copy_v3_v3(tmp, bezt->vec[0]); + copy_v3_v3(bezt->vec[0], bezt->vec[2]); + copy_v3_v3(bezt->vec[2], tmp); + + /* Flip type and slection flag. */ + SWAP(uint8_t, bezt->h1, bezt->h2); + SWAP(uint8_t, bezt->f1, bezt->f3); +} + /* Flip stroke. */ void BKE_gpencil_stroke_flip(bGPDstroke *gps) { - int end = gps->totpoints - 1; - - for (int i = 0; i < gps->totpoints / 2; i++) { - bGPDspoint *point, *point2; - bGPDspoint pt; - - /* save first point */ - point = &gps->points[i]; - pt.x = point->x; - pt.y = point->y; - pt.z = point->z; - pt.flag = point->flag; - pt.pressure = point->pressure; - pt.strength = point->strength; - pt.time = point->time; - copy_v4_v4(pt.vert_color, point->vert_color); - - /* replace first point with last point */ - point2 = &gps->points[end]; - point->x = point2->x; - point->y = point2->y; - point->z = point2->z; - point->flag = point2->flag; - point->pressure = point2->pressure; - point->strength = point2->strength; - point->time = point2->time; - copy_v4_v4(point->vert_color, point2->vert_color); - - /* replace last point with first saved before */ - point = &gps->points[end]; - point->x = pt.x; - point->y = pt.y; - point->z = pt.z; - point->flag = pt.flag; - point->pressure = pt.pressure; - point->strength = pt.strength; - point->time = pt.time; - copy_v4_v4(point->vert_color, pt.vert_color); - - end--; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + + int end = gpc->tot_curve_points - 1; + for (int i = 0; i < gpc->tot_curve_points / 2; i++) { + bGPDcurve_point *cpt_first = &gpc->curve_points[i]; + bGPDcurve_point *cpt_last = &gpc->curve_points[end]; + bGPDcurve_point temp; + + memcpy(&temp.bezt, &cpt_first->bezt, sizeof(BezTriple)); + temp.pressure = cpt_first->pressure; + temp.strength = cpt_first->strength; + temp.point_index = cpt_first->point_index; + temp.flag = cpt_first->flag; + temp.uv_fac = cpt_first->uv_fac; + temp.uv_rot = cpt_first->uv_rot; + copy_v2_v2(temp.uv_fill, cpt_first->uv_fill); + copy_v4_v4(temp.vert_color, cpt_first->vert_color); + + memcpy(&cpt_first->bezt, &cpt_last->bezt, sizeof(BezTriple)); + gpencil_flip_beztriple(&cpt_first->bezt); + cpt_first->pressure = cpt_last->pressure; + cpt_first->strength = cpt_last->strength; + cpt_first->point_index = cpt_last->point_index; + cpt_first->flag = cpt_last->flag; + cpt_first->uv_fac = cpt_last->uv_fac; + cpt_first->uv_rot = cpt_last->uv_rot; + copy_v2_v2(cpt_first->uv_fill, cpt_last->uv_fill); + copy_v4_v4(cpt_first->vert_color, cpt_last->vert_color); + + memcpy(&cpt_last->bezt, &temp.bezt, sizeof(BezTriple)); + gpencil_flip_beztriple(&cpt_last->bezt); + cpt_last->pressure = temp.pressure; + cpt_last->strength = temp.strength; + cpt_last->point_index = temp.point_index; + cpt_last->flag = temp.flag; + cpt_last->uv_fac = temp.uv_fac; + cpt_last->uv_rot = temp.uv_rot; + copy_v2_v2(cpt_last->uv_fill, temp.uv_fill); + copy_v4_v4(cpt_last->vert_color, temp.vert_color); + + end--; + } + } + else { + int end = gps->totpoints - 1; + + for (int i = 0; i < gps->totpoints / 2; i++) { + bGPDspoint *point, *point2; + bGPDspoint pt; + + /* save first point */ + point = &gps->points[i]; + pt.x = point->x; + pt.y = point->y; + pt.z = point->z; + pt.flag = point->flag; + pt.pressure = point->pressure; + pt.strength = point->strength; + pt.time = point->time; + copy_v4_v4(pt.vert_color, point->vert_color); + + /* replace first point with last point */ + point2 = &gps->points[end]; + point->x = point2->x; + point->y = point2->y; + point->z = point2->z; + point->flag = point2->flag; + point->pressure = point2->pressure; + point->strength = point2->strength; + point->time = point2->time; + copy_v4_v4(point->vert_color, point2->vert_color); + + /* replace last point with first saved before */ + point = &gps->points[end]; + point->x = pt.x; + point->y = pt.y; + point->z = pt.z; + point->flag = pt.flag; + point->pressure = pt.pressure; + point->strength = pt.strength; + point->time = pt.time; + copy_v4_v4(point->vert_color, pt.vert_color); + + end--; + } } } @@ -2901,7 +2956,7 @@ static void gpencil_stroke_join_islands(bGPdata *gpd, /* add new stroke at head */ BLI_addhead(&gpf->strokes, join_stroke); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, join_stroke); + BKE_gpencil_stroke_geometry_update(gpd, join_stroke, GP_GEO_UPDATE_DEFAULT); /* remove first stroke */ BLI_remlink(&gpf->strokes, gps_first); @@ -3053,7 +3108,7 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd, } else { /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke, GP_GEO_UPDATE_DEFAULT); if (next_stroke) { BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); @@ -3137,10 +3192,10 @@ void BKE_gpencil_curve_delete_tagged_points(bGPdata *gpd, sizeof(bGPDcurve_point) * island_length); BKE_gpencil_editcurve_recalculate_handles(new_stroke); - new_stroke->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_curve_sync_selection(gpd, new_stroke); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke, GP_GEO_UPDATE_DEFAULT); if (next_stroke) { BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); @@ -3170,10 +3225,10 @@ void BKE_gpencil_curve_delete_tagged_points(bGPdata *gpd, sizeof(bGPDcurve_point) * first_tot_points); BKE_gpencil_editcurve_recalculate_handles(gps_last); - gps_last->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_curve_sync_selection(gpd, gps_last); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps_last); + BKE_gpencil_stroke_geometry_update(gpd, gps_last, GP_GEO_UPDATE_DEFAULT); /* remove first one */ BLI_remlink(&gpf->strokes, gps_first); @@ -3241,7 +3296,6 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a, { bGPDspoint point; bGPDspoint *pt; - int i; const float delta[3] = {1.0f, 1.0f, 1.0f}; float deltatime = 0.0f; @@ -3250,6 +3304,11 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a, return; } + /* Cannot join strokes of different types for now... */ + if (GPENCIL_STROKE_TYPE_BEZIER(gps_a) != GPENCIL_STROKE_TYPE_BEZIER(gps_b)) { + return; + } + if ((gps_a->totpoints == 0) || (gps_b->totpoints == 0)) { return; } @@ -3302,28 +3361,52 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a, BKE_gpencil_stroke_flip(gps_b); } - /* don't visibly link the first and last points? */ - if (leave_gaps) { - /* 1st: add one tail point to start invisible area */ - point = gps_a->points[gps_a->totpoints - 1]; - deltatime = point.time; + const float thickness_ratio = (fit_thickness && gps_a->thickness > 0.0f) ? + (float)gps_b->thickness / (float)gps_a->thickness : + 1.0f; + + if (GPENCIL_STROKE_TYPE_BEZIER(gps_a)) { + /* TODO: Support leave gaps. */ + bGPDcurve *gpc_a = gps_a->editcurve; + bGPDcurve *gpc_b = gps_b->editcurve; - gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, 0.0f); + int old_num_points = gpc_a->tot_curve_points; + gpc_a->tot_curve_points += gpc_b->tot_curve_points; + gpc_a->curve_points = MEM_recallocN(gpc_a->curve_points, + sizeof(bGPDcurve_point) * gpc_a->tot_curve_points); - /* 2nd: add one head point to finish invisible area */ - point = gps_b->points[0]; - gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, deltatime); + memcpy(gpc_a->curve_points + old_num_points, + gpc_b->curve_points, + sizeof(bGPDcurve_point) * gpc_b->tot_curve_points); + /* TODO: copy dvert curve data. */ + + /* Rescale other points. */ + for (int i = old_num_points; i < gpc_a->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc_a->curve_points[i]; + cpt->pressure *= thickness_ratio; + } } + else { + /* don't visibly link the first and last points? */ + if (leave_gaps) { + /* 1st: add one tail point to start invisible area */ + point = gps_a->points[gps_a->totpoints - 1]; + deltatime = point.time; - const float ratio = (fit_thickness && gps_a->thickness > 0.0f) ? - (float)gps_b->thickness / (float)gps_a->thickness : - 1.0f; + gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, 0.0f); - /* 3rd: add all points */ - for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) { - MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : NULL; - gpencil_stroke_copy_point( - gps_a, dvert, pt, delta, pt->pressure * ratio, pt->strength, deltatime); + /* 2nd: add one head point to finish invisible area */ + point = gps_b->points[0]; + gpencil_stroke_copy_point(gps_a, NULL, &point, delta, 0.0f, 0.0f, deltatime); + } + + /* 3rd: add all points */ + for (int i = 0; i < gps_b->totpoints && pt; i++) { + pt = &gps_b->points[i]; + MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : NULL; + gpencil_stroke_copy_point( + gps_a, dvert, pt, delta, pt->pressure * thickness_ratio, pt->strength, deltatime); + } } } @@ -3556,7 +3639,7 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd, } /* Update the geometry of the stroke. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } /** @@ -4067,7 +4150,7 @@ bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d, MEM_freeN(perimeter_points); /* Triangles cache needs to be recalculated. */ - BKE_gpencil_stroke_geometry_update(gpd, perimeter_stroke); + BKE_gpencil_stroke_geometry_update(gpd, perimeter_stroke, GP_GEO_UPDATE_DEFAULT); perimeter_stroke->flag |= GP_STROKE_SELECT | GP_STROKE_CYCLIC; diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index 4d9c9878a67..d24152eae21 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -774,9 +774,7 @@ 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 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) && + const bool do_modifiers = (bool)((!is_multiedit) && (ob->greasepencil_modifiers.first != NULL) && (!GPENCIL_SIMPLIFY_MODIF(scene))); if ((!do_modifiers) && (!do_parent) && (!do_transform)) { return; @@ -812,11 +810,9 @@ 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_render = (bool)(DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); - const bool is_curve_edit = (bool)(GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd) && !is_render); - const bool is_multiedit = (bool)(GPENCIL_MULTIEDIT_SESSIONS_ON(gpd) && !is_render); - const bool do_modifiers = (bool)((!is_multiedit) && (!is_curve_edit) && - (ob->greasepencil_modifiers.first != NULL) && + const bool do_modifiers = (bool)((!is_multiedit) && (ob->greasepencil_modifiers.first != NULL) && (!GPENCIL_SIMPLIFY_MODIF(scene))); if (!do_modifiers) { return; @@ -849,16 +845,20 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) } /* Apply deform modifiers and Time remap (only change geometry). */ - if ((time_remap) || (mti && mti->deformStroke)) { + if ((time_remap) || (mti && mti->deformPolyline) || (mti && mti->deformBezier)) { LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { bGPDframe *gpf = BKE_gpencil_frame_retime_get(depsgraph, scene, ob, gpl); if (gpf == NULL) { continue; } - - if (mti->deformStroke) { - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - mti->deformStroke(md, depsgraph, ob, gpl, gpf, gps); + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (!GPENCIL_STROKE_TYPE_BEZIER(gps)) { + if (mti->deformPolyline) { + mti->deformPolyline(md, depsgraph, ob, gpl, gpf, gps); + } + } + else if (mti->deformBezier) { + mti->deformBezier(md, depsgraph, ob, gpl, gpf, gps); } } } diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 82c4709f187..7b8854d4203 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -4878,7 +4878,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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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 09d43676b8f..947a46d4b37 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -1693,13 +1693,6 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) 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, 292, 14)) || diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 5d3bde428c0..dd5577283c5 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -21,6 +21,7 @@ #define DNA_DEPRECATED_ALLOW #include "BLI_listbase.h" +#include "BLI_math.h" #include "BLI_math_vector.h" #include "BLI_string.h" #include "BLI_utildefines.h" @@ -30,6 +31,7 @@ #include "DNA_brush_types.h" #include "DNA_collection_types.h" #include "DNA_genfile.h" +#include "DNA_gpencil_types.h" #include "DNA_listBase.h" #include "DNA_modifier_types.h" #include "DNA_text_types.h" @@ -303,6 +305,17 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + + /* Init grease pencil curve editing error threshold. */ + if (!DNA_struct_elem_find( + fd->filesdna, "ToolSettings", "float", "gpencil_curve_fit_threshold")) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->toolsettings->sequencer_tool_settings == NULL) { + scene->toolsettings->gpencil_curve_fit_threshold = GP_DEFAULT_CURVE_ERROR; + scene->toolsettings->gpencil_curve_fit_corner_angle = GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE; + } + } + } } if (!MAIN_VERSION_ATLEAST(bmain, 300, 2)) { diff --git a/source/blender/draw/engines/overlay/overlay_gpencil.c b/source/blender/draw/engines/overlay/overlay_gpencil.c index 5c03d70d7be..7e97e6babd6 100644 --- a/source/blender/draw/engines/overlay/overlay_gpencil.c +++ b/source/blender/draw/engines/overlay/overlay_gpencil.c @@ -111,8 +111,7 @@ void OVERLAY_edit_gpencil_cache_init(OVERLAY_Data *vedata) (GPENCIL_EDIT_MODE(gpd) && (ts->gpencil_selectmode_edit != GP_SELECTMODE_STROKE)); - if ((!GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) && - ((!GPENCIL_VERTEX_MODE(gpd) && !GPENCIL_PAINT_MODE(gpd)) || use_vertex_mask)) { + if (((!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); @@ -139,35 +138,25 @@ void OVERLAY_edit_gpencil_cache_init(OVERLAY_Data *vedata) } } - /* Handles and curve point for Curve Edit submode. */ - if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) { + /* Handles and curve point for Edit mode. */ + if (GPENCIL_EDIT_MODE(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); + if (show_points && !hide_select) { + 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, "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); + 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); + 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 */ diff --git a/source/blender/draw/engines/overlay/shaders/edit_gpencil_vert.glsl b/source/blender/draw/engines/overlay/shaders/edit_gpencil_vert.glsl index a6161d36a07..ee7375714aa 100644 --- a/source/blender/draw/engines/overlay/shaders/edit_gpencil_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/edit_gpencil_vert.glsl @@ -21,12 +21,13 @@ void discard_vert() gl_Position = vec4(0.0, 0.0, -3e36, 0.0); } -#define GP_EDIT_POINT_SELECTED 1u /* 1 << 0 */ +#define GP_EDIT_POINT_SELECTED 1u /* 1 << 0 */ #define GP_EDIT_STROKE_SELECTED 2u /* 1 << 1 */ -#define GP_EDIT_MULTIFRAME 4u /* 1 << 2 */ -#define GP_EDIT_STROKE_START 8u /* 1 << 3 */ -#define GP_EDIT_STROKE_END 16u /* 1 << 4 */ -#define GP_EDIT_POINT_DIMMED 32u /* 1 << 5 */ +#define GP_EDIT_MULTIFRAME 4u /* 1 << 2 */ +#define GP_EDIT_STROKE_START 8u /* 1 << 3 */ +#define GP_EDIT_STROKE_END 16u /* 1 << 4 */ +#define GP_EDIT_POINT_DIMMED 32u /* 1 << 5 */ +#define GP_EDIT_POINT_HIDDEN 64u /* 1 << 6 */ #ifdef USE_POINTS # define colorUnselect colorGpencilVertex @@ -62,6 +63,7 @@ void main() bool is_stroke_sel = (vflag & GP_EDIT_STROKE_SELECTED) != 0u; bool is_point_sel = (vflag & GP_EDIT_POINT_SELECTED) != 0u; bool is_point_dimmed = (vflag & GP_EDIT_POINT_DIMMED) != 0u; + bool is_point_hidden = (vflag & GP_EDIT_POINT_HIDDEN) != 0u; if (doWeightColor) { finalColor.rgb = weight_to_rgb(weight); @@ -78,6 +80,9 @@ void main() if (is_point_dimmed) { finalColor.rgb = clamp(colorUnselect.rgb + vec3(0.3), 0.0, 1.0); } + if (is_point_hidden) { + gl_PointSize = sizeVertexGpencil * 0.8; + } if (doStrokeEndpoints && !doWeightColor) { bool is_stroke_start = (vflag & GP_EDIT_STROKE_START) != 0u; diff --git a/source/blender/draw/intern/draw_cache_impl_gpencil.c b/source/blender/draw/intern/draw_cache_impl_gpencil.c index 7fe543db01f..5dab0070aff 100644 --- a/source/blender/draw/intern/draw_cache_impl_gpencil.c +++ b/source/blender/draw/intern/draw_cache_impl_gpencil.c @@ -686,6 +686,7 @@ void DRW_cache_gpencil_sbuffer_clear(Object *ob) #define GP_EDIT_STROKE_START (1 << 3) #define GP_EDIT_STROKE_END (1 << 4) #define GP_EDIT_POINT_DIMMED (1 << 5) +#define GP_EDIT_POINT_HIDDEN (1 << 6) typedef struct gpEditIterData { gpEditVert *verts; @@ -697,16 +698,19 @@ typedef struct gpEditCurveIterData { int vgindex; } gpEditCurveIterData; -static uint32_t gpencil_point_edit_flag(const bool layer_lock, - const bGPDspoint *pt, - int v, - int v_len) +static uint32_t gpencil_point_edit_flag( + const bool layer_lock, const bool is_bezier, const bGPDspoint *pt, int v, int v_len) { uint32_t sflag = 0; SET_FLAG_FROM_TEST(sflag, (!layer_lock) && pt->flag & GP_SPOINT_SELECT, GP_EDIT_POINT_SELECTED); SET_FLAG_FROM_TEST(sflag, v == 0, GP_EDIT_STROKE_START); SET_FLAG_FROM_TEST(sflag, v == (v_len - 1), GP_EDIT_STROKE_END); - SET_FLAG_FROM_TEST(sflag, pt->runtime.pt_orig == NULL, GP_EDIT_POINT_DIMMED); + if (!is_bezier) { + SET_FLAG_FROM_TEST(sflag, pt->runtime.pt_orig == NULL, GP_EDIT_POINT_DIMMED); + } + else { + SET_FLAG_FROM_TEST(sflag, ((pt->flag & GP_SPOINT_IS_BEZT_CONTROL) == 0), GP_EDIT_POINT_HIDDEN); + } return sflag; } @@ -725,6 +729,7 @@ static void gpencil_edit_stroke_iter_cb(bGPDlayer *gpl, const int v = gps->runtime.stroke_start + 1; MDeformVert *dvert = ((iter->vgindex > -1) && gps->dvert) ? gps->dvert : NULL; gpEditVert *vert_ptr = iter->verts + v; + const bool is_bezier = GPENCIL_STROKE_TYPE_BEZIER(gps); const bool layer_lock = (gpl->flag & GP_LAYER_LOCKED); uint32_t sflag = 0; @@ -733,12 +738,14 @@ static void gpencil_edit_stroke_iter_cb(bGPDlayer *gpl, SET_FLAG_FROM_TEST(sflag, gpf->runtime.onion_id != 0.0f, GP_EDIT_MULTIFRAME); for (int i = 0; i < v_len; i++) { - vert_ptr->vflag = sflag | gpencil_point_edit_flag(layer_lock, &gps->points[i], i, v_len); + vert_ptr->vflag = sflag | + gpencil_point_edit_flag(layer_lock, is_bezier, &gps->points[i], i, v_len); vert_ptr->weight = gpencil_point_edit_weight(dvert, i, iter->vgindex); vert_ptr++; } /* Draw line to first point to complete the loop for cyclic strokes. */ - vert_ptr->vflag = sflag | gpencil_point_edit_flag(layer_lock, &gps->points[0], 0, v_len); + vert_ptr->vflag = sflag | + gpencil_point_edit_flag(layer_lock, is_bezier, &gps->points[0], 0, v_len); vert_ptr->weight = gpencil_point_edit_weight(dvert, 0, iter->vgindex); } diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index bff7310e9f7..83ba519c4c7 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -43,6 +43,7 @@ set(SRC gpencil_armature.c gpencil_bake_animation.c gpencil_convert.c + gpencil_curve_draw.c gpencil_data.c gpencil_edit.c gpencil_edit_curve.c diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index d8734c4ae6b..95d7648ace2 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -848,115 +848,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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); /* 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 e95496b51ee..291df3b678e 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -236,7 +236,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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); /* update depsgraph */ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/gpencil/gpencil_bake_animation.c b/source/blender/editors/gpencil/gpencil_bake_animation.c index 1a5e2950e09..af9bd55fb44 100644 --- a/source/blender/editors/gpencil/gpencil_bake_animation.c +++ b/source/blender/editors/gpencil/gpencil_bake_animation.c @@ -346,7 +346,7 @@ static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op depsgraph, &gsc, sctx, gpl_dst, gpf_dst, gps, project_type, false); } else { - BKE_gpencil_stroke_geometry_update(gpd_dst, gps); + BKE_gpencil_stroke_geometry_update(gpd_dst, gps, GP_GEO_UPDATE_DEFAULT); } } } diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 2cb1e09d9a6..2d2d3cf95ab 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -1378,6 +1378,7 @@ static void gpencil_layer_to_curve(bContext *C, nu = NULL; } + /* TODO: check if the strok is of type BEZIER. In that case the conversion can be less expensive. */ switch (mode) { case GP_STROKECONVERT_PATH: gpencil_stroke_to_path(C, diff --git a/source/blender/editors/gpencil/gpencil_curve_draw.c b/source/blender/editors/gpencil/gpencil_curve_draw.c new file mode 100644 index 00000000000..83df6bb63ad --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_curve_draw.c @@ -0,0 +1,845 @@ +/* + * 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) 2017, Blender Foundation + * This is a new part of Blender + * Operators for creating new Grease Pencil primitives (boxes, circles, ...) + */ + +/** \file + * \ingroup edgpencil + */ +#include <stdio.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_string.h" + +#include "BLT_translation.h" + +#include "DNA_brush_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_scene_types.h" +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_brush.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" +#include "BKE_gpencil_geom.h" +#include "BKE_main.h" +#include "BKE_paint.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_types.h" + +#include "ED_gpencil.h" +#include "ED_screen.h" +#include "ED_space_api.h" +#include "ED_view3d.h" + +#include "GPU_immediate.h" +#include "GPU_matrix.h" +#include "GPU_shader.h" +#include "GPU_state.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "gpencil_intern.h" + +/* ------------------------------------------------------------------------- */ +/* Structs & enums */ + +typedef enum eGPDcurve_draw_state { + IN_MOVE = 0, + IN_SET_VECTOR = 1, + IN_DRAG_ALIGNED_HANDLE = 2, + IN_DRAG_FREE_HANDLE = 3, + IN_SET_THICKNESS = 4, +} eGPDcurve_draw_state; + +typedef struct tGPDcurve_draw { + Scene *scene; + ARegion *region; + Object *ob; + bGPdata *gpd; + bGPDlayer *gpl; + bGPDframe *gpf; + bGPDstroke *gps; + bGPDcurve *gpc; + int cframe; + + Brush *brush; + + GP_SpaceConversion gsc; + + /* imval of current event */ + int imval[2]; + /* imval of previous event */ + int imval_prev[2]; + /* imval when mouse was last pressed */ + int imval_start[2]; + /* imval when mouse was last released */ + int imval_end[2]; + bool is_mouse_down; + + bool is_cyclic; + float prev_pressure; + + /* Curve resolution */ + int resolution; + + /* Callback for viewport drawing. */ + void *draw_handle; + + eGPDcurve_draw_state state; +} tGPDcurve_draw; + +enum { + CD_MODAL_CANCEL = 1, + CD_MODAL_CONFIRM, + CD_MODAL_FREE_HANDLE_ON, + CD_MODAL_FREE_HANDLE_OFF, + CD_MODAL_CYCLIC_TOGGLE, + CD_MODAL_DELETE_LAST, + CD_MODAL_SET_THICKNESS, +}; + +/* Forward declaration */ +static void gpencil_curve_draw_init(bContext *C, wmOperator *op, const wmEvent *event); +static void gpencil_curve_draw_update(bContext *C, tGPDcurve_draw *tcd); +static void gpencil_curve_draw_confirm(bContext *C, wmOperator *op, tGPDcurve_draw *tcd); +static void gpencil_curve_draw_exit(bContext *C, wmOperator *op); + +/* ------------------------------------------------------------------------- */ +/* Helper functions */ + +static void debug_print_state(tGPDcurve_draw *tcd) +{ + const char *state_str[] = {"MOVE", "VECTOR", "ALIGN", "FREE", "THICK", "ALPHA"}; + printf("State: %s\tMouse x=%d\ty=%d\tpressed:%s\n", + state_str[tcd->state], + tcd->imval[0], + tcd->imval[1], + (tcd->is_mouse_down) ? "TRUE" : "FALSE"); +} + +static void gpencil_project_mval_to_v3( + Scene *scene, ARegion *region, Object *ob, const int mval_i[2], float r_out[3]) +{ + ToolSettings *ts = scene->toolsettings; + float mval_f[2], mval_prj[2], rvec[3], dvec[3], zfac; + copy_v2fl_v2i(mval_f, mval_i); + + ED_gpencil_drawing_reference_get(scene, ob, ts->gpencil_v3d_align, rvec); + zfac = ED_view3d_calc_zfac(region->regiondata, rvec, NULL); + + if (ED_view3d_project_float_global(region, rvec, mval_prj, V3D_PROJ_TEST_NOP) == + V3D_PROJ_RET_OK) { + sub_v2_v2v2(mval_f, mval_prj, mval_f); + ED_view3d_win_to_delta(region, mval_f, dvec, zfac); + sub_v3_v3v3(r_out, rvec, dvec); + } + else { + zero_v3(r_out); + } +} + +/* Helper: Add a new curve point at the end (duplicating the previous last) */ +static void gpencil_push_curve_point(bContext *C, tGPDcurve_draw *tcd) +{ + bGPDcurve *gpc = tcd->gpc; + int old_num_points = gpc->tot_curve_points; + int new_num_points = old_num_points + 1; + gpc->tot_curve_points = new_num_points; + + gpc->curve_points = MEM_recallocN(gpc->curve_points, sizeof(bGPDcurve_point) * new_num_points); + + 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)); + + new_last->bezt.h1 = new_last->bezt.h2 = HD_VECT; + + BKE_gpencil_stroke_update_geometry_from_editcurve( + tcd->gps, tcd->gpd->curve_edit_resolution, false, GP_GEO_UPDATE_DEFAULT); +} + +/* Helper: Remove the last curve point */ +static void gpencil_pop_curve_point(bContext *C, tGPDcurve_draw *tcd) +{ + bGPdata *gpd = tcd->gpd; + bGPDstroke *gps = tcd->gps; + bGPDcurve *gpc = tcd->gpc; + const int old_num_points = gpc->tot_curve_points; + const int new_num_points = old_num_points - 1; + if (G.debug & G_DEBUG) { + printf("old: %d, new: %d\n", old_num_points, new_num_points); + } + + /* Create new stroke and curve */ + bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(tcd->gps, false, false); + new_stroke->points = NULL; + + bGPDcurve *new_curve = BKE_gpencil_stroke_editcurve_new(new_num_points); + new_curve->flag = gpc->flag; + memcpy(new_curve->curve_points, gpc->curve_points, sizeof(bGPDcurve_point) * new_num_points); + new_stroke->editcurve = new_curve; + + BKE_gpencil_stroke_update_geometry_from_editcurve( + new_stroke, gpd->curve_edit_resolution, false, GP_GEO_UPDATE_DEFAULT); + + /* Remove and free old stroke and curve */ + BLI_remlink(&tcd->gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + + tcd->gps = new_stroke; + tcd->gpc = new_curve; + + BLI_addtail(&tcd->gpf->strokes, new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke, GP_GEO_UPDATE_DEFAULT); + + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); +} + +static void gpencil_set_handle_type_last_point(tGPDcurve_draw *tcd, eBezTriple_Handle type) +{ + bGPDcurve *gpc = tcd->gpc; + bGPDcurve_point *cpt = &gpc->curve_points[gpc->tot_curve_points - 1]; + cpt->bezt.h1 = cpt->bezt.h2 = type; +} + +static void gpencil_set_alpha_last_segment(tGPDcurve_draw *tcd, float alpha) +{ + bGPDstroke *gps = tcd->gps; + bGPDcurve *gpc = tcd->gpc; + + if (gpc->tot_curve_points < 2) { + return; + } + + bGPDcurve_point *old_last = &gpc->curve_points[gpc->tot_curve_points - 2]; + for (uint32_t i = old_last->point_index; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + pt->strength = alpha; + } +} + +static void gpencil_curve_draw_ui_callback(const struct bContext *UNUSED(C), + struct ARegion *UNUSED(region), + void *customdata) +{ + const tGPDcurve_draw *tcd = customdata; + GPU_depth_test(GPU_DEPTH_NONE); + + GPU_matrix_push_projection(); + GPU_polygon_offset(1.0f, 1.0f); + + GPU_matrix_push(); + GPU_matrix_mul(tcd->ob->obmat); + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + /* Draw overlays. */ + if (ELEM(tcd->state, IN_DRAG_ALIGNED_HANDLE, IN_DRAG_FREE_HANDLE)) { + bGPDcurve *gpc = tcd->gpc; + bGPDcurve_point *cpt_last = &gpc->curve_points[gpc->tot_curve_points - 1]; + BezTriple *bezt = &cpt_last->bezt; + + float viewport[4]; + GPU_viewport_size_get_f(viewport); + immUniform2fv("viewportSize", &viewport[2]); + + float color[4] = {0, 0, 0, 1.0f}; + UI_GetThemeColorType3fv(TH_GP_VERTEX_SELECT, SPACE_VIEW3D, color); + + /* TODO: Use the GPU_SHADER_3D_POLYLINE_* shader instead. GPU_line_smooth will be deprecated. + */ + GPU_line_smooth(true); + GPU_blend(GPU_BLEND_ALPHA); + + // immUniform1f("lineWidth", U.pixelsize * 2.0f); + // immUniform1i("lineSmooth", 1); + + immUniformColor4fv(color); + + /* Handle lines. */ + immBegin(GPU_PRIM_LINES, 4); + immVertex3fv(pos, bezt->vec[0]); + immVertex3fv(pos, bezt->vec[1]); + immVertex3fv(pos, bezt->vec[1]); + immVertex3fv(pos, bezt->vec[2]); + immEnd(); + + // immUniform1f("size", U.pixelsize * UI_GetThemeValuef(TH_GP_VERTEX_SIZE) * 2.0f); + + // immUniformColor4fv(color); + + /* Handle points. */ + immBegin(GPU_PRIM_POINTS, 3); + immVertex3fv(pos, bezt->vec[0]); + immVertex3fv(pos, bezt->vec[1]); + immVertex3fv(pos, bezt->vec[2]); + immEnd(); + + GPU_line_smooth(false); + GPU_blend(GPU_BLEND_NONE); + } + + immUnbindProgram(); + + GPU_matrix_pop(); + GPU_matrix_pop_projection(); + + /* Reset default */ + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); +} + +/* ------------------------------------------------------------------------- */ +/* Main drawing functions */ + +static void gpencil_curve_draw_update_header(bContext *C, + wmOperator *op, + const tGPDcurve_draw *tcd) +{ + char header[UI_MAX_DRAW_STR]; + char buf[UI_MAX_DRAW_STR]; + + char *p = buf; + int available_len = sizeof(buf); + +#define WM_MODALKEY(_id) \ + WM_modalkeymap_operator_items_to_string_buf( \ + op->type, (_id), true, UI_MAX_SHORTCUT_STR, &available_len, &p) + + switch (tcd->state) { + case IN_MOVE: + case IN_SET_VECTOR: + BLI_snprintf(header, + sizeof(header), + TIP_("%s: confirm, %s: cancel, " + "%s: toggle cyclic (%s), " + "%s: delete last, %s: set thickness"), + WM_MODALKEY(CD_MODAL_CONFIRM), + WM_MODALKEY(CD_MODAL_CANCEL), + WM_MODALKEY(CD_MODAL_CYCLIC_TOGGLE), + WM_bool_as_string(tcd->is_cyclic), + WM_MODALKEY(CD_MODAL_DELETE_LAST), + WM_MODALKEY(CD_MODAL_SET_THICKNESS)); + break; + case IN_DRAG_FREE_HANDLE: + case IN_DRAG_ALIGNED_HANDLE: + BLI_snprintf(header, + sizeof(header), + TIP_("%s: confirm, %s: cancel, " + "%s: toggle cyclic (%s), " + "%s: free handle (%s), " + "%s: delete last, %s: set thickness"), + WM_MODALKEY(CD_MODAL_CONFIRM), + WM_MODALKEY(CD_MODAL_CANCEL), + WM_MODALKEY(CD_MODAL_CYCLIC_TOGGLE), + WM_bool_as_string(tcd->is_cyclic), + WM_MODALKEY(CD_MODAL_FREE_HANDLE_ON), + WM_bool_as_string(tcd->state == IN_DRAG_FREE_HANDLE), + WM_MODALKEY(CD_MODAL_DELETE_LAST), + WM_MODALKEY(CD_MODAL_SET_THICKNESS)); + break; + case IN_SET_THICKNESS: + BLI_snprintf(header, + sizeof(header), + TIP_("%s: confirm, %s: cancel, " + "%s: toggle cyclic (%s), " + "%s: delete last"), + WM_MODALKEY(CD_MODAL_CONFIRM), + WM_MODALKEY(CD_MODAL_CANCEL), + WM_MODALKEY(CD_MODAL_CYCLIC_TOGGLE), + WM_bool_as_string(tcd->is_cyclic), + WM_MODALKEY(CD_MODAL_DELETE_LAST)); + break; + } + + ED_workspace_status_text(C, header); + +#undef WM_MODALKEY +} + +/* ------------------------------------------------------------------------- */ +/* Main drawing functions */ + +static void gpencil_curve_draw_init(bContext *C, wmOperator *op, const wmEvent *event) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ARegion *region = CTX_wm_region(C); + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = CTX_data_gpencil_data(C); + + ToolSettings *ts = scene->toolsettings; + Paint *paint = &ts->gp_paint->paint; + int cfra = CFRA; + + /* Allocate temp curve draw data. */ + tGPDcurve_draw *tcd = MEM_callocN(sizeof(tGPDcurve_draw), __func__); + tcd->scene = scene; + tcd->region = region; + tcd->gpd = gpd; + tcd->ob = ob; + /* Fixed resolution. */ + tcd->resolution = 32; + + /* Initialize mouse state */ + copy_v2_v2_int(tcd->imval, event->mval); + copy_v2_v2_int(tcd->imval_prev, event->mval); + tcd->is_mouse_down = (event->val == KM_PRESS); + tcd->state = IN_SET_VECTOR; + + if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) { + BKE_brush_gpencil_paint_presets(bmain, ts, true); + } + + Brush *brush = BKE_paint_toolslots_brush_get(paint, 0); + BKE_brush_tool_set(brush, paint, 0); + BKE_paint_brush_set(paint, brush); + BrushGpencilSettings *brush_settings = brush->gpencil_settings; + tcd->brush = brush; + + /* Get active layer or create a new one. */ + bGPDlayer *gpl = CTX_data_active_gpencil_layer(C); + if (gpl == NULL) { + gpl = BKE_gpencil_layer_addnew(tcd->gpd, DATA_("Curve"), true, false); + } + tcd->gpl = gpl; + + /* Recalculate layer transform matrix to avoid problems if props are animated. */ + loc_eul_size_to_mat4( + tcd->gpl->layer_mat, tcd->gpl->location, tcd->gpl->rotation, tcd->gpl->scale); + invert_m4_m4(tcd->gpl->layer_invmat, tcd->gpl->layer_mat); + + /* Get current frame or create new one. */ + short add_frame_mode; + if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) { + add_frame_mode = GP_GETFRAME_ADD_COPY; + } + else { + add_frame_mode = GP_GETFRAME_ADD_NEW; + } + + tcd->cframe = cfra; + bool need_tag = tcd->gpl->actframe == NULL; + bGPDframe *gpf = BKE_gpencil_layer_frame_get(tcd->gpl, tcd->cframe, add_frame_mode); + if (need_tag) { + DEG_id_tag_update(&tcd->gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + } + tcd->gpf = gpf; + + /* Create stroke. */ + int mat_idx = BKE_gpencil_object_material_get_index_from_brush(ob, brush); + bGPDstroke *gps = BKE_gpencil_stroke_new(mat_idx, 1, brush->size); + gps->thickness = brush->size; + gps->hardeness = brush_settings->hardeness; + copy_v2_v2(gps->aspect_ratio, brush_settings->aspect_ratio); + + float first_pt[3]; + gpencil_project_mval_to_v3(scene, region, ob, tcd->imval, first_pt); + gps->points[0].pressure = 1.0f; + gps->points[0].strength = 1.0f; + copy_v3_v3(&gps->points[0].x, first_pt); + + BLI_addtail(&gpf->strokes, gps); + tcd->gps = gps; + + /* Create editcurve. */ + bGPDcurve *gpc = BKE_gpencil_stroke_editcurve_new(1); + bGPDcurve_point *cpt = &gpc->curve_points[0]; + copy_v3_v3(cpt->bezt.vec[0], first_pt); + copy_v3_v3(cpt->bezt.vec[1], first_pt); + copy_v3_v3(cpt->bezt.vec[2], first_pt); + cpt->pressure = 1.0f; + cpt->strength = 1.0f; + + gps->editcurve = gpc; + tcd->gpc = gpc; + + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(tcd->gpd, gps, GP_GEO_UPDATE_DEFAULT); + + /* Initialize space conversion. */ + gpencil_point_conversion_init(C, &tcd->gsc); + + tcd->draw_handle = ED_region_draw_cb_activate( + tcd->region->type, gpencil_curve_draw_ui_callback, tcd, REGION_DRAW_POST_VIEW); + + gpencil_curve_draw_update(C, tcd); + op->customdata = tcd; +} + +static void gpencil_curve_draw_update(bContext *C, tGPDcurve_draw *tcd) +{ + bGPdata *gpd = tcd->gpd; + bGPDstroke *gps = tcd->gps; + bGPDcurve *gpc = tcd->gpc; + int tot_points = gpc->tot_curve_points; + bGPDcurve_point *cpt = &gpc->curve_points[tot_points - 1]; + BezTriple *bezt = &cpt->bezt; + + float co[3]; + switch (tcd->state) { + case IN_MOVE: { + gpencil_project_mval_to_v3(tcd->scene, tcd->region, tcd->ob, tcd->imval, co); + copy_v3_v3(bezt->vec[0], co); + copy_v3_v3(bezt->vec[1], co); + copy_v3_v3(bezt->vec[2], co); + + BKE_gpencil_editcurve_recalculate_handles(gps); + BKE_gpencil_stroke_update_geometry_from_editcurve( + gps, tcd->resolution, false, GP_GEO_UPDATE_DEFAULT); + gpencil_set_alpha_last_segment(tcd, 0.1f); + break; + } + case IN_DRAG_ALIGNED_HANDLE: { + float vec[3]; + gpencil_project_mval_to_v3(tcd->scene, tcd->region, tcd->ob, tcd->imval, co); + sub_v3_v3v3(vec, bezt->vec[1], co); + add_v3_v3(vec, bezt->vec[1]); + copy_v3_v3(bezt->vec[0], vec); + copy_v3_v3(bezt->vec[2], co); + + BKE_gpencil_stroke_update_geometry_from_editcurve( + gps, tcd->resolution, false, GP_GEO_UPDATE_DEFAULT); + break; + } + case IN_DRAG_FREE_HANDLE: { + gpencil_project_mval_to_v3(tcd->scene, tcd->region, tcd->ob, tcd->imval, co); + copy_v3_v3(bezt->vec[2], co); + + BKE_gpencil_stroke_update_geometry_from_editcurve( + gps, tcd->resolution, false, GP_GEO_UPDATE_DEFAULT); + break; + } + case IN_SET_THICKNESS: { + int move[2]; + sub_v2_v2v2_int(move, tcd->imval, tcd->imval_start); + int dir = move[0] > 0.0f ? 1 : -1; + int dist = len_manhattan_v2_int(move); + /* TODO: calculate correct radius. */ + float dr = dir * ((float)dist / 10.0f); + cpt->pressure = tcd->prev_pressure + dr; + CLAMP_MIN(cpt->pressure, 0.0f); + + BKE_gpencil_stroke_update_geometry_from_editcurve( + gps, tcd->resolution, false, GP_GEO_UPDATE_DEFAULT); + break; + } + default: + break; + } + + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); + + DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); +} + +static void gpencil_curve_draw_confirm(bContext *C, wmOperator *op, tGPDcurve_draw *tcd) +{ + if (G.debug & G_DEBUG) { + printf("Confirm curve draw\n"); + } + bGPDcurve *gpc = tcd->gpc; + int tot_points = gpc->tot_curve_points; + bGPDcurve_point *cpt = &gpc->curve_points[tot_points - 1]; + cpt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&cpt->bezt); + + BKE_gpencil_editcurve_recalculate_handles(tcd->gps); +} + +static void gpencil_curve_draw_exit(bContext *C, wmOperator *op) +{ + if (G.debug & G_DEBUG) { + printf("Exit curve draw\n"); + } + + wmWindow *win = CTX_wm_window(C); + tGPDcurve_draw *tcd = op->customdata; + + ED_workspace_status_text(C, NULL); + WM_cursor_modal_restore(win); + + ED_region_draw_cb_exit(tcd->region->type, tcd->draw_handle); + + bGPdata *gpd = tcd->gpd; + + MEM_SAFE_FREE(tcd); + + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); + + op->customdata = NULL; +} + +/* ------------------------------------------------------------------------- */ +/* Operator callbacks */ + +static int gpencil_curve_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (G.debug & G_DEBUG) { + printf("Invoke curve draw\n"); + } + wmWindow *win = CTX_wm_window(C); + + /* Set cursor to dot. */ + WM_cursor_modal_set(win, WM_CURSOR_DOT); + + gpencil_curve_draw_init(C, op, event); + // tGPDcurve_draw *tcd = op->customdata; + + /* Add modal handler. */ + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int gpencil_curve_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + tGPDcurve_draw *tcd = op->customdata; + wmWindow *win = CTX_wm_window(C); + float drag_threshold = (float)WM_event_drag_threshold(event); + + copy_v2_v2_int(tcd->imval, event->mval); + + /* Modal keymap event. */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case CD_MODAL_CONFIRM: { + /* Delete the 'preview' point. */ + if (tcd->state == IN_MOVE) { + gpencil_pop_curve_point(C, tcd); + } + /* Create curve */ + gpencil_curve_draw_confirm(C, op, tcd); + gpencil_curve_draw_exit(C, op); + return OPERATOR_FINISHED; + } + case CD_MODAL_CANCEL: { + /* Delete the stroke. */ + BLI_remlink(&tcd->gpf->strokes, tcd->gps); + BKE_gpencil_free_stroke(tcd->gps); + gpencil_curve_draw_exit(C, op); + return OPERATOR_CANCELLED; + } + case CD_MODAL_FREE_HANDLE_ON: { + if (tcd->state == IN_DRAG_ALIGNED_HANDLE) { + tcd->state = IN_DRAG_FREE_HANDLE; + gpencil_set_handle_type_last_point(tcd, HD_FREE); + gpencil_curve_draw_update(C, tcd); + } + break; + } + case CD_MODAL_FREE_HANDLE_OFF: { + if (tcd->state == IN_DRAG_FREE_HANDLE) { + tcd->state = IN_DRAG_ALIGNED_HANDLE; + gpencil_set_handle_type_last_point(tcd, HD_ALIGN); + gpencil_curve_draw_update(C, tcd); + } + break; + } + case CD_MODAL_CYCLIC_TOGGLE: { + if (tcd->is_cyclic) { + tcd->gps->flag &= ~GP_STROKE_CYCLIC; + } + else { + tcd->gps->flag |= GP_STROKE_CYCLIC; + } + tcd->is_cyclic = !tcd->is_cyclic; + gpencil_curve_draw_update(C, tcd); + break; + } + case CD_MODAL_DELETE_LAST: { + if (tcd->state == IN_MOVE) { + gpencil_pop_curve_point(C, tcd); + } + else if (ELEM(tcd->state, IN_DRAG_ALIGNED_HANDLE, IN_DRAG_FREE_HANDLE)) { + tcd->state = IN_MOVE; + } + gpencil_curve_draw_update(C, tcd); + break; + } + case CD_MODAL_SET_THICKNESS: { + if (tcd->state != IN_SET_THICKNESS) { + tcd->state = IN_SET_THICKNESS; + WM_cursor_modal_set(win, WM_CURSOR_EW_SCROLL); + + bGPDcurve_point *cpt_last = &tcd->gpc->curve_points[tcd->gpc->tot_curve_points - 1]; + tcd->prev_pressure = cpt_last->pressure; + copy_v2_v2_int(tcd->imval_start, tcd->imval); + + gpencil_curve_draw_update(C, tcd); + } + break; + } + } + } + /* Event not in keymap. */ + else { + switch (event->type) { + case LEFTMOUSE: { + if (event->val == KM_PRESS) { + copy_v2_v2_int(tcd->imval_start, tcd->imval); + tcd->is_mouse_down = true; + /* Set state to vector. */ + if (tcd->state == IN_MOVE) { + tcd->state = IN_SET_VECTOR; + } + /* Reset state to move. */ + else if (tcd->state == IN_SET_THICKNESS) { + tcd->state = IN_MOVE; + WM_cursor_modal_set(win, WM_CURSOR_DOT); + } + } + else if (event->val == KM_RELEASE) { + copy_v2_v2_int(tcd->imval_end, tcd->imval); + tcd->is_mouse_down = false; + /* Reset state to move. */ + if (ELEM(tcd->state, IN_SET_VECTOR, IN_DRAG_ALIGNED_HANDLE, IN_DRAG_FREE_HANDLE)) { + tcd->state = IN_MOVE; + gpencil_push_curve_point(C, tcd); + } + + gpencil_curve_draw_update(C, tcd); + } + break; + } + case MOUSEMOVE: { + if (tcd->state == IN_SET_VECTOR && + len_v2v2_int(tcd->imval, tcd->imval_start) > drag_threshold) { + tcd->state = IN_DRAG_ALIGNED_HANDLE; + gpencil_set_handle_type_last_point(tcd, HD_ALIGN); + } + gpencil_curve_draw_update(C, tcd); + break; + } + default: { + copy_v2_v2_int(tcd->imval_prev, tcd->imval); + return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH; + } + } + } + + gpencil_curve_draw_update_header(C, op, tcd); + + if (G.debug & G_DEBUG) { + debug_print_state(tcd); + } + copy_v2_v2_int(tcd->imval_prev, tcd->imval); + return OPERATOR_RUNNING_MODAL; +} + +static void gpencil_curve_draw_cancel(bContext *C, wmOperator *op) +{ + if (G.debug & G_DEBUG) { + printf("Cancel curve draw\n"); + } + gpencil_curve_draw_exit(C, op); +} + +static bool gpencil_curve_draw_poll(bContext *C) +{ + if (G.debug & G_DEBUG) { + printf("Poll curve draw\n"); + } + ScrArea *area = CTX_wm_area(C); + if (area && area->spacetype != SPACE_VIEW3D) { + return false; + } + + bGPdata *gpd = CTX_data_gpencil_data(C); + if (gpd == NULL) { + return false; + } + + if ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0) { + return false; + } + + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + if ((gpl) && (gpl->flag & (GP_LAYER_LOCKED | GP_LAYER_HIDE))) { + return false; + } + + return true; +} + +wmKeyMap *gpencil_curve_draw_modal_keymap(wmKeyConfig *keyconf) +{ + static const EnumPropertyItem modal_items[] = { + {CD_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, + {CD_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""}, + {CD_MODAL_FREE_HANDLE_ON, "FREE_HANDLE_ON", 0, "Free Handle On", ""}, + {CD_MODAL_FREE_HANDLE_OFF, "FREE_HANDLE_OFF", 0, "Free Handle Off", ""}, + {CD_MODAL_CYCLIC_TOGGLE, "CYCLIC_TOGGLE", 0, "Toggle Stroke Cyclic", ""}, + {CD_MODAL_DELETE_LAST, "DELETE_LAST", 0, "Delete the Last Confirmed Point", ""}, + {CD_MODAL_SET_THICKNESS, "SET_THICKNESS", 0, "Set the Thickness", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Curve Draw Tool Modal Map"); + + /* this function is called for each spacetype, only needs to add map once */ + if (keymap && keymap->modal_items) { + return NULL; + } + + keymap = WM_modalkeymap_ensure(keyconf, "Curve Draw Tool Modal Map", modal_items); + + WM_modalkeymap_assign(keymap, "GPENCIL_OT_draw_curve"); + + return keymap; +} + +void GPENCIL_OT_draw_curve(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Grease Pencil Draw Curve"; + ot->idname = "GPENCIL_OT_draw_curve"; + ot->description = "Draw a bézier stroke in the active grease pencil object"; + + /* api callbacks */ + ot->invoke = gpencil_curve_draw_invoke; + ot->modal = gpencil_curve_draw_modal; + ot->cancel = gpencil_curve_draw_cancel; + ot->poll = gpencil_curve_draw_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; +} diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index e272f46d13d..685cd54fc1b 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -1552,7 +1552,6 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) ListBase selected = {NULL}; bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - bGPDstroke *gps = NULL; for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { @@ -1560,13 +1559,12 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) continue; } /* verify if any selected stroke is in the extreme of the stack and select to move */ - for (gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); /* only if selected */ - if (gps->flag & GP_STROKE_SELECT) { - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { - continue; - } + if (is_stroke_selected) { /* check if the color is editable */ if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { continue; @@ -1597,6 +1595,7 @@ static int gpencil_stroke_arrange_exec(bContext *C, wmOperator *op) int prev_index = target_index; /* Now do the movement of the stroke */ switch (direction) { + bGPDstroke *gps = NULL; /* Bring to Front */ case GP_STROKE_MOVE_TOP: LISTBASE_FOREACH (LinkData *, link, &selected) { @@ -1755,21 +1754,26 @@ static int gpencil_stroke_change_color_exec(bContext *C, wmOperator *op) LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* only if selected */ - if (gps->flag & GP_STROKE_SELECT) { - /* 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_material_editable(ob, gpl, gps) == false) { - continue; - } - - /* assign new color */ - gps->mat_nr = idx; + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); + if (!is_stroke_selected) { + continue; + } - changed = true; + /* 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_material_editable(ob, gpl, gps) == false) { + continue; + } + + /* assign new color */ + gps->mat_nr = idx; + + changed = true; } } /* If not multi-edit, exit loop. */ diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 8d1f841da6c..b57b2fb3ace 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -207,22 +207,6 @@ 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(gpd, 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 */ @@ -846,24 +830,99 @@ static void gpencil_duplicate_points(bGPdata *gpd, ListBase *new_strokes, const char *layername) { - bGPDspoint *pt; - int i; - int start_idx = -1; - /* Step through the original stroke's points: - * - We accumulate selected points (from start_idx to current index) - * and then convert that to a new stroke - */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - /* searching for start, are waiting for end? */ - if (start_idx == -1) { - /* is this the first selected point for a new island? */ - if (pt->flag & GP_SPOINT_SELECT) { - start_idx = i; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if (start_idx == -1) { + if (cpt->flag & GP_CURVE_POINT_SELECT) { + start_idx = i; + } + continue; + } + + size_t len = 0; + + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + len = i - start_idx; } + else if (i == gpc->tot_curve_points - 1) { + len = i - start_idx + 1; + } + + if (len < 1) { + continue; + } + + /* make a stupid copy first of the entire stroke (to get the flags too) */ + bGPDstroke *gpsd = BKE_gpencil_stroke_duplicate((bGPDstroke *)gps, false, false); + + /* saves original layer name */ + BLI_strncpy(gpsd->runtime.tmp_layerinfo, layername, sizeof(gpsd->runtime.tmp_layerinfo)); + + /* To avoid a curve update, we just copy the points. */ + int start_idx_stroke = gpc->curve_points[start_idx].point_index; + int len_stroke = (gpc->curve_points[start_idx + len - 1].point_index - start_idx_stroke) + 1; + + gpsd->points = MEM_mallocN(sizeof(bGPDspoint) * len_stroke, "gps stroke points copy"); + memcpy(gpsd->points, gps->points + start_idx_stroke, sizeof(bGPDspoint) * len_stroke); + gpsd->totpoints = len_stroke; + + gpsd->editcurve = BKE_gpencil_stroke_editcurve_new(len); + bGPDcurve *gpcd = gpsd->editcurve; + memcpy(gpcd->curve_points, gpc->curve_points + start_idx, sizeof(bGPDcurve_point) * len); + + if (gps->dvert != NULL) { + gpsd->dvert = MEM_mallocN(sizeof(MDeformVert) * len, "gps stroke weights copy"); + memcpy(gpsd->dvert, gps->dvert + start_idx, sizeof(MDeformVert) * len); + + /* Copy weights */ + int e = start_idx; + for (int j = 0; j < gpsd->totpoints; j++) { + MDeformVert *dvert_dst = &gps->dvert[e]; + MDeformVert *dvert_src = &gps->dvert[j]; + dvert_dst->dw = MEM_dupallocN(dvert_src->dw); + e++; + } + } + + /* TODO: Copy vertex weights*/ + for (uint32_t j = 0; j < gpcd->tot_curve_points; j++) { + bGPDcurve_point *gpcd_pt = &gpcd->curve_points[j]; + BezTriple *bezt = &gpcd_pt->bezt; + gpcd_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + gpcd->flag |= GP_CURVE_SELECT; + + BKE_gpencil_stroke_geometry_update(gpd, gpsd, GP_GEO_UPDATE_DEFAULT); + + /* add to temp buffer */ + gpsd->next = gpsd->prev = NULL; + + BLI_addtail(new_strokes, gpsd); + + start_idx = -1; } - if ((start_idx != -1) || (start_idx == gps->totpoints - 1)) { + } + else { + /* Step through the original stroke's points: + * - We accumulate selected points (from start_idx to current index) + * and then convert that to a new stroke + */ + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + /* searching for start, are waiting for end? */ + if ((start_idx != -1) || (start_idx == gps->totpoints - 1)) { + /* is this the first selected point for a new island? */ + if (pt->flag & GP_SPOINT_SELECT) { + start_idx = i; + } + continue; + } + size_t len = 0; /* is this the end of current island yet? @@ -877,45 +936,47 @@ static void gpencil_duplicate_points(bGPdata *gpd, len = i - start_idx + 1; } + if (len < 1) { + continue; + } + /* make copies of the relevant data */ - if (len) { - bGPDstroke *gpsd; + bGPDstroke *gpsd; - /* make a stupid copy first of the entire stroke (to get the flags too) */ - gpsd = BKE_gpencil_stroke_duplicate((bGPDstroke *)gps, false, true); + /* make a stupid copy first of the entire stroke (to get the flags too) */ + gpsd = BKE_gpencil_stroke_duplicate((bGPDstroke *)gps, false, false); - /* saves original layer name */ - BLI_strncpy(gpsd->runtime.tmp_layerinfo, layername, sizeof(gpsd->runtime.tmp_layerinfo)); + /* saves original layer name */ + BLI_strncpy(gpsd->runtime.tmp_layerinfo, layername, sizeof(gpsd->runtime.tmp_layerinfo)); - /* now, make a new points array, and copy of the relevant parts */ - gpsd->points = MEM_mallocN(sizeof(bGPDspoint) * len, "gps stroke points copy"); - memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len); - gpsd->totpoints = len; + /* now, make a new points array, and copy of the relevant parts */ + gpsd->points = MEM_mallocN(sizeof(bGPDspoint) * len, "gps stroke points copy"); + memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len); + gpsd->totpoints = len; - if (gps->dvert != NULL) { - gpsd->dvert = MEM_mallocN(sizeof(MDeformVert) * len, "gps stroke weights copy"); - memcpy(gpsd->dvert, gps->dvert + start_idx, sizeof(MDeformVert) * len); - - /* Copy weights */ - int e = start_idx; - for (int j = 0; j < gpsd->totpoints; j++) { - MDeformVert *dvert_dst = &gps->dvert[e]; - MDeformVert *dvert_src = &gps->dvert[j]; - dvert_dst->dw = MEM_dupallocN(dvert_src->dw); - e++; - } + if (gps->dvert != NULL) { + gpsd->dvert = MEM_mallocN(sizeof(MDeformVert) * len, "gps stroke weights copy"); + memcpy(gpsd->dvert, gps->dvert + start_idx, sizeof(MDeformVert) * len); + + /* Copy weights */ + int e = start_idx; + for (int j = 0; j < gpsd->totpoints; j++) { + MDeformVert *dvert_dst = &gps->dvert[e]; + MDeformVert *dvert_src = &gps->dvert[j]; + dvert_dst->dw = MEM_dupallocN(dvert_src->dw); + e++; } + } - BKE_gpencil_stroke_geometry_update(gpd, gpsd); + BKE_gpencil_stroke_geometry_update(gpd, gpsd, GP_GEO_UPDATE_DEFAULT); - /* add to temp buffer */ - gpsd->next = gpsd->prev = NULL; + /* add to temp buffer */ + gpsd->next = gpsd->prev = NULL; - BLI_addtail(new_strokes, gpsd); + BLI_addtail(new_strokes, gpsd); - /* cleanup + reset for next */ - start_idx = -1; - } + /* cleanup + reset for next */ + start_idx = -1; } } } @@ -923,7 +984,6 @@ static void gpencil_duplicate_points(bGPdata *gpd, 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"); @@ -936,74 +996,108 @@ static int gpencil_duplicate_exec(bContext *C, wmOperator *op) } 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; + /* 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; + + if (gpf == NULL) { + continue; + } - if (gpf == NULL) { + /* make copies of selected strokes, and deselect these once we're done */ + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; } - /* 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 (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if ((gpc->flag & GP_CURVE_SELECT) == 0) { 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; + if (gpc->tot_curve_points == 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, true, true); + /* make direct copies of the stroke and its points */ + gpsd = BKE_gpencil_stroke_duplicate(gps, true, true); - BLI_strncpy( - gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); + BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); - /* Initialize triangle information. */ - BKE_gpencil_stroke_geometry_update(gpd, gpsd); + /* Initialize triangle information. */ + BKE_gpencil_stroke_geometry_update(gpd, gpsd, GP_GEO_UPDATE_DEFAULT); - /* 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; - BKE_gpencil_stroke_select_index_reset(gps); + /* add to temp buffer */ + gpsd->next = gpsd->prev = NULL; + BLI_addtail(&new_strokes, gpsd); + } + else { + gpencil_duplicate_points(gpd, gps, &new_strokes, gpl->info); + } - changed = true; + /* Deselect the points */ + for (uint32_t 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; + BKE_gpencil_stroke_select_index_reset(gps); + + changed = true; } + else { + if ((gps->flag & GP_STROKE_SELECT) == 0) { + continue; + } + + if (gps->totpoints == 1) { + /* Special Case: If there's just a single point in this stroke... */ + bGPDstroke *gpsd; - /* 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); + /* make direct copies of the stroke and its points */ + gpsd = BKE_gpencil_stroke_duplicate(gps, true, true); + + BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); + + /* Initialize triangle information. */ + BKE_gpencil_stroke_geometry_update(gpd, gpsd, GP_GEO_UPDATE_DEFAULT); + + /* 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) + */ + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + pt->flag &= ~GP_SPOINT_SELECT; + } + gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_reset(gps); + + changed = true; + } } - CTX_DATA_END; + + /* 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; if (changed) { /* updates */ @@ -1109,8 +1203,8 @@ static void gpencil_add_move_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gp gpencil_copy_move_point(gps_new, gps->points, gps->dvert, i, 0, true); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); - BKE_gpencil_stroke_geometry_update(gpd, gps_new); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); + BKE_gpencil_stroke_geometry_update(gpd, gps_new, GP_GEO_UPDATE_DEFAULT); /* Deselect original point. */ pt->flag &= ~GP_SPOINT_SELECT; @@ -1185,7 +1279,7 @@ static void gpencil_add_move_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gp } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); MEM_SAFE_FREE(temp_points); MEM_SAFE_FREE(temp_dverts); @@ -1235,8 +1329,7 @@ static void gpencil_curve_extrude_points(bGPdata *gpd, BLI_insertlinkafter(&gpf->strokes, gps, gps_new); - gps_new->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps_new); + BKE_gpencil_stroke_geometry_update(gpd, gps_new, GP_GEO_UPDATE_DEFAULT); gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; BEZT_DESEL_ALL(&gpc_pt->bezt); @@ -1282,8 +1375,7 @@ static void gpencil_curve_extrude_points(bGPdata *gpd, BEZT_DESEL_ALL(&old_last->bezt); } - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } @@ -1291,9 +1383,7 @@ 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; if (gpd == NULL) { BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data"); @@ -1310,16 +1400,13 @@ static int gpencil_extrude_exec(bContext *C, wmOperator *op) continue; } - for (gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; } - if (is_curve_edit) { - if (gps->editcurve == NULL) { - continue; - } + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { bGPDcurve *gpc = gps->editcurve; if (gpc->flag & GP_CURVE_SELECT) { gpencil_curve_extrude_points(gpd, gpf, gps, gpc); @@ -1432,8 +1519,6 @@ static void gpencil_strokes_copypastebuf_colors_name_to_material_free(GHash *nam /* Free copy/paste buffer data */ void ED_gpencil_strokes_copybuf_free(void) { - bGPDstroke *gps, *gpsn; - /* Free the colors buffer * NOTE: This is done before the strokes so that the ptrs are still safe */ @@ -1443,23 +1528,12 @@ void ED_gpencil_strokes_copybuf_free(void) } /* Free the stroke buffer */ - for (gps = gpencil_strokes_copypastebuf.first; gps; gps = gpsn) { - gpsn = gps->next; - - 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(&gpencil_strokes_copypastebuf, gps); + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpencil_strokes_copypastebuf) { + BLI_remlink(&gpencil_strokes_copypastebuf, gps); + BKE_gpencil_free_stroke(gps); } - gpencil_strokes_copypastebuf.first = gpencil_strokes_copypastebuf.last = NULL; + BLI_listbase_clear(&gpencil_strokes_copypastebuf); } /** @@ -1505,7 +1579,6 @@ 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"); @@ -1520,62 +1593,54 @@ static int gpencil_strokes_copy_exec(bContext *C, wmOperator *op) /* clear the buffer first */ ED_gpencil_strokes_copybuf_free(); - 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; + /* 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; - if (gpf == NULL) { + if (gpf == NULL) { + continue; + } + + /* make copies of selected strokes, and deselect these once we're done */ + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; } - /* 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; - } + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); - if (gps->flag & GP_STROKE_SELECT) { - if (gps->totpoints == 1) { - /* Special Case: If there's just a single point in this stroke... */ - bGPDstroke *gpsd; + if (is_stroke_selected) { + 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); + /* make direct copies of the stroke and its points */ + gpsd = BKE_gpencil_stroke_duplicate(gps, true, 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); - } + /* saves original layer name */ + BLI_strncpy(gpsd->runtime.tmp_layerinfo, gpl->info, sizeof(gpsd->runtime.tmp_layerinfo)); - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gpsd); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gpsd, GP_GEO_UPDATE_DEFAULT); - /* 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); - } + /* 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) { @@ -1653,7 +1718,6 @@ 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; @@ -1718,54 +1782,50 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) /* Ensure that all the necessary colors exist */ new_colors = gpencil_copybuf_validate_colormap(C); - 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, true); - new_stroke->runtime.tmp_layerinfo[0] = '\0'; - new_stroke->next = new_stroke->prev = NULL; + /* 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)) { + continue; + } + /* 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); + } + } - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + /* 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 == NULL) { + continue; + } + /* 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; - if (on_back) { - BLI_addhead(&gpf->strokes, new_stroke); - } - else { - BLI_addtail(&gpf->strokes, new_stroke); - } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, new_stroke, GP_GEO_UPDATE_DEFAULT); - /* 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); - } - } + 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); } /* free temp data */ @@ -1874,7 +1934,12 @@ static int gpencil_move_to_layer_exec(bContext *C, wmOperator *op) continue; } - if (gps->flag & GP_STROKE_SELECT) { + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + gps->editcurve->flag & GP_CURVE_SELECT : + gps->flag & GP_STROKE_SELECT; + + /* TODO: Don't just move entire strokes - instead, only copy the selected portions... */ + if (is_stroke_selected) { BLI_remlink(&gpf_src->strokes, gps); BLI_addtail(&strokes, gps); } @@ -2210,7 +2275,11 @@ static int gpencil_delete_selected_strokes(bContext *C) } /* free stroke if selected */ - if (gps->flag & GP_STROKE_SELECT) { + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + gps->editcurve->flag & GP_CURVE_SELECT : + gps->flag & GP_STROKE_SELECT; + + if (is_stroke_selected) { BLI_remlink(&gpf->strokes, gps); /* free stroke memory arrays, then stroke itself */ BKE_gpencil_free_stroke(gps); @@ -2233,243 +2302,166 @@ static int gpencil_delete_selected_strokes(bContext *C) /* ----------------------------------- */ -static bool gpencil_dissolve_selected_curve_points(bContext *C, - bGPdata *gpd, - eGP_DissolveMode mode) +static bool gpencil_dissolve_selected_curve_points(bGPdata *gpd, + bGPDframe *gpf, + bGPDstroke *gps, + eGP_DissolveMode mode, + const bool do_segments_refit, + const float error_threshold) { - 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; - } - } + bGPDcurve *gpc = gps->editcurve; + if ((gpc->flag & GP_CURVE_SELECT) == 0) { + return false; + } - 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--; - } + int num_points_remaining = gpc->tot_curve_points; + int old_num_points = gpc->tot_curve_points; + switch (mode) { + case GP_DISSOLVE_POINTS: { + num_points_remaining = BKE_gpencil_editcurve_dissolve( + gps, GP_CURVE_POINT_SELECT, do_segments_refit, error_threshold); + break; + } + case GP_DISSOLVE_BETWEEN: { + int first = -1, last = 0; + 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; } - break; - default: - return false; - break; - } - - if (num_points_remaining < 1) { - /* Delete stroke */ - BLI_remlink(&gpf_->strokes, gps); - BKE_gpencil_free_stroke(gps); + last = i; + } } - 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; + for (int i = first + 1; i < last; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + cpt->flag |= GP_CURVE_POINT_TAG; } + } - if (gpc->curve_points != NULL) { - MEM_freeN(gpc->curve_points); + num_points_remaining = BKE_gpencil_editcurve_dissolve( + gps, GP_CURVE_POINT_TAG, do_segments_refit, error_threshold); + 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) { + cpt->flag |= GP_CURVE_POINT_TAG; } - - 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; + num_points_remaining = BKE_gpencil_editcurve_dissolve( + gps, GP_CURVE_POINT_TAG, do_segments_refit, error_threshold); + + break; } + default: + return false; + break; } - GP_EDITABLE_CURVES_END(gps_iter); - return changed; + if (num_points_remaining < 1) { + /* Delete stroke */ + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + return true; + } + else if (num_points_remaining == old_num_points) { + /* Nothing to do so return. */ + return false; + } + + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); + return true; } -static bool gpencil_dissolve_selected_stroke_points(bContext *C, - bGPdata *gpd, +static bool gpencil_dissolve_selected_stroke_points(bGPdata *gpd, + bGPDframe *gpf, + bGPDstroke *gps, eGP_DissolveMode mode) { bool changed = false; int first = 0; int last = 0; - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - /* the stroke must have at least one point selected for any operator */ - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - MDeformVert *dvert = NULL; - int i; + /* the stroke must have at least one point selected for any operator */ + if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + MDeformVert *dvert = NULL; + int i; - int tot = gps->totpoints; /* number of points in new buffer */ + int tot = gps->totpoints; /* number of points in new buffer */ - /* first pass: count points to remove */ - switch (mode) { - case GP_DISSOLVE_POINTS: - /* Count how many points are selected (i.e. how many to remove) */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - /* selected point - one of the points to remove */ - tot--; - } + /* first pass: count points to remove */ + switch (mode) { + case GP_DISSOLVE_POINTS: + /* Count how many points are selected (i.e. how many to remove) */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + /* selected point - one of the points to remove */ + tot--; } - break; - case GP_DISSOLVE_BETWEEN: - /* need to find first and last point selected */ - first = -1; - last = 0; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - if (first < 0) { - first = i; - } - last = i; + } + break; + case GP_DISSOLVE_BETWEEN: + /* need to find first and last point selected */ + first = -1; + last = 0; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + if (first < 0) { + first = i; } + last = i; } - /* count unselected points in the range */ - for (i = first, pt = gps->points + first; i < last; i++, pt++) { - if ((pt->flag & GP_SPOINT_SELECT) == 0) { - tot--; - } + } + /* count unselected points in the range */ + for (i = first, pt = gps->points + first; i < last; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) == 0) { + tot--; } - break; - case GP_DISSOLVE_UNSELECT: - /* count number of unselected points */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if ((pt->flag & GP_SPOINT_SELECT) == 0) { - tot--; - } + } + break; + case GP_DISSOLVE_UNSELECT: + /* count number of unselected points */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) == 0) { + tot--; } - break; - default: - return false; - break; - } + } + break; + default: + return false; + break; + } - /* if no points are left, we simply delete the entire stroke */ - if (tot <= 0) { - /* remove the entire stroke */ - BLI_remlink(&gpf_->strokes, gps); - BKE_gpencil_free_stroke(gps); - } - else { - /* just copy all points to keep into a smaller buffer */ - bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, - "new gp stroke points copy"); - bGPDspoint *npt = new_points; + /* if no points are left, we simply delete the entire stroke */ + if (tot <= 0) { + /* remove the entire stroke */ + BLI_remlink(&gpf->strokes, gps); + BKE_gpencil_free_stroke(gps); + } + else { + /* just copy all points to keep into a smaller buffer */ + bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); + bGPDspoint *npt = new_points; - MDeformVert *new_dvert = NULL; - MDeformVert *ndvert = NULL; + MDeformVert *new_dvert = NULL; + MDeformVert *ndvert = NULL; - if (gps->dvert != NULL) { - new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy"); - ndvert = new_dvert; - } + if (gps->dvert != NULL) { + new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy"); + ndvert = new_dvert; + } - switch (mode) { - case GP_DISSOLVE_POINTS: - (gps->dvert != NULL) ? dvert = gps->dvert : NULL; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if ((pt->flag & GP_SPOINT_SELECT) == 0) { - *npt = *pt; - npt++; - - if (gps->dvert != NULL) { - *ndvert = *dvert; - ndvert->dw = MEM_dupallocN(dvert->dw); - ndvert++; - } - } - if (gps->dvert != NULL) { - dvert++; - } - } - break; - case GP_DISSOLVE_BETWEEN: - /* copy first segment */ - (gps->dvert != NULL) ? dvert = gps->dvert : NULL; - for (i = 0, pt = gps->points; i < first; i++, pt++) { + switch (mode) { + case GP_DISSOLVE_POINTS: + (gps->dvert != NULL) ? dvert = gps->dvert : NULL; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & GP_SPOINT_SELECT) == 0) { *npt = *pt; npt++; @@ -2477,29 +2469,31 @@ static bool gpencil_dissolve_selected_stroke_points(bContext *C, *ndvert = *dvert; ndvert->dw = MEM_dupallocN(dvert->dw); ndvert++; - dvert++; } } - /* copy segment (selected points) */ - (gps->dvert != NULL) ? dvert = gps->dvert + first : NULL; - for (i = first, pt = gps->points + first; i < last; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - *npt = *pt; - npt++; + if (gps->dvert != NULL) { + dvert++; + } + } + break; + case GP_DISSOLVE_BETWEEN: + /* copy first segment */ + (gps->dvert != NULL) ? dvert = gps->dvert : NULL; + for (i = 0, pt = gps->points; i < first; i++, pt++) { + *npt = *pt; + npt++; - if (gps->dvert != NULL) { - *ndvert = *dvert; - ndvert->dw = MEM_dupallocN(dvert->dw); - ndvert++; - } - } - if (gps->dvert != NULL) { - dvert++; - } + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + dvert++; } - /* copy last segment */ - (gps->dvert != NULL) ? dvert = gps->dvert + last : NULL; - for (i = last, pt = gps->points + last; i < gps->totpoints; i++, pt++) { + } + /* copy segment (selected points) */ + (gps->dvert != NULL) ? dvert = gps->dvert + first : NULL; + for (i = first, pt = gps->points + first; i < last; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { *npt = *pt; npt++; @@ -2507,85 +2501,125 @@ static bool gpencil_dissolve_selected_stroke_points(bContext *C, *ndvert = *dvert; ndvert->dw = MEM_dupallocN(dvert->dw); ndvert++; - dvert++; } } + if (gps->dvert != NULL) { + dvert++; + } + } + /* copy last segment */ + (gps->dvert != NULL) ? dvert = gps->dvert + last : NULL; + for (i = last, pt = gps->points + last; i < gps->totpoints; i++, pt++) { + *npt = *pt; + npt++; - break; - case GP_DISSOLVE_UNSELECT: - /* copy any selected point */ - (gps->dvert != NULL) ? dvert = gps->dvert : NULL; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - *npt = *pt; - npt++; + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + dvert++; + } + } + + break; + case GP_DISSOLVE_UNSELECT: + /* copy any selected point */ + (gps->dvert != NULL) ? dvert = gps->dvert : NULL; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + *npt = *pt; + npt++; - if (gps->dvert != NULL) { - *ndvert = *dvert; - ndvert->dw = MEM_dupallocN(dvert->dw); - ndvert++; - } - } if (gps->dvert != NULL) { - dvert++; + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; } } - break; - } + if (gps->dvert != NULL) { + dvert++; + } + } + break; + } - /* free the old buffer */ - if (gps->points) { - MEM_freeN(gps->points); - } - if (gps->dvert) { - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->dvert); - } + /* free the old buffer */ + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } - /* save the new buffer */ - gps->points = new_points; - gps->dvert = new_dvert; - gps->totpoints = tot; + /* save the new buffer */ + gps->points = new_points; + gps->dvert = new_dvert; + gps->totpoints = tot; - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); - /* deselect the stroke, since none of its selected points will still be selected */ - gps->flag &= ~GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_reset(gps); - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; - } + /* deselect the stroke, since none of its selected points will still be selected */ + gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_reset(gps); + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; } - - changed = true; } + + changed = true; } - 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) +static int gpencil_dissolve_selected_points(bContext *C, + eGP_DissolveMode mode, + const bool do_segments_refit, + const float error_threshold) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = (bGPdata *)ob->data; - const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); + bool changed = false; + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - if (is_curve_edit) { - changed = gpencil_dissolve_selected_curve_points(C, gpd, mode); - } - else { - changed = gpencil_dissolve_selected_stroke_points(C, gpd, mode); + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + + if (gpf == NULL) { + continue; + } + + /* Use LISTBASE_FOREACH_MUTABLE because strokes might be entirely deleted. */ + LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + if (gpencil_dissolve_selected_curve_points( + gpd, gpf, gps, mode, do_segments_refit, error_threshold)) { + changed = true; + } + } + else { + if (gpencil_dissolve_selected_stroke_points(gpd, gpf, gps, mode)) { + changed = true; + } + } + } + } + } } + CTX_DATA_END; 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); return OPERATOR_FINISHED; } + return OPERATOR_CANCELLED; } @@ -2596,7 +2630,6 @@ 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; @@ -2622,23 +2655,27 @@ static int gpencil_delete_selected_points(bContext *C) continue; } - 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_reset(gps); + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + gpc->flag &= ~GP_CURVE_SELECT; + BKE_gpencil_stroke_select_index_reset(gps); - if (is_curve_edit) { - bGPDcurve *gpc = gps->editcurve; BKE_gpencil_curve_delete_tagged_points( gpd, gpf, gps, gps->next, gpc, GP_CURVE_POINT_SELECT); + changed = true; } - else { + } + else { + 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_reset(gps); /* delete unwanted points by splitting stroke into several smaller ones */ BKE_gpencil_stroke_delete_tagged_points( gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + changed = true; } - - changed = true; } } } @@ -2732,8 +2769,10 @@ void GPENCIL_OT_delete(wmOperatorType *ot) static int gpencil_dissolve_exec(bContext *C, wmOperator *op) { eGP_DissolveMode mode = RNA_enum_get(op->ptr, "type"); + const bool do_segments_refit = RNA_boolean_get(op->ptr, "do_segments_refit"); + const float error_threshold = RNA_float_get(op->ptr, "error_threshold"); - return gpencil_dissolve_selected_points(C, mode); + return gpencil_dissolve_selected_points(C, mode, do_segments_refit, error_threshold); } void GPENCIL_OT_dissolve(wmOperatorType *ot) @@ -2769,6 +2808,22 @@ void GPENCIL_OT_dissolve(wmOperatorType *ot) 0, "Type", "Method used for dissolving stroke points"); + + RNA_def_boolean(ot->srna, + "do_segments_refit", + false, + "Refit Segments", + "Try to match the previous shape of bézier stroke segment"); + + RNA_def_float(ot->srna, + "error_threshold", + GP_DEFAULT_CURVE_ERROR, + 0.0f, + 100.0f, + "Threshold", + "Bézier curve fitting error threshold", + 0.0f, + 3.0f); } /** \} */ @@ -2799,7 +2854,6 @@ 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) { @@ -2821,39 +2875,60 @@ static int gpencil_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) continue; } - if (is_curve_edit) { - if (gps->editcurve == NULL) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if ((gpc->flag & GP_CURVE_SELECT) == 0) { 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); + /* We move the entire handle if the control point is selected. */ + if (bezt->f2 & SELECT) { + float tmp[3], offset[3]; + mul_v3_m4v3(tmp, diff_mat, bezt->vec[1]); + + /* calculate the offset vector */ + offset[0] = gridf * floorf(0.5f + tmp[0] / gridf) - tmp[0]; + offset[1] = gridf * floorf(0.5f + tmp[1] / gridf) - tmp[1]; + offset[2] = gridf * floorf(0.5f + tmp[2] / gridf) - tmp[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(bezt->vec[0], inv_diff_mat, bezt->vec[0]); + mul_v3_m4v3(bezt->vec[1], inv_diff_mat, bezt->vec[1]); + mul_v3_m4v3(bezt->vec[2], inv_diff_mat, bezt->vec[2]); + } + else { + /* Move the handles to the grid individually. */ + float tmp0[3], tmp1[3]; + mul_v3_m4v3(tmp0, diff_mat, bezt->vec[0]); + mul_v3_m4v3(tmp1, diff_mat, bezt->vec[2]); + + /* Calculate nearest point on the grid. */ + tmp0[0] = gridf * floorf(0.5f + tmp0[0] / gridf); + tmp0[1] = gridf * floorf(0.5f + tmp0[1] / gridf); + tmp0[2] = gridf * floorf(0.5f + tmp0[2] / gridf); + + tmp1[0] = gridf * floorf(0.5f + tmp1[0] / gridf); + tmp1[1] = gridf * floorf(0.5f + tmp1[1] / gridf); + tmp1[2] = gridf * floorf(0.5f + tmp1[2] / gridf); + + /* Write to the selected handles. */ + if (bezt->f1 & SELECT) { + mul_v3_m4v3(bezt->vec[0], inv_diff_mat, tmp0); + } + if (bezt->f3 & SELECT) { + mul_v3_m4v3(bezt->vec[2], inv_diff_mat, tmp1); + } + } changed = true; } @@ -2861,11 +2936,13 @@ static int gpencil_snap_to_grid(bContext *C, wmOperator *UNUSED(op)) if (changed) { BKE_gpencil_editcurve_recalculate_handles(gps); - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } else { + if ((gps->flag & GP_STROKE_SELECT) == 0) { + continue; + } /* 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]; @@ -2924,7 +3001,6 @@ 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); @@ -2933,53 +3009,117 @@ static int gpencil_snap_to_cursor(bContext *C, wmOperator *op) const float *cursor_global = scene->cursor.location; 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]; + 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_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat); + /* calculate difference matrix */ + BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat); - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - bGPDspoint *pt; - int i; + 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_material_editable(obact, gpl, gps) == false) { + continue; + } - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { - continue; + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); + + /* only continue if this stroke is selected (editable doesn't guarantee this)... */ + if ((is_stroke_selected) == 0) { + continue; + } + + if (use_offset) { + /* TODO: Allow using midpoint instead? */ + float offset[3]; + + /* To avoid recalculating the curve, we will offset the curve data first and then all the + * stroke points. */ + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + float tmp[3]; + bGPDcurve *gpc = gps->editcurve; + /* Calculate offset. */ + mul_v3_m4v3(tmp, diff_mat, gpc->curve_points->bezt.vec[1]); + sub_v3_v3v3(offset, cursor_global, tmp); + + /* Offset points. */ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + BezTriple *bezt = &cpt->bezt; + for (int j = 0; j < 3; j++) { + add_v3_v3(bezt->vec[j], offset); + } + } } - /* check if the color is editable */ - if (ED_gpencil_stroke_material_editable(obact, gpl, gps) == false) { - continue; + else { + /* compute offset from first point of stroke to cursor */ + sub_v3_v3v3(offset, cursor_global, &gps->points->x); } - /* only continue if this stroke is selected (editable doesn't guarantee this)... */ - if ((gps->flag & GP_STROKE_SELECT) == 0) { - continue; + + /* apply offset to all points in the stroke */ + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + add_v3_v3(&pt->x, offset); } - if (use_offset) { - float offset[3]; + changed = true; + } + else { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + float inv_diff_mat[4][4]; + invert_m4_m4_safe(inv_diff_mat, diff_mat); - /* compute offset from first point of stroke to cursor */ - /* TODO: Allow using midpoint instead? */ - sub_v3_v3v3(offset, cursor_global, &gps->points->x); + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + BezTriple *bezt = &cpt->bezt; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + continue; + } + + float cur[3]; + mul_v3_m4v3(cur, inv_diff_mat, cursor_global); + + /* If the control point is selected, snap it to the cursor and offset the handles + * accordingly. */ + if (bezt->f2 & SELECT) { + float offset[3]; + sub_v3_v3v3(offset, cur, bezt->vec[1]); + + for (int j = 0; j < 3; j++) { + add_v3_v3(bezt->vec[j], offset); + } + } + /* Snap the handles to the cursor. */ + else { + if (bezt->f1 & SELECT) { + copy_v3_v3(bezt->vec[0], cur); + } + if (bezt->f3 & SELECT) { + copy_v3_v3(bezt->vec[2], cur); + } + } - /* 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; } - changed = true; + if (changed) { + BKE_gpencil_editcurve_recalculate_handles(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); + } } else { /* affect each selected point */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; if (pt->flag & GP_SPOINT_SELECT) { copy_v3_v3(&pt->x, cursor_global); gpencil_apply_parent_point(depsgraph, obact, gpl, pt); @@ -3051,9 +3191,6 @@ static bool gpencil_stroke_points_centroid(Depsgraph *depsgraph, BKE_gpencil_layer_transform_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; @@ -3062,13 +3199,45 @@ static bool gpencil_stroke_points_centroid(Depsgraph *depsgraph, if (ED_gpencil_stroke_material_editable(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 (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if ((gpc->flag & GP_CURVE_SELECT) == 0) { + continue; + } + + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + BezTriple *bezt = &cpt->bezt; + if ((cpt->flag * GP_CURVE_POINT_SELECT) == 0) { + continue; + } + + float fpt[3]; + for (int j = 0; j < 3; j++) { + if (BEZT_ISSEL_IDX(bezt, j)) { + mul_v3_m4v3(fpt, diff_mat, bezt->vec[j]); + + add_v3_v3(r_centroid, fpt); + minmax_v3v3_v3(r_min, r_max, fpt); + (*count)++; + } + } + } + + changed = true; } + else { + /* only continue if this stroke is selected (editable doesn't guarantee this)... */ + if ((gps->flag & GP_STROKE_SELECT) == 0) { + continue; + } - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + if ((pt->flag & GP_SPOINT_SELECT) == 0) { + continue; + } /* apply parent transformations */ float fpt[3]; mul_v3_m4v3(fpt, diff_mat, &pt->x); @@ -3078,9 +3247,8 @@ static bool gpencil_stroke_points_centroid(Depsgraph *depsgraph, (*count)++; } + changed = true; } - - changed = true; } } } @@ -3088,12 +3256,11 @@ static bool gpencil_stroke_points_centroid(Depsgraph *depsgraph, return changed; } -static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *op) +static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *UNUSED(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); @@ -3105,12 +3272,7 @@ static int gpencil_snap_cursor_to_sel(bContext *C, wmOperator *op) INIT_MINMAX(min, max); bool changed = false; - if (is_curve_edit) { - BKE_report(op->reports, RPT_ERROR, "Not implemented!"); - } - else { - changed = gpencil_stroke_points_centroid(depsgraph, C, obact, gpd, centroid, min, max, &count); - } + changed = gpencil_stroke_points_centroid(depsgraph, C, obact, gpd, centroid, min, max, &count); if (changed) { if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CENTER_BOUNDS) { @@ -3218,8 +3380,6 @@ 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 */ if (ELEM(NULL, gpd)) { @@ -3237,11 +3397,18 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) continue; } - for (gps = gpf->strokes.first; gps; gps = gps->next) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); /* skip strokes that are not selected or invalid for current view */ - if (((gps->flag & GP_STROKE_SELECT) == 0) || - ED_gpencil_stroke_can_use(C, gps) == false) { + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); + + if (!is_stroke_selected) { + continue; + } + + if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; } /* skip hidden or locked colors */ @@ -3271,14 +3438,13 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) if (before != (gps->flag & GP_STROKE_CYCLIC)) { /* Create new geometry. */ - if (is_curve_edit) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { BKE_gpencil_editcurve_recalculate_handles(gps); - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } else if ((gps->flag & GP_STROKE_CYCLIC) && geometry) { BKE_gpencil_stroke_close(gps); - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } changed = true; @@ -3303,22 +3469,6 @@ static int gpencil_stroke_cyclical_set_exec(bContext *C, wmOperator *op) 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. @@ -3342,15 +3492,17 @@ 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; /* properties */ ot->prop = RNA_def_enum(ot->srna, "type", cyclic_type, GP_STROKE_CYCLIC_TOGGLE, "Type", ""); - prop = RNA_def_boolean( - ot->srna, "geometry", false, "Create Geometry", "Create new geometry for closing stroke"); + prop = RNA_def_boolean(ot->srna, + "geometry", + false, + "Create Geometry", + "Create new geometry for closing stroke (only applies for poly strokes)"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } @@ -3371,6 +3523,7 @@ 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 bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); const int type = RNA_enum_get(op->ptr, "type"); /* sanity checks */ @@ -3381,45 +3534,55 @@ static int gpencil_stroke_caps_set_exec(bContext *C, wmOperator *op) bool changed = false; /* loop all selected strokes */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - if (gpl->actframe == NULL) { - continue; - } + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { - MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } - /* skip strokes that are not selected or invalid for current view */ - if (((gps->flag & GP_STROKE_SELECT) == 0) || (ED_gpencil_stroke_can_use(C, gps) == false)) { - continue; - } - /* skip hidden or locked colors */ - if (!gp_style || (gp_style->flag & GP_MATERIAL_HIDE) || - (gp_style->flag & GP_MATERIAL_LOCKED)) { - continue; - } + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); - short prev_first = gps->caps[0]; - short prev_last = gps->caps[1]; + /* skip strokes that are not selected or invalid for current view */ + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); - if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_START)) { - ++gps->caps[0]; - if (gps->caps[0] >= GP_STROKE_CAP_MAX) { - gps->caps[0] = GP_STROKE_CAP_ROUND; - } - } - if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_END)) { - ++gps->caps[1]; - if (gps->caps[1] >= GP_STROKE_CAP_MAX) { - gps->caps[1] = GP_STROKE_CAP_ROUND; - } - } - if (type == GP_STROKE_CAPS_TOGGLE_DEFAULT) { - gps->caps[0] = GP_STROKE_CAP_ROUND; - gps->caps[1] = GP_STROKE_CAP_ROUND; - } + if (!is_stroke_selected) { + continue; + } + /* skip hidden or locked colors */ + if (!gp_style || (gp_style->flag & GP_MATERIAL_HIDE) || + (gp_style->flag & GP_MATERIAL_LOCKED)) { + continue; + } - if (prev_first != gps->caps[0] || prev_last != gps->caps[1]) { - changed = true; + 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) { + gps->caps[0] = GP_STROKE_CAP_ROUND; + } + } + if (ELEM(type, GP_STROKE_CAPS_TOGGLE_BOTH, GP_STROKE_CAPS_TOGGLE_END)) { + ++gps->caps[1]; + if (gps->caps[1] >= GP_STROKE_CAP_MAX) { + gps->caps[1] = GP_STROKE_CAP_ROUND; + } + } + if (type == GP_STROKE_CAPS_TOGGLE_DEFAULT) { + 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; + } + } } } } @@ -3543,11 +3706,6 @@ 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; } @@ -3567,29 +3725,34 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) /* Add all stroke selected of the frame. */ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - if (gps->flag & GP_STROKE_SELECT) { - /* 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_material_editable(ob, gpl, gps) == false) { - continue; - } - elem = &strokes_list[tot_strokes]; - elem->gpf = gpf; - elem->gps = gps; - elem->used = false; - - tot_strokes++; - /* Limit the number of strokes. */ - if (tot_strokes == max_join_strokes) { - BKE_reportf(op->reports, - RPT_WARNING, - "Too many strokes selected, only joined first %d strokes", - max_join_strokes); - break; - } + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); + + if (!is_stroke_selected) { + 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_material_editable(ob, gpl, gps) == false) { + continue; + } + elem = &strokes_list[tot_strokes]; + elem->gpf = gpf; + elem->gps = gps; + elem->used = false; + + tot_strokes++; + /* Limit the number of strokes. */ + if (tot_strokes == max_join_strokes) { + BKE_reportf(op->reports, + RPT_WARNING, + "Too many strokes selected, only joined first %d strokes", + max_join_strokes); + break; } } } @@ -3623,12 +3786,16 @@ static int gpencil_stroke_join_exec(bContext *C, wmOperator *op) } /* Calc geometry data for new stroke. */ - BKE_gpencil_stroke_geometry_update(gpd, gps_new); + BKE_gpencil_stroke_geometry_update(gpd, gps_new, GP_GEO_UPDATE_DEFAULT); /* If join only, delete old strokes. */ if (type == GP_STROKE_JOIN) { for (int i = 0; i < tot_strokes; i++) { elem = &strokes_list[i]; + if (GPENCIL_STROKE_TYPE_BEZIER(elem->gps) != GPENCIL_STROKE_TYPE_BEZIER(gps_new)) { + continue; + } + BLI_remlink(&elem->gpf->strokes, elem->gps); BKE_gpencil_free_stroke(elem->gps); } @@ -3679,7 +3846,7 @@ void GPENCIL_OT_stroke_join(wmOperatorType *ot) /** \name Stroke Flip Operator * \{ */ -static int gpencil_stroke_flip_exec(bContext *C, wmOperator *op) +static int gpencil_stroke_flip_exec(bContext *C, wmOperator *UNUSED(op)) { bGPdata *gpd = ED_gpencil_data_get_active(C); Object *ob = CTX_data_active_object(C); @@ -3689,7 +3856,6 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *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) { @@ -3699,26 +3865,27 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *op) } LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - if (gps->flag & GP_STROKE_SELECT) { - /* 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_material_editable(ob, gpl, gps) == false) { - continue; - } + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); - if (is_curve_edit) { - BKE_report(op->reports, RPT_ERROR, "Not implemented!"); - } - else { - /* Flip stroke. */ - BKE_gpencil_stroke_flip(gps); - } + if (!is_stroke_selected) { + continue; + } - changed = true; + /* 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_material_editable(ob, gpl, gps) == false) { + continue; + } + + /* Flip stroke. */ + BKE_gpencil_stroke_flip(gps); + + changed = true; } } CTX_DATA_END; @@ -3762,7 +3929,6 @@ 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); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); /* Init snap context for geometry projection. */ @@ -3785,37 +3951,28 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) continue; } for (bGPDstroke *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) { + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); + + if (!is_stroke_selected) { continue; } - bool curve_select = false; - if (is_curve_edit && gps->editcurve != NULL) { - curve_select = gps->editcurve->flag & GP_CURVE_SELECT; + /* update frame to get the new location of objects */ + if ((mode == GP_REPROJECT_SURFACE) && (cfra_prv != gpf->framenum)) { + cfra_prv = gpf->framenum; + CFRA = gpf->framenum; + BKE_scene_graph_update_for_newframe(depsgraph); } - 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)) { - cfra_prv = gpf->framenum; - CFRA = gpf->framenum; - BKE_scene_graph_update_for_newframe(depsgraph); - } - - ED_gpencil_stroke_reproject(depsgraph, &gsc, sctx, gpl, gpf, gps, mode, keep_original); + 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(gpd, gps, gps->editcurve); + /* TODO: Reproject curve data and regenerate stroke. + * Right now we are using the projected points to regenerate the curve. This will most + * likely change the handles which is usually not wanted.*/ + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps); - } - - changed = true; - } + changed = true; } } /* If not multi-edit, exit loop. */ @@ -3909,7 +4066,8 @@ 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(gpd, gps); + /* TODO: maybe add an option to only include selected strokes? */ + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } } @@ -3957,7 +4115,7 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op) } GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - if (gps->flag & GP_STROKE_SELECT) { + if (gps->flag & GP_STROKE_SELECT && GPENCIL_STROKE_TYPE_POLY(gps)) { for (int r = 0; r < repeat; r++) { for (int i = 0; i < gps->totpoints; i++) { bGPDspoint *pt = &gps->points[i]; @@ -3984,6 +4142,22 @@ static void gpencil_smooth_stroke(bContext *C, wmOperator *op) } } } + else if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + BKE_gpencil_editcurve_smooth(gps, + factor, + 2, + repeat, + only_selected, + false, + smooth_position, + smooth_thickness, + smooth_strength); + BKE_gpencil_editcurve_recalculate_handles(gps); + BKE_gpencil_stroke_geometry_update(gpd_, gps, GP_GEO_UPDATE_DEFAULT); + } + } } GP_EDITABLE_STROKES_END(gpstroke_iter); } @@ -4164,38 +4338,33 @@ static int gpencil_stroke_subdivide_exec(bContext *C, wmOperator *op) 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) - { + + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; 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); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); changed = true; } } - GP_EDITABLE_CURVES_END(gps_iter); - } - else { - /* Go through each editable + selected stroke */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + else { if (gps->flag & GP_STROKE_SELECT) { gpencil_stroke_subdivide(gps, cuts); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); changed = true; } } - GP_EDITABLE_STROKES_END(gpstroke_iter); + } + GP_EDITABLE_STROKES_END(gpstroke_iter); - if (changed) { - /* smooth stroke */ - gpencil_smooth_stroke(C, op); - } + if (changed) { + /* smooth stroke */ + gpencil_smooth_stroke(C, op); } if (changed) { @@ -4207,22 +4376,6 @@ static int gpencil_stroke_subdivide_exec(bContext *C, wmOperator *op) 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; @@ -4238,7 +4391,6 @@ 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; @@ -4274,23 +4426,24 @@ static int gpencil_stroke_simplify_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - 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); + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + if (gps->editcurve->flag & GP_CURVE_SELECT) { + BKE_gpencil_editcurve_simplify_adaptive(gps, factor); + BKE_gpencil_editcurve_recalculate_handles(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); changed = true; } } - GP_EDITABLE_STROKES_END(gpstroke_iter); + else 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); if (changed) { /* notifiers */ @@ -4334,24 +4487,26 @@ static int gpencil_stroke_simplify_fixed_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - 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); - } + /* Go through each editable + selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + BKE_gpencil_editcurve_simplify_fixed(gps, steps); + BKE_gpencil_editcurve_recalculate_handles(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); + changed = true; } } - GP_EDITABLE_STROKES_END(gpstroke_iter); + else if (gps->flag & GP_STROKE_SELECT) { + for (int i = 0; i < steps; i++) { + BKE_gpencil_stroke_simplify_fixed(gpd, gps); + } + changed = true; + } } + GP_EDITABLE_STROKES_END(gpstroke_iter); if (changed) { /* notifiers */ @@ -4538,14 +4693,13 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) bGPdata *gpd_dst = NULL; bGPDlayer *gpl_dst = NULL; bGPDframe *gpf_dst = NULL; - bGPDspoint *pt; Material *ma = NULL; - int i, idx; + int idx; eGP_SeparateModes mode = RNA_enum_get(op->ptr, "mode"); + const bool keep_ends = RNA_boolean_get(op->ptr, "keep_ends"); const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_src); - const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd_src); /* sanity checks */ if (ELEM(NULL, gpd_src)) { @@ -4604,6 +4758,13 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) gpf_dst = NULL; LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + bool is_stroke_selected = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (bool)(gps->editcurve->flag & GP_CURVE_SELECT) : + (bool)(gps->flag & GP_STROKE_SELECT); + + if (!is_stroke_selected) { + continue; + } /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { @@ -4613,70 +4774,202 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { continue; } + /* Separate selected strokes. */ - if (gps->flag & GP_STROKE_SELECT) { - /* add layer if not created before */ - if (gpl_dst == NULL) { - gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl->info, false, false); - BKE_gpencil_layer_copy_settings(gpl, gpl_dst); - /* Copy masks. */ - BKE_gpencil_layer_mask_copy(gpl, gpl_dst); - } + if (gpl_dst == NULL) { + gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl->info, false, false); + BKE_gpencil_layer_copy_settings(gpl, gpl_dst); + /* Copy masks. */ + BKE_gpencil_layer_mask_copy(gpl, gpl_dst); + } - /* add frame if not created before */ - if (gpf_dst == NULL) { - gpf_dst = BKE_gpencil_layer_frame_get(gpl_dst, gpf->framenum, GP_GETFRAME_ADD_NEW); - } + /* add frame if not created before */ + if (gpf_dst == NULL) { + gpf_dst = BKE_gpencil_layer_frame_get(gpl_dst, gpf->framenum, GP_GETFRAME_ADD_NEW); + } - /* add duplicate materials */ + /* add duplicate materials */ - /* XXX same material can be in multiple slots. */ - ma = BKE_gpencil_material(ob, gps->mat_nr + 1); + /* XXX same material can be in multiple slots. */ + ma = BKE_gpencil_material(ob, gps->mat_nr + 1); - idx = BKE_gpencil_object_material_ensure(bmain, ob_dst, ma); + idx = BKE_gpencil_object_material_ensure(bmain, ob_dst, ma); - /* selected points mode */ - if (mode == GP_SEPARATE_POINT) { - if (is_curve_edit) { - BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + /* selected points mode */ + if (mode == GP_SEPARATE_POINT) { + /* make copy of source stroke */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true, true); + + /* Reassign material. */ + gps_dst->mat_nr = idx; + + /* link to destination frame */ + BLI_addtail(&gpf_dst->strokes, gps_dst); + + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc_src = gps->editcurve; + bGPDcurve *gpc_dst = gps_dst->editcurve; + + /* Flip the selection */ + for (int i = 0; i < gpc_dst->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc_dst->curve_points[i]; + BezTriple *bezt = &cpt->bezt; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + cpt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + } + else { + cpt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt); + } + } + + if (keep_ends) { + /* Shrink the selection in the original stroke to keep the connecting points. */ + int tot_selected = 0, num_deselected = 0; + + bool prev_sel = false; + int i; + for (i = 0; i < gpc_src->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc_src->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); + num_deselected++; + } + prev_sel = true; + tot_selected++; + } + 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 = gpc_src->tot_curve_points - 1; i > 0; i--) { + bGPDcurve_point *gpc_pt = &gpc_src->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); + num_deselected++; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; + } + } + + /* Deselect curve if all points are deselected. */ + if (tot_selected - num_deselected == 0) { + gpc_src->flag &= ~GP_CURVE_SELECT; + } } - else { - /* make copy of source stroke */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true, true); - /* Reassign material. */ - gps_dst->mat_nr = idx; + BKE_gpencil_curve_delete_tagged_points( + gpd_dst, gpf_dst, gps_dst, NULL, gpc_dst, GP_CURVE_POINT_SELECT); + + BKE_gpencil_curve_delete_tagged_points( + gpd_src, gpf, gps, gps->next, gpc_src, GP_CURVE_POINT_SELECT); + } + else { - /* link to destination frame */ - BLI_addtail(&gpf_dst->strokes, gps_dst); + /* Invert selection status of all points in destination stroke */ + for (int i = 0; i < gps_dst->totpoints; i++) { + bGPDspoint *pt = &gps_dst->points[i]; + 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; + if (keep_ends) { + bGPDspoint *pt; + int i, tot_selected = 0, num_deselected = 0; + bool prev_sel; + + /* 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; + num_deselected++; + } + prev_sel = true; + tot_selected++; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; + } } - /* delete selected points from destination stroke */ - BKE_gpencil_stroke_delete_tagged_points( - gpd_dst, gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); + /* 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; + num_deselected++; + } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; + } + } - /* delete selected points from origin stroke */ - BKE_gpencil_stroke_delete_tagged_points( - gpd_src, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); + /* Deselect stroke if all points are deselected. */ + if (tot_selected - num_deselected == 0) { + gps->flag &= ~GP_STROKE_SELECT; + } } + + /* delete selected points from destination stroke */ + BKE_gpencil_stroke_delete_tagged_points( + gpd_dst, gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); + + /* delete selected points from origin stroke */ + BKE_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) { + /* deselect old stroke */ + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + gps->editcurve->flag &= ~GP_CURVE_SELECT; } - /* selected strokes mode */ - else if (mode == GP_SEPARATE_STROKE) { - /* deselect old stroke */ + else { gps->flag &= ~GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_reset(gps); - /* unlink from source frame */ - BLI_remlink(&gpf->strokes, gps); - gps->prev = gps->next = NULL; - /* relink to destination frame */ - BLI_addtail(&gpf_dst->strokes, gps); - /* Reassign material. */ - gps->mat_nr = idx; } + BKE_gpencil_stroke_select_index_reset(gps); + /* unlink from source frame */ + BLI_remlink(&gpf->strokes, gps); + gps->prev = gps->next = NULL; + /* relink to destination frame */ + BLI_addtail(&gpf_dst->strokes, gps); + /* Reassign material. */ + gps->mat_nr = idx; } } } @@ -4754,6 +5047,24 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static bool gpencil_stroke_separate_poll_property(const bContext *UNUSED(C), + wmOperator *op, + const PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + + /* Only show connect keep_ends in GP_SEPARATE_POINT mode. */ + if (STREQ(prop_id, "keep_ends")) { + const int type = RNA_enum_get(op->ptr, "mode"); + if (type == GP_SEPARATE_POINT) { + return true; + } + return false; + } + + return true; +} + void GPENCIL_OT_stroke_separate(wmOperatorType *ot) { static const EnumPropertyItem separate_type[] = { @@ -4772,12 +5083,19 @@ void GPENCIL_OT_stroke_separate(wmOperatorType *ot) ot->invoke = WM_menu_invoke; ot->exec = gpencil_stroke_separate_exec; ot->poll = gpencil_strokes_edit3d_poll; + ot->poll_property = gpencil_stroke_separate_poll_property; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ ot->prop = RNA_def_enum(ot->srna, "mode", separate_type, GP_SEPARATE_POINT, "Mode", ""); + + RNA_def_boolean(ot->srna, + "keep_ends", + false, + "Keep Ends", + "Seperate the selected points, but keep the ends in both"); } /** \} */ @@ -4786,19 +5104,17 @@ void GPENCIL_OT_stroke_separate(wmOperatorType *ot) /** \name Stroke Split Operator * \{ */ -static int gpencil_stroke_split_exec(bContext *C, wmOperator *op) +static int gpencil_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = ED_gpencil_data_get_active(C); - bGPDspoint *pt; - int i; /* sanity checks */ if (ELEM(NULL, gpd)) { 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); + bool changed = false; /* loop strokes and split parts */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { @@ -4811,8 +5127,9 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *op) continue; } - LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { - + /* We are deleting the strokes we are iterating over and adding new strokes to the end of + * the frame. So we need to use backwards mutable iteration here. */ + LISTBASE_FOREACH_BACKWARD_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; @@ -4821,42 +5138,66 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *op) if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { continue; } + /* Split selected strokes. */ - if (gps->flag & GP_STROKE_SELECT) { - if (is_curve_edit) { - BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if ((gpc->flag & GP_CURVE_SELECT) == 0) { + continue; } - 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); + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true, true); + 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; + bGPDcurve *gpc_dst = gps_dst->editcurve; + for (int i = 0; i < gpc_dst->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc_dst->curve_points[i]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + cpt->flag &= ~GP_CURVE_POINT_TAG; + } + else { + cpt->flag |= GP_CURVE_POINT_TAG; } + } - /* delete selected points from destination stroke */ - BKE_gpencil_stroke_delete_tagged_points( - gpd, gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0); + BKE_gpencil_curve_delete_tagged_points( + gpd, gpf, gps_dst, NULL, gpc_dst, GP_CURVE_POINT_TAG); - /* delete selected points from origin stroke */ - BKE_gpencil_stroke_delete_tagged_points( - gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); - } + BKE_gpencil_curve_delete_tagged_points( + gpd, gpf, gps, gps->next, gpc, GP_CURVE_POINT_SELECT); } - } - /* select again tagged points */ - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - bGPDspoint *ptn = gps->points; - for (int i2 = 0; i2 < gps->totpoints; i2++, ptn++) { - if (ptn->flag & GP_SPOINT_TAG) { - ptn->flag |= GP_SPOINT_SELECT; - ptn->flag &= ~GP_SPOINT_TAG; + else { + if ((gps->flag & GP_STROKE_SELECT) == 0) { + continue; } + + /* Make copy of source stroke. */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps, true, false); + + /* Link to same frame. */ + BLI_addtail(&gpf->strokes, gps_dst); + + /* Tag the unselected points. */ + for (int i = 0; i < gps_dst->totpoints; i++) { + bGPDspoint *pt = &gps_dst->points[i]; + if (pt->flag & GP_SPOINT_SELECT) { + pt->flag &= ~GP_SPOINT_TAG; + } + else { + pt->flag |= GP_SPOINT_TAG; + } + } + + /* Delete tagged points from destination stroke. */ + BKE_gpencil_stroke_delete_tagged_points( + gpd, gpf, gps_dst, NULL, GP_SPOINT_TAG, false, 0); + + /* Delete selected points from origin stroke. */ + BKE_gpencil_stroke_delete_tagged_points( + gpd, gpf, gps, gps->next, GP_SPOINT_SELECT, false, 0); } + + changed = true; } } @@ -4868,9 +5209,10 @@ static int gpencil_stroke_split_exec(bContext *C, wmOperator *op) } CTX_DATA_END; - 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) { + 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; } @@ -4934,7 +5276,7 @@ void GPENCIL_OT_stroke_smooth(wmOperatorType *ot) prop = RNA_def_int(ot->srna, "repeat", 1, 1, 50, "Repeat", "", 1, 20); RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 2.0f, "Factor", "", 0.0f, 2.0f); + RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 2.0f, "Factor", "", 0.0f, 1.0f); RNA_def_boolean(ot->srna, "only_selected", true, @@ -5305,24 +5647,31 @@ static int gpencil_merge_by_distance_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - 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); + bool changed = false; + /* Go through each editable selected stroke */ + GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + /* TODO: don't hardcode the refit and threshold. Figure out how to set these. */ + if (BKE_gpencil_editcurve_merge_distance(gps, threshold, unselected, false, 0.0f)) { + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); + changed = true; + } } } - GP_EDITABLE_STROKES_END(gpstroke_iter); + else if (gps->flag & GP_STROKE_SELECT) { + BKE_gpencil_stroke_merge_distance(gpd, gpf_, gps, threshold, unselected); + changed = true; + } } + 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; } @@ -5474,8 +5823,7 @@ static int gpencil_stroke_normalize_exec(bContext *C, wmOperator *op) } } - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } /* If not multi-edit, exit loop. */ diff --git a/source/blender/editors/gpencil/gpencil_edit_curve.c b/source/blender/editors/gpencil/gpencil_edit_curve.c index e766a410889..925375d7103 100644 --- a/source/blender/editors/gpencil/gpencil_edit_curve.c +++ b/source/blender/editors/gpencil/gpencil_edit_curve.c @@ -35,6 +35,7 @@ #include "BKE_gpencil.h" #include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" +#include "BKE_report.h" #include "BLI_listbase.h" #include "BLI_math.h" @@ -49,91 +50,14 @@ #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; +#include "UI_interface.h" +#include "UI_resources.h" - 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(gpd, 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; +#include "gpencil_intern.h" - /* 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.0f); - RNA_def_property_ui_range(prop, FLT_MIN, 10.0f, 0.1f, 5); -} +/* -------------------------------------------------------------------- */ +/** \name Set handle type operator + * \{ */ static int gpencil_editcurve_set_handle_type_exec(bContext *C, wmOperator *op) { @@ -169,8 +93,7 @@ static int gpencil_editcurve_set_handle_type_exec(bContext *C, wmOperator *op) } BKE_gpencil_editcurve_recalculate_handles(gps); - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } GP_EDITABLE_CURVES_END(gps_iter); @@ -199,7 +122,7 @@ void GPENCIL_OT_stroke_editcurve_set_handle_type(wmOperatorType *ot) /* api callbacks */ ot->invoke = WM_menu_invoke; ot->exec = gpencil_editcurve_set_handle_type_exec; - ot->poll = gpencil_curve_edit_mode_poll; + ot->poll = gpencil_active_layer_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -207,5 +130,151 @@ void GPENCIL_OT_stroke_editcurve_set_handle_type(wmOperatorType *ot) /* properties */ ot->prop = RNA_def_enum(ot->srna, "type", editcurve_handle_type_items, 1, "Type", "Spline type"); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Set stroke type operator + * \{ */ + +static int gpencil_stroke_set_type_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + bGPdata *gpd = ob->data; + eGPStrokeType type = RNA_enum_get(op->ptr, "type"); + const bool only_selected = RNA_boolean_get(op->ptr, "only_selected"); + + if (ELEM(NULL, gpd)) { + return OPERATOR_CANCELLED; + } + + bool changed = false; + switch (type) { + case STROKE_POLY: { + GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) + { + if (only_selected && (gpc->flag & GP_CURVE_SELECT) == 0) { + continue; + } + BKE_gpencil_stroke_editcurve_sync_selection(gpd, gps, gpc); + BKE_gpencil_free_stroke_editcurve(gps); + changed = true; + } + GP_EDITABLE_CURVES_END(gps_iter); + } break; + + case STROKE_BEZIER: { + const float threshold = RNA_float_get(op->ptr, "threshold"); + const float corner_angle = RNA_float_get(op->ptr, "corner_angle"); + + GP_EDITABLE_STROKES_BEGIN (gps_iter, C, gpl, gps) { + if (!GPENCIL_STROKE_TYPE_BEZIER(gps)) { + if (only_selected && (gps->flag & GP_STROKE_SELECT) == 0) { + continue; + } + + BKE_gpencil_stroke_refit_curve(gps, threshold, corner_angle, GP_GEO_UPDATE_DEFAULT); + if (gps->editcurve != NULL) { + bGPDcurve *gpc = gps->editcurve; + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_POLYLINE_REGENERATE_ALL); + + /* Select all curve points. */ + for (uint32_t i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&pt->bezt); + } + gpc->flag |= GP_CURVE_SELECT; + + /* Deselect stroke points. */ + for (uint32_t i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + pt->flag &= ~GP_SPOINT_SELECT; + } + gps->flag &= ~GP_STROKE_SELECT; + changed = true; + } + } + } + GP_EDITABLE_STROKES_END(gps_iter); + } break; + default: { + BKE_report(op->reports, RPT_ERROR, "Unknown stroke type"); + return OPERATOR_CANCELLED; + } + } + + 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 void gpencil_stroke_set_type_ui(bContext *UNUSED(C), wmOperator *op) +{ + uiLayout *layout = op->layout; + PointerRNA ptr; + uiLayoutSetPropSep(layout, true); + RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr); + + uiItemR(layout, &ptr, "only_selected", 0, NULL, ICON_NONE); + eGPStrokeType type = RNA_enum_get(&ptr, "type"); + if (type == STROKE_BEZIER) { + uiItemR(layout, &ptr, "threshold", 0, NULL, ICON_NONE); + uiItemR(layout, &ptr, "corner_angle", 0, NULL, ICON_NONE); + } +} + +void GPENCIL_OT_stroke_set_type(wmOperatorType *ot) +{ + PropertyRNA *prop; + static const EnumPropertyItem stroke_types[] = { + {STROKE_POLY, "POLY", 0, "Poly", ""}, + {STROKE_BEZIER, "BEZIER", 0, "Bezier", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Set Stroke Type"; + ot->idname = "GPENCIL_OT_stroke_set_type"; + ot->description = "Set the type of the stroke"; + + /* api callbacks */ + ot->exec = gpencil_stroke_set_type_exec; + ot->poll = gpencil_active_layer_poll; + ot->ui = gpencil_stroke_set_type_ui; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_enum(ot->srna, "type", stroke_types, STROKE_POLY, "Type", "Stroke type"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + prop = RNA_def_boolean( + ot->srna, "only_selected", true, "Selected Only", "Only set the type for selected strokes"); + + prop = RNA_def_float(ot->srna, + "threshold", + GP_DEFAULT_CURVE_ERROR, + 0.0f, + 100.0f, + "Threshold", + "Bézier curve fitting error threshold", + 0.0f, + 3.0f); + + prop = RNA_def_float_distance(ot->srna, + "corner_angle", + GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE, + 0.0f, + M_PI, + "Corner Angle", + "Angle threshold to be treated as corners", + 0.0f, + M_PI); + RNA_def_property_subtype(prop, PROP_ANGLE); +} /** \} */ diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 091ff2c16b0..b1c83a2634e 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -1599,7 +1599,7 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(tgpf->gpd, gps); + BKE_gpencil_stroke_geometry_update(tgpf->gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* ----------------------- */ diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index d1a1e417d9e..49ca83c511c 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -227,6 +227,12 @@ typedef struct tGPDprimitive { } tGPDprimitive; +/* Stroke types enum definition. */ +typedef enum eGPStrokeType { + STROKE_POLY = 0, + STROKE_BEZIER = 1, +} eGPStrokeType; + bool gpencil_stroke_inside_circle(const float mval[2], int rad, int x0, int y0, int x1, int y1); void gpencil_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc); @@ -397,8 +403,8 @@ 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); +void GPENCIL_OT_stroke_set_type(struct wmOperatorType *ot); /* stroke sculpting -- */ @@ -519,6 +525,9 @@ void GPENCIL_OT_primitive_polyline(struct wmOperatorType *ot); void GPENCIL_OT_primitive_circle(struct wmOperatorType *ot); void GPENCIL_OT_primitive_curve(struct wmOperatorType *ot); +wmKeyMap *gpencil_curve_draw_modal_keymap(wmKeyConfig *keyconf); +void GPENCIL_OT_draw_curve(struct wmOperatorType *ot); + /* vertex groups ------------ */ void GPENCIL_OT_vertex_group_assign(struct wmOperatorType *ot); void GPENCIL_OT_vertex_group_remove_from(struct wmOperatorType *ot); @@ -639,10 +648,10 @@ struct GP_EditableStrokes_Iter { /* skip strokes that are invalid for current view */ \ if (ED_gpencil_stroke_can_use(C, gps) == false) \ continue; \ - if (gps->editcurve == NULL) \ + if (!GPENCIL_STROKE_TYPE_BEZIER(gps)) \ continue; \ bGPDcurve *gpc = gps->editcurve; \ - /* ... Do Stuff With Strokes ... */ + /* ... Do Stuff With Curves ... */ #define GP_EDITABLE_CURVES_END(gpstroke_iter) \ } \ diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index 0062e363cdf..a5fc76ed041 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -424,7 +424,7 @@ static void gpencil_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgp gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke, GP_GEO_UPDATE_DEFAULT); /* Add to strokes. */ BLI_addtail(&tgpil->interFrame->strokes, new_stroke); @@ -569,7 +569,7 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi) gpencil_interpolate_smooth_stroke(new_stroke, tgpi->smooth_factor, tgpi->smooth_steps); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke, GP_GEO_UPDATE_DEFAULT); /* add to strokes */ BLI_addtail(&tgpil->interFrame->strokes, new_stroke); } @@ -844,7 +844,7 @@ static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent gps_dst->flag &= ~GP_STROKE_TAG; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps_dst); + BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps_dst, GP_GEO_UPDATE_DEFAULT); BLI_addtail(&gpf_dst->strokes, gps_dst); } @@ -1398,7 +1398,7 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) gpencil_interpolate_smooth_stroke(new_stroke, smooth_factor, smooth_steps); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, new_stroke); + BKE_gpencil_stroke_geometry_update(gpd, new_stroke, GP_GEO_UPDATE_DEFAULT); /* Add strokes to frame. */ bGPDframe *interFrame = BKE_gpencil_layer_frame_get(gpl, cframe, GP_GETFRAME_ADD_NEW); diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c index 259b2882589..ef31ed6c3da 100644 --- a/source/blender/editors/gpencil/gpencil_merge.c +++ b/source/blender/editors/gpencil/gpencil_merge.c @@ -523,7 +523,7 @@ static int gpencil_stroke_merge_exec(bContext *C, wmOperator *op) gpencil_dissolve_points(C); } - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); /* 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 35640cf3b66..08543cce5bd 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -64,13 +64,6 @@ 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) { @@ -175,6 +168,13 @@ static bool gpencil_stroke_paintmode_tint_poll(bContext *C) return gpencil_stroke_paintmode_poll_with_tool(C, GPAINT_TOOL_TINT); } +#if 0 +static bool gpencil_stroke_paintmode_curve_poll(bContext *C) +{ + return gpencil_stroke_paintmode_poll_with_tool(C, GPAINT_TOOL_CURVE); +} +#endif + /* Poll callback for stroke sculpting mode */ static bool gpencil_stroke_sculptmode_poll(bContext *C) { @@ -317,15 +317,6 @@ 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) { @@ -354,6 +345,15 @@ static void ed_keymap_gpencil_painting_tint(wmKeyConfig *keyconf) keymap->poll = gpencil_stroke_paintmode_tint_poll; } +#if 0 +/* keys for draw with a curve brush */ +static void ed_keymap_gpencil_painting_curve(wmKeyConfig *keyconf) +{ + wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Grease Pencil Stroke Paint (Curve)", 0, 0); + keymap->poll = gpencil_stroke_paintmode_curve_poll; +} +#endif + /* Stroke Painting Keymap - Only when paintmode is enabled */ static void ed_keymap_gpencil_painting(wmKeyConfig *keyconf) { @@ -482,13 +482,16 @@ 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); ed_keymap_gpencil_painting_erase(keyconf); ed_keymap_gpencil_painting_fill(keyconf); ed_keymap_gpencil_painting_tint(keyconf); +#if 0 + ed_keymap_gpencil_painting_curve(keyconf); +#endif + gpencil_curve_draw_modal_keymap(keyconf); ed_keymap_gpencil_sculpting(keyconf); ed_keymap_gpencil_sculptpainting_smooth(keyconf); ed_keymap_gpencil_sculptpainting_thickness(keyconf); @@ -580,10 +583,10 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_sculpt_paint); WM_operatortype_append(GPENCIL_OT_weight_paint); - /* Edit stroke editcurve */ + /* Editcurve */ - WM_operatortype_append(GPENCIL_OT_stroke_enter_editcurve_mode); WM_operatortype_append(GPENCIL_OT_stroke_editcurve_set_handle_type); + WM_operatortype_append(GPENCIL_OT_stroke_set_type); /* Editing (Buttons) ------------ */ @@ -695,6 +698,8 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_primitive_circle); WM_operatortype_append(GPENCIL_OT_primitive_curve); + WM_operatortype_append(GPENCIL_OT_draw_curve); + /* convert old 2.7 files to 2.8 */ WM_operatortype_append(GPENCIL_OT_convert_old_files); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 67d4b7726b5..fa867bcd4c2 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -54,6 +54,7 @@ #include "BKE_deform.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_main.h" @@ -944,6 +945,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) const bool is_depth = (bool)(align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE)); const bool is_lock_axis_view = (bool)(ts->gp_sculpt.lock_axis == 0); const bool is_camera = is_lock_axis_view && (rv3d->persp == RV3D_CAMOB) && (!is_depth); + const bool is_bezier_mode = ts->gpencil_flags & GP_TOOL_FLAG_BEZIER_MODE; int totelem; /* For very low pressure at the end, truncate stroke. */ @@ -951,7 +953,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) int last_i = gpd->runtime.sbuffer_used - 1; while (last_i > 0) { ptc = (tGPspoint *)gpd->runtime.sbuffer + last_i; - if (ptc->pressure > 0.001f) { + if (ptc->pressure > 0.0f) { break; } gpd->runtime.sbuffer_used = last_i - 1; @@ -1197,8 +1199,21 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) } } + /* Convert to bezier stroke when we are in bezier mode. */ + if (is_bezier_mode) { + /* The refitting algorithm assumes that we have a bounding box calculated. */ + BKE_gpencil_stroke_boundingbox_calc(gps); + BKE_gpencil_stroke_refit_curve(gps, + ts->gpencil_curve_fit_threshold, + ts->gpencil_curve_fit_corner_angle, + GP_GEO_UPDATE_CURVE_REFIT_ALL); + } + /* subdivide and smooth the stroke */ - if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (subdivide > 0)) { + if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && + (subdivide > 0) + /* XXX: For now, don't subdivide in bezier mode. */ + && !(is_bezier_mode)) { gpencil_subdivide_stroke(gpd, gps, subdivide); } @@ -1207,6 +1222,17 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) * without changing too much the original stroke. */ if ((brush->gpencil_settings->flag & GP_BRUSH_GROUP_SETTINGS) && (brush->gpencil_settings->draw_smoothfac > 0.0f)) { + if (is_bezier_mode) { + BKE_gpencil_editcurve_smooth(gps, + brush->gpencil_settings->draw_smoothfac, + 2, + brush->gpencil_settings->draw_smoothlvl, + false, + true, + true, + false, + true); + } float reduce = 0.0f; for (int r = 0; r < brush->gpencil_settings->draw_smoothlvl; r++) { for (i = 0; i < gps->totpoints - 1; i++) { @@ -1230,15 +1256,24 @@ 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(gpd, gps, brush->gpencil_settings->simplify_f); + if (is_bezier_mode) { + BKE_gpencil_editcurve_simplify_adaptive(gps, brush->gpencil_settings->simplify_f); + } + else { + BKE_gpencil_stroke_simplify_adaptive(gpd, gps, brush->gpencil_settings->simplify_f); + } + } + + if (!is_bezier_mode) { + /* reproject to plane (only in 3d space) */ + gpencil_reproject_toplane(p, gps); } - /* reproject to plane (only in 3d space) */ - gpencil_reproject_toplane(p, gps); /* change position relative to parent object */ gpencil_apply_parent(depsgraph, obact, gpl, gps); /* If camera view or view projection, reproject flat to view to avoid perspective effect. */ - if ((!is_depth) && (((align_flag & GP_PROJECT_VIEWSPACE) && is_lock_axis_view) || is_camera)) { + if ((!is_bezier_mode) && (!is_depth) && + (((align_flag & GP_PROJECT_VIEWSPACE) && is_lock_axis_view) || is_camera)) { ED_gpencil_project_stroke_to_view(p->C, p->gpl, gps); } @@ -1282,12 +1317,12 @@ 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) { + (p->brush->gpencil_settings->flag & GP_BRUSH_TRIM_STROKE) && (!is_bezier_mode)) { BKE_gpencil_stroke_trim(gpd, gps); } /* Join with existing strokes. */ - if (ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) { + if ((ts->gpencil_flags & GP_TOOL_FLAG_AUTOMERGE_STROKE) && (!is_bezier_mode)) { if (gps->prev != NULL) { int pt_index = 0; bool doit = true; @@ -1306,7 +1341,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); /* In Multiframe mode, duplicate the stroke in other frames. */ if (GPENCIL_MULTIEDIT_SESSIONS_ON(p->gpd)) { diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index a2b4e5dee64..d7d0cd2c05b 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -1093,7 +1093,7 @@ static void gpencil_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); /* Update evaluated data. */ ED_gpencil_sbuffer_update_eval(tgpi->gpd, tgpi->ob_eval); @@ -1351,7 +1351,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(tgpi->gpd, gps); + BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* transfer stroke from temporary buffer to the actual frame */ @@ -1394,7 +1394,7 @@ static void gpencil_primitive_interaction_end(bContext *C, } ED_gpencil_stroke_close_by_distance(gps, 0.02f); } - BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps); + BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* In Multiframe mode, duplicate the stroke in other frames. */ diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c index efd0f86df03..9ea4f176585 100644 --- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c +++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c @@ -54,6 +54,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_gpencil_modifier.h" #include "BKE_main.h" @@ -96,6 +97,8 @@ typedef struct tGP_BrushEditData { ScrArea *area; ARegion *region; + ToolSettings *ts; + /* Current GPencil datablock */ bGPdata *gpd; @@ -291,7 +294,7 @@ static void gpencil_recalc_geometry_tag(bGPDstroke *gps) } /* Recalc any stroke tagged. */ -static void gpencil_update_geometry(bGPdata *gpd) +static void gpencil_update_geometry(bGPdata *gpd, ToolSettings *ts) { if (gpd == NULL) { return; @@ -305,7 +308,11 @@ static void gpencil_update_geometry(bGPdata *gpd) LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { if (gps->flag & GP_STROKE_TAG) { - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_refit_curve(gps, + ts->gpencil_curve_fit_threshold, + ts->gpencil_curve_fit_corner_angle, + GP_GEO_UPDATE_CURVE_REFIT_ALL); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_POLYLINE_REGENERATE_ALL); gps->flag &= ~GP_STROKE_TAG; } } @@ -1161,6 +1168,7 @@ static bool gpencil_sculpt_brush_init(bContext *C, wmOperator *op) gso->bmain = CTX_data_main(C); /* store state */ gso->settings = gpencil_sculpt_get_settings(scene); + gso->ts = ts; /* Random generator, only init once. */ uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX); @@ -1304,7 +1312,7 @@ static void gpencil_sculpt_brush_exit(bContext *C, wmOperator *op) gso->brush->gpencil_settings->sculpt_flag &= ~GP_SCULPT_FLAG_TMP_INVERT; /* Update geometry data for tagged strokes. */ - gpencil_update_geometry(gso->gpd); + gpencil_update_geometry(gso->gpd, gso->ts); /* free operator data */ MEM_freeN(gso); @@ -1449,6 +1457,7 @@ static bool gpencil_sculpt_brush_do_stroke(tGP_BrushEditData *gso, bool include_last = false; bool changed = false; float rot_eval = 0.0f; + const bool is_curve = GPENCIL_STROKE_TYPE_BEZIER(gps_active); if (gps->totpoints == 1) { bGPDspoint pt_temp; @@ -1465,6 +1474,9 @@ static bool gpencil_sculpt_brush_do_stroke(tGP_BrushEditData *gso, if (len_v2v2_int(mval_i, pc1) <= radius) { /* apply operation to this point */ if (pt_active != NULL) { + if (is_curve) { + pt_active->flag |= GP_SPOINT_TAG; + } rot_eval = gpencil_sculpt_rotation_eval_get(gso, gps, pt, 0); changed = apply(gso, gps_active, rot_eval, 0, radius, pc1); } @@ -1508,7 +1520,7 @@ static bool gpencil_sculpt_brush_do_stroke(tGP_BrushEditData *gso, /* To each point individually... */ pt = &gps->points[i]; - if ((pt->runtime.pt_orig == NULL) && (tool != GPSCULPT_TOOL_GRAB)) { + if (!is_curve && (pt->runtime.pt_orig == NULL) && (tool != GPSCULPT_TOOL_GRAB)) { continue; } pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; @@ -1517,8 +1529,14 @@ static bool gpencil_sculpt_brush_do_stroke(tGP_BrushEditData *gso, ((pt_active->flag & GP_SPOINT_SELECT) == 0)) { continue; } + index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i; if ((pt_active != NULL) && (index < gps_active->totpoints)) { + if (is_curve) { + /* Tag points that will be transformed for curve update. */ + pt_active->flag |= GP_SPOINT_TAG; + } + rot_eval = gpencil_sculpt_rotation_eval_get(gso, gps, pt, i); ok = apply(gso, gps_active, rot_eval, index, radius, pc1); } @@ -1690,7 +1708,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(gpd, gps_active); + BKE_gpencil_stroke_geometry_update(gpd, gps_active, GP_GEO_UPDATE_DEFAULT); } 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 c33b43247fd..e4f397be20e 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -132,27 +132,6 @@ 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) { @@ -162,33 +141,32 @@ static void deselect_all_selected(bContext *C) 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) { - bGPDspoint *pt; - int i; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; - /* deselect points */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; + if (gpc->flag & GP_CURVE_SELECT) { + /* Deselect the curve points. */ + for (uint32_t 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; } - - /* deselect stroke itself too */ - gps->flag &= ~GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_reset(gps); } - - /* 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); + else { + if (gps->flag & GP_STROKE_SELECT) { + /* Deselect the points. */ + for (uint32_t i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + pt->flag &= ~GP_SPOINT_SELECT; + } + gps->flag &= ~GP_STROKE_SELECT; } - - gpc->flag &= ~GP_CURVE_SELECT; } + + BKE_gpencil_stroke_select_index_reset(gps); } CTX_DATA_END; } @@ -210,12 +188,10 @@ static void select_all_curve_points(bGPdata *gpd, bGPDstroke *gps, bGPDcurve *gp if (deselect == false) { gpc->flag |= GP_CURVE_SELECT; - gps->flag |= GP_STROKE_SELECT; BKE_gpencil_stroke_select_index_set(gpd, gps); } else { gpc->flag &= ~GP_CURVE_SELECT; - gps->flag &= ~GP_STROKE_SELECT; BKE_gpencil_stroke_select_index_reset(gps); } } @@ -244,7 +220,6 @@ 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"); @@ -264,12 +239,7 @@ static int gpencil_select_all_exec(bContext *C, wmOperator *op) } } - if (is_curve_edit) { - ED_gpencil_select_curve_toggle_all(C, action); - } - else { - ED_gpencil_select_toggle_all(C, action); - } + ED_gpencil_select_toggle_all(C, action); /* updates */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); @@ -308,7 +278,6 @@ 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"); @@ -320,9 +289,11 @@ static int gpencil_select_linked_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - if (is_curve_edit) { - GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc) - { + bool changed = false; + /* select all points in selected strokes */ + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; if (gpc->flag & GP_CURVE_SELECT) { for (int i = 0; i < gpc->tot_curve_points; i++) { bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; @@ -330,13 +301,10 @@ static int gpencil_select_linked_exec(bContext *C, wmOperator *op) gpc_pt->flag |= GP_CURVE_POINT_SELECT; BEZT_SEL_ALL(bezt); } + changed = true; } } - GP_EDITABLE_CURVES_END(gps_iter); - } - else { - /* select all points in selected strokes */ - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + else { if (gps->flag & GP_STROKE_SELECT) { bGPDspoint *pt; int i; @@ -344,19 +312,24 @@ static int gpencil_select_linked_exec(bContext *C, wmOperator *op) for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { 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; } @@ -385,7 +358,6 @@ static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) { 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"); @@ -398,10 +370,10 @@ static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) } 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)) { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if ((gpc->flag & GP_CURVE_SELECT) && (gpc->tot_curve_points > 1)) { int idx = 0; int start = 0; if (unselect_ends) { @@ -435,11 +407,7 @@ static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) changed = true; } } - GP_EDITABLE_CURVES_END(gps_iter); - } - else { - /* select all points in selected strokes */ - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + else { if ((gps->flag & GP_STROKE_SELECT) && (gps->totpoints > 1)) { bGPDspoint *pt; int row = 0; @@ -471,8 +439,8 @@ static int gpencil_select_alternate_exec(bContext *C, wmOperator *op) changed = true; } } - CTX_DATA_END; } + CTX_DATA_END; if (changed) { /* updates */ @@ -535,7 +503,6 @@ 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) { @@ -549,49 +516,46 @@ static bool gpencil_select_same_layer(bContext *C) /* Search for a selected stroke */ for (gps = gpf->strokes.first; gps; gps = gps->next) { - if (ED_gpencil_stroke_can_use(C, gps)) { - if (gps->flag & GP_STROKE_SELECT) { - found = true; - break; - } + if (!ED_gpencil_stroke_can_use(C, gps)) { + continue; + } + + if (gps->flag & GP_STROKE_SELECT || + (GPENCIL_STROKE_TYPE_BEZIER(gps) && gps->editcurve->flag & GP_CURVE_SELECT)) { + found = true; + break; } } /* Select all if found */ if (found) { - 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; - BKE_gpencil_stroke_select_index_set(gpd, gps); + for (gps = gpf->strokes.first; gps; gps = gps->next) { + if (!ED_gpencil_stroke_can_use(C, gps)) { + continue; + } - changed = true; + if (GPENCIL_STROKE_TYPE_BEZIER(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; } - } - 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; - BKE_gpencil_stroke_select_index_set(gpd, gps); + else { + bGPDspoint *pt; + int i; - changed = true; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag |= GP_SPOINT_SELECT; } + + gps->flag |= GP_STROKE_SELECT; } + + BKE_gpencil_stroke_select_index_set(gpd, gps); + changed = true; } } } @@ -604,14 +568,12 @@ static bool gpencil_select_same_layer(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) { + if (gps->flag & GP_STROKE_SELECT || + (GPENCIL_STROKE_TYPE_BEZIER(gps) && gps->editcurve->flag & GP_CURVE_SELECT)) { /* add instead of insert here, otherwise the uniqueness check gets skipped, * and we get many duplicate entries... */ @@ -620,28 +582,22 @@ static bool gpencil_select_same_material(bContext *C) } CTX_DATA_END; + bool changed = false; + /* Second, select any visible stroke that uses these colors */ - 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)) { + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (BLI_gset_haskey(selected_colors, &gps->mat_nr)) { + if (GPENCIL_STROKE_TYPE_BEZIER(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; - BKE_gpencil_stroke_select_index_set(gpd, gps); - changed = true; + gpc->flag |= GP_CURVE_SELECT; } - } - CTX_DATA_END; - } - else { - CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (BLI_gset_haskey(selected_colors, &gps->mat_nr)) { + else { /* select this stroke */ bGPDspoint *pt; int i; @@ -651,13 +607,13 @@ static bool gpencil_select_same_material(bContext *C) } gps->flag |= GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_set(gpd, gps); - - changed = true; } + + BKE_gpencil_stroke_select_index_set(gpd, gps); + changed = true; } - CTX_DATA_END; } + CTX_DATA_END; /* Free memory. */ if (selected_colors != NULL) { @@ -741,7 +697,6 @@ 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 has been caught but not processed. */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { @@ -754,36 +709,33 @@ static int gpencil_select_first_exec(bContext *C, wmOperator *op) 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)) { + if (only_selected && + !((gps->flag & GP_STROKE_SELECT) || + (GPENCIL_STROKE_TYPE_BEZIER(gps) && gps->editcurve->flag & GP_CURVE_SELECT))) { continue; } /* select first point */ BLI_assert(gps->totpoints >= 1); - 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; - BKE_gpencil_stroke_select_index_set(gpd, gps); + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; - 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); - } + gpc->curve_points[0].flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc->curve_points[0].bezt); + gpc->flag |= GP_CURVE_SELECT; + + if ((extend == false) && (gpc->tot_curve_points > 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; - BKE_gpencil_stroke_select_index_set(gpd, gps); /* deselect rest? */ if ((extend == false) && (gps->totpoints > 1)) { @@ -795,8 +747,10 @@ static int gpencil_select_first_exec(bContext *C, wmOperator *op) pt->flag &= ~GP_SPOINT_SELECT; } } - changed = true; } + + BKE_gpencil_stroke_select_index_set(gpd, gps); + changed = true; } CTX_DATA_END; @@ -851,7 +805,6 @@ void GPENCIL_OT_select_first(wmOperatorType *ot) 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 has been caught but not processed. */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { @@ -864,35 +817,33 @@ static int gpencil_select_last_exec(bContext *C, wmOperator *op) 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)) { + if (only_selected && + !((gps->flag & GP_STROKE_SELECT) || + (GPENCIL_STROKE_TYPE_BEZIER(gps) && gps->editcurve->flag & GP_CURVE_SELECT))) { continue; } /* select last point */ BLI_assert(gps->totpoints >= 1); - 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; - BKE_gpencil_stroke_select_index_set(gpd, gps); - 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); - } + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + 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; + BKE_gpencil_stroke_select_index_set(gpd, gps); + 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; - BKE_gpencil_stroke_select_index_set(gpd, gps); /* deselect rest? */ if ((extend == false) && (gps->totpoints > 1)) { @@ -904,9 +855,10 @@ static int gpencil_select_last_exec(bContext *C, wmOperator *op) pt->flag &= ~GP_SPOINT_SELECT; } } - - changed = true; } + + BKE_gpencil_stroke_select_index_set(gpd, gps); + changed = true; } CTX_DATA_END; @@ -961,106 +913,98 @@ 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 has been caught but not processed. */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { return OPERATOR_CANCELLED; } 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; + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps) && (gps->editcurve->flag & GP_CURVE_SELECT)) { + bGPDcurve *gpc = gps->editcurve; + + bool prev_sel = 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 (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; } + } - 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; + for (int i = gpc->tot_curve_points - 1; i >= 0; i--) { + bGPDcurve_point *gpc_pt = &gpc->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; } } } - 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; + else 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; - } - else { - /* unselected point - expand selection if previous was selected... */ - if (prev_sel) { - pt->flag |= GP_SPOINT_SELECT; - changed = true; - } - prev_sel = false; + /* 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; } + } - /* 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; + /* 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; } } } - CTX_DATA_END; } + CTX_DATA_END; if (changed) { /* updates */ @@ -1100,7 +1044,6 @@ 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 has been caught but not processed. */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { @@ -1108,105 +1051,115 @@ static int gpencil_select_less_exec(bContext *C, wmOperator *UNUSED(op)) } 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; + CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps) && (gps->editcurve->flag & GP_CURVE_SELECT)) { + bGPDcurve *gpc = gps->editcurve; + int tot_selected = 0, num_deselected = 0; - 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; + bool prev_sel = false; + int i; + for (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) { + /* shrink if previous wasn't selected */ + if (prev_sel == false) { + gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt); + changed = true; + num_deselected++; } + prev_sel = true; + tot_selected++; + } + 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; + /* 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 = gpc->tot_curve_points - 1; i > 0; i--) { + bGPDcurve_point *gpc_pt = &gpc->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; + num_deselected++; } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; } } + + /* Deselect curve if all points are deselected. */ + if (tot_selected - num_deselected == 0) { + gpc->flag &= ~GP_CURVE_SELECT; + } } - 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; + else if (gps->flag & GP_STROKE_SELECT) { + bGPDspoint *pt; + int i, tot_selected = 0, num_deselected = 0; + bool prev_sel; - /* 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; + /* 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; + num_deselected++; } + prev_sel = true; + tot_selected++; + } + 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; + /* 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; + num_deselected++; } + prev_sel = true; + } + else { + /* mark previous as being unselected - and hence, is trigger for shrinking */ + prev_sel = false; } } + + /* Deselect curve if all points are deselected. */ + if (tot_selected - num_deselected == 0) { + gps->flag &= ~GP_STROKE_SELECT; + } } - CTX_DATA_END; } + CTX_DATA_END; if (changed) { /* updates */ @@ -1261,8 +1214,7 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *gpd, rcti *rect, const float diff_mat[4][4], const int selectmode, - const float scale, - const bool is_curve_edit) + const float scale) { bGPDspoint *pt = NULL; int x0 = 0, y0 = 0; @@ -1328,23 +1280,13 @@ static bool gpencil_stroke_do_circle_sel(bGPdata *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(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(gpd, gps_active); return changed; } -static bool gpencil_do_curve_circle_sel(bContext *C, +static bool gpencil_curve_do_circle_sel(bContext *C, bGPDstroke *gps, bGPDcurve *gpc, const int mx, @@ -1366,6 +1308,9 @@ static bool gpencil_do_curve_circle_sel(bContext *C, for (int i = 0; i < gpc->tot_curve_points; i++) { bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; BezTriple *bezt = &gpc_pt->bezt; + bGPDcurve_point *gpc_active_pt = (gpc_pt->runtime.gpc_pt_orig) ? gpc_pt->runtime.gpc_pt_orig : + gpc_pt; + BezTriple *bezt_active = &gpc_active_pt->bezt; if (bezt->hide == 1) { continue; @@ -1403,13 +1348,13 @@ static bool gpencil_do_curve_circle_sel(bContext *C, hit = true; /* change selection */ if (select) { - gpc_pt->flag |= GP_CURVE_POINT_SELECT; - BEZT_SEL_IDX(bezt, j); + gpc_active_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(bezt_active, j); } else { - BEZT_DESEL_IDX(bezt, j); - if (!BEZT_ISSEL_ANY(bezt)) { - gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_IDX(bezt_active, j); + if (!BEZT_ISSEL_ANY(bezt_active)) { + gpc_active_pt->flag &= ~GP_CURVE_POINT_SELECT; } } } @@ -1420,20 +1365,31 @@ static bool gpencil_do_curve_circle_sel(bContext *C, 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; + bGPDcurve_point *gpc_active_pt = (gpc_pt->runtime.gpc_pt_orig) ? + gpc_pt->runtime.gpc_pt_orig : + gpc_pt; + BezTriple *bezt_active = &gpc_active_pt->bezt; if (select) { - gpc_pt->flag |= GP_CURVE_POINT_SELECT; - BEZT_SEL_ALL(bezt); + gpc_active_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt_active); } else { - gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; - BEZT_DESEL_ALL(bezt); + gpc_active_pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt_active); } } - } - BKE_gpencil_curve_sync_selection(gpd, gps); + if (select) { + gpc->flag |= GP_CURVE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + } + } + else { + BKE_gpencil_curve_sync_selection(gpd, gps); + } return hit; } @@ -1443,7 +1399,6 @@ 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) { @@ -1487,32 +1442,24 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) rect.xmax = mx + radius; rect.ymax = my + radius; - if (is_curve_edit) { - if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - ED_gpencil_select_curve_toggle_all(C, SEL_DESELECT); - changed = true; - } + GP_SpaceConversion gsc = {NULL}; + /* init space conversion stuff */ + gpencil_point_conversion_init(C, &gsc); - 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 (SEL_OP_USE_PRE_DESELECT(sel_op)) { + // ED_gpencil_select_curve_toggle_all(C, SEL_DESELECT); + ED_gpencil_select_toggle_all(C, SEL_DESELECT); + changed = true; } - 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) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + changed |= gpencil_curve_do_circle_sel( + C, gps, gpc, mx, my, radius, select, &rect, gpstroke_iter.diff_mat, selectmode); } - - /* find visible strokes, and select if hit */ - GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + else { changed |= gpencil_stroke_do_circle_sel(gpd, gpl, gps, @@ -1524,11 +1471,10 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) &rect, gpstroke_iter.diff_mat, selectmode, - scale, - is_curve_edit); + scale); } - GP_EVALUATED_STROKES_END(gpstroke_iter); } + GP_EVALUATED_STROKES_END(gpstroke_iter); /* updates */ if (changed) { @@ -1604,7 +1550,7 @@ static bool gpencil_stroke_fill_isect_rect(ARegion *region, int *pt2d = points2d[i]; int screen_co[2]; - gpencil_3d_point_to_screen_space(region, diff_mat, &pt->x, screen_co); + ED_gpencil_3d_point_to_screen_space(region, NULL, diff_mat, &pt->x, screen_co); DO_MINMAX2(screen_co, min, max); copy_v2_v2_int(pt2d, screen_co); @@ -1654,278 +1600,251 @@ static bool gpencil_stroke_fill_isect_rect(ARegion *region, static bool gpencil_generic_curve_select(bContext *C, Object *ob, + bGPdata *gpd, + bGPDstroke *gps, + bGPDcurve *gpc, + struct GP_EditableStrokes_Iter *gpstroke_iter, + GP_SpaceConversion *gsc, GPencilTestFn is_inside_fn, - rcti UNUSED(box), + rcti box, GP_SelectUserData *user_data, const bool strokemode, const eSelectOp sel_op) { - ARegion *region = CTX_wm_region(C); View3D *v3d = CTX_wm_view3d(C); - bGPdata *gpd = ob->data; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + 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; + 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; - 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; + bGPDcurve_point *gpc_pt_active = (gpc_pt->runtime.gpc_pt_orig) ? gpc_pt->runtime.gpc_pt_orig : + gpc_pt; + BezTriple *bezt_active = &gpc_pt_active->bezt; - if (bezt->hide == 1) { - continue; - } + if (bezt_active->hide == 1) { + continue; + } - const bool handles_visible = (handle_all || (handle_only_selected && - (gpc_pt->flag & GP_CURVE_POINT_SELECT))); + 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 (handles_visible) { + for (int j = 0; j < 3; j++) { + const bool is_select = BEZT_ISSEL_IDX(bezt_active, j); + bool is_inside = is_inside_fn( + gsc->region, gpstroke_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->f2 |= SELECT; + gpc_pt_active->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_IDX(bezt_active, j); any_select = true; } else { - gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; - bezt->f2 &= ~SELECT; + gpc_pt_active->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_IDX(bezt_active, j); } changed = true; hit = true; } else { if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; - bezt->f2 &= ~SELECT; + gpc_pt_active->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_IDX(bezt_active, j); } } } } } - - /* 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; + /* if the handles are not visible only check ctrl point (vec[1])*/ + else { + const bool is_select = bezt_active->f2; + bool is_inside = is_inside_fn(gsc->region, gpstroke_iter->diff_mat, bezt->vec[1], user_data); + if (strokemode) { + if (is_inside) { + hit = true; + any_select = true; + } } - - 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; - + 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_ALL(bezt); + gpc_pt_active->flag |= GP_CURVE_POINT_SELECT; + bezt_active->f2 |= SELECT; + any_select = true; } else { - gpc_pt->flag &= ~GP_CURVE_POINT_SELECT; - BEZT_DESEL_ALL(bezt); + gpc_pt_active->flag &= ~GP_CURVE_POINT_SELECT; + bezt_active->f2 &= ~SELECT; + } + changed = true; + hit = true; + } + else { + if (SEL_OP_USE_PRE_DESELECT(sel_op)) { + gpc_pt_active->flag &= ~GP_CURVE_POINT_SELECT; + bezt_active->f2 &= ~SELECT; } } + } + } + } + + if (!hit) { + /* check if we selected the inside of a filled curve */ + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps_active->mat_nr + 1); + if ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0) { + return changed; + } + int mval[2]; + mval[0] = (box.xmax + box.xmin) / 2; + mval[1] = (box.ymax + box.ymin) / 2; + + whole = ED_gpencil_stroke_point_is_inside(gps_active, gsc, mval, gpstroke_iter->diff_mat); + } + + /* 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]; + bGPDcurve_point *gpc_pt_active = (gpc_pt->runtime.gpc_pt_orig) ? + gpc_pt->runtime.gpc_pt_orig : + gpc_pt; + BezTriple *bezt_active = &gpc_pt_active->bezt; if (sel_op_result) { - gpc->flag |= GP_CURVE_SELECT; + gpc_pt_active->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(bezt_active); } else { - gpc->flag &= ~GP_CURVE_SELECT; + gpc_pt_active->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(bezt_active); } - changed = true; } - } - BKE_gpencil_curve_sync_selection(gpd, gps); + if (sel_op_result) { + gpc->flag |= GP_CURVE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + } + changed = true; + } + } + else { + BKE_gpencil_curve_sync_selection(gpd, gps_active); } - GP_EDITABLE_CURVES_END(gps_iter); return changed; } -static bool gpencil_generic_stroke_select(bContext *C, - Object *ob, +static bool gpencil_generic_stroke_select(Object *ob, bGPdata *gpd, + bGPDlayer *gpl, + bGPDstroke *gps, + struct GP_EditableStrokes_Iter *gpstroke_iter, + GP_SpaceConversion *gsc, 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) + const float scale) { - GP_SpaceConversion gsc = {NULL}; bool changed = false; - /* init space conversion stuff */ - gpencil_point_conversion_init(C, &gsc); - - /* 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; - int i; - - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - pt->flag &= ~GP_SPOINT_SELECT; - } - - gps->flag &= ~GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_reset(gps); - } - CTX_DATA_END; - - changed = true; - } - - /* select/deselect points */ - GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; - bool whole = false; + bool whole = false; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; - bGPDspoint *pt; - int i; - bool hit = false; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + bGPDspoint *pt; + int i; + bool hit = false; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; - /* convert point coords to screenspace */ - 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); - if (sel_op_result != -1) { - SET_FLAG_FROM_TEST(pt_active->flag, sel_op_result, GP_SPOINT_SELECT); - changed = true; - hit = true; + /* convert point coords to screenspace */ + 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); + if (sel_op_result != -1) { + SET_FLAG_FROM_TEST(pt_active->flag, sel_op_result, GP_SPOINT_SELECT); + changed = true; + hit = true; - /* Expand selection to segment. */ - if (segmentmode) { - bool hit_select = (bool)(pt_active->flag & GP_SPOINT_SELECT); - float r_hita[3], r_hitb[3]; - ED_gpencil_select_stroke_segment( - gpd, gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); - } - } - } - else { - if (is_inside) { - hit = true; - break; + /* Expand selection to segment. */ + if (segmentmode) { + bool hit_select = (bool)(pt_active->flag & GP_SPOINT_SELECT); + float r_hita[3], r_hitb[3]; + ED_gpencil_select_stroke_segment( + gpd, gpl, gps_active, pt_active, hit_select, false, scale, r_hita, r_hitb); } } } - - /* If nothing hit, check if the mouse is inside a filled stroke using the center or - * Box or lasso area. */ - if (!hit) { - /* Only check filled strokes. */ - MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1); - if ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0) { - continue; + else { + if (is_inside) { + hit = true; + break; } - int mval[2]; - mval[0] = (box.xmax + box.xmin) / 2; - mval[1] = (box.ymax + box.ymin) / 2; + } + } - whole = ED_gpencil_stroke_point_is_inside(gps, &gsc, mval, gpstroke_iter.diff_mat); + /* If nothing hit, check if the mouse is inside a filled stroke using the center or + * Box or lasso area. */ + if (!hit) { + /* Only check filled strokes. */ + MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps_active->mat_nr + 1); + if ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0) { + return changed; } + int mval[2]; + mval[0] = (box.xmax + box.xmin) / 2; + mval[1] = (box.ymax + box.ymin) / 2; - /* if stroke mode expand selection. */ - if ((strokemode) || (whole)) { - const bool is_select = BKE_gpencil_stroke_select_check(gps_active) || whole; - const bool is_inside = hit || whole; - const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); - if (sel_op_result != -1) { - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + whole = ED_gpencil_stroke_point_is_inside(gps_active, gsc, mval, gpstroke_iter->diff_mat); + } - if (sel_op_result) { - pt_active->flag |= GP_SPOINT_SELECT; - } - else { - pt_active->flag &= ~GP_SPOINT_SELECT; - } + /* if stroke mode expand selection. */ + if ((strokemode) || (whole)) { + const bool is_select = BKE_gpencil_stroke_select_check(gps_active) || whole; + const bool is_inside = hit || whole; + const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); + if (sel_op_result != -1) { + for (i = 0, pt = gps_active->points; i < gps_active->totpoints; i++, pt++) { + bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + + if (sel_op_result) { + pt_active->flag |= GP_SPOINT_SELECT; + } + else { + pt_active->flag &= ~GP_SPOINT_SELECT; } - changed = true; } - } - - /* 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(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(gpd, gps_active); } - GP_EVALUATED_STROKES_END(gpstroke_iter); + + /* Ensure that stroke selection is in sync with its points */ + BKE_gpencil_stroke_sync_selection(gpd, gps_active); return changed; } @@ -1940,7 +1859,6 @@ static int gpencil_generic_select_exec(bContext *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) { @@ -1961,38 +1879,69 @@ static int gpencil_generic_select_exec(bContext *C, 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); + bool changed = false; + GP_SpaceConversion gsc = {NULL}; + /* init space conversion stuff */ + gpencil_point_conversion_init(C, &gsc); + + /* 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; + + deselect_all_selected(C); + changed = true; } - 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); + GP_EVALUATED_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpencil_generic_curve_select(C, + ob, + gpd, + gps, + gpc, + &gpstroke_iter, + &gsc, + is_inside_fn, + box, + user_data, + strokemode, + sel_op)) { + changed = true; + } + } + else { + if (gpencil_generic_stroke_select(ob, + gpd, + gpl, + gps, + &gpstroke_iter, + &gsc, + is_inside_fn, + box, + user_data, + strokemode, + segmentmode, + sel_op, + scale)) { + changed = true; + } + } } + GP_EVALUATED_STROKES_END(gpstroke_iter); /* if paint mode,delete selected points */ if (GPENCIL_PAINT_MODE(gpd)) { gpencil_delete_selected_point_wrap(C); - changed = true; DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + changed = true; } /* updates */ @@ -2020,7 +1969,7 @@ static bool gpencil_test_box(ARegion *region, GP_SelectUserData *user_data) { int co[2] = {0}; - if (gpencil_3d_point_to_screen_space(region, diff_mat, pt, co)) { + if (ED_gpencil_3d_point_to_screen_space(region, NULL, diff_mat, pt, co)) { return BLI_rcti_isect_pt(&user_data->rect, co[0], co[1]); } return false; @@ -2069,7 +2018,7 @@ static bool gpencil_test_lasso(ARegion *region, GP_SelectUserData *user_data) { int co[2] = {0}; - if (gpencil_3d_point_to_screen_space(region, diff_mat, pt, co)) { + if (ED_gpencil_3d_point_to_screen_space(region, NULL, 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( @@ -2125,56 +2074,93 @@ void GPENCIL_OT_select_lasso(wmOperatorType *ot) /** \name Mouse Pick Select Operator * \{ */ -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) +static bool gpencil_select_curve_point_closest(bContext *C, + bGPDcurve *gpc, + struct GP_EditableStrokes_Iter *gps_iter, + const int mval[2], + const int radius_squared, + bGPDcurve_point **r_pt, + int *handle_idx, + int *hit_distance) { + bool hit = false; ARegion *region = CTX_wm_region(C); View3D *v3d = CTX_wm_view3d(C); const bool only_selected = (v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); - int hit_distance = radius_squared; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + BezTriple *bezt = &gpc_pt->bezt; - 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; + bGPDcurve_point *gpc_pt_active = (gpc_pt->runtime.gpc_pt_orig) ? gpc_pt->runtime.gpc_pt_orig : + gpc_pt; + BezTriple *bezt_active = &gpc_pt_active->bezt; - if (bezt->hide == 1) { - continue; + if (bezt_active->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++) { + int screen_co[2]; + if (ED_gpencil_3d_point_to_screen_space( + region, NULL, 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_pt = gpc_pt_active; + *handle_idx = j; + *hit_distance = pt_distance; + hit = true; + } } + } + } - const bool handles_visible = (v3d->overlay.handle_display != CURVE_HANDLE_NONE) && - (!only_selected || BEZT_ISSEL_ANY(bezt)); + return hit; +} + +static bool gpencil_select_stroke_point_closest(bGPDstroke *gps, + struct GP_EditableStrokes_Iter *gps_iter, + GP_SpaceConversion *gsc, + const int mval[2], + const int radius_squared, + bGPDspoint **r_pt, + int *hit_distance) +{ + bool hit = false; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; - /* If the handles are not visible only check control point (vec[1]). */ - int from = (!handles_visible) ? 1 : 0; - int to = (!handles_visible) ? 2 : 3; + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + bGPDspoint *pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt; + int xy[2]; - 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); + bGPDspoint pt2; + gpencil_point_to_parent_space(pt, gps_iter->diff_mat, &pt2); + gpencil_point_to_xy(gsc, gps_active, &pt2, &xy[0], &xy[1]); - 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; - } - } + /* 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 && pt_distance < *hit_distance) { + /* only use this point if it is a better match than the current hit - T44685 */ + *r_pt = pt_active; + *hit_distance = pt_distance; + hit = true; } } } - GP_EDITABLE_CURVES_END(gps_iter); + + return hit; } static int gpencil_select_exec(bContext *C, wmOperator *op) @@ -2184,7 +2170,6 @@ 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; @@ -2201,14 +2186,12 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) /* 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_curve_handle_idx = 0; int hit_distance = radius_squared; /* sanity checks */ @@ -2230,62 +2213,45 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) whole = (bool)(ts->gpencil_selectmode_edit == GP_SELECTMODE_STROKE); } - if (is_curve_edit) { - gpencil_select_curve_point(C, - mval, - radius_squared, - &hit_layer, - &hit_stroke, - &hit_curve, - &hit_curve_point, - &hit_curve_handle); - } - - 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; + GP_SpaceConversion gsc = {NULL}; + /* init space conversion stuff */ + gpencil_point_conversion_init(C, &gsc); - /* 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_active, &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; - } - } - } + /* First Pass: Find 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; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpencil_select_curve_point_closest(C, + gpc, + &gpstroke_iter, + mval, + radius_squared, + &hit_curve_point, + &hit_curve_handle_idx, + &hit_distance)) { + hit_layer = gpl; + hit_stroke = gps_active; + hit_point = &gps_active->points[hit_curve_point->point_index]; + hit_curve = gps_active->editcurve; + } + } + else { + if (gpencil_select_stroke_point_closest( + gps, &gpstroke_iter, &gsc, mval, radius_squared, &hit_point, &hit_distance)) { + hit_layer = gpl; + hit_stroke = gps_active; } } - GP_EVALUATED_STROKES_END(gpstroke_iter); } + GP_EVALUATED_STROKES_END(gpstroke_iter); /* Abort if nothing hit... */ - if (!hit_curve && !hit_curve_point && !hit_point && !hit_stroke) { - + if ((hit_curve == NULL && hit_curve_point == NULL) && + (hit_point == NULL && hit_stroke == NULL)) { if (deselect_all) { /* since left mouse select change, deselect all if click outside any hit */ deselect_all_selected(C); - /* copy on write tag is needed, or else no refresh happens */ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); DEG_id_tag_update(&gpd->id, ID_RECALC_COPY_ON_WRITE); @@ -2298,23 +2264,17 @@ 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) { if (hit_curve_point != NULL) { BezTriple *bezt = &hit_curve_point->bezt; - if ((bezt->f1 & SELECT) && (hit_curve_handle == 0)) { + if ((bezt->f1 & SELECT) && (hit_curve_handle_idx == 0)) { deselect = true; } - if ((bezt->f2 & SELECT) && (hit_curve_handle == 1)) { + if ((bezt->f2 & SELECT) && (hit_curve_handle_idx == 1)) { deselect = true; } - if ((bezt->f3 & SELECT) && (hit_curve_handle == 2)) { + if ((bezt->f3 & SELECT) && (hit_curve_handle_idx == 2)) { deselect = true; } } @@ -2330,13 +2290,6 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) /* Perform selection operations... */ if (whole) { - /* 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(gpd, hit_stroke, hit_curve, deselect); @@ -2371,9 +2324,8 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) if (deselect == false) { if (hit_curve_point != NULL) { hit_curve_point->flag |= GP_CURVE_POINT_SELECT; - BEZT_SEL_IDX(&hit_curve_point->bezt, hit_curve_handle); + BEZT_SEL_IDX(&hit_curve_point->bezt, hit_curve_handle_idx); hit_curve->flag |= GP_CURVE_SELECT; - hit_stroke->flag |= GP_STROKE_SELECT; BKE_gpencil_stroke_select_index_set(gpd, hit_stroke); } else { @@ -2404,7 +2356,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) } else { if (hit_curve_point != NULL) { - BEZT_DESEL_IDX(&hit_curve_point->bezt, hit_curve_handle); + BEZT_DESEL_IDX(&hit_curve_point->bezt, hit_curve_handle_idx); if (!BEZT_ISSEL_ANY(&hit_curve_point->bezt)) { hit_curve_point->flag &= ~GP_CURVE_POINT_SELECT; } @@ -2487,8 +2439,12 @@ void GPENCIL_OT_select(wmOperatorType *ot) prop = RNA_def_boolean(ot->srna, "use_shift_extend", false, "Extend", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select by Vertex Color + * \{ */ -/* Select by Vertex Color. */ /* Helper to create a hash of colors. */ static void gpencil_selected_hue_table(bContext *C, Object *ob, @@ -2508,22 +2464,43 @@ static void gpencil_selected_hue_table(bContext *C, if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { continue; } - if ((gps->flag & GP_STROKE_SELECT) == 0) { - continue; - } /* Read all points to get all colors selected. */ - bGPDspoint *pt; - int i; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (((pt->flag & GP_SPOINT_SELECT) == 0) || (pt->vert_color[3] == 0.0f)) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if ((gpc->flag & GP_CURVE_SELECT) == 0) { + continue; + } + + 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) == 0) || (gpc_pt->vert_color[3] == 0.0f)) { + continue; + } + /* Round Hue value. */ + rgb_to_hsv_compat_v(gpc_pt->vert_color, hsv); + uint key = truncf(hsv[0] * range); + if (!BLI_ghash_haskey(hue_table, POINTER_FROM_INT(key))) { + BLI_ghash_insert(hue_table, POINTER_FROM_INT(key), POINTER_FROM_INT(key)); + } + } + } + else { + if ((gps->flag & GP_STROKE_SELECT) == 0) { continue; } - /* Round Hue value. */ - rgb_to_hsv_compat_v(pt->vert_color, hsv); - uint key = truncf(hsv[0] * range); - if (!BLI_ghash_haskey(hue_table, POINTER_FROM_INT(key))) { - BLI_ghash_insert(hue_table, POINTER_FROM_INT(key), POINTER_FROM_INT(key)); + + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + if (((pt->flag & GP_SPOINT_SELECT) == 0) || (pt->vert_color[3] == 0.0f)) { + continue; + } + /* Round Hue value. */ + rgb_to_hsv_compat_v(pt->vert_color, hsv); + uint key = truncf(hsv[0] * range); + if (!BLI_ghash_haskey(hue_table, POINTER_FROM_INT(key))) { + BLI_ghash_insert(hue_table, POINTER_FROM_INT(key), POINTER_FROM_INT(key)); + } } } } @@ -2577,39 +2554,77 @@ static int gpencil_select_vertex_color_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + deselect_all_selected(C); + /* Select any visible stroke that uses any of these colors. */ CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - bGPDspoint *pt; - int i; - bool gps_selected = false; - /* Check all stroke points. */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->vert_color[3] == 0.0f) { - continue; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + bool gpc_selected = false; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + if (gpc_pt->vert_color[3] == 0.0f) { + continue; + } + + /* Only check Hue to get value and saturation full ranges. */ + float hsv[3]; + /* Round Hue value. */ + rgb_to_hsv_compat_v(gpc_pt->vert_color, hsv); + uint key = truncf(hsv[0] * range); + + if (BLI_ghash_haskey(hue_table, POINTER_FROM_INT(key))) { + gpc_pt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&gpc_pt->bezt); + gpc_selected = true; + changed = true; + } } - /* Only check Hue to get value and saturation full ranges. */ - float hsv[3]; - /* Round Hue value. */ - rgb_to_hsv_compat_v(pt->vert_color, hsv); - uint key = truncf(hsv[0] * range); + if (gpc_selected) { + gpc->flag |= GP_CURVE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps); - if (BLI_ghash_haskey(hue_table, POINTER_FROM_INT(key))) { - pt->flag |= GP_SPOINT_SELECT; - gps_selected = true; + /* Extend selection. */ + if (selectmode == GP_SELECTMODE_STROKE) { + select_all_curve_points(gpd, gps, gpc, false); + } } } + else { + bGPDspoint *pt; + int i; + bool gps_selected = false; + /* Check all stroke points. */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->vert_color[3] == 0.0f) { + continue; + } - if (gps_selected) { - gps->flag |= GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_set(gpd, gps); + /* Only check Hue to get value and saturation full ranges. */ + float hsv[3]; + /* Round Hue value. */ + rgb_to_hsv_compat_v(pt->vert_color, hsv); + uint key = truncf(hsv[0] * range); - /* Extend stroke selection. */ - if (selectmode == GP_SELECTMODE_STROKE) { - bGPDspoint *pt1 = NULL; + if (BLI_ghash_haskey(hue_table, POINTER_FROM_INT(key))) { + pt->flag |= GP_SPOINT_SELECT; + gps_selected = true; + changed = true; + } + } - for (i = 0, pt1 = gps->points; i < gps->totpoints; i++, pt1++) { - pt1->flag |= GP_SPOINT_SELECT; + if (gps_selected) { + gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps); + + /* Extend stroke selection. */ + if (selectmode == GP_SELECTMODE_STROKE) { + bGPDspoint *pt1 = NULL; + + for (i = 0, pt1 = gps->points; i < gps->totpoints; i++, pt1++) { + pt1->flag |= GP_SPOINT_SELECT; + } } } } diff --git a/source/blender/editors/gpencil/gpencil_trace_utils.c b/source/blender/editors/gpencil/gpencil_trace_utils.c index 970afc3ff6b..35d1c69d709 100644 --- a/source/blender/editors/gpencil/gpencil_trace_utils.c +++ b/source/blender/editors/gpencil/gpencil_trace_utils.c @@ -365,7 +365,7 @@ void ED_gpencil_trace_data_to_strokes(Main *bmain, BKE_gpencil_stroke_sample(gpd, gps, sample, false); } else { - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } else { diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index e6488cfe454..f9659a4061b 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -713,9 +713,6 @@ void gpencil_point_to_parent_space(const bGPDspoint *pt, */ void gpencil_apply_parent(Depsgraph *depsgraph, Object *obact, bGPDlayer *gpl, bGPDstroke *gps) { - bGPDspoint *pt; - int i; - /* undo matrix */ float diff_mat[4][4]; float inverse_diff_mat[4][4]; @@ -724,8 +721,20 @@ void gpencil_apply_parent(Depsgraph *depsgraph, Object *obact, bGPDlayer *gpl, b BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat); invert_m4_m4(inverse_diff_mat, diff_mat); - for (i = 0; i < gps->totpoints; i++) { - pt = &gps->points[i]; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + for (int j = 0; j < 3; j++) { + mul_v3_m4v3(fpt, inverse_diff_mat, bezt->vec[j]); + copy_v3_v3(bezt->vec[j], fpt); + } + } + } + + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; mul_v3_m4v3(fpt, inverse_diff_mat, &pt->x); copy_v3_v3(&pt->x, fpt); } @@ -1497,7 +1506,7 @@ void gpencil_subdivide_stroke(bGPdata *gpd, bGPDstroke *gps, const int subdivide MEM_SAFE_FREE(temp_points); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* Reset parent matrix for all layers. */ @@ -1622,7 +1631,21 @@ void ED_gpencil_vgroup_assign(bContext *C, Object *ob, float weight) continue; } - if (gps->flag & GP_STROKE_SELECT) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps) && (gps->editcurve->flag & GP_CURVE_SELECT)) { + BKE_gpencil_dvert_ensure(gps); + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + MDeformVert *dvert = &gpc->dvert[i]; + if (cpt->flag & GP_CURVE_POINT_SELECT) { + MDeformWeight *dw = BKE_defvert_ensure_index(dvert, def_nr); + if (dw != NULL) { + dw->weight = weight; + } + } + } + } + else if (gps->flag & GP_STROKE_SELECT) { /* verify the weight array is created */ BKE_gpencil_dvert_ensure(gps); @@ -1676,17 +1699,36 @@ void ED_gpencil_vgroup_remove(bContext *C, Object *ob) continue; } - for (int i = 0; i < gps->totpoints; i++) { - bGPDspoint *pt = &gps->points[i]; + if (GPENCIL_STROKE_TYPE_BEZIER(gps) && (gps->editcurve->flag & GP_CURVE_SELECT)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc->dvert == NULL) { + continue; + } + + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + MDeformVert *dvert = &gpc->dvert[i]; + if ((cpt->flag & GP_CURVE_POINT_SELECT) && (dvert->totweight > 0)) { + MDeformWeight *dw = BKE_defvert_find_index(dvert, def_nr); + if (dw != NULL) { + BKE_defvert_remove_group(dvert, dw); + } + } + } + } + else if ((gps->flag & GP_STROKE_SELECT)) { if (gps->dvert == NULL) { continue; } - MDeformVert *dvert = &gps->dvert[i]; - if ((pt->flag & GP_SPOINT_SELECT) && (dvert->totweight > 0)) { - MDeformWeight *dw = BKE_defvert_find_index(dvert, def_nr); - if (dw != NULL) { - BKE_defvert_remove_group(dvert, dw); + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + MDeformVert *dvert = &gps->dvert[i]; + if ((pt->flag & GP_SPOINT_SELECT) && (dvert->totweight > 0)) { + MDeformWeight *dw = BKE_defvert_find_index(dvert, def_nr); + if (dw != NULL) { + BKE_defvert_remove_group(dvert, dw); + } } } } @@ -1729,20 +1771,39 @@ void ED_gpencil_vgroup_select(bContext *C, Object *ob) continue; } - for (int i = 0; i < gps->totpoints; i++) { - bGPDspoint *pt = &gps->points[i]; - if (gps->dvert == NULL) { + bool selected = false; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc->dvert == NULL) { continue; } - MDeformVert *dvert = &gps->dvert[i]; - if (BKE_defvert_find_index(dvert, def_nr) != NULL) { - pt->flag |= GP_SPOINT_SELECT; - gps->flag |= GP_STROKE_SELECT; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + MDeformVert *dvert = &gpc->dvert[i]; + if (BKE_defvert_find_index(dvert, def_nr) != NULL) { + cpt->flag |= GP_CURVE_POINT_SELECT; + BEZT_SEL_ALL(&cpt->bezt); + gpc->flag |= GP_CURVE_SELECT; + selected = true; + } + } + } + else if (gps->dvert != NULL) { + + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + MDeformVert *dvert = &gps->dvert[i]; + + if (BKE_defvert_find_index(dvert, def_nr) != NULL) { + pt->flag |= GP_SPOINT_SELECT; + gps->flag |= GP_STROKE_SELECT; + selected = true; + } } } - if (gps->flag & GP_STROKE_SELECT) { + if (selected) { BKE_gpencil_stroke_select_index_set(gpd, gps); } } @@ -1783,17 +1844,57 @@ void ED_gpencil_vgroup_deselect(bContext *C, Object *ob) if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; } + bool deselected = false; - for (int i = 0; i < gps->totpoints; i++) { - bGPDspoint *pt = &gps->points[i]; - if (gps->dvert == NULL) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc->dvert == NULL) { continue; } - MDeformVert *dvert = &gps->dvert[i]; + int desel_count = 0; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + MDeformVert *dvert = &gpc->dvert[i]; + if (BKE_defvert_find_index(dvert, def_nr) != NULL) { + cpt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&cpt->bezt); + desel_count++; + } - if (BKE_defvert_find_index(dvert, def_nr) != NULL) { - pt->flag &= ~GP_SPOINT_SELECT; + if ((cpt->flag & GP_CURVE_POINT_SELECT) == 0) { + desel_count++; + } + } + + if (desel_count == gpc->tot_curve_points) { + deselected = true; + gpc->flag &= ~GP_CURVE_SELECT; + } + } + else if (gps->dvert != NULL) { + int desel_count = 0; + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + MDeformVert *dvert = &gps->dvert[i]; + + if (BKE_defvert_find_index(dvert, def_nr) != NULL) { + pt->flag &= ~GP_SPOINT_SELECT; + desel_count++; + } + + if ((pt->flag & GP_SPOINT_SELECT) == 0) { + desel_count++; + } } + + if (desel_count == gps->totpoints) { + deselected = true; + gps->flag &= ~GP_STROKE_SELECT; + } + } + + if (deselected) { + BKE_gpencil_stroke_select_index_reset(gps); } } } @@ -2396,7 +2497,7 @@ static void gpencil_insert_point(bGPdata *gpd, i2++; } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); MEM_SAFE_FREE(temp_points); } @@ -2628,9 +2729,18 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) action = SEL_SELECT; CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - if (gps->flag & GP_STROKE_SELECT) { - action = SEL_DESELECT; - break; /* XXX: this only gets out of the inner loop. */ + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (gpc->flag & GP_CURVE_SELECT) { + action = SEL_DESELECT; + break; + } + } + else { + if (gps->flag & GP_STROKE_SELECT) { + action = SEL_DESELECT; + break; /* XXX: this only gets out of the inner loop. */ + } } } CTX_DATA_END; @@ -2654,18 +2764,25 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) /* deselect all strokes on all frames */ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - bGPDstroke *gps; - - for (gps = gpf->strokes.first; gps; gps = gps->next) { - bGPDspoint *pt; - int i; - + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { /* only edit strokes that are valid in this view... */ - if (ED_gpencil_stroke_can_use(C, gps)) { - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (!ED_gpencil_stroke_can_use(C, gps)) + continue; + + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + for (uint32_t i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + pt->flag &= ~GP_CURVE_POINT_SELECT; + BEZT_DESEL_ALL(&pt->bezt); + } + gpc->flag &= ~GP_CURVE_SELECT; + } + else { + for (uint32_t i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; pt->flag &= ~GP_SPOINT_SELECT; } - gps->flag &= ~GP_STROKE_SELECT; BKE_gpencil_stroke_select_index_reset(gps); } @@ -2677,39 +2794,68 @@ void ED_gpencil_select_toggle_all(bContext *C, int action) else { /* select or deselect all strokes */ CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) { - bGPDspoint *pt; - int i; bool selected = false; - /* Change selection status of all points, then make the stroke match */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - switch (action) { - case SEL_SELECT: - pt->flag |= GP_SPOINT_SELECT; - break; -#if 0 - case SEL_DESELECT: - pt->flag &= ~GP_SPOINT_SELECT; - break; -#endif - case SEL_INVERT: - pt->flag ^= GP_SPOINT_SELECT; - break; - } + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + + for (uint32_t 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 (pt->flag & GP_SPOINT_SELECT) { - selected = true; + if (gpc_pt->flag & GP_CURVE_POINT_SELECT) { + selected = true; + } } - } - /* Change status of stroke */ - if (selected) { - gps->flag |= GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_set(gpd, gps); + if (selected) { + gpc->flag |= GP_CURVE_SELECT; + } + else { + gpc->flag &= ~GP_CURVE_SELECT; + } } else { - gps->flag &= ~GP_STROKE_SELECT; - BKE_gpencil_stroke_select_index_reset(gps); + 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++) { + switch (action) { + case SEL_SELECT: + pt->flag |= GP_SPOINT_SELECT; + break; + case SEL_INVERT: + pt->flag ^= GP_SPOINT_SELECT; + break; + } + + if (pt->flag & GP_SPOINT_SELECT) { + selected = true; + } + } + + /* Change status of stroke */ + if (selected) { + gps->flag |= GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_set(gpd, gps); + } + else { + gps->flag &= ~GP_STROKE_SELECT; + BKE_gpencil_stroke_select_index_reset(gps); + } } } CTX_DATA_END; @@ -2758,9 +2904,7 @@ void ED_gpencil_select_curve_toggle_all(bContext *C, int action) /* 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); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } bGPDcurve *gpc = gps->editcurve; @@ -3388,3 +3532,34 @@ void ED_gpencil_stroke_close_by_distance(bGPDstroke *gps, const float threshold) BKE_gpencil_stroke_close(gps); } } + +/* Convert 3D point to 2D point in screen space. */ +bool ED_gpencil_3d_point_to_screen_space(struct ARegion *region, + const struct rcti *rect, + 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])) { + if (rect == NULL) { + copy_v2_v2_int(r_co, screen_co); + return true; + } + else { + if (BLI_rcti_isect_pt(rect, 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; +} diff --git a/source/blender/editors/gpencil/gpencil_uv.c b/source/blender/editors/gpencil/gpencil_uv.c index 6bd0540a9d4..64182115b20 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); 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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); changed = true; } } diff --git a/source/blender/editors/gpencil/gpencil_vertex_ops.c b/source/blender/editors/gpencil/gpencil_vertex_ops.c index 402bccce2f7..c68d81f8a6b 100644 --- a/source/blender/editors/gpencil/gpencil_vertex_ops.c +++ b/source/blender/editors/gpencil/gpencil_vertex_ops.c @@ -59,7 +59,7 @@ static const EnumPropertyItem gpencil_modesEnumPropertyItem_mode[] = { }; /* Helper: Check if any stroke is selected. */ -static bool is_any_stroke_selected(bContext *C, const bool is_multiedit, const bool is_curve_edit) +static bool is_any_stroke_selected(bContext *C, const bool is_multiedit) { bool is_selected = false; @@ -82,10 +82,7 @@ static bool is_any_stroke_selected(bContext *C, const bool is_multiedit, const b continue; } - if (is_curve_edit) { - if (gps->editcurve == NULL) { - continue; - } + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { bGPDcurve *gpc = gps->editcurve; if (gpc->flag & GP_CURVE_SELECT) { is_selected = true; @@ -136,7 +133,7 @@ static int gpencil_vertexpaint_brightness_contrast_exec(bContext *C, wmOperator bGPdata *gpd = (bGPdata *)ob->data; const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode"); - const bool any_selected = is_any_stroke_selected(C, is_multiedit, false); + const bool any_selected = is_any_stroke_selected(C, is_multiedit); float gain, offset; { @@ -254,7 +251,7 @@ static int gpencil_vertexpaint_hsv_exec(bContext *C, wmOperator *op) const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode"); - const bool any_selected = is_any_stroke_selected(C, is_multiedit, false); + const bool any_selected = is_any_stroke_selected(C, is_multiedit); float hue = RNA_float_get(op->ptr, "h"); float sat = RNA_float_get(op->ptr, "s"); float val = RNA_float_get(op->ptr, "v"); @@ -373,7 +370,7 @@ static int gpencil_vertexpaint_invert_exec(bContext *C, wmOperator *op) const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode"); - const bool any_selected = is_any_stroke_selected(C, is_multiedit, false); + const bool any_selected = is_any_stroke_selected(C, is_multiedit); bool changed = false; CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { @@ -463,7 +460,7 @@ static int gpencil_vertexpaint_levels_exec(bContext *C, wmOperator *op) const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode"); - const bool any_selected = is_any_stroke_selected(C, is_multiedit, false); + const bool any_selected = is_any_stroke_selected(C, is_multiedit); float gain = RNA_float_get(op->ptr, "gain"); float offset = RNA_float_get(op->ptr, "offset"); @@ -563,7 +560,7 @@ static int gpencil_vertexpaint_set_exec(bContext *C, wmOperator *op) const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode"); - const bool any_selected = is_any_stroke_selected(C, is_multiedit, false); + const bool any_selected = is_any_stroke_selected(C, is_multiedit); float factor = RNA_float_get(op->ptr, "factor"); bool changed = false; @@ -1060,6 +1057,14 @@ static void gpencil_reset_vertex(bGPDstroke *gps, eGp_Vertex_Mode mode) } if (mode != GPPAINT_MODE_FILL) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *gpc_pt = &gpc->curve_points[i]; + zero_v4(gpc_pt->vert_color); + } + } + bGPDspoint *pt; for (int i = 0; i < gps->totpoints; i++) { pt = &gps->points[i]; @@ -1072,12 +1077,11 @@ static int gpencil_stroke_reset_vertex_color_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); const eGp_Vertex_Mode mode = RNA_enum_get(op->ptr, "mode"); /* First need to check if there are something selected. If not, apply to all strokes. */ - const bool any_selected = is_any_stroke_selected(C, is_multiedit, is_curve_edit); + const bool any_selected = is_any_stroke_selected(C, is_multiedit); /* Reset Vertex colors. */ bool changed = false; @@ -1096,10 +1100,7 @@ static int gpencil_stroke_reset_vertex_color_exec(bContext *C, wmOperator *op) continue; } - if (is_curve_edit) { - if (gps->editcurve == NULL) { - continue; - } + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { bGPDcurve *gpc = gps->editcurve; if ((!any_selected) || (gpc->flag & GP_CURVE_SELECT)) { gpencil_reset_vertex(gps, mode); diff --git a/source/blender/editors/gpencil/gpencil_vertex_paint.c b/source/blender/editors/gpencil/gpencil_vertex_paint.c index 16605b6c634..7694dc1ce91 100644 --- a/source/blender/editors/gpencil/gpencil_vertex_paint.c +++ b/source/blender/editors/gpencil/gpencil_vertex_paint.c @@ -37,6 +37,7 @@ #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_material.h" #include "BKE_report.h" @@ -435,20 +436,40 @@ static bool brush_tint_apply(tGP_BrushVertexpaintData *gso, /* Apply color to Stroke point. */ if (GPENCIL_TINT_VERTEX_COLOR_STROKE(brush) && (pt_index > -1)) { - bGPDspoint *pt = &gps->points[pt_index]; - if (brush_invert_check(gso)) { - pt->vert_color[3] -= inf; - CLAMP_MIN(pt->vert_color[3], 0.0f); + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve_point *pt = &gps->editcurve->curve_points[pt_index]; + if (brush_invert_check(gso)) { + pt->vert_color[3] -= inf; + CLAMP_MIN(pt->vert_color[3], 0.0f); + } + else { + /* Premult. */ + mul_v3_fl(pt->vert_color, pt->vert_color[3]); + /* "Alpha over" blending. */ + interp_v3_v3v3(pt->vert_color, pt->vert_color, gso->linear_color, inf); + pt->vert_color[3] = pt->vert_color[3] * (1.0 - inf) + inf; + /* Un-premult. */ + if (pt->vert_color[3] > 0.0f) { + mul_v3_fl(pt->vert_color, 1.0f / pt->vert_color[3]); + } + } } else { - /* Premult. */ - mul_v3_fl(pt->vert_color, pt->vert_color[3]); - /* "Alpha over" blending. */ - interp_v3_v3v3(pt->vert_color, pt->vert_color, gso->linear_color, inf); - pt->vert_color[3] = pt->vert_color[3] * (1.0 - inf) + inf; - /* Un-premult. */ - if (pt->vert_color[3] > 0.0f) { - mul_v3_fl(pt->vert_color, 1.0f / pt->vert_color[3]); + bGPDspoint *pt = &gps->points[pt_index]; + if (brush_invert_check(gso)) { + pt->vert_color[3] -= inf; + CLAMP_MIN(pt->vert_color[3], 0.0f); + } + else { + /* Premult. */ + mul_v3_fl(pt->vert_color, pt->vert_color[3]); + /* "Alpha over" blending. */ + interp_v3_v3v3(pt->vert_color, pt->vert_color, gso->linear_color, inf); + pt->vert_color[3] = pt->vert_color[3] * (1.0 - inf) + inf; + /* Un-premult. */ + if (pt->vert_color[3] > 0.0f) { + mul_v3_fl(pt->vert_color, 1.0f / pt->vert_color[3]); + } } } } @@ -820,9 +841,71 @@ static void gpencil_save_selected_point(tGP_BrushVertexpaintData *gso, gso->pbuffer_used++; } +static void gpencil_vertexpaint_select_curve(tGP_BrushVertexpaintData *gso, + bGPDstroke *gps, + const char tool, + const float diff_mat[4][4], + const float bound_mat[4][4]) +{ + ARegion *region = gso->region; + GP_SpaceConversion *gsc = &gso->gsc; + rcti *rect = &gso->brush_rect; + Brush *brush = gso->brush; + const int radius = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure : + gso->brush->size; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + bGPDcurve *gpc = gps->editcurve; + + /* Check stroke masking. */ + if (GPENCIL_ANY_VERTEX_MASK(gso->mask)) { + if ((gpc->flag & GP_CURVE_SELECT) == 0) { + return; + } + } + + /* Check if the stroke collide with brush. */ + if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, bound_mat)) { + return; + } + + if (gpc->tot_curve_points == 1) { + return; + } + + /* If the curve has more than one control point... */ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + BezTriple *bezt = &cpt->bezt; + + int screen_co[2]; + /* Test if points can be projected. */ + if (!ED_gpencil_3d_point_to_screen_space(region, rect, diff_mat, bezt->vec[1], screen_co)) { + continue; + } + + float co[2] = {(float)screen_co[0], (float)screen_co[1]}; + /* Test if the point is in the circle. */ + if (len_v2v2(gso->mval, co) > radius) { + continue; + } + + bGPDcurve_point *cpt_active = NULL; + int index = -1; + if (cpt->runtime.gpc_pt_orig) { + cpt_active = cpt->runtime.gpc_pt_orig; + index = cpt->runtime.idx_orig; + } + else { + cpt_active = cpt; + index = i; + } + gpencil_save_selected_point(gso, gps_active, index, screen_co); + } +} + /* 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, +static void gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, bGPDstroke *gps, const char tool, const float diff_mat[4][4], @@ -849,13 +932,13 @@ static bool gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, /* Check stroke masking. */ if (GPENCIL_ANY_VERTEX_MASK(gso->mask)) { if ((gps->flag & GP_STROKE_SELECT) == 0) { - return false; + return; } } /* Check if the stroke collide with brush. */ if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, bound_mat)) { - return false; + return; } if (gps->totpoints == 1) { @@ -990,8 +1073,6 @@ static bool gpencil_vertexpaint_select_stroke(tGP_BrushVertexpaintData *gso, } } } - - return saved; } /* Apply vertex paint brushes to strokes in the given frame. */ @@ -1027,12 +1108,11 @@ static bool gpencil_vertexpaint_brush_do_frame(bContext *C, } /* Check points below the brush. */ - bool hit = gpencil_vertexpaint_select_stroke(gso, gps, tool, diff_mat, bound_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; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + gpencil_vertexpaint_select_curve(gso, gps, tool, diff_mat, bound_mat); + } + else { + gpencil_vertexpaint_select_stroke(gso, gps, tool, diff_mat, bound_mat); } } @@ -1106,6 +1186,10 @@ static bool gpencil_vertexpaint_brush_do_frame(bContext *C, printf("ERROR: Unknown type of GPencil Vertex Paint brush\n"); break; } + + if (changed && GPENCIL_STROKE_TYPE_BEZIER(selected->gps)) { + BKE_gpencil_stroke_geometry_update(gso->gpd, selected->gps, GP_GEO_UPDATE_POLYLINE_COLOR); + } } /* Clear the selected array, but keep the memory allocation. */ gso->pbuffer = gpencil_select_buffer_ensure( diff --git a/source/blender/editors/gpencil/gpencil_weight_paint.c b/source/blender/editors/gpencil/gpencil_weight_paint.c index 6d953a4d8cf..8ba23f7678f 100644 --- a/source/blender/editors/gpencil/gpencil_weight_paint.c +++ b/source/blender/editors/gpencil/gpencil_weight_paint.c @@ -37,6 +37,7 @@ #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_object_deform.h" #include "BKE_report.h" @@ -237,8 +238,15 @@ static bool brush_draw_apply(tGP_BrushWeightpaintData *gso, /* create dvert */ BKE_gpencil_dvert_ensure(gps); - MDeformVert *dvert = gps->dvert + pt_index; + MDeformVert *dvert; float inf; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + dvert = gpc->dvert + pt_index; + } + else { + dvert = gps->dvert + pt_index; + } /* Compute strength of effect */ inf = brush_influence_calc(gso, radius, co); @@ -365,7 +373,14 @@ static void gpencil_save_selected_point(tGP_BrushWeightpaintData *gso, int pc[2]) { tGP_Selected *selected; - bGPDspoint *pt = &gps->points[index]; + bGPDspoint *pt = NULL; + bGPDcurve_point *cpt = NULL; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + cpt = &gps->editcurve->curve_points[index]; + } + else { + pt = &gps->points[index]; + } /* Ensure the array to save the list of selected points is big enough. */ gso->pbuffer = gpencil_select_buffer_ensure( @@ -375,11 +390,65 @@ static void gpencil_save_selected_point(tGP_BrushWeightpaintData *gso, selected->gps = gps; selected->pt_index = index; copy_v2_v2_int(selected->pc, pc); - copy_v4_v4(selected->color, pt->vert_color); + copy_v4_v4(selected->color, (cpt != NULL) ? cpt->vert_color : pt->vert_color); gso->pbuffer_used++; } +static void gpencil_weightpaint_select_curve(tGP_BrushWeightpaintData *gso, + bGPDstroke *gps, + const float diff_mat[4][4], + const float bound_mat[4][4]) +{ + ARegion *region = gso->region; + GP_SpaceConversion *gsc = &gso->gsc; + rcti *rect = &gso->brush_rect; + Brush *brush = gso->brush; + const int radius = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure : + gso->brush->size; + bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps; + bGPDcurve *gpc = gps->editcurve; + + /* Check if the stroke collide with brush. */ + if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, bound_mat)) { + return; + } + + if (gpc->tot_curve_points == 1) { + return; + } + + /* If the curve has more than one control point... */ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *cpt = &gpc->curve_points[i]; + BezTriple *bezt = &cpt->bezt; + + int screen_co[2]; + /* Test if points can be projected. */ + if (!ED_gpencil_3d_point_to_screen_space(region, rect, diff_mat, bezt->vec[1], screen_co)) { + continue; + } + + float co[2] = {(float)screen_co[0], (float)screen_co[1]}; + /* Test if the point is in the circle. */ + if (len_v2v2(gso->mval, co) > radius) { + continue; + } + + bGPDcurve_point *cpt_active = NULL; + int index = -1; + if (cpt->runtime.gpc_pt_orig) { + cpt_active = cpt->runtime.gpc_pt_orig; + index = cpt->runtime.idx_orig; + } + else { + cpt_active = cpt; + index = i; + } + gpencil_save_selected_point(gso, gps_active, index, screen_co); + } +} + /* Select points in this stroke and add to an array to be used later. */ static void gpencil_weightpaint_select_stroke(tGP_BrushWeightpaintData *gso, bGPDstroke *gps, @@ -531,9 +600,13 @@ static bool gpencil_weightpaint_brush_do_frame(bContext *C, if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { continue; } - - /* Check points below the brush. */ - gpencil_weightpaint_select_stroke(gso, gps, diff_mat, bound_mat); + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + gpencil_weightpaint_select_curve(gso, gps, diff_mat, bound_mat); + } + else { + /* Check points below the brush. */ + gpencil_weightpaint_select_stroke(gso, gps, diff_mat, bound_mat); + } } /*--------------------------------------------------------------------- @@ -541,7 +614,6 @@ static bool gpencil_weightpaint_brush_do_frame(bContext *C, *--------------------------------------------------------------------- */ bool changed = false; for (i = 0; i < gso->pbuffer_used; i++) { - changed = true; selected = &gso->pbuffer[i]; switch (tool) { @@ -554,6 +626,10 @@ static bool gpencil_weightpaint_brush_do_frame(bContext *C, printf("ERROR: Unknown type of GPencil Weight Paint brush\n"); break; } + + if (changed && GPENCIL_STROKE_TYPE_BEZIER(selected->gps)) { + BKE_gpencil_stroke_geometry_update(gso->gpd, selected->gps, GP_GEO_UPDATE_POLYLINE_WEIGHT); + } } /* Clear the selected array, but keep the memory allocation. */ gso->pbuffer = gpencil_select_buffer_ensure( diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 59b5a1abaa6..ca10f240ffa 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -52,6 +52,7 @@ struct SnapObjectContext; struct ToolSettings; struct View3D; struct bContext; +struct rcti; struct Material; struct Object; @@ -295,6 +296,11 @@ void ED_gpencil_stroke_reproject(struct Depsgraph *depsgraph, struct bGPDstroke *gps, const eGP_ReprojectModes mode, const bool keep_original); +bool ED_gpencil_3d_point_to_screen_space(struct ARegion *region, + const struct rcti *rect, + const float diff_mat[4][4], + const float co[3], + int r_co[2]); /* set sculpt cursor */ void ED_gpencil_toggle_brush_cursor(struct bContext *C, bool enable, void *customdata); diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index 39de63c22a9..8825be6d60e 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1761,10 +1761,6 @@ 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 f7b78b10868..7e64d9dea3d 100644 --- a/source/blender/editors/transform/transform_convert_gpencil.c +++ b/source/blender/editors/transform/transform_convert_gpencil.c @@ -95,54 +95,77 @@ static short get_bezt_sel_triple_flag(BezTriple *bezt, const bool handles_visibl 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) +static void createTransGPencil_strokes(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; - Scene *scene = CTX_data_scene(C); + Scene *scene = t->scene; + TransData *td = NULL; + float mtx[3][3], smtx[3][3]; + 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); + /* == 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. + * For now, we just do these without creating TransData2D for the 2D + * strokes. This may cause issues in future though. + */ tc->data_len = 0; - /* Number of selected curve points */ + /* First Pass: Count the number of data-points required for the strokes, + * (and additional info about the configuration - e.g. 2D/3D?). + */ 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_material_editable(obact, gpl, gps) == false) { - continue; + if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) { + 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) == 0 && (is_multiedit))) { + continue; + } + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(t->context, gps) == false) { + continue; + } + /* Check if the color is editable. */ + if (ED_gpencil_stroke_material_editable(obact, gpl, gps) == false) { + continue; + } + + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + if (is_prop_edit) { + /* Add all points if prop edit or prop edit connected + stroke is selected. */ + if (is_prop_edit_connected && gpc->flag & GP_CURVE_SELECT) { + tot_curve_points += gpc->tot_curve_points; + tot_points += gpc->tot_curve_points * 3; } - /* Check if stroke has an editcurve */ - if (gps->editcurve == NULL) { - continue; + else { + tot_curve_points += gpc->tot_curve_points; + tot_points += gpc->tot_curve_points * 3; } - - bGPDcurve *gpc = gps->editcurve; + } + else 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; @@ -167,60 +190,66 @@ static void createTransGPencil_curves(bContext *C, } tot_sel_curve_points++; } - - if (is_prop_edit) { - tot_points += 3; - tot_curve_points++; - } } } } - - /* If not multi-edit out of loop. */ - if (!is_multiedit) { - break; + else { + if (is_prop_edit) { + /* Add all points if prop edit or prop edit connected + stroke is selected. */ + if (is_prop_edit_connected && gps->flag & GP_STROKE_SELECT) { + tot_points += gps->totpoints; + } + else { + tot_points += gps->totpoints; + } + } + else if (gps->flag & GP_STROKE_SELECT) { + /* Only selected stroke points are considered. */ + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + if (pt->flag & GP_SPOINT_SELECT) { + tot_sel_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; - } - - int data_len_pt = 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; - } + tc->data_len = (is_prop_edit || is_prop_edit_connected) ? tot_points : tot_sel_points; + /* Stop trying if nothing selected. */ if (tc->data_len == 0) { return; } - transform_around_single_fallback_ex(t, data_len_pt); + transform_around_single_fallback_ex(t, tc->data_len); - tc->data = MEM_callocN(tc->data_len * sizeof(TransData), __func__); - TransData *td = tc->data; + /* Allocate memory for data */ + tc->data = MEM_callocN(tc->data_len * sizeof(TransData), "TransData(GPencil)"); + td = tc->data; + + unit_m3(smtx); + unit_m3(mtx); const bool use_around_origins_for_handles_test = ((t->around == V3D_AROUND_LOCAL_ORIGINS) && transform_mode_use_local_origins(t)); + /* Second Pass: Build transdata array. */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - /* Only editable and visible layers are considered. */ + /* 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]; + float diff_mat[4][4]; + // float inverse_diff_mat[4][4]; + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; /* Init multiframe falloff options. */ int f_init = 0; int f_end = 0; @@ -229,6 +258,12 @@ static void createTransGPencil_curves(bContext *C, BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end); } + /* Make a new frame to work on if the layer's frame + * and the current scene frame don't match up. + * + * - This is useful when animating as it saves that "uh-oh" moment when you realize you've + * spent too much time editing the wrong frame... + */ if ((gpf->framenum != cfra) && (!is_multiedit)) { if (IS_AUTOKEY_ON(scene)) { gpf = BKE_gpencil_frame_addcopy(gpl, cfra); @@ -244,41 +279,75 @@ static void createTransGPencil_curves(bContext *C, /* Calculate difference matrix. */ BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat); + // /* Undo matrix. */ + // invert_m4_m4(inverse_diff_mat, diff_mat); + copy_m3_m4(mtx, diff_mat); pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); + /* Loop over strokes, adding TransData for points as needed... */ 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); + if ((gpf != gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) == 0 && (is_multiedit))) { + continue; + } + + /* 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) { + TransData *head = td; + TransData *tail = td; + bool stroke_ok; + + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(t->context, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_material_editable(obact, gpl, gps) == false) { + continue; } - 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_material_editable(obact, gpl, gps) == false) { - continue; + bool stroke_select = GPENCIL_STROKE_TYPE_BEZIER(gps) ? + (gps->editcurve->flag & GP_CURVE_SELECT) : + (gps->flag & GP_STROKE_SELECT); + if (is_prop_edit) { + if (is_prop_edit_connected) { + /* A) "Connected" - Only those in selected strokes */ + stroke_ok = stroke_select; } - /* Check if stroke has an editcurve */ - if (gps->editcurve == NULL) { - continue; + else { + /* B) Proportinal All points, always */ + stroke_ok = true; } - TransData *head, *tail; - head = tail = td; + } + else { + /* C) Only selected points in selected strokes */ + stroke_ok = stroke_select; + } + + if (!stroke_ok || gps->totpoints == 0) { + continue; + } - gps->runtime.multi_frame_falloff = falloff; - bool need_handle_recalc = false; + /* save falloff factor */ + gps->runtime.multi_frame_falloff = falloff; + /* calculate stroke center */ + float center[3]; + createTransGPencil_center_get(gps, center); + + const bool is_cyclic = gps->flag & GP_STROKE_CYCLIC; + bool need_handle_recalc = false; + + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { 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; @@ -297,13 +366,16 @@ static void createTransGPencil_curves(bContext *C, 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 (!is_prop_edit && !sel) { + continue; + } + + 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) { @@ -322,44 +394,43 @@ static void createTransGPencil_curves(bContext *C, } } - 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; + 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; - } + } + 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); - } + 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; + td->extra = gps; + td->ob = obact; - copy_m3_m3(td->smtx, smtx); - copy_m3_m3(td->mtx, mtx); - copy_m3_m3(td->axismtx, mtx); + copy_m3_m3(td->smtx, smtx); + copy_m3_m3(td->mtx, mtx); + copy_m3_m3(td->axismtx, mtx); - td++; - tail++; - } + td++; + tail++; bezt_use |= sel; } @@ -371,307 +442,97 @@ static void createTransGPencil_curves(bContext *C, 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); - } } - } + else { + /* add all necessary points... */ + for (int i = 0; i < gps->totpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + bool point_ok; - /* If not multi-edit 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) -{ - Scene *scene = CTX_data_scene(C); - 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. - * For now, we just do these without creating TransData2D for the 2D - * strokes. This may cause issues in future though. - */ - tc->data_len = 0; - - /* First Pass: Count the number of data-points required for the strokes, - * (and additional info about the configuration - e.g. 2D/3D?). - */ - 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; - bGPDstroke *gps; - bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - - for (gpf = init_gpf; gpf; gpf = gpf->next) { - if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { - 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; - } - /* Check if the color is editable. */ - if (ED_gpencil_stroke_material_editable(obact, gpl, gps) == false) { - continue; - } - - if (is_prop_edit) { - /* Proportional Editing... */ - if (is_prop_edit_connected) { - /* Connected only - so only if selected. */ - if (gps->flag & GP_STROKE_SELECT) { - tc->data_len += gps->totpoints; - } + /* include point? */ + if (is_prop_edit) { + /* Always all points in strokes that get included. */ + point_ok = true; } else { - /* Everything goes - connection status doesn't matter. */ - tc->data_len += gps->totpoints; - } - } - else { - /* Only selected stroke points are considered. */ - if (gps->flag & GP_STROKE_SELECT) { - bGPDspoint *pt; - int i; - - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & GP_SPOINT_SELECT) { - tc->data_len++; - } - } + /* Only selected points in selected strokes. */ + point_ok = (pt->flag & GP_SPOINT_SELECT) != 0; } - } - } - } - /* If not multi-edit out of loop. */ - if (!is_multiedit) { - break; - } - } - } - } - - /* Stop trying if nothing selected. */ - if (tc->data_len == 0) { - return; - } - /* Allocate memory for data */ - tc->data = MEM_callocN(tc->data_len * sizeof(TransData), "TransData(GPencil)"); - td = tc->data; - - unit_m3(smtx); - unit_m3(mtx); - - /* Second Pass: Build transdata array. */ - 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; - float diff_mat[4][4]; - float inverse_diff_mat[4][4]; - - bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - /* 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); - } - - /* Calculate difference matrix. */ - BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat); - /* Undo matrix. */ - invert_m4_m4(inverse_diff_mat, diff_mat); - - /* Make a new frame to work on if the layer's frame - * and the current scene frame don't match up. - * - * - This is useful when animating as it saves that "uh-oh" moment when you realize you've - * spent too much time editing the wrong frame... - */ - if ((gpf->framenum != cfra) && (!is_multiedit)) { - if (IS_AUTOKEY_ON(scene)) { - 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; - } - } - - /* Loop over strokes, adding TransData for points as needed... */ - 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) { - TransData *head = td; - TransData *tail = td; - bool stroke_ok; - - /* 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_material_editable(obact, gpl, gps) == false) { - continue; - } - /* What we need to include depends on proportional editing settings... */ - if (is_prop_edit) { - if (is_prop_edit_connected) { - /* A) "Connected" - Only those in selected strokes */ - stroke_ok = (gps->flag & GP_STROKE_SELECT) != 0; + if (!point_ok) { + continue; + } + /* do point... */ + + copy_v3_v3(td->iloc, &pt->x); + /* Only copy center in local origins. + * This allows get interesting effects also when move + * using proportional editing. */ + if ((gps->flag & GP_STROKE_SELECT) && + (ts->transform_pivot_point == V3D_AROUND_LOCAL_ORIGINS)) { + copy_v3_v3(td->center, center); } else { - /* B) All points, always */ - stroke_ok = true; + copy_v3_v3(td->center, &pt->x); } - } - else { - /* C) Only selected points in selected strokes */ - stroke_ok = (gps->flag & GP_STROKE_SELECT) != 0; - } - - /* Do stroke... */ - if (stroke_ok && gps->totpoints) { - bGPDspoint *pt; - int i; - /* save falloff factor */ - gps->runtime.multi_frame_falloff = falloff; + td->loc = &pt->x; - /* calculate stroke center */ - float center[3]; - createTransGPencil_center_get(gps, center); + td->flag = 0; - /* add all necessary points... */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - bool point_ok; + if (pt->flag & GP_SPOINT_SELECT) { + td->flag |= TD_SELECTED; + } - /* include point? */ - if (is_prop_edit) { - /* Always all points in strokes that get included. */ - point_ok = true; + /* For other transform modes (e.g. shrink-fatten), need to additional data + * but never for mirror. + */ + if (t->mode != TFM_MIRROR) { + if (t->mode != TFM_GPENCIL_OPACITY) { + if (is_scale_thickness) { + td->val = &pt->pressure; + td->ival = pt->pressure; + } } else { - /* Only selected points in selected strokes. */ - point_ok = (pt->flag & GP_SPOINT_SELECT) != 0; + td->val = &pt->strength; + td->ival = pt->strength; } + } - /* do point... */ - if (point_ok) { - copy_v3_v3(td->iloc, &pt->x); - /* Only copy center in local origins. - * This allows get interesting effects also when move - * using proportional editing. */ - if ((gps->flag & GP_STROKE_SELECT) && - (ts->transform_pivot_point == V3D_AROUND_LOCAL_ORIGINS)) { - copy_v3_v3(td->center, center); - } - else { - copy_v3_v3(td->center, &pt->x); - } - - td->loc = &pt->x; - - td->flag = 0; + /* screenspace needs special matrices... */ + if ((gps->flag & (GP_STROKE_3DSPACE | GP_STROKE_2DSPACE | GP_STROKE_2DIMAGE)) == 0) { + /* screenspace */ + td->protectflag = OB_LOCK_LOCZ | OB_LOCK_ROTZ | OB_LOCK_SCALEZ; + } + else if (gps->flag & (GP_STROKE_2DSPACE | GP_STROKE_2DIMAGE)) { + /* configure 2D dataspace points so that they don't play up... */ + td->protectflag = OB_LOCK_LOCZ | OB_LOCK_ROTZ | OB_LOCK_SCALEZ; + } + /* apply parent transformations */ + copy_m3_m3(td->smtx, smtx); /* final position */ + copy_m3_m3(td->mtx, mtx); /* display position */ + copy_m3_m3(td->axismtx, mtx); /* axis orientation */ - if (pt->flag & GP_SPOINT_SELECT) { - td->flag |= TD_SELECTED; - } + /* Triangulation must be calculated again, + * so save the stroke for recalc function */ + td->extra = gps; - /* For other transform modes (e.g. shrink-fatten), need to additional data - * but never for mirror. - */ - if (t->mode != TFM_MIRROR) { - if (t->mode != TFM_GPENCIL_OPACITY) { - if (is_scale_thickness) { - td->val = &pt->pressure; - td->ival = pt->pressure; - } - } - else { - td->val = &pt->strength; - td->ival = pt->strength; - } - } + /* save pointer to object */ + td->ob = obact; - /* screenspace needs special matrices... */ - if ((gps->flag & (GP_STROKE_3DSPACE | GP_STROKE_2DSPACE | GP_STROKE_2DIMAGE)) == - 0) { - /* screenspace */ - td->protectflag = OB_LOCK_LOCZ | OB_LOCK_ROTZ | OB_LOCK_SCALEZ; - } - else { - /* configure 2D dataspace points so that they don't play up... */ - if (gps->flag & (GP_STROKE_2DSPACE | GP_STROKE_2DIMAGE)) { - td->protectflag = OB_LOCK_LOCZ | OB_LOCK_ROTZ | OB_LOCK_SCALEZ; - } - } - /* apply parent transformations */ - copy_m3_m4(td->smtx, inverse_diff_mat); /* final position */ - copy_m3_m4(td->mtx, diff_mat); /* display position */ - copy_m3_m4(td->axismtx, diff_mat); /* axis orientation */ - - /* Triangulation must be calculated again, - * so save the stroke for recalc function */ - td->extra = gps; - - /* save pointer to object */ - td->ob = obact; + td++; + tail++; + } + } - td++; - tail++; - } - } + /* March over these points, and calculate the proportional editing distances */ + if (is_prop_edit && (head != tail)) { + calc_distanceCurveVerts(head, tail - 1, is_cyclic); + } - /* March over these points, and calculate the proportional editing distances */ - if (is_prop_edit && (head != tail)) { - calc_distanceCurveVerts(head, tail - 1, false); - } - } + if (need_handle_recalc) { + BKE_gpencil_editcurve_recalculate_handles(gps); } } /* If not multi-edit out of loop. */ @@ -681,6 +542,10 @@ static void createTransGPencil_strokes(bContext *C, } } } + +#undef SEL_F1 +#undef SEL_F2 +#undef SEL_F3 } void createTransGPencil(bContext *C, TransInfo *t) @@ -707,8 +572,6 @@ void createTransGPencil(bContext *C, TransInfo *t) 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); @@ -718,34 +581,17 @@ void createTransGPencil(bContext *C, TransInfo *t) 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); - } + createTransGPencil_strokes(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 */ @@ -756,18 +602,16 @@ void recalcData_gpencil_strokes(TransInfo *t) 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) { + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { BKE_gpencil_editcurve_recalculate_handles(gps); - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } BLI_ghash_free(strokes, NULL, NULL); diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index 080a19cce1f..fc35cf6eb15 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -653,7 +653,6 @@ 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; @@ -709,11 +708,7 @@ int ED_transform_calc_gizmo_stats(const bContext *C, continue; } - if (is_curve_edit) { - if (gps->editcurve == NULL) { - continue; - } - + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { bGPDcurve *gpc = gps->editcurve; if (gpc->flag & GP_CURVE_SELECT) { for (uint32_t i = 0; i < gpc->tot_curve_points; i++) { diff --git a/source/blender/editors/transform/transform_mode_gpopacity.c b/source/blender/editors/transform/transform_mode_gpopacity.c index 7c496d271ef..0dcd4e4fd17 100644 --- a/source/blender/editors/transform/transform_mode_gpopacity.c +++ b/source/blender/editors/transform/transform_mode_gpopacity.c @@ -73,18 +73,18 @@ static void applyGPOpacity(TransInfo *t, const int UNUSED(mval[2])) 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; } + /* Only recalculate data for bezier strokes. */ + bGPDstroke *gps = td->extra; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + recalc = true; + } + if (td->val) { *td->val = td->ival * ratio; /* apply PET */ diff --git a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c index 608a49f38b1..897655ee674 100644 --- a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c @@ -73,18 +73,18 @@ static void applyGPShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) 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; } + /* Only recalculate data for bezier strokes. */ + bGPDstroke *gps = td->extra; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + recalc = true; + } + if (td->val) { *td->val = td->ival * ratio; /* apply PET */ diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c index 6409c86b6e3..d588c87576d 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c @@ -34,6 +34,7 @@ #include "DNA_object_types.h" #include "BKE_deform.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_modifier.h" #include "BKE_material.h" @@ -138,8 +139,15 @@ bool is_stroke_affected_by_modifier(Object *ob, } } /* need to have a minimum number of points */ - if ((minpoints > 0) && (gps->totpoints < minpoints)) { - return false; + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + if ((minpoints > 0) && (gps->editcurve->tot_curve_points < minpoints)) { + return false; + } + } + else { + if ((minpoints > 0) && (gps->totpoints < minpoints)) { + return false; + } } return true; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c index 08200b03466..4f068bef59e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c @@ -41,6 +41,7 @@ #include "BKE_armature.h" #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_lib_query.h" @@ -77,7 +78,9 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) BKE_gpencil_modifier_copydata_generic(md, target); } -static void gpencil_deform_verts(ArmatureGpencilModifierData *mmd, Object *target, bGPDstroke *gps) +static void gpencil_deform_polyline_verts(ArmatureGpencilModifierData *mmd, + Object *target, + bGPDstroke *gps) { bGPDspoint *pt = gps->points; float(*vert_coords)[3] = MEM_mallocN(sizeof(float[3]) * gps->totpoints, __func__); @@ -110,8 +113,73 @@ static void gpencil_deform_verts(ArmatureGpencilModifierData *mmd, Object *targe MEM_freeN(vert_coords); } +static void gpencil_deform_bezier_verts(ArmatureGpencilModifierData *mmd, + Object *target, + bGPDstroke *gps) +{ + bGPDcurve *gpc = gps->editcurve; + const int totpoints = gpc->tot_curve_points * 3; + float(*vert_coords)[3] = MEM_mallocN(sizeof(float[3]) * totpoints, __func__); + + BKE_gpencil_dvert_ensure(gps); + + /* prepare array of points */ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + int idx = i * 3; + copy_v3_v3(vert_coords[idx], bezt->vec[0]); + copy_v3_v3(vert_coords[idx + 1], bezt->vec[1]); + copy_v3_v3(vert_coords[idx + 2], bezt->vec[2]); + } + + /* deform verts */ + BKE_armature_deform_coords_with_gpencil_stroke(mmd->object, + target, + vert_coords, + NULL, + totpoints, + mmd->deformflag, + mmd->vert_coords_prev, + mmd->vgname, + gps); + + /* Apply deformed coordinates */ + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + int idx = i * 3; + copy_v3_v3(bezt->vec[0], vert_coords[idx]); + copy_v3_v3(bezt->vec[1], vert_coords[idx + 1]); + copy_v3_v3(bezt->vec[2], vert_coords[idx + 2]); + } + + /* Recalculate the handles. */ + BKE_gpencil_editcurve_recalculate_handles(gps); + + MEM_freeN(vert_coords); +} + /* deform stroke */ -static void deformStroke(GpencilModifierData *md, +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *UNUSED(gpl), + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) +{ + ArmatureGpencilModifierData *mmd = (ArmatureGpencilModifierData *)md; + if (!mmd->object) { + return; + } + bGPdata *gpd = ob->data; + + gpencil_deform_polyline_verts(mmd, ob, gps); + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); +} + +static void deformBezier(GpencilModifierData *md, Depsgraph *UNUSED(depsgraph), Object *ob, bGPDlayer *UNUSED(gpl), @@ -124,9 +192,9 @@ static void deformStroke(GpencilModifierData *md, } bGPdata *gpd = ob->data; - gpencil_deform_verts(mmd, ob, gps); + gpencil_deform_bezier_verts(mmd, ob, gps); /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } static void bakeModifier(Main *UNUSED(bmain), @@ -155,7 +223,12 @@ static void bakeModifier(Main *UNUSED(bmain), /* compute armature effects on this frame */ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md_eval, depsgraph, object_eval, gpl, gpf, gps); + if (!GPENCIL_STROKE_TYPE_BEZIER(gps)) { + deformPolyline(md_eval, depsgraph, object_eval, gpl, gpf, gps); + } + else { + deformBezier(md_eval, depsgraph, object_eval, gpl, gpf, gps); + } } } } @@ -236,7 +309,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Armature = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c index 79d4f4dffec..1cca72c3156 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c @@ -260,20 +260,46 @@ static void generate_geometry(GpencilModifierData *md, /* Duplicate stroke */ bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(iter->gps, true, true); - /* Move points */ - for (int i = 0; i < iter->gps->totpoints; i++) { - bGPDspoint *pt = &gps_dst->points[i]; - /* Apply randomness matrix. */ - mul_m4_v3(mat_rnd, &pt->x); - - /* Apply object local transform (Rot/Scale). */ - if ((mmd->flag & GP_ARRAY_USE_OB_OFFSET) && (mmd->object)) { - mul_m4_v3(mat, &pt->x); + /* Bezier type. */ + if (GPENCIL_STROKE_TYPE_BEZIER(gps_dst)) { + bGPDcurve *gpc = gps_dst->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + + for (int j = 0; j < 3; j++) { + /* Apply randomness matrix. */ + mul_m4_v3(mat_rnd, bezt->vec[j]); + /* Apply object local transform (Rot/Scale). */ + if ((mmd->flag & GP_ARRAY_USE_OB_OFFSET) && (mmd->object)) { + mul_m4_v3(mat, bezt->vec[j]); + } + /* Global Rotate and scale. */ + mul_mat3_m4_v3(current_offset, bezt->vec[j]); + /* Global translate. */ + add_v3_v3(bezt->vec[j], current_offset[3]); + } } - /* Global Rotate and scale. */ - mul_mat3_m4_v3(current_offset, &pt->x); - /* Global translate. */ - add_v3_v3(&pt->x, current_offset[3]); + BKE_gpencil_stroke_geometry_update(gpd, gps_dst, GP_GEO_UPDATE_DEFAULT); + } + else { + /* Polygon type. */ + for (int i = 0; i < iter->gps->totpoints; i++) { + bGPDspoint *pt = &gps_dst->points[i]; + /* Apply randomness matrix. */ + mul_m4_v3(mat_rnd, &pt->x); + + /* Apply object local transform (Rot/Scale). */ + if ((mmd->flag & GP_ARRAY_USE_OB_OFFSET) && (mmd->object)) { + mul_m4_v3(mat, &pt->x); + } + /* Global Rotate and scale. */ + mul_mat3_m4_v3(current_offset, &pt->x); + /* Global translate. */ + add_v3_v3(&pt->x, current_offset[3]); + } + /* Calc bounding box. */ + BKE_gpencil_stroke_boundingbox_calc(gps_dst); } /* If replace material, use new one. */ @@ -283,8 +309,6 @@ static void generate_geometry(GpencilModifierData *md, /* Add new stroke. */ BLI_addhead(&iter->gpf->strokes, gps_dst); - /* Calc bounding box. */ - BKE_gpencil_stroke_boundingbox_calc(gps_dst); } } @@ -472,7 +496,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Array = { /* copyData */ copyData, - /* deformStroke */ NULL, + /* deformPolyline */ NULL, + /* deformBezier */ NULL, /* generateStrokes */ generateStrokes, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c index 4e07827c940..c5c079b89d8 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c @@ -180,7 +180,7 @@ static void reduce_stroke_points(bGPdata *gpd, gps->totpoints = num_points; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* --------------------------------------------- */ @@ -637,7 +637,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Build = { /* copyData */ copyData, - /* deformStroke */ NULL, + /* deformPolyline */ NULL, + /* deformBezier */ NULL, /* generateStrokes */ generateStrokes, /* bakeModifier */ NULL, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilcolor.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilcolor.c index 0cb32b693bb..3d0f5f8bb8e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilcolor.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilcolor.c @@ -83,12 +83,12 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) } /* color correction strokes */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { ColorGpencilModifierData *mmd = (ColorGpencilModifierData *)md; @@ -159,6 +159,18 @@ static void deformStroke(GpencilModifierData *md, } } +/* Deform Bezier. */ +static void deformBezier(GpencilModifierData *md, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) +{ + /* Reuse deformPolyline because the changes are not affecting the geometry. */ + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); +} + static void bakeModifier(Main *UNUSED(bmain), Depsgraph *depsgraph, GpencilModifierData *md, @@ -169,7 +181,7 @@ static void bakeModifier(Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); } } } @@ -235,7 +247,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Color = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c index bad324ea1ca..cafa76c347c 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c @@ -163,7 +163,7 @@ static float gpencil_hook_falloff(const struct GPHookData_cb *tData, const float } /* apply point deformation */ -static void gpencil_hook_co_apply(struct GPHookData_cb *tData, float weight, bGPDspoint *pt) +static void gpencil_hook_co_apply(struct GPHookData_cb *tData, float weight, float co[3]) { float fac; @@ -172,11 +172,11 @@ static void gpencil_hook_co_apply(struct GPHookData_cb *tData, float weight, bGP if (tData->use_uniform) { float co_uniform[3]; - mul_v3_m3v3(co_uniform, tData->mat_uniform, &pt->x); + mul_v3_m3v3(co_uniform, tData->mat_uniform, co); len_sq = len_squared_v3v3(tData->cent, co_uniform); } else { - len_sq = len_squared_v3v3(tData->cent, &pt->x); + len_sq = len_squared_v3v3(tData->cent, co); } fac = gpencil_hook_falloff(tData, len_sq); @@ -187,62 +187,48 @@ static void gpencil_hook_co_apply(struct GPHookData_cb *tData, float weight, bGP if (fac) { float co_tmp[3]; - mul_v3_m4v3(co_tmp, tData->mat, &pt->x); - interp_v3_v3v3(&pt->x, &pt->x, co_tmp, fac * weight); + mul_v3_m4v3(co_tmp, tData->mat, co); + interp_v3_v3v3(co, co, co_tmp, fac * weight); } } -/* deform stroke */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static bool do_modifier(Object *ob, HookGpencilModifierData *mmd, bGPDlayer *gpl, bGPDstroke *gps) { - HookGpencilModifierData *mmd = (HookGpencilModifierData *)md; - if (!mmd->object) { - return; - } - - const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + return is_stroke_affected_by_modifier(ob, + mmd->layername, + mmd->material, + mmd->pass_index, + mmd->layer_pass, + 1, + gpl, + gps, + mmd->flag & GP_HOOK_INVERT_LAYER, + mmd->flag & GP_HOOK_INVERT_PASS, + mmd->flag & GP_HOOK_INVERT_LAYERPASS, + mmd->flag & GP_HOOK_INVERT_MATERIAL); +} +static void calc_hook_data(HookGpencilModifierData *mmd, Object *ob, struct GPHookData_cb *tData) +{ bPoseChannel *pchan = BKE_pose_channel_find_name(mmd->object->pose, mmd->subtarget); float dmat[4][4]; - struct GPHookData_cb tData; - - if (!is_stroke_affected_by_modifier(ob, - mmd->layername, - mmd->material, - mmd->pass_index, - mmd->layer_pass, - 1, - gpl, - gps, - mmd->flag & GP_HOOK_INVERT_LAYER, - mmd->flag & GP_HOOK_INVERT_PASS, - mmd->flag & GP_HOOK_INVERT_LAYERPASS, - mmd->flag & GP_HOOK_INVERT_MATERIAL)) { - return; - } - bGPdata *gpd = ob->data; /* init struct */ - tData.curfalloff = mmd->curfalloff; - tData.falloff_type = mmd->falloff_type; - tData.falloff = (mmd->falloff_type == eHook_Falloff_None) ? 0.0f : mmd->falloff; - tData.falloff_sq = square_f(tData.falloff); - tData.fac_orig = mmd->force; - tData.use_falloff = (tData.falloff_sq != 0.0f); - tData.use_uniform = (mmd->flag & GP_HOOK_UNIFORM_SPACE) != 0; - - if (tData.use_uniform) { - copy_m3_m4(tData.mat_uniform, mmd->parentinv); - mul_v3_m3v3(tData.cent, tData.mat_uniform, mmd->cent); + tData->curfalloff = mmd->curfalloff; + tData->falloff_type = mmd->falloff_type; + tData->falloff = (mmd->falloff_type == eHook_Falloff_None) ? 0.0f : mmd->falloff; + tData->falloff_sq = square_f(tData->falloff); + tData->fac_orig = mmd->force; + tData->use_falloff = (tData->falloff_sq != 0.0f); + tData->use_uniform = (mmd->flag & GP_HOOK_UNIFORM_SPACE) != 0; + + if (tData->use_uniform) { + copy_m3_m4(tData->mat_uniform, mmd->parentinv); + mul_v3_m3v3(tData->cent, tData->mat_uniform, mmd->cent); } else { - unit_m3(tData.mat_uniform); - copy_v3_v3(tData.cent, mmd->cent); + unit_m3(tData->mat_uniform); + copy_v3_v3(tData->cent, mmd->cent); } /* get world-space matrix of target, corrected for the space the verts are in */ @@ -255,7 +241,31 @@ static void deformStroke(GpencilModifierData *md, copy_m4_m4(dmat, mmd->object->obmat); } invert_m4_m4(ob->imat, ob->obmat); - mul_m4_series(tData.mat, ob->imat, dmat, mmd->parentinv); + mul_m4_series(tData->mat, ob->imat, dmat, mmd->parentinv); +} + +/* deform stroke */ +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) +{ + HookGpencilModifierData *mmd = (HookGpencilModifierData *)md; + if (!mmd->object) { + return; + } + + const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + + if (!do_modifier(ob, mmd, gpl, gps)) { + return; + } + bGPdata *gpd = ob->data; + + struct GPHookData_cb tData; + calc_hook_data(mmd, ob, &tData); /* loop points and apply deform */ for (int i = 0; i < gps->totpoints; i++) { @@ -268,10 +278,53 @@ static void deformStroke(GpencilModifierData *md, if (weight < 0.0f) { continue; } - gpencil_hook_co_apply(&tData, weight, pt); + gpencil_hook_co_apply(&tData, weight, &pt->x); + } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); +} + +static void deformBezier(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) +{ + HookGpencilModifierData *mmd = (HookGpencilModifierData *)md; + if (!mmd->object) { + return; + } + + const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + + if (!do_modifier(ob, mmd, gpl, gps)) { + return; + } + bGPdata *gpd = ob->data; + + struct GPHookData_cb tData; + calc_hook_data(mmd, ob, &tData); + + /* Loop points and apply deform. */ + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + MDeformVert *dvert = gpc->dvert != NULL ? &gpc->dvert[i] : NULL; + + /* verify vertex group */ + const float weight = get_modifier_point_weight( + dvert, (mmd->flag & GP_HOOK_INVERT_VGROUP) != 0, def_nr); + if (weight < 0.0f) { + continue; + } + for (int j = 0; j < 3; j++) { + gpencil_hook_co_apply(&tData, weight, bezt->vec[j]); + } } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* FIXME: Ideally we be doing this on a copy of the main depsgraph @@ -301,7 +354,12 @@ static void bakeModifier(Main *UNUSED(bmain), /* compute hook effects on this frame */ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + if (!GPENCIL_STROKE_TYPE_BEZIER(gps)) { + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); + } + else { + deformBezier(md, depsgraph, ob, gpl, gpf, gps); + } } } } @@ -429,7 +487,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Hook = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c index 2934b89c747..40f9e5f9151 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c @@ -75,29 +75,37 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) BKE_gpencil_modifier_copydata_generic(md, target); } -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static bool do_modifier(Object *ob, + LatticeGpencilModifierData *mmd, + bGPDlayer *gpl, + bGPDstroke *gps) +{ + return is_stroke_affected_by_modifier(ob, + mmd->layername, + mmd->material, + mmd->pass_index, + mmd->layer_pass, + 1, + gpl, + gps, + mmd->flag & GP_LATTICE_INVERT_LAYER, + mmd->flag & GP_LATTICE_INVERT_PASS, + mmd->flag & GP_LATTICE_INVERT_LAYERPASS, + mmd->flag & GP_LATTICE_INVERT_MATERIAL); +} + +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + 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); - if (!is_stroke_affected_by_modifier(ob, - mmd->layername, - mmd->material, - mmd->pass_index, - mmd->layer_pass, - 1, - gpl, - gps, - mmd->flag & GP_LATTICE_INVERT_LAYER, - mmd->flag & GP_LATTICE_INVERT_PASS, - mmd->flag & GP_LATTICE_INVERT_LAYERPASS, - mmd->flag & GP_LATTICE_INVERT_MATERIAL)) { + if (!do_modifier(ob, mmd, gpl, gps)) { return; } @@ -119,7 +127,47 @@ static void deformStroke(GpencilModifierData *md, (struct LatticeDeformData *)mmd->cache_data, &pt->x, mmd->strength * weight); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); +} + +static void deformBezier(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + 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); + + if (!do_modifier(ob, mmd, gpl, gps)) { + return; + } + + if (mmd->cache_data == NULL) { + return; + } + + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + MDeformVert *dvert = gpc->dvert != NULL ? &gpc->dvert[i] : NULL; + + /* verify vertex group */ + const float weight = get_modifier_point_weight( + dvert, (mmd->flag & GP_LATTICE_INVERT_VGROUP) != 0, def_nr); + if (weight < 0.0f) { + continue; + } + for (int j = 0; j < 3; j++) { + BKE_lattice_deform_data_eval_co( + (struct LatticeDeformData *)mmd->cache_data, bezt->vec[j], mmd->strength * weight); + } + } + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* FIXME: Ideally we be doing this on a copy of the main depsgraph @@ -156,7 +204,12 @@ static void bakeModifier(Main *UNUSED(bmain), /* Compute lattice effects on this frame. */ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + if (!GPENCIL_STROKE_TYPE_BEZIER(gps)) { + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); + } + else { + deformBezier(md, depsgraph, ob, gpl, gpf, gps); + } } } } @@ -271,7 +324,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Lattice = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c index fd94ac92bc3..20209cdad81 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c @@ -109,7 +109,7 @@ static void applyLength(LengthGpencilModifierData *lmd, bGPdata *gpd, bGPDstroke changed |= gpencil_modify_stroke(gps, len * lmd->end_fac, lmd->overshoot_fac, 2); if (changed) { - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } @@ -134,12 +134,12 @@ static void bakeModifier(Main *UNUSED(bmain), /* -------------------------------- */ /* Generic "generateStrokes" callback */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { bGPdata *gpd = ob->data; LengthGpencilModifierData *lmd = (LengthGpencilModifierData *)md; @@ -207,7 +207,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Length = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ NULL, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index fcc44aab583..a9f50f08cc7 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -681,7 +681,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Lineart = { /* copyData. */ copyData, - /* deformStroke. */ NULL, + /* deformPolyline. */ NULL, + /* deformBezier. */ NULL, /* generateStrokes. */ generateStrokes, /* bakeModifier. */ bakeModifier, /* remapTime. */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c index 1dc09cf1f74..d7e5458ef98 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c @@ -39,6 +39,7 @@ #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_lib_query.h" #include "BKE_main.h" @@ -74,13 +75,25 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) /* Mirror is using current object as origin. */ static void update_mirror_local(bGPDstroke *gps, int axis) { - int i; - bGPDspoint *pt; float factor[3] = {1.0f, 1.0f, 1.0f}; factor[axis] = -1.0f; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - mul_v3_v3(&pt->x, factor); + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + for (int j = 0; j < 3; j++) { + mul_v3_v3(bezt->vec[j], factor); + } + } + } + else { + int i; + bGPDspoint *pt; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + mul_v3_v3(&pt->x, factor); + } } } @@ -101,8 +114,20 @@ static void update_mirror_object(Object *ob, invert_m4_m4(itmp, tmp); mul_m4_series(mtx, itmp, mtx, tmp); - for (int i = 0; i < gps->totpoints; i++) { - mul_m4_v3(mtx, &gps->points[i].x); + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + for (int j = 0; j < 3; j++) { + mul_m4_v3(mtx, bezt->vec[j]); + } + } + } + else { + for (int i = 0; i < gps->totpoints; i++) { + mul_m4_v3(mtx, &gps->points[i].x); + } } } @@ -120,6 +145,7 @@ static void generate_geometry(GpencilModifierData *md, Object *ob, bGPDlayer *gp { MirrorGpencilModifierData *mmd = (MirrorGpencilModifierData *)md; bGPDstroke *gps, *gps_new = NULL; + bGPdata *gpd = ob->data; int tot_strokes; int i; @@ -145,6 +171,7 @@ static void generate_geometry(GpencilModifierData *md, Object *ob, bGPDlayer *gp mmd->flag & GP_MIRROR_INVERT_MATERIAL)) { gps_new = BKE_gpencil_stroke_duplicate(gps, true, true); update_position(ob, mmd, gps_new, xi); + BKE_gpencil_stroke_geometry_update(gpd, gps_new, GP_GEO_UPDATE_DEFAULT); BLI_addtail(&gpf->strokes, gps_new); } } @@ -261,7 +288,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Mirror = { /* copyData */ copyData, - /* deformStroke */ NULL, + /* deformPolyline */ NULL, + /* deformBezier */ NULL, /* generateStrokes */ generateStrokes, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c index ac60e694618..d4fd73142c1 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c @@ -185,7 +185,7 @@ static void duplicateStroke(Object *ob, } /* Calc geometry data. */ if (new_gps != NULL) { - BKE_gpencil_stroke_geometry_update(gpd, new_gps); + BKE_gpencil_stroke_geometry_update(gpd, new_gps, GP_GEO_UPDATE_DEFAULT); } MEM_freeN(t1_array); MEM_freeN(t2_array); @@ -368,7 +368,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Multiply = { /* copyData */ copyData, - /* deformStroke */ NULL, + /* deformPolyline */ NULL, + /* deformBezier */ NULL, /* generateStrokes */ generateStrokes, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c index 9e9eba3d61e..c76ff79a62e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c @@ -123,12 +123,12 @@ BLI_INLINE float table_sample(float *table, float x) /** * Apply noise effect based on stroke direction. */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *depsgraph, - Object *ob, - bGPDlayer *gpl, - bGPDframe *gpf, - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) { NoiseGpencilModifierData *mmd = (NoiseGpencilModifierData *)md; MDeformVert *dvert = NULL; @@ -273,7 +273,7 @@ static void bakeModifier(struct Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); } } } @@ -359,7 +359,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Noise = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ NULL, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c index 1a38b91a18b..b45653a9361 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c @@ -70,41 +70,50 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) BKE_gpencil_modifier_copydata_generic(md, target); } -/* change stroke offsetness */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *gpf, - bGPDstroke *gps) +static bool do_modifier(Object *ob, + OffsetGpencilModifierData *mmd, + bGPDlayer *gpl, + bGPDstroke *gps) { - OffsetGpencilModifierData *mmd = (OffsetGpencilModifierData *)md; - const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + return is_stroke_affected_by_modifier(ob, + mmd->layername, + mmd->material, + mmd->pass_index, + mmd->layer_pass, + 1, + gpl, + gps, + mmd->flag & GP_OFFSET_INVERT_LAYER, + mmd->flag & GP_OFFSET_INVERT_PASS, + mmd->flag & GP_OFFSET_INVERT_LAYERPASS, + mmd->flag & GP_OFFSET_INVERT_MATERIAL); +} - float mat[4][4]; +/* Calculate transform matrix. */ +static float prepare_matrix(OffsetGpencilModifierData *mmd, float weight, float r_mat[4][4]) +{ float loc[3], rot[3], scale[3]; - if (!is_stroke_affected_by_modifier(ob, - mmd->layername, - mmd->material, - mmd->pass_index, - mmd->layer_pass, - 1, - gpl, - gps, - mmd->flag & GP_OFFSET_INVERT_LAYER, - mmd->flag & GP_OFFSET_INVERT_PASS, - mmd->flag & GP_OFFSET_INVERT_LAYERPASS, - mmd->flag & GP_OFFSET_INVERT_MATERIAL)) { - return; - } + mul_v3_v3fl(loc, mmd->loc, weight); + mul_v3_v3fl(rot, mmd->rot, weight); + mul_v3_v3fl(scale, mmd->scale, weight); + add_v3_fl(scale, 1.0); + loc_eul_size_to_mat4(r_mat, loc, rot, scale); + + return (scale[0] + scale[1] + scale[2]) / 3.0f; +} + +/* Calculate random seeds. */ +static void prepare_random_seeds( + GpencilModifierData *md, Object *ob, bGPDframe *gpf, bGPDstroke *gps, float rand[3][3]) +{ + OffsetGpencilModifierData *mmd = (OffsetGpencilModifierData *)md; int seed = mmd->seed; /* Make sure different modifiers get different seeds. */ seed += BLI_hash_string(ob->id.name + 2); seed += BLI_hash_string(md->name); - float rand[3][3]; float rand_offset = BLI_hash_int_01(seed); /* Get stroke index for random offset. */ @@ -129,9 +138,28 @@ static void deformStroke(GpencilModifierData *md, } } } +} - bGPdata *gpd = ob->data; +/* change stroke offsetness */ +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) +{ + OffsetGpencilModifierData *mmd = (OffsetGpencilModifierData *)md; + const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + + if (!do_modifier(ob, mmd, gpl, gps)) { + return; + } + /* Calculate Random seeds. */ + float rand[3][3]; + prepare_random_seeds(md, ob, gpf, gps, rand); + + bGPdata *gpd = ob->data; for (int i = 0; i < gps->totpoints; i++) { bGPDspoint *pt = &gps->points[i]; MDeformVert *dvert = gps->dvert != NULL ? &gps->dvert[i] : NULL; @@ -161,20 +189,78 @@ static void deformStroke(GpencilModifierData *md, mul_m4_v3(mat_rnd, &pt->x); /* Calculate matrix. */ - mul_v3_v3fl(loc, mmd->loc, weight); - mul_v3_v3fl(rot, mmd->rot, weight); - mul_v3_v3fl(scale, mmd->scale, weight); - add_v3_fl(scale, 1.0); - loc_eul_size_to_mat4(mat, loc, rot, scale); - - /* Apply scale to thickness. */ - float unit_scale = (scale[0] + scale[1] + scale[2]) / 3.0f; + float mat[4][4]; + float unit_scale = prepare_matrix(mmd, weight, mat); pt->pressure *= unit_scale; mul_m4_v3(mat, &pt->x); } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); +} + +static void deformBezier(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) +{ + OffsetGpencilModifierData *mmd = (OffsetGpencilModifierData *)md; + const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + + if (!do_modifier(ob, mmd, gpl, gps)) { + return; + } + + /* Calculate Random seeds. */ + float rand[3][3]; + prepare_random_seeds(md, ob, gpf, gps, rand); + + bGPdata *gpd = ob->data; + bGPDcurve *gpc = gps->editcurve; + + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + BezTriple *bezt = &pt->bezt; + MDeformVert *dvert = (gpc->dvert != NULL) ? &gpc->dvert[i] : NULL; + + /* Verify vertex group. */ + const float weight = get_modifier_point_weight( + dvert, (mmd->flag & GP_OFFSET_INVERT_VGROUP) != 0, def_nr); + if (weight < 0.0f) { + continue; + } + + /* Calculate Random matrix. */ + float mat_rnd[4][4]; + float rnd_loc[3], rnd_rot[3], rnd_scale_weight[3]; + float rnd_scale[3] = {1.0f, 1.0f, 1.0f}; + + mul_v3_v3fl(rnd_loc, rand[0], weight); + mul_v3_v3fl(rnd_rot, rand[1], weight); + mul_v3_v3fl(rnd_scale_weight, rand[2], weight); + + mul_v3_v3v3(rnd_loc, mmd->rnd_offset, rnd_loc); + mul_v3_v3v3(rnd_rot, mmd->rnd_rot, rnd_rot); + madd_v3_v3v3(rnd_scale, mmd->rnd_scale, rnd_scale_weight); + + loc_eul_size_to_mat4(mat_rnd, rnd_loc, rnd_rot, rnd_scale); + /* Apply randomness matrix. */ + for (int j = 0; j < 3; j++) { + mul_m4_v3(mat_rnd, bezt->vec[j]); + } + + float mat[4][4]; + float unit_scale = prepare_matrix(mmd, weight, mat); + pt->pressure *= unit_scale; + + for (int j = 0; j < 3; j++) { + mul_m4_v3(mat, bezt->vec[j]); + } + } + + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } static void bakeModifier(struct Main *UNUSED(bmain), @@ -187,7 +273,12 @@ static void bakeModifier(struct Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + if (!GPENCIL_STROKE_TYPE_BEZIER(gps)) { + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); + } + else { + deformBezier(md, depsgraph, ob, gpl, gpf, gps); + } } } } @@ -254,7 +345,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Offset = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilopacity.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilopacity.c index fb75b1e99ac..7ba6443b2b2 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilopacity.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilopacity.c @@ -85,12 +85,12 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) } /* opacity strokes */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { OpacityGpencilModifierData *mmd = (OpacityGpencilModifierData *)md; const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); @@ -192,6 +192,18 @@ static void deformStroke(GpencilModifierData *md, } } +/* Deform Bezier. */ +static void deformBezier(GpencilModifierData *md, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) +{ + /* Reuse deformPolyline because the changes are not affecting the geometry. */ + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); +} + static void bakeModifier(Main *UNUSED(bmain), Depsgraph *depsgraph, GpencilModifierData *md, @@ -202,7 +214,7 @@ static void bakeModifier(Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); } } } @@ -313,7 +325,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Opacity = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c index 18785705e50..fe32f5baadc 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c @@ -66,12 +66,12 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) BKE_gpencil_modifier_copydata_generic(md, target); } -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *gpf, - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) { SimplifyGpencilModifierData *mmd = (SimplifyGpencilModifierData *)md; @@ -126,7 +126,7 @@ static void bakeModifier(struct Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); } } } @@ -189,7 +189,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Simplify = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ NULL, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c index 4142a1c02dc..f8d230f1401 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c @@ -39,6 +39,7 @@ #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_deform.h" +#include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_lib_query.h" @@ -84,12 +85,12 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) /** * Apply smooth effect based on stroke direction. */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { SmoothGpencilModifierData *mmd = (SmoothGpencilModifierData *)md; const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); @@ -151,6 +152,55 @@ static void deformStroke(GpencilModifierData *md, } } +static void deformBezier(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) +{ + SmoothGpencilModifierData *mmd = (SmoothGpencilModifierData *)md; + + if (!is_stroke_affected_by_modifier(ob, + mmd->layername, + mmd->material, + mmd->pass_index, + mmd->layer_pass, + 3, + gpl, + gps, + mmd->flag & GP_SMOOTH_INVERT_LAYER, + mmd->flag & GP_SMOOTH_INVERT_PASS, + mmd->flag & GP_SMOOTH_INVERT_LAYERPASS, + mmd->flag & GP_SMOOTH_INVERT_MATERIAL)) { + return; + } + + if (mmd->factor <= 0.0f) { + return; + } + bGPdata *gpd = ob->data; + const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + const bool use_curve = (mmd->flag & GP_SMOOTH_CUSTOM_CURVE) != 0 && mmd->curve_intensity; + + BKE_gpencil_editcurve_smooth_ex(gps, + mmd->factor, + 2, + mmd->step, + false, + false, + true, + (mmd->flag & GP_SMOOTH_INVERT_VGROUP) != 0, + def_nr, + use_curve ? mmd->curve_intensity : NULL, + mmd->flag & GP_SMOOTH_MOD_LOCATION, + mmd->flag & GP_SMOOTH_MOD_THICKNESS, + mmd->flag & GP_SMOOTH_MOD_STRENGTH); + + BKE_gpencil_editcurve_recalculate_handles(gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_POLYLINE_REGENERATE_ALL); +} + static void bakeModifier(struct Main *UNUSED(bmain), Depsgraph *depsgraph, GpencilModifierData *md, @@ -161,7 +211,12 @@ static void bakeModifier(struct Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + if (GPENCIL_STROKE_TYPE_BEZIER(gps)) { + deformBezier(md, depsgraph, ob, gpl, gpf, gps); + } + else { + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); + } } } } @@ -232,7 +287,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Smooth = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c index f444103ae3f..dab909ecd12 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c @@ -66,12 +66,12 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) } /* subdivide stroke to get more control points */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { SubdivGpencilModifierData *mmd = (SubdivGpencilModifierData *)md; bGPdata *gpd = ob->data; @@ -113,7 +113,7 @@ static void bakeModifier(struct Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); } } } @@ -162,7 +162,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Subdiv = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ NULL, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c index 74297908808..5a424be5e54 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciltexture.c @@ -70,12 +70,12 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) } /* change stroke uv texture values */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { TextureGpencilModifierData *mmd = (TextureGpencilModifierData *)md; const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); @@ -100,7 +100,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(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } if (ELEM(mmd->mode, STROKE, STROKE_AND_FILL)) { @@ -130,6 +130,18 @@ static void deformStroke(GpencilModifierData *md, } } +/* Deform Bezier. */ +static void deformBezier(GpencilModifierData *md, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) +{ + /* Reuse deformPolyline because the UV values of curve are not used yet. */ + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); +} + static void bakeModifier(struct Main *UNUSED(bmain), Depsgraph *depsgraph, GpencilModifierData *md, @@ -140,7 +152,7 @@ static void bakeModifier(struct Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); } } } @@ -210,7 +222,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Texture = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilthick.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilthick.c index cac700e15f4..f7fe39caad5 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilthick.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilthick.c @@ -37,6 +37,7 @@ #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_deform.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_lib_query.h" #include "BKE_modifier.h" @@ -89,29 +90,34 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) tgmd->curve_thickness = BKE_curvemapping_copy(gmd->curve_thickness); } +static bool do_modifier(Object *ob, ThickGpencilModifierData *mmd, bGPDlayer *gpl, bGPDstroke *gps) +{ + return is_stroke_affected_by_modifier(ob, + mmd->layername, + mmd->material, + mmd->pass_index, + mmd->layer_pass, + 1, + gpl, + gps, + mmd->flag & GP_THICK_INVERT_LAYER, + mmd->flag & GP_THICK_INVERT_PASS, + mmd->flag & GP_THICK_INVERT_LAYERPASS, + mmd->flag & GP_THICK_INVERT_MATERIAL); +} + /* change stroke thickness */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { ThickGpencilModifierData *mmd = (ThickGpencilModifierData *)md; const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); - if (!is_stroke_affected_by_modifier(ob, - mmd->layername, - mmd->material, - mmd->pass_index, - mmd->layer_pass, - 1, - gpl, - gps, - mmd->flag & GP_THICK_INVERT_LAYER, - mmd->flag & GP_THICK_INVERT_PASS, - mmd->flag & GP_THICK_INVERT_LAYERPASS, - mmd->flag & GP_THICK_INVERT_MATERIAL)) { + if (!do_modifier(ob, mmd, gpl, gps)) { return; } @@ -160,6 +166,59 @@ static void deformStroke(GpencilModifierData *md, } } +static void deformBezier(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) +{ + ThickGpencilModifierData *mmd = (ThickGpencilModifierData *)md; + const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + + if (!do_modifier(ob, mmd, gpl, gps)) { + return; + } + float stroke_thickness_inv = 1.0f / max_ii(gps->thickness, 1); + + bGPDcurve *gpc = gps->editcurve; + for (int i = 0; i < gpc->tot_curve_points; i++) { + bGPDcurve_point *pt = &gpc->curve_points[i]; + MDeformVert *dvert = gpc->dvert != NULL ? &gpc->dvert[i] : NULL; + + /* Verify point is part of vertex group. */ + float weight = get_modifier_point_weight( + dvert, (mmd->flag & GP_THICK_INVERT_VGROUP) != 0, def_nr); + if (weight < 0.0f) { + continue; + } + + float curvef = 1.0f; + if ((mmd->flag & GP_THICK_CUSTOM_CURVE) && (mmd->curve_thickness)) { + /* Normalize value to evaluate curve. */ + float value = (float)i / (gpc->tot_curve_points - 1); + curvef = BKE_curvemapping_evaluateF(mmd->curve_thickness, 0, value); + } + + float target; + if (mmd->flag & GP_THICK_NORMALIZE) { + target = mmd->thickness * stroke_thickness_inv; + target *= curvef; + } + else { + target = pt->pressure * mmd->thickness_fac; + weight *= curvef; + } + + pt->pressure = interpf(target, pt->pressure, weight); + + CLAMP_MIN(pt->pressure, 0.0f); + /* Calc geometry data. */ + bGPdata *gpd = ob->data; + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); + } +} + static void bakeModifier(struct Main *UNUSED(bmain), Depsgraph *depsgraph, GpencilModifierData *md, @@ -170,7 +229,12 @@ static void bakeModifier(struct Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + if (!GPENCIL_STROKE_TYPE_BEZIER(gps)) { + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); + } + else { + deformBezier(md, depsgraph, ob, gpl, gpf, gps); + } } } } @@ -236,7 +300,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Thick = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciltime.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciltime.c index e743a724425..ba1074f4a6b 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciltime.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciltime.c @@ -258,7 +258,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Time = { /* copyData */ copyData, - /* deformStroke */ NULL, + /* deformPolyline */ NULL, + /* deformBezier */ NULL, /* generateStrokes */ NULL, /* bakeModifier */ NULL, /* remapTime */ remapTime, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c index 680f5ab05ec..26637e53168 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c @@ -113,12 +113,12 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) } /* deform stroke */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { TintGpencilModifierData *mmd = (TintGpencilModifierData *)md; if ((mmd->type == GP_TINT_GRADIENT) && (!mmd->object)) { @@ -268,6 +268,18 @@ static void deformStroke(GpencilModifierData *md, } } +/* Deform Bezier. */ +static void deformBezier(GpencilModifierData *md, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) +{ + /* Reuse deformPolyline because the changes are not affecting the geometry. */ + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); +} + /* FIXME: Ideally we be doing this on a copy of the main depsgraph * (i.e. one where we don't have to worry about restoring state) */ @@ -295,7 +307,7 @@ static void bakeModifier(Main *UNUSED(bmain), /* compute effects on this frame */ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); } } } @@ -413,7 +425,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Tint = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c index c7fe20edef7..bf9269582ae 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c @@ -96,12 +96,12 @@ static float calc_point_weight_by_distance(Object *ob, } /* change stroke thickness */ -static void deformStroke(GpencilModifierData *md, - Depsgraph *UNUSED(depsgraph), - Object *ob, - bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), - bGPDstroke *gps) +static void deformPolyline(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) { WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md; const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); @@ -205,6 +205,18 @@ static void deformStroke(GpencilModifierData *md, } } +/* Deform Bezier. */ +static void deformBezier(GpencilModifierData *md, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + bGPDstroke *gps) +{ + /* Reuse deformPolyline. */ + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); +} + static void bakeModifier(struct Main *UNUSED(bmain), Depsgraph *depsgraph, GpencilModifierData *md, @@ -215,7 +227,7 @@ static void bakeModifier(struct Main *UNUSED(bmain), LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - deformStroke(md, depsgraph, ob, gpl, gpf, gps); + deformPolyline(md, depsgraph, ob, gpl, gpf, gps); } } } @@ -319,7 +331,8 @@ GpencilModifierTypeInfo modifierType_Gpencil_Weight = { /* copyData */ copyData, - /* deformStroke */ deformStroke, + /* deformPolyline */ deformPolyline, + /* deformBezier */ deformBezier, /* generateStrokes */ NULL, /* bakeModifier */ bakeModifier, /* remapTime */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index 3f722f33946..1eb717040af 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -4338,7 +4338,7 @@ static void lineart_gpencil_generate(LineartCache *cache, if (G.debug_value == 4000) { BKE_gpencil_stroke_set_random_color(gps); } - BKE_gpencil_stroke_geometry_update(gpencil_object->data, gps); + BKE_gpencil_stroke_geometry_update(gpencil_object->data, gps, GP_GEO_UPDATE_DEFAULT); stroke_count++; } diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc index 52fcc017ffb..39bfb2e5ba6 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc @@ -222,7 +222,7 @@ void GpencilImporterSVG::create_stroke(bGPdata *gpd, /* Cleanup and recalculate geometry. */ BKE_gpencil_stroke_merge_distance(gpd, gpf, gps, 0.001f, true); - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } /* Unpack internal NanoSVG color. */ diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 6563afd2b4a..cd1f5aeb68d 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -44,6 +44,7 @@ typedef enum eGPBrush_Presets { GP_BRUSH_PRESET_ERASER_POINT = 12, GP_BRUSH_PRESET_ERASER_STROKE = 13, GP_BRUSH_PRESET_TINT = 14, + GP_BRUSH_PRESET_INK_CURVE = 15, /* Vertex Paint 100-199. */ GP_BRUSH_PRESET_VERTEX_DRAW = 100, @@ -101,6 +102,8 @@ typedef enum eGPDbrush_Flag { GP_BRUSH_OCCLUDE_ERASER = (1 << 15), /* Post process trim stroke */ GP_BRUSH_TRIM_STROKE = (1 << 16), + /* Indicates that the stroke will be converted to a curve automatically. */ + GP_BRUSH_CURVE_DATA = (1 << 17), } eGPDbrush_Flag; typedef enum eGPDbrush_Flag2 { @@ -554,6 +557,7 @@ typedef enum eBrushGPaintTool { GPAINT_TOOL_FILL = 1, GPAINT_TOOL_ERASE = 2, GPAINT_TOOL_TINT = 3, + GPAINT_TOOL_CURVE = 4, /* UNUSED */ } eBrushGPaintTool; /* BrushGpencilSettings->brush type */ diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index c573de6b54e..8874d7e3ca0 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -107,13 +107,14 @@ typedef struct bGPDspoint { typedef enum eGPDspoint_Flag { /* stroke point is selected (for editing) */ GP_SPOINT_SELECT = (1 << 0), - /* stroke point is tagged (for some editing operation) */ GP_SPOINT_TAG = (1 << 1), /* stroke point is temp tagged (for some editing operation) */ GP_SPOINT_TEMP_TAG = (1 << 2), /* stroke point is temp tagged (for some editing operation) */ GP_SPOINT_TEMP_TAG2 = (1 << 3), + /* Stroke point is "linked" to control point of bezier handle. */ + GP_SPOINT_IS_BEZT_CONTROL = (1 << 4), } eGPSPoint_Flag; /* ***************************************** */ @@ -183,6 +184,14 @@ typedef enum eGPDpalette_Flag { /* ***************************************** */ /* GP Curve Point */ +typedef struct bGPDcpoint_Runtime { + /* Original curve point */ + struct bGPDcurve_point *gpc_pt_orig; + /* Original index array position */ + int idx_orig; + char _pad0[4]; +} bGPDcpoint_Runtime; + typedef struct bGPDcurve_point { /** Bezier Triple for the handles and control points. */ BezTriple bezt; @@ -205,12 +214,14 @@ typedef struct bGPDcurve_point { /** Vertex Color RGBA (A=mix factor). */ float vert_color[4]; - char _pad[4]; + + bGPDcpoint_Runtime runtime; } bGPDcurve_point; /* bGPDcurve_point->flag */ typedef enum eGPDcurve_point_Flag { GP_CURVE_POINT_SELECT = (1 << 0), + GP_CURVE_POINT_TAG = (1 << 1), } eGPDcurve_point_Flag; /* ***************************************** */ @@ -218,19 +229,22 @@ typedef enum eGPDcurve_point_Flag { /* Curve for Bezier Editing. */ typedef struct bGPDcurve { - /** Array of BezTriple. */ + /** Array of curve points. */ bGPDcurve_point *curve_points; /** Total number of curve points. */ int tot_curve_points; /** General flag. */ short flag; char _pad[2]; + /** Vertex weight data. */ + struct MDeformVert *dvert; + void *_pad2; + } 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), + /* GP_CURVE_NEEDS_STROKE_UPDATE = (1 << 0), */ /* Deprecated */ /* Curve is selected */ GP_CURVE_SELECT = (1 << 1), } bGPDcurve_Flag; @@ -341,9 +355,6 @@ 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) */ @@ -657,10 +668,6 @@ typedef struct bGPdata { int flag; /** 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). */ @@ -871,6 +878,17 @@ typedef enum eGP_DrawMode { #define GPENCIL_PLAY_ON(gpd) ((gpd) && ((gpd)->runtime.playing == 1)) +/* True if the stroke has curve data (is of type BEZIER). */ +#define GPENCIL_STROKE_TYPE_BEZIER(gps) ((gps) && ((gps)->editcurve != NULL)) + +/* True if the stroke is of type POLY */ +#define GPENCIL_STROKE_TYPE_POLY(gps) ((gps) && ((gps)->editcurve == NULL)) + +/* True if the stroke type of gps_a and gps_b is equal. */ +#define GPENCIL_STROKE_TYPE_EQ(gps_a, gps_b) \ + ((GPENCIL_STROKE_TYPE_BEZIER(gps_a) && GPENCIL_STROKE_TYPE_BEZIER(gps_b)) || \ + (GPENCIL_STROKE_TYPE_POLY(gps_a) && GPENCIL_STROKE_TYPE_POLY(gps_b))) + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index 1a2a8892e64..cf09c277af5 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -376,6 +376,9 @@ .gpencil_v2d_align = GP_PROJECT_VIEWSPACE, \ .gpencil_seq_align = GP_PROJECT_VIEWSPACE, \ .gpencil_ima_align = GP_PROJECT_VIEWSPACE, \ + /* GP Curve Fitting */ \ + .gpencil_curve_fit_threshold = GP_DEFAULT_CURVE_ERROR, \ + .gpencil_curve_fit_corner_angle = GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE,\ } /* clang-format off */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index cd752b220a3..15cae8c47ce 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -483,7 +483,7 @@ typedef struct ImageFormatData { #define R_IMF_IMTYPE_INVALID 255 /** #ImageFormatData.flag */ -#define R_IMF_FLAG_ZBUF (1 << 0) /* was R_OPENEXR_ZBUF */ +#define R_IMF_FLAG_ZBUF (1 << 0) /* was R_OPENEXR_ZBUF */ #define R_IMF_FLAG_PREVIEW_JPG (1 << 1) /* was R_PREVIEW_JPG */ /* Return values from #BKE_imtype_valid_depths, note this is depths per channel. */ @@ -525,8 +525,8 @@ typedef enum eImageFormatDepth { /** #ImageFormatData.jp2_flag */ #define R_IMF_JP2_FLAG_YCC (1 << 0) /* when disabled use RGB */ /* was R_JPEG2K_YCC */ -#define R_IMF_JP2_FLAG_CINE_PRESET (1 << 1) /* was R_JPEG2K_CINE_PRESET */ -#define R_IMF_JP2_FLAG_CINE_48 (1 << 2) /* was R_JPEG2K_CINE_48FPS */ +#define R_IMF_JP2_FLAG_CINE_PRESET (1 << 1) /* was R_JPEG2K_CINE_PRESET */ +#define R_IMF_JP2_FLAG_CINE_48 (1 << 2) /* was R_JPEG2K_CINE_48FPS */ /** #ImageFormatData.jp2_codec */ #define R_IMF_JP2_CODEC_JP2 0 @@ -1437,6 +1437,11 @@ typedef struct ToolSettings { /* Particle Editing */ struct ParticleEditSettings particle; + /** Curve Fit error threshold. */ + float gpencil_curve_fit_threshold; + /** Curve Fit corner angle (less or equal is treated as corner). */ + float gpencil_curve_fit_corner_angle; + /* Transform Proportional Area of Effect */ float proportional_size; @@ -1842,12 +1847,12 @@ typedef struct Scene { #define R_MODE_UNUSED_20 (1 << 20) /* cleared */ #define R_MODE_UNUSED_21 (1 << 21) /* cleared */ -#define R_NO_OVERWRITE (1 << 22) /* skip existing files */ -#define R_TOUCH (1 << 23) /* touch files before rendering */ +#define R_NO_OVERWRITE (1 << 22) /* skip existing files */ +#define R_TOUCH (1 << 23) /* touch files before rendering */ #define R_SIMPLIFY (1 << 24) -#define R_EDGE_FRS (1 << 25) /* R_EDGE reserved for Freestyle */ +#define R_EDGE_FRS (1 << 25) /* R_EDGE reserved for Freestyle */ #define R_PERSISTENT_DATA (1 << 26) /* keep data around for re-render */ -#define R_MODE_UNUSED_27 (1 << 27) /* cleared */ +#define R_MODE_UNUSED_27 (1 << 27) /* cleared */ /** #RenderData.seq_flag */ enum { @@ -2306,6 +2311,8 @@ typedef enum eGPencil_Flags { GP_TOOL_FLAG_CREATE_WEIGHTS = (1 << 4), /* Automerge with last stroke */ GP_TOOL_FLAG_AUTOMERGE_STROKE = (1 << 5), + /* Convert all strokes to Bezier */ + GP_TOOL_FLAG_BEZIER_MODE = (1 << 6), } eGPencil_Flags; /** #Scene.r.simplify_gpencil */ diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index 03f7dbf6489..d482dc91d7d 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -94,6 +94,7 @@ #include "DNA_curve_types.h" #include "DNA_fluid_types.h" #include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" #include "DNA_hair_types.h" #include "DNA_image_types.h" #include "DNA_key_types.h" diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 2b09ea51a84..7b221dfb32d 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -192,6 +192,13 @@ const EnumPropertyItem rna_enum_brush_gpencil_types_items[] = { ICON_BRUSH_TEXDRAW, "Tint", "The brush is of type used for tinting strokes"}, +#if 0 + {GPAINT_TOOL_CURVE, + "CURVE", + ICON_STROKE, + "Curve", + "The brush is of type used for drawing curves"}, +#endif {0, NULL, 0, NULL, NULL}, }; @@ -1904,6 +1911,11 @@ static void rna_def_gpencil_options(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_OCCLUDE_ERASER); RNA_def_property_ui_text(prop, "Occlude Eraser", "Erase only strokes visible and not occluded"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + prop = RNA_def_property(srna, "use_curve_data", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_CURVE_DATA); + RNA_def_property_ui_text(prop, "Generate Curve", "Create Curve data for the new stroke"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); } static void rna_def_brush(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index aad6f1231dd..bf313444de5 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -193,43 +193,16 @@ static void rna_GpencilLayerMatrix_update(Main *bmain, Scene *scene, PointerRNA rna_GPencil_update(bmain, scene, ptr); } -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); - } + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + if (gpl->actframe != NULL) { + bGPDframe *gpf = gpl->actframe; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->editcurve != NULL) { + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); } } } @@ -238,26 +211,6 @@ static void rna_GPencil_stroke_curve_update(Main *bmain, Scene *scene, PointerRN 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); @@ -275,7 +228,7 @@ static void rna_GPencil_uv_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Poi bGPDstroke *gps = (bGPDstroke *)ptr->data; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, gps); + BKE_gpencil_stroke_geometry_update(gpd, gps, GP_GEO_UPDATE_DEFAULT); DEG_id_tag_update(ptr->owner_id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL); @@ -749,7 +702,7 @@ static void rna_GPencil_stroke_point_add( stroke->totpoints += count; /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, stroke); + BKE_gpencil_stroke_geometry_update(gpd, stroke, GP_GEO_UPDATE_DEFAULT); DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); @@ -810,7 +763,7 @@ static void rna_GPencil_stroke_point_pop(ID *id, } /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gpd, stroke); + BKE_gpencil_stroke_geometry_update(gpd, stroke, GP_GEO_UPDATE_DEFAULT); DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); @@ -823,7 +776,7 @@ static void rna_GPencil_stroke_point_update(ID *id, bGPDstroke *stroke) /* Calc geometry data. */ if (stroke) { - BKE_gpencil_stroke_geometry_update(gpd, stroke); + BKE_gpencil_stroke_geometry_update(gpd, stroke, GP_GEO_UPDATE_DEFAULT); DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); @@ -2525,11 +2478,8 @@ static void rna_def_gpencil_data(BlenderRNA *brna) 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, "Bézier Curve Resolution", "Number of segments generated between control points"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_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); @@ -2539,25 +2489,7 @@ static void rna_def_gpencil_data(BlenderRNA *brna) "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); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_stroke_curve_update"); prop = RNA_def_property(srna, "use_multiedit", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_STROKE_MULTIEDIT); @@ -2567,11 +2499,6 @@ 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( diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 0a91d5f01bc..40f072bea93 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -3035,6 +3035,21 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_range(prop, 0.0, 1.0); RNA_def_property_ui_range(prop, 0.0, 0.1, 0.01, 6); + /* Grease Pencil Curve fitting error threshold. */ + prop = RNA_def_property(srna, "gpencil_curve_fit_threshold", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "gpencil_curve_fit_threshold"); + RNA_def_property_range(prop, FLT_MIN, 10.0); + 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); + + /* Grease Pencil Curve fitting corner angle. */ + prop = RNA_def_property(srna, "gpencil_curve_fit_corner_angle", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, NULL, "gpencil_curve_fit_corner_angle"); + RNA_def_property_range(prop, 0.0f, DEG2RADF(180.0f)); + RNA_def_property_ui_text(prop, "Corner Angle", "Angle threshold to be treated as corners"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + /* Pivot Point */ prop = RNA_def_property(srna, "transform_pivot_point", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "transform_pivot_point"); @@ -3232,6 +3247,12 @@ static void rna_def_tool_settings(BlenderRNA *brna) "Join by distance last drawn stroke with previous strokes in the active layer"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "use_gpencil_bezier_mode", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "gpencil_flags", GP_TOOL_FLAG_BEZIER_MODE); + RNA_def_property_boolean_default(prop, false); + RNA_def_property_ui_text(prop, "Bezier Mode", "Convert all strokes to Bezier curves"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + prop = RNA_def_property(srna, "gpencil_sculpt", PROP_POINTER, PROP_NONE); RNA_def_property_pointer_sdna(prop, NULL, "gp_sculpt"); RNA_def_property_struct_type(prop, "GPencilSculptSettings"); |