Grease Pencil: Merge GPencil_Editing_Stage3 branch into master
authorJoshua Leung <aligorith@gmail.com>
Sun, 13 Dec 2015 08:03:13 +0000 (21:03 +1300)
committerJoshua Leung <aligorith@gmail.com>
Sun, 13 Dec 2015 08:03:13 +0000 (21:03 +1300)
This commit merges all the work done in the GPencil_Editing_Stage3 branch
as of ef2aecf2db981b5344e0d14e7f074f1742b0b2f7 into master. For more details
about the changes that this brings, see the WIP release notes:

http://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.77/GPencil

54 files changed:
release/scripts/startup/bl_ui/properties_grease_pencil_common.py
release/scripts/startup/bl_ui/space_clip.py
release/scripts/startup/bl_ui/space_dopesheet.py
release/scripts/startup/bl_ui/space_image.py
release/scripts/startup/bl_ui/space_node.py
release/scripts/startup/bl_ui/space_view3d.py
release/scripts/startup/bl_ui/space_view3d_toolbar.py
source/blender/blenkernel/BKE_gpencil.h
source/blender/blenkernel/intern/gpencil.c
source/blender/blenkernel/intern/scene.c
source/blender/blenloader/intern/readfile.c
source/blender/blenloader/intern/versioning_270.c
source/blender/blenloader/intern/versioning_defaults.c
source/blender/editors/animation/anim_draw.c
source/blender/editors/animation/anim_filter.c
source/blender/editors/animation/keyframes_general.c
source/blender/editors/gpencil/CMakeLists.txt
source/blender/editors/gpencil/drawgpencil.c
source/blender/editors/gpencil/editaction_gpencil.c
source/blender/editors/gpencil/gpencil_brush.c [new file with mode: 0644]
source/blender/editors/gpencil/gpencil_convert.c
source/blender/editors/gpencil/gpencil_data.c
source/blender/editors/gpencil/gpencil_edit.c
source/blender/editors/gpencil/gpencil_intern.h
source/blender/editors/gpencil/gpencil_ops.c
source/blender/editors/gpencil/gpencil_paint.c
source/blender/editors/gpencil/gpencil_select.c
source/blender/editors/gpencil/gpencil_utils.c
source/blender/editors/include/ED_gpencil.h
source/blender/editors/object/object_edit.c
source/blender/editors/screen/screen_ops.c
source/blender/editors/space_action/action_edit.c
source/blender/editors/space_time/space_time.c
source/blender/editors/space_view3d/view3d_draw.c
source/blender/editors/space_view3d/view3d_header.c
source/blender/editors/transform/transform_conversions.c
source/blender/editors/transform/transform_generics.c
source/blender/editors/transform/transform_manipulator.c
source/blender/makesdna/DNA_action_types.h
source/blender/makesdna/DNA_gpencil_types.h
source/blender/makesdna/DNA_object_types.h
source/blender/makesdna/DNA_scene_types.h
source/blender/makesrna/RNA_access.h
source/blender/makesrna/RNA_enum_types.h
source/blender/makesrna/intern/rna_action.c
source/blender/makesrna/intern/rna_gpencil.c
source/blender/makesrna/intern/rna_object.c
source/blender/makesrna/intern/rna_scene.c
source/blender/makesrna/intern/rna_sculpt_paint.c
source/blender/makesrna/intern/rna_wm.c
source/blender/windowmanager/intern/wm_event_system.c
source/blender/windowmanager/intern/wm_init_exit.c
source/blender/windowmanager/intern/wm_keymap.c
source/blender/windowmanager/wm_event_types.h

index 91a986d8e50fc7438082e58ba2e36e65de912205..59c05c192fdb8f4adeca80c4104d3aa51d765dad 100644 (file)
 from bpy.types import Menu, UIList
 
 
-def gpencil_stroke_placement_settings(context, layout, gpd):
+def gpencil_stroke_placement_settings(context, layout):
+    if context.space_data.type == 'VIEW_3D':
+        propname = "gpencil_stroke_placement_view3d"
+    elif context.space_data.type == 'SEQUENCE_EDITOR':
+        propname = "gpencil_stroke_placement_sequencer_preview"
+    elif context.space_data.type == 'IMAGE_EDITOR':
+        propname = "gpencil_stroke_placement_image_edit"
+    else:
+        propname = "gpencil_stroke_placement_view2d"
+
+    ts = context.tool_settings
+
     col = layout.column(align=True)
 
     col.label(text="Stroke Placement:")
 
     row = col.row(align=True)
-    row.prop_enum(gpd, "draw_mode", 'VIEW')
-    row.prop_enum(gpd, "draw_mode", 'CURSOR')
+    row.prop_enum(ts, propname, 'VIEW')
+    row.prop_enum(ts, propname, 'CURSOR')
 
     if context.space_data.type == 'VIEW_3D':
         row = col.row(align=True)
-        row.prop_enum(gpd, "draw_mode", 'SURFACE')
-        row.prop_enum(gpd, "draw_mode", 'STROKE')
+        row.prop_enum(ts, propname, 'SURFACE')
+        row.prop_enum(ts, propname, 'STROKE')
 
         row = col.row(align=False)
-        row.active = gpd.draw_mode in {'SURFACE', 'STROKE'}
-        row.prop(gpd, "use_stroke_endpoints")
+        row.active = getattr(ts, propname) in {'SURFACE', 'STROKE'}
+        row.prop(ts, "use_gpencil_stroke_endpoints")
 
 
 class GreasePencilDrawingToolsPanel:
@@ -56,15 +67,19 @@ class GreasePencilDrawingToolsPanel:
 
         col.label(text="Draw:")
         row = col.row(align=True)
-        row.operator("gpencil.draw", text="Draw").mode = 'DRAW'
-        row.operator("gpencil.draw", text="Erase").mode = 'ERASER'
+        row.operator("gpencil.draw", icon='GREASEPENCIL', text="Draw").mode = 'DRAW'
+        row.operator("gpencil.draw", icon='FORCE_CURVE', text="Erase").mode = 'ERASER'  # XXX: Needs a dedicated icon
 
         row = col.row(align=True)
-        row.operator("gpencil.draw", text="Line").mode = 'DRAW_STRAIGHT'
-        row.operator("gpencil.draw", text="Poly").mode = 'DRAW_POLY'
+        row.operator("gpencil.draw", icon='LINE_DATA', text="Line").mode = 'DRAW_STRAIGHT'
+        row.operator("gpencil.draw", icon='MESH_DATA', text="Poly").mode = 'DRAW_POLY'
 
-        row = col.row(align=True)
-        row.prop(context.tool_settings, "use_grease_pencil_sessions", text="Continuous Drawing")
+        sub = col.column(align=True)
+        sub.prop(context.tool_settings, "use_gpencil_additive_drawing", text="Additive Drawing")
+        sub.prop(context.tool_settings, "use_gpencil_continuous_drawing", text="Continuous Drawing")
+
+        col.separator()
+        col.separator()
 
         if context.space_data.type in {'VIEW_3D', 'CLIP_EDITOR'}:
             col.separator()
@@ -74,11 +89,20 @@ class GreasePencilDrawingToolsPanel:
                 row.prop(context.tool_settings, "grease_pencil_source", expand=True)
             elif context.space_data.type == 'CLIP_EDITOR':
                 row.prop(context.space_data, "grease_pencil_source", expand=True)
+        
+        col.separator()
+        col.separator()
+
+        gpencil_stroke_placement_settings(context, col)
 
         gpd = context.gpencil_data
+
         if gpd:
-            col.separator()
-            gpencil_stroke_placement_settings(context, col, gpd)
+            layout.separator()
+            layout.separator()
+
+            col = layout.column(align=True)
+            col.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
 
         if context.space_data.type == 'VIEW_3D':
             col.separator()
@@ -95,67 +119,103 @@ class GreasePencilStrokeEditPanel:
     bl_label = "Edit Strokes"
     bl_category = "Grease Pencil"
     bl_region_type = 'TOOLS'
+    bl_options = {'DEFAULT_CLOSED'}
 
     @classmethod
     def poll(cls, context):
-        return (context.gpencil_data is not None)
+        if context.gpencil_data is None:
+            return False
+
+        gpd = context.gpencil_data
+        return bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
 
     @staticmethod
     def draw(self, context):
         layout = self.layout
 
-        gpd = context.gpencil_data
-        edit_ok = bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
+        layout.label(text="Select:")
+        col = layout.column(align=True)
+        col.operator("gpencil.select_all", text="Select All")
+        col.operator("gpencil.select_border")
+        col.operator("gpencil.select_circle")
+
+        layout.separator()
 
         col = layout.column(align=True)
-        col.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
+        col.operator("gpencil.select_linked")
+        col.operator("gpencil.select_more")
+        col.operator("gpencil.select_less")
 
-        col.separator()
+        layout.separator()
 
-        col.label(text="Select:")
-        subcol = col.column(align=True)
-        subcol.active = edit_ok
-        subcol.operator("gpencil.select_all", text="Select All")
-        subcol.operator("gpencil.select_border")
-        subcol.operator("gpencil.select_circle")
+        layout.label(text="Edit:")
+        row = layout.row(align=True)
+        row.operator("gpencil.copy", text="Copy")
+        row.operator("gpencil.paste", text="Paste")
 
-        col.separator()
+        col = layout.column(align=True)
+        col.operator("gpencil.delete", text="Delete")
+        col.operator("gpencil.duplicate_move", text="Duplicate")
+        col.operator("transform.mirror", text="Mirror")
 
-        subcol = col.column(align=True)
-        subcol.active = edit_ok
-        subcol.operator("gpencil.select_linked")
-        subcol.operator("gpencil.select_more")
-        subcol.operator("gpencil.select_less")
+        layout.separator()
 
-        col.separator()
+        col = layout.column(align=True)
+        col.operator("transform.translate")                # icon='MAN_TRANS'
+        col.operator("transform.rotate")                   # icon='MAN_ROT'
+        col.operator("transform.resize", text="Scale")     # icon='MAN_SCALE'
 
-        col.label(text="Edit:")
-        row = col.row(align=True)
-        row.active = edit_ok
-        row.operator("gpencil.copy", text="Copy")
-        row.operator("gpencil.paste", text="Paste")
+        layout.separator()
 
-        subcol = col.column(align=True)
-        subcol.active = edit_ok
-        subcol.operator("gpencil.delete", text="Delete")
-        subcol.operator("gpencil.duplicate_move", text="Duplicate")
-        subcol.operator("transform.mirror", text="Mirror").gpencil_strokes = True
+        col = layout.column(align=True)
+        col.operator("transform.bend", text="Bend")
+        col.operator("transform.shear", text="Shear")
+        col.operator("transform.tosphere", text="To Sphere")
 
-        col.separator()
 
-        subcol = col.column(align=True)
-        subcol.active = edit_ok
-        subcol.operator("transform.translate").gpencil_strokes = True   # icon='MAN_TRANS'
-        subcol.operator("transform.rotate").gpencil_strokes = True      # icon='MAN_ROT'
-        subcol.operator("transform.resize", text="Scale").gpencil_strokes = True      # icon='MAN_SCALE'
+class GreasePencilStrokeSculptPanel:
+    # subclass must set
+    # bl_space_type = 'IMAGE_EDITOR'
+    bl_label = "Sculpt Strokes"
+    bl_category = "Grease Pencil"
+    bl_region_type = 'TOOLS'
+    bl_options = {'DEFAULT_CLOSED'}
 
-        col.separator()
+    @classmethod
+    def poll(cls, context):
+        if context.gpencil_data is None:
+            return False
+
+        gpd = context.gpencil_data
+        return bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
+
+    @staticmethod
+    def draw(self, context):
+        layout = self.layout
+        
+        settings = context.tool_settings.gpencil_sculpt
+        tool = settings.tool
+        brush = settings.brush
+        
+        layout.column().prop(settings, "tool", expand=True)
+
+        col = layout.column()
+        col.prop(brush, "size", slider=True)
+        row = col.row(align=True)
+        row.prop(brush, "strength", slider=True)
+        row.prop(brush, "use_pressure_strength", text="")
+        col.prop(brush, "use_falloff")
+
+        layout.separator()
+
+        if settings.tool in {'THICKNESS', 'PINCH', 'TWIST'}:
+            layout.row().prop(brush, "direction", expand=True)
 
-        subcol = col.column(align=True)
-        subcol.active = edit_ok
-        subcol.operator("transform.bend", text="Bend").gpencil_strokes = True
-        subcol.operator("transform.shear", text="Shear").gpencil_strokes = True
-        subcol.operator("transform.tosphere", text="To Sphere").gpencil_strokes = True
+        layout.separator()
+        layout.prop(settings, "use_select_mask")
+
+        if settings.tool == 'SMOOTH':
+            layout.prop(brush, "affect_pressure")
 
 
 ###############################
@@ -190,14 +250,14 @@ class GPENCIL_PIE_tool_palette(Menu):
         if gpd:
             if gpd.use_stroke_edit_mode and context.editable_gpencil_strokes:
                 # S - Exit Edit Mode
-                pie.prop(gpd, "use_stroke_edit_mode", text="Exit Edit Mode", icon='EDIT')
+                pie.operator("gpencil.editmode_toggle", text="Exit Edit Mode", icon='EDIT')
 
                 # N - Transforms
                 col = pie.column()
                 row = col.row(align=True)
-                row.operator("transform.translate", icon='MAN_TRANS').gpencil_strokes = True
-                row.operator("transform.rotate", icon='MAN_ROT').gpencil_strokes = True
-                row.operator("transform.resize", text="Scale", icon='MAN_SCALE').gpencil_strokes = True
+                row.operator("transform.translate", icon='MAN_TRANS')
+                row.operator("transform.rotate", icon='MAN_ROT')
+                row.operator("transform.resize", text="Scale", icon='MAN_SCALE')
                 row = col.row(align=True)
                 row.label("Proportional Edit:")
                 row.prop(context.tool_settings, "proportional_edit", text="", icon_only=True)
@@ -224,7 +284,7 @@ class GPENCIL_PIE_tool_palette(Menu):
                 pie.operator("wm.call_menu_pie", text="More...").name = "GPENCIL_PIE_tools_more"
             else:
                 # Toggle Edit Mode
-                pie.prop(gpd, "use_stroke_edit_mode", text="Enable Stroke Editing", icon='EDIT')
+                pie.operator("gpencil.editmode_toggle", text="Enable Stroke Editing", icon='EDIT')
 
 
 class GPENCIL_PIE_settings_palette(Menu):
@@ -261,11 +321,15 @@ class GPENCIL_PIE_settings_palette(Menu):
         col.prop(gpl, "use_onion_skinning")
 
         # N - Active Layer
-        # XXX: this should show an operator to change the active layer instead
         col = pie.column()
         col.label("Active Layer:      ")
-        col.prop(gpl, "info", text="")
-        # col.prop(gpd, "layers")
+
+        row = col.row()
+        row.operator_context = 'EXEC_REGION_WIN'
+        row.operator_menu_enum("gpencil.layer_change", "layer", text="", icon='GREASEPENCIL')
+        row.prop(gpl, "info", text="")
+        row.operator("gpencil.layer_remove", text="", icon='X')
+
         row = col.row()
         row.prop(gpl, "lock")
         row.prop(gpl, "hide")
@@ -294,17 +358,82 @@ class GPENCIL_PIE_tools_more(Menu):
         col.operator("gpencil.select_more", icon='ZOOMIN')
         col.operator("gpencil.select_less", icon='ZOOMOUT')
 
-        pie.operator("transform.mirror", icon='MOD_MIRROR').gpencil_strokes = True
-        pie.operator("transform.bend", icon='MOD_SIMPLEDEFORM').gpencil_strokes = True
-        pie.operator("transform.shear", icon='MOD_TRIANGULATE').gpencil_strokes = True
-        pie.operator("transform.tosphere", icon='MOD_MULTIRES').gpencil_strokes = True
+        pie.operator("transform.mirror", icon='MOD_MIRROR')
+        pie.operator("transform.bend", icon='MOD_SIMPLEDEFORM')
+        pie.operator("transform.shear", icon='MOD_TRIANGULATE')
+        pie.operator("transform.tosphere", icon='MOD_MULTIRES')
 
         pie.operator("gpencil.convert", icon='OUTLINER_OB_CURVE', text="Convert...")
         pie.operator("wm.call_menu_pie", text="Back to Main Palette...").name = "GPENCIL_PIE_tool_palette"
 
 
+class GPENCIL_PIE_sculpt(Menu):
+    """A pie menu for accessing Grease Pencil stroke sculpting settings"""
+    bl_label = "Grease Pencil Sculpt"
+
+    @classmethod
+    def poll(cls, context):
+        gpd = context.gpencil_data
+        return bool(gpd and gpd.use_stroke_edit_mode and context.editable_gpencil_strokes)
+
+    def draw(self, context):
+        layout = self.layout
+
+        pie = layout.menu_pie()
+
+        settings = context.tool_settings.gpencil_sculpt
+        brush = settings.brush
+
+        # W - Launch Sculpt Mode
+        col = pie.column()
+        #col.label("Tool:")
+        col.prop(settings, "tool", text="")
+        col.operator("gpencil.brush_paint", text="Sculpt", icon='SCULPTMODE_HLT')
+
+        # E - Common Settings
+        col = pie.column(align=True)
+        col.prop(brush, "size", slider=True)
+        row = col.row(align=True)
+        row.prop(brush, "strength", slider=True)
+       # row.prop(brush, "use_pressure_strength", text="", icon_only=True)
+        col.prop(brush, "use_falloff")
+
+        # S - Change Brush Type Shortcuts
+        row = pie.row()
+        row.prop_enum(settings, "tool", value='GRAB')
+        row.prop_enum(settings, "tool", value='PUSH')
+        row.prop_enum(settings, "tool", value='CLONE')
+
+        # N - Change Brush Type Shortcuts
+        row = pie.row()
+        row.prop_enum(settings, "tool", value='SMOOTH')
+        row.prop_enum(settings, "tool", value='THICKNESS')
+        row.prop_enum(settings, "tool", value='RANDOMISE')
+
+
+###############################
+
+
+class GPENCIL_MT_snap(Menu):
+    bl_label = "Snap"
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.operator("gpencil.snap_to_grid", text="Selection to Grid")
+        layout.operator("gpencil.snap_to_cursor", text="Selection to Cursor").use_offset = False
+        layout.operator("gpencil.snap_to_cursor", text="Selection to Cursor (Offset)").use_offset = True
+
+        layout.separator()
+
+        layout.operator("gpencil.snap_cursor_to_selected", text="Cursor to Selected")
+        layout.operator("view3d.snap_cursor_to_center", text="Cursor to Center")
+        layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid")
+
+
 ###############################
 
+
 class GPENCIL_UL_layer(UIList):
     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
         # assert(isinstance(item, bpy.types.GPencilLayer)
@@ -328,6 +457,25 @@ class GPENCIL_UL_layer(UIList):
             layout.label(text="", icon_value=icon)
 
 
+class GPENCIL_MT_layer_specials(Menu):
+    bl_label = "Layer"
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.operator("gpencil.layer_duplicate", icon='COPY_ID')  # XXX: needs a dedicated icon
+
+        layout.separator()
+
+        layout.operator("gpencil.reveal", icon='RESTRICT_VIEW_OFF', text="Show All")
+        layout.operator("gpencil.hide", icon='RESTRICT_VIEW_ON', text="Hide Others").unselected = True
+
+        layout.separator()
+
+        layout.operator("gpencil.lock_all", icon='LOCKED', text="Lock All")
+        layout.operator("gpencil.unlock_all", icon='UNLOCKED', text="UnLock All")
+
+
 class GreasePencilDataPanel:
     # subclass must set
     # bl_space_type = 'IMAGE_EDITOR'
@@ -379,7 +527,7 @@ class GreasePencilDataPanel:
 
         gpl = context.active_gpencil_layer
         if gpl:
-            sub.operator("gpencil.layer_duplicate", icon='COPY_ID', text="")  # XXX: needs a dedicated icon
+            sub.menu("GPENCIL_MT_layer_specials", icon='DOWNARROW_HLT', text="")
 
             if len(gpd.layers) > 1:
                 col.separator()
@@ -388,6 +536,12 @@ class GreasePencilDataPanel:
                 sub.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP'
                 sub.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN'
 
+                col.separator()
+
+                sub = col.column(align=True)
+                sub.operator("gpencil.layer_isolate", icon='SOLO_OFF', text="").affect_visibility = False
+                sub.operator("gpencil.layer_isolate", icon='RESTRICT_VIEW_OFF', text="").affect_visibility = True
+
         if gpl:
             self.draw_layer(layout, gpl)
 
@@ -492,4 +646,4 @@ class GreasePencilToolsPanel:
         layout.separator()
         layout.separator()
 
-        gpencil_stroke_placement_settings(context, layout, gpd)
+        gpencil_stroke_placement_settings(context, layout)
index d0f4b5a0df99e8047fc244924e541ba2c0235453..a730ab009dbe20e5472483b0ee7ce26438a35d2c 100644 (file)
@@ -24,6 +24,7 @@ from bpy.app.translations import pgettext_iface as iface_
 from bl_ui.properties_grease_pencil_common import (
         GreasePencilDrawingToolsPanel,
         GreasePencilStrokeEditPanel,
+        GreasePencilStrokeSculptPanel,
         GreasePencilDataPanel
         )
 
@@ -1134,6 +1135,10 @@ class CLIP_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
 class CLIP_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
     bl_space_type = 'CLIP_EDITOR'
 
+# Grease Pencil stroke sculpting tools
+class CLIP_PT_tools_grease_pencil_sculpt(GreasePencilStrokeSculptPanel, Panel):
+    bl_space_type = 'CLIP_EDITOR'
+
 
 class CLIP_MT_view(Menu):
     bl_label = "View"
index 34137c8e9d36c41e2d8417aa6bad561cbb018cf6..47775251955b1161e3be2fb46f7a580b1169438e 100644 (file)
@@ -138,6 +138,19 @@ class DOPESHEET_HT_header(Header):
             # 'genericFiltersOnly' limits the options to only the relevant 'generic' subset of
             # filters which will work here and are useful (especially for character animation)
             dopesheet_filter(layout, context, genericFiltersOnly=True)
+        elif st.mode == 'GPENCIL':
+            row = layout.row(align=True)
+            row.prop(st.dopesheet, "show_gpencil_3d_only", text="Active Only")
+
+            if st.dopesheet.show_gpencil_3d_only:
+                row = layout.row(align=True)
+                row.prop(st.dopesheet, "show_only_selected", text="")
+                row.prop(st.dopesheet, "show_hidden", text="")
+
+            row = layout.row(align=True)
+            row.prop(st.dopesheet, "use_filter_text", text="")
+            if st.dopesheet.use_filter_text:
+                row.prop(st.dopesheet, "filter_text", text="")
 
         row = layout.row(align=True)
         row.prop(toolsettings, "use_proportional_action",
index 0ed173414007344c7a24094207c2aeaba9377bbd..0f01956f2892383ddebc9f7052af5a7c85476456 100644 (file)
@@ -28,6 +28,7 @@ from bl_ui.properties_paint_common import (
 from bl_ui.properties_grease_pencil_common import (
         GreasePencilDrawingToolsPanel,
         GreasePencilStrokeEditPanel,
+        GreasePencilStrokeSculptPanel,
         GreasePencilDataPanel,
         )
 from bpy.app.translations import pgettext_iface as iface_
@@ -1192,5 +1193,10 @@ class IMAGE_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
     bl_space_type = 'IMAGE_EDITOR'
 
 
+# Grease Pencil stroke sculpting tools
+class IMAGE_PT_tools_grease_pencil_sculpt(GreasePencilStrokeSculptPanel, Panel):
+    bl_space_type = 'IMAGE_EDITOR'
+
+
 if __name__ == "__main__":  # only for live edit.
     bpy.utils.register_module(__name__)
index 3941e618b5b2fadc0ff326ca5dd1a66c9200352e..d0a2e094f0bcaef361cafd908406e9996f154d03 100644 (file)
@@ -24,6 +24,7 @@ from bpy.app.translations import pgettext_iface as iface_
 from bl_ui.properties_grease_pencil_common import (
         GreasePencilDrawingToolsPanel,
         GreasePencilStrokeEditPanel,
+        GreasePencilStrokeSculptPanel,
         GreasePencilDataPanel,
         GreasePencilToolsPanel,
         )
@@ -488,6 +489,11 @@ class NODE_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
     bl_space_type = 'NODE_EDITOR'
     bl_region_type = 'TOOLS'
 
+# Grease Pencil stroke sculpting tools
+class NODE_PT_tools_grease_pencil_sculpt(GreasePencilStrokeSculptPanel, Panel):
+    bl_space_type = 'NODE_EDITOR'
+    bl_region_type = 'TOOLS'
+
 # -----------------------------
 
 
index c8c0c27f9c45fa2d0407af95f10854a1f4fe78af..4dc4b667a63b41f4803d429ddb03aad7e0bba5db 100644 (file)
@@ -118,6 +118,14 @@ class VIEW3D_HT_header(Header):
             row.operator("pose.paste", text="", icon='PASTEDOWN').flipped = False
             row.operator("pose.paste", text="", icon='PASTEFLIPDOWN').flipped = True
 
+        # GPencil
+        if context.gpencil_data and context.gpencil_data.use_stroke_edit_mode:
+            row = layout.row(align=True)
+            row.operator("gpencil.copy", text="", icon='COPYDOWN')
+            row.operator("gpencil.paste", text="", icon='PASTEDOWN')
+
+            layout.prop(context.gpencil_data, "use_onion_skinning", text="Onion Skins", icon='PARTICLE_PATH') # XXX: icon
+
 
 class VIEW3D_MT_editor_menus(Menu):
     bl_space_type = 'VIEW3D_MT_editor_menus'
@@ -131,11 +139,14 @@ class VIEW3D_MT_editor_menus(Menu):
         obj = context.active_object
         mode_string = context.mode
         edit_object = context.edit_object
+        gp_edit = context.gpencil_data and context.gpencil_data.use_stroke_edit_mode
 
         layout.menu("VIEW3D_MT_view")
 
         # Select Menu
-        if mode_string in {'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}:
+        if gp_edit:
+            layout.menu("VIEW3D_MT_select_gpencil")
+        elif mode_string in {'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}:
             mesh = obj.data
             if mesh.use_paint_mask:
                 layout.menu("VIEW3D_MT_select_paint_mask")
@@ -144,7 +155,9 @@ class VIEW3D_MT_editor_menus(Menu):
         elif mode_string != 'SCULPT':
             layout.menu("VIEW3D_MT_select_%s" % mode_string.lower())
 
-        if mode_string == 'OBJECT':
+        if gp_edit:
+            pass
+        elif mode_string == 'OBJECT':
             layout.menu("INFO_MT_add", text="Add")
         elif mode_string == 'EDIT_MESH':
             layout.menu("INFO_MT_mesh_add", text="Add")
@@ -157,7 +170,9 @@ class VIEW3D_MT_editor_menus(Menu):
         elif mode_string == 'EDIT_ARMATURE':
             layout.menu("INFO_MT_edit_armature_add", text="Add")
 
-        if edit_object:
+        if gp_edit:
+            layout.menu("VIEW3D_MT_edit_gpencil")
+        elif edit_object:
             layout.menu("VIEW3D_MT_edit_%s" % edit_object.type.lower())
         elif obj:
             if mode_string != 'PAINT_TEXTURE':
@@ -883,6 +898,27 @@ class VIEW3D_MT_select_edit_armature(Menu):
         layout.operator("object.select_pattern", text="Select Pattern...")
 
 
+class VIEW3D_MT_select_gpencil(Menu):
+    bl_label = "Select"
+    
+    def draw(self, context):
+        layout = self.layout
+
+        layout.operator("gpencil.select_border")
+        layout.operator("gpencil.select_circle")
+
+        layout.separator()
+
+        layout.operator("gpencil.select_all", text="(De)select All").action = 'TOGGLE'
+        layout.operator("gpencil.select_all", text="Inverse").action = 'INVERT'
+        layout.operator("gpencil.select_linked", text="Linked")
+
+        layout.separator()
+
+        layout.operator("gpencil.select_more")
+        layout.operator("gpencil.select_less")
+
+
 class VIEW3D_MT_select_paint_mask(Menu):
     bl_label = "Select"
 
@@ -2822,6 +2858,81 @@ class VIEW3D_MT_edit_armature_delete(Menu):
         layout.operator("armature.dissolve", text="Dissolve")
 
 
+# ********** GPencil Stroke Edit menu **********
+
+
+class VIEW3D_MT_edit_gpencil(Menu):
+    bl_label = "GPencil"
+    
+    def draw(self, context):
+        toolsettings = context.tool_settings
+
+        layout = self.layout
+
+        layout.operator("ed.undo")
+        layout.operator("ed.redo")
+        layout.operator("ed.undo_history")
+
+        layout.separator()
+
+        layout.operator("gpencil.brush_paint", text="Sculpt Strokes").wait_for_input = True
+        layout.prop_menu_enum(toolsettings.gpencil_sculpt, "tool", text="Sculpt Brush")
+
+        layout.separator()
+
+        layout.menu("VIEW3D_MT_edit_gpencil_transform")
+        layout.operator("transform.mirror", text="Mirror")
+        layout.menu("GPENCIL_MT_snap")
+
+        layout.separator()
+
+        layout.menu("VIEW3D_MT_object_animation")   # NOTE: provides keyingset access...
+
+        layout.separator()
+
+        layout.menu("VIEW3D_MT_edit_gpencil_delete")
+        layout.operator("gpencil.duplicate_move", text="Duplicate")
+
+        layout.separator()
+
+        layout.operator("gpencil.copy", text="Copy")
+        layout.operator("gpencil.paste", text="Paste")
+
+        layout.separator()
+
+        layout.prop_menu_enum(toolsettings, "proportional_edit")
+        layout.prop_menu_enum(toolsettings, "proportional_edit_falloff")
+
+        layout.separator()
+
+        layout.operator("gpencil.reveal")
+        layout.operator("gpencil.hide", text="Show Active Layer Only").unselected = True
+        layout.operator("gpencil.hide", text="Hide Active Layer").unselected = False
+
+        layout.separator()
+
+        layout.operator_menu_enum("gpencil.move_to_layer", "layer", text="Move to Layer")
+        layout.operator_menu_enum("gpencil.convert", "type", text="Convert to Geometry...")
+
+
+class VIEW3D_MT_edit_gpencil_transform(Menu):
+    bl_label = "Transform"
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.operator("transform.translate")
+        layout.operator("transform.rotate")
+        layout.operator("transform.resize", text="Scale")
+
+        layout.separator()
+
+        layout.operator("transform.bend", text="Bend")
+        layout.operator("transform.shear", text="Shear")
+        layout.operator("transform.tosphere", text="To Sphere")
+        layout.operator("transform.transform", text="Shrink Fatten").mode = 'GPENCIL_SHRINKFATTEN'
+
+
 # ********** Panel **********
 
 
index a24dc494c30a183688c41b02a99dce448372f280..cf5e2daf0ce9c5375c182ce529b940e2c3ff24ab 100644 (file)
@@ -21,7 +21,8 @@ import bpy
 from bpy.types import Menu, Panel, UIList
 from bl_ui.properties_grease_pencil_common import (
         GreasePencilDrawingToolsPanel,
-        GreasePencilStrokeEditPanel
+        GreasePencilStrokeEditPanel,
+        GreasePencilStrokeSculptPanel
         )
 from bl_ui.properties_paint_common import (
         UnifiedPaintPanel,
@@ -1875,6 +1876,11 @@ class VIEW3D_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
     bl_space_type = 'VIEW_3D'
 
 
+# Grease Pencil stroke sculpting tools
+class VIEW3D_PT_tools_grease_pencil_sculpt(GreasePencilStrokeSculptPanel, Panel):
+    bl_space_type = 'VIEW_3D'
+
+
 # Note: moved here so that it's always in last position in 'Tools' panels!
 class VIEW3D_PT_tools_history(View3DPanel, Panel):
     bl_category = "Tools"
index 084c5527f211076bdb2fdb5ce37a3c64a0304242..99fc195ed57d251e4829a37ebc0104deb4eb64ce 100644 (file)
@@ -47,6 +47,7 @@ void BKE_gpencil_free(struct bGPdata *gpd);
 void gpencil_stroke_sync_selection(struct bGPDstroke *gps);
 
 struct bGPDframe *gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe);
+struct bGPDframe *gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe);
 struct bGPDlayer *gpencil_layer_addnew(struct bGPdata *gpd, const char *name, int setactive);
 struct bGPdata *gpencil_data_addnew(const char name[]);
 
@@ -56,9 +57,24 @@ struct bGPdata *gpencil_data_duplicate(struct bGPdata *gpd, bool internal_copy);
 
 void gpencil_frame_delete_laststroke(struct bGPDlayer *gpl, struct bGPDframe *gpf);
 
+
+/* How gpencil_layer_getframe() should behave when there
+ * is no existing GP-Frame on the frame requested.
+ */
+typedef enum eGP_GetFrame_Mode {
+       /* Use the preceeding gp-frame (i.e. don't add anything) */
+       GP_GETFRAME_USE_PREV  = 0,
+       
+       /* Add a new empty/blank frame */
+       GP_GETFRAME_ADD_NEW   = 1,
+       /* Make a copy of the active frame */
+       GP_GETFRAME_ADD_COPY  = 2
+} eGP_GetFrame_Mode;
+
+struct bGPDframe *gpencil_layer_getframe(struct bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew);
 struct bGPDframe *BKE_gpencil_layer_find_frame(struct bGPDlayer *gpl, int cframe);
-struct bGPDframe *gpencil_layer_getframe(struct bGPDlayer *gpl, int cframe, short addnew);
 bool gpencil_layer_delframe(struct bGPDlayer *gpl, struct bGPDframe *gpf);
+
 struct bGPDlayer *gpencil_layer_getactive(struct bGPdata *gpd);
 void gpencil_layer_setactive(struct bGPdata *gpd, struct bGPDlayer *active);
 void gpencil_layer_delete(struct bGPdata *gpd, struct bGPDlayer *gpl);
index ee5c91923716121789da4b189d98e578a4e5e781..e629a0791c95ab5c927b40c488d29f72a320a3ed 100644 (file)
@@ -132,7 +132,7 @@ bGPDframe *gpencil_frame_addnew(bGPDlayer *gpl, int cframe)
        bGPDframe *gpf = NULL, *gf = NULL;
        short state = 0;
        
-       /* error checking (neg frame only if they are not allowed in Blender!) */
+       /* error checking */
        if (gpl == NULL)
                return NULL;
                
@@ -178,6 +178,61 @@ bGPDframe *gpencil_frame_addnew(bGPDlayer *gpl, int cframe)
        return gpf;
 }
 
+/* add a copy of the active gp-frame to the given layer */
+bGPDframe *gpencil_frame_addcopy(bGPDlayer *gpl, int cframe)
+{
+       bGPDframe *new_frame, *gpf;
+       bool found = false;
+       
+       /* Error checking/handling */
+       if (gpl == NULL) {
+               /* no layer */
+               return NULL;
+       }
+       else if (gpl->actframe == NULL) {
+               /* no active frame, so just create a new one from scratch */
+               return gpencil_frame_addnew(gpl, cframe);
+       }
+       
+       /* Create a copy of the frame */
+       new_frame = gpencil_frame_duplicate(gpl->actframe);
+       
+       /* Find frame to insert it before */
+       for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
+               if (gpf->framenum > cframe) {
+                       /* Add it here */
+                       BLI_insertlinkbefore(&gpl->frames, gpf, new_frame);
+                       
+                       found = true;
+                       break;
+               }
+               else if (gpf->framenum == cframe) {
+                       /* This only happens when we're editing with framelock on...
+                        * - Delete the new frame and don't do anything else here...
+                        */
+                       free_gpencil_strokes(new_frame);
+                       MEM_freeN(new_frame);
+                       new_frame = NULL;
+                       
+                       found = true;
+                       break;
+               }
+       }
+       
+       if (found == false) {
+               /* Add new frame to the end */
+               BLI_addtail(&gpl->frames, new_frame);
+       }
+       
+       /* Ensure that frame is set up correctly, and return it */
+       if (new_frame) {
+               new_frame->framenum = cframe;
+               gpl->actframe = new_frame;
+       }
+       
+       return new_frame;
+}
+
 /* add a new gp-layer and make it the active layer */
 bGPDlayer *gpencil_layer_addnew(bGPdata *gpd, const char *name, int setactive)
 {
@@ -197,6 +252,13 @@ bGPDlayer *gpencil_layer_addnew(bGPdata *gpd, const char *name, int setactive)
        copy_v4_v4(gpl->color, U.gpencil_new_layer_col);
        gpl->thickness = 3;
        
+       /* onion-skinning settings */
+       gpl->flag |= (GP_LAYER_GHOST_PREVCOL | GP_LAYER_GHOST_NEXTCOL);
+       
+       ARRAY_SET_ITEMS(gpl->gcolor_prev, 0.145098f, 0.419608f, 0.137255f); /* green */
+       ARRAY_SET_ITEMS(gpl->gcolor_next, 0.125490f, 0.082353f, 0.529412f); /* blue */
+       
+       
        /* auto-name */
        BLI_strncpy(gpl->info, name, sizeof(gpl->info));
        BLI_uniquename(&gpd->layers, gpl, DATA_("GP_Layer"), '.', offsetof(bGPDlayer, info), sizeof(gpl->info));
@@ -387,7 +449,7 @@ bGPDframe *BKE_gpencil_layer_find_frame(bGPDlayer *gpl, int cframe)
  *     - this sets the layer's actframe var (if allowed to)
  *     - extension beyond range (if first gp-frame is after all frame in interest and cannot add)
  */
-bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew)
+bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew)
 {
        bGPDframe *gpf = NULL;
        short found = 0;
@@ -425,6 +487,8 @@ bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew)
                        if (addnew) {
                                if ((found) && (gpf->framenum == cframe))
                                        gpl->actframe = gpf;
+                               else if (addnew == GP_GETFRAME_ADD_COPY)
+                                       gpl->actframe = gpencil_frame_addcopy(gpl, cframe);
                                else
                                        gpl->actframe = gpencil_frame_addnew(gpl, cframe);
                        }
@@ -445,6 +509,8 @@ bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew)
                        if (addnew) {
                                if ((found) && (gpf->framenum == cframe))
                                        gpl->actframe = gpf;
+                               else if (addnew == GP_GETFRAME_ADD_COPY)
+                                       gpl->actframe = gpencil_frame_addcopy(gpl, cframe);
                                else
                                        gpl->actframe = gpencil_frame_addnew(gpl, cframe);
                        }
index 82a040f4ca0bf53f2bef6101e814447e0a540b05..b2ef693c7eb1ebac1fafc4fecb6cf4609fd5d5d9 100644 (file)
@@ -750,6 +750,53 @@ void BKE_scene_init(Scene *sce)
        copy_v2_fl2(sce->safe_areas.action_center, 15.0f / 100.0f, 5.0f / 100.0f);
 
        sce->preview = NULL;
+       
+       /* GP Sculpt brushes */
+       {
+               GP_BrushEdit_Settings *gset = &sce->toolsettings->gp_sculpt;
+               GP_EditBrush_Data *gp_brush;
+               
+               gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH];
+               gp_brush->size = 25;
+               gp_brush->strength = 0.3f;
+               gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE;
+               
+               gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS];
+               gp_brush->size = 25;
+               gp_brush->strength = 0.5f;
+               gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+               
+               gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB];
+               gp_brush->size = 50;
+               gp_brush->strength = 0.3f;
+               gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+               
+               gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH];
+               gp_brush->size = 25;
+               gp_brush->strength = 0.3f;
+               gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+               
+               gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST];
+               gp_brush->size = 50;
+               gp_brush->strength = 0.3f; // XXX?
+               gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+               
+               gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH];
+               gp_brush->size = 50;
+               gp_brush->strength = 0.5f; // XXX?
+               gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+               
+               gp_brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE];
+               gp_brush->size = 25;
+               gp_brush->strength = 0.5f;
+               gp_brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+       }
+       
+       /* GP Stroke Placement */
+       sce->toolsettings->gpencil_v3d_align = GP_PROJECT_VIEWSPACE;
+       sce->toolsettings->gpencil_v2d_align = GP_PROJECT_VIEWSPACE;
+       sce->toolsettings->gpencil_seq_align = GP_PROJECT_VIEWSPACE;
+       sce->toolsettings->gpencil_ima_align = GP_PROJECT_VIEWSPACE;
 }
 
 Scene *BKE_scene_add(Main *bmain, const char *name)
index 9bc0e2417227632e1c27a51ad24977e0c8571ff7..177055e720674b0e6a2c1b71fb6bee366d2f1dcf 100644 (file)
@@ -5822,6 +5822,7 @@ static void direct_link_scene(FileData *fd, Scene *sce)
                sce->toolsettings->particle.paintcursor = NULL;
                sce->toolsettings->particle.scene = NULL;
                sce->toolsettings->particle.object = NULL;
+               sce->toolsettings->gp_sculpt.paintcursor = NULL;
 
                /* in rare cases this is needed, see [#33806] */
                if (sce->toolsettings->vpaint) {
index a0248d5a97c602397bbee88055a1a2db1e3e1b50..8a2045ddacf46ca020704c7ec5250ef6497805bd 100644 (file)
@@ -38,6 +38,7 @@
 #include "DNA_camera_types.h"
 #include "DNA_cloth_types.h"
 #include "DNA_constraint_types.h"
+#include "DNA_gpencil_types.h"
 #include "DNA_sdna_types.h"
 #include "DNA_sequence_types.h"
 #include "DNA_space_types.h"
@@ -935,5 +936,81 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main)
                }
        }
 
-
+       {
+               Scene *scene;
+               for (scene = main->scene.first; scene; scene = scene->id.next) {
+                       ToolSettings *ts = scene->toolsettings;
+                       
+                       if (ts->gp_sculpt.brush[0].size == 0) {
+                               GP_BrushEdit_Settings *gset = &ts->gp_sculpt;
+                               GP_EditBrush_Data *brush;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH];
+                               brush->size = 25;
+                               brush->strength = 0.3f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS];
+                               brush->size = 25;
+                               brush->strength = 0.5f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB];
+                               brush->size = 50;
+                               brush->strength = 0.3f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH];
+                               brush->size = 25;
+                               brush->strength = 0.3f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST];
+                               brush->size = 50;
+                               brush->strength = 0.3f; // XXX?
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH];
+                               brush->size = 50;
+                               brush->strength = 0.5f; // XXX?
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE];
+                               brush->size = 25;
+                               brush->strength = 0.5f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_CLONE];
+                               brush->size = 50;
+                               brush->strength = 1.0f;
+                       }
+                       
+                       if (!DNA_struct_elem_find(fd->filesdna, "ToolSettings", "char", "gpencil_v3d_align")) {
+#if 0 /* XXX: Cannot do this, as we get random crashes... */
+                               if (scene->gpd) {
+                                       bGPdata *gpd = scene->gpd;
+                                       
+                                       /* Copy over the settings stored in the GP datablock linked to the scene, for minimal disruption */
+                                       ts->gpencil_v3d_align = 0;
+                                       
+                                       if (gpd->flag & GP_DATA_VIEWALIGN)    ts->gpencil_v3d_align |= GP_PROJECT_VIEWSPACE;
+                                       if (gpd->flag & GP_DATA_DEPTH_VIEW)   ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_VIEW;
+                                       if (gpd->flag & GP_DATA_DEPTH_STROKE) ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_STROKE;
+                                       
+                                       if (gpd->flag & GP_DATA_DEPTH_STROKE_ENDPOINTS)
+                                               ts->gpencil_v3d_align |= GP_PROJECT_DEPTH_STROKE_ENDPOINTS;
+                               }
+                               else {
+                                       /* Default to cursor for all standard 3D views */
+                                       ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE;
+                               }
+#endif
+                               
+                               ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE;
+                               ts->gpencil_v2d_align = GP_PROJECT_VIEWSPACE;
+                               ts->gpencil_seq_align = GP_PROJECT_VIEWSPACE;
+                               ts->gpencil_ima_align = GP_PROJECT_VIEWSPACE;
+                       }
+               }
+       }
 }
index 01af11e78d17f8188b2859f246273ca4c9df761d..eb0d392aa924325b819dfc2c40c9af6ed3999fc4 100644 (file)
@@ -91,6 +91,51 @@ void BLO_update_defaults_startup_blend(Main *bmain)
                                sculpt->flags |= SCULPT_DYNTOPO_COLLAPSE;
                                sculpt->detail_size = 12;
                        }
+                       
+                       if (ts->gp_sculpt.brush[0].size == 0) {
+                               GP_BrushEdit_Settings *gset = &ts->gp_sculpt;
+                               GP_EditBrush_Data *brush;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_SMOOTH];
+                               brush->size = 25;
+                               brush->strength = 0.3f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF | GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_THICKNESS];
+                               brush->size = 25;
+                               brush->strength = 0.5f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_GRAB];
+                               brush->size = 50;
+                               brush->strength = 0.3f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_PUSH];
+                               brush->size = 25;
+                               brush->strength = 0.3f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_TWIST];
+                               brush->size = 50;
+                               brush->strength = 0.3f; // XXX?
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_PINCH];
+                               brush->size = 50;
+                               brush->strength = 0.5f; // XXX?
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                               
+                               brush = &gset->brush[GP_EDITBRUSH_TYPE_RANDOMISE];
+                               brush->size = 25;
+                               brush->strength = 0.5f;
+                               brush->flag = GP_EDITBRUSH_FLAG_USE_FALLOFF;
+                       }
+                       
+                       ts->gpencil_v3d_align = GP_PROJECT_VIEWSPACE;
+                       ts->gpencil_v2d_align = GP_PROJECT_VIEWSPACE;
+                       ts->gpencil_seq_align = GP_PROJECT_VIEWSPACE;
+                       ts->gpencil_ima_align = GP_PROJECT_VIEWSPACE;
                }
 
                scene->gm.lodflag |= SCE_LOD_USE_HYST;
index ed3d228a93e9561dd3142559167379b3e1e020fd..f41420ad231a6c49c111203dd92890caba306e7a 100644 (file)
@@ -403,7 +403,6 @@ static bool find_prev_next_keyframes(struct bContext *C, int *nextfra, int *prev
 {
        Scene *scene = CTX_data_scene(C);
        Object *ob = CTX_data_active_object(C);
-       bGPdata *gpd = CTX_data_gpencil_data(C);
        Mask *mask = CTX_data_edit_mask(C);
        bDopeSheet ads = {NULL};
        DLRBT_Tree keys;
@@ -425,11 +424,12 @@ static bool find_prev_next_keyframes(struct bContext *C, int *nextfra, int *prev
 
        /* populate tree with keyframe nodes */
        scene_to_keylist(&ads, scene, &keys, NULL);
+       gpencil_to_keylist(&ads, scene->gpd, &keys);
 
-       if (ob)
+       if (ob) {
                ob_to_keylist(&ads, ob, &keys, NULL);
-
-       gpencil_to_keylist(&ads, gpd, &keys);
+               gpencil_to_keylist(&ads, ob->gpd, &keys);
+       }
 
        if (mask) {
                MaskLayer *masklay = BKE_mask_layer_active(mask);
index bdf5291dcc79d1e268ef382fbea58c9d4f93ca7f..51b5a366861cf50c5748f2621d261876298d556a 100644 (file)
@@ -1505,7 +1505,7 @@ static size_t animdata_filter_shapekey(bAnimContext *ac, ListBase *anim_data, Ke
 }
 
 /* Helper for Grease Pencil - layers within a datablock */
-static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, int filter_mode)
+static size_t animdata_filter_gpencil_layers_data(ListBase *anim_data, bDopeSheet *ads, bGPdata *gpd, int filter_mode)
 {
        bGPDlayer *gpl;
        size_t items = 0;
@@ -1518,6 +1518,13 @@ static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, in
                        if (!(filter_mode & ANIMFILTER_FOREDIT) || EDITABLE_GPL(gpl)) {
                                /* active... */
                                if (!(filter_mode & ANIMFILTER_ACTIVE) || (gpl->flag & GP_LAYER_ACTIVE)) {
+                                       /* skip layer if the name doesn't match the filter string */
+                                       if ((ads) && (ads->filterflag & ADS_FILTER_BY_FCU_NAME)) {
+                                               if (BLI_strcasestr(gpl->info, ads->searchstr) == NULL)
+                                                       continue;
+                                       }
+                                       
+                                       
                                        /* add to list */
                                        ANIMCHANNEL_NEW_CHANNEL(gpl, ANIMTYPE_GPLAYER, gpd);
                                }
@@ -1528,54 +1535,121 @@ static size_t animdata_filter_gpencil_data(ListBase *anim_data, bGPdata *gpd, in
        return items;
 }
 
-/* Grab all Grease Pencil datablocks in file */
-// TODO: should this be amalgamated with the dopesheet filtering code?
-static size_t animdata_filter_gpencil(ListBase *anim_data, void *UNUSED(data), int filter_mode)
+/* Helper for Grease Pencil - Grease Pencil datablock - GP Frames */
+static size_t animdata_filter_gpencil_data(ListBase *anim_data, bDopeSheet *ads, bGPdata *gpd, int filter_mode)
 {
-       bGPdata *gpd;
        size_t items = 0;
        
-       /* for now, grab grease pencil datablocks directly from main */
-       // XXX: this is not good...
-       for (gpd = G.main->gpencil.first; gpd; gpd = gpd->id.next) {
+       /* When asked from "AnimData" blocks (i.e. the top-level containers for normal animation),
+        * for convenience, this will return GP Datablocks instead. This may cause issues down
+        * the track, but for now, this will do...
+        */
+       if (filter_mode & ANIMFILTER_ANIMDATA) {
+               /* just add GPD as a channel - this will add everything needed */
+               ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL);
+       }
+       else {
                ListBase tmp_data = {NULL, NULL};
                size_t tmp_items = 0;
                
-               /* only show if gpd is used by something... */
-               if (ID_REAL_USERS(gpd) < 1)
-                       continue;
-               
-               /* When asked from "AnimData" blocks (i.e. the top-level containers for normal animation),
-                * for convenience, this will return GP Datablocks instead. This may cause issues down
-                * the track, but for now, this will do...
-                */
-               if (filter_mode & ANIMFILTER_ANIMDATA) {
-                       /* just add GPD as a channel - this will add everything needed */
-                       ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL);
+               /* add gpencil animation channels */
+               BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_GPD(gpd))
+               {
+                       tmp_items += animdata_filter_gpencil_layers_data(&tmp_data, ads, gpd, filter_mode);
                }
-               else {
-                       /* add gpencil animation channels */
-                       BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_GPD(gpd))
-                       {
-                               tmp_items += animdata_filter_gpencil_data(&tmp_data, gpd, filter_mode);
+               END_ANIMFILTER_SUBCHANNELS;
+               
+               /* did we find anything? */
+               if (tmp_items) {
+                       /* include data-expand widget first */
+                       if (filter_mode & ANIMFILTER_LIST_CHANNELS) {
+                               /* add gpd as channel too (if for drawing, and it has layers) */
+                               ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL);
                        }
-                       END_ANIMFILTER_SUBCHANNELS;
                        
-                       /* did we find anything? */
-                       if (tmp_items) {
-                               /* include data-expand widget first */
-                               if (filter_mode & ANIMFILTER_LIST_CHANNELS) {
-                                       /* add gpd as channel too (if for drawing, and it has layers) */
-                                       ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_GPDATABLOCK, NULL);
+                       /* now add the list of collected channels */
+                       BLI_movelisttolist(anim_data, &tmp_data);
+                       BLI_assert(BLI_listbase_is_empty(&tmp_data));
+                       items += tmp_items;
+               }
+       }
+       
+       return items;
+}
+
+/* Grab all Grease Pencil datablocks in file */
+// TODO: should this be amalgamated with the dopesheet filtering code?
+static size_t animdata_filter_gpencil(bAnimContext *ac, ListBase *anim_data, void *UNUSED(data), int filter_mode)
+{
+       bDopeSheet *ads = ac->ads;
+       size_t items = 0;
+       
+       if (ads->filterflag & ADS_FILTER_GP_3DONLY) {
+               Scene *scene = (Scene *)ads->source;
+               Base *base;
+               
+               /* Active scene's GPencil block first - No parent item needed... */
+               if (scene->gpd) {
+                       items += animdata_filter_gpencil_data(anim_data, ads, scene->gpd, filter_mode);
+               }
+               
+               /* Objects in the scene */
+               for (base = scene->base.first; base; base = base->next) {
+                       /* Only consider this object if it has got some GP data (saving on all the other tests) */
+                       if (base->object && base->object->gpd) {
+                               Object *ob = base->object;
+                               
+                               /* firstly, check if object can be included, by the following factors:
+                                *      - if only visible, must check for layer and also viewport visibility
+                                *              --> while tools may demand only visible, user setting takes priority
+                                *                      as user option controls whether sets of channels get included while
+                                *                      tool-flag takes into account collapsed/open channels too
+                                *      - if only selected, must check if object is selected 
+                                *      - there must be animation data to edit (this is done recursively as we 
+                                *        try to add the channels)
+                                */
+                               if ((filter_mode & ANIMFILTER_DATA_VISIBLE) && !(ads->filterflag & ADS_FILTER_INCL_HIDDEN)) {
+                                       /* layer visibility - we check both object and base, since these may not be in sync yet */
+                                       if ((scene->lay & (ob->lay | base->lay)) == 0) continue;
+                                       
+                                       /* outliner restrict-flag */
+                                       if (ob->restrictflag & OB_RESTRICT_VIEW) continue;
                                }
                                
-                               /* now add the list of collected channels */
-                               BLI_movelisttolist(anim_data, &tmp_data);
-                               BLI_assert(BLI_listbase_is_empty(&tmp_data));
-                               items += tmp_items;
+                               /* check selection and object type filters */
+                               if ( (ads->filterflag & ADS_FILTER_ONLYSEL) && !((base->flag & SELECT) /*|| (base == scene->basact)*/) ) {
+                                       /* only selected should be shown */
+                                       continue;
+                               }
+                               
+                               /* check if object belongs to the filtering group if option to filter 
+                                * objects by the grouped status is on
+                                *      - used to ease the process of doing multiple-character choreographies
+                                */
+                               if (ads->filterflag & ADS_FILTER_ONLYOBGROUP) {
+                                       if (BKE_group_object_exists(ads->filter_grp, ob) == 0)
+                                               continue;
+                               }
+                               
+                               /* finally, include this object's grease pencil datablock */
+                               /* XXX: Should we store these under expanders per item? */
+                               items += animdata_filter_gpencil_data(anim_data, ads, ob->gpd, filter_mode);
                        }
                }
        }
+       else {
+               bGPdata *gpd;
+               
+               /* Grab all Grease Pencil datablocks directly from main, but only those that seem to be useful somewhere */
+               for (gpd = G.main->gpencil.first; gpd; gpd = gpd->id.next) {
+                       /* only show if gpd is used by something... */
+                       if (ID_REAL_USERS(gpd) < 1)
+                               continue;
+                       
+                       /* add GP frames from this datablock */
+                       items += animdata_filter_gpencil_data(anim_data, ads, gpd, filter_mode);
+               }
+       }
        
        /* return the number of items added to the list */
        return items;
@@ -2880,7 +2954,7 @@ size_t ANIM_animdata_filter(bAnimContext *ac, ListBase *anim_data, eAnimFilter_F
                        case ANIMCONT_GPENCIL:
                        {
                                if (animdata_filter_dopesheet_summary(ac, anim_data, filter_mode, &items))
-                                       items = animdata_filter_gpencil(anim_data, data, filter_mode);
+                                       items = animdata_filter_gpencil(ac, anim_data, data, filter_mode);
                                break;
                        }
                        case ANIMCONT_MASK:
index be58c75e202f9c8241786bb8be81fd071bb99d49..0e9e46bced023fa8cc7df430e15fbb71d13f304b 100644 (file)
@@ -929,7 +929,7 @@ short paste_animedit_keys(bAnimContext *ac, ListBase *anim_data,
                return -1;
        }
        
-       /* mathods of offset */
+       /* methods of offset */
        switch (offset_mode) {
                case KEYFRAME_PASTE_OFFSET_CFRA_START:
                        offset = (float)(CFRA - animcopy_firstframe);
index 83a13abb33ff2d93edbb42dc133d53583e9e3c68..6604d5955738fc58a4d51fdf8d44539d52a61c08 100644 (file)
@@ -40,6 +40,7 @@ set(INC_SYS
 set(SRC
        drawgpencil.c
        editaction_gpencil.c
+       gpencil_brush.c
        gpencil_convert.c
        gpencil_data.c
        gpencil_edit.c
index d17ed22b1ec0db4fa92ccba331ad6d8970229902..f207c71474c48ffe0f19e770a90c0621577540ac 100644 (file)
@@ -1166,6 +1166,7 @@ static void gp_draw_data_all(Scene *scene, bGPdata *gpd, int offsx, int offsy, i
 /* draw grease-pencil sketches to specified 2d-view that uses ibuf corrections */
 void ED_gpencil_draw_2dimage(const bContext *C)
 {
+       wmWindowManager *wm = CTX_wm_manager(C);
        ScrArea *sa = CTX_wm_area(C);
        ARegion *ar = CTX_wm_region(C);
        Scene *scene = CTX_data_scene(C);
@@ -1218,6 +1219,13 @@ void ED_gpencil_draw_2dimage(const bContext *C)
                        break;
        }
        
+       if (ED_screen_animation_playing(wm)) {
+               /* don't show onionskins during animation playback/scrub (i.e. it obscures the poses)
+                * OpenGL Renders (i.e. final output), or depth buffer (i.e. not real strokes)
+                */
+               dflag |= GP_DRAWDATA_NO_ONIONS;
+       }
+       
        
        /* draw it! */
        gp_draw_data_all(scene, gpd, offsx, offsy, sizex, sizey, CFRA, dflag, sa->spacetype);
@@ -1228,6 +1236,7 @@ void ED_gpencil_draw_2dimage(const bContext *C)
  * second time with onlyv2d=0 for screen-aligned strokes */
 void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d)
 {
+       wmWindowManager *wm = CTX_wm_manager(C);
        ScrArea *sa = CTX_wm_area(C);
        ARegion *ar = CTX_wm_region(C);
        Scene *scene = CTX_data_scene(C);
@@ -1246,6 +1255,8 @@ void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d)
        
        /* draw it! */
        if (onlyv2d) dflag |= (GP_DRAWDATA_ONLYV2D | GP_DRAWDATA_NOSTATUS);
+       if (ED_screen_animation_playing(wm)) dflag |= GP_DRAWDATA_NO_ONIONS;
+       
        gp_draw_data_all(scene, gpd, 0, 0, ar->winx, ar->winy, CFRA, dflag, sa->spacetype);
        
        /* draw status text (if in screen/pixel-space) */
@@ -1257,7 +1268,7 @@ void ED_gpencil_draw_view2d(const bContext *C, bool onlyv2d)
 /* draw grease-pencil sketches to specified 3d-view assuming that matrices are already set correctly
  * Note: this gets called twice - first time with only3d=1 to draw 3d-strokes,
  * second time with only3d=0 for screen-aligned strokes */
-void ED_gpencil_draw_view3d(Scene *scene, View3D *v3d, ARegion *ar, bool only3d)
+void ED_gpencil_draw_view3d(wmWindowManager *wm, Scene *scene, View3D *v3d, ARegion *ar, bool only3d)
 {
        bGPdata *gpd;
        int dflag = 0;
@@ -1300,13 +1311,15 @@ void ED_gpencil_draw_view3d(Scene *scene, View3D *v3d, ARegion *ar, bool only3d)
                dflag |= GP_DRAWDATA_NOSTATUS;
        }
        
+       if ((wm == NULL) || ED_screen_animation_playing(wm)) {
+               /* don't show onionskins during animation playback/scrub (i.e. it obscures the poses)
+                * OpenGL Renders (i.e. final output), or depth buffer (i.e. not real strokes)
+                */
+               dflag |= GP_DRAWDATA_NO_ONIONS;
+       }
+       
        /* draw it! */
        gp_draw_data_all(scene, gpd, offsx, offsy, winx, winy, CFRA, dflag, v3d->spacetype);
-       
-       /* draw status text (if in screen/pixel-space) */
-       if (only3d == false) {
-               gp_draw_status_text(gpd, ar);
-       }
 }
 
 void ED_gpencil_draw_ex(Scene *scene, bGPdata *gpd, int winx, int winy, const int cfra, const char spacetype)
index a2ba6216f9cfd6d68023e9426b80740529fb20a3..9f96ac6122f7bd36566568444a7f5a4b56035bad 100644 (file)
 
 #include "BKE_fcurve.h"
 #include "BKE_gpencil.h"
+#include "BKE_report.h"
 
+#include "ED_anim_api.h"
 #include "ED_gpencil.h"
 #include "ED_keyframes_edit.h"
 #include "ED_markers.h"
 
+#include "WM_api.h"
+
 /* ***************************************** */
 /* NOTE ABOUT THIS FILE:
  *  This file contains code for editing Grease Pencil data in the Action Editor
@@ -268,7 +272,7 @@ void ED_gplayer_frames_keytype_set(bGPDlayer *gpl, short type)
        }
 }
 
-#if 0 // XXX disabled until grease pencil code stabilises again
+
 /* -------------------------------------- */
 /* Copy and Paste Tools */
 /* - The copy/paste buffer currently stores a set of GP_Layers, with temporary
@@ -280,118 +284,155 @@ void ED_gplayer_frames_keytype_set(bGPDlayer *gpl, short type)
  */
 
 /* globals for copy/paste data (like for other copy/paste buffers) */
-ListBase gpcopybuf = {NULL, NULL};
-static int gpcopy_firstframe = 999999999;
+ListBase gp_anim_copybuf = {NULL, NULL};
+static int gp_anim_copy_firstframe =  999999999;
+static int gp_anim_copy_lastframe  = -999999999;
+static int gp_anim_copy_cfra       =  0;
+
 
 /* This function frees any MEM_calloc'ed copy/paste buffer data */
-void free_gpcopybuf()
+void ED_gpencil_anim_copybuf_free(void)
 {
-       free_gpencil_layers(&gpcopybuf);
+       free_gpencil_layers(&gp_anim_copybuf);
+       BLI_listbase_clear(&gp_anim_copybuf);
        
-       BLI_listbase_clear(&gpcopybuf);
-       gpcopy_firstframe = 999999999;
+       gp_anim_copy_firstframe =  999999999;
+       gp_anim_copy_lastframe  = -999999999;
+       gp_anim_copy_cfra       =  0;
 }
 
+
 /* This function adds data to the copy/paste buffer, freeing existing data first
  * Only the selected GP-layers get their selected keyframes copied.
+ *
+ * Returns whether the copy operation was successful or not
  */
-void copy_gpdata()
+bool ED_gpencil_anim_copybuf_copy(bAnimContext *ac)
 {
-       ListBase act_data = {NULL, NULL};
-       bActListElem *ale;
+       ListBase anim_data = {NULL, NULL};
+       bAnimListElem *ale;
        int filter;
-       void *data;
-       short datatype;
        
-       /* clear buffer first */
-       free_gpcopybuf();
+       Scene *scene = ac->scene;
        
-       /* get data */
-       data = get_action_context(&datatype);
-       if (data == NULL) return;
-       if (datatype != ACTCONT_GPENCIL) return;
        
-       /* filter data */
-       filter = (ACTFILTER_VISIBLE | ACTFILTER_SEL);
-       actdata_filter(&act_data, filter, data, datatype);
+       /* clear buffer first */
+       ED_gpencil_anim_copybuf_free();
        
-       /* assume that each of these is an ipo-block */
-       for (ale = act_data.first; ale; ale = ale->next) {
-               bGPDlayer *gpls, *gpln;
-               bGPDframe *gpf, *gpfn;
-               
-               /* get new layer to put into buffer */
-               gpls = (bGPDlayer *)ale->data;
-               gpln = MEM_callocN(sizeof(bGPDlayer), "GPCopyPasteLayer");
-               
-               BLI_listbase_clear(&gpln->frames);
-               BLI_strncpy(gpln->info, gpls->info, sizeof(gpln->info));
-               
-               BLI_addtail(&gpcopybuf, gpln);
+       /* filter data */
+       filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS);
+       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+       
+       /* assume that each of these is a GP layer */
+       for (ale = anim_data.first; ale; ale = ale->next) {
+               ListBase copied_frames = {NULL, NULL};
+               bGPDlayer *gpl = (bGPDlayer *)ale->data;
+               bGPDframe *gpf;
                
                /* loop over frames, and copy only selected frames */
-               for (gpf = gpls->frames.first; gpf; gpf = gpf->next) {
+               for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
                        /* if frame is selected, make duplicate it and its strokes */
                        if (gpf->flag & GP_FRAME_SELECT) {
-                               /* add frame to buffer */
-                               gpfn = gpencil_frame_duplicate(gpf);
-                               BLI_addtail(&gpln->frames, gpfn);
+                               /* make a copy of this frame */
+                               bGPDframe *new_frame = gpencil_frame_duplicate(gpf);
+                               BLI_addtail(&copied_frames, new_frame);
                                
-                               /* check if this is the earliest frame encountered so far */
-                               if (gpf->framenum < gpcopy_firstframe)
-                                       gpcopy_firstframe = gpf->framenum;
+                               /* extend extents for keyframes encountered */
+                               if (gpf->framenum  < gp_anim_copy_firstframe)
+                                       gp_anim_copy_firstframe = gpf->framenum; 
+                               if (gpf->framenum > gp_anim_copy_lastframe)
+                                       gp_anim_copy_lastframe = gpf->framenum;
                        }
                }
+               
+               /* create a new layer in buffer if there were keyframes here */
+               if (BLI_listbase_is_empty(&copied_frames) == false) {
+                       bGPDlayer *new_layer = MEM_callocN(sizeof(bGPDlayer), "GPCopyPasteLayer");
+                       BLI_addtail(&gp_anim_copybuf, new_layer);
+                       
+                       /* move over copied frames */
+                       BLI_movelisttolist(&new_layer->frames, &copied_frames);
+                       BLI_assert(copied_frames.first == NULL);
+                       
+                       /* make a copy of the layer's name - for name-based matching later... */
+                       BLI_strncpy(new_layer->info, gpl->info, sizeof(new_layer->info));
+               }
        }
        
+       /* in case 'relative' paste method is used */
+       gp_anim_copy_cfra = CFRA;
+       
+       /* clean up */
+       ANIM_animdata_freelist(&anim_data);
+       
        /* check if anything ended up in the buffer */
-       if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last))
-               error("Nothing copied to buffer");
+       if (ELEM(NULL, gp_anim_copybuf.first, gp_anim_copybuf.last)) {
+               BKE_report(ac->reports, RPT_ERROR, "No keyframes copied to keyframes copy/paste buffer");
+               return false;
+       }
        
-       /* free temp memory */
-       BLI_freelistN(&act_data);
+       /* report success */
+       return true;
 }
 
-void paste_gpdata(Scene *scene)
+
+/* Pastes keyframes from buffer, and reports success */
+bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode)
 {
-       ListBase act_data = {NULL, NULL};
-       bActListElem *ale;
+       ListBase anim_data = {NULL, NULL};
+       bAnimListElem *ale;
        int filter;
-       void *data;
-       short datatype;
        
-       const int offset = (CFRA - gpcopy_firstframe);
-       short no_name = 0;
+       Scene *scene = ac->scene;
+       bool no_name = false;
+       int offset = 0;
        
        /* check if buffer is empty */
-       if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last)) {
-               error("No data in buffer to paste");
-               return;
+       if (BLI_listbase_is_empty(&gp_anim_copybuf)) {
+               BKE_report(ac->reports, RPT_ERROR, "No data in buffer to paste");
+               return false;
        }
+       
        /* check if single channel in buffer (disregard names if so)  */
-       if (gpcopybuf.first == gpcopybuf.last)
-               no_name = 1;
+       if (gp_anim_copybuf.first == gp_anim_copybuf.last) {
+               no_name = true;
+       }
        
-       /* get data */
-       data = get_action_context(&datatype);
-       if (data == NULL) return;
-       if (datatype != ACTCONT_GPENCIL) return;
+       /* methods of offset (eKeyPasteOffset) */
+       switch (offset_mode) {
+               case KEYFRAME_PASTE_OFFSET_CFRA_START:
+                       offset = (CFRA - gp_anim_copy_firstframe);
+                       break;
+               case KEYFRAME_PASTE_OFFSET_CFRA_END:
+                       offset = (CFRA - gp_anim_copy_lastframe);
+                       break;
+               case KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE:
+                       offset = (CFRA - gp_anim_copy_cfra);
+                       break;
+               case KEYFRAME_PASTE_OFFSET_NONE:
+                       offset = 0;
+                       break;
+       }
+
        
        /* filter data */
-       filter = (ACTFILTER_VISIBLE | ACTFILTER_SEL | ACTFILTER_FOREDIT);
-       actdata_filter(&act_data, filter, data, datatype);
+       // TODO: try doing it with selection, then without selection imits
+       filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
+       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
        
        /* from selected channels */
-       for (ale = act_data.first; ale; ale = ale->next) {
+       for (ale = anim_data.first; ale; ale = ale->next) {
                bGPDlayer *gpld = (bGPDlayer *)ale->data;
                bGPDlayer *gpls = NULL;
                bGPDframe *gpfs, *gpf;
                
+               
                /* find suitable layer from buffer to use to paste from */
-               for (gpls = gpcopybuf.first; gpls; gpls = gpls->next) {
+               for (gpls = gp_anim_copybuf.first; gpls; gpls = gpls->next) {
                        /* check if layer name matches */
-                       if ((no_name) || STREQ(gpls->info, gpld->info))
+                       if ((no_name) || STREQ(gpls->info, gpld->info)) {
                                break;
+                       }
                }
                
                /* this situation might occur! */
@@ -407,58 +448,21 @@ void paste_gpdata(Scene *scene)
                        gpf = gpencil_layer_getframe(gpld, gpfs->framenum, 1);
                        if (gpf) {
                                bGPDstroke *gps, *gpsn;
-                               ScrArea *sa;
-                               
-                               /* get area that gp-data comes from */
-                               //sa = gpencil_data_findowner((bGPdata *)ale->owner);
-                               sa = NULL;
                                
-                               /* this should be the right frame... as it may be a pre-existing frame,
+                               /* This should be the right frame... as it may be a pre-existing frame,
                                 * must make sure that only compatible stroke types get copied over
-                                *      - we cannot just add a duplicate frame, as that would cause errors
-                                *      - need to check for compatible types to minimize memory usage (copying 'junk' over)
+                                *      - We cannot just add a duplicate frame, as that would cause errors
+                                *  - For now, we don't check if the types will be compatible since we
+                                *    don't have enough info to do so. Instead, we simply just paste,
+                                *    af it works, it will show up.
                                 */
                                for (gps = gpfs->strokes.first; gps; gps = gps->next) {
-                                       short stroke_ok;
+                                       /* make a copy of stroke, then of its points array */
+                                       gpsn = MEM_dupallocN(gps);
+                                       gpsn->points = MEM_dupallocN(gps->points);
                                        
-                                       /* if there's an area, check that it supports this type of stroke */
-                                       if (sa) {
-                                               stroke_ok = 0;
-                                               
-                                               /* check if spacetype supports this type of stroke
-                                                *      - NOTE: must sync this with gp_paint_initstroke() in gpencil.c
-                                                */
-                                               switch (sa->spacetype) {
-                                                       case SPACE_VIEW3D: /* 3D-View: either screen-aligned or 3d-space */
-                                                               if ((gps->flag == 0) || (gps->flag & GP_STROKE_3DSPACE))
-                                                                       stroke_ok = 1;
-                                                               break;
-                                                       
-                                                       case SPACE_NODE: /* Nodes Editor: either screen-aligned or view-aligned */
-                                                       case SPACE_IMAGE: /* Image Editor: either screen-aligned or view\image-aligned */
-                                                       case SPACE_CLIP: /* Image Editor: either screen-aligned or view\image-aligned */
-                                                               if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DSPACE))
-                                                                       stroke_ok = 1;
-                                                               break;
-                                                       
-                                                       case SPACE_SEQ: /* Sequence Editor: either screen-aligned or view-aligned */
-                                                               if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DIMAGE))
-                                                                       stroke_ok = 1;
-                                                               break;
-                                               }
-                                       }
-                                       else
-                                               stroke_ok = 1;
-                                       
-                                       /* if stroke is ok, we make a copy of this stroke and add to frame */
-                                       if (stroke_ok) {
-                                               /* make a copy of stroke, then of its points array */
-                                               gpsn = MEM_dupallocN(gps);
-                                               gpsn->points = MEM_dupallocN(gps->points);
-                                               
-                                               /* append stroke to frame */
-                                               BLI_addtail(&gpf->strokes, gpsn);
-                                       }
+                                       /* append stroke to frame */
+                                       BLI_addtail(&gpf->strokes, gpsn);                               
                                }
                                
                                /* if no strokes (i.e. new frame) added, free gpf */
@@ -471,13 +475,10 @@ void paste_gpdata(Scene *scene)
                }
        }
        
-       /* free temp memory */
-       BLI_freelistN(&act_data);
-       
-       /* undo and redraw stuff */
-       BIF_undo_push("Paste Grease Pencil Frames");
+       /* clean up */
+       ANIM_animdata_freelist(&anim_data);
+       return true;
 }
-#endif /* XXX disabled until Grease Pencil code stabilises again... */
 
 /* -------------------------------------- */
 /* Snap Tools */
diff --git a/source/blender/editors/gpencil/gpencil_brush.c b/source/blender/editors/gpencil/gpencil_brush.c
new file mode 100644 (file)
index 0000000..ecc78df
--- /dev/null
@@ -0,0 +1,1691 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2015, Blender Foundation
+ * This is a new part of Blender
+ *
+ * Contributor(s): Joshua Leung
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ * Brush based operators for editing Grease Pencil strokes
+ */
+
+/** \file blender/editors/gpencil/gpencil_edit.c
+ *  \ingroup edgpencil
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <math.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_ghash.h"
+#include "BLI_math.h"
+#include "BLI_rand.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+#include "DNA_view3d_types.h"
+#include "DNA_gpencil_types.h"
+
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_gpencil.h"
+#include "BKE_library.h"
+#include "BKE_report.h"
+#include "BKE_screen.h"
+
+#include "UI_interface.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "UI_view2d.h"
+
+#include "ED_gpencil.h"
+#include "ED_screen.h"
+#include "ED_view3d.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "gpencil_intern.h"
+
+/* ************************************************ */
+/* General Brush Editing Context */
+
+/* Context for brush operators */
+typedef struct tGP_BrushEditData {
+       /* Current editor/region/etc. */
+       /* NOTE: This stuff is mainly needed to handle 3D view projection stuff... */
+       Scene *scene;
+       
+       ScrArea *sa;
+       ARegion *ar;
+       
+       /* Current GPencil datablock */
+       bGPdata *gpd;
+       
+       /* Brush Settings */
+       GP_BrushEdit_Settings *settings;
+       GP_EditBrush_Data *brush;
+       
+       eGP_EditBrush_Types brush_type;
+       eGP_EditBrush_Flag  flag;
+       
+       /* Space Conversion Data */
+       GP_SpaceConversion gsc;
+       
+       
+       /* Is the brush currently painting? */
+       bool is_painting;
+       
+       /* Start of new sculpt stroke */
+       bool first;
+       
+       /* Current frame */
+       int cfra;
+       
+       
+       /* Brush Runtime Data: */
+       /* - position and pressure
+        * - the *_prev variants are the previous values
+        */
+       int   mval[2], mval_prev[2];
+       float pressure, pressure_prev;
+       
+       /* - effect vector (e.g. 2D/3D translation for grab brush) */
+       float dvec[3];
+       
+       /* brush geometry (bounding box) */
+       rcti brush_rect;
+       
+       /* Custom data for certain brushes */
+       /* - map from bGPDstroke's to structs containing custom data about those strokes */
+       GHash *stroke_customdata;
+       /* - general customdata */
+       void *customdata;
+       
+       
+       /* Timer for in-place accumulation of brush effect */
+       wmTimer *timer;
+       bool timerTick; /* is this event from a timer */
+} tGP_BrushEditData;
+
+
+/* Callback for performing some brush operation on a single point */
+typedef bool (*GP_BrushApplyCb)(tGP_BrushEditData *gso, bGPDstroke *gps, int i,
+                                const int radius, const int co[2]);
+
+/* ************************************************ */
+/* Utility Functions */
+
+/* Context ---------------------------------------- */
+
+/* Get the sculpting settings */
+static GP_BrushEdit_Settings *gpsculpt_get_settings(Scene *scene)
+{
+       return &scene->toolsettings->gp_sculpt;
+}
+
+/* Get the active brush */
+static GP_EditBrush_Data *gpsculpt_get_brush(Scene *scene)
+{
+       GP_BrushEdit_Settings *gset = &scene->toolsettings->gp_sculpt;
+       return &gset->brush[gset->brushtype];
+}
+
+/* Brush Operations ------------------------------- */
+
+/* Invert behaviour of brush? */
+static bool gp_brush_invert_check(tGP_BrushEditData *gso)
+{
+       /* The basic setting is the brush's setting (from the panel) */
+       bool invert = ((gso->brush->flag & GP_EDITBRUSH_FLAG_INVERT) != 0);
+       
+       /* During runtime, the user can hold down the Ctrl key to invert the basic behaviour */
+       if (gso->flag & GP_EDITBRUSH_FLAG_INVERT) {
+               invert ^= true;
+       }
+               
+       return invert;
+}
+
+/* Compute strength of effect */
+static float gp_brush_influence_calc(tGP_BrushEditData *gso, const int radius, const int co[2])
+{
+       GP_EditBrush_Data *brush = gso->brush;
+       
+       /* basic strength factor from brush settings */
+       float influence = brush->strength;
+       
+       /* use pressure? */
+       if (brush->flag & GP_EDITBRUSH_FLAG_USE_PRESSURE) {
+               influence *= gso->pressure;
+       }
+       
+       /* distance fading */
+       if (brush->flag & GP_EDITBRUSH_FLAG_USE_FALLOFF) {
+               float distance = (float)len_v2v2_int(gso->mval, co);
+               float fac;
+               
+               CLAMP(distance, 0.0f, (float)radius);
+               fac = 1.0f - (distance / (float)radius);
+               
+               influence *= fac;
+       }
+       
+       /* return influence */
+       return influence;
+}
+
+/* ************************************************ */
+/* Brush Callbacks */
+/* This section defines the callbacks used by each brush to perform their magic.
+ * These are called on each point within the brush's radius.
+ */
+
+/* ----------------------------------------------- */
+/* Smooth Brush */
+
+/* A simple (but slower + inaccurate) smooth-brush implementation to test the algorithm for stroke smoothing */
+static bool gp_brush_smooth_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i,
+                                  const int radius, const int co[2])
+{
+       GP_EditBrush_Data *brush = gso->brush;
+       bGPDspoint *pt = &gps->points[i];
+       float inf = gp_brush_influence_calc(gso, radius, co);
+       float pressure = 0.0f;
+       float sco[3] = {0.0f};
+       
+       /* Do nothing if not enough points to smooth out */
+       if (gps->totpoints <= 2) {
+               return false;
+       }
+       
+       /* Only affect endpoints by a fraction of the normal strength,
+        * to prevent the stroke from shrinking too much
+        */
+       if ((i == 0) || (i == gps->totpoints - 1)) {
+               inf *= 0.1f;
+       }
+       
+       /* Compute smoothed coordinate by taking the ones nearby */
+       /* XXX: This is potentially slow, and suffers from accumulation error as earlier points are handled before later ones */
+       {       
+               // XXX: this is hardcoded to look at 2 points on either side of the current one (i.e. 5 items total)
+               const int   steps = 2;
+               const float average_fac = 1.0f / (float)(steps * 2 + 1);
+               int step;
+               
+               /* add the point itself */
+               madd_v3_v3fl(sco, &pt->x, average_fac);
+               
+               if (brush->flag & GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE) {
+                       pressure += pt->pressure * average_fac;
+               }
+               
+               /* n-steps before/after current point */
+               // XXX: review how the endpoints are treated by this algorithm
+               // XXX: falloff measures should also introduce some weighting variations, so that further-out points get less weight
+               for (step = 1; step <= steps; step++) {
+                       bGPDspoint *pt1, *pt2;
+                       int before = i - step;
+                       int after  = i + step;
+                       
+                       CLAMP_MIN(before, 0);
+                       CLAMP_MAX(after, gps->totpoints - 1);
+                       
+                       pt1 = &gps->points[before];
+                       pt2 = &gps->points[after];
+                       
+                       /* add both these points to the average-sum (s += p[i]/n) */
+                       madd_v3_v3fl(sco, &pt1->x, average_fac);
+                       madd_v3_v3fl(sco, &pt2->x, average_fac);
+                       
+                       /* do pressure too? */
+                       if (brush->flag & GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE) {
+                               pressure += pt1->pressure * average_fac;
+                               pressure += pt2->pressure * average_fac;
+                       }
+               }
+       }
+       
+       /* Based on influence factor, blend between original and optimal smoothed coordinate */
+       interp_v3_v3v3(&pt->x, &pt->x, sco, inf);
+       
+       if (brush->flag & GP_EDITBRUSH_FLAG_SMOOTH_PRESSURE) {
+               pt->pressure = pressure;
+       }
+       
+       return true;
+}
+
+/* ----------------------------------------------- */
+/* Line Thickness Brush */
+
+/* Make lines thicker or thinner by the specified amounts */
+static bool gp_brush_thickness_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i,
+                                     const int radius, const int co[2])
+{
+       bGPDspoint *pt = gps->points + i;
+       float inf;
+       
+       /* Compute strength of effect
+        * - We divide the strength by 10, so that users can set "sane" values.
+        *   Otherwise, good default values are in the range of 0.093
+        */
+       inf = gp_brush_influence_calc(gso, radius, co) / 10.0f;
+       
+       /* apply */
+       // XXX: this is much too strong, and it should probably do some smoothing with the surrounding stuff
+       if (gp_brush_invert_check(gso)) {
+               /* make line thinner - reduce stroke pressure */
+               pt->pressure -= inf;
+       }
+       else {
+               /* make line thicker - increase stroke pressure */
+               pt->pressure += inf;
+       }
+       
+       /* Pressure should stay within [0.0, 1.0]
+        * However, it is nice for volumetric strokes to be able to exceed
+        * the upper end of this range. Therefore, we don't actually clamp
+        * down on the upper end.
+        */
+       if (pt->pressure < 0.0f)
+               pt->pressure = 0.0f;
+       
+       return true;
+}
+
+
+/* ----------------------------------------------- */
+/* Grab Brush */
+
+/* Custom data per stroke for the Grab Brush
+ *
+ * This basically defines the strength of the effect for each
+ * affected stroke point that was within the initial range of
+ * the brush region.
+ */
+typedef struct tGPSB_Grab_StrokeData {
+       /* array of indices to corresponding points in the stroke */
+       int   *points;
+       /* array of influence weights for each of the included points */
+       float *weights;
+       
+       /* capacity of the arrays */
+       int capacity;
+       /* actual number of items currently stored */
+       int size;
+} tGPSB_Grab_StrokeData;
+
+/* initialise custom data for handling this stroke */
+static void gp_brush_grab_stroke_init(tGP_BrushEditData *gso, bGPDstroke *gps)
+{
+       tGPSB_Grab_StrokeData *data = NULL;
+       
+       BLI_assert(gps->totpoints > 0);
+       
+       /* Check if there are buffers already (from a prior run) */
+       if (BLI_ghash_haskey(gso->stroke_customdata, gps)) {
+               /* Ensure that the caches are empty
+                * - Since we reuse these between different strokes, we don't
+                *   want the previous invocation's data polluting the arrays
+                */
+               data = BLI_ghash_lookup(gso->stroke_customdata, gps);
+               BLI_assert(data != NULL);
+               
+               data->size = 0; /* minimum requirement - so that we can repopulate again */
+               
+               memset(data->points, 0, sizeof(int) * data->capacity);
+               memset(data->weights, 0, sizeof(float) * data->capacity);
+       }
+       else {
+               /* Create new instance */
+               data = MEM_callocN(sizeof(tGPSB_Grab_StrokeData), "GP Stroke Grab Data");
+               
+               data->capacity = gps->totpoints;
+               data->size = 0;
+               
+               data->points  = MEM_callocN(sizeof(int) * data->capacity, "GP Stroke Grab Indices");
+               data->weights = MEM_callocN(sizeof(float) * data->capacity, "GP Stroke Grab Weights");
+               
+               /* hook up to the cache */
+               BLI_ghash_insert(gso->stroke_customdata, gps, data);
+       }       
+}
+
+/* store references to stroke points in the initial stage */
+static bool gp_brush_grab_store_points(tGP_BrushEditData *gso, bGPDstroke *gps, int i,
+                                       const int radius, const int co[2])
+{
+       tGPSB_Grab_StrokeData *data = BLI_ghash_lookup(gso->stroke_customdata, gps);
+       float inf = gp_brush_influence_calc(gso, radius, co);
+       
+       BLI_assert(data != NULL);
+       BLI_assert(data->size < data->capacity);
+       
+       /* insert this point into the set of affected points */
+       data->points[data->size]  = i;
+       data->weights[data->size] = inf;
+       data->size++;
+       
+       /* done */
+       return true;
+}
+
+/* Compute effect vector for grab brush */
+static void gp_brush_grab_calc_dvec(tGP_BrushEditData *gso)
+{
+       /* Convert mouse-movements to movement vector */
+       // TODO: incorporate pressure into this?
+       // XXX: screen-space strokes in 3D space will suffer!
+       if (gso->sa->spacetype == SPACE_VIEW3D) {
+               View3D *v3d = gso->sa->spacedata.first;
+               RegionView3D *rv3d = gso->ar->regiondata;
+               float *rvec = ED_view3d_cursor3d_get(gso->scene, v3d);
+               float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL);
+               
+               float mval_f[2];
+               
+               /* convert from 2D screenspace to 3D... */
+               mval_f[0] = (float)(gso->mval[0] - gso->mval_prev[0]);
+               mval_f[1] = (float)(gso->mval[1] - gso->mval_prev[1]);
+               
+               ED_view3d_win_to_delta(gso->ar, mval_f, gso->dvec, zfac);
+       }
+       else {
+               /* 2D - just copy */
+               // XXX: view2d?
+               gso->dvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]);
+               gso->dvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]);
+               gso->dvec[2] = 0.0f;  /* unused */
+       }
+}
+
+/* Apply grab transform to all relevant points of the affected strokes */
+static void gp_brush_grab_apply_cached(tGP_BrushEditData *gso, bGPDstroke *gps)
+{
+       tGPSB_Grab_StrokeData *data = BLI_ghash_lookup(gso->stroke_customdata, gps);
+       int i;
+       
+       /* Apply dvec to all of the stored points */
+       for (i = 0; i < data->size; i++) {
+               bGPDspoint *pt = &gps->points[data->points[i]];
+               float delta[3] = {0.0f};
+               
+               /* adjust the amount of displacement to apply */
+               mul_v3_v3fl(delta, gso->dvec, data->weights[i]);
+               
+               /* apply */
+               add_v3_v3(&pt->x, delta);
+       }
+}
+
+/* free customdata used for handling this stroke */
+static void gp_brush_grab_stroke_free(void *ptr)
+{
+       tGPSB_Grab_StrokeData *data = (tGPSB_Grab_StrokeData *)ptr;
+       
+       /* free arrays */
+       MEM_freeN(data->points);
+       MEM_freeN(data->weights);
+       
+       /* ... and this item itself, since it was also allocated */
+       MEM_freeN(data);
+}
+
+/* ----------------------------------------------- */
+/* Push Brush */
+/* NOTE: Depends on gp_brush_grab_calc_dvec() */
+
+static bool gp_brush_push_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i,
+                                const int radius, const int co[2])
+{
+       bGPDspoint *pt = gps->points + i;
+       float inf = gp_brush_influence_calc(gso, radius, co);
+       float delta[3] = {0.0f};
+               
+       /* adjust the amount of displacement to apply */
+       mul_v3_v3fl(delta, gso->dvec, inf);
+       
+       /* apply */
+       add_v3_v3(&pt->x, delta);
+
+       /* done */
+       return true;
+}
+
+/* ----------------------------------------------- */
+/* Pinch Brush */
+
+/* Compute reference midpoint for the brush - this is what we'll be moving towards */
+static void gp_brush_calc_midpoint(tGP_BrushEditData *gso)
+{
+       if (gso->sa->spacetype == SPACE_VIEW3D) {
+               /* Convert mouse position to 3D space
+                * See: gpencil_paint.c :: gp_stroke_convertcoords()
+                */
+               View3D *v3d = gso->sa->spacedata.first;
+               RegionView3D *rv3d = gso->ar->regiondata;
+               float *rvec = ED_view3d_cursor3d_get(gso->scene, v3d);
+               float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL);
+               
+               float mval_f[2] = {UNPACK2(gso->mval)};
+               float mval_prj[2];
+               float dvec[3];
+               
+               
+               if (ED_view3d_project_float_global(gso->ar, rvec, mval_prj, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
+                       sub_v2_v2v2(mval_f, mval_prj, mval_f);
+                       ED_view3d_win_to_delta(gso->ar, mval_f, dvec, zfac);
+                       sub_v3_v3v3(gso->dvec, rvec, dvec);
+               }
+               else {
+                       zero_v3(gso->dvec);
+               }
+       }
+       else {
+               /* Just 2D coordinates */
+               // XXX: fix View2D offsets later
+               gso->dvec[0] = (float)gso->mval[0];
+               gso->dvec[1] = (float)gso->mval[1];
+               gso->dvec[2] = 0.0f;
+       }
+}
+
+/* Shrink distance between midpoint and this point... */
+static bool gp_brush_pinch_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i,
+                                 const int radius, const int co[2])
+{
+       bGPDspoint *pt = gps->points + i;
+       float fac, inf;
+       float vec[3];
+       
+       /* Scale down standard influence value to get it more manageable...
+        *  - No damping = Unmanageable at > 0.5 strength
+        *  - Div 10     = Not enough effect
+        *  - Div 5      = Happy medium... (by trial and error)
+        */
+       inf = gp_brush_influence_calc(gso, radius, co) / 5.0f;
+       
+       /* 1) Make this point relative to the cursor/midpoint (dvec) */
+       sub_v3_v3v3(vec, &pt->x, gso->dvec);
+       
+       /* 2) Shrink the distance by pulling the point towards the midpoint
+        *    (0.0 = at midpoint, 1 = at edge of brush region)
+        *                         OR
+        *    Increase the distance (if inverting the brush action!)
+        */
+       if (gp_brush_invert_check(gso)) {
+               /* Inflate (inverse) */
+               fac = 1.0f + (inf * inf); /* squared to temper the effect... */
+       }
+       else {
+               /* Shrink (default) */
+               fac = 1.0f - (inf * inf); /* squared to temper the effect... */
+       }
+       mul_v3_fl(vec, fac);
+       
+       /* 3) Translate back to original space, with the shrinkage applied */
+       add_v3_v3v3(&pt->x, gso->dvec, vec);
+       
+       /* done */
+       return true;
+}
+
+/* ----------------------------------------------- */
+/* Twist Brush - Rotate Around midpoint */
+
+/* Take the screenspace coordinates of the point, rotate this around the brush midpoint,
+ * convert the rotated point and convert it into "data" space
+ */
+
+static bool gp_brush_twist_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i,
+                                 const int radius, const int co[2])
+{
+       bGPDspoint *pt = gps->points + i;
+       float tco[2], rco[2], nco[2];
+       float rmat[2][2];
+       float angle, inf;
+       
+       /* Angle to rotate by */
+       inf = gp_brush_influence_calc(gso, radius, co);
+       angle = DEG2RADF(1.0f) * inf;
+       
+       if (gp_brush_invert_check(gso)) {
+               /* invert angle that we rotate by */
+               angle *= -1;
+       }
+       
+       /* Express position of point relative to cursor, ready to rotate */
+       tco[0] = (float)(co[0] - gso->mval[0]);
+       tco[1] = (float)(co[1] - gso->mval[1]);
+       
+       /* Rotate point in 2D */
+       angle_to_mat2(rmat, angle);
+       mul_v2_m2v2(rco, rmat, tco);
+       
+       /* Convert back to screen-coordinates */
+       nco[0] = rco[0] + (float)gso->mval[0];
+       nco[1] = rco[1] + (float)gso->mval[1];
+       
+#if 0
+       printf("C: %d %d | P: %d %d -> t: %f %f -> r: %f %f x %f -> %f %f\n",
+                       gso->mval[0], gso->mval[1], co[0], co[1],
+                       tco[0], tco[1],
+                       rco[0], rco[1], angle,
+                       nco[0], nco[1]);
+#endif
+       
+       /* convert to dataspace */
+       if (gps->flag & GP_STROKE_3DSPACE) {
+               /* 3D: Project to 3D space */
+               if (gso->sa->spacetype == SPACE_VIEW3D) {
+                       // XXX: this conversion process sometimes introduces noise to the data -> some parts don't seem to move at all, while others get random offsets
+                       gp_point_xy_to_3d(&gso->gsc, gso->scene, nco, &pt->x);
+               }
+               else {
+                       /* ERROR */
+                       BLI_assert("3D stroke being sculpted in non-3D view");
+               }
+       }
+       else {
+               /* 2D: As-is */
+               // XXX: v2d scaling/offset?
+               copy_v2_v2(&pt->x, nco);
+       }
+       
+       /* done */
+       return true;
+}
+
+
+/* ----------------------------------------------- */
+/* Randomise Brush */
+
+/* Apply some random jitter to the point */
+static bool gp_brush_randomise_apply(tGP_BrushEditData *gso, bGPDstroke *gps, int i,
+                                     const int radius, const int co[2])
+{
+       bGPDspoint *pt = gps->points + i;
+       
+       /* Amount of jitter to apply depends on the distance of the point to the cursor,
+        * as well as the strength of the brush
+        */
+       const float inf = gp_brush_influence_calc(gso, radius, co) / 2.0f;
+       const float fac = BLI_frand() * inf;
+       
+       /* Jitter is applied perpendicular to the mouse movement vector
+        * - We compute all effects in screenspace (since it's easier)
+        *   and then project these to get the points/distances in
+        *   viewspace as needed
+        */
+       float mvec[2], svec[2], nco[2];
+       
+       /* mouse movement in ints -> floats */
+       mvec[0] = (float)(gso->mval[0] - gso->mval_prev[0]);
+       mvec[1] = (float)(gso->mval[1] - gso->mval_prev[1]);
+       
+       /* rotate mvec by 90 degrees... */
+       svec[0] = -mvec[1];
+       svec[1] =  mvec[0];
+       
+       //printf("svec = %f %f, ", svec[0], svec[1]);
+       
+       /* scale the displacement by the random displacement, and apply */
+       if (BLI_frand() > 0.5f) {
+               mul_v2_fl(svec, -fac);
+       }
+       else {
+               mul_v2_fl(svec, fac);
+       }
+       
+       nco[0] = (float)co[0] + svec[0];
+       nco[1] = (float)co[1] + svec[1];
+       
+       //printf("%f %f (%f), nco = {%f %f}, co = %d %d\n", svec[0], svec[1], fac, nco[0], nco[1], co[0], co[1]);
+       
+       /* convert to dataspace */
+       if (gps->flag & GP_STROKE_3DSPACE) {
+               /* 3D: Project to 3D space */
+               if (gso->sa->spacetype == SPACE_VIEW3D) {
+                       gp_point_xy_to_3d(&gso->gsc, gso->scene, nco, &pt->x);
+               }
+               else {
+                       /* ERROR */
+                       BLI_assert("3D stroke being sculpted in non-3D view");
+               }
+       }
+       else {
+               /* 2D: As-is */
+               // XXX: v2d scaling/offset?
+               copy_v2_v2(&pt->x, nco);
+       }
+       
+       /* done */
+       return true;
+}
+
+/* ************************************************ */
+/* Non Callback-Based Brushes */
+
+/* Clone Brush ------------------------------------- */
+/* How this brush currently works:
+ * - If this is start of the brush stroke, paste immediately under the cursor
+ *   by placing the midpoint of the buffer strokes under the cursor now
+ *
+ * - Otherwise, in:
+ *   "Stamp Mode" - Move the newly pasted strokes so that their center
+ *   "Continuous" - Repeatedly just paste new copies for where the brush is now
+ */
+
+/* Custom state data for clone brush */
+typedef struct tGPSB_CloneBrushData {
+       /* midpoint of the strokes on the clipboard */
+       float buffer_midpoint[3];
+       
+       /* number of strokes in the paste buffer (and/or to be created each time) */
+       size_t totitems;
+       
+       /* for "stamp" mode, the currently pasted brushes */
+       bGPDstroke **new_strokes;
+} tGPSB_CloneBrushData;
+
+/* Initialise "clone" brush data */
+static void gp_brush_clone_init(bContext *C, tGP_BrushEditData *gso)
+{
+       tGPSB_CloneBrushData *data;
+       bGPDstroke *gps;
+       
+       /* init custom data */
+       gso->customdata = data = MEM_callocN(sizeof(tGPSB_CloneBrushData), "CloneBrushData");
+       
+       /* compute midpoint of strokes on clipboard */
+       for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
+               if (ED_gpencil_stroke_can_use(C, gps)) {
+                       const float dfac = 1.0f / ((float)gps->totpoints);
+                       float mid[3] = {0.0f};
+                       
+                       bGPDspoint *pt;
+                       int i;
+                       
+                       /* compute midpoint of this stroke */
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               float co[3];
+                               
+                               mul_v3_v3fl(co, &pt->x, dfac);
+                               add_v3_v3(mid, co);
+                       }
+                       
+                       /* combine this stroke's data with the main data */
+                       add_v3_v3(data->buffer_midpoint, mid);
+                       data->totitems++;
+               }
+       }
+       
+       /* Divide the midpoint by the number of strokes, to finish averaging it */
+       if (data->totitems > 1) {
+               mul_v3_fl(data->buffer_midpoint, 1.0f / (float)data->totitems);
+       }
+       
+       /* Create a buffer for storing the current strokes */
+       if (1 /*gso->brush->mode == GP_EDITBRUSH_CLONE_MODE_STAMP*/) {
+               data->new_strokes = MEM_callocN(sizeof(bGPDstroke *) * data->totitems, "cloned strokes ptr array");
+       }
+}
+
+/* Free custom data used for "clone" brush */
+static void gp_brush_clone_free(tGP_BrushEditData *gso)
+{
+       tGPSB_CloneBrushData *data = gso->customdata;
+       
+       /* free strokes array */
+       if (data->new_strokes) {
+               MEM_freeN(data->new_strokes);
+               data->new_strokes = NULL;
+       }
+       
+       /* free the customdata itself */
+       MEM_freeN(data);
+       gso->customdata = NULL;
+}
+
+/* Create new copies of the strokes on the clipboard */
+static void gp_brush_clone_add(bContext *C, tGP_BrushEditData *gso)
+{
+       tGPSB_CloneBrushData *data = gso->customdata;
+       
+       Scene *scene = gso->scene;
+       bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
+       bGPDframe *gpf = gpencil_layer_getframe(gpl, CFRA, true);
+       bGPDstroke *gps;
+       
+       float delta[3];
+       size_t strokes_added = 0;
+       
+       /* Compute amount to offset the points by */
+       /* NOTE: This assumes that screenspace strokes are NOT used in the 3D view... */
+       
+       gp_brush_calc_midpoint(gso); /* this puts the cursor location into gso->dvec */
+       sub_v3_v3v3(delta, gso->dvec, data->buffer_midpoint);
+       
+       /* Copy each stroke into the layer */
+       for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
+               if (ED_gpencil_stroke_can_use(C, gps)) {
+                       bGPDstroke *new_stroke;
+                       bGPDspoint *pt;
+                       int i;
+                       
+                       /* Make a new stroke */
+                       new_stroke = MEM_dupallocN(gps);
+                       
+                       new_stroke->points = MEM_dupallocN(gps->points);
+                       new_stroke->next = new_stroke->prev = NULL;
+                       
+                       BLI_addtail(&gpf->strokes, new_stroke);
+                       
+                       /* Adjust all the stroke's points, so that the strokes
+                        * get pasted relative to where the cursor is now
+                        */
+                       for (i = 0, pt = new_stroke->points; i < new_stroke->totpoints; i++, pt++) {
+                               /* assume that the delta can just be applied, and then everything works */
+                               add_v3_v3(&pt->x, delta);
+                       }
+                       
+                       /* Store ref for later */
+                       if ((data->new_strokes) && (strokes_added < data->totitems)) {
+                               data->new_strokes[strokes_added] = new_stroke;
+                               strokes_added++;
+                       }
+               }
+       }
+}
+
+/* Move newly-added strokes around - "Stamp" mode of the Clone brush */
+static void gp_brush_clone_adjust(bContext *C, tGP_BrushEditData *gso)
+{
+       tGPSB_CloneBrushData *data = gso->customdata;
+       size_t snum;
+       
+       /* Compute the amount of movement to apply (overwrites dvec) */
+       gp_brush_grab_calc_dvec(gso);
+       
+       /* For each of the stored strokes, apply the offset to each point */
+       /* NOTE: Again this assumes that in the 3D view, we only have 3d space and not screenspace strokes... */
+       for (snum = 0; snum < data->totitems; snum++) {
+               bGPDstroke *gps = data->new_strokes[snum];
+               bGPDspoint *pt;
+               int i;
+               
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       if (gso->brush->flag & GP_EDITBRUSH_FLAG_USE_FALLOFF) {
+                               /* "Smudge" Effect when falloff is enabled */
+                               float delta[3] = {0.0f};
+                               int sco[2] = {0};
+                               float influence;
+                               
+                               /* compute influence on point */
+                               gp_point_to_xy(&gso->gsc, gps, pt, &sco[0], &sco[1]);
+                               influence = gp_brush_influence_calc(gso, gso->brush->size, sco);
+                               
+                               /* adjust the amount of displacement to apply */
+                               mul_v3_v3fl(delta, gso->dvec, influence);
+                               
+                               /* apply */
+                               add_v3_v3(&pt->x, delta);
+                       }
+                       else {
+                               /* Just apply the offset - All points move perfectly in sync with the cursor */
+                               add_v3_v3(&pt->x, gso->dvec);
+                       }
+               }
+       }
+}
+
+/* Entrypoint for applying "clone" brush */
+static bool gpsculpt_brush_apply_clone(bContext *C, tGP_BrushEditData *gso)
+{
+       /* Which "mode" are we operating in? */
+       if (gso->first) {
+               /* Create initial clones */
+               gp_brush_clone_add(C, gso);
+       }
+       else {
+               /* Stamp or Continous Mode */
+               if (1 /*gso->brush->mode == GP_EDITBRUSH_CLONE_MODE_STAMP*/) {
+                       /* Stamp - Proceed to translate the newly added strokes */
+                       gp_brush_clone_adjust(C, gso);
+               }
+               else {
+                       /* Continuous - Just keep pasting everytime we move */
+                       /* TODO: The spacing of repeat should be controlled using a "stepsize" or similar property? */
+                       gp_brush_clone_add(C, gso);
+               }
+       }
+       
+       return true;
+}
+
+/* ************************************************ */
+/* Cursor drawing */
+
+/* Helper callback for drawing the cursor itself */
+static void gp_brush_drawcursor(bContext *C, int x, int y, void *UNUSED(customdata))
+{
+       GP_EditBrush_Data *brush = gpsculpt_get_brush(CTX_data_scene(C));
+       
+       if (brush) {
+               glPushMatrix();
+               
+               glTranslatef((float)x, (float)y, 0.0f);
+               
+               /* TODO: toggle between add and remove? */
+               glColor4ub(255, 255, 255, 128);
+               
+               glEnable(GL_LINE_SMOOTH);
+               glEnable(GL_BLEND);
+               
+               glutil_draw_lined_arc(0.0, M_PI * 2.0, brush->size, 40);
+               
+               glDisable(GL_BLEND);
+               glDisable(GL_LINE_SMOOTH);
+               
+               glPopMatrix();
+       }
+}
+
+/* Turn brush cursor in on/off */
+static void gpencil_toggle_brush_cursor(bContext *C, bool enable)
+{
+       GP_BrushEdit_Settings *gset = gpsculpt_get_settings(CTX_data_scene(C));
+       
+       if (gset->paintcursor && !enable) {
+               /* clear cursor */
+               WM_paint_cursor_end(CTX_wm_manager(C), gset->paintcursor);
+               gset->paintcursor = NULL;
+       }
+       else if (enable) {
+               /* enable cursor */
+               gset->paintcursor = WM_paint_cursor_activate(CTX_wm_manager(C), 
+                                                            NULL, 
+                                                            gp_brush_drawcursor, NULL);
+       }
+}
+
+
+/* ************************************************ */
+/* Header Info for GPencil Sculpt */
+
+static void gpsculpt_brush_header_set(bContext *C, tGP_BrushEditData *gso)
+{
+       const char *brush_name = NULL;
+       char str[256] = "";
+       
+       RNA_enum_name(rna_enum_gpencil_sculpt_brush_items, gso->brush_type, &brush_name);
+       
+       BLI_snprintf(str, sizeof(str),
+                    IFACE_("GPencil Sculpt: %s Stroke  | LMB to paint | RMB/Escape to Exit"
+                           " | Ctrl to Invert Action | Wheel Up/Down for Size "
+                                               " | Shift-Wheel Up/Down for Strength"),
+                    (brush_name) ? brush_name : "<?>");
+       
+       ED_area_headerprint(CTX_wm_area(C), str);
+}
+
+/* ************************************************ */
+/* Grease Pencil Sculpting Operator */
+
+/* Init/Exit ----------------------------------------------- */
+
+static bool gpsculpt_brush_init(bContext *C, wmOperator *op)
+{
+       Scene *scene = CTX_data_scene(C);
+       tGP_BrushEditData *gso;
+       
+       /* setup operator data */
+       gso = MEM_callocN(sizeof(tGP_BrushEditData), "tGP_BrushEditData");
+       op->customdata = gso;
+       
+       /* store state */
+       gso->settings = gpsculpt_get_settings(scene);
+       gso->brush = gpsculpt_get_brush(scene);
+       
+       gso->brush_type = gso->settings->brushtype;
+       
+       
+       gso->is_painting = false;
+       gso->first = true;
+       
+       gso->gpd = ED_gpencil_data_get_active(C);
+       gso->cfra = INT_MAX; /* NOTE: So that first stroke will get handled in init_stroke() */
+       
+       gso->scene = scene;
+       
+       gso->sa = CTX_wm_area(C);
+       gso->ar = CTX_wm_region(C);
+       
+       /* initialise custom data for brushes */
+       switch (gso->brush_type) {
+               case GP_EDITBRUSH_TYPE_CLONE:
+               {
+                       bGPDstroke *gps;
+                       bool found = false;
+                       
+                       /* check that there are some usable strokes in the buffer */
+                       for (gps = gp_strokes_copypastebuf.first; gps; gps = gps->next) {
+                               if (ED_gpencil_stroke_can_use(C, gps)) {
+                                       found = true;
+                                       break;
+                               }
+                       }
+                       
+                       if (found == false) {
+                               /* STOP HERE! Nothing to paste! */
+                               BKE_report(op->reports, RPT_ERROR, 
+                                          "Copy some strokes to the clipboard before using the Clone brush to paste copies of them");
+                                          
+                               MEM_freeN(gso);
+                               op->customdata = NULL;
+                               return false;
+                       }
+                       else {
+                               /* initialise customdata */
+                               gp_brush_clone_init(C, gso);
+                       }
+                       break;
+               }
+               
+               case GP_EDITBRUSH_TYPE_GRAB:
+               {
+                       /* initialise the cache needed for this brush */
+                       gso->stroke_customdata = BLI_ghash_ptr_new("GP Grab Brush - Strokes Hash");
+                       break;
+               }
+                       
+               /* Others - No customdata needed */
+               default:
+                       break;
+       }
+       
+       
+       /* setup space conversions */
+       gp_point_conversion_init(C, &gso->gsc);
+       
+       /* update header */
+       gpsculpt_brush_header_set(C, gso);
+       
+       /* setup cursor drawing */
+       WM_cursor_modal_set(CTX_wm_window(C), BC_CROSSCURSOR);
+       gpencil_toggle_brush_cursor(C, true);
+       
+       return true;
+}
+
+static void gpsculpt_brush_exit(bContext *C, wmOperator *op)
+{
+       tGP_BrushEditData *gso = op->customdata;
+       wmWindow *win = CTX_wm_window(C);
+       
+       /* free brush-specific data */
+       switch (gso->brush_type) {
+               case GP_EDITBRUSH_TYPE_GRAB:
+               {
+                       /* Free per-stroke customdata
+                        * - Keys don't need to be freed, as those are the strokes
+                        * - Values assigned to those keys do, as they are custom structs
+                        */
+                       BLI_ghash_free(gso->stroke_customdata, NULL, gp_brush_grab_stroke_free);
+                       break;
+               }
+               
+               case GP_EDITBRUSH_TYPE_CLONE:
+               {
+                       /* Free customdata */
+                       gp_brush_clone_free(gso);
+                       break;
+               }
+               
+               default:
+                       break;
+       }
+       
+       /* unregister timer (only used for realtime) */
+       if (gso->timer) {
+               WM_event_remove_timer(CTX_wm_manager(C), win, gso->timer);
+       }
+
+       /* disable cursor and headerprints */
+       ED_area_headerprint(CTX_wm_area(C), NULL);
+       WM_cursor_modal_restore(win);
+       gpencil_toggle_brush_cursor(C, false);
+       
+       /* free operator data */
+       MEM_freeN(gso);
+       op->customdata = NULL;
+}
+
+/* poll callback for stroke sculpting operator(s) */
+static int gpsculpt_brush_poll(bContext *C)
+{
+       /* NOTE: this is a bit slower, but is the most accurate... */
+       return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
+}
+
+/* Init Sculpt Stroke ---------------------------------- */
+
+static void gpsculpt_brush_init_stroke(tGP_BrushEditData *gso)
+{
+       Scene *scene = gso->scene;
+       bGPdata *gpd = gso->gpd;
+       bGPDlayer *gpl;
+       int cfra = CFRA;
+       
+       /* only try to add a new frame if this is the first stroke, or the frame has changed */
+       if ((gpd == NULL) || (cfra == gso->cfra))
+               return;
+       
+       /* go through each layer, and ensure that we've got a valid frame to use */
+       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+               /* only editable and visible layers are considered */
+               if ((gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) == 0 &&
+                       (gpl->actframe != NULL))
+               {
+                       bGPDframe *gpf = gpl->actframe;
+                       
+                       /* Make a new frame to work on if the layer's frame and the current scene frame don't match up 
+                        * - This is useful when animating as it saves that "uh-oh" moment when you realize you've
+                        *   spent too much time editing the wrong frame...
+                        */
+                       // XXX: should this be allowed when framelock is enabled?
+                       if (gpf->framenum != cfra) {
+                               gpencil_frame_addcopy(gpl, cfra);
+                       }
+               }
+       }
+       
+       /* save off new current frame, so that next update works fine */
+       gso->cfra = cfra;
+}
+
+/* Apply ----------------------------------------------- */
+
+/* Apply brush operation to points in this stroke */
+static bool gpsculpt_brush_do_stroke(tGP_BrushEditData *gso, bGPDstroke *gps, GP_BrushApplyCb apply)
+{
+       GP_SpaceConversion *gsc = &gso->gsc;
+       rcti *rect = &gso->brush_rect;
+       const int radius = gso->brush->size;
+       
+       bGPDspoint *pt1, *pt2;
+       int pc1[2] = {0};
+       int pc2[2] = {0};
+       int i;
+       bool include_last = false;
+       bool changed = false;
+       
+       if (gps->totpoints == 1) {
+               gp_point_to_xy(gsc, gps, gps->points, &pc1[0], &pc1[1]);
+               
+               /* do boundbox check first */
+               if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) {
+                       /* only check if point is inside */
+                       if (len_v2v2_int(gso->mval, pc1) <= radius) {
+                               /* apply operation to this point */
+                               changed = apply(gso, gps, 0, radius, pc1);
+                       }
+               }
+       }
+       else {
+               /* Loop over the points in the stroke, checking for intersections 
+                *  - an intersection means that we touched the stroke
+                */
+               for (i = 0; (i + 1) < gps->totpoints; i++) {
+                       /* Get points to work with */
+                       pt1 = gps->points + i;
+                       pt2 = gps->points + i + 1;
+                       
+                       /* Skip if neither one is selected (and we are only allowed to edit/consider selected points) */
+                       if (gso->settings->flag & GP_BRUSHEDIT_FLAG_SELECT_MASK) {
+                               if (!(pt1->flag & GP_SPOINT_SELECT) && !(pt2->flag & GP_SPOINT_SELECT)) {
+                                       include_last = false;
+                                       continue;
+                               }
+                       }
+                       
+                       gp_point_to_xy(gsc, gps, pt1, &pc1[0], &pc1[1]);
+                       gp_point_to_xy(gsc, gps, pt2, &pc2[0], &pc2[1]);
+                       
+                       /* Check that point segment of the boundbox of the selection stroke */
+                       if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) ||
+                           ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1])))
+                       {
+                               /* Check if point segment of stroke had anything to do with
+                                * eraser region  (either within stroke painted, or on its lines)
+                                *  - this assumes that linewidth is irrelevant
+                                */
+                               if (gp_stroke_inside_circle(gso->mval, gso->mval_prev, radius, pc1[0], pc1[1], pc2[0], pc2[1])) {
+                                       /* Apply operation to these points */
+                                       bool ok = false;
+                                       
+                                       /* To each point individually... */
+                                       ok = apply(gso, gps, i, radius, pc1);
+                                       
+                                       /* Only do the second point if this is the last segment,
+                                        * and it is unlikely that the point will get handled
+                                        * otherwise. 
+                                        * 
+                                        * NOTE: There is a small risk here that the second point wasn't really
+                                        *       actually in-range. In that case, it only got in because
+                                        *       the line linking the points was!
+                                        */
+                                       if (i + 1 == gps->totpoints - 1) {
+                                               ok |= apply(gso, gps, i + 1, radius, pc2);
+                                               include_last = false;
+                                       }
+                                       else {
+                                               include_last = true;
+                                       }
+                                       
+                                       changed |= ok;
+                               }
+                               else if (include_last) {
+                                       /* This case is for cases where for whatever reason the second vert (1st here) doesn't get included
+                                        * because the whole edge isn't in bounds, but it would've qualified since it did with the
+                                        * previous step (but wasn't added then, to avoid double-ups) 
+                                        */
+                                       changed |= apply(gso, gps, i, radius, pc1);
+                                       include_last = false;
+                               }
+                       }
+               }
+       }
+       
+       return changed;
+}
+
+/* Perform two-pass brushes which modify the existing strokes */
+static bool gpsculpt_brush_apply_standard(bContext *C, tGP_BrushEditData *gso)
+{
+       bool changed = false;
+       
+       /* Calculate brush-specific data which applies equally to all points */
+       switch (gso->brush_type) {
+               case GP_EDITBRUSH_TYPE_GRAB: /* Grab points */
+               case GP_EDITBRUSH_TYPE_PUSH: /* Push points */
+               {
+                       /* calculate amount of displacement to apply */
+                       gp_brush_grab_calc_dvec(gso);
+                       break;
+               }
+               
+               case GP_EDITBRUSH_TYPE_PINCH: /* Pinch points */
+               //case GP_EDITBRUSH_TYPE_TWIST: /* Twist points around midpoint */
+               {
+                       /* calculate midpoint of the brush (in data space) */
+                       gp_brush_calc_midpoint(gso);
+                       break;
+               }
+               
+               case GP_EDITBRUSH_TYPE_RANDOMISE: /* Random jitter */
+               {
+                       /* compute the displacement vector for the cursor (in data space) */
+                       gp_brush_grab_calc_dvec(gso);
+                       break;
+               }
+               
+               default:
+                       break;
+       }
+       
+       
+       /* Find visible strokes, and perform operations on those if hit */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               switch (gso->brush_type) {
+                       case GP_EDITBRUSH_TYPE_SMOOTH: /* Smooth strokes */
+                       {
+                               changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_smooth_apply);
+                               break;
+                       }
+                       
+                       case GP_EDITBRUSH_TYPE_THICKNESS: /* Adjust stroke thickness */
+                       {
+                               changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_thickness_apply);
+                               break;
+                       }
+                       
+                       case GP_EDITBRUSH_TYPE_GRAB: /* Grab points */
+                       {
+                               if (gso->first) {
+                                       /* First time this brush stroke is being applied:
+                                        * 1) Prepare data buffers (init/clear) for this stroke
+                                        * 2) Use the points now under the cursor
+                                        */
+                                       gp_brush_grab_stroke_init(gso, gps);
+                                       changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_grab_store_points);
+                               }
+                               else {
+                                       /* Apply effect to the stored points */
+                                       gp_brush_grab_apply_cached(gso, gps);
+                                       changed |= true;
+                               }
+                               break;
+                       }
+                       
+                       case GP_EDITBRUSH_TYPE_PUSH: /* Push points */
+                       {
+                               changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_push_apply);
+                               break;
+                       }
+                       
+                       case GP_EDITBRUSH_TYPE_PINCH: /* Pinch points */
+                       {
+                               changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_pinch_apply);
+                               break;
+                       }
+                       
+                       case GP_EDITBRUSH_TYPE_TWIST: /* Twist points around midpoint */
+                       {
+                               changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_twist_apply);
+                               break;
+                       }
+                       
+                       case GP_EDITBRUSH_TYPE_RANDOMISE: /* Apply jitter */
+                       {
+                               changed |= gpsculpt_brush_do_stroke(gso, gps, gp_brush_randomise_apply);
+                               break;
+                       }
+                       
+                       default:
+                               printf("ERROR: Unknown type of GPencil Sculpt brush - %d\n", gso->brush_type);
+                               break;
+               }
+       }
+       CTX_DATA_END;
+       
+       return changed;
+}
+
+/* Calculate settings for applying brush */
+static void gpsculpt_brush_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
+{
+       tGP_BrushEditData *gso = op->customdata;
+       const int radius = gso->brush->size;
+       float mousef[2];
+       int mouse[2];
+       bool changed = false;
+       
+       /* Get latest mouse coordinates */
+       RNA_float_get_array(itemptr, "mouse", mousef);
+       gso->mval[0] = mouse[0] = (int)(mousef[0]);
+       gso->mval[1] = mouse[1] = (int)(mousef[1]);
+       
+       gso->pressure = RNA_float_get(itemptr, "pressure");
+       
+       if (RNA_boolean_get(itemptr, "pen_flip"))
+               gso->flag |= GP_EDITBRUSH_FLAG_INVERT;
+       else
+               gso->flag &= ~GP_EDITBRUSH_FLAG_INVERT;
+       
+       
+       /* Store coordinates as reference, if operator just started running */
+       if (gso->first) {
+               gso->mval_prev[0]  = gso->mval[0];
+               gso->mval_prev[1]  = gso->mval[1];
+               gso->pressure_prev = gso->pressure;
+       }
+       
+       /* Update brush_rect, so that it represents the bounding rectangle of brush */
+       gso->brush_rect.xmin = mouse[0] - radius;
+       gso->brush_rect.ymin = mouse[1] - radius;
+       gso->brush_rect.xmax = mouse[0] + radius;
+       gso->brush_rect.ymax = mouse[1] + radius;
+       
+       
+       /* Apply brush */
+       if (gso->brush_type == GP_EDITBRUSH_TYPE_CLONE) {
+               changed = gpsculpt_brush_apply_clone(C, gso);
+       }
+       else {
+               changed = gpsculpt_brush_apply_standard(C, gso);
+       }
+       
+       
+       /* Updates */
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       }
+       
+       /* Store values for next step */
+       gso->mval_prev[0]  = gso->mval[0];
+       gso->mval_prev[1]  = gso->mval[1];
+       gso->pressure_prev = gso->pressure;
+       gso->first = false;
+}
+
+/* Running --------------------------------------------- */
+
+/* helper - a record stroke, and apply paint event */
+static void gpsculpt_brush_apply_event(bContext *C, wmOperator *op, const wmEvent *event)
+{
+       tGP_BrushEditData *gso = op->customdata;
+       PointerRNA itemptr;
+       float mouse[2];
+       int tablet = 0;
+       
+       mouse[0] = event->mval[0] + 1;
+       mouse[1] = event->mval[1] + 1;
+       
+       /* fill in stroke */
+       RNA_collection_add(op->ptr, "stroke", &itemptr);
+       
+       RNA_float_set_array(&itemptr, "mouse", mouse);
+       RNA_boolean_set(&itemptr, "pen_flip", event->ctrl != false);
+       RNA_boolean_set(&itemptr, "is_start", gso->first);
+       
+       /* handle pressure sensitivity (which is supplied by tablets) */
+       if (event->tablet_data) {
+               const wmTabletData *wmtab = event->tablet_data;
+               float pressure = wmtab->Pressure;
+               
+               tablet = (wmtab->Active != EVT_TABLET_NONE);
+               
+               /* special exception here for too high pressure values on first touch in
+                * windows for some tablets: clamp the values to be sane
+                */
+               if (tablet && (pressure >= 0.99f)) {
+                       pressure = 1.0f;
+               }
+               RNA_float_set(&itemptr, "pressure", pressure);
+       }
+       else {
+               RNA_float_set(&itemptr, "pressure", 1.0f);
+       }
+       
+       /* apply */
+       gpsculpt_brush_apply(C, op, &itemptr);
+}
+
+/* reapply */
+static int gpsculpt_brush_exec(bContext *C, wmOperator *op)
+{
+       if (!gpsculpt_brush_init(C, op))
+               return OPERATOR_CANCELLED;
+       
+       RNA_BEGIN(op->ptr, itemptr, "stroke") 
+       {
+               gpsculpt_brush_apply(C, op, &itemptr);
+       }
+       RNA_END;
+       
+       gpsculpt_brush_exit(C, op);
+       
+       return OPERATOR_FINISHED;
+}
+
+
+/* start modal painting */
+static int gpsculpt_brush_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+       tGP_BrushEditData *gso = NULL;
+       const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
+       bool needs_timer = false;
+       float brush_rate = 0.0f;
+       
+       /* init painting data */
+       if (!gpsculpt_brush_init(C, op))
+               return OPERATOR_CANCELLED;
+       
+       gso = op->customdata;
+       
+       /* initialise type-specific data (used for the entire session) */
+       switch (gso->brush_type) {
+               /* Brushes requiring timer... */
+               case GP_EDITBRUSH_TYPE_THICKNESS:
+                       brush_rate = 0.01f; // XXX: hardcoded
+                       needs_timer = true;
+                       break;
+                       
+               case GP_EDITBRUSH_TYPE_PINCH:
+                       brush_rate = 0.001f; // XXX: hardcoded
+                       needs_timer = true;
+                       break;
+               
+               case GP_EDITBRUSH_TYPE_TWIST:
+                       brush_rate = 0.01f; // XXX: hardcoded
+                       needs_timer = true;
+                       break;
+                       
+               default:
+                       break;
+       }
+       
+       /* register timer for increasing influence by hovering over an area */
+       if (needs_timer) {
+               gso->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, brush_rate);
+       }
+       
+       /* register modal handler */
+       WM_event_add_modal_handler(C, op);
+       
+       /* start drawing immediately? */
+       if (is_modal == false) {
+               ARegion *ar = CTX_wm_region(C);
+               
+               /* ensure that we'll have a new frame to draw on */
+               gpsculpt_brush_init_stroke(gso);
+               
+               /* apply first dab... */
+               gso->is_painting = true;
+               gpsculpt_brush_apply_event(C, op, event);
+               
+               /* redraw view with feedback */
+               ED_region_tag_redraw(ar);
+       }
+       
+       return OPERATOR_RUNNING_MODAL;
+}
+
+/* painting - handle events */
+static int gpsculpt_brush_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+       tGP_BrushEditData *gso = op->customdata;
+       const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
+       bool redraw_region = false;
+       
+       /* The operator can be in 2 states: Painting and Idling */
+       if (gso->is_painting) {
+               /* Painting  */
+               switch (event->type) {
+                       /* Mouse Move = Apply somewhere else */
+                       case MOUSEMOVE:
+                       case INBETWEEN_MOUSEMOVE:
+                               /* apply brush effect at new position */
+                               gpsculpt_brush_apply_event(C, op, event);
+                               
+                               /* force redraw, so that the cursor will at least be valid */
+                               redraw_region = true;
+                               break;
+                       
+                       /* Timer Tick - Only if this was our own timer */
+                       case TIMER:
+                               if (event->customdata == gso->timer) {
+                                       gso->timerTick = true;
+                                       gpsculpt_brush_apply_event(C, op, event);
+                                       gso->timerTick = false;
+                               }
+                               break;
+                       
+                       /* Painting mbut release = Stop painting (back to idle) */
+                       case LEFTMOUSE:
+                               //BLI_assert(event->val == KM_RELEASE);
+                               if (is_modal) {
+                                       /* go back to idling... */
+                                       gso->is_painting = false;
+                               }
+                               else {
+                                       /* end sculpt session, since we're not modal */
+                                       gso->is_painting = false;
+                                       
+                                       gpsculpt_brush_exit(C, op);
+                                       return OPERATOR_FINISHED;
+                               }
+                               break;
+                               
+                       /* Abort painting if any of the usual things are tried */
+                       case MIDDLEMOUSE:
+                       case RIGHTMOUSE:
+                       case ESCKEY:
+                               gpsculpt_brush_exit(C, op);
+                               return OPERATOR_FINISHED;
+               }
+       }
+       else {
+               /* Idling */
+               BLI_assert(is_modal == true);
+               
+               switch (event->type) {
+                       /* Painting mbut press = Start painting (switch to painting state) */
+                       case LEFTMOUSE:
+                               /* do initial "click" apply */
+                               gso->is_painting = true;
+                               gso->first = true;
+                               
+                               gpsculpt_brush_init_stroke(gso);
+                               gpsculpt_brush_apply_event(C, op, event);
+                               break;
+                               
+                       /* Exit modal operator, based on the "standard" ops */
+                       case RIGHTMOUSE:
+                       case ESCKEY:
+                               gpsculpt_brush_exit(C, op);
+                               return OPERATOR_FINISHED;
+                               
+                       /* MMB is often used for view manipulations */
+                       case MIDDLEMOUSE:
+                               return OPERATOR_PASS_THROUGH;
+                       
+                       /* Mouse movements should update the brush cursor - Just redraw the active region */
+                       case MOUSEMOVE:
+                       case INBETWEEN_MOUSEMOVE:
+                               redraw_region = true;
+                               break;
+                       
+                       /* Adjust brush settings */
+                       /* FIXME: Step increments and modifier keys are hardcoded here! */
+                       case WHEELUPMOUSE:
+                       case PADPLUSKEY:
+                               if (event->shift) {
+                                       /* increase strength */
+                                       gso->brush->strength += 0.05f;
+                                       CLAMP_MAX(gso->brush->strength, 1.0f);
+                               }
+                               else {
+                                       /* increase brush size */
+                                       gso->brush->size += 3;
+                                       CLAMP_MAX(gso->brush->size, 300);
+                               }
+                                       
+                               redraw_region = true;
+                               break;
+                       
+                       case WHEELDOWNMOUSE: 
+                       case PADMINUS:
+                               if (event->shift) {
+                                       /* decrease strength */
+                                       gso->brush->strength -= 0.05f;
+                                       CLAMP_MIN(gso->brush->strength, 0.0f);
+                               }
+                               else {
+                                       /* decrease brush size */
+                                       gso->brush->size -= 3;
+                                       CLAMP_MIN(gso->brush->size, 1);
+                               }
+                                       
+                               redraw_region = true;
+                               break;
+                       
+                       /* Change Frame - Allowed */
+                       case LEFTARROWKEY:
+                       case RIGHTARROWKEY:
+                       case UPARROWKEY:
+                       case DOWNARROWKEY:
+                               return OPERATOR_PASS_THROUGH;
+                       
+                       /* Unhandled event */
+                       default:
+                               break;
+               }
+       }
+       
+       /* Redraw region? */
+       if (redraw_region) {
+               ARegion *ar = CTX_wm_region(C);
+               ED_region_tag_redraw(ar);
+       }
+       
+       return OPERATOR_RUNNING_MODAL;
+}
+
+
+/* Operator --------------------------------------------- */
+
+void GPENCIL_OT_brush_paint(wmOperatorType *ot)
+{
+       PropertyRNA *prop;
+       
+       /* identifiers */
+       ot->name = "Stroke Sculpt";
+       ot->idname = "GPENCIL_OT_brush_paint";
+       ot->description = "Apply tweaks to strokes by painting over the strokes"; // XXX
+       
+       /* api callbacks */
+       ot->exec = gpsculpt_brush_exec;
+       ot->invoke = gpsculpt_brush_invoke;
+       ot->modal = gpsculpt_brush_modal;
+       ot->cancel = gpsculpt_brush_exit;
+       ot->poll = gpsculpt_brush_poll;
+
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
+
+       /* properties */
+       RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+       
+       prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input",
+                              "Enter a mini 'sculpt-mode' if enabled, otherwise, exit after drawing a single stroke");
+       RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+}
+
+/* ************************************************ */
index e8d73eaffdf74bb1f7dddce45a1e5879c33c0a69..ab02000a2009bafc3941f4b8efeef4126ae1a3d4 100644 (file)
@@ -77,6 +77,7 @@
 #include "RNA_access.h"
 #include "RNA_define.h"
 
+#include "UI_resources.h"
 #include "UI_view2d.h"
 
 #include "ED_gpencil.h"
@@ -106,9 +107,9 @@ enum {
 
 /* RNA enum define */
 static EnumPropertyItem prop_gpencil_convertmodes[] = {
-       {GP_STROKECONVERT_PATH, "PATH", 0, "Path", ""},
-       {GP_STROKECONVERT_CURVE, "CURVE", 0, "Bezier Curve", ""},
-       {GP_STROKECONVERT_POLY, "POLY", 0, "Polygon Curve", ""},
+       {GP_STROKECONVERT_PATH, "PATH", ICON_CURVE_PATH, "Path", "Animation path"},
+       {GP_STROKECONVERT_CURVE, "CURVE", ICON_CURVE_BEZCURVE, "Bezier Curve", "Smooth Bezier curve"},
+       {GP_STROKECONVERT_POLY, "POLY", ICON_MESH_DATA, "Polygon Curve", "Bezier curve with straight-line segments (vector handles)"},
        {0, NULL, 0, NULL, NULL}
 };
 
index de9667766453475b693eacf34c3fbfbe22aa3f67..4f03a53e736bccaa12bb0d7f45d06db38ce6dfbd 100644 (file)
 #include "BKE_screen.h"
 
 #include "UI_interface.h"
+#include "UI_resources.h"
 
 #include "WM_api.h"
 #include "WM_types.h"
 
 #include "RNA_access.h"
 #include "RNA_define.h"
+#include "RNA_enum_types.h"
 
 #include "ED_gpencil.h"
 
@@ -444,4 +446,220 @@ void GPENCIL_OT_reveal(wmOperatorType *ot)
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
+/* ***************** Lock/Unlock All Layers ************************ */
+
+static int gp_lock_all_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *gpl;
+       
+       /* sanity checks */
+       if (gpd == NULL)
+               return OPERATOR_CANCELLED;
+       
+       /* make all layers non-editable */
+       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+               gpl->flag |= GP_LAYER_LOCKED;
+       }
+       
+       /* notifiers */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_lock_all(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Lock All Layers";
+       ot->idname = "GPENCIL_OT_lock_all";
+       ot->description = "Lock all Grease Pencil layers to prevent them from being accidentally modified";
+       
+       /* callbacks */
+       ot->exec = gp_lock_all_exec;
+       ot->poll = gp_reveal_poll; /* XXX: could use dedicated poll later */
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* -------------------------- */
+
+static int gp_unlock_all_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *gpl;
+       
+       /* sanity checks */
+       if (gpd == NULL)
+               return OPERATOR_CANCELLED;
+       
+       /* make all layers editable again */
+       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+               gpl->flag &= ~GP_LAYER_LOCKED;
+       }
+       
+       /* notifiers */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_unlock_all(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Unlock All Layers";
+       ot->idname = "GPENCIL_OT_unlock_all";
+       ot->description = "unlock all Grease Pencil layers so that they can be edited";
+       
+       /* callbacks */
+       ot->exec = gp_unlock_all_exec;
+       ot->poll = gp_reveal_poll; /* XXX: could use dedicated poll later */
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* ********************** Isolate Layer **************************** */
+
+static int gp_isolate_layer_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *layer = gpencil_layer_getactive(gpd);
+       bGPDlayer *gpl;
+       int flags = GP_LAYER_LOCKED;
+       bool isolate = false;
+       
+       if (RNA_boolean_get(op->ptr, "affect_visibility"))
+               flags |= GP_LAYER_HIDE;
+               
+       if (ELEM(NULL, gpd, layer)) {
+               BKE_report(op->reports, RPT_ERROR, "No active layer to isolate");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* Test whether to isolate or clear all flags */
+       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+               /* Skip if this is the active layer */
+               if (gpl == layer)
+                       continue;
+               
+               /* If the flags aren't set, that means that the layer is
+                * not alone, so we have some layers to isolate still
+                */
+               if ((gpl->flag & flags) == 0) {
+                       isolate = true;
+                       break;
+               }
+       }
+       
+       /* Set/Clear flags as appropriate */
+       /* TODO: Include onionskinning on this list? */
+       if (isolate) {
+               /* Set flags on all "other" layers */
+               for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+                       if (gpl == layer)
+                               continue;
+                       else
+                               gpl->flag |= flags;
+               }
+       }
+       else {
+               /* Clear flags - Restore everything else */
+               for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+                       gpl->flag &= ~flags;
+               }
+       }
+       
+       /* notifiers */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_layer_isolate(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Isolate Layer";
+       ot->idname = "GPENCIL_OT_layer_isolate";
+       ot->description = "Toggle whether the active layer is the only one that can be edited and/or visible";
+       
+       /* callbacks */
+       ot->exec = gp_isolate_layer_exec;
+       ot->poll = gp_active_layer_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* properties */
+       RNA_def_boolean(ot->srna, "affect_visibility", false, "Affect Visibility",
+                       "In addition to toggling the editability, also affect the visibility");
+}
+
+/* ********************** Change Layer ***************************** */
+
+static int gp_layer_change_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
+{
+       uiPopupMenu *pup;
+       uiLayout *layout;
+       
+       /* call the menu, which will call this operator again, hence the canceled */
+       pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE);
+       layout = UI_popup_menu_layout(pup);
+       uiItemsEnumO(layout, "GPENCIL_OT_layer_change", "layer");
+       UI_popup_menu_end(C, pup);
+       
+       return OPERATOR_INTERFACE;
+}
+
+static int gp_layer_change_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       bGPDlayer *gpl = NULL;
+       int layer_num = RNA_enum_get(op->ptr, "layer");
+       
+       /* Get layer or create new one */
+       if (layer_num == -1) {
+               /* Create layer */
+               gpl = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
+       }
+       else {
+               /* Try to get layer */
+               gpl = BLI_findlink(&gpd->layers, layer_num);
+               
+               if (gpl == NULL) {
+                       BKE_reportf(op->reports, RPT_ERROR, "Cannot change to non-existent layer (index = %d)", layer_num);
+                       return OPERATOR_CANCELLED;
+               }
+       }
+       
+       /* Set active layer */
+       gpencil_layer_setactive(gpd, gpl);
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_layer_change(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Change Layer";
+       ot->idname = "GPENCIL_OT_layer_change";
+       ot->description = "Change active Grease Pencil layer";
+       
+       /* callbacks */
+       ot->invoke = gp_layer_change_invoke;
+       ot->exec = gp_layer_change_exec;
+       ot->poll = gp_active_layer_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* gp layer to use (dynamic enum) */
+       ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", "");
+       RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf);
+}
+
 /* ************************************************ */
index 5c37a0a5b60583a4ac5e011c0c6e0c74bf2ba46a..9c832ebe20c7b3045e1f3306792bd8c0f022b8c7 100644 (file)
@@ -44,6 +44,7 @@
 
 #include "BLT_translation.h"
 
+#include "DNA_object_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_screen_types.h"
 #include "DNA_space_types.h"
 #include "BKE_screen.h"
 
 #include "UI_interface.h"
+#include "UI_resources.h"
 
 #include "WM_api.h"
 #include "WM_types.h"
 
 #include "RNA_access.h"
 #include "RNA_define.h"
+#include "RNA_enum_types.h"
 
 #include "UI_view2d.h"
 
 #include "ED_gpencil.h"
+#include "ED_object.h"
 #include "ED_view3d.h"
 
 #include "gpencil_intern.h"
 
+/* ************************************************ */
+/* Stroke Edit Mode Management */
+
+static int gpencil_editmode_toggle_poll(bContext *C)
+{
+       return ED_gpencil_data_get_active(C) != NULL;
+}
+
+static int gpencil_editmode_toggle_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       
+       if (gpd == NULL)
+               return OPERATOR_CANCELLED;
+       
+       /* Just toggle editmode flag... */
+       gpd->flag ^= GP_DATA_STROKE_EDITMODE;
+       
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, NULL);
+       WM_event_add_notifier(C, NC_SCENE | ND_MODE, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_editmode_toggle(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Strokes Edit Mode Toggle";
+       ot->idname = "GPENCIL_OT_editmode_toggle";
+       ot->description = "Enter/Exit edit mode for Grease Pencil strokes";
+       
+       /* callbacks */
+       ot->exec = gpencil_editmode_toggle_exec;
+       ot->poll = gpencil_editmode_toggle_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
+}
+
 /* ************************************************ */
 /* Stroke Editing Operators */
 
@@ -233,7 +276,8 @@ void GPENCIL_OT_duplicate(wmOperatorType *ot)
  */
 
 /* list of bGPDstroke instances */
-static ListBase gp_strokes_copypastebuf = {NULL, NULL};
+/* NOTE: is exposed within the editors/gpencil module so that other tools can use it too */
+ListBase gp_strokes_copypastebuf = {NULL, NULL};
 
 /* Free copy/paste buffer data */
 void ED_gpencil_strokes_copybuf_free(void)
@@ -339,7 +383,7 @@ static int gp_strokes_paste_exec(bContext *C, wmOperator *op)
                BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
                return OPERATOR_CANCELLED;
        }
-       else if (gp_strokes_copypastebuf.first == NULL) {
+       else if (BLI_listbase_is_empty(&gp_strokes_copypastebuf)) {
                BKE_report(op->reports, RPT_ERROR, "No strokes to paste, select and copy some points before trying again");
                return OPERATOR_CANCELLED;
        }
@@ -434,6 +478,110 @@ void GPENCIL_OT_paste(wmOperatorType *ot)
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
+/* ******************* Move To Layer ****************************** */
+
+static int gp_move_to_layer_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(evt))
+{
+       uiPopupMenu *pup;
+       uiLayout *layout;
+       
+       /* call the menu, which will call this operator again, hence the canceled */
+       pup = UI_popup_menu_begin(C, op->type->name, ICON_NONE);
+       layout = UI_popup_menu_layout(pup);
+       uiItemsEnumO(layout, "GPENCIL_OT_move_to_layer", "layer");
+       UI_popup_menu_end(C, pup);
+       
+       return OPERATOR_INTERFACE;
+}
+
+// FIXME: allow moving partial strokes
+static int gp_move_to_layer_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       bGPDlayer *target_layer = NULL;
+       ListBase strokes = {NULL, NULL};
+       int layer_num = RNA_enum_get(op->ptr, "layer");
+       
+       /* Get layer or create new one */
+       if (layer_num == -1) {
+               /* Create layer */
+               target_layer = gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
+       }
+       else {
+               /* Try to get layer */
+               target_layer = BLI_findlink(&gpd->layers, layer_num);
+               
+               if (target_layer == NULL) {
+                       BKE_reportf(op->reports, RPT_ERROR, "There is no layer number %d", layer_num);
+                       return OPERATOR_CANCELLED;
+               }
+       }
+       
+       /* Extract all strokes to move to this layer
+        * NOTE: We need to do this in a two-pass system to avoid conflicts with strokes
+        *       getting repeatedly moved
+        */
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps, *gpsn;
+               
+               /* skip if no frame with strokes, or if this is the layer we're moving strokes to */
+               if ((gpl == target_layer) || (gpf == NULL))
+                       continue;
+               
+               /* make copies of selected strokes, and deselect these once we're done */
+               for (gps = gpf->strokes.first; gps; gps = gpsn) {
+                       gpsn = gps->next;
+                       
+                       /* skip strokes that are invalid for current view */
+                       if (ED_gpencil_stroke_can_use(C, gps) == false)
+                               continue;
+                       
+                       /* TODO: Don't just move entire strokes - instead, only copy the selected portions... */
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               BLI_remlink(&gpf->strokes, gps);
+                               BLI_addtail(&strokes, gps);
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       /* Paste them all in one go */
+       if (strokes.first) {
+               Scene *scene = CTX_data_scene(C);
+               bGPDframe *gpf = gpencil_layer_getframe(target_layer, CFRA, true);
+               
+               BLI_movelisttolist(&gpf->strokes, &strokes);
+               BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL));
+       }
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_move_to_layer(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Move Strokes to Layer";
+       ot->idname = "GPENCIL_OT_move_to_layer";
+       ot->description = "Move selected strokes to another layer"; // XXX: allow moving individual points too?
+       
+       /* callbacks */
+       ot->invoke = gp_move_to_layer_invoke;
+       ot->exec = gp_move_to_layer_exec;
+       ot->poll = gp_stroke_edit_poll; // XXX?
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* gp layer to use (dynamic enum) */
+       ot->prop = RNA_def_enum(ot->srna, "layer", DummyRNA_DEFAULT_items, 0, "Grease Pencil Layer", "");
+       RNA_def_enum_funcs(ot->prop, ED_gpencil_layers_with_new_enum_itemf);
+}
+
 /* ******************* Delete Active Frame ************************ */
 
 static int gp_actframe_delete_poll(bContext *C)
@@ -497,6 +645,7 @@ typedef enum eGP_DeleteMode {
        GP_DELETEOP_FRAME           = 2,
 } eGP_DeleteMode;
 
+/* ----------------------------------- */
 
 /* Delete selected strokes */
 static int gp_delete_selected_strokes(bContext *C)
@@ -540,6 +689,8 @@ static int gp_delete_selected_strokes(bContext *C)
        }
 }
 
+/* ----------------------------------- */
+
 /* Delete selected points but keep the stroke */
 static int gp_dissolve_selected_points(bContext *C)
 {
@@ -621,6 +772,124 @@ static int gp_dissolve_selected_points(bContext *C)
        }
 }
 
+/* ----------------------------------- */
+
+/* Temp data for storing information about an "island" of points
+ * that should be kept when splitting up a stroke. Used in:
+ * gp_stroke_delete_tagged_points()
+ */
+typedef struct tGPDeleteIsland {
+       int start_idx;
+       int end_idx;
+} tGPDeleteIsland;
+
+
+/* Split the given stroke into several new strokes, partitioning
+ * it based on whether the stroke points have a particular flag
+ * is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always)
+ *
+ * The algorithm used here is as follows:
+ * 1) We firstly identify the number of "islands" of non-tagged points
+ *    which will all end up being in new strokes.
+ *    - In the most extreme case (i.e. every other vert is a 1-vert island),
+ *      we have at most n / 2 islands
+ *    - Once we start having larger islands than that, the number required
+ *      becomes much less
+ * 2) Each island gets converted to a new stroke
+ */
+void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags)
+{
+       tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
+       bool in_island  = false;
+       int num_islands = 0;
+       
+       bGPDspoint *pt;
+       int i;
+       
+       /* First Pass: Identify start/end of islands */
+       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+               if (pt->flag & tag_flags) {
+                       /* selected - stop accumulating to island */
+                       in_island = false;
+               }
+               else {
+                       /* unselected - start of a new island? */
+                       int idx;
+                       
+                       if (in_island) {
+                               /* extend existing island */
+                               idx = num_islands - 1;
+                               islands[idx].end_idx = i;
+                       }
+                       else {
+                               /* start of new island */
+                               in_island = true;
+                               num_islands++;
+                               
+                               idx = num_islands - 1;
+                               islands[idx].start_idx = islands[idx].end_idx = i;
+                       }
+               }
+       }
+       
+       /* Watch out for special case where No islands = All points selected = Delete Stroke only */
+       if (num_islands) {
+               /* there are islands, so create a series of new strokes, adding them before the "next" stroke */
+               int idx;
+               
+               /* Create each new stroke... */
+               for (idx = 0; idx < num_islands; idx++) {
+                       tGPDeleteIsland *island = &islands[idx];
+                       bGPDstroke *new_stroke  = MEM_dupallocN(gps);
+                       
+                       /* Compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
+                       new_stroke->totpoints = island->end_idx - island->start_idx + 1;
+                       new_stroke->points    = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
+                       
+                       /* Copy over the relevant points */
+                       memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
+                       
+                       
+                       /* Each island corresponds to a new stroke. We must adjust the 
+                        * timings of these new strokes:
+                        *
+                        * Each point's timing data is a delta from stroke's inittime, so as we erase some points from
+                        * the start of the stroke, we have to offset this inittime and all remaing points' delta values.
+                        * This way we get a new stroke with exactly the same timing as if user had started drawing from
+                        * the first non-removed point...
+                        */
+                       {
+                               bGPDspoint *pts;
+                               float delta = gps->points[island->start_idx].time;
+                               int j;
+                               
+                               new_stroke->inittime += (double)delta;
+                               
+                               pts = new_stroke->points;
+                               for (j = 0; j < new_stroke->totpoints; j++, pts++) {
+                                       pts->time -= delta;
+                               }
+                       }
+                       
+                       /* Add new stroke to the frame */
+                       if (next_stroke) {
+                               BLI_insertlinkbefore(&gpf->strokes, next_stroke, new_stroke);
+                       }
+                       else {
+                               BLI_addtail(&gpf->strokes, new_stroke);
+                       }
+               }
+       }
+       
+       /* free islands */
+       MEM_freeN(islands);
+       
+       /* Delete the old stroke */
+       MEM_freeN(gps->points);
+       BLI_freelinkN(&gpf->strokes, gps);
+}
+
+
 /* Split selected strokes into segments, splitting on selected points */
 static int gp_delete_selected_points(bContext *C)
 {
@@ -644,89 +913,11 @@ static int gp_delete_selected_points(bContext *C)
                        
                        
                        if (gps->flag & GP_STROKE_SELECT) {
-                               bGPDspoint *pt;
-                               int i;
-                               
-                               /* The algorithm used here is as follows:
-                                * 1) We firstly identify the number of "islands" of non-selected points
-                                *    which will all end up being in new strokes.
-                                *    - In the most extreme case (i.e. every other vert is a 1-vert island),
-                                *      we have at most n / 2 islands
-                                *    - Once we start having larger islands than that, the number required
-                                *      becomes much less
-                                * 2) Each island gets converted to a new stroke
-                                */
-                               typedef struct tGPDeleteIsland {
-                                       int start_idx;
-                                       int end_idx;
-                               } tGPDeleteIsland;
-                               
-                               tGPDeleteIsland *islands = MEM_callocN(sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands");
-                               bool in_island  = false;
-                               int num_islands = 0;
-                               
-                               /* First Pass: Identify start/end of islands */
-                               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
-                                       if (pt->flag & GP_SPOINT_SELECT) {
-                                               /* selected - stop accumulating to island */
-                                               in_island = false;
-                                       }
-                                       else {
-                                               /* unselected - start of a new island? */
-                                               int idx;
-                                               
-                                               if (in_island) {
-                                                       /* extend existing island */
-                                                       idx = num_islands - 1;
-                                                       islands[idx].end_idx = i;
-                                               }
-                                               else {
-                                                       /* start of new island */
-                                                       in_island = true;
-                                                       num_islands++;
-                                                       
-                                                       idx = num_islands - 1;
-                                                       islands[idx].start_idx = islands[idx].end_idx = i;
-                                               }
-                                       }
-                               }
-                               
-                               /* Watch out for special case where No islands = All points selected = Delete Stroke only */
-                               if (num_islands) {
-                                       /* there are islands, so create a series of new strokes, adding them before the "next" stroke */
-                                       int idx;
-                                       
-                                       /* deselect old stroke, since it will be used as template for the new strokes */
-                                       gps->flag &= ~GP_STROKE_SELECT;
-                                       
-                                       /* create each new stroke... */
-                                       for (idx = 0; idx < num_islands; idx++) {
-                                               tGPDeleteIsland *island = &islands[idx];
-                                               bGPDstroke *new_stroke  = MEM_dupallocN(gps);
-                                               
-                                               /* compute new buffer size (+ 1 needed as the endpoint index is "inclusive") */
-                                               new_stroke->totpoints = island->end_idx - island->start_idx + 1;
-                                               new_stroke->points    = MEM_callocN(sizeof(bGPDspoint) * new_stroke->totpoints, "gp delete stroke fragment");
-                                               
-                                               /* copy over the relevant points */
-                                               memcpy(new_stroke->points, gps->points + island->start_idx, sizeof(bGPDspoint) * new_stroke->totpoints);
-                                               
-                                               /* add new stroke to the frame */
-                                               if (gpsn) {
-                                                       BLI_insertlinkbefore(&gpf->strokes, gpsn, new_stroke);
-                                               }
-                                               else {
-                                                       BLI_addtail(&gpf->strokes, new_stroke);
-                                               }
-                                       }
-                               }
-                               
-                               /* free islands */
-                               MEM_freeN(islands);
+                               /* deselect old stroke, since it will be used as template for the new strokes */
+                               gps->flag &= ~GP_STROKE_SELECT;
                                
-                               /* Delete the old stroke */
-                               MEM_freeN(gps->points);
-                               BLI_freelinkN(&gpf->strokes, gps);
+                               /* delete unwanted points by splitting stroke into several smaller ones */
+                               gp_stroke_delete_tagged_points(gpf, gps, gpsn, GP_SPOINT_SELECT);
                                
                                changed = true;
                        }
@@ -743,6 +934,7 @@ static int gp_delete_selected_points(bContext *C)
        }
 }
 
+/* ----------------------------------- */
 
 static int gp_delete_exec(bContext *C, wmOperator *op)
 {
@@ -812,4 +1004,190 @@ void GPENCIL_OT_dissolve(wmOperatorType *ot)
        ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
 }
 
+/* ****************** Snapping - Strokes <-> Cursor ************************ */
+
+/* Poll callback for snap operators */
+/* NOTE: For now, we only allow these in the 3D view, as other editors do not
+ *       define a cursor or gridstep which can be used
+ */
+static int gp_snap_poll(bContext *C)
+{
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       ScrArea *sa = CTX_wm_area(C);
+       
+       return (gpd != NULL) && ((sa != NULL) && (sa->spacetype == SPACE_VIEW3D));
+}
+
+/* --------------------------------- */
+
+static int gp_snap_to_grid(bContext *C, wmOperator *op)
+{
+       RegionView3D *rv3d = CTX_wm_region_data(C);
+       float gridf = rv3d->gridview;
+       
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               
+               // TOOD: if entire stroke is selected, offset entire stroke by same amount?
+               
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       /* only if point is selected.. */
+                       if (pt->flag & GP_SPOINT_SELECT) {
+                               pt->x = gridf * floorf(0.5f + pt->x / gridf);
+                               pt->y = gridf * floorf(0.5f + pt->y / gridf);
+                               pt->z = gridf * floorf(0.5f + pt->z / gridf);
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_snap_to_grid(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Snap Selection to Grid";
+       ot->idname = "GPENCIL_OT_snap_to_grid";
+       ot->description = "Snap selected points to the nearest grid points";
+       
+       /* callbacks */
+       ot->exec = gp_snap_to_grid;
+       ot->poll = gp_snap_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* ------------------------------- */
+
+static int gp_snap_to_cursor(bContext *C, wmOperator *op)
+{
+       Scene *scene = CTX_data_scene(C);
+       View3D *v3d = CTX_wm_view3d(C);
+       
+       const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
+       const float *cursor_global = ED_view3d_cursor3d_get(scene, v3d);
+       
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               
+               /* only continue if this stroke is selected (editable doesn't guarantee this)... */
+               if ((gps->flag & GP_STROKE_SELECT) == 0)
+                       continue;
+               
+               if (use_offset) {
+                       float offset[3];
+                       
+                       /* compute offset from first point of stroke to cursor */
+                       /* TODO: Allow using midpoint instead? */
+                       sub_v3_v3v3(offset, cursor_global, &gps->points->x);
+                       
+                       /* apply offset to all points in the stroke */
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               add_v3_v3(&pt->x, offset);
+                       }
+               }
+               else {
+                       /* affect each selected point */
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               if (pt->flag & GP_SPOINT_SELECT) {
+                                       copy_v3_v3(&pt->x, cursor_global);
+                               }
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_snap_to_cursor(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Snap Selection to Cursor";
+       ot->idname = "GPENCIL_OT_snap_to_cursor";
+       ot->description = "Snap selected points/strokes to the cursor";
+       
+       /* callbacks */
+       ot->exec = gp_snap_to_cursor;
+       ot->poll = gp_snap_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* props */
+       ot->prop = RNA_def_boolean(ot->srna, "use_offset", true, "With Offset",
+                                  "Offset the entire stroke instead of selected points only");
+}
+
+/* ------------------------------- */
+
+static int gp_snap_cursor_to_sel(bContext *C, wmOperator *op)
+{
+       Scene *scene = CTX_data_scene(C);
+       View3D *v3d = CTX_wm_view3d(C);
+       
+       float *cursor = ED_view3d_cursor3d_get(scene, v3d);
+       float centroid[3] = {0.0f};
+       float min[3], max[3];
+       size_t count = 0;
+       
+       INIT_MINMAX(min, max);
+       
+       /* calculate midpoints from selected points */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               
+               /* only continue if this stroke is selected (editable doesn't guarantee this)... */
+               if ((gps->flag & GP_STROKE_SELECT) == 0)
+                       continue;
+               
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       if (pt->flag & GP_SPOINT_SELECT) {
+                               add_v3_v3(centroid, &pt->x);
+                               minmax_v3v3_v3(min, max, &pt->x);
+                               count++;
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       if (v3d->around == V3D_AROUND_CENTER_MEAN) {
+               mul_v3_fl(centroid, 1.0f / (float)count);
+               copy_v3_v3(cursor, centroid);
+       }
+       else {
+               mid_v3_v3v3(cursor, min, max);
+       }
+
+       
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Snap Cursor to Selected Points";
+       ot->idname = "GPENCIL_OT_snap_cursor_to_selected";
+       ot->description = "Snap cursor to center of selected points";
+       
+       /* callbacks */
+       ot->exec = gp_snap_cursor_to_sel;
+       ot->poll = gp_snap_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+
 /* ************************************************ */
index 9c0b6f785d1c95fc91f5cb289a0d19462a08c5b3..d9a6441c00e9b5458bc968a0067afb9723abc98c 100644 (file)
@@ -44,6 +44,10 @@ struct ARegion;
 struct View2D;
 struct wmOperatorType;
 
+struct PointerRNA;
+struct PropertyRNA;
+struct EnumPropertyItem;
+
 
 /* ***************************************************** */
 /* Internal API */
@@ -96,12 +100,37 @@ void gp_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc);
 void gp_point_to_xy(GP_SpaceConversion *settings, struct bGPDstroke *gps, struct bGPDspoint *pt,
                     int *r_x, int *r_y);
 
+/**
+ * Convert a screenspace point to a 3D Grease Pencil coordinate.
+ *
+ * For use with editing tools where it is easier to perform the operations in 2D,
+ * and then later convert the transformed points back to 3D.
+ *
+ * \param screeN_co    The screenspace 2D coordinates to convert to 
+ * \param[out] r_out  The resulting 3D coordinates of the input point
+ */
+bool gp_point_xy_to_3d(GP_SpaceConversion *gsc, struct Scene *scene, const float screen_co[2], float r_out[3]);
+
 /* Poll Callbacks ------------------------------------ */
 /* gpencil_utils.c */
 
 int gp_add_poll(struct bContext *C);
 int gp_active_layer_poll(struct bContext *C);
 
+/* Copy/Paste Buffer --------------------------------- */
+/* gpencil_edit.c */
+
+extern ListBase gp_strokes_copypastebuf;
+
+/* Stroke Editing ------------------------------------ */
+
+void gp_stroke_delete_tagged_points(bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags);
+
+/* Layers Enums -------------------------------------- */
+
+struct EnumPropertyItem *ED_gpencil_layers_enum_itemf(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, bool *r_free);
+struct EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, bool *r_free);
+
 /* ***************************************************** */
 /* Operator Defines */
 
@@ -119,6 +148,8 @@ typedef enum eGPencil_PaintModes {
 
 /* stroke editing ----- */
 
+void GPENCIL_OT_editmode_toggle(struct wmOperatorType *ot);
+
 void GPENCIL_OT_select(struct wmOperatorType *ot);
 void GPENCIL_OT_select_all(struct wmOperatorType *ot);
 void GPENCIL_OT_select_circle(struct wmOperatorType *ot);
@@ -135,6 +166,19 @@ void GPENCIL_OT_dissolve(struct wmOperatorType *ot);
 void GPENCIL_OT_copy(struct wmOperatorType *ot);
 void GPENCIL_OT_paste(struct wmOperatorType *ot);
 
+void GPENCIL_OT_move_to_layer(struct wmOperatorType *ot);
+void GPENCIL_OT_layer_change(struct wmOperatorType *ot);
+
+void GPENCIL_OT_snap_to_grid(struct wmOperatorType *ot);
+void GPENCIL_OT_snap_to_cursor(struct wmOperatorType *ot);
+void GPENCIL_OT_snap_cursor_to_selected(struct wmOperatorType *ot);
+void GPENCIL_OT_snap_cursor_to_center(struct wmOperatorType *ot);
+
+
+/* stroke sculpting -- */
+
+void GPENCIL_OT_brush_paint(struct wmOperatorType *ot);
+
 /* buttons editing --- */
 
 void GPENCIL_OT_data_add(struct wmOperatorType *ot);
@@ -148,6 +192,11 @@ void GPENCIL_OT_layer_duplicate(struct wmOperatorType *ot);
 void GPENCIL_OT_hide(struct wmOperatorType *ot);
 void GPENCIL_OT_reveal(struct wmOperatorType *ot);
 
+void GPENCIL_OT_lock_all(struct wmOperatorType *ot);
+void GPENCIL_OT_unlock_all(struct wmOperatorType *ot);
+
+void GPENCIL_OT_layer_isolate(struct wmOperatorType *ot);
+
 void GPENCIL_OT_active_frame_delete(struct wmOperatorType *ot);
 
 void GPENCIL_OT_convert(struct wmOperatorType *ot);
index ab56565f4ca3708531e8115385de8ddb7f991186..25012ab6d64c44fbd30754cc9faf314827b73323 100644 (file)
@@ -79,11 +79,25 @@ static void ed_keymap_gpencil_general(wmKeyConfig *keyconf)
        RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_ERASER);
        RNA_boolean_set(kmi->ptr, "wait_for_input", false);
        
+       
+       /* 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!
+        */
+#if 0
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", TABLET_STYLUS, KM_PRESS, 0, 0);
+       RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_DRAW);
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+#endif
+       
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", TABLET_ERASER, KM_PRESS, 0, 0);
+       RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_ERASER);
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+       
        /* Viewport Tools ------------------------------- */
        
        /* Enter EditMode */
-       kmi = WM_keymap_add_item(keymap, "WM_OT_context_toggle", TABKEY, KM_PRESS, 0, DKEY);
-       RNA_string_set(kmi->ptr, "data_path", "gpencil_data.use_stroke_edit_mode");
+       WM_keymap_add_item(keymap, "GPENCIL_OT_editmode_toggle", TABKEY, KM_PRESS, 0, DKEY);
        
        /* Pie Menu - For standard tools */
        WM_keymap_add_menu_pie(keymap, "GPENCIL_PIE_tool_palette", QKEY, KM_PRESS, 0, DKEY);
@@ -111,8 +125,10 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
        /* ----------------------------------------------- */
        
        /* Exit EditMode */
-       kmi = WM_keymap_add_item(keymap, "WM_OT_context_toggle", TABKEY, KM_PRESS, 0, 0);
-       RNA_string_set(kmi->ptr, "data_path", "gpencil_data.use_stroke_edit_mode");
+       WM_keymap_add_item(keymap, "GPENCIL_OT_editmode_toggle", TABKEY, KM_PRESS, 0, 0);
+       
+       /* Pie Menu - For settings/tools easy access */
+       WM_keymap_add_menu_pie(keymap, "GPENCIL_PIE_sculpt", EKEY, KM_PRESS, 0, DKEY);
        
        /* Brush Settings */
        /* NOTE: We cannot expose these in the standard keymap, as they will interfere with regular hotkeys
@@ -185,6 +201,14 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
        WM_keymap_add_item(keymap, "GPENCIL_OT_copy", CKEY, KM_PRESS, KM_OSKEY, 0);
        WM_keymap_add_item(keymap, "GPENCIL_OT_paste", VKEY, KM_PRESS, KM_OSKEY, 0);
 #endif
+
+       /* snap */
+       WM_keymap_add_menu(keymap, "GPENCIL_MT_snap", SKEY, KM_PRESS, KM_SHIFT, 0);
+       
+       
+       /* convert to geometry */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_convert", CKEY, KM_PRESS, KM_ALT, 0);
+       
        
        /* Show/Hide */
        /* NOTE: These are available only in EditMode now, since they clash with general-purpose hotkeys */
@@ -196,35 +220,62 @@ static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
        kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_hide", HKEY, KM_PRESS, KM_SHIFT, 0);
        RNA_boolean_set(kmi->ptr, "unselected", true);
        
+       /* Isolate Layer */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_layer_isolate", PADASTERKEY, KM_PRESS, 0, 0);
+       
+       /* Move to Layer */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_move_to_layer", MKEY, KM_PRESS, 0, 0);
+       
+       
+       
+       /* Brush-Based Editing:
+        *   EKEY + LMB                          = Single stroke, draw immediately 
+        *        + Other Modifiers (Ctrl/Shift) = Invert, Smooth, etc.
+        *
+        * For the modal version, use D+E -> Sculpt
+        */
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, 0, EKEY);
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+       
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, KM_CTRL, EKEY);
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+       /*RNA_boolean_set(kmi->ptr, "use_invert", true);*/
+       
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_brush_paint", LEFTMOUSE, KM_PRESS, KM_SHIFT, EKEY);
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+       /*RNA_boolean_set(kmi->ptr, "use_smooth", true);*/
+       
+       
+       /* Shift-FKEY = Sculpt Strength */
+       kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0);
+       RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.gpencil_sculpt.brush.strength");
+       
+       /* Ctrl-FKEY = Sculpt Brush Size */
+       kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0);
+       RNA_string_set(kmi->ptr, "data_path_primary", "tool_settings.gpencil_sculpt.brush.size");
+       
+       
+       
        
        /* Transform Tools */
        kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_translate", GKEY, KM_PRESS, 0, 0);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_translate", EVT_TWEAK_S, KM_ANY, 0, 0);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_rotate", RKEY, KM_PRESS, 0, 0);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_resize", SKEY, KM_PRESS, 0, 0);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_mirror", MKEY, KM_PRESS, KM_CTRL, 0);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_bend", WKEY, KM_PRESS, KM_SHIFT, 0);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        WM_keymap_add_item(keymap, "TRANSFORM_OT_tosphere", SKEY, KM_PRESS, KM_ALT | KM_SHIFT, 0);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        WM_keymap_add_item(keymap, "TRANSFORM_OT_shear", SKEY, KM_PRESS, KM_ALT | KM_CTRL | KM_SHIFT, 0);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        kmi = WM_keymap_add_item(keymap, "TRANSFORM_OT_transform", SKEY, KM_PRESS, KM_ALT, 0);
        RNA_enum_set(kmi->ptr, "mode", TFM_GPENCIL_SHRINKFATTEN);
-       RNA_boolean_set(kmi->ptr, "gpencil_strokes", true);
        
        /* Proportional Editing */
        ED_keymap_proportional_cycle(keyconf, keymap);
@@ -249,6 +300,8 @@ void ED_operatortypes_gpencil(void)
        
        /* Editing (Strokes) ------------ */
        
+       WM_operatortype_append(GPENCIL_OT_editmode_toggle);
+       
        WM_operatortype_append(GPENCIL_OT_select);
        WM_operatortype_append(GPENCIL_OT_select_all);
        WM_operatortype_append(GPENCIL_OT_select_circle);
@@ -265,6 +318,15 @@ void ED_operatortypes_gpencil(void)
        WM_operatortype_append(GPENCIL_OT_copy);
        WM_operatortype_append(GPENCIL_OT_paste);
        
+       WM_operatortype_append(GPENCIL_OT_move_to_layer);
+       WM_operatortype_append(GPENCIL_OT_layer_change);
+       
+       WM_operatortype_append(GPENCIL_OT_snap_to_grid);
+       WM_operatortype_append(GPENCIL_OT_snap_to_cursor);
+       WM_operatortype_append(GPENCIL_OT_snap_cursor_to_selected);
+       
+       WM_operatortype_append(GPENCIL_OT_brush_paint);
+       
        /* Editing (Buttons) ------------ */
        
        WM_operatortype_append(GPENCIL_OT_data_add);
@@ -277,6 +339,9 @@ void ED_operatortypes_gpencil(void)
        
        WM_operatortype_append(GPENCIL_OT_hide);
        WM_operatortype_append(GPENCIL_OT_reveal);
+       WM_operatortype_append(GPENCIL_OT_lock_all);
+       WM_operatortype_append(GPENCIL_OT_unlock_all);
+       WM_operatortype_append(GPENCIL_OT_layer_isolate);
        
        WM_operatortype_append(GPENCIL_OT_active_frame_delete);
        
@@ -290,12 +355,14 @@ void ED_operatormacros_gpencil(void)
        wmOperatorType *ot;
        wmOperatorTypeMacro *otmacro;
        
+       /* Duplicate + Move = Interactively place newly duplicated strokes */
        ot = WM_operatortype_append_macro("GPENCIL_OT_duplicate_move", "Duplicate Strokes",
                                          "Make copies of the selected Grease Pencil strokes and move them",
                                          OPTYPE_UNDO | OPTYPE_REGISTER);
        WM_operatortype_macro_define(ot, "GPENCIL_OT_duplicate");
        otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
        RNA_boolean_set(otmacro->ptr, "gpencil_strokes", true);
+
 }
 
 /* ****************************************** */
index df88da073caaff33072d5e707c848f480d024c5e..c5a92c4383d0fdc8d24e2d2ef155c737f11e5378 100644 (file)
 /* ******************************************* */
 /* 'Globals' and Defines */
 
-/* Temporary 'Stroke' Operation data */
+/* values for tGPsdata->status */
+typedef enum eGPencil_PaintStatus {
+       GP_STATUS_IDLING = 0,   /* stroke isn't in progress yet */
+       GP_STATUS_PAINTING,     /* a stroke is in progress */
+       GP_STATUS_ERROR,        /* something wasn't correctly set up */
+       GP_STATUS_DONE          /* painting done */
+} eGPencil_PaintStatus;
+
+/* Return flags for adding points to stroke buffer */
+typedef enum eGP_StrokeAdd_Result {
+       GP_STROKEADD_INVALID    = -2,       /* error occurred - insufficient info to do so */
+       GP_STROKEADD_OVERFLOW   = -1,       /* error occurred - cannot fit any more points */
+       GP_STROKEADD_NORMAL,                /* point was successfully added */
+       GP_STROKEADD_FULL                   /* cannot add any more points to buffer */
+} eGP_StrokeAdd_Result;
+
+/* Runtime flags */
+typedef enum eGPencil_PaintFlags {
+       GP_PAINTFLAG_FIRSTRUN       = (1 << 0),    /* operator just started */
+       GP_PAINTFLAG_STROKEADDED    = (1 << 1),
+       GP_PAINTFLAG_V3D_ERASER_DEPTH = (1 << 2)
+} eGPencil_PaintFlags;
+
+
+/* Temporary 'Stroke' Operation data
+ *   "p" = op->customdata
+ */
 typedef struct tGPsdata {
        Scene *scene;       /* current scene from context */
        
@@ -95,8 +121,13 @@ typedef struct tGPsdata {
        bGPDlayer *gpl;     /* layer we're working on */
        bGPDframe *gpf;     /* frame we're working on */
        
-       short status;       /* current status of painting */
-       short paintmode;    /* mode for painting */
+       char *align_flag;   /* projection-mode flags (toolsettings - eGPencil_Placement_Flags) */
+       
+       eGPencil_PaintStatus status;     /* current status of painting */
+       eGPencil_PaintModes  paintmode;  /* mode for painting */
+       eGPencil_PaintFlags  flags;      /* flags that can get set during runtime (eGPencil_PaintFlags) */
+       
+       short radius;       /* radius of influence for eraser */
        
        int mval[2];        /* current mouse-position */
        int mvalo[2];       /* previous recorded mouse-position */
@@ -104,9 +135,6 @@ typedef struct tGPsdata {
        float pressure;     /* current stylus pressure */
        float opressure;    /* previous stylus pressure */
        
-       short radius;       /* radius of influence for eraser */
-       short flags;        /* flags that can get set during runtime */
-       
        /* These need to be doubles, as (at least under unix) they are in seconds since epoch,
         * float (and its 7 digits precision) is definitively not enough here!
         * double, with its 15 digits precision, ensures us millisecond precision for a few centuries at least.
@@ -124,29 +152,6 @@ typedef struct tGPsdata {
        void *erasercursor; /* radial cursor data for drawing eraser */
 } tGPsdata;
 
-/* values for tGPsdata->status */
-enum {
-       GP_STATUS_IDLING = 0,   /* stroke isn't in progress yet */
-       GP_STATUS_PAINTING,     /* a stroke is in progress */
-       GP_STATUS_ERROR,        /* something wasn't correctly set up */
-       GP_STATUS_DONE          /* painting done */
-};
-
-/* Return flags for adding points to stroke buffer */
-enum {
-       GP_STROKEADD_INVALID    = -2,       /* error occurred - insufficient info to do so */
-       GP_STROKEADD_OVERFLOW   = -1,       /* error occurred - cannot fit any more points */
-       GP_STROKEADD_NORMAL,                /* point was successfully added */
-       GP_STROKEADD_FULL                   /* cannot add any more points to buffer */
-};
-
-/* Runtime flags */
-enum {
-       GP_PAINTFLAG_FIRSTRUN       = (1 << 0),    /* operator just started */
-       GP_PAINTFLAG_STROKEADDED    = (1 << 1),
-       GP_PAINTFLAG_V3D_ERASER_DEPTH = (1 << 2)
-};
-
 /* ------ */
 
 /* maximum sizes of gp-session buffer */
@@ -204,7 +209,7 @@ static int gpencil_draw_poll(bContext *C)
 static bool gpencil_project_check(tGPsdata *p)
 {
        bGPdata *gpd = p->gpd;
-       return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (p->gpd->flag & (GP_DATA_DEPTH_VIEW | GP_DATA_DEPTH_STROKE)));
+       return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (*p->align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE)));
 }
 
 /* ******************************************* */
@@ -736,112 +741,6 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
 
 /* --- 'Eraser' for 'Paint' Tool ------ */
 
-/* eraser tool - remove segment from stroke/split stroke (after lasso inside) */
-static short gp_stroke_eraser_splitdel(bGPDframe *gpf, bGPDstroke *gps, int i)
-{
-       bGPDspoint *pt_tmp = gps->points;
-       bGPDstroke *gsn = NULL;
-       
-       /* if stroke only had two points, get rid of stroke */
-       if (gps->totpoints == 2) {
-               /* free stroke points, then stroke */
-               MEM_freeN(pt_tmp);
-               BLI_freelinkN(&gpf->strokes, gps);
-
-               /* nothing left in stroke, so stop */
-               return 1;
-       }
-       
-       /* if last segment, just remove segment from the stroke */
-       else if (i == gps->totpoints - 2) {
-               /* allocate new points array, and assign most of the old stroke there */
-               gps->totpoints--;
-               gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
-               memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * gps->totpoints);
-               
-               /* free temp buffer */
-               MEM_freeN(pt_tmp);
-               
-               /* nothing left in stroke, so stop */
-               return 1;
-       }
-       
-       /* if first segment, just remove segment from the stroke */
-       else if (i == 0) {
-               /* allocate new points array, and assign most of the old stroke there */
-               gps->totpoints--;
-               gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
-               memcpy(gps->points, pt_tmp + 1, sizeof(bGPDspoint) * gps->totpoints);
-               
-               /* We must adjust timings!
-                * Each point's timing data is a delta from stroke's inittime, so as we erase the first
-                * point of the stroke, we have to offset this inittime and all remaining points' delta values.
-                * This way we get a new stroke with exactly the same timing as if user had started drawing from
-                * the second point...
-                */
-               {
-                       bGPDspoint *pts;
-                       float delta = pt_tmp[1].time;
-                       int j;
-                       
-                       gps->inittime += (double)delta;
-                       
-                       pts = gps->points;
-                       for (j = 0; j < gps->totpoints; j++, pts++) {
-                               pts->time -= delta;
-                       }
-               }
-               
-               /* free temp buffer */
-               MEM_freeN(pt_tmp);
-               
-               /* no break here, as there might still be stuff to remove in this stroke */
-               return 0;
-       }
-       
-       /* segment occurs in 'middle' of stroke, so split */
-       else {
-               /* duplicate stroke, and assign 'later' data to that stroke */
-               gsn = MEM_dupallocN(gps);
-               gsn->prev = gsn->next = NULL;
-               BLI_insertlinkafter(&gpf->strokes, gps, gsn);
-               
-               gsn->totpoints = gps->totpoints - i;
-               gsn->points = MEM_mallocN(sizeof(bGPDspoint) * gsn->totpoints, "gp_stroke_points");
-               memcpy(gsn->points, pt_tmp + i, sizeof(bGPDspoint) * gsn->totpoints);
-               
-               /* We must adjust timings of this new stroke!
-                * Each point's timing data is a delta from stroke's inittime, so as we erase the first
-                * point of the stroke, we have to offset this inittime and all remaing points' delta values.
-                * This way we get a new stroke with exactly the same timing as if user had started drawing from
-                * the second point...
-                */
-               {
-                       bGPDspoint *pts;
-                       float delta = pt_tmp[i].time;
-                       int j;
-                       
-                       gsn->inittime += (double)delta;
-                       
-                       pts = gsn->points;
-                       for (j = 0; j < gsn->totpoints; j++, pts++) {
-                               pts->time -= delta;
-                       }
-               }
-               
-               /* adjust existing stroke  */
-               gps->totpoints = i;
-               gps->points = MEM_mallocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
-               memcpy(gps->points, pt_tmp, sizeof(bGPDspoint) * gps->totpoints);
-               
-               /* free temp buffer */
-               MEM_freeN(pt_tmp);
-               
-               /* nothing left in stroke, so stop */
-               return 1;
-       }
-}
-
 /* which which point is infront (result should only be used for comparison) */
 static float view3d_point_depth(const RegionView3D *rv3d, const float co[3])
 {
@@ -853,6 +752,7 @@ static float view3d_point_depth(const RegionView3D *rv3d, const float co[3])
        }
 }
 
+/* only erase stroke points that are visible */
 static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, const int x, const int y)
 {
        if ((p->sa->spacetype == SPACE_VIEW3D) &&
@@ -874,15 +774,33 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, cons
        return false;
 }
 
+/* apply a falloff effect to brush strength, based on distance */
+static float gp_stroke_eraser_calc_influence(tGPsdata *p, const int mval[2], const int radius, const int co[2])
+{
+       /* Linear Falloff... */
+       float distance = (float)len_v2v2_int(mval, co);
+       float fac;
+       
+       CLAMP(distance, 0.0f, (float)radius);
+       fac = 1.0f - (distance / (float)radius);
+       
+       /* Control this further using pen pressure */
+       fac *= p->pressure;
+       
+       /* Return influence factor computed here */
+       return fac;
+}
 
 /* eraser tool - evaluation per stroke */
 /* TODO: this could really do with some optimization (KD-Tree/BVH?) */
 static void gp_stroke_eraser_dostroke(tGPsdata *p,
+                                      bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps,
                                       const int mval[2], const int mvalo[2],
-                                      short rad, const rcti *rect, bGPDframe *gpf, bGPDstroke *gps)
+                                      const int radius, const rcti *rect)
 {
        bGPDspoint *pt1, *pt2;
-       int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
+       int pc1[2] = {0};
+       int pc2[2] = {0};
        int i;
        
        if (gps->totpoints == 0) {
@@ -892,56 +810,101 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p,
                BLI_freelinkN(&gpf->strokes, gps);
        }
        else if (gps->totpoints == 1) {
-               gp_point_to_xy(&p->gsc, gps, gps->points, &x0, &y0);
+               gp_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]);
                
                /* do boundbox check first */
-               if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) {
+               if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) {
                        /* only check if point is inside */
-                       if (((x0 - mval[0]) * (x0 - mval[0]) + (y0 - mval[1]) * (y0 - mval[1])) <= rad * rad) {
+                       if (len_v2v2_int(mval, pc1) <= radius) {
                                /* free stroke */
+                               // XXX: pressure sensitive eraser should apply here too?
                                MEM_freeN(gps->points);
                                BLI_freelinkN(&gpf->strokes, gps);
                        }
                }
        }
        else {
-               /* loop over the points in the stroke, checking for intersections
-                *  - an intersection will require the stroke to be split
+               /* Pressure threshold at which stroke should be culled: Calculated as pressure value
+                * below which we would have invisible strokes
+                */
+               const float cull_thresh = (gpl->thickness) ?  1.0f / ((float)gpl->thickness)  : 1.0f;
+               
+               /* Amount to decrease the pressure of each point with each stroke */
+               // TODO: Fetch from toolsettings, or compute based on thickness instead?
+               const float strength = 0.1f;
+               
+               /* Perform culling? */
+               bool do_cull = false;
+               
+               
+               /* Clear Tags
+                *
+                * Note: It's better this way, as we are sure that
+                * we don't miss anything, though things will be
+                * slightly slower as a result
+                */
+               for (i = 0; i < gps->totpoints; i++) {
+                       bGPDspoint *pt = &gps->points[i];
+                       pt->flag &= ~GP_SPOINT_TAG;
+               }
+               
+               /* First Pass: Loop over the points in the stroke
+                *   1) Thin out parts of the stroke under the brush
+                *   2) Tag "too thin" parts for removal (in second pass)
                 */
                for (i = 0; (i + 1) < gps->totpoints; i++) {
                        /* get points to work with */
                        pt1 = gps->points + i;
                        pt2 = gps->points + i + 1;
                        
-                       gp_point_to_xy(&p->gsc, gps, pt1, &x0, &y0);
-                       gp_point_to_xy(&p->gsc, gps, pt2, &x1, &y1);
+                       gp_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]);
+                       gp_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]);
                        
-                       /* check that point segment of the boundbox of the eraser stroke */
-                       if (((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) ||
-                           ((!ELEM(V2D_IS_CLIPPED, x1, y1)) && BLI_rcti_isect_pt(rect, x1, y1)))
+                       /* Check that point segment of the boundbox of the eraser stroke */
+                       if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) ||
+                           ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1])))
                        {
-                               /* check if point segment of stroke had anything to do with
+                               /* Check if point segment of stroke had anything to do with
                                 * eraser region  (either within stroke painted, or on its lines)
                                 *  - this assumes that linewidth is irrelevant
                                 */
-                               if (gp_stroke_inside_circle(mval, mvalo, rad, x0, y0, x1, y1)) {
-                                       if ((gp_stroke_eraser_is_occluded(p, pt1, x0, y0) == false) ||
-                                           (gp_stroke_eraser_is_occluded(p, pt2, x1, y1) == false))
+                               if (gp_stroke_inside_circle(mval, mvalo, radius, pc1[0], pc1[1], pc2[0], pc2[1])) {
+                                       if ((gp_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) ||
+                                           (gp_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false))
                                        {
-                                               /* if function returns true, break this loop (as no more point to check) */
-                                               if (gp_stroke_eraser_splitdel(gpf, gps, i))
-                                                       break;
+                                               /* Point is affected: */
+                                               /* 1) Adjust thickness
+                                                *  - Influence of eraser falls off with distance from the middle of the eraser
+                                                *  - Second point gets less influence, as it might get hit again in the next segment
+                                                */
+                                               pt1->pressure -= gp_stroke_eraser_calc_influence(p, mval, radius, pc1) * strength;
+                                               pt2->pressure -= gp_stroke_eraser_calc_influence(p, mval, radius, pc2) * strength / 2.0f;
+                                               
+                                               /* 2) Tag any point with overly low influence for removal in the next pass */
+                                               if (pt1->pressure < cull_thresh) {
+                                                       pt1->flag |= GP_SPOINT_TAG;
+                                                       do_cull = true;
+                                               }
+                                               if (pt2->pressure < cull_thresh) {
+                                                       pt2->flag |= GP_SPOINT_TAG;
+                                                       do_cull = true;
+                                               }
                                        }
                                }
                        }
                }
+               
+               /* Second Pass: Remove any points that are tagged */
+               if (do_cull) {
+                       gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG);
+               }
        }
 }
 
 /* erase strokes which fall under the eraser strokes */
 static void gp_stroke_doeraser(tGPsdata *p)
 {
-       bGPDframe *gpf = p->gpf;
+       bGPDlayer *gpl;
        bGPDstroke *gps, *gpn;
        rcti rect;
        
@@ -960,10 +923,32 @@ static void gp_stroke_doeraser(tGPsdata *p)
                }
        }
        
-       /* loop over strokes, checking segments for intersections */
-       for (gps = gpf->strokes.first; gps; gps = gpn) {
-               gpn = gps->next;
-               gp_stroke_eraser_dostroke(p, p->mval, p->mvalo, p->radius, &rect, gpf, gps);
+       /* loop over all layers too, since while it's easy to restrict editing to
+        * only a subset of layers, it is harder to perform the same erase operation
+        * on multiple layers...
+        */
+       for (gpl = p->gpd->layers.first; gpl; gpl = gpl->next) {
+               bGPDframe *gpf = gpl->actframe;
+               
+               /* only affect layer if it's editable (and visible) */
+               if (gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) {
+                       continue;
+               }
+               else if (gpf == NULL) {
+                       continue;
+               }
+               
+               /* loop over strokes, checking segments for intersections */
+               for (gps = gpf->strokes.first; gps; gps = gpn) {
+                       gpn = gps->next;
+                       
+                       /* Not all strokes in the datablock may be valid in the current editor/context
+                        * (e.g. 2D space strokes in the 3D view, if the same datablock is shared)
+                        */
+                       if (ED_gpencil_stroke_can_use_direct(p->sa, gps)) {
+                               gp_stroke_eraser_dostroke(p, gpl, gpf, gps, p->mval, p->mvalo, p->radius, &rect);
+                       }
+               }
        }
 }
 
@@ -1001,6 +986,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
        bGPdata **gpd_ptr = NULL;
        ScrArea *curarea = CTX_wm_area(C);
        ARegion *ar = CTX_wm_region(C);
+       ToolSettings *ts = CTX_data_tool_settings(C);
        
        /* make sure the active view (at the starting time) is a 3d-view */
        if (curarea == NULL) {
@@ -1030,6 +1016,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
                        /* CAUTION: If this is the "toolbar", then this will change on the first stroke */
                        p->sa = curarea;
                        p->ar = ar;
+                       p->align_flag = &ts->gpencil_v3d_align;
                        
                        if (ar->regiondata == NULL) {
                                p->status = GP_STATUS_ERROR;
@@ -1047,6 +1034,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
                        p->sa = curarea;
                        p->ar = ar;
                        p->v2d = &ar->v2d;
+                       p->align_flag = &ts->gpencil_v2d_align;
                        break;
                }
                case SPACE_SEQ:
@@ -1057,6 +1045,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
                        p->sa = curarea;
                        p->ar = ar;
                        p->v2d = &ar->v2d;
+                       p->align_flag = &ts->gpencil_seq_align;
                        
                        /* check that gpencil data is allowed to be drawn */
                        if (sseq->mainb == SEQ_DRAW_SEQUENCE) {
@@ -1075,6 +1064,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
                        p->sa = curarea;
                        p->ar = ar;
                        p->v2d = &ar->v2d;
+                       p->align_flag = &ts->gpencil_ima_align;
                        break;
                }
                case SPACE_CLIP:
@@ -1091,6 +1081,7 @@ static bool gp_session_initdata(bContext *C, tGPsdata *p)
                        p->sa = curarea;
                        p->ar = ar;
                        p->v2d = &ar->v2d;
+                       p->align_flag = &ts->gpencil_v2d_align;
                        
                        invert_m4_m4(p->imat, sc->unistabmat);
                        
@@ -1167,6 +1158,12 @@ static tGPsdata *gp_session_initpaint(bContext *C)
        
        gp_session_initdata(C, p);
        
+       /* radius for eraser circle is defined in userprefs now */
+       /* NOTE: we do this here, so that if we exit immediately,
+        *       erase size won't get lost
+        */
+       p->radius = U.gp_eraser;
+       
        /* return context data for running paint operator */
        return p;
 }
@@ -1194,8 +1191,10 @@ static void gp_session_cleanup(tGPsdata *p)
 }
 
 /* init new stroke */
-static void gp_paint_initstroke(tGPsdata *p, short paintmode)
+static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode)
 {
+       Scene *scene = p->scene;
+       
        /* get active layer (or add a new one if non-existent) */
        p->gpl = gpencil_layer_getactive(p->gpd);
        if (p->gpl == NULL) {
@@ -1212,15 +1211,61 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
        }
        
        /* get active frame (add a new one if not matching frame) */
-       p->gpf = gpencil_layer_getframe(p->gpl, p->scene->r.cfra, 1);
-       if (p->gpf == NULL) {
-               p->status = GP_STATUS_ERROR;
-               if (G.debug & G_DEBUG)
-                       printf("Error: No frame created (gpencil_paint_init)\n");
-               return;
+       if (paintmode == GP_PAINTMODE_ERASER) {
+               /* Eraser mode:
+                * 1) Add new frames to all frames that we might touch,
+                * 2) Ensure that p->gpf refers to the frame used for the active layer
+                *    (to avoid problems with other tools which expect it to exist)
+                */
+               bGPDlayer *gpl;
+               for (gpl = p->gpd->layers.first; gpl; gpl = gpl->next) {
+                       /* Skip if layer not editable */
+                       if (gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED))
+                               continue;
+                       
+                       /* Add a new frame if needed (and based off the active frame,
+                        * as we need some existing strokes to erase)
+                        */
+                       gpl->actframe = gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_COPY);
+                       
+                       /* XXX: we omit GP_FRAME_PAINT here for now,
+                        * as it is only really useful for doing
+                        * paintbuffer drawing
+                        */
+               }
+               
+               /* Ensure this gets set... */
+               p->gpf = p->gpl->actframe;
+               
+               if (p->gpf == NULL) {
+                       p->status = GP_STATUS_ERROR;
+                       //if (G.debug & G_DEBUG)
+                               printf("Error: No frame created (gpencil_paint_init)\n");
+                       return;
+               }
+       }
+       else {
+               /* Drawing Modes - Add a new frame if needed on the active layer */
+               ToolSettings *ts = p->scene->toolsettings;
+               short add_frame_mode;
+               
+               if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST)
+                       add_frame_mode = GP_GETFRAME_ADD_COPY;
+               else
+                       add_frame_mode = GP_GETFRAME_ADD_NEW;
+                       
+               p->gpf = gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode);
+               
+               if (p->gpf == NULL) {
+                       p->status = GP_STATUS_ERROR;
+                       if (G.debug & G_DEBUG)
+                               printf("Error: No frame created (gpencil_paint_init)\n");
+                       return;
+               }
+               else {
+                       p->gpf->flag |= GP_FRAME_PAINT;
+               }
        }
-       else
-               p->gpf->flag |= GP_FRAME_PAINT;
        
        /* set 'eraser' for this stroke if using eraser */
        p->paintmode = paintmode;
@@ -1251,7 +1296,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
        
        /* when drawing in the camera view, in 2D space, set the subrect */
        p->subrect = NULL;
-       if (!(p->gpd->flag & GP_DATA_VIEWALIGN)) {
+       if ((*p->align_flag & GP_PROJECT_VIEWSPACE) == 0) {
                if (p->sa->spacetype == SPACE_VIEW3D) {
                        View3D *v3d = p->sa->spacedata.first;
                        RegionView3D *rv3d = p->ar->regiondata;
@@ -1279,7 +1324,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
        
        
        /* check if points will need to be made in view-aligned space */
-       if (p->gpd->flag & GP_DATA_VIEWALIGN) {
+       if (*p->align_flag & GP_PROJECT_VIEWSPACE) {
                switch (p->sa->spacetype) {
                        case SPACE_VIEW3D:
                        {
@@ -1308,7 +1353,7 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
                                if (ELEM(NULL, sima, sima->image)) {
                                        /* make strokes be drawn in screen space */
                                        p->gpd->sbuffer_sflag &= ~GP_STROKE_2DSPACE;
-                                       p->gpd->flag &= ~GP_DATA_VIEWALIGN;
+                                       *(p->align_flag) &= ~GP_PROJECT_VIEWSPACE;
                                }
                                else {
                                        p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE;
@@ -1417,6 +1462,17 @@ static void gpencil_draw_toggle_eraser_cursor(bContext *C, tGPsdata *p, short en
        }
 }
 
+/* Check if tablet eraser is being used (when processing events) */
+static bool gpencil_is_tablet_eraser_active(const wmEvent *event)
+{
+       if (event->tablet_data) {
+               const wmTabletData *wmtab = event->tablet_data;
+               return (wmtab->Active == EVT_TABLET_ERASER);
+       }
+       
+       return false;
+}
+
 /* ------------------------------- */
 
 
@@ -1467,7 +1523,7 @@ static void gpencil_draw_cancel(bContext *C, wmOperator *op)
 static int gpencil_draw_init(bContext *C, wmOperator *op)
 {
        tGPsdata *p;
-       int paintmode = RNA_enum_get(op->ptr, "mode");
+       eGPencil_PaintModes paintmode = RNA_enum_get(op->ptr, "mode");
        
        /* check context */
        p = op->customdata = gp_session_initpaint(C);
@@ -1484,9 +1540,6 @@ static int gpencil_draw_init(bContext *C, wmOperator *op)
                return 0;
        }
        
-       /* radius for eraser circle is defined in userprefs now */
-       p->radius = U.gp_eraser;
-       
        /* everything is now setup ok */
        return 1;
 }
@@ -1529,6 +1582,10 @@ static void gpencil_draw_status_indicators(tGPsdata *p)
                                        ED_area_headerprint(p->sa, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw | "
                                                                          "ESC/Enter to end"));
                                        break;
+                               case GP_PAINTMODE_DRAW_POLY:
+                                       ED_area_headerprint(p->sa, IFACE_("Grease Pencil Poly Session: LMB click to place next stroke vertex | "
+                                                                         "ESC/Enter to end"));
+                                       break;
                                
                                default: /* unhandled future cases */
                                        ED_area_headerprint(p->sa, IFACE_("Grease Pencil Session: ESC/Enter to end"));
@@ -1622,9 +1679,6 @@ static void gpencil_draw_apply_event(wmOperator *op, const wmEvent *event)
                
                tablet = (wmtab->Active != EVT_TABLET_NONE);
                p->pressure = wmtab->Pressure;
-               
-               /* if (wmtab->Active == EVT_TABLET_ERASER) */
-               /* TODO... this should get caught by the keymaps which call drawing in the first place */
        }
        else
                p->pressure = 1.0f;
@@ -1819,7 +1873,6 @@ static tGPsdata *gpencil_stroke_begin(bContext *C, wmOperator *op)
        /* we may need to set up paint env again if we're resuming */
        /* XXX: watch it with the paintmode! in future,
         *      it'd be nice to allow changing paint-mode when in sketching-sessions */
-       /* XXX: with tablet events, we may event want to check for eraser here, for nicer tablet support */
        
        if (gp_session_initdata(C, p))
                gp_paint_initstroke(p, p->paintmode);
@@ -2008,14 +2061,14 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
                                /* Switch paintmode (temporarily if need be) based on which button was used
                                 * NOTE: This is to make it more convenient to erase strokes when using drawing sessions
                                 */
-                               if (event->type == LEFTMOUSE) {
-                                       /* restore drawmode to default */
-                                       p->paintmode = RNA_enum_get(op->ptr, "mode");
-                               }
-                               else if (event->type == RIGHTMOUSE) {
+                               if ((event->type == RIGHTMOUSE) || gpencil_is_tablet_eraser_active(event)) {
                                        /* turn on eraser */
                                        p->paintmode = GP_PAINTMODE_ERASER;
                                }
+                               else if (event->type == LEFTMOUSE) {
+                                       /* restore drawmode to default */
+                                       p->paintmode = RNA_enum_get(op->ptr, "mode");
+                               }
                                
                                gpencil_draw_toggle_eraser_cursor(C, p, p->paintmode == GP_PAINTMODE_ERASER);
                                
index 0dd91019a8cc461cdd85574b8c1bbc21275a7aec..83a1f2458ebb48e7e037de1b1830595292650390 100644 (file)
 static int gpencil_select_poll(bContext *C)
 {
        bGPdata *gpd = ED_gpencil_data_get_active(C);
-       bGPDlayer *gpl = gpencil_layer_getactive(gpd);
        
-       /* only if there's an active layer with an active frame */
-       return (gpl && gpl->actframe);
+       /* we just need some visible strokes, and to be in editmode */
+       if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) {
+               /* TODO: include a check for visible strokes? */
+               if (gpd->layers.first)
+                       return true;
+       }
+       
+       return false;
 }
 
 /* ********************************************** */
index 0d7aac7f48f82a96ba3dce11ac2179b867ef3365..36508751d0811aa6c773a40d1fa50aa51e79476e 100644 (file)
@@ -51,7 +51,9 @@
 
 #include "RNA_access.h"
 #include "RNA_define.h"
+#include "RNA_enum_types.h"
 
+#include "UI_resources.h"
 #include "UI_view2d.h"
 
 #include "ED_gpencil.h"
@@ -76,8 +78,6 @@ bGPdata **ED_gpencil_data_get_pointers_direct(ID *screen_id, Scene *scene, ScrAr
                
                switch (sa->spacetype) {
                        case SPACE_VIEW3D: /* 3D-View */
-                       case SPACE_TIME:   /* Timeline - XXX: this is a hack to get it to show GP keyframes for 3D view */
-                       case SPACE_ACTION: /* DepeSheet - XXX: this is a hack to get the keyframe jump operator to take GP Keyframes into account */
                        {
                                BLI_assert(scene && ELEM(scene->toolsettings->gpencil_src,
                                                         GP_TOOL_SOURCE_SCENE, GP_TOOL_SOURCE_OBJECT));
@@ -211,6 +211,45 @@ bGPdata *ED_gpencil_data_get_active_v3d(Scene *scene, View3D *v3d)
        return gpd ? gpd : scene->gpd;
 }
 
+/* ******************************************************** */
+/* Keyframe Indicator Checks */
+
+/* Check whether there's an active GP keyframe on the current frame */
+bool ED_gpencil_has_keyframe_v3d(Scene *scene, Object *ob, int cfra)
+{
+       /* just check both for now... */
+       // XXX: this could get confusing (e.g. if only on the object, but other places don't show this)
+       if (scene->gpd) {
+               bGPDlayer *gpl = gpencil_layer_getactive(scene->gpd);
+               if (gpl) {
+                       if (gpl->actframe) {
+                               // XXX: assumes that frame has been fetched already
+                               return (gpl->actframe->framenum == cfra);
+                       }
+                       else {
+                               /* XXX: disabled as could be too much of a penalty */
+                               /* return BKE_gpencil_layer_find_frame(gpl, cfra); */
+                       }
+               }
+       }
+       
+       if (ob && ob->gpd) {
+               bGPDlayer *gpl = gpencil_layer_getactive(ob->gpd);
+               if (gpl) {
+                       if (gpl->actframe) {
+                               // XXX: assumes that frame has been fetched already
+                               return (gpl->actframe->framenum == cfra);
+                       }
+                       else {
+                               /* XXX: disabled as could be too much of a penalty */
+                               /* return BKE_gpencil_layer_find_frame(gpl, cfra); */
+                       }
+               }
+       }
+       
+       return false;
+}
+
 /* ******************************************************** */
 /* Poll Callbacks */
 
@@ -230,6 +269,92 @@ int gp_active_layer_poll(bContext *C)
        return (gpl != NULL);
 }
 
+/* ******************************************************** */
+/* Dynamic Enums of GP Layers */
+/* NOTE: These include an option to create a new layer and use that... */
+
+/* Just existing layers */
+EnumPropertyItem *ED_gpencil_layers_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
+{
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       bGPDlayer *gpl;
+       EnumPropertyItem *item = NULL, item_tmp = {0};
+       int totitem = 0;
+       int i = 0;
+       
+       if (ELEM(NULL, C, gpd)) {
+               return DummyRNA_DEFAULT_items;
+       }
+       
+       /* Existing layers */
+       for (gpl = gpd->layers.first; gpl; gpl = gpl->next, i++) {
+               item_tmp.identifier = gpl->info;
+               item_tmp.name = gpl->info;
+               item_tmp.value = i;
+               
+               if (gpl->flag & GP_LAYER_ACTIVE)
+                       item_tmp.icon = ICON_GREASEPENCIL;
+               else 
+                       item_tmp.icon = ICON_NONE;
+               
+               RNA_enum_item_add(&item, &totitem, &item_tmp);
+       }
+       
+       RNA_enum_item_end(&item, &totitem);
+       *r_free = true;
+
+       return item;
+}
+
+/* Existing + Option to add/use new layer */
+EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
+{
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       bGPDlayer *gpl;
+       EnumPropertyItem *item = NULL, item_tmp = {0};
+       int totitem = 0;
+       int i = 0;
+       
+       if (ELEM(NULL, C, gpd)) {
+               return DummyRNA_DEFAULT_items;
+       }
+       
+       /* Create new layer */
+       /* TODO: have some way of specifying that we don't want this? */
+       {
+               /* active Keying Set */
+               item_tmp.identifier = "__CREATE__";
+               item_tmp.name = "New Layer";
+               item_tmp.value = -1;
+               item_tmp.icon = ICON_ZOOMIN;
+               RNA_enum_item_add(&item, &totitem, &item_tmp);
+               
+               /* separator */
+               RNA_enum_item_add_separator(&item, &totitem);
+       }
+       
+       /* Existing layers */
+       for (gpl = gpd->layers.first, i = 0; gpl; gpl = gpl->next, i++) {
+               item_tmp.identifier = gpl->info;
+               item_tmp.name = gpl->info;
+               item_tmp.value = i;
+               
+               if (gpl->flag & GP_LAYER_ACTIVE)
+                       item_tmp.icon = ICON_GREASEPENCIL;
+               else 
+                       item_tmp.icon = ICON_NONE;
+               
+               RNA_enum_item_add(&item, &totitem, &item_tmp);
+       }
+       
+       RNA_enum_item_end(&item, &totitem);
+       *r_free = true;
+
+       return item;
+}
+
+
+
 /* ******************************************************** */
 /* Brush Tool Core */
 
@@ -372,4 +497,37 @@ void gp_point_to_xy(GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
        }
 }
 
+/* Project screenspace coordinates to 3D-space
+ * NOTE: We include this as a utility function, since the standard method
+ *       involves quite a few steps, which are invariably always the same
+ *       for all GPencil operations. So, it's nicer to just centralise these.
+ * 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])
+{
+       View3D *v3d = gsc->sa->spacedata.first;
+       RegionView3D *rv3d = gsc->ar->regiondata;
+       float *rvec = ED_view3d_cursor3d_get(scene, v3d);
+       float ref[3] = {rvec[0], rvec[1], rvec[2]};
+       float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL);
+       
+       float mval_f[2], mval_prj[2];
+       float dvec[3];
+       
+       copy_v2_v2(mval_f, screen_co);
+       
+       if (ED_view3d_project_float_global(gsc->ar, ref, mval_prj, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
+               sub_v2_v2v2(mval_f, mval_prj, mval_f);
+               ED_view3d_win_to_delta(gsc->ar, mval_f, dvec, zfac);
+               sub_v3_v3v3(r_out, rvec, dvec);
+               
+               return true;
+       }
+       else {
+               zero_v3(r_out);
+               
+               return false;
+       }
+}
+
 /* ******************************************************** */
index 448f2c83aad45f7451cb869a8df4a72e619fe2a3..4761903150138f53c082fdb2026dbe7f7491fac4 100644 (file)
@@ -41,7 +41,9 @@ struct bGPdata;
 struct bGPDlayer;
 struct bGPDframe;
 struct bGPDstroke;
+struct bAnimContext;
 struct PointerRNA;
+struct wmWindowManager;
 struct wmKeyConfig;
 
 
@@ -77,6 +79,8 @@ struct bGPdata *ED_gpencil_data_get_active_direct(struct ID *screen_id, struct S
 /* 3D View */
 struct bGPdata  *ED_gpencil_data_get_active_v3d(struct Scene *scene, struct View3D *v3d);
 
+bool ED_gpencil_has_keyframe_v3d(struct Scene *scene, struct Object *ob, int cfra);
+
 /* ----------- Stroke Editing Utilities ---------------- */
 
 bool ED_gpencil_stroke_can_use_direct(const struct ScrArea *sa, const struct bGPDstroke *gps);
@@ -100,7 +104,7 @@ void ED_gpencil_strokes_copybuf_free(void);
 
 void ED_gpencil_draw_2dimage(const struct bContext *C);
 void ED_gpencil_draw_view2d(const struct bContext *C, bool onlyv2d);
-void ED_gpencil_draw_view3d(struct Scene *scene, struct View3D *v3d, struct ARegion *ar, bool only3d);
+void ED_gpencil_draw_view3d(struct wmWindowManager *wm, struct Scene *scene, struct View3D *v3d, struct ARegion *ar, bool only3d);
 void ED_gpencil_draw_ex(struct Scene *scene, struct bGPdata *gpd, int winx, int winy,
                         const int cfra, const char spacetype);
 
@@ -122,11 +126,11 @@ void ED_gplayer_frames_keytype_set(struct bGPDlayer *gpl, short type);
 
 void  ED_gplayer_snap_frames(struct bGPDlayer *gpl, struct Scene *scene, short mode);
 
-#if 0
-void free_gpcopybuf(void);
-void copy_gpdata(void);
-void paste_gpdata(void);
+void ED_gpencil_anim_copybuf_free(void);
+bool ED_gpencil_anim_copybuf_copy(struct bAnimContext *ac);
+bool ED_gpencil_anim_copybuf_paste(struct bAnimContext *ac, const short copy_mode);
 
+#if 0
 void mirror_gplayer_frames(struct bGPDlayer *gpl, short mode);
 #endif
 
index fb4065bf5de254daa836b6f3cc48ff95d09187ff..64a69542e3828e53ddaeb2045f32f711b9ff1ddc 100644 (file)
@@ -46,6 +46,7 @@
 
 #include "DNA_armature_types.h"
 #include "DNA_curve_types.h"
+#include "DNA_gpencil_types.h"
 #include "DNA_group_types.h"
 #include "DNA_material_types.h"
 #include "DNA_meta_types.h"
@@ -1509,6 +1510,7 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED(
        EnumPropertyItem *input = rna_enum_object_mode_items;
        EnumPropertyItem *item = NULL;
        Object *ob;
+       bGPdata *gpd;
        int totitem = 0;
 
        if (!C) /* needed for docs */
@@ -1536,6 +1538,14 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED(
                /* We need at least this one! */
                RNA_enum_items_add_value(&item, &totitem, input, OB_MODE_OBJECT);
        }
+       
+       /* On top of all the rest, GPencil Stroke Edit Mode
+        * is available if there's a valid gp datablock...
+        */
+       gpd = CTX_data_gpencil_data(C);
+       if (gpd) {
+               RNA_enum_items_add_value(&item, &totitem, rna_enum_object_mode_items, OB_MODE_GPENCIL);
+       }
 
        RNA_enum_item_end(&item, &totitem);
 
@@ -1560,6 +1570,8 @@ static const char *object_mode_op_string(int mode)
                return "PARTICLE_OT_particle_edit_toggle";
        if (mode == OB_MODE_POSE)
                return "OBJECT_OT_posemode_toggle";
+       if (mode == OB_MODE_GPENCIL)
+               return "GPENCIL_OT_editmode_toggle";
        return NULL;
 }
 
@@ -1571,6 +1583,8 @@ static bool object_mode_compat_test(Object *ob, ObjectMode mode)
        if (ob) {
                if (mode == OB_MODE_OBJECT)
                        return true;
+               else if (mode == OB_MODE_GPENCIL)
+                       return true; /* XXX: assume this is the case for now... */
 
                switch (ob->type) {
                        case OB_MESH:
@@ -1625,13 +1639,45 @@ bool ED_object_mode_compat_set(bContext *C, Object *ob, int mode, ReportList *re
        return ok;
 }
 
+static int object_mode_set_poll(bContext *C)
+{
+       /* Since Grease Pencil editmode is also handled here,
+        * we have a special exception for allowing this operator
+        * to still work in that case when there's no active object
+        * so that users can exit editmode this way as per normal.
+        */
+       if (ED_operator_object_active_editable(C))
+               return true;
+       else
+               return (CTX_data_gpencil_data(C) != NULL);
+}
+
 static int object_mode_set_exec(bContext *C, wmOperator *op)
 {
        Object *ob = CTX_data_active_object(C);
+       bGPdata *gpd = CTX_data_gpencil_data(C);
        ObjectMode mode = RNA_enum_get(op->ptr, "mode");
        ObjectMode restore_mode = (ob) ? ob->mode : OB_MODE_OBJECT;
        const bool toggle = RNA_boolean_get(op->ptr, "toggle");
-
+       
+       if (gpd) {
+               /* GP Mode is not bound to a specific object. Therefore,
+                * we don't want it to be actually saved on any objects,
+                * as weirdness can happen if you select other objects,
+                * or load old files.
+                *
+                * Instead, we use the following 2 rules to ensure that
+                * the mode selector works as expected:
+                *  1) If there's no object, we want to enter editmode.
+                *     (i.e. with no object, we're in object mode)
+                *  2) Otherwise, exit stroke editmode, so that we can
+                *     enter another mode...
+                */
+               if (!ob || (gpd->flag & GP_DATA_STROKE_EDITMODE)) {
+                       WM_operator_name_call(C, "GPENCIL_OT_editmode_toggle", WM_OP_EXEC_REGION_WIN, NULL);
+               }
+       }
+       
        if (!ob || !object_mode_compat_test(ob, mode))
                return OPERATOR_PASS_THROUGH;
 
@@ -1675,7 +1721,7 @@ void OBJECT_OT_mode_set(wmOperatorType *ot)
        /* api callbacks */
        ot->exec = object_mode_set_exec;
        
-       ot->poll = ED_operator_object_active_editable;
+       ot->poll = object_mode_set_poll; //ED_operator_object_active_editable;
        
        /* flags */
        ot->flag = 0; /* no register/undo here, leave it to operators being called */
index 7c1ff0db2a975a3df9f829430b23e3aafdbebed9..7806038b313db11fd42028fe3fa6e50f53b2d5a3 100644 (file)
@@ -2204,7 +2204,6 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
        Main *bmain = CTX_data_main(C);
        Scene *scene = CTX_data_scene(C);
        Object *ob = CTX_data_active_object(C);
-       bGPdata *gpd = CTX_data_gpencil_data(C);
        bDopeSheet ads = {NULL};
        DLRBT_Tree keys;
        ActKeyColumn *ak;
@@ -2229,11 +2228,12 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
        
        /* populate tree with keyframe nodes */
        scene_to_keylist(&ads, scene, &keys, NULL);
+       gpencil_to_keylist(&ads, scene->gpd, &keys);
 
-       if (ob)
+       if (ob) {
                ob_to_keylist(&ads, ob, &keys, NULL);
-       
-       gpencil_to_keylist(&ads, gpd, &keys);
+               gpencil_to_keylist(&ads, ob->gpd, &keys);
+       }
        
        {
                Mask *mask = CTX_data_edit_mask(C);
index 0dd5c7ca77515fd55f97d3b60fb5bf90d90bfb0d..ce0db9e55233caaca1715a9f7406370a7e7a7625 100644 (file)
@@ -549,9 +549,10 @@ static int actkeys_copy_exec(bContext *C, wmOperator *op)
 
        /* copy keyframes */
        if (ac.datatype == ANIMCONT_GPENCIL) {
-               /* FIXME... */
-               BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for grease pencil mode");
-               return OPERATOR_CANCELLED;
+               if (ED_gpencil_anim_copybuf_copy(&ac) == false) {
+                       /* Nothing got copied - An error about this should be been logged already */
+                       return OPERATOR_CANCELLED;
+               }
        }
        else if (ac.datatype == ANIMCONT_MASK) {
                /* FIXME... */
@@ -599,7 +600,13 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op)
        ac.reports = op->reports;
        
        /* paste keyframes */
-       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
+       if (ac.datatype == ANIMCONT_GPENCIL) {
+               if (ED_gpencil_anim_copybuf_paste(&ac, offset_mode) == false) {
+                       /* An error occurred - Reports should have been fired already */
+                       return OPERATOR_CANCELLED;
+               }
+       }
+       else if (ac.datatype == ANIMCONT_MASK) {
                /* FIXME... */
                BKE_report(op->reports, RPT_ERROR, "Keyframe pasting is not available for grease pencil or mask mode");
                return OPERATOR_CANCELLED;
index c5dfc123f398a307912ccb184930c5ea5c053831..4c64c52ba2b68fc3d4c6afd364be38a2f9bbce0b 100644 (file)
@@ -349,14 +349,16 @@ static void time_draw_keyframes(const bContext *C, ARegion *ar)
 {
        Scene *scene = CTX_data_scene(C);
        Object *ob = CTX_data_active_object(C);
-       bGPdata *gpd = CTX_data_gpencil_data(C);
        View2D *v2d = &ar->v2d;
        bool onlysel = ((scene->flag & SCE_KEYS_NO_SELONLY) == 0);
        
-       /* draw grease pencil keyframes (if available) */
-       if (gpd) {
-               UI_ThemeColor(TH_TIME_GP_KEYFRAME);
-               time_draw_idblock_keyframes(v2d, (ID *)gpd, onlysel);
+       /* draw grease pencil keyframes (if available) */       
+       UI_ThemeColor(TH_TIME_GP_KEYFRAME);
+       if (scene->gpd) {
+               time_draw_idblock_keyframes(v2d, (ID *)scene->gpd, onlysel);
+       }
+       if (ob && ob->gpd) {
+               time_draw_idblock_keyframes(v2d, (ID *)ob->gpd, onlysel);
        }
        
        /* draw scene keyframes first 
index 5a839c25e05e7cbb1f59ba5426606e9a1ef34316..e7223ddf065ae8310e18b7cae264bdc4f7d9d242 100644 (file)
@@ -941,6 +941,8 @@ static void draw_selected_name(Scene *scene, Object *ob, rcti *rect)
                /* color depends on whether there is a keyframe */
                if (id_frame_has_keyframe((ID *)ob, /* BKE_scene_frame_get(scene) */ (float)cfra, ANIMFILTER_KEYS_LOCAL))
                        UI_ThemeColor(TH_VERTEX_SELECT);
+               else if (ED_gpencil_has_keyframe_v3d(scene, ob, cfra))
+                       UI_ThemeColor(TH_CFRAME); // XXX
                else
                        UI_ThemeColor(TH_TEXT_HI);
        }
@@ -2349,7 +2351,7 @@ void ED_view3d_draw_depth_gpencil(Scene *scene, ARegion *ar, View3D *v3d)
        glEnable(GL_DEPTH_TEST);
 
        if (v3d->flag2 & V3D_SHOW_GPENCIL) {
-               ED_gpencil_draw_view3d(scene, v3d, ar, true);
+               ED_gpencil_draw_view3d(NULL, scene, v3d, ar, true);
        }
        
        v3d->zbuf = zbuf;
@@ -2853,9 +2855,11 @@ static void view3d_draw_objects(
 
        /* must be before xray draw which clears the depth buffer */
        if (v3d->flag2 & V3D_SHOW_GPENCIL) {
+               wmWindowManager *wm = (C != NULL) ? CTX_wm_manager(C) : NULL;
+               
                /* must be before xray draw which clears the depth buffer */
                if (v3d->zbuf) glDisable(GL_DEPTH_TEST);
-               ED_gpencil_draw_view3d(scene, v3d, ar, true);
+               ED_gpencil_draw_view3d(wm, scene, v3d, ar, true);
                if (v3d->zbuf) glEnable(GL_DEPTH_TEST);
        }
 
@@ -3266,7 +3270,7 @@ void ED_view3d_draw_offscreen(
 
                if (v3d->flag2 & V3D_SHOW_GPENCIL) {
                        /* draw grease-pencil stuff - needed to get paint-buffer shown too (since it's 2D) */
-                       ED_gpencil_draw_view3d(scene, v3d, ar, false);
+                       ED_gpencil_draw_view3d(NULL, scene, v3d, ar, false);
                }
 
                /* freeing the images again here could be done after the operator runs, leaving for now */
@@ -3481,6 +3485,9 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(
 
        if (use_solid_tex)
                v3d.flag2 |= V3D_SOLID_TEX;
+               
+       if (draw_background)
+               v3d.flag3 |= V3D_SHOW_WORLD;
 
        rv3d.persp = RV3D_CAMOB;
 
@@ -3949,6 +3956,7 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene,
                                        ARegion *ar, View3D *v3d,
                                        const char *grid_unit, bool render_border)
 {
+       wmWindowManager *wm = CTX_wm_manager(C);
        RegionView3D *rv3d = ar->regiondata;
        rcti rect;
        
@@ -3972,7 +3980,7 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene,
 
        if (v3d->flag2 & V3D_SHOW_GPENCIL) {
                /* draw grease-pencil stuff - needed to get paint-buffer shown too (since it's 2D) */
-               ED_gpencil_draw_view3d(scene, v3d, ar, false);
+               ED_gpencil_draw_view3d(wm, scene, v3d, ar, false);
        }
 
        if ((v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) {
@@ -3999,8 +4007,6 @@ static void view3d_main_region_draw_info(const bContext *C, Scene *scene,
        }
 
        if ((v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) {
-               wmWindowManager *wm = CTX_wm_manager(C);
-
                if ((U.uiflag & USER_SHOW_FPS) && ED_screen_animation_no_scrub(wm)) {
                        ED_scene_draw_fps(scene, &rect);
                }
index f5247fff4a7e16737f3e7743998d99bc5ec3f88b..0713377d210e2dfc9d3396523acd25e30db4774f 100644 (file)
@@ -34,6 +34,7 @@
 
 #include "DNA_scene_types.h"
 #include "DNA_object_types.h"
+#include "DNA_gpencil_types.h"
 
 #include "BLI_utildefines.h"
 
@@ -287,6 +288,7 @@ void uiTemplateHeader3D(uiLayout *layout, struct bContext *C)
        PointerRNA v3dptr, toolsptr, sceneptr;
        Object *ob = OBACT;
        Object *obedit = CTX_data_edit_object(C);
+       bGPdata *gpd = CTX_data_gpencil_data(C);
        uiBlock *block;
        uiLayout *row;
        bool is_paint = false;
@@ -303,7 +305,10 @@ void uiTemplateHeader3D(uiLayout *layout, struct bContext *C)
        UI_block_emboss_set(block, UI_EMBOSS);
        
        /* mode */
-       if (ob) {
+       if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) {
+               modeselect = OB_MODE_GPENCIL;
+       }
+       else if (ob) {
                modeselect = ob->mode;
                is_paint = ELEM(ob->mode, OB_MODE_SCULPT, OB_MODE_VERTEX_PAINT, OB_MODE_WEIGHT_PAINT, OB_MODE_TEXTURE_PAINT);
        }
index 16642dec5b0ddf8f96245f1bbb1cffabe5a7b472..dc0b153d6e90af98aa5a851b2553a06ceadc2826 100644 (file)
@@ -7767,45 +7767,7 @@ static void createTransGPencil(bContext *C, TransInfo *t)
                         */
                        // XXX: should this be allowed when framelock is enabled?
                        if (gpf->framenum != cfra) {
-                               bGPDframe *new_frame = gpencil_frame_duplicate(gpf);
-                               bGPDframe *gf;
-                               bool found = false;
-                               
-                               /* Find frame to insert it before */
-                               for (gf = gpf->next; gf; gf = gf->next) {
-                                       if (gf->framenum > cfra) {
-                                               /* Add it here */
-                                               BLI_insertlinkbefore(&gpl->frames, gf, new_frame);
-                                               
-                                               found = true;
-                                               break;
-                                       }
-                                       else if (gf->framenum == cfra) {
-                                               /* This only happens when we're editing with framelock on...
-                                                * - Delete the new frame and don't do anything else here...
-                                                */
-                                               //printf("GP Frame convert to TransData - Copy aborted for frame %d -> %d\n", gpf->framenum, gf->framenum);
-                                               free_gpencil_strokes(new_frame);
-                                               MEM_freeN(new_frame);
-                                               new_frame = NULL;
-                                               
-                                               found = true;
-                                               break;
-                                       }
-                               }
-                               
-                               if (found == false) {
-                                       /* Add new frame to the end */
-                                       BLI_addtail(&gpl->frames, new_frame);
-                               }
-                               
-                               /* Edit the new frame instead, if it did get created + added */
-                               if (new_frame) {
-                                       // TODO: tag this one as being "newly created" so that we can remove it if the edit is cancelled
-                                       new_frame->framenum = cfra;
-                                       
-                                       gpf = new_frame;
-                               }
+                               gpf = gpencil_frame_addcopy(gpl, cfra);
                        }
                        
                        /* Loop over strokes, adding TransData for points as needed... */
@@ -7930,7 +7892,8 @@ void createTransData(bContext *C, TransInfo *t)
                }
        }
        else if (t->options & CTX_GPENCIL_STROKES) {
-               t->flag |= T_POINTS; // XXX...
+               t->options |= CTX_GPENCIL_STROKES;
+               t->flag |= T_POINTS;
                createTransGPencil(C, t);
                
                if (t->data && (t->flag & T_PROP_EDIT)) {
index 3b40a55a27003cbbfa5580d7a7e6eecc4975b1e6..84087ec884097aef89f00e38a8d82b4ee745dc1d 100644 (file)
@@ -39,6 +39,7 @@
 #include "DNA_anim_types.h"
 #include "DNA_armature_types.h"
 #include "DNA_brush_types.h"
+#include "DNA_gpencil_types.h"
 #include "DNA_lattice_types.h"
 #include "DNA_screen_types.h"
 #include "DNA_sequence_types.h"
@@ -1095,6 +1096,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
        ScrArea *sa = CTX_wm_area(C);
        Object *obedit = CTX_data_edit_object(C);
        Object *ob = CTX_data_active_object(C);
+       bGPdata *gpd = CTX_data_gpencil_data(C);
        PropertyRNA *prop;
        
        t->scene = sce;
@@ -1164,6 +1166,11 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
                        t->remove_on_cancel = true;
                }
        }
+       
+       /* GPencil editing context */
+       if ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE)) {
+               t->options |= CTX_GPENCIL_STROKES;
+       }
 
        /* Assign the space type, some exceptions for running in different mode */
        if (sa == NULL) {
index 8131758f93cbc52ba96a3dc8518e7a106a903d7f..74d27fb3068282284440433691349de559fa6321 100644 (file)
 
 #include "DNA_armature_types.h"
 #include "DNA_curve_types.h"
+#include "DNA_gpencil_types.h"
 #include "DNA_lattice_types.h"
 #include "DNA_meta_types.h"
 #include "DNA_screen_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_view3d_types.h"
 
+#include "BLI_listbase.h"
 #include "BLI_math.h"
 #include "BLI_utildefines.h"
 
@@ -272,6 +274,8 @@ static int calc_manipulator_stats(const bContext *C)
        RegionView3D *rv3d = ar->regiondata;
        Base *base;
        Object *ob = OBACT;
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       const bool is_gp_edit = ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE));
        int a, totsel = 0;
 
        /* transform widget matrix */
@@ -282,8 +286,32 @@ static int calc_manipulator_stats(const bContext *C)
        /* transform widget centroid/center */
        INIT_MINMAX(scene->twmin, scene->twmax);
        zero_v3(scene->twcent);
-
-       if (obedit) {
+       
+       if (is_gp_edit) {
+               CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+               {
+                       /* we're only interested in selected points here... */
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               bGPDspoint *pt;
+                               int i;
+                               
+                               /* Change selection status of all points, then make the stroke match */
+                               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                       if (pt->flag & GP_SPOINT_SELECT) {
+                                               calc_tw_center(scene, &pt->x);
+                                               totsel++;
+                                       }
+                               }
+                       }
+               }
+               CTX_DATA_END;
+               
+               /* selection center */
+               if (totsel) {
+                       mul_v3_fl(scene->twcent, 1.0f / (float)totsel);   /* centroid! */
+               }
+       }
+       else if (obedit) {
                ob = obedit;
                if ((ob->lay & v3d->lay) == 0) return 0;
 
@@ -546,7 +574,7 @@ static int calc_manipulator_stats(const bContext *C)
        }
 
        /* global, local or normal orientation? */
-       if (ob && totsel) {
+       if (ob && totsel && !is_gp_edit) {
 
                switch (v3d->twmode) {
                
@@ -1595,9 +1623,12 @@ void BIF_draw_manipulator(const bContext *C)
                        case V3D_AROUND_CENTER_BOUNDS:
                        case V3D_AROUND_ACTIVE:
                        {
-                               Object *ob;
+                               bGPdata *gpd = CTX_data_gpencil_data(C);
+                               Object *ob = OBACT;
+                               
                                if (((v3d->around == V3D_AROUND_ACTIVE) && (scene->obedit == NULL)) &&
-                                   ((ob = OBACT) && !(ob->mode & OB_MODE_POSE)))
+                                   ((gpd == NULL) || !(gpd->flag & GP_DATA_STROKE_EDITMODE)) &&
+                                   (!(ob->mode & OB_MODE_POSE)))
                                {
                                        copy_v3_v3(rv3d->twmat[3], ob->obmat[3]);
                                }
index d574694c70dfd73d7f8c6e4271544b1379fa57b0..9c17a1f4f5bbbf42052b8cb33ba60579666d95f5 100644 (file)
@@ -596,6 +596,9 @@ typedef enum eDopeSheet_FilterFlag {
        ADS_FILTER_BY_FCU_NAME      = (1 << 27),  /* for F-Curves, filter by the displayed name (i.e. to isolate all Location curves only) */
        ADS_FILTER_ONLY_ERRORS          = (1 << 28),  /* show only F-Curves which are disabled/have errors - for debugging drivers */
        
+       /* GPencil Mode */
+       ADS_FILTER_GP_3DONLY        = (1 << 29),  /* GP Mode - Only show datablocks used in the scene */
+       
        /* combination filters (some only used at runtime) */
        ADS_FILTER_NOOBDATA = (ADS_FILTER_NOCAM | ADS_FILTER_NOMAT | ADS_FILTER_NOLAM | ADS_FILTER_NOCUR | ADS_FILTER_NOPART | ADS_FILTER_NOARM | ADS_FILTER_NOSPK | ADS_FILTER_NOMODIFIERS)
 } eDopeSheet_FilterFlag;       
index beffbc4c01783d89e6d58160a74ddce336562000..aa98ddb11aee6bcc69d95b567d2d022e0670dbc7 100644 (file)
@@ -51,7 +51,10 @@ typedef struct bGPDspoint {
 /* bGPDspoint->flag */
 typedef enum eGPDspoint_Flag {
        /* stroke point is selected (for editing) */
-       GP_SPOINT_SELECT        = (1 << 0)
+       GP_SPOINT_SELECT        = (1 << 0),
+       
+       /* stroke point is tagged (for some editing operation) */
+       GP_SPOINT_TAG       = (1 << 1),
 } eGPSPoint_Flag;
 
 /* Grease-Pencil Annotations - 'Stroke'
@@ -190,6 +193,7 @@ typedef enum eGPdata_Flag {
        /* is the block overriding all clicks? */
        /* GP_DATA_EDITPAINT = (1 << 3), */
        
+/* ------------------------------------------------ DEPRECATED */
        /* new strokes are added in viewport space */
        GP_DATA_VIEWALIGN       = (1 << 4),
        
@@ -198,9 +202,13 @@ typedef enum eGPdata_Flag {
        GP_DATA_DEPTH_STROKE = (1 << 6),
 
        GP_DATA_DEPTH_STROKE_ENDPOINTS = (1 << 7),
+/* ------------------------------------------------ DEPRECATED */
        
        /* Stroke Editing Mode - Toggle to enable alternative keymap for easier editing of stroke points */
-       GP_DATA_STROKE_EDITMODE = (1 << 8)
+       GP_DATA_STROKE_EDITMODE = (1 << 8),
+       
+       /* Convenience/cache flag to make it easier to quickly toggle onion skinning on/off */
+       GP_DATA_SHOW_ONIONSKINS = (1 << 9)
 } eGPdata_Flag;
 
 #endif /*  __DNA_GPENCIL_TYPES_H__ */