From bb9c9d0eaaab836b8f20ab7b2228795f607b823a Mon Sep 17 00:00:00 2001 From: Antonioya Date: Fri, 11 Jan 2019 19:15:23 +0100 Subject: [PATCH] GP: New Cutter, Constraints and Segment selection This commit groups a set of new tools that were tested in grease pencil object branch before moving to master. We decide to do all the development in a separated branch because it could break master during days or weeks before the new tools were ready to deploy. The commit includes: - New Cutter tool to trim strokes and help cleaning up drawings. - New set of constraints and guides to draw different types of shapes. All the credits for this development goes to Charlie Jolly (@charlie), thanks for your help! - Segment selection mode to select strokes between intersections. - New operator to change strokes cap mode. - New option to display only keyframed frames. This option is very important when fill strokes with color. - Multiple small fixes and tweaks. Thanks to @pepeland and @mendio for their ideas, tests, reviews and support. Note: Still pending the final icons for Cutter in Toolbar and Segment Selection in Topbar. @billreynish could help us here? --- .../keyconfig/keymap_data/blender_default.py | 37 +- .../startup/bl_ui/properties_data_gpencil.py | 3 + .../bl_ui/properties_grease_pencil_common.py | 1 + .../startup/bl_ui/space_toolsystem_toolbar.py | 18 +- release/scripts/startup/bl_ui/space_topbar.py | 11 +- release/scripts/startup/bl_ui/space_view3d.py | 55 ++ source/blender/blenkernel/BKE_gpencil.h | 4 + source/blender/blenkernel/intern/gpencil.c | 95 ++- .../blender/blenkernel/intern/library_query.c | 5 +- source/blender/blenkernel/intern/scene.c | 2 + source/blender/blenloader/intern/readfile.c | 2 + .../blenloader/intern/versioning_280.c | 10 + .../engines/gpencil/gpencil_draw_cache_impl.c | 29 + .../draw/engines/gpencil/gpencil_draw_utils.c | 39 +- .../draw/engines/gpencil/gpencil_engine.h | 4 +- .../gpencil/shaders/gpencil_stroke_geom.glsl | 11 +- .../blender/editors/gpencil/annotate_paint.c | 2 +- source/blender/editors/gpencil/gpencil_edit.c | 364 +++++++++++- .../blender/editors/gpencil/gpencil_intern.h | 17 +- .../blender/editors/gpencil/gpencil_merge.c | 2 +- source/blender/editors/gpencil/gpencil_ops.c | 6 + .../blender/editors/gpencil/gpencil_paint.c | 545 +++++++++++++++--- .../editors/gpencil/gpencil_primitive.c | 61 +- .../blender/editors/gpencil/gpencil_select.c | 46 +- .../blender/editors/gpencil/gpencil_utils.c | 404 ++++++++++++- source/blender/editors/include/ED_gpencil.h | 13 + source/blender/makesdna/DNA_gpencil_types.h | 14 +- source/blender/makesdna/DNA_scene_types.h | 39 +- source/blender/makesrna/RNA_access.h | 1 + source/blender/makesrna/intern/rna_gpencil.c | 74 ++- source/blender/makesrna/intern/rna_scene.c | 2 + .../makesrna/intern/rna_sculpt_paint.c | 113 +++- 32 files changed, 1871 insertions(+), 158 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 3a7c0620b68..21c31e3a6f3 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -2983,6 +2983,8 @@ def km_grease_pencil_stroke_edit_mode(params): {"properties": [("mode", 0)]}), ("gpencil.selectmode_toggle", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("mode", 1)]}), + ("gpencil.selectmode_toggle", {"type": 'THREE', "value": 'PRESS'}, + {"properties": [("mode", 2)]}), ]) if params.apple: @@ -3045,7 +3047,25 @@ def km_grease_pencil_stroke_paint_draw_brush(params): # Erase ("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, {"properties": [("mode", 'ERASER'), ("wait_for_input", False)]}), - + # Constrain Guides Speedlines + # Freehand + ("gpencil.draw", {"type": 'O', "value": 'PRESS'}, None), + ("gpencil.draw", {"type": 'J', "value": 'PRESS'}, None), + ("gpencil.draw", {"type": 'J', "value": 'PRESS', "alt": True}, None), + ("gpencil.draw", {"type": 'J', "value": 'PRESS', "shift": True}, None), + ("gpencil.draw", {"type": 'K', "value": 'PRESS'}, None), + ("gpencil.draw", {"type": 'K', "value": 'PRESS', "alt": True}, None), + ("gpencil.draw", {"type": 'K', "value": 'PRESS', "shift": True}, None), + ("gpencil.draw", {"type": 'L', "value": 'PRESS'}, None), + ("gpencil.draw", {"type": 'L', "value": 'PRESS', "alt": True}, None), + ("gpencil.draw", {"type": 'L', "value": 'PRESS', "ctrl": True}, None), + ("gpencil.draw", {"type": 'V', "value": 'PRESS'}, None), + # Mirror or flip + ("gpencil.draw", {"type": 'M', "value": 'PRESS'}, None), + # Mode + ("gpencil.draw", {"type": 'C', "value": 'PRESS'}, None), + # Set reference point + ("gpencil.draw", {"type": 'C', "value": 'PRESS', "alt": True}, None), # Tablet Mappings for Drawing ------------------ */ # For now, only support direct drawing using the eraser, as most users using a tablet # may still want to use that as their primary pointing device! @@ -5718,6 +5738,7 @@ def km_3d_view_tool_paint_gpencil_arc(params): ]}, ) + def km_3d_view_tool_paint_gpencil_curve(params): return ( "3D View Tool: Paint Gpencil, Curve", @@ -5730,6 +5751,19 @@ def km_3d_view_tool_paint_gpencil_curve(params): ]}, ) + +def km_3d_view_tool_paint_gpencil_cutter(params): + return ( + "3D View Tool: Paint Gpencil, Cutter", + {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, + {"items": [ + ("gpencil.stroke_cutter", {"type": params.tool_mouse, "value": 'PRESS'}, None), + # Lasso select + ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None), + ]}, + ) + + def km_3d_view_tool_edit_gpencil_select(params): return ( "3D View Tool: Edit Gpencil, Select", @@ -6040,6 +6074,7 @@ def generate_keymaps(params=None): km_3d_view_tool_paint_gpencil_circle(params), km_3d_view_tool_paint_gpencil_arc(params), km_3d_view_tool_paint_gpencil_curve(params), + km_3d_view_tool_paint_gpencil_cutter(params), km_3d_view_tool_edit_gpencil_select(params), km_3d_view_tool_edit_gpencil_select_box(params), km_3d_view_tool_edit_gpencil_select_circle(params), diff --git a/release/scripts/startup/bl_ui/properties_data_gpencil.py b/release/scripts/startup/bl_ui/properties_data_gpencil.py index 59e54a4c62d..d6633c99456 100644 --- a/release/scripts/startup/bl_ui/properties_data_gpencil.py +++ b/release/scripts/startup/bl_ui/properties_data_gpencil.py @@ -145,6 +145,9 @@ class DATA_PT_gpencil_datapanel(Panel): srow.prop(gpl, "clamp_layer", text="", icon='MOD_MASK' if gpl.clamp_layer else 'LAYER_ACTIVE') + srow = col.row(align=True) + srow.prop(gpl, "use_solo_mode", text="Show Only On Keyframed") + col = row.column() sub = col.column(align=True) 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 8a93014670a..49f857492f3 100644 --- a/release/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/release/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -191,6 +191,7 @@ class GreasePencilStrokeEditPanel: col.operator("gpencil.duplicate_move", text="Duplicate") if is_3d_view: col.operator("gpencil.stroke_cyclical_set", text="Toggle Cyclic").type = 'TOGGLE' + col.operator_menu_enum("gpencil.stroke_caps_set", text="Toggle Caps...", property="type") layout.separator() diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 18dff12185f..8f4a2ca1c84 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -1077,6 +1077,16 @@ class _defs_gpencil_paint: ), ) + @ToolDef.from_fn + def cutter(): + return dict( + text="Cutter", + icon="ops.gpencil.stroke_cutter", + cursor='KNIFE', + widget=None, + keymap=(), + ) + @ToolDef.from_fn def line(): return dict( @@ -1141,7 +1151,7 @@ class _defs_gpencil_edit: @ToolDef.from_fn def select(): def draw_settings(context, layout, tool): - pass + layout.prop(context.tool_settings.gpencil_sculpt, "intersection_threshold") return dict( text="Select", icon="ops.generic.select", @@ -1155,6 +1165,7 @@ class _defs_gpencil_edit: def draw_settings(context, layout, tool): props = tool.operator_properties("gpencil.select_box") layout.prop(props, "mode", expand=True) + layout.prop(context.tool_settings.gpencil_sculpt, "intersection_threshold") return dict( text="Select Box", icon="ops.generic.select_box", @@ -1168,6 +1179,7 @@ class _defs_gpencil_edit: def draw_settings(context, layout, tool): props = tool.operator_properties("gpencil.select_lasso") layout.prop(props, "mode", expand=True) + layout.prop(context.tool_settings.gpencil_sculpt, "intersection_threshold") return dict( text="Select Lasso", icon="ops.generic.select_lasso", @@ -1178,11 +1190,14 @@ class _defs_gpencil_edit: @ToolDef.from_fn def circle_select(): + def draw_settings(context, layout, tool): + layout.prop(context.tool_settings.gpencil_sculpt, "intersection_threshold") return dict( text="Select Circle", icon="ops.generic.select_circle", widget=None, keymap=(), + draw_settings=draw_settings, ) @ToolDef.from_fn @@ -1629,6 +1644,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): _defs_view3d_generic.cursor, None, _defs_gpencil_paint.generate_from_brushes, + _defs_gpencil_paint.cutter, None, _defs_gpencil_paint.line, _defs_gpencil_paint.arc, diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index 06a6e3dc5ac..d27f8e303c9 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -303,9 +303,13 @@ class _draw_left_context_mode: return is_paint = True - if (tool.name in {"Line", "Box", "Circle", "Arc", "Curve"}): + if tool.name in {"Line", "Box", "Circle", "Arc", "Curve"}: is_paint = False - elif (not tool.has_datablock): + elif tool.name == "Cutter": + row = layout.row(align=True) + row.prop(context.tool_settings.gpencil_sculpt, "intersection_threshold") + return + elif not tool.has_datablock: return paint = context.tool_settings.gpencil_paint @@ -509,6 +513,9 @@ class TOPBAR_PT_gpencil_layers(Panel): srow.prop(gpl, "clamp_layer", text="", icon='MOD_MASK' if gpl.clamp_layer else 'LAYER_ACTIVE') + srow = col.row(align=True) + srow.prop(gpl, "use_solo_mode", text="Show Only On Keyframed") + col = row.column() sub = col.column(align=True) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 5342ae44d11..5c2cf8d529f 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -245,6 +245,18 @@ class VIEW3D_HT_header(Header): text=lk_name, icon=lk_icon, ) + + if object_mode in {'PAINT_GPENCIL'}: + if context.workspace.tools.from_space_view3d_mode(object_mode).name == "Draw": + settings = tool_settings.gpencil_sculpt.guide + row = layout.row(align=True) + row.prop(settings, "use_guide", text="", icon='GRID') + sub = row.row(align=True) + sub.active = settings.use_guide + sub.popover( + panel="VIEW3D_PT_gpencil_guide", + text="Guides" + ) layout.separator_spacer() @@ -3955,6 +3967,7 @@ class VIEW3D_MT_edit_gpencil(Menu): layout.menu("VIEW3D_MT_edit_gpencil_delete") layout.operator("gpencil.stroke_cyclical_set", text="Toggle Cyclic").type = 'TOGGLE' + layout.operator_menu_enum("gpencil.stroke_caps_set", text="Toggle Caps...", property="type") layout.separator() @@ -5317,7 +5330,47 @@ class VIEW3D_PT_gpencil_lock(Panel): col = row.column() col.prop(context.tool_settings.gpencil_sculpt, "lock_axis", expand=True) + +class VIEW3D_PT_gpencil_guide(Panel): + bl_space_type = 'VIEW_3D' + bl_region_type = 'HEADER' + bl_label = "Guides" + @staticmethod + def draw(self, context): + from math import pi + settings = context.tool_settings.gpencil_sculpt.guide + + layout = self.layout + layout.label(text="Guides") + + col = layout.column() + col.active = settings.use_guide + col.prop(settings, "type", expand=True) + + if settings.type in {'PARALLEL'}: + col.prop(settings, "angle") + row = col.row(align=True) + + col.prop(settings, "use_snapping") + if settings.use_snapping: + + if settings.type in {'RADIAL'}: + col.prop(settings, "angle_snap") + else: + col.prop(settings, "spacing") + + col.label(text="Reference Point") + row = col.row(align=True) + row.prop(settings, "reference_point", expand=True) + if settings.reference_point in {'CUSTOM'}: + col.prop(settings, "location", text="Custom Location") + if settings.reference_point in {'OBJECT'}: + col.prop(settings, "reference_object", text="Object Location") + if not settings.reference_object: + col.label(text="No object selected, using cursor") + + class VIEW3D_PT_overlay_gpencil_options(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'HEADER' @@ -5560,6 +5613,7 @@ class VIEW3D_MT_gpencil_edit_specials(Menu): layout.operator("gpencil.stroke_join", text="Join").type = 'JOIN' layout.operator("gpencil.stroke_join", text="Join & Copy").type = 'JOINCOPY' layout.operator("gpencil.stroke_flip", text="Flip Direction") + layout.operator_menu_enum("gpencil.stroke_caps_set", text="Toggle Caps...", property="type") layout.separator() layout.operator("gpencil.frame_duplicate", text="Duplicate Active Frame") @@ -5787,6 +5841,7 @@ classes = ( VIEW3D_PT_snapping, VIEW3D_PT_gpencil_origin, VIEW3D_PT_gpencil_lock, + VIEW3D_PT_gpencil_guide, VIEW3D_PT_transform_orientations, VIEW3D_PT_overlay_gpencil_options, VIEW3D_PT_context_properties, diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 989bec1957b..02495f861ab 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -170,6 +170,10 @@ void BKE_gpencil_subdivide(struct bGPDstroke *gps, int level, int flag); void BKE_gpencil_stroke_2d_flat( const struct bGPDspoint *points, int totpoints, float(*points2d)[2], int *r_direction); +void BKE_gpencil_stroke_2d_flat_ref( + const struct bGPDspoint *ref_points, int ref_totpoints, + const struct bGPDspoint *points, int totpoints, + float(*points2d)[2], const float scale, int *r_direction); void BKE_gpencil_transform(struct bGPdata *gpd, float mat[4][4]); diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index d347e10921c..54025a3cd20 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -1563,6 +1563,8 @@ int BKE_gpencil_get_material_index(Object *ob, Material *ma) /* Get points of stroke always flat to view not affected by camera view or view position */ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, int totpoints, float(*points2d)[2], int *r_direction) { + BLI_assert(totpoints >= 2); + const bGPDspoint *pt0 = &points[0]; const bGPDspoint *pt1 = &points[1]; const bGPDspoint *pt3 = &points[(int)(totpoints * 0.75)]; @@ -1576,7 +1578,15 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, int totpoints, float(* sub_v3_v3v3(locx, &pt1->x, &pt0->x); /* point vector at 3/4 */ - sub_v3_v3v3(loc3, &pt3->x, &pt0->x); + float v3[3]; + if (totpoints == 2) { + mul_v3_v3fl(v3, &pt3->x, 0.001f); + } + else { + copy_v3_v3(v3, &pt3->x); + } + + sub_v3_v3v3(loc3, v3, &pt0->x); /* vector orthogonal to polygon plane */ cross_v3_v3v3(normal, locx, loc3); @@ -1604,3 +1614,86 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, int totpoints, float(* *r_direction = (int)locy[2]; } +/* Get points of stroke always flat to view not affected by camera view or view position + * using another stroke as reference + */ +void BKE_gpencil_stroke_2d_flat_ref( + const bGPDspoint *ref_points, int ref_totpoints, + const bGPDspoint *points, int totpoints, + float(*points2d)[2], const float scale, int *r_direction) +{ + BLI_assert(totpoints >= 2); + + const bGPDspoint *pt0 = &ref_points[0]; + const bGPDspoint *pt1 = &ref_points[1]; + const bGPDspoint *pt3 = &ref_points[(int)(ref_totpoints * 0.75)]; + + float locx[3]; + float locy[3]; + float loc3[3]; + float normal[3]; + + /* local X axis (p0 -> p1) */ + sub_v3_v3v3(locx, &pt1->x, &pt0->x); + + /* point vector at 3/4 */ + float v3[3]; + if (totpoints == 2) { + mul_v3_v3fl(v3, &pt3->x, 0.001f); + } + else { + copy_v3_v3(v3, &pt3->x); + } + + sub_v3_v3v3(loc3,v3, &pt0->x); + + /* vector orthogonal to polygon plane */ + cross_v3_v3v3(normal, locx, loc3); + + /* local Y axis (cross to normal/x axis) */ + cross_v3_v3v3(locy, normal, locx); + + /* Normalize vectors */ + normalize_v3(locx); + normalize_v3(locy); + + /* Get all points in local space */ + for (int i = 0; i < totpoints; i++) { + const bGPDspoint *pt = &points[i]; + float loc[3]; + float v1[3]; + float vn[3] = { 0.0f, 0.0f, 0.0f }; + + /* apply scale to extremes of the stroke to get better collision detection + * the scale is divided to get more control in the UI parameter + */ + /* first point */ + if (i == 0) { + const bGPDspoint *pt_next = &points[i + 1]; + sub_v3_v3v3(vn, &pt->x, &pt_next->x); + normalize_v3(vn); + mul_v3_fl(vn, scale / 10.0f); + add_v3_v3v3(v1, &pt->x, vn); + } + /* last point */ + else if (i == totpoints - 1) { + const bGPDspoint *pt_prev = &points[i - 1]; + sub_v3_v3v3(vn, &pt->x, &pt_prev->x); + normalize_v3(vn); + mul_v3_fl(vn, scale / 10.0f); + add_v3_v3v3(v1, &pt->x, vn); + } + else { + copy_v3_v3(v1, &pt->x); + } + + /* Get local space using first point as origin (ref stroke) */ + sub_v3_v3v3(loc, v1, &pt0->x); + + points2d[i][0] = dot_v3v3(loc, locx); + points2d[i][1] = dot_v3v3(loc, locy); + } + + /* Concave (-1), Convex (1), or Autodetect (0)? */ + *r_direction = (int)locy[2]; +} diff --git a/source/blender/blenkernel/intern/library_query.c b/source/blender/blenkernel/intern/library_query.c index 2134c02b63d..c902aa8c5d5 100644 --- a/source/blender/blenkernel/intern/library_query.c +++ b/source/blender/blenkernel/intern/library_query.c @@ -466,7 +466,7 @@ void BKE_library_foreach_ID_link(Main *bmain, ID *id, LibraryIDLinkCallback call for (TimeMarker *marker = scene->markers.first; marker; marker = marker->next) { CALLBACK_INVOKE(marker->camera, IDWALK_CB_NOP); } - + if (toolsett) { CALLBACK_INVOKE(toolsett->particle.scene, IDWALK_CB_NOP); CALLBACK_INVOKE(toolsett->particle.object, IDWALK_CB_NOP); @@ -493,6 +493,9 @@ void BKE_library_foreach_ID_link(Main *bmain, ID *id, LibraryIDLinkCallback call if (toolsett->gp_paint) { library_foreach_paint(&data, &toolsett->gp_paint->paint); } + + CALLBACK_INVOKE(toolsett->gp_sculpt.guide.reference_object, IDWALK_CB_NOP); + } if (scene->rigidbody_world) { diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index ca33abd3245..1bae41ce035 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -715,6 +715,8 @@ void BKE_scene_init(Scene *sce) CURVE_PRESET_BELL, CURVEMAP_SLOPE_POSITIVE); + sce->toolsettings->gp_sculpt.guide.spacing = 20.0f; + sce->physics_settings.gravity[0] = 0.0f; sce->physics_settings.gravity[1] = 0.0f; sce->physics_settings.gravity[2] = -9.81f; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 54f269a35f0..4029f9f9a31 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -6010,6 +6010,8 @@ static void lib_link_scene(FileData *fd, Main *main) sce->toolsettings->particle.shape_object = newlibadr(fd, sce->id.lib, sce->toolsettings->particle.shape_object); + sce->toolsettings->gp_sculpt.guide.reference_object = newlibadr(fd, sce->id.lib, sce->toolsettings->gp_sculpt.guide.reference_object); + for (Base *base_legacy_next, *base_legacy = sce->base.first; base_legacy; base_legacy = base_legacy_next) { base_legacy_next = base_legacy->next; diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index bf416fd2c02..572140a835e 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -2741,6 +2741,16 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + /* Grease pencil cutter/select segment intersection threshold */ + if (!DNA_struct_elem_find(fd->filesdna, "GP_Sculpt_Settings", "float", "isect_threshold")) { + for (Scene *scene = bmain->scene.first; scene; scene = scene->id.next) { + GP_Sculpt_Settings *gset = &scene->toolsettings->gp_sculpt; + if (gset) { + gset->isect_threshold = 0.1f; + } + } + } + /* Fix anamorphic bokeh eevee rna limits.*/ for (Camera *ca = bmain->camera.first; ca; ca = ca->id.next) { if (ca->gpu_dof.ratio < 0.01f) { diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c b/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c index beb9f3ee847..b7207b6af65 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_cache_impl.c @@ -390,6 +390,14 @@ GPUBatch *DRW_gpencil_get_buffer_ctrlpoint_geom(bGPdata *gpd) bGPDcontrolpoint *cps = gpd->runtime.cp_points; int totpoints = gpd->runtime.tot_cp_points; + const DRWContextState *draw_ctx = DRW_context_state_get(); + Scene *scene = draw_ctx->scene; + ToolSettings *ts = scene->toolsettings; + + if (ts->gp_sculpt.guide.use_guide) { + totpoints++; + } + static GPUVertFormat format = { 0 }; static uint pos_id, color_id, size_id; if (format.attr_len == 0) { @@ -415,6 +423,27 @@ GPUBatch *DRW_gpencil_get_buffer_ctrlpoint_geom(bGPdata *gpd) idx++; } + if (ts->gp_sculpt.guide.use_guide) { + float size = 10 * 0.8f; + float color[4]; + float position[3]; + if (ts->gp_sculpt.guide.reference_point == GP_GUIDE_REF_CUSTOM) { + UI_GetThemeColor4fv(TH_GIZMO_PRIMARY, color); + copy_v3_v3(position, ts->gp_sculpt.guide.location); + } + else if (ts->gp_sculpt.guide.reference_point == GP_GUIDE_REF_OBJECT && ts->gp_sculpt.guide.reference_object != NULL) { + UI_GetThemeColor4fv(TH_GIZMO_SECONDARY, color); + copy_v3_v3(position, ts->gp_sculpt.guide.reference_object->loc); + } + else { + UI_GetThemeColor4fv(TH_REDALERT, color); + copy_v3_v3(position, scene->cursor.location); + } + GPU_vertbuf_attr_set(vbo, pos_id, idx, position); + GPU_vertbuf_attr_set(vbo, size_id, idx, &size); + GPU_vertbuf_attr_set(vbo, color_id, idx, color); + } + return GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO); } diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_utils.c b/source/blender/draw/engines/gpencil/gpencil_draw_utils.c index 2e9a212170c..f925f9fb506 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_utils.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_utils.c @@ -357,7 +357,7 @@ bool DRW_gpencil_onion_active(bGPdata *gpd) /* create shading group for strokes */ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create( GPENCIL_e_data *e_data, GPENCIL_Data *vedata, DRWPass *pass, GPUShader *shader, Object *ob, - bGPdata *gpd, MaterialGPencilStyle *gp_style, int id, bool onion) + bGPdata *gpd, bGPDstroke *gps, MaterialGPencilStyle *gp_style, int id, bool onion) { GPENCIL_StorageList *stl = ((GPENCIL_Data *)vedata)->stl; const float *viewport_size = DRW_viewport_size_get(); @@ -391,6 +391,10 @@ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create( } DRW_shgroup_uniform_int(grp, "color_type", &stl->shgroups[id].color_type, 1); DRW_shgroup_uniform_float(grp, "pixfactor", &gpd->pixfactor, 1); + + stl->shgroups[id].caps_mode[0] = gps->caps[0]; + stl->shgroups[id].caps_mode[1] = gps->caps[1]; + DRW_shgroup_uniform_int(grp, "caps_mode", &stl->shgroups[id].caps_mode[0], 2); } else { stl->storage->obj_scale = 1.0f; @@ -405,6 +409,8 @@ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create( else { DRW_shgroup_uniform_float(grp, "pixfactor", &stl->storage->pixfactor, 1); } + const int zero[2] = { 0, 0 }; + DRW_shgroup_uniform_int(grp, "caps_mode", &zero[0], 2); } if ((gpd) && (id > -1)) { @@ -1177,7 +1183,8 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T if (gpd->runtime.sbuffer_size > 1) { if ((gp_style) && (gp_style->mode == GP_STYLE_MODE_LINE)) { stl->g_data->shgrps_drawing_stroke = DRW_gpencil_shgroup_stroke_create( - e_data, vedata, psl->drawing_pass, e_data->gpencil_stroke_sh, NULL, gpd, gp_style, -1, false); + e_data, vedata, psl->drawing_pass, e_data->gpencil_stroke_sh, NULL, + gpd, NULL, gp_style, -1, false); } else { stl->g_data->shgrps_drawing_stroke = DRW_gpencil_shgroup_point_create( @@ -1240,13 +1247,14 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T } } - /* control points */ - if ((overlay) && (gpd->runtime.tot_cp_points > 0) && - ((gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0) && - ((v3d->gizmo_flag & V3D_GIZMO_HIDE) == 0) && - ((v3d->gizmo_flag & V3D_GIZMO_HIDE_TOOL) == 0)) - { + /* control points for primitives and speed guide */ + const bool is_cppoint = (gpd->runtime.tot_cp_points > 0); + const bool is_speed_guide = (ts->gp_sculpt.guide.use_guide && (draw_ctx->object_mode == OB_MODE_PAINT_GPENCIL)); + const bool is_show_gizmo = (((v3d->gizmo_flag & V3D_GIZMO_HIDE) == 0) && ((v3d->gizmo_flag & V3D_GIZMO_HIDE_TOOL) == 0)); + if ((overlay) && (is_cppoint || is_speed_guide) && (is_show_gizmo) && + ((gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0)) + { DRWShadingGroup *shgrp = DRW_shgroup_create( e_data->gpencil_edit_point_sh, psl->drawing_pass); const float *viewport_size = DRW_viewport_size_get(); @@ -1357,7 +1365,7 @@ static void DRW_gpencil_shgroups_create( shgrp = DRW_gpencil_shgroup_stroke_create( e_data, vedata, psl->stroke_pass, e_data->gpencil_stroke_sh, - ob, gpd, gp_style, stl->storage->shgroup_id, elm->onion); + ob, gpd, gps, gp_style, stl->storage->shgroup_id, elm->onion); DRW_shgroup_call_range_add( shgrp, cache->b_stroke.batch, @@ -1472,7 +1480,7 @@ void DRW_gpencil_populate_multiedit( ToolSettings *ts = scene->toolsettings; /* check if playing animation */ - bool playing = stl->storage->is_playing; + const bool playing = stl->storage->is_playing; /* calc max size of VBOs */ gpencil_calc_vertex(stl, cache_ob, cache, gpd, cfra_eval); @@ -1541,7 +1549,7 @@ void DRW_gpencil_populate_datablock( bGPDlayer *gpl_active = BKE_gpencil_layer_getactive(gpd); /* check if playing animation */ - bool playing = stl->storage->is_playing; + const bool playing = stl->storage->is_playing; GpencilBatchCache *cache = gpencil_batch_cache_get(ob, cfra_eval); @@ -1567,6 +1575,10 @@ void DRW_gpencil_populate_datablock( continue; } + const bool is_solomode = GPENCIL_PAINT_MODE(gpd) && + (!playing) && (!stl->storage->is_render) && + (gpl->flag & GP_LAYER_SOLO_MODE); + /* filter view layer to gp layers in the same view layer (for compo) */ if ((stl->storage->is_render) && (gpl->viewlayername[0] != '\0')) { if (!STREQ(view_layer->name, gpl->viewlayername)) { @@ -1586,6 +1598,11 @@ void DRW_gpencil_populate_datablock( if (gpf == NULL) continue; + /* if solo mode, display only frames with keyframe in the current frame */ + if ((is_solomode) && (gpf->framenum != remap_cfra)) { + continue; + } + opacity = gpl->opacity; /* if pose mode, maybe the overlay to fade geometry is enabled */ if ((draw_ctx->obact) && (draw_ctx->object_mode == OB_MODE_POSE) && diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index 6f2b40136ca..c560321df9f 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -114,6 +114,7 @@ typedef struct GPENCIL_shgroup { int texture_clamp; int fill_style; int keep_size; + int caps_mode[2]; float obj_scale; } GPENCIL_shgroup; @@ -364,7 +365,8 @@ typedef struct GpencilBatchCache { /* general drawing functions */ struct DRWShadingGroup *DRW_gpencil_shgroup_stroke_create( struct GPENCIL_e_data *e_data, struct GPENCIL_Data *vedata, struct DRWPass *pass, struct GPUShader *shader, - struct Object *ob, struct bGPdata *gpd, struct MaterialGPencilStyle *gp_style, int id, bool onion); + struct Object *ob, struct bGPdata *gpd, struct bGPDstroke *gps, + struct MaterialGPencilStyle *gp_style, int id, bool onion); void DRW_gpencil_populate_datablock( struct GPENCIL_e_data *e_data, void *vedata, struct Object *ob, struct tGPencilObjectCache *cache_ob); diff --git a/source/blender/draw/engines/gpencil/shaders/gpencil_stroke_geom.glsl b/source/blender/draw/engines/gpencil/shaders/gpencil_stroke_geom.glsl index c2a5f6b0b84..ad85046487b 100644 --- a/source/blender/draw/engines/gpencil/shaders/gpencil_stroke_geom.glsl +++ b/source/blender/draw/engines/gpencil/shaders/gpencil_stroke_geom.glsl @@ -2,6 +2,7 @@ uniform mat4 ModelViewProjectionMatrix; uniform vec2 Viewport; uniform int xraymode; uniform int color_type; +uniform int caps_mode[2]; layout(lines_adjacency) in; layout(triangle_strip, max_vertices = 13) out; @@ -23,6 +24,8 @@ out vec2 uvfac; #define GPENCIL_COLOR_TEXTURE 1 #define GPENCIL_COLOR_PATTERN 2 +#define GPENCIL_FLATCAP 1 + /* project 3d point to 2d on screen space */ vec2 toScreenSpace(vec4 vertex) { @@ -159,7 +162,9 @@ void main(void) } /* generate the start endcap */ - if (is_equal(P0,P2) && (color_type == GPENCIL_COLOR_SOLID)){ + if ((caps_mode[0] != GPENCIL_FLATCAP) && is_equal(P0,P2) && + (color_type == GPENCIL_COLOR_SOLID)) + { vec4 cap_color = finalColor[1]; mTexCoord = vec2(2.0, 1.0); @@ -205,7 +210,9 @@ void main(void) EmitVertex(); /* generate the end endcap */ - if (is_equal(P1,P3) && (color_type == GPENCIL_COLOR_SOLID) && (finaluvdata[2].x > 0)){ + if ((caps_mode[1] != GPENCIL_FLATCAP) && is_equal(P1,P3) && + (color_type == GPENCIL_COLOR_SOLID) && (finaluvdata[2].x > 0)) + { vec4 cap_color = finalColor[2]; mTexCoord = vec2(finaluvdata[2].x, 2.0); diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index c6f88287e3e..7991c317468 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -907,7 +907,7 @@ static void gp_stroke_eraser_dostroke( /* Second Pass: Remove any points that are tagged */ if (do_cull) { - gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false); + gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } } } diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index a6ebc81c178..45653ac69df 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -40,6 +40,7 @@ #include "BLI_blenlib.h" #include "BLI_ghash.h" +#include "BLI_lasso_2d.h" #include "BLI_math.h" #include "BLI_string.h" #include "BLI_string_utils.h" @@ -86,6 +87,7 @@ #include "ED_object.h" #include "ED_screen.h" #include "ED_view3d.h" +#include "ED_select_utils.h" #include "ED_space_api.h" #include "DEG_depsgraph.h" @@ -260,7 +262,7 @@ void GPENCIL_OT_selectmode_toggle(wmOperatorType *ot) ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; /* properties */ - prop = RNA_def_int(ot->srna, "mode", 0, 0, 1, "Select mode", "Select mode", 0, 1); + prop = RNA_def_int(ot->srna, "mode", 0, 0, 2, "Select mode", "Select mode", 0, 2); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } @@ -1829,9 +1831,10 @@ typedef struct tGPDeleteIsland { * - Once we start having larger islands than that, the number required * becomes much less * 2) Each island gets converted to a new stroke + * If the number of points is <= limit, the stroke is deleted */ void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, - int tag_flags, bool select) + int tag_flags, bool select, int limit) { tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands"); bool in_island = false; @@ -1929,12 +1932,17 @@ void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke } } - /* Add new stroke to the frame */ - if (next_stroke) { - BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); + /* Add new stroke to the frame or delete if below limit */ + if ((limit > 0) && (new_stroke->totpoints <= limit)) { + BKE_gpencil_free_stroke(new_stroke); } else { - BLI_addtail(&gpf->strokes, new_stroke); + if (next_stroke) { + BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke); + } + else { + BLI_addtail(&gpf->strokes, new_stroke); + } } } } @@ -1995,7 +2003,7 @@ static int gp_delete_selected_points(bContext *C) gps->flag &= ~GP_STROKE_SELECT; /* delete unwanted points by splitting stroke into several smaller ones */ - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false); + gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0); changed = true; } @@ -2507,6 +2515,102 @@ void GPENCIL_OT_stroke_cyclical_set(wmOperatorType *ot) ot->prop = RNA_def_enum(ot->srna, "type", cyclic_type, GP_STROKE_CYCLIC_TOGGLE, "Type", ""); } +/* ******************* Flat Stroke Caps ************************** */ + +enum { + GP_STROKE_CAPS_TOGGLE_BOTH = 0, + GP_STROKE_CAPS_TOGGLE_START = 1, + GP_STROKE_CAPS_TOGGLE_END = 2, + GP_STROKE_CAPS_TOGGLE_DEFAULT = 3 +}; + +static int gp_stroke_caps_set_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + Object *ob = CTX_data_active_object(C); + + const int type = RNA_enum_get(op->ptr, "type"); + + /* sanity checks */ + if (ELEM(NULL, gpd)) + return OPERATOR_CANCELLED; + + /* loop all selected strokes */ + CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers) + { + if (gpl->actframe == NULL) + continue; + + for (bGPDstroke *gps = gpl->actframe->strokes.last; gps; gps = gps->prev) { + MaterialGPencilStyle *gp_style = BKE_material_gpencil_settings_get(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) + continue; + /* skip hidden or locked colors */ + if (!gp_style || (gp_style->flag & GP_STYLE_COLOR_HIDE) || (gp_style->flag & GP_STYLE_COLOR_LOCKED)) + continue; + + if ((type == GP_STROKE_CAPS_TOGGLE_BOTH) || + (type == GP_STROKE_CAPS_TOGGLE_START)) + { + ++gps->caps[0]; + if (gps->caps[0] >= GP_STROKE_CAP_MAX) { + gps->caps[0] = GP_STROKE_CAP_ROUND; + } + } + if ((type == GP_STROKE_CAPS_TOGGLE_BOTH) || + (type == 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; + } + } + } + CTX_DATA_END; + + /* notifiers */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +/** + * Change Stroke caps mode Rounded or Flat + */ +void GPENCIL_OT_stroke_caps_set(wmOperatorType *ot) +{ + static const EnumPropertyItem toggle_type[] = { + {GP_STROKE_CAPS_TOGGLE_BOTH, "TOGGLE", 0, "Both", ""}, + {GP_STROKE_CAPS_TOGGLE_START, "START", 0, "Start", ""}, + {GP_STROKE_CAPS_TOGGLE_END, "END", 0, "End", ""}, + {GP_STROKE_CAPS_TOGGLE_DEFAULT, "TOGGLE", 0, "Default", "Set as default rounded"}, + {0, NULL, 0, NULL, NULL} + }; + + /* identifiers */ + ot->name = "Set Caps Mode"; + ot->idname = "GPENCIL_OT_stroke_caps_set"; + ot->description = "Change Stroke caps mode (rounded or flat)"; + + /* api callbacks */ + ot->exec = gp_stroke_caps_set_exec; + ot->poll = gp_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "type", toggle_type, GP_STROKE_CAPS_TOGGLE_BOTH, "Type", ""); +} + /* ******************* Stroke join ************************** */ /* Helper: flip stroke */ @@ -3472,10 +3576,10 @@ static int gp_stroke_separate_exec(bContext *C, wmOperator *op) } /* delete selected points from destination stroke */ - gp_stroke_delete_tagged_points(gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false); + gp_stroke_delete_tagged_points(gpf_dst, gps_dst, NULL, GP_SPOINT_SELECT, false, 0); /* delete selected points from origin stroke */ - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false); + gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0); } /* selected strokes mode */ else if (mode == GP_SEPARATE_STROKE) { @@ -3609,10 +3713,10 @@ static int gp_stroke_split_exec(bContext *C, wmOperator *UNUSED(op)) } /* delete selected points from destination stroke */ - gp_stroke_delete_tagged_points(gpf, gps_dst, NULL, GP_SPOINT_SELECT, true); + gp_stroke_delete_tagged_points(gpf, gps_dst, NULL, GP_SPOINT_SELECT, true, 0); /* delete selected points from origin stroke */ - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false); + gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT, false, 0); } } /* select again tagged points */ @@ -3702,3 +3806,241 @@ void GPENCIL_OT_stroke_smooth(wmOperatorType *ot) RNA_def_boolean(ot->srna, "smooth_strength", false, "Strength", ""); RNA_def_boolean(ot->srna, "smooth_uv", false, "UV", ""); } + +/* smart stroke cutter for trimming stroke ends */ +struct GP_SelectLassoUserData { + rcti rect; + const int(*mcords)[2]; + int mcords_len; +}; + +static bool gpencil_test_lasso( + bGPDstroke *gps, bGPDspoint *pt, + const GP_SpaceConversion *gsc, const float diff_mat[4][4], + void *user_data) +{ + const struct GP_SelectLassoUserData *data = user_data; + bGPDspoint pt2; + int x0, y0; + gp_point_to_parent_space(pt, diff_mat, &pt2); + gp_point_to_xy(gsc, gps, &pt2, &x0, &y0); + /* test if in lasso */ + return ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && + BLI_rcti_isect_pt(&data->rect, x0, y0) && + BLI_lasso_is_point_inside(data->mcords, data->mcords_len, x0, y0, INT_MAX)); +} + +typedef bool(*GPencilTestFn)( + bGPDstroke *gps, bGPDspoint *pt, + const GP_SpaceConversion *gsc, const float diff_mat[4][4], void *user_data); + +static void gpencil_cutter_dissolve(bGPDlayer *hit_layer, bGPDstroke *hit_stroke) +{ + bGPDspoint *pt = NULL; + bGPDspoint *pt1 = NULL; + int i; + + bGPDstroke *gpsn = hit_stroke->next; + + int totselect = 0; + for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + totselect++; + } + } + + /* if all points selected delete or only 2 points and 1 selected */ + if ((totselect == 1) && (hit_stroke->totpoints == 2) || + (hit_stroke->totpoints == totselect)) + { + BLI_remlink(&hit_layer->actframe->strokes, hit_stroke); + BKE_gpencil_free_stroke(hit_stroke); + hit_stroke = NULL; + } + + /* if very small distance delete */ + if ((hit_stroke) && (hit_stroke->totpoints == 2)) { + pt = &hit_stroke->points[0]; + pt1 = &hit_stroke->points[1]; + if (len_v3v3(&pt->x, &pt1->x) < 0.001f) { + BLI_remlink(&hit_layer->actframe->strokes, hit_stroke); + BKE_gpencil_free_stroke(hit_stroke); + hit_stroke = NULL; + } + } + + if (hit_stroke) { + /* tag and dissolve (untag new points) */ + for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) { + if (pt->flag & GP_SPOINT_SELECT) { + pt->flag &= ~GP_SPOINT_SELECT; + pt->flag |= GP_SPOINT_TAG; + } + else if (pt->flag & GP_SPOINT_TAG) { + pt->flag &= ~GP_SPOINT_TAG; + } + } + gp_stroke_delete_tagged_points( + hit_layer->actframe, hit_stroke, gpsn, GP_SPOINT_TAG, false, 1); + } +} + +static int gpencil_cutter_lasso_select( + bContext *C, wmOperator *op, + GPencilTestFn is_inside_fn, void *user_data) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + ScrArea *sa = CTX_wm_area(C); + ToolSettings *ts = CTX_data_tool_settings(C); + const float scale = ts->gp_sculpt.isect_threshold; + + bGPDspoint *pt; + int i; + GP_SpaceConversion gsc = { NULL }; + + bool changed = false; + + /* sanity checks */ + if (sa == NULL) { + BKE_report(op->reports, RPT_ERROR, "No active area"); + return OPERATOR_CANCELLED; + } + + /* init space conversion stuff */ + gp_point_conversion_init(C, &gsc); + + /* deselect all strokes first */ + CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) + { + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + pt->flag &= ~GP_SPOINT_SELECT; + } + + gps->flag &= ~GP_STROKE_SELECT; + } + CTX_DATA_END; + + /* select points */ + GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) + { + int tot_inside = 0; + const int oldtot = gps->totpoints; + for (i = 0; i < gps->totpoints; i++) { + pt = &gps->points[i]; + if ((pt->flag & GP_SPOINT_SELECT) || (pt->flag & GP_SPOINT_TAG)) { + continue; + } + /* convert point coords to screenspace */ + const bool is_inside = is_inside_fn(gps, pt, &gsc, gpstroke_iter.diff_mat, user_data); + if (is_inside) { + tot_inside++; + changed = true; + pt->flag |= GP_SPOINT_SELECT; + gps->flag |= GP_STROKE_SELECT; + float r_hita[3], r_hitb[3]; + if (gps->totpoints > 1) { + ED_gpencil_select_stroke_segment( + gpl, gps, pt, true, true, scale, r_hita, r_hitb); + } + /* avoid infinite loops */ + if (gps->totpoints > oldtot) { + break; + } + } + } + /* if mark all points inside lasso set to remove all stroke */ + if ((tot_inside == oldtot) || + ((tot_inside == 1) && (oldtot == 2))) + { + for (i = 0; i < gps->totpoints; i++) { + pt = &gps->points[i]; + pt->flag |= GP_SPOINT_SELECT; + } + } + } + GP_EDITABLE_STROKES_END(gpstroke_iter); + + /* dissolve selected points */ + bGPDstroke *gpsn; + for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) { + bGPDframe *gpf = gpl->actframe; + if (gpf == NULL) { + continue; + } + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; + if (gps->flag & GP_STROKE_SELECT) { + gpencil_cutter_dissolve(gpl, gps); + } + } + } + + /* updates */ + if (changed) { + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | 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); + } + + return OPERATOR_FINISHED; +} + +static bool gpencil_cutter_poll(bContext *C) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + + if (GPENCIL_PAINT_MODE(gpd)) { + if (gpd->layers.first) + return true; + } + + return false; +} + +static int gpencil_cutter_exec(bContext *C, wmOperator *op) +{ + ScrArea *sa = CTX_wm_area(C); + /* sanity checks */ + if (sa == NULL) { + BKE_report(op->reports, RPT_ERROR, "No active area"); + return OPERATOR_CANCELLED; + } + + struct GP_SelectLassoUserData data = { 0 }; + data.mcords = WM_gesture_lasso_path_to_array(C, op, &data.mcords_len); + + /* Sanity check. */ + if (data.mcords == NULL) { + return OPERATOR_PASS_THROUGH; + } + + /* Compute boundbox of lasso (for faster testing later). */ + BLI_lasso_boundbox(&data.rect, data.mcords, data.mcords_len); + + gpencil_cutter_lasso_select(C, op, gpencil_test_lasso, &data); + + MEM_freeN((void *)data.mcords); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_stroke_cutter(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Stroke Cutter"; + ot->description = "Select section and cut"; + ot->idname = "GPENCIL_OT_stroke_cutter"; + + /* callbacks */ + ot->invoke = WM_gesture_lasso_invoke; + ot->modal = WM_gesture_lasso_modal; + ot->exec = gpencil_cutter_exec; + ot->poll = gpencil_cutter_poll; + ot->cancel = WM_gesture_lasso_cancel; + + /* flag */ + ot->flag = OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA; + + /* properties */ + WM_operator_properties_gesture_lasso(ot); +} diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index e6ef70009c8..56ddf81f357 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -234,7 +234,9 @@ void gp_apply_parent(struct Depsgraph *depsgraph, struct Object *obact, bGPdata */ void gp_apply_parent_point(struct Depsgraph *depsgraph, struct Object *obact, bGPdata *gpd, bGPDlayer *gpl, bGPDspoint *pt); -bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, struct Scene *scene, const float screen_co[2], float r_out[3]); +void gp_point_3d_to_xy(const GP_SpaceConversion *gsc, const short flag, const float pt[3], float xy[2]); + +bool gp_point_xy_to_3d(const GP_SpaceConversion *gsc, struct Scene *scene, const float screen_co[2], float r_out[3]); /* helper to convert 2d to 3d */ void gp_stroke_convertcoords_tpoint( @@ -263,7 +265,7 @@ struct GHash *gp_copybuf_validate_colormap(struct bContext *C); void gp_stroke_delete_tagged_points( bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, - int tag_flags, bool select); + int tag_flags, bool select, int limit); int gp_delete_selected_point_wrap(bContext *C); void gp_subdivide_stroke(bGPDstroke *gps, const int subdivide); @@ -291,12 +293,17 @@ void GPENCIL_OT_annotate(struct wmOperatorType *ot); void GPENCIL_OT_draw(struct wmOperatorType *ot); void GPENCIL_OT_fill(struct wmOperatorType *ot); +/* Guides ----------------------- */ + +void GPENCIL_OT_guide_rotate(struct wmOperatorType *ot); + /* Paint Modes for operator */ typedef enum eGPencil_PaintModes { GP_PAINTMODE_DRAW = 0, GP_PAINTMODE_ERASER, GP_PAINTMODE_DRAW_STRAIGHT, - GP_PAINTMODE_DRAW_POLY + GP_PAINTMODE_DRAW_POLY, + GP_PAINTMODE_SET_CP } eGPencil_PaintModes; /* maximum sizes of gp-session buffer */ @@ -384,7 +391,7 @@ enum { GP_STROKE_LINE = 1, GP_STROKE_CIRCLE = 2, GP_STROKE_ARC = 3, - GP_STROKE_CURVE = 4 + GP_STROKE_CURVE = 4, }; enum { @@ -397,6 +404,7 @@ void GPENCIL_OT_stroke_change_color(struct wmOperatorType *ot); void GPENCIL_OT_stroke_lock_color(struct wmOperatorType *ot); void GPENCIL_OT_stroke_apply_thickness(struct wmOperatorType *ot); void GPENCIL_OT_stroke_cyclical_set(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_caps_set(struct wmOperatorType *ot); void GPENCIL_OT_stroke_join(struct wmOperatorType *ot); void GPENCIL_OT_stroke_flip(struct wmOperatorType *ot); void GPENCIL_OT_stroke_subdivide(struct wmOperatorType *ot); @@ -406,6 +414,7 @@ void GPENCIL_OT_stroke_separate(struct wmOperatorType *ot); void GPENCIL_OT_stroke_split(struct wmOperatorType *ot); void GPENCIL_OT_stroke_smooth(struct wmOperatorType *ot); void GPENCIL_OT_stroke_merge(struct wmOperatorType *ot); +void GPENCIL_OT_stroke_cutter(struct wmOperatorType *ot); void GPENCIL_OT_brush_presets_create(struct wmOperatorType *ot); diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c index 3641308ae17..830b4a1738f 100644 --- a/source/blender/editors/gpencil/gpencil_merge.c +++ b/source/blender/editors/gpencil/gpencil_merge.c @@ -194,7 +194,7 @@ static void gpencil_dissolve_points(bContext *C) for (gps = gpf->strokes.first; gps; gps = gpsn) { gpsn = gps->next; - gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_TAG, false); + gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_TAG, false, 0); } } CTX_DATA_END; diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 7814aa963ba..8493244998d 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -228,6 +228,10 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_draw); WM_operatortype_append(GPENCIL_OT_fill); + /* Guides ----------------------- */ + + WM_operatortype_append(GPENCIL_OT_guide_rotate); + /* Editing (Strokes) ------------ */ WM_operatortype_append(GPENCIL_OT_editmode_toggle); @@ -301,6 +305,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_stroke_lock_color); WM_operatortype_append(GPENCIL_OT_stroke_apply_thickness); WM_operatortype_append(GPENCIL_OT_stroke_cyclical_set); + WM_operatortype_append(GPENCIL_OT_stroke_caps_set); WM_operatortype_append(GPENCIL_OT_stroke_join); WM_operatortype_append(GPENCIL_OT_stroke_flip); WM_operatortype_append(GPENCIL_OT_stroke_subdivide); @@ -310,6 +315,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_stroke_split); WM_operatortype_append(GPENCIL_OT_stroke_smooth); WM_operatortype_append(GPENCIL_OT_stroke_merge); + WM_operatortype_append(GPENCIL_OT_stroke_cutter); WM_operatortype_append(GPENCIL_OT_brush_presets_create); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 6543a65f67f..16e154b0289 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -18,7 +18,7 @@ * The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung * This is a new part of Blender * - * Contributor(s): Joshua Leung, Antonio Vazquez + * Contributor(s): Joshua Leung, Antonio Vazquez, Charlie Jolly * * ***** END GPL LICENSE BLOCK ***** */ @@ -119,9 +119,9 @@ typedef enum eGPencil_PaintFlags { GP_PAINTFLAG_SELECTMASK = (1 << 3), GP_PAINTFLAG_HARD_ERASER = (1 << 4), GP_PAINTFLAG_STROKE_ERASER = (1 << 5), + GP_PAINTFLAG_REQ_VECTOR = (1 << 6), } eGPencil_PaintFlags; - /* Temporary 'Stroke' Operation data * "p" = op->customdata */ @@ -177,6 +177,8 @@ typedef struct tGPsdata { float mval[2]; /** previous recorded mouse-position. */ float mvalo[2]; + /** initial recorded mouse-position */ + float mvali[2]; /** current stylus pressure. */ float pressure; @@ -206,35 +208,45 @@ typedef struct tGPsdata { void *erasercursor; /* mat settings are only used for 3D view */ - /** current material. */ + /** current material */ Material *material; - - /** current drawing brush. */ + /** current drawing brush */ Brush *brush; - /** default eraser brush. */ + /** default eraser brush */ Brush *eraser; - /** 1: line horizontal, 2: line vertical, other: not defined, second element position. */ - short straight[2]; - /** lock drawing to one axis. */ + + /** 1: line horizontal, 2: line vertical, other: not defined */ + short straight; + /** lock drawing to one axis */ int lock_axis; - /** the stroke is no fill mode. */ + /** the stroke is no fill mode */ bool disable_fill; RNG *rng; - /** key used for invoking the operator. */ + /** key used for invoking the operator */ short keymodifier; - /** shift modifier flag. */ + /** shift modifier flag */ short shift; - - /** size in pixels for uv calculation. */ + /** size in pixels for uv calculation */ float totpixlen; + /* guide */ + /** guide spacing */ + float guide_spacing; + /** half guide spacing */ + float half_spacing; + /** origin */ + float origin[2]; + ReportList *reports; } tGPsdata; /* ------ */ +#define STROKE_HORIZONTAL 1 +#define STROKE_VERTICAL 2 + /* Macros for accessing sensitivity thresholds... */ /* minimum number of pixels mouse should move before new point created */ #define MIN_MANHATTEN_PX (U.gp_manhattendist) @@ -330,11 +342,11 @@ static void gp_get_3d_reference(tGPsdata *p, float vec[3]) /* Stroke Editing ---------------------------- */ /* check if the current mouse position is suitable for adding a new point */ -static bool gp_stroke_filtermval(tGPsdata *p, const float mval[2], float pmval[2]) +static bool gp_stroke_filtermval(tGPsdata *p, const float mval[2], float mvalo[2]) { Brush *brush = p->brush; - int dx = (int)fabsf(mval[0] - pmval[0]); - int dy = (int)fabsf(mval[1] - pmval[1]); + int dx = (int)fabsf(mval[0] - mvalo[0]); + int dy = (int)fabsf(mval[1] - mvalo[1]); brush->gpencil_settings->flag &= ~GP_BRUSH_STABILIZE_MOUSE_TEMP; /* if buffer is empty, just let this go through (i.e. so that dots will work) */ @@ -422,7 +434,7 @@ static void gp_stroke_convertcoords(tGPsdata *p, const float mval[2], float out[ * - nothing more needs to be done here, since view_autodist_simple() has already done it */ - /* verify valid zdepth, if it's wrong, the default darwing mode is used + /* verify valid zdepth, if it's wrong, the default drawing mode is used * and the function doesn't return now */ if ((depth == NULL) || (*depth <= 1.0f)) { return; @@ -1607,7 +1619,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p, gp_stroke_soft_refine(gps, cull_thresh); } - gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false); + gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false, 0); } gp_update_cache(p->gpd); } @@ -2019,7 +2031,7 @@ static void gp_session_cleanup(tGPsdata *p) /* free stroke buffer */ if (gpd->runtime.sbuffer) { /* printf("\t\tGP - free sbuffer\n"); */ - MEM_freeN(gpd->runtime.sbuffer); + MEM_SAFE_FREE(gpd->runtime.sbuffer); gpd->runtime.sbuffer = NULL; } @@ -2034,10 +2046,10 @@ static void gp_session_free(tGPsdata *p) if (p->rng != NULL) { BLI_rng_free(p->rng); } + MEM_freeN(p); } - /* init new stroke */ static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Depsgraph *depsgraph) { @@ -2472,8 +2484,19 @@ static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) ED_workspace_status_text(C, IFACE_("Grease Pencil Line Session: Hold and drag LMB to draw | " "ESC/Enter to end (or click outside this area)")); break; + case GP_PAINTMODE_SET_CP: + ED_workspace_status_text(C, IFACE_("Grease Pencil Guides: LMB click and release to place reference point | " + "Esc/RMB to cancel")); + break; case GP_PAINTMODE_DRAW: - ED_workspace_status_text(C, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw")); + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; + if (guide->use_guide) { + ED_workspace_status_text(C, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw | " + "M key to flip guide | O key to move reference point")); + } + else { + ED_workspace_status_text(C, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw")); + } break; case GP_PAINTMODE_DRAW_POLY: ED_workspace_status_text(C, IFACE_("Grease Pencil Poly Session: LMB click to place next stroke vertex | " @@ -2510,8 +2533,7 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra gp_stroke_doeraser(p); /* store used values */ - p->mvalo[0] = p->mval[0]; - p->mvalo[1] = p->mval[1]; + copy_v2_v2(p->mvalo, p->mval); p->opressure = p->pressure; } /* only add current point to buffer if mouse moved (even though we got an event, it might be just noise) */ @@ -2560,8 +2582,7 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra } /* store used values */ - p->mvalo[0] = p->mval[0]; - p->mvalo[1] = p->mval[1]; + copy_v2_v2(p->mvalo, p->mval); p->opressure = p->pressure; p->ocurtime = p->curtime; @@ -2580,45 +2601,99 @@ static void gpencil_draw_apply(bContext *C, wmOperator *op, tGPsdata *p, Depsgra } } +/* Helper to rotate point around origin */ +static void gp_rotate_v2_v2v2fl(float v[2], const float p[2], const float origin[2], const float angle) +{ + float pt[2]; + float r[2]; + sub_v2_v2v2(pt, p, origin); + rotate_v2_v2fl(r, pt, angle); + add_v2_v2v2(v, r, origin); +} + +/* Helper to snap value to grid */ +static float gp_snap_to_grid_fl(float v, const float offset, const float spacing) +{ + if (spacing > 0.0f) + return roundf(v / spacing) * spacing + fmodf(offset, spacing); + else + return v; +} + +static void gp_snap_to_grid_v2(float v[2], const float offset[2], const float spacing) +{ + v[0] = gp_snap_to_grid_fl(v[0], offset[0], spacing); + v[1] = gp_snap_to_grid_fl(v[1], offset[1], spacing); +} + +/* get reference point - screen coords to buffer coords */ +static void gp_origin_set(wmOperator *op, const int mval[2]) +{ + tGPsdata *p = op->customdata; + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; + float origin[2]; + float point[3]; + copy_v2fl_v2i(origin, mval); + gp_stroke_convertcoords(p, origin, point, NULL); + if (guide->reference_point == GP_GUIDE_REF_CUSTOM) { + copy_v3_v3(guide->location, point); + } + else if (guide->reference_point == GP_GUIDE_REF_CURSOR) { + copy_v3_v3(p->scene->cursor.location, point); + } +} + +/* get reference point - buffer coords to screen coords */ +static void gp_origin_get(tGPsdata *p, float origin[2]) +{ + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; + float location[3]; + if (guide->reference_point == GP_GUIDE_REF_CUSTOM) { + copy_v3_v3(location, guide->location); + } + else if (guide->reference_point == GP_GUIDE_REF_OBJECT && + guide->reference_object != NULL) { + copy_v3_v3(location, guide->reference_object->loc); + } + else { + copy_v3_v3(location, p->scene->cursor.location); + } + GP_SpaceConversion *gsc = &p->gsc; + gp_point_3d_to_xy(gsc, p->gpd->runtime.sbuffer_sflag, location, origin); +} + /* handle draw event */ static void gpencil_draw_apply_event(bContext *C, wmOperator *op, const wmEvent *event, Depsgraph *depsgraph, float x, float y) { tGPsdata *p = op->customdata; + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; PointerRNA itemptr; float mousef[2]; int tablet = 0; /* convert from window-space to area-space mouse coordinates * add any x,y override position for fake events - * NOTE: float to ints conversions, +1 factor is probably used to ensure a bit more accurate rounding... */ - p->mval[0] = event->mval[0] + 1.0f - x; - p->mval[1] = event->mval[1] + 1.0f - y; + p->mval[0] = (float)event->mval[0] - x; + p->mval[1] = (float)event->mval[1] - y; p->shift = event->shift; - /* verify key status for straight lines */ - if ((event->alt > 0) && (RNA_boolean_get(op->ptr, "disable_straight") == false)) { - if (p->straight[0] == 0) { - int dx = (int)fabsf(p->mval[0] - p->mvalo[0]); - int dy = (int)fabsf(p->mval[1] - p->mvalo[1]); + /* verify direction for straight lines */ + if ((guide->use_guide) || ((event->alt > 0) && (RNA_boolean_get(op->ptr, "disable_straight") == false))) { + if (p->straight == 0) { + int dx = (int)fabsf(p->mval[0] - p->mvali[0]); + int dy = (int)fabsf(p->mval[1] - p->mvali[1]); if ((dx > 0) || (dy > 0)) { - /* check mouse direction to replace the other coordinate with previous values */ - if (dx >= dy) { - /* horizontal */ - p->straight[0] = 1; - p->straight[1] = (short)p->mval[1]; /* save y */ + /* store mouse direction */ + if (dx > dy) { + p->straight = STROKE_HORIZONTAL; } - else { - /* vertical */ - p->straight[0] = 2; - p->straight[1] = (short)p->mval[0]; /* save x */ + else if (dx < dy) { + p->straight = STROKE_VERTICAL; } } } } - else { - p->straight[0] = 0; - } p->curtime = PIL_check_seconds_timer(); @@ -2666,29 +2741,164 @@ static void gpencil_draw_apply_event(bContext *C, wmOperator *op, const wmEvent if (p->flags & GP_PAINTFLAG_FIRSTRUN) { p->flags &= ~GP_PAINTFLAG_FIRSTRUN; - p->mvalo[0] = p->mval[0]; - p->mvalo[1] = p->mval[1]; + /* set values */ + copy_v2_v2(p->mvalo, p->mval); p->opressure = p->pressure; p->inittime = p->ocurtime = p->curtime; - p->straight[0] = 0; - p->straight[1] = 0; + p->straight = 0; + + /* save initial mouse */ + copy_v2_v2(p->mvali, p->mval); + + /* calculate once and store snapping distance and origin */ + RegionView3D * rv3d = p->ar->regiondata; + float scale = 1.0f; + if (rv3d->is_persp) { + float vec[3]; + gp_get_3d_reference(p, vec); + mul_m4_v3(rv3d->persmat, vec); + scale = vec[2] * rv3d->pixsize; + } + else { + scale = rv3d->pixsize; + } + p->guide_spacing = guide->spacing / scale; + p->half_spacing = p->guide_spacing * 0.5f; + gp_origin_get(p, p->origin); /* special exception here for too high pressure values on first touch in * windows for some tablets, then we just skip first touch... */ - if (tablet && (p->pressure >= 0.99f)) + if (tablet && (p->pressure >= 0.99f)) { return; + } + + /* special exception for grid snapping + * it requires direction which needs at least two points + */ + if (!ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP) && + guide->use_guide && + guide->use_snapping && + (guide->type == GP_GUIDE_GRID)) { + p->flags |= GP_PAINTFLAG_REQ_VECTOR; + } } - /* check if alt key is pressed and limit to straight lines */ - if ((p->paintmode != GP_PAINTMODE_ERASER) && (p->straight[0] != 0)) { - if (p->straight[0] == 1) { - /* horizontal */ - p->mval[1] = p->straight[1]; /* replace y */ + /* wait for vector then add initial point */ + if (p->flags & GP_PAINTFLAG_REQ_VECTOR) { + if (p->straight == 0) { + return; + } + + p->flags &= ~GP_PAINTFLAG_REQ_VECTOR; + + /* create fake events */ + float tmp[2]; + float pt[2]; + copy_v2_v2(tmp, p->mval); + sub_v2_v2v2(pt, p->mval, p->mvali); + gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), pt[0], pt[1]); + if (len_v2v2(p->mval, p->mvalo)) { + sub_v2_v2v2(pt, p->mval, p->mvalo); + gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), pt[0], pt[1]); + } + copy_v2_v2(p->mval, tmp); + } + + /* check if stroke is straight or guided */ + if ((p->paintmode != GP_PAINTMODE_ERASER) + && ((p->straight) || (guide->use_guide))) { + /* guided stroke */ + if (guide->use_guide) { + switch (guide->type) { + default: + case GP_GUIDE_CIRCULAR: + { + float distance; + distance = len_v2v2(p->mvali, p->origin); + + if (guide->use_snapping && (guide->spacing > 0.0f)) { + distance = gp_snap_to_grid_fl(distance, 0.0f, p->guide_spacing); + } + + dist_ensure_v2_v2fl(p->mval, p->origin, distance); + } + break; + case GP_GUIDE_RADIAL: + { + if (guide->use_snapping && + (guide->angle_snap > 0.0f)) { + float point[2]; + float xy[2]; + float angle; + float half_angle = guide->angle_snap * 0.5f; + sub_v2_v2v2(xy, p->mvali, p->origin); + angle = atan2f(xy[1], xy[0]); + angle += (M_PI * 2.0f); + angle = fmodf(angle + half_angle, guide->angle_snap); + angle -= half_angle; + gp_rotate_v2_v2v2fl(point, p->mvali, p->origin, -angle); + closest_to_line_v2(p->mval, p->mval, point, p->origin); + } + else { + closest_to_line_v2(p->mval, p->mval, p->mvali, p->origin); + } + } + break; + case GP_GUIDE_PARALLEL: + { + float point[2]; + float unit[2]; + copy_v2_v2(unit, p->mvali); + unit[0] += 1.0f; /* start from horizontal */ + gp_rotate_v2_v2v2fl(point, unit, p->mvali, guide->angle); + closest_to_line_v2(p->mval, p->mval, p->mvali, point); + + if (guide->use_snapping && + (guide->spacing > 0.0f)) { + gp_rotate_v2_v2v2fl(p->mval, p->mval, p->origin, -guide->angle); + p->mval[1] = gp_snap_to_grid_fl(p->mval[1] - p->half_spacing, p->origin[1], p->guide_spacing); + gp_rotate_v2_v2v2fl(p->mval, p->mval, p->origin, guide->angle); + } + + } + break; + case GP_GUIDE_GRID: + { + if (guide->use_snapping && + (guide->spacing > 0.0f)) { + + float point[2]; + float unit[2]; + float angle; + copy_v2_v2(unit, p->mvali); + unit[0] += 1.0f; /* start from horizontal */ + angle = (p->straight == STROKE_VERTICAL) ? M_PI_2 : 0.0f; + gp_rotate_v2_v2v2fl(point, unit, p->mvali, angle); + closest_to_line_v2(p->mval, p->mval, p->mvali, point); + + if (p->straight == STROKE_HORIZONTAL) { + p->mval[1] = gp_snap_to_grid_fl(p->mval[1] - p->half_spacing, p->origin[1], p->guide_spacing); + } + else { + p->mval[0] = gp_snap_to_grid_fl(p->mval[0] - p->half_spacing, p->origin[0], p->guide_spacing); + } + } + else if (p->straight == STROKE_HORIZONTAL) { + p->mval[1] = p->mvali[1]; /* replace y */ + } + else { + p->mval[0] = p->mvali[0]; /* replace x */ + } + } + break; + } + } + else if (p->straight == STROKE_HORIZONTAL) { + p->mval[1] = p->mvali[1]; /* replace y */ } else { - /* vertical */ - p->mval[0] = p->straight[1]; /* replace x */ + p->mval[0] = p->mvali[0]; /* replace x */ } } @@ -2742,8 +2952,8 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op) /* get relevant data for this point from stroke */ RNA_float_get_array(&itemptr, "mouse", mousef); - p->mval[0] = (int)mousef[0]; - p->mval[1] = (int)mousef[1]; + p->mval[0] = mousef[0]; + p->mval[1] = mousef[1]; p->pressure = RNA_float_get(&itemptr, "pressure"); p->curtime = (double)RNA_float_get(&itemptr, "time") + p->inittime; @@ -2787,6 +2997,93 @@ static int gpencil_draw_exec(bContext *C, wmOperator *op) /* ------------------------------- */ +/* handle events for guides */ +static void gpencil_guide_event_handling(bContext *C, wmOperator *op, const wmEvent *event, tGPsdata *p) +{ + bool add_notifier = false; + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; + + /* Enter or exit set center point mode */ + if ((event->type == OKEY) && (event->val == KM_RELEASE)) { + if (p->paintmode == GP_PAINTMODE_DRAW && guide->reference_point != GP_GUIDE_REF_OBJECT) { + add_notifier = true; + p->paintmode = GP_PAINTMODE_SET_CP; + ED_gpencil_toggle_brush_cursor(C, false, NULL); + } + } + /* Freehand mode, turn off speed guide */ + else if ((event->type == VKEY) && (event->val == KM_RELEASE)) { + guide->use_guide = false; + add_notifier = true; + } + /* Alternate or flip direction */ + else if ((event->type == MKEY) && (event->val == KM_RELEASE)) { + if (guide->type == GP_GUIDE_CIRCULAR) { + add_notifier = true; + guide->type = GP_GUIDE_RADIAL; + } + else if (guide->type == GP_GUIDE_RADIAL) { + add_notifier = true; + guide->type = GP_GUIDE_CIRCULAR; + } + else if (guide->type == GP_GUIDE_PARALLEL) { + add_notifier = true; + guide->angle += M_PI_2; + guide->angle = angle_compat_rad(guide->angle, M_PI); + } + else { + add_notifier = false; + } + } + /* Line guides */ + else if ((event->type == LKEY) && (event->val == KM_RELEASE)) { + add_notifier = true; + guide->use_guide = true; + if (event->ctrl) { + guide->angle = 0.0f; + guide->type = GP_GUIDE_PARALLEL; + } + else if (event->alt) { + guide->type = GP_GUIDE_PARALLEL; + guide->angle = RNA_float_get(op->ptr, "guide_last_angle"); + } + else { + guide->type = GP_GUIDE_PARALLEL; + } + } + /* Point guide */ + else if ((event->type == CKEY) && (event->val == KM_RELEASE)) { + add_notifier = true; + guide->use_guide = true; + if (guide->type == GP_GUIDE_CIRCULAR) { + guide->type = GP_GUIDE_RADIAL; + } + else if (guide->type == GP_GUIDE_RADIAL) { + guide->type = GP_GUIDE_CIRCULAR; + } + else { + guide->type = GP_GUIDE_CIRCULAR; + } + } + /* Change line angle */ + else if (ELEM(event->type, JKEY, KKEY) && (event->val == KM_RELEASE)) { + add_notifier = true; + float angle = guide->angle; + float adjust = (float)M_PI / 180.0f; + if (event->alt) + adjust *= 45.0f; + else if (!event->shift) + adjust *= 15.0f; + angle += (event->type == JKEY) ? adjust : -adjust; + angle = angle_compat_rad(angle, M_PI); + guide->angle = angle; + } + + if (add_notifier) { + WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS | NC_GPENCIL | NA_EDITED, NULL); + } +} + /* start of interactive drawing part of operator */ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event) { @@ -2836,7 +3133,7 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event p->status = GP_STATUS_PAINTING; /* handle the initial drawing - i.e. for just doing a simple dot */ - gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), 0, 0); + gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), 0.0f, 0.0f); op->flag |= OP_IS_MODAL_CURSOR_REGION; } else { @@ -2847,6 +3144,12 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event /* enable paint mode */ if (p->sa->spacetype == SPACE_VIEW3D) { + + /* handle speed guide events before drawing inside view3d */ + if (!ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP)) { + gpencil_guide_event_handling(C, op, event, p); + } + Object *ob = CTX_data_active_object(C); if (ob && (ob->type == OB_GPENCIL) && ((p->gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)) { /* FIXME: use the mode switching operator, this misses notifiers, messages. */ @@ -2952,12 +3255,21 @@ static void gpencil_move_last_stroke_to_back(bContext *C) static void gpencil_add_missing_events(bContext *C, wmOperator *op, const wmEvent *event, tGPsdata *p) { Brush *brush = p->brush; - if (brush->gpencil_settings->input_samples == 0) { + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; + int input_samples = brush->gpencil_settings->input_samples; + + /* ensure sampling when using circular guide */ + if (guide->use_guide && (guide->type == GP_GUIDE_CIRCULAR)) { + input_samples = GP_MAX_INPUT_SAMPLES; + } + + if (input_samples == 0) { return; } + RegionView3D *rv3d = p->ar->regiondata; float defaultpixsize = rv3d->pixsize * 1000.0f; - int samples = (GP_MAX_INPUT_SAMPLES - brush->gpencil_settings->input_samples + 1); + int samples = (GP_MAX_INPUT_SAMPLES - input_samples + 1); float thickness = (float)brush->size; float pt[2], a[2], b[2]; @@ -2991,8 +3303,8 @@ static void gpencil_add_missing_events(bContext *C, wmOperator *op, const wmEven float factor = ((thickness * dot_factor) / scale) * samples; copy_v2_v2(a, p->mvalo); - b[0] = event->mval[0] + 1; - b[1] = event->mval[1] + 1; + b[0] = (float)event->mval[0] + 1.0f; + b[1] = (float)event->mval[1] + 1.0f; /* get distance in pixels */ float dist = len_v2v2(a, b); @@ -3003,7 +3315,7 @@ static void gpencil_add_missing_events(bContext *C, wmOperator *op, const wmEven sub_v2_v2v2(pt, b, pt); /* create fake event */ gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), - (int)pt[0], (int)pt[1]); + pt[0], pt[1]); } else if (dist >= factor) { int slices = 2 + (int)((dist - 1.0) / factor); @@ -3013,7 +3325,7 @@ static void gpencil_add_missing_events(bContext *C, wmOperator *op, const wmEven sub_v2_v2v2(pt, b, pt); /* create fake event */ gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), - (int)pt[0], (int)pt[1]); + pt[0], pt[1]); } } } @@ -3023,6 +3335,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) { tGPsdata *p = op->customdata; ToolSettings *ts = CTX_data_tool_settings(C); + GP_Sculpt_Guide *guide = &p->scene->toolsettings->gp_sculpt.guide; /* default exit state - pass through to support MMB view nav, etc. */ int estate = OPERATOR_PASS_THROUGH; @@ -3044,6 +3357,43 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) p->ar = ar; } + /* special mode for editing control points */ + if (p->paintmode == GP_PAINTMODE_SET_CP) { + wmWindow *win = p->win; + WM_cursor_modal_set(win, BC_NSEW_SCROLLCURSOR); + bool drawmode = false; + + switch (event->type) { + /* cancel */ + case ESCKEY: + case RIGHTMOUSE: + { + if (ELEM(event->val, KM_RELEASE)) { + drawmode = true; + } + } + break; + /* set */ + case LEFTMOUSE: + { + if (ELEM(event->val, KM_RELEASE)) { + gp_origin_set(op, event->mval); + drawmode = true; + } + } + break; + } + if (drawmode) { + p->status = GP_STATUS_IDLING; + p->paintmode = GP_PAINTMODE_DRAW; + ED_gpencil_toggle_brush_cursor(C, true, NULL); + DEG_id_tag_update(&p->scene->id, ID_RECALC_COPY_ON_WRITE); + } + else { + return OPERATOR_RUNNING_MODAL; + } + } + /* we don't pass on key events, GP is used with key-modifiers - prevents Dkey to insert drivers */ if (ISKEYBOARD(event->type)) { if (ELEM(event->type, LEFTARROWKEY, DOWNARROWKEY, RIGHTARROWKEY, UPARROWKEY, ZKEY)) { @@ -3067,6 +3417,10 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) WM_operator_name_call(C, "GPENCIL_OT_blank_frame_add", WM_OP_EXEC_DEFAULT, NULL); estate = OPERATOR_RUNNING_MODAL; } + else if ((!ELEM(p->paintmode, GP_PAINTMODE_ERASER, GP_PAINTMODE_SET_CP))) { + gpencil_guide_event_handling(C, op, event, p); + estate = OPERATOR_RUNNING_MODAL; + } else { estate = OPERATOR_RUNNING_MODAL; } @@ -3091,6 +3445,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) } } } + p->status = GP_STATUS_DONE; estate = OPERATOR_FINISHED; } @@ -3251,6 +3606,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) else if (event->val == KM_RELEASE) { p->status = GP_STATUS_IDLING; op->flag |= OP_IS_MODAL_CURSOR_REGION; + ED_region_tag_redraw(p->ar); } } @@ -3260,9 +3616,10 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { /* handle drawing event */ /* printf("\t\tGP - add point\n"); */ + gpencil_add_missing_events(C, op, event, p); - gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), 0, 0); + gpencil_draw_apply_event(C, op, event, CTX_data_depsgraph(C), 0.0f, 0.0f); /* finish painting operation if anything went wrong just now */ if (p->status == GP_STATUS_ERROR) { @@ -3326,6 +3683,13 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* process last operations before exiting */ switch (estate) { case OPERATOR_FINISHED: + /* store stroke angle for parallel guide */ + if ((p->straight == 0) || (guide->use_guide && (guide->type == GP_GUIDE_CIRCULAR))){ + float xy[2]; + sub_v2_v2v2(xy, p->mval, p->mvali); + float angle = atan2f(xy[1], xy[0]); + RNA_float_set(op->ptr, "guide_last_angle", angle); + } /* one last flush before we're done */ gpencil_draw_exit(C, op); WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); @@ -3392,4 +3756,49 @@ void GPENCIL_OT_draw(wmOperatorType *ot) prop = RNA_def_boolean(ot->srna, "disable_fill", false, "No Fill Areas", "Disable fill to use stroke as fill boundary"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + /* guides */ + prop = RNA_def_float(ot->srna, "guide_last_angle", 0.0f, -10000.0f, 10000.0f, "Angle", "Speed guide angle", -10000.0f, 10000.0f); + prop = RNA_def_float_vector(ot->srna, "guide_origin", 3, NULL, -10000.0f, 10000.0f, "Origin", "Speed guide origin", -10000.0f, 10000.0f); +} + +/* additional OPs */ + +static int gpencil_guide_rotate(bContext *C, wmOperator *op) +{ + ToolSettings *ts = CTX_data_tool_settings(C); + GP_Sculpt_Guide *guide = &ts->gp_sculpt.guide; + float angle = RNA_float_get(op->ptr, "angle"); + bool increment = RNA_boolean_get(op->ptr, "increment"); + if (increment) { + float oldangle = guide->angle; + oldangle += angle; + guide->angle = angle_compat_rad(oldangle, M_PI); + } + else { + guide->angle = angle_compat_rad(angle, M_PI); + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_guide_rotate(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Rotate Guide Angle"; + ot->idname = "GPENCIL_OT_guide_rotate"; + ot->description = "Rotate guide angle"; + + /* api callbacks */ + ot->exec = gpencil_guide_rotate; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + PropertyRNA *prop; + + prop = RNA_def_boolean(ot->srna, "increment", true, "Increment", "Increment angle"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + prop = RNA_def_float(ot->srna, "angle", 0.0f, -10000.0f, 10000.0f, "Angle", "Guide angle", -10000.0f, 10000.0f); + RNA_def_property_flag(prop, PROP_HIDDEN); } diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 6849e876bf5..ba49afd0680 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -18,7 +18,7 @@ * The Original Code is Copyright (C) 2017, Blender Foundation * This is a new part of Blender * - * Contributor(s): Antonio Vazquez + * Contributor(s): Antonio Vazquez, Charlie Jolly * * ***** END GPL LICENSE BLOCK ***** * @@ -248,6 +248,15 @@ static void gp_primitive_update_cps(tGPDprimitive *tgpi) } } +/* Helper to reflect point */ +static void gp_reflect_point_v2_v2v2v2(float va[2], const float p[2], const float a[2], const float b[2]) +{ + float point[2]; + closest_to_line_v2(point, p, a, b); + va[0] = point[0] - (p[0] - point[0]); + va[1] = point[1] - (p[1] - point[1]); +} + /* Poll callback for primitive operators */ static bool gpencil_primitive_add_poll(bContext *C) { @@ -677,7 +686,8 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) /* compute screen-space coordinates for points */ tGPspoint *points2D = tgpi->points; - switch (tgpi->type) { + if (tgpi->tot_edges > 1) { + switch (tgpi->type) { case GP_STROKE_BOX: gp_primitive_rectangle(tgpi, points2D); break; @@ -694,6 +704,7 @@ static void gp_primitive_update_strokes(bContext *C, tGPDprimitive *tgpi) gp_primitive_bezier(tgpi, points2D); default: break; + } } /* convert screen-coordinates to 3D coordinates */ @@ -1321,7 +1332,7 @@ static void gpencil_primitive_edit_event_handling(bContext *C, wmOperator *op, w { if ((event->val == KM_PRESS) && (tgpi->curve) && - (tgpi->orign_type == GP_STROKE_ARC)) + (ELEM(tgpi->orign_type, GP_STROKE_ARC) )) { tgpi->flip ^= 1; gp_primitive_update_cps(tgpi); @@ -1432,22 +1443,22 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e if (tgpi->flag == IN_MOVE) { switch (event->type) { - case MOUSEMOVE: - gpencil_primitive_move(tgpi, false); - gpencil_primitive_update(C, op, tgpi); - break; - case ESCKEY: - case LEFTMOUSE: - zero_v2(tgpi->move); + case MOUSEMOVE: + gpencil_primitive_move(tgpi, false); + gpencil_primitive_update(C, op, tgpi); + break; + case ESCKEY: + case LEFTMOUSE: + zero_v2(tgpi->move); + tgpi->flag = IN_CURVE_EDIT; + break; + case RIGHTMOUSE: + if (event->val == KM_RELEASE) { tgpi->flag = IN_CURVE_EDIT; - break; - case RIGHTMOUSE: - if (event->val == KM_RELEASE) { - tgpi->flag = IN_CURVE_EDIT; - gpencil_primitive_move(tgpi, true); - gpencil_primitive_update(C, op, tgpi); - } - break; + gpencil_primitive_move(tgpi, true); + gpencil_primitive_update(C, op, tgpi); + } + break; } copy_v2_v2(tgpi->mvalo, tgpi->mval); return OPERATOR_RUNNING_MODAL; @@ -1543,7 +1554,19 @@ static int gpencil_primitive_modal(bContext *C, wmOperator *op, const wmEvent *e /* done! */ return OPERATOR_FINISHED; } - case RIGHTMOUSE: /* cancel */ + case RIGHTMOUSE: + { + /* exception to cancel current stroke when we have previous strokes in buffer */ + if (tgpi->tot_stored_edges > 0) { + tgpi->flag = IDLE; + tgpi->tot_edges = 0; + gp_primitive_update_strokes(C, tgpi); + gpencil_primitive_interaction_end(C, op, win, tgpi); + /* done! */ + return OPERATOR_FINISHED; + } + ATTR_FALLTHROUGH; + } case ESCKEY: { /* return to normal cursor and header status */ diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c index fff753cf3a9..1deeab641f4 100644 --- a/source/blender/editors/gpencil/gpencil_select.c +++ b/source/blender/editors/gpencil/gpencil_select.c @@ -861,11 +861,14 @@ void GPENCIL_OT_select_less(wmOperatorType *ot) * It would be great to de-duplicate the logic here sometime, but that can wait... */ static bool gp_stroke_do_circle_sel( + bGPDlayer *gpl, bGPDstroke *gps, GP_SpaceConversion *gsc, const int mx, const int my, const int radius, - const bool select, rcti *rect, float diff_mat[4][4], const int selectmode) + const bool select, rcti *rect, float diff_mat[4][4], const int selectmode, + const float scale) { - bGPDspoint *pt1, *pt2; + bGPDspoint *pt1 = NULL; + bGPDspoint *pt2 = NULL; int x0 = 0, y0 = 0, x1 = 0, y1 = 0; int i; bool changed = false; @@ -958,6 +961,14 @@ static bool gp_stroke_do_circle_sel( } } + /* expand selection to segment */ + if ((hit) && (selectmode == GP_SELECTMODE_SEGMENT) && (select)) { + float r_hita[3], r_hitb[3]; + bool hit_select = (bool)(pt1->flag & GP_SPOINT_SELECT); + ED_gpencil_select_stroke_segment( + gpl, gps, pt1, hit_select, false, scale, r_hita, r_hitb); + } + /* Ensure that stroke selection is in sync with its points */ BKE_gpencil_stroke_sync_selection(gps); } @@ -971,6 +982,7 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) bGPdata *gpd = ED_gpencil_data_get_active(C); ToolSettings *ts = CTX_data_tool_settings(C); const int selectmode = ts->gpencil_selectmode; + const float scale = ts->gp_sculpt.isect_threshold; /* if not edit/sculpt mode, the event is catched but not processed */ if (GPENCIL_NONE_EDIT_MODE(gpd)) { @@ -1012,7 +1024,8 @@ static int gpencil_circle_select_exec(bContext *C, wmOperator *op) GP_EDITABLE_STROKES_BEGIN(gpstroke_iter, C, gpl, gps) { changed |= gp_stroke_do_circle_sel( - gps, &gsc, mx, my, radius, select, &rect, gpstroke_iter.diff_mat, selectmode); + gpl, gps, &gsc, mx, my, radius, select, &rect, + gpstroke_iter.diff_mat, selectmode, scale); } GP_EDITABLE_STROKES_END(gpstroke_iter); @@ -1074,7 +1087,11 @@ static int gpencil_generic_select_exec( const bool strokemode = ( (ts->gpencil_selectmode == GP_SELECTMODE_STROKE) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); + const bool segmentmode = ( + (ts->gpencil_selectmode == GP_SELECTMODE_SEGMENT) && + ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0)); const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); + const float scale = ts->gp_sculpt.isect_threshold; GP_SpaceConversion gsc = {NULL}; @@ -1124,6 +1141,17 @@ static int gpencil_generic_select_exec( if (sel_op_result != -1) { SET_FLAG_FROM_TEST(pt->flag, sel_op_result, GP_SPOINT_SELECT); changed = true; + + /* expand selection to segment */ + if ((sel_op_result != -1) && + (segmentmode)) + { + bool hit_select = (bool)(pt->flag & GP_SPOINT_SELECT); + float r_hita[3], r_hitb[3]; + ED_gpencil_select_stroke_segment( + gpl, gps, pt, hit_select, false, scale, r_hita, r_hitb); + } + } } else { @@ -1336,6 +1364,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) ScrArea *sa = CTX_wm_area(C); bGPdata *gpd = ED_gpencil_data_get_active(C); ToolSettings *ts = CTX_data_tool_settings(C); + const float scale = ts->gp_sculpt.isect_threshold; /* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */ const float radius = 0.50f * U.widget_unit; @@ -1350,6 +1379,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) GP_SpaceConversion gsc = {NULL}; + bGPDlayer *hit_layer = NULL; bGPDstroke *hit_stroke = NULL; bGPDspoint *hit_point = NULL; int hit_distance = radius_squared; @@ -1394,6 +1424,7 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) 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; hit_point = pt; hit_distance = pt_distance; @@ -1454,6 +1485,15 @@ static int gpencil_select_exec(bContext *C, wmOperator *op) /* we're adding selection, so selection must be true */ hit_point->flag |= GP_SPOINT_SELECT; hit_stroke->flag |= GP_STROKE_SELECT; + + /* expand selection to segment */ + if (ts->gpencil_selectmode == GP_SELECTMODE_SEGMENT) { + float r_hita[3], r_hitb[3]; + bool hit_select = (bool)(hit_point->flag & GP_SPOINT_SELECT); + ED_gpencil_select_stroke_segment( + hit_layer, hit_stroke, hit_point, hit_select, + false, scale, r_hita, r_hitb); + } } else { /* deselect point */ diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index 607341add86..232f3b8f606 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -36,6 +36,7 @@ #include "BLI_math.h" #include "BLI_blenlib.h" +#include "BLI_ghash.h" #include "BLI_utildefines.h" #include "BLT_translation.h" #include "BLI_rand.h" @@ -723,6 +724,62 @@ void gp_point_to_xy_fl( } } + +/** +* generic based on gp_point_to_xy_fl +*/ +void gp_point_3d_to_xy(const GP_SpaceConversion *gsc, const short flag, const float pt[3], float xy[2]) +{ + const ARegion *ar = gsc->ar; + const View2D *v2d = gsc->v2d; + const rctf *subrect = gsc->subrect; + float xyval[2]; + + /* sanity checks */ + BLI_assert((gsc->sa->spacetype == SPACE_VIEW3D)); + + if (flag & GP_STROKE_3DSPACE) { + if (ED_view3d_project_float_global(ar, pt, xyval, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) { + xy[0] = xyval[0]; + xy[1] = xyval[1]; + } + else { + xy[0] = 0.0f; + xy[1] = 0.0f; + } + } + else if (flag & GP_STROKE_2DSPACE) { + float vec[3] = { pt[0], pt[1], 0.0f }; + int t_x, t_y; + + mul_m4_v3(gsc->mat, vec); + UI_view2d_view_to_region_clip(v2d, vec[0], vec[1], &t_x, &t_y); + + if ((t_x == t_y) && (t_x == V2D_IS_CLIPPED)) { + /* XXX: Or should we just always use the values as-is? */ + xy[0] = 0.0f; + xy[1] = 0.0f; + } + else { + xy[0] = (float)t_x; + xy[1] = (float)t_y; + } + } + else { + if (subrect == NULL) { + /* normal 3D view (or view space) */ + xy[0] = (pt[0] / 100.0f * ar->winx); + xy[1] = (pt[1] / 100.0f * ar->winy); + } + else { + /* camera view, use subrect */ + xy[0] = ((pt[0] / 100.0f) * BLI_rctf_size_x(subrect)) + subrect->xmin; + xy[1] = ((pt[1] / 100.0f) * BLI_rctf_size_y(subrect)) + subrect->ymin; + } + } +} + + /** * Project screenspace coordinates to 3D-space * @@ -738,7 +795,7 @@ void gp_point_to_xy_fl( * * \warning Assumes that it is getting called in a 3D view only. */ -bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, Scene *scene, const float screen_co[2], float r_out[3]) +bool gp_point_xy_to_3d(const GP_SpaceConversion *gsc, Scene *scene, const float screen_co[2], float r_out[3]) { const RegionView3D *rv3d = gsc->ar->regiondata; float rvec[3]; @@ -1946,4 +2003,347 @@ void ED_gpencil_update_color_uv(Main *bmain, Material *mat) } } } -/* ******************************************************** */ + +static bool gpencil_check_collision( + bGPDstroke *gps, bGPDstroke **gps_array, GHash *all_2d, + int totstrokes, float p2d_a1[2], float p2d_a2[2], float r_hit[2]) +{ + bool hit = false; + /* check segment with all segments of all strokes */ + for (int s = 0; s < totstrokes; s++) { + bGPDstroke *gps_iter = gps_array[s]; + if (gps_iter->totpoints < 2) { + continue; + } + /* get stroke 2d version */ + float(*points2d)[2] = BLI_ghash_lookup(all_2d, gps_iter); + + for (int i2 = 0; i2 < gps_iter->totpoints - 1; i2++) { + float p2d_b1[2], p2d_b2[2]; + copy_v2_v2(p2d_b1, points2d[i2]); + copy_v2_v2(p2d_b2, points2d[i2 + 1]); + + /* don't self check */ + if (gps == gps_iter) { + if (equals_v2v2(p2d_a1, p2d_b1) || equals_v2v2(p2d_a1, p2d_b2)) { + continue; + } + if (equals_v2v2(p2d_a2, p2d_b1) || equals_v2v2(p2d_a2, p2d_b2)) { + continue; + } + } + /* check collision */ + int check = isect_seg_seg_v2_point(p2d_a1, p2d_a2, p2d_b1, p2d_b2, r_hit); + if (check > 0) { + hit = true; + break; + } + } + + if (hit) { + break; + } + } + + if (!hit) { + zero_v2(r_hit); + } + + return hit; +} + +void static gp_copy_points( + bGPDstroke *gps, bGPDspoint *pt, bGPDspoint *pt_final, int i, int i2) +{ + copy_v3_v3(&pt_final->x, &pt->x); + pt_final->pressure = pt->pressure; + pt_final->strength = pt->strength; + pt_final->time = pt->time; + pt_final->flag = pt->flag; + pt_final->uv_fac = pt->uv_fac; + pt_final->uv_rot = pt->uv_rot; + + if (gps->dvert != NULL) { + MDeformVert *dvert = &gps->dvert[i]; + MDeformVert *dvert_final = &gps->dvert[i2]; + + dvert_final->totweight = dvert->totweight; + dvert_final->dw = dvert->dw; + } + +} + +void static gp_insert_point( + bGPDstroke *gps, + bGPDspoint *a_pt, bGPDspoint *b_pt, + float co_a[3], float co_b[3]) +{ + bGPDspoint *temp_points; + int totnewpoints, oldtotpoints; + + totnewpoints = gps->totpoints; + if (a_pt) { + totnewpoints++; + } + if (b_pt) { + totnewpoints++; + } + + /* duplicate points in a temp area */ + temp_points = MEM_dupallocN(gps->points); + oldtotpoints = gps->totpoints; + + /* look index of base points because memory is changed when resize points array */ + int a_idx = -1; + int b_idx = -1; + for (int i = 0; i < oldtotpoints; i++) { + bGPDspoint *pt = &gps->points[i]; + if (pt == a_pt) { + a_idx = i; + } + if (pt == b_pt) { + b_idx = i; + } + } + + /* resize the points arrays */ + gps->totpoints = totnewpoints; + gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); + if (gps->dvert != NULL) { + gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); + } + gps->flag |= GP_STROKE_RECALC_GEOMETRY; + + /* copy all points */ + int i2 = 0; + for (int i = 0; i < oldtotpoints; i++) { + bGPDspoint *pt = &temp_points[i]; + bGPDspoint *pt_final = &gps->points[i2]; + gp_copy_points(gps, pt, pt_final, i, i2); + + /* create new point duplicating point and copy location */ + if ((i == a_idx) || (i == b_idx)) { + i2++; + pt_final = &gps->points[i2]; + gp_copy_points(gps, pt, pt_final, i, i2); + copy_v3_v3(&pt_final->x, (i == a_idx) ? co_a : co_b); + + /* unselect */ + pt_final->flag &= ~GP_SPOINT_SELECT; + /* tag to avoid more checking with this point */ + pt_final->flag |= GP_SPOINT_TAG; + } + + i2++; + } + + MEM_SAFE_FREE(temp_points); +} + +static float gp_calc_factor(float p2d_a1[2], float p2d_a2[2], float r_hit2d[2]) +{ + float dist1 = len_squared_v2v2(p2d_a1, p2d_a2); + float dist2 = len_squared_v2v2(p2d_a1, r_hit2d); + float f = dist1 > 0.0f ? dist2 / dist1 : 0.0f; + + /* apply a correction factor */ + float v1[2]; + interp_v2_v2v2(v1, p2d_a1, p2d_a2, f); + float dist3 = len_squared_v2v2(p2d_a1, v1); + float f1 = dist1 > 0.0f ? dist3 / dist1 : 0.0f; + f = f + (f - f1); + + return f; +} + +/* extend selection to stroke intersections */ +int ED_gpencil_select_stroke_segment( + bGPDlayer *gpl, bGPDstroke *gps, bGPDspoint *pt, + bool select, bool insert, const float scale, + float r_hita[3], float r_hitb[3]) +{ + const float min_factor = 0.0015f; + bGPDspoint *pta1 = NULL; + bGPDspoint *pta2 = NULL; + float f = 0.0f; + int i2 = 0; + + bGPDframe *gpf = gpl->actframe; + if (gpf == NULL) { + return 0; + } + + int memsize = BLI_listbase_count(&gpf->strokes); + bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * memsize, __func__); + + /* save points */ + bGPDspoint *oldpoints = MEM_dupallocN(gps->points); + + /* Save list of strokes to check */ + int totstrokes = 0; + for (bGPDstroke *gps_iter = gpf->strokes.first; gps_iter; gps_iter = gps_iter->next) { + + if (gps_iter->totpoints < 2) { + continue; + } + gps_array[totstrokes] = gps_iter; + totstrokes++; + } + + if (totstrokes == 0) { + return 0; + } + + /* look for index of the current point */ + int cur_idx = -1; + for (int i = 0; i < gps->totpoints; i++) { + pta1 = &gps->points[i]; + if (pta1 == pt) { + cur_idx = i; + break; + } + } + if (cur_idx < 0) { + return 0; + } + + /* convert all gps points to 2d and save in a hash to avoid recalculation */ + int direction = 0; + float(*points2d)[2] = MEM_mallocN(sizeof(*points2d) * gps->totpoints, "GP Stroke temp 2d points"); + BKE_gpencil_stroke_2d_flat_ref( + gps->points, gps->totpoints, + gps->points, gps->totpoints, points2d, scale, &direction); + + GHash *all_2d = BLI_ghash_ptr_new(__func__); + + for (int s = 0; s < totstrokes; s++) { + bGPDstroke *gps_iter = gps_array[s]; + float(*points2d_iter)[2] = MEM_mallocN(sizeof(*points2d_iter) * gps_iter->totpoints, __func__); + + /* the extremes of the stroke are scaled to improve collision detection + * for near lines */ + BKE_gpencil_stroke_2d_flat_ref( + gps->points, gps->totpoints, + gps_iter->points, gps_iter->totpoints, points2d_iter, + scale, &direction); + BLI_ghash_insert(all_2d, gps_iter, points2d_iter); + } + + bool hit_a = false; + bool hit_b = false; + float p2d_a1[2] = {0.0f, 0.0f}; + float p2d_a2[2] = {0.0f, 0.0f}; + float r_hit2d[2]; + bGPDspoint *hit_pointa = NULL; + bGPDspoint *hit_pointb = NULL; + + /* analyze points before current */ + if (cur_idx > 0) { + for (int i = cur_idx; i >= 0; i--) { + pta1 = &gps->points[i]; + copy_v2_v2(p2d_a1, points2d[i]); + + i2 = i - 1; + CLAMP_MIN(i2, 0); + pta2 = &gps->points[i2]; + copy_v2_v2(p2d_a2, points2d[i2]); + + hit_a = gpencil_check_collision( + gps, gps_array, all_2d, totstrokes, p2d_a1, p2d_a2, r_hit2d); + + if (select) { + pta1->flag |= GP_SPOINT_SELECT; + } + else { + pta1->flag &= ~GP_SPOINT_SELECT; + } + + if (hit_a) { + f = gp_calc_factor(p2d_a1, p2d_a2, r_hit2d); + interp_v3_v3v3(r_hita, &pta1->x, &pta2->x, f); + if (f > min_factor) { + hit_pointa = pta2; /* first point is second (inverted loop) */ + } + else { + pta1->flag &= ~GP_SPOINT_SELECT; + } + break; + } + } + } + + /* analyze points after current */ + for (int i = cur_idx; i < gps->totpoints; i++) { + pta1 = &gps->points[i]; + copy_v2_v2(p2d_a1, points2d[i]); + + i2 = i + 1; + CLAMP_MAX(i2, gps->totpoints - 1); + pta2 = &gps->points[i2]; + copy_v2_v2(p2d_a2, points2d[i2]); + + hit_b = gpencil_check_collision( + gps, gps_array, all_2d, totstrokes, p2d_a1, p2d_a2, r_hit2d); + + if (select) { + pta1->flag |= GP_SPOINT_SELECT; + } + else { + pta1->flag &= ~GP_SPOINT_SELECT; + } + + if (hit_b) { + f = gp_calc_factor(p2d_a1, p2d_a2, r_hit2d); + interp_v3_v3v3(r_hitb, &pta1->x, &pta2->x, f); + if (f > min_factor) { + hit_pointb = pta1; + } + else { + pta1->flag &= ~GP_SPOINT_SELECT; + } + break; + } + } + + /* insert new point in the collision points */ + if (insert) { + gp_insert_point(gps, hit_pointa, hit_pointb, r_hita, r_hitb); + } + + /* free memory */ + if (all_2d) { + GHashIterator gh_iter; + GHASH_ITER(gh_iter, all_2d) { + float(*p2d)[2] = BLI_ghashIterator_getValue(&gh_iter); + MEM_SAFE_FREE(p2d); + } + BLI_ghash_free(all_2d, NULL, NULL); + } + + /* if no hit, reset selection flag */ + if ((!hit_a) && (!hit_b)) { + for (int i = 0; i < gps->totpoints; i++) { + pta1 = &gps->points[i]; + pta2 = &oldpoints[i]; + pta1->flag = pta2->flag; + } + } + + MEM_SAFE_FREE(points2d); + MEM_SAFE_FREE(gps_array); + MEM_SAFE_FREE(oldpoints); + + /* return type of hit */ + if ((hit_a) && (hit_b)) { + return 3; + } + else if (hit_a) { + return 1; + } + else if (hit_b) { + return 2; + } + else { + return 0; + } +} diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index c231d0dc355..808e8461471 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -259,4 +259,17 @@ void ED_gpencil_tpoint_to_point(struct ARegion *ar, float origin[3], const struc void ED_gpencil_calc_stroke_uv(struct Object *ob, struct bGPDstroke *gps); void ED_gpencil_update_color_uv(struct Main *bmain, struct Material *mat); +/* extend selection to stroke intersections + * returns: + * 0 - No hit + * 1 - Hit in point A + * 2 - Hit in point B + * 3 - Hit in point A and B +*/ +int ED_gpencil_select_stroke_segment( + struct bGPDlayer *gpl, + struct bGPDstroke *gps, struct bGPDspoint *pt, + bool select, bool insert, const float scale, + float r_hita[3], float r_hitb[3]); + #endif /* __ED_GPENCIL_H__ */ diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 0ae193752a8..17813021fdf 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -203,7 +203,8 @@ typedef struct bGPDstroke { /** Material index. */ int mat_nr; - char _pad1[4]; + /** Caps mode for each stroke extreme */ + short caps[2]; /** Vertex weight data. */ struct MDeformVert *dvert; @@ -232,6 +233,15 @@ typedef enum eGPDstroke_Flag { GP_STROKE_ERASER = (1 << 15) } eGPDstroke_Flag; +/* bGPDstroke->caps */ +typedef enum eGPDstroke_Caps { + /* type of extreme */ + GP_STROKE_CAP_ROUND = 0, + GP_STROKE_CAP_FLAT = 1, + + GP_STROKE_CAP_MAX +} GPDstroke_Caps; + /* ***************************************** */ /* GP Frame */ @@ -371,6 +381,8 @@ typedef enum eGPDlayer_Flag { GP_LAYER_UNLOCK_COLOR = (1 << 12), /* Mask Layer */ GP_LAYER_USE_MASK = (1 << 13), + /* Flag used to display in Paint mode only layers with keyframe */ + GP_LAYER_SOLO_MODE = (1 << 4), } eGPDlayer_Flag; /* bGPDlayer->onion_flag */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 3186e8a435b..fd68d8e0d74 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1073,6 +1073,20 @@ typedef struct GP_Sculpt_Data { char _pad[4]; } GP_Sculpt_Data; +/* Settings for a GPencil Speed Guide */ +typedef struct GP_Sculpt_Guide { + char use_guide; + char use_snapping; + char reference_point; + char type; + char _pad2[4]; + float angle; + float angle_snap; + float spacing; + float location[3]; + struct Object *reference_object; +} GP_Sculpt_Guide; + /* GP_Sculpt_Data.flag */ typedef enum eGP_Sculpt_Flag { /* invert the effect of the brush */ @@ -1106,7 +1120,8 @@ typedef struct GP_Sculpt_Settings { int flag; /** #eGP_Lockaxis_Types lock drawing to one axis. */ int lock_axis; - char pad1[4]; + /** Threshold for intersections */ + float isect_threshold; /* weight paint is a submode of sculpt but use its own index. All weight paint * brushes must be defined at the end of the brush array. @@ -1118,6 +1133,8 @@ typedef struct GP_Sculpt_Settings { struct CurveMapping *cur_falloff; /** Curve used for primitve tools. */ struct CurveMapping *cur_primitive; + /** Guides used for paint tools */ + struct GP_Sculpt_Guide guide; } GP_Sculpt_Settings; /* GP_Sculpt_Settings.flag */ @@ -2317,10 +2334,26 @@ typedef enum eGPencil_Placement_Flags { /* ToolSettings.gpencil_selectmode */ typedef enum eGPencil_Selectmode_types { - GP_SELECTMODE_POINT = 0, - GP_SELECTMODE_STROKE = 1 + GP_SELECTMODE_POINT = 0, + GP_SELECTMODE_STROKE = 1, + GP_SELECTMODE_SEGMENT = 2 } eGPencil_Selectmode_types; +/* ToolSettings.gpencil_guide_types */ +typedef enum eGPencil_GuideTypes { + GP_GUIDE_CIRCULAR = 0, + GP_GUIDE_RADIAL, + GP_GUIDE_PARALLEL, + GP_GUIDE_GRID +} eGPencil_GuideTypes; + +/* ToolSettings.gpencil_guide_references */ +typedef enum eGPencil_Guide_Reference { + GP_GUIDE_REF_CURSOR = 0, + GP_GUIDE_REF_CUSTOM, + GP_GUIDE_REF_OBJECT +} eGPencil_Guide_Reference; + /* ToolSettings.particle flag */ #define PE_KEEP_LENGTHS (1 << 0) #define PE_LOCK_FIRST (1 << 1) diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 0deff2ab6d2..0cd4a9aeac1 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -274,6 +274,7 @@ extern StructRNA RNA_GPencilFrame; extern StructRNA RNA_GPencilInterpolateSettings; extern StructRNA RNA_GPencilLayer; extern StructRNA RNA_GPencilSculptBrush; +extern StructRNA RNA_GPencilSculptGuide; extern StructRNA RNA_GPencilSculptSettings; extern StructRNA RNA_GPencilStroke; extern StructRNA RNA_GPencilStrokePoint; diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index e632bf36d67..da322307454 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -20,9 +20,9 @@ * ***** END GPL LICENSE BLOCK ***** */ -/** \file blender/makesrna/intern/rna_gpencil.c - * \ingroup RNA - */ + /** \file blender/makesrna/intern/rna_gpencil.c + * \ingroup RNA + */ #include @@ -49,7 +49,7 @@ #include "WM_types.h" -/* parent type */ + /* parent type */ static const EnumPropertyItem parent_type_items[] = { {PAROBJECT, "OBJECT", 0, "Object", "The layer is parented to an object"}, {PARSKEL, "ARMATURE", 0, "Armature", ""}, @@ -87,6 +87,12 @@ static const EnumPropertyItem rna_enum_layer_blend_modes_items[] = { {eGplBlendMode_Divide, "DIVIDE", 0, "Divide", "" }, {0, NULL, 0, NULL, NULL } }; + +static EnumPropertyItem rna_enum_gpencil_caps_modes_items[] = { + {GP_STROKE_CAP_ROUND, "ROUND", 0, "Rounded", ""}, + {GP_STROKE_CAP_FLAT, "FLAT", 0, "Flat", ""}, + {0, NULL, 0, NULL, NULL} +}; #endif #ifdef RNA_RUNTIME @@ -285,8 +291,8 @@ static void rna_GPencilLayer_parent_bone_set(PointerRNA *ptr, const char *value) /* parent types enum */ static const EnumPropertyItem *rna_Object_parent_type_itemf( - bContext *UNUSED(C), PointerRNA *ptr, - PropertyRNA *UNUSED(prop), bool *r_free) + bContext *UNUSED(C), PointerRNA *ptr, + PropertyRNA *UNUSED(prop), bool *r_free) { bGPDlayer *gpl = (bGPDlayer *)ptr->data; EnumPropertyItem *item = NULL; @@ -376,7 +382,7 @@ static int rna_GPencil_active_layer_index_get(PointerRNA *ptr) static void rna_GPencil_active_layer_index_set(PointerRNA *ptr, int value) { - bGPdata *gpd = (bGPdata *)ptr->id.data; + bGPdata *gpd = (bGPdata *)ptr->id.data; bGPDlayer *gpl = BLI_findlink(&gpd->layers, value); BKE_gpencil_layer_setactive(gpd, gpl); @@ -398,11 +404,11 @@ static void rna_GPencil_active_layer_index_range(PointerRNA *ptr, int *min, int } static const EnumPropertyItem *rna_GPencil_active_layer_itemf( - bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) + bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { bGPdata *gpd = (bGPdata *)ptr->id.data; bGPDlayer *gpl; - EnumPropertyItem *item = NULL, item_tmp = {0}; + EnumPropertyItem *item = NULL, item_tmp = { 0 }; int totitem = 0; int i = 0; @@ -507,11 +513,11 @@ static void rna_GPencil_stroke_point_add(ID *id, bGPDstroke *stroke, int count, if (count > 0) { /* create space at the end of the array for extra points */ stroke->points = MEM_recallocN_id(stroke->points, - sizeof(bGPDspoint) * (stroke->totpoints + count), - "gp_stroke_points"); + sizeof(bGPDspoint) * (stroke->totpoints + count), + "gp_stroke_points"); stroke->dvert = MEM_recallocN_id(stroke->dvert, - sizeof(MDeformVert) * (stroke->totpoints + count), - "gp_stroke_weight"); + sizeof(MDeformVert) * (stroke->totpoints + count), + "gp_stroke_weight"); /* init the pressure and strength values so that old scripts won't need to * be modified to give these initial values... @@ -968,6 +974,19 @@ static void rna_def_gpencil_stroke(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Cyclic", "Enable cyclic drawing, closing the stroke"); RNA_def_property_update(prop, 0, "rna_GPencil_update"); + /* Caps mode */ + prop = RNA_def_property(srna, "start_cap_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "caps[0]"); + RNA_def_property_enum_items(prop, rna_enum_gpencil_caps_modes_items); + RNA_def_property_ui_text(prop, "Start Cap", "Stroke start extreme cap style"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + + prop = RNA_def_property(srna, "end_cap_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "caps[1]"); + RNA_def_property_enum_items(prop, rna_enum_gpencil_caps_modes_items); + RNA_def_property_ui_text(prop, "End Cap", "Stroke end extreme cap style"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + /* No fill: The stroke never must fill area and must use fill color as stroke color (this is a special flag for fill brush) */ prop = RNA_def_property(srna, "is_nofill_stroke", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_STROKE_NOFILL); @@ -1066,7 +1085,7 @@ static void rna_def_gpencil_frames_api(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_function_ui_description(func, "Add a new grease pencil frame"); RNA_def_function_flag(func, FUNC_USE_REPORTS); parm = RNA_def_int(func, "frame_number", 1, MINAFRAME, MAXFRAME, "Frame Number", - "The frame on which this sketch appears", MINAFRAME, MAXFRAME); + "The frame on which this sketch appears", MINAFRAME, MAXFRAME); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); parm = RNA_def_pointer(func, "frame", "GPencilFrame", "", "The newly created frame"); RNA_def_function_return(func, parm); @@ -1261,6 +1280,13 @@ static void rna_def_gpencil_layer(BlenderRNA *brna) "Clamp any pixel outside underlying layers drawing"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + /* solo mode: Only display frames with keyframe */ + prop = RNA_def_property(srna, "use_solo_mode", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LAYER_SOLO_MODE); + RNA_def_property_ui_text(prop, "Solo Mode", + "In Paint mode display only layers with keyframe in current frame"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + /* exposed as layers.active */ #if 0 prop = RNA_def_property(srna, "active", PROP_BOOLEAN, PROP_NONE); @@ -1373,20 +1399,20 @@ static void rna_def_gpencil_layers_api(BlenderRNA *brna, PropertyRNA *cprop) prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED); RNA_def_property_int_funcs( - prop, - "rna_GPencil_active_layer_index_get", - "rna_GPencil_active_layer_index_set", - "rna_GPencil_active_layer_index_range"); + prop, + "rna_GPencil_active_layer_index_get", + "rna_GPencil_active_layer_index_set", + "rna_GPencil_active_layer_index_range"); RNA_def_property_ui_text(prop, "Active Layer Index", "Index of active grease pencil layer"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA | NA_SELECTED, NULL); /* Active Layer - As an enum (for selecting active layer for annotations) */ prop = RNA_def_property(srna, "active_note", PROP_ENUM, PROP_NONE); RNA_def_property_enum_funcs( - prop, - "rna_GPencil_active_layer_index_get", - "rna_GPencil_active_layer_index_set", - "rna_GPencil_active_layer_itemf"); + prop, + "rna_GPencil_active_layer_index_get", + "rna_GPencil_active_layer_index_set", + "rna_GPencil_active_layer_itemf"); RNA_def_property_enum_items(prop, DummyRNA_DEFAULT_items); /* purely dynamic, as it maps to user-data */ RNA_def_property_ui_text(prop, "Active Note", "Note/Layer to add annotation strokes to"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); @@ -1405,7 +1431,7 @@ static void rna_def_gpencil_grid(BlenderRNA *brna) RNA_def_struct_path_func(srna, "rna_GreasePencilGrid_path"); RNA_def_struct_ui_text(srna, "Grid and Canvas Settings", - "Settings for grid and canvas in 3D viewport"); + "Settings for grid and canvas in 3D viewport"); prop = RNA_def_property(srna, "scale", PROP_FLOAT, PROP_XYZ); RNA_def_property_float_sdna(prop, NULL, "scale"); @@ -1510,7 +1536,7 @@ static void rna_def_gpencil_data(BlenderRNA *brna) prop = RNA_def_property(srna, "show_stroke_direction", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_DATA_SHOW_DIRECTION); RNA_def_property_ui_text(prop, "Show Direction", "Show stroke drawing direction with a bigger green dot (start) " - "and smaller red dot (end) points"); + "and smaller red dot (end) points"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); prop = RNA_def_property(srna, "show_constant_thickness", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index a760e088d90..114672017b7 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -2381,6 +2381,8 @@ static void rna_def_tool_settings(BlenderRNA *brna) static const EnumPropertyItem gpencil_selectmode_items[] = { {GP_SELECTMODE_POINT, "POINT", ICON_GP_SELECT_POINTS, "Point", "Select only points"}, {GP_SELECTMODE_STROKE, "STROKE", ICON_GP_SELECT_STROKES, "Stroke", "Select all stroke points" }, + /* GPXX need better icon for segment */ + {GP_SELECTMODE_SEGMENT, "SEGMENT", ICON_SHADERFX, "Segment", "Select all stroke points between other strokes" }, {0, NULL, 0, NULL, NULL} }; diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index f9f21c4c76a..950ed047532 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -27,6 +27,7 @@ #include +#include "BLI_math.h" #include "BLI_utildefines.h" #include "RNA_define.h" @@ -544,6 +545,10 @@ static char *rna_GPencilSculptBrush_path(PointerRNA *UNUSED(ptr)) return BLI_strdup("tool_settings.gpencil_sculpt.brush"); } +static char *rna_GPencilSculptGuide_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("tool_settings.gpencil_sculpt.guide"); +} #else @@ -1194,6 +1199,99 @@ static void rna_def_particle_edit(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Curve", ""); } +/* srna -- gpencil speed guides */ +static void rna_def_gpencil_guides(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "GPencilSculptGuide", NULL); + RNA_def_struct_sdna(srna, "GP_Sculpt_Guide"); + RNA_def_struct_path_func(srna, "rna_GPencilSculptGuide_path"); + RNA_def_struct_ui_text(srna, "GPencil Sculpt Guide", "Guides for drawing"); + + static const EnumPropertyItem prop_gpencil_guidetypes[] = { + {GP_GUIDE_CIRCULAR, "CIRCULAR", 0, "Circular", "Use single point to create rings"}, + {GP_GUIDE_RADIAL, "RADIAL", 0, "Radial", "Use single point as direction"}, + {GP_GUIDE_PARALLEL, "PARALLEL", 0, "Parallel", "Parallel lines"}, + {GP_GUIDE_GRID, "GRID", 0, "Grid", "Grid allows horizontal and vertical lines"}, + {0, NULL, 0, NULL, NULL} + }; + + static const EnumPropertyItem prop_gpencil_guide_references[] = { + {GP_GUIDE_REF_CURSOR, "CURSOR", 0, "Cursor", "Use cursor as reference point"}, + {GP_GUIDE_REF_CUSTOM, "CUSTOM", 0, "Custom", "Use custom reference point"}, + {GP_GUIDE_REF_OBJECT, "OBJECT", 0, "Object", "Use object as reference point"}, + {0, NULL, 0, NULL, NULL} + }; + + prop = RNA_def_property(srna, "use_guide", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_guide", false); + RNA_def_property_boolean_default(prop, false); + RNA_def_property_ui_text(prop, "Use Guides", "Enable speed guides"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + + prop = RNA_def_property(srna, "use_snapping", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_snapping", false); + RNA_def_property_boolean_default(prop, false); + RNA_def_property_ui_text(prop, "Use Snapping", "Enable snapping to guides angle or spacing options"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + + prop = RNA_def_property(srna, "reference_object", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "reference_object"); + RNA_def_property_ui_text(prop, "Object", "Object used for reference point"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, "rna_ImaPaint_viewport_update"); + + prop = RNA_def_property(srna, "reference_point", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "reference_point"); + RNA_def_property_enum_items(prop, prop_gpencil_guide_references); + RNA_def_property_ui_text(prop, "Type", "Type of speed guide"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, "rna_ImaPaint_viewport_update"); + + prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "type"); + RNA_def_property_enum_items(prop, prop_gpencil_guidetypes); + RNA_def_property_ui_text(prop, "Type", "Type of speed guide"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + + prop = RNA_def_property(srna, "angle", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, NULL, "angle"); + RNA_def_property_range(prop, -(M_PI * 2.0f), (M_PI * 2.0f)); + RNA_def_property_ui_text(prop, "Angle", "Direction of lines"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + + prop = RNA_def_property(srna, "angle_snap", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, NULL, "angle_snap"); + RNA_def_property_range(prop, -(M_PI * 2.0f), (M_PI * 2.0f)); + RNA_def_property_ui_text(prop, "Angle Snap", "Angle snapping"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + + prop = RNA_def_property(srna, "spacing", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_float_sdna(prop, NULL, "spacing"); + RNA_def_property_float_default(prop, 0.01f); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0f, FLT_MAX, 1, 3); + RNA_def_property_ui_text(prop, "Spacing", "Guide spacing"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + + prop = RNA_def_property(srna, "location", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_float_sdna(prop, NULL, "location"); + RNA_def_property_array(prop, 3); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_ui_text(prop, "Location", "Custom reference point for guides"); + RNA_def_property_range(prop, -FLT_MAX, FLT_MAX); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, 3); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, "rna_ImaPaint_viewport_update"); +} + static void rna_def_gpencil_sculpt(BlenderRNA *brna) { static const EnumPropertyItem prop_direction_items[] = { @@ -1230,6 +1328,11 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna) RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); RNA_def_property_ui_text(prop, "Brush", ""); + prop = RNA_def_property(srna, "guide", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "GPencilSculptGuide"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + RNA_def_property_ui_text(prop, "Guide", ""); + prop = RNA_def_property(srna, "use_select_mask", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_SCULPT_SETT_FLAG_SELECT_MASK); RNA_def_property_ui_text(prop, "Selection Mask", "Only sculpt selected stroke points"); @@ -1299,6 +1402,14 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna) RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + /* threshold for cutter */ + prop = RNA_def_property(srna, "intersection_threshold", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "isect_threshold"); + RNA_def_property_range(prop, 0.0f, 10.0f); + RNA_def_property_float_default(prop, 0.1f); + RNA_def_property_ui_text(prop, "Threshold", "Threshold for stroke intersections"); + RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + /* brush */ srna = RNA_def_struct(brna, "GPencilSculptBrush", NULL); RNA_def_struct_sdna(srna, "GP_Sculpt_Data"); @@ -1383,7 +1494,6 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna) RNA_def_property_boolean_default(prop, true); RNA_def_property_ui_text(prop, "Enable Cursor", "Enable cursor on screen"); RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); - } void RNA_def_sculpt_paint(BlenderRNA *brna) @@ -1399,6 +1509,7 @@ void RNA_def_sculpt_paint(BlenderRNA *brna) rna_def_vertex_paint(brna); rna_def_image_paint(brna); rna_def_particle_edit(brna); + rna_def_gpencil_guides(brna); rna_def_gpencil_sculpt(brna); RNA_define_animate_sdna(true); }