Grease Pencil - Storyboarding Features (merge from GPencil_EditStrokes branch)
authorJoshua Leung <aligorith@gmail.com>
Sun, 30 Nov 2014 12:52:06 +0000 (01:52 +1300)
committerJoshua Leung <aligorith@gmail.com>
Sun, 30 Nov 2014 12:52:06 +0000 (01:52 +1300)
This merge-commit brings in a number of new features and workflow/UI improvements for
working with Grease Pencil. While these were originally targetted at improving
the workflow for creating 3D storyboards in Blender using the Grease Pencil,
many of these changes should also prove useful in other workflows too.

The main highlights here are:
1) It is now possible to edit Grease Pencil strokes
   - Use D Tab, or toggle the "Enable Editing" toggles in the Toolbar/Properties regions
     to enter "Stroke Edit Mode". In this mode, many common editing tools will
     operate on Grease Pencil stroke points instead.
   - Tools implemented include Select, Select All/Border/Circle/Linked/More/Less,
     Grab, Rotate, Scale, Bend, Shear, To Sphere, Mirror, Duplicate, Delete.
   - Proportional Editing works when using the transform tools

2) Grease Pencil stroke settings can now be animated
   NOTE: Currently drivers don't work, but if time allows, this may still be
         added before the release.

3) Strokes can be drawn with "filled" interiors, using a separate set of
   colour/opacity settings to the ones used for the lines themselves.

   This makes use of OpenGL filled polys, which has the limitation of only
   being able to fill convex shapes. Some artifacts may be visible on concave
   shapes (e.g. pacman's mouth will be overdrawn)

4) "Volumetric Strokes" - An alternative drawing technique for stroke drawing
   has been added which draws strokes as a series of screen-aligned discs.

   While this was originally a partial experimental technique at getting better
   quality 3D lines, the effects possible using this technique were interesting
   enough to warrant making this a dedicated feature. Best results when partial
   opacity and large stroke widths are used.

5) Improved Onion Skinning Support
   - Different colours can be selected for the before/after ghosts. To do so,
     enable the "colour wheel" toggle beside the Onion Skinning toggle, and set
     the colours accordingly.
   - Different numbers of ghosts can be shown before/after the current frame

6) Grease Pencil datablocks are now attached to the scene by default instead of
   the active object.
   - For a long time, the object-attachment has proved to be quite problematic
     for users to keep track of. Now that this is done at scene level, it is
     easier for most users to use.
   - An exception for old files (and for any addons which may benefit from object
     attachment instead), is that if the active object has a Grease Pencil datablock,
     that will be used instead.
   - It is not currently possible to choose object-attachment from the UI, but
     it is simple to do this from the console instead, by doing:
     context.active_object.grease_pencil = bpy.data.grease_pencil["blah"]

7) Various UI Cleanups
   - The layers UI has been cleaned up to use a list instead of the nested-panels
     design. Apart from saving space, this is also much nicer to look at now.

   - The UI code is now all defined in Python. To support this, it has been necessary
     to add some new context properties to make it easier to access these settings.
     e.g. "gpencil_data" for the datablock
          "active_gpencil_layer" and "active_gpencil_frame" for active data,
          "editable_gpencil_strokes" for the strokes that can be edited

   - The "stroke placement/alignment" settings (previously "Drawing Settings" at the
     bottom of the Grease Pencil panel in the Properties Region) is now located in
     the toolbar. These were more toolsettings than properties for how GPencil got drawn.

   - "Use Sketching Sessions" has been renamed "Continuous Drawing", as per a
     suggestion for an earlier discussion on developer.blender.org

   - By default, the painting operator will wait for a mouse button to be pressed
     before it starts creating the stroke. This is to make it easier to include
     this operator in various toolbars/menus/etc.   To get it immediately starting
     (as when you hold down DKEy to draw), set "wait_for_input" to False.

   - GPencil Layers can be rearranged in the "Grease Pencil" mode of the Action Editor

   - Toolbar panels have been added to all the other editors which support these.

8) Pie menus for quick-access to tools
   A set of experimental pie menus has been included for quick access to many
   tools and settings. It is not necessary to use these to get things done,
   but they have been designed to help make certain common tasks easier.

   - Ctrl-D = The main pie menu. Reveals tools in a context sensitive and
              spatially stable manner.
   - D Q    = "Quick Settings" pie. This allows quick access to the active
              layer's settings. Notably, colours, thickness, and turning
              onion skinning on/off.

69 files changed:
release/scripts/modules/bpy_extras/keyconfig_utils.py
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_sequencer.py
release/scripts/startup/bl_ui/space_view3d.py
release/scripts/startup/bl_ui/space_view3d_toolbar.py
source/blender/blenkernel/BKE_context.h
source/blender/blenkernel/BKE_gpencil.h
source/blender/blenkernel/intern/anim_sys.c
source/blender/blenkernel/intern/context.c
source/blender/blenkernel/intern/gpencil.c
source/blender/blenloader/intern/readfile.c
source/blender/blenloader/intern/writefile.c
source/blender/editors/animation/anim_channels_defines.c
source/blender/editors/animation/anim_channels_edit.c
source/blender/editors/animation/anim_filter.c
source/blender/editors/animation/keyframes_draw.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_buttons.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 [new file with mode: 0644]
source/blender/editors/gpencil/gpencil_undo.c
source/blender/editors/gpencil/gpencil_utils.c [new file with mode: 0644]
source/blender/editors/include/ED_anim_api.h
source/blender/editors/include/ED_gpencil.h
source/blender/editors/include/ED_keyframes_draw.h
source/blender/editors/include/ED_transform.h
source/blender/editors/render/render_opengl.c
source/blender/editors/screen/area.c
source/blender/editors/screen/screen_context.c
source/blender/editors/screen/screen_ops.c
source/blender/editors/space_action/action_edit.c
source/blender/editors/space_api/spacetypes.c
source/blender/editors/space_clip/clip_buttons.c
source/blender/editors/space_clip/space_clip.c
source/blender/editors/space_image/image_buttons.c
source/blender/editors/space_nla/nla_buttons.c
source/blender/editors/space_nla/nla_channels.c
source/blender/editors/space_node/node_buttons.c
source/blender/editors/space_node/space_node.c
source/blender/editors/space_sequencer/sequencer_buttons.c
source/blender/editors/space_sequencer/space_sequencer.c
source/blender/editors/space_time/space_time.c
source/blender/editors/space_view3d/space_view3d.c
source/blender/editors/space_view3d/view3d_buttons.c
source/blender/editors/transform/transform.c
source/blender/editors/transform/transform_conversions.c
source/blender/editors/transform/transform_generics.c
source/blender/editors/transform/transform_ops.c
source/blender/makesdna/DNA_action_types.h
source/blender/makesdna/DNA_gpencil_types.h
source/blender/makesrna/intern/rna_action.c
source/blender/makesrna/intern/rna_gpencil.c
source/blender/makesrna/intern/rna_movieclip.c
source/blender/makesrna/intern/rna_nodetree.c
source/blender/makesrna/intern/rna_object.c
source/blender/makesrna/intern/rna_scene.c
source/blender/makesrna/intern/rna_space.c
source/blender/makesrna/intern/rna_tracking.c
source/blender/windowmanager/WM_types.h
source/blender/windowmanager/intern/wm_operators.c

index c50b320dcebe6cbd9b763be129352e419ed96e25..1903f3c0e09628ecaca989390e6cd4bb13f5fa16 100644 (file)
@@ -32,7 +32,10 @@ KM_HIERARCHY = [
     ('View2D', 'EMPTY', 'WINDOW', []),    # view 2d navigation (per region)
     ('View2D Buttons List', 'EMPTY', 'WINDOW', []),  # view 2d with buttons navigation
     ('Header', 'EMPTY', 'WINDOW', []),    # header stuff (per region)
-    ('Grease Pencil', 'EMPTY', 'WINDOW', []),  # grease pencil stuff (per region)
+
+    ('Grease Pencil', 'EMPTY', 'WINDOW', [ # grease pencil stuff (per region)
+        ('Grease Pencil Stroke Edit Mode', 'EMPTY', 'WINDOW', []),
+        ]),
 
     ('3D View', 'VIEW_3D', 'WINDOW', [  # view 3d navigation and generic stuff (select, transform)
         ('Object Mode', 'EMPTY', 'WINDOW', []),
index 4789d1191924170569249bf3e364a1e35abf2d43..789d0a10a9e11fa32b94aab5572c06f6c61a9700 100644 (file)
 # <pep8 compliant>
 
 
-class GreasePencilPanel():
+import bpy
+from bpy.types import Menu, UIList
+
+
+def gpencil_stroke_placement_settings(context, layout, gpd):
+    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')
+
+    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 = col.row(align=False)
+        row.active = gpd.draw_mode in ('SURFACE', 'STROKE')
+        row.prop(gpd, "use_stroke_endpoints")
+
+
+class GreasePencilDrawingToolsPanel():
     # subclass must set
     # bl_space_type = 'IMAGE_EDITOR'
-    # bl_region_type = 'TOOLS'
     bl_label = "Grease Pencil"
+    bl_category = "Grease Pencil"
+    bl_region_type = 'TOOLS'
 
     @staticmethod
     def draw(self, context):
@@ -31,19 +55,413 @@ class GreasePencilPanel():
 
         col = layout.column(align=True)
 
+        col.label(text="Draw:")
         row = col.row(align=True)
         row.operator("gpencil.draw", text="Draw").mode = 'DRAW'
-        row.operator("gpencil.draw", text="Line").mode = 'DRAW_STRAIGHT'
+        row.operator("gpencil.draw", text="Erase").mode = 'ERASER'
 
         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", text="Erase").mode = 'ERASER'
+
 
         row = col.row(align=True)
-        row.prop(context.tool_settings, "use_grease_pencil_sessions")
+        row.prop(context.tool_settings, "use_grease_pencil_sessions", text="Continuous Drawing")
+
+        gpd = context.gpencil_data
+        if gpd:
+            col.separator()
+            gpencil_stroke_placement_settings(context, col, gpd)
+
 
         if context.space_data.type == 'VIEW_3D':
             col.separator()
+            col.separator()
 
-            col.label(text="Measure:")
+            col.label(text="Tools:")
+            col.operator("gpencil.convert", text="Convert...")
             col.operator("view3d.ruler")
+
+
+class GreasePencilStrokeEditPanel():
+    # subclass must set
+    # bl_space_type = 'IMAGE_EDITOR'
+    bl_label = "Edit Strokes"
+    bl_category = "Grease Pencil"
+    bl_region_type = 'TOOLS'
+
+    @classmethod
+    def poll(cls, context):
+        return (context.gpencil_data is not None)
+
+    @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)
+
+        col = layout.column(align=True)
+        col.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
+
+        col.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")
+
+        col.separator()
+
+        subcol = col.column(align=True)
+        subcol.active = edit_ok
+        subcol.operator("gpencil.select_linked")
+        subcol.operator("gpencil.select_more")
+        subcol.operator("gpencil.select_less")
+
+        col.separator()
+
+        col.label(text="Edit:")
+        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.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'
+
+        col.separator()
+
+        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
+
+
+###############################
+
+class GPENCIL_PIE_tool_palette(Menu):
+    """A pie menu for quick access to Grease Pencil tools"""
+    bl_label = "Grease Pencil Tools"
+
+    def draw(self, context):
+        layout = self.layout
+
+        pie = layout.menu_pie()
+        gpd = context.gpencil_data
+
+        # W - Drawing Types
+        col = pie.column()
+        col.operator("gpencil.draw", text="Draw", icon='GREASEPENCIL').mode = 'DRAW'
+        col.operator("gpencil.draw", text="Straight Lines", icon='LINE_DATA').mode = 'DRAW_STRAIGHT'
+        col.operator("gpencil.draw", text="Poly", icon='MESH_DATA').mode = 'DRAW_POLY'
+
+        # E - Eraser
+        # XXX: needs a dedicated icon...
+        col = pie.column()
+        col.operator("gpencil.draw", text="Eraser", icon='FORCE_CURVE').mode = 'ERASER'
+
+        # E - "Settings" Palette is included here too, since it needs to be in a stable position...
+        if gpd and gpd.layers.active:
+            col.separator()
+            col.operator("wm.call_menu_pie", text="Settings...", icon='SCRIPTWIN').name = "GPENCIL_PIE_settings_palette"
+
+        # Editing tools
+        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')
+
+                # 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 = col.row(align=True)
+                row.label("Proportional Edit:")
+                row.prop(context.tool_settings, "proportional_edit", text="", icon_only=True)
+                row.prop(context.tool_settings, "proportional_edit_falloff", text="", icon_only=True)
+
+                # NW - Select (Non-Modal)
+                col = pie.column()
+                col.operator("gpencil.select_all", text="Select All", icon='PARTICLE_POINT')
+                col.operator("gpencil.select_linked", text="Select Linked", icon='LINKED')
+
+                # NE - Select (Modal)
+                col = pie.column()
+                col.operator("gpencil.select_border", text="Border Select", icon='BORDER_RECT')
+                col.operator("gpencil.select_circle", text="Circle Select", icon='META_EMPTY')
+
+                # SW - Edit Tools
+                col = pie.column()
+                col.operator("gpencil.duplicate_move", icon='PARTICLE_PATH', text="Duplicate")
+                col.operator("gpencil.delete", icon='X', text="Delete...")
+
+                # SE - More Tools
+                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')
+
+
+class GPENCIL_PIE_settings_palette(Menu):
+    """A pie menu for quick access to Grease Pencil settings"""
+    bl_label = "Grease Pencil Settings"
+
+    @classmethod
+    def poll(cls, context):
+        return bool(context.gpencil_data and context.active_gpencil_layer)
+
+    def draw(self, context):
+        layout = self.layout
+
+        pie = layout.menu_pie()
+        gpd = context.gpencil_data
+        gpl = context.active_gpencil_layer
+
+        # W - Stroke draw settings
+        col = pie.column(align=True)
+        col.label(text="Stroke")
+        col.prop(gpl, "color", text="")
+        col.prop(gpl, "alpha", text="", slider=True)
+
+        # E - Fill draw settings
+        col = pie.column(align=True)
+        col.label(text="Fill")
+        col.prop(gpl, "fill_color", text="")
+        col.prop(gpl, "fill_alpha", text="", slider=True)
+
+        # S - Layer settings
+        col = pie.column()
+        col.prop(gpl, "line_width", slider=True)
+        #col.prop(gpl, "use_volumetric_strokes")
+        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.prop(gpl, "lock")
+        row.prop(gpl, "hide")
+
+        
+class GPENCIL_PIE_tools_more(Menu):
+    """A pie menu for accessing more Grease Pencil tools"""
+    bl_label = "More Grease Pencil Tools"
+
+    @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()
+        gpd = context.gpencil_data
+        
+        pie.operator("gpencil.select_more", icon='ZOOMIN')
+        pie.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("gpencil.convert", icon='OUTLINER_OB_CURVE')
+        pie.operator("wm.call_menu_pie", text="Back to Main Palette...").name = "GPENCIL_PIE_tool_palette"
+
+###############################
+
+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)
+        gpl = item
+
+        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+            if gpl.lock:
+                layout.active = False
+
+            split = layout.split(percentage=0.2)
+            split.prop(gpl, "color", text="")
+            split.prop(gpl, "info", text="", emboss=False)
+
+            row = layout.row(align=True)
+            row.prop(gpl, "lock", text="", emboss=False)
+            row.prop(gpl, "hide", text="", emboss=False)
+        elif self.layout_type in {'GRID'}:
+            layout.alignment = 'CENTER'
+            layout.label(text="", icon_value=icon)
+
+
+class GreasePencilDataPanel():
+    # subclass must set
+    # bl_space_type = 'IMAGE_EDITOR'
+    bl_label = "Grease Pencil"
+    bl_region_type = 'UI'
+
+    @staticmethod
+    def draw_header(self, context):
+        self.layout.prop(context.space_data, "show_grease_pencil", text="")
+
+    @staticmethod
+    def draw(self, context):
+        layout = self.layout
+
+        # owner of Grease Pencil data
+        gpd_owner = context.gpencil_data_owner
+        gpd = context.gpencil_data
+
+        # Owner Selector
+        # XXX: add this for 3D view too
+        if context.space_data.type == 'CLIP_EDITOR':
+            layout.prop(context.space_data, "grease_pencil_source", expand=True)
+
+        # Grease Pencil data selector
+        layout.template_ID(gpd_owner, "grease_pencil", new="gpencil.data_add", unlink="gpencil.data_unlink")
+
+        # Grease Pencil data...
+        if gpd:
+            self.draw_layers(context, layout, gpd)
+
+    def draw_layers(self, context, layout, gpd):
+        row = layout.row()
+
+        col = row.column()
+        col.template_list("GPENCIL_UL_layer", "", gpd, "layers", gpd.layers, "active_index", rows=5)
+
+        col = row.column()
+
+        sub = col.column(align=True)
+        sub.operator("gpencil.layer_add", icon='ZOOMIN', text="")
+        sub.operator("gpencil.layer_remove", icon='ZOOMOUT', text="")
+
+        gpl = context.active_gpencil_layer
+        if gpl:
+            col.separator()
+
+            sub = col.column(align=True)
+            sub.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP'
+            sub.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN'
+
+        if gpl:
+            self.draw_layer(layout, gpl)
+
+    def draw_layer(self, layout, gpl):
+        # layer settings
+        split = layout.split(percentage=0.5)
+        split.active = not gpl.lock
+
+        # Column 1 - Stroke
+        col = split.column(align=True)
+        col.label(text="Stroke:")
+        col.prop(gpl, "color", text="")
+        col.prop(gpl, "alpha", slider=True)
+
+        # Column 2 - Fill
+        col = split.column(align=True)
+        col.label(text="Fill:")
+        col.prop(gpl, "fill_color", text="")
+        col.prop(gpl, "fill_alpha", text="Opacity", slider=True)
+
+        # Options
+        split = layout.split(percentage=0.5)
+        split.active = not gpl.lock
+
+        col = split.column(align=True)
+        col.prop(gpl, "line_width", slider=True)
+        col.prop(gpl, "use_volumetric_strokes")
+
+        col = split.column(align=True)
+        col.prop(gpl, "show_x_ray")
+
+        #if debug:
+        #   layout.prop(gpl, "show_points")
+
+        layout.separator()
+
+        # Full-Row - Frame Locking (and Delete Frame)
+        row = layout.row(align=True)
+        row.active = not gpl.lock
+
+        if gpl.active_frame:
+            lock_status = "Locked" if gpl.lock_frame else "Unlocked"
+            lock_label = "Frame: %d (%s)" % (gpl.active_frame.frame_number, lock_status)
+        else:
+            lock_label = "Lock Frame"
+        row.prop(gpl, "lock_frame", text=lock_label, icon='UNLOCKED')
+        row.operator("gpencil.active_frame_delete", text="", icon='X')
+
+        layout.separator()
+
+        # Onion skinning
+        col = layout.column(align=True)
+        col.active = not gpl.lock
+
+        row = col.row()
+        row.prop(gpl, "use_onion_skinning")
+        row.prop(gpl, "use_ghost_custom_colors", text="", icon='COLOR')
+
+        split = col.split(percentage = 0.5)
+        split.active = gpl.use_onion_skinning
+
+        # - Before Frames
+        sub = split.column(align=True)
+        row = sub.row(align=True)
+        row.active = gpl.use_ghost_custom_colors
+        row.prop(gpl, "before_color", text="")
+        sub.prop(gpl, "ghost_before_range", text="Before")
+
+
+        # - After Frames
+        sub = split.column(align=True)
+        row = sub.row(align=True)
+        row.active = gpl.use_ghost_custom_colors
+        row.prop(gpl, "after_color", text="")
+        sub.prop(gpl, "ghost_after_range", text="After")
+
+
+class GreasePencilToolsPanel():
+    # subclass must set
+    # bl_space_type = 'IMAGE_EDITOR'
+    # bl_options = {'DEFAULT_CLOSED'}
+    bl_label = "Grease Pencil Settings"
+    bl_region_type = 'UI'
+
+    @classmethod
+    def poll(cls, context):
+        return (context.gpencil_data is not None)
+
+    @staticmethod
+    def draw(self, context):
+        layout = self.layout
+
+        gpd_owner = context.gpencil_data_owner
+        gpd = context.gpencil_data
+
+        layout.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
+
+        layout.separator()
+
+        layout.label("Proportional Edit:")
+        row = layout.row()
+        row.prop(context.tool_settings, "proportional_edit", text="")
+        row.prop(context.tool_settings, "proportional_edit_falloff", text="")
+
+        layout.separator()
+        layout.separator()
+
+        gpencil_stroke_placement_settings(context, layout, gpd)
index d43792bc61db7555fde182148e8193626af7b60f..158de750185bcad9dbc444bb2f485da01f56649a 100644 (file)
 import bpy
 from bpy.types import Panel, Header, Menu, UIList
 from bpy.app.translations import pgettext_iface as iface_
-from bl_ui.properties_grease_pencil_common import GreasePencilPanel
+from bl_ui.properties_grease_pencil_common import (
+        GreasePencilDrawingToolsPanel,
+        GreasePencilStrokeEditPanel,
+        GreasePencilDataPanel
+        )
 
 
 class CLIP_UL_tracking_objects(UIList):
@@ -1050,12 +1054,6 @@ class CLIP_PT_tools_mask(MASK_PT_tools, Panel):
 # --- end mask ---
 
 
-class CLIP_PT_tools_grease_pencil(GreasePencilPanel, Panel):
-    bl_space_type = 'CLIP_EDITOR'
-    bl_region_type = 'TOOLS'
-    bl_category = "Grease Pencil"
-
-
 class CLIP_PT_footage(CLIP_PT_clip_view_panel, Panel):
     bl_space_type = 'CLIP_EDITOR'
     bl_region_type = 'UI'
@@ -1110,6 +1108,26 @@ class CLIP_PT_tools_scenesetup(Panel):
         layout.operator("clip.setup_tracking_scene")
 
 
+# Grease Pencil properties
+class CLIP_PT_grease_pencil(GreasePencilDataPanel, CLIP_PT_clip_view_panel, Panel):
+    bl_space_type = 'CLIP_EDITOR'
+    bl_region_type = 'UI'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    # NOTE: this is just a wrapper around the generic GP Panel
+    # But, this should only be visible in "clip" view
+
+
+# Grease Pencil drawing tools
+class CLIP_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
+    bl_space_type = 'CLIP_EDITOR'
+
+
+# Grease Pencil stroke editing tools
+class CLIP_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
+    bl_space_type = 'CLIP_EDITOR'
+
+
 class CLIP_MT_view(Menu):
     bl_label = "View"
 
index 79240bbf72a6f4dbe00006071ea6a69c57ba2902..0458ffe33772ada87547b1f3811ced7a6cceef24 100644 (file)
@@ -91,6 +91,8 @@ def dopesheet_filter(layout, context, genericFiltersOnly=False):
                 row.prop(dopesheet, "show_speakers", text="")
             if bpy.data.linestyles:
                 row.prop(dopesheet, "show_linestyles", text="")
+            if bpy.data.grease_pencil:
+                row.prop(dopesheet, "show_gpencil", text="")
 
 
 #######################################
@@ -365,14 +367,16 @@ class DOPESHEET_MT_gpencil_frame(Menu):
         layout = self.layout
 
         layout.menu("DOPESHEET_MT_key_transform", text="Transform")
-
-        #layout.operator_menu_enum("action.snap", "type", text="Snap")
-        #layout.operator_menu_enum("action.mirror", "type", text="Mirror")
+        layout.operator_menu_enum("action.snap", "type", text="Snap")
+        layout.operator_menu_enum("action.mirror", "type", text="Mirror")
 
         layout.separator()
         layout.operator("action.duplicate")
         layout.operator("action.delete")
 
+        layout.separator()
+        layout.operator("action.keyframe_type")
+
         #layout.separator()
         #layout.operator("action.copy")
         #layout.operator("action.paste")
index 56c0bda56082d752873f165364374e20997bf68b..a1da262d9f26dc24f5882e279f6a7095a979959b 100644 (file)
@@ -25,7 +25,11 @@ from bl_ui.properties_paint_common import (
         brush_texpaint_common,
         brush_mask_texture_settings,
         )
-from bl_ui.properties_grease_pencil_common import GreasePencilPanel
+from bl_ui.properties_grease_pencil_common import (
+        GreasePencilDrawingToolsPanel,
+        GreasePencilStrokeEditPanel,
+        GreasePencilDataPanel
+        )
 from bpy.app.translations import pgettext_iface as iface_
 
 
@@ -1149,10 +1153,21 @@ class IMAGE_PT_scope_sample(Panel):
         sub.prop(sima.scopes, "accuracy")
 
 
-class IMAGE_PT_tools_grease_pencil(GreasePencilPanel, Panel):
+# Grease Pencil properties
+class IMAGE_PT_grease_pencil(GreasePencilDataPanel, Panel):
+    bl_space_type = 'IMAGE_EDITOR'
+    bl_region_type = 'UI'
+
+    # NOTE: this is just a wrapper around the generic GP Panel
+
+# Grease Pencil drawing tools
+class IMAGE_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
+    bl_space_type = 'IMAGE_EDITOR'
+
+
+# Grease Pencil stroke editing tools
+class IMAGE_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
     bl_space_type = 'IMAGE_EDITOR'
-    bl_region_type = 'TOOLS'
-    bl_category = "Grease Pencil"
 
 
 if __name__ == "__main__":  # only for live edit.
index 17eeeec248074d4737b838e31c3e5781bb8ac304..4cc72f1dc4b5f5d954b32fe039a1c906bbd6963d 100644 (file)
 # <pep8 compliant>
 import bpy
 from bpy.types import Header, Menu, Panel
+from bl_ui.properties_grease_pencil_common import (
+        GreasePencilDrawingToolsPanel,
+        GreasePencilStrokeEditPanel,
+        GreasePencilDataPanel,
+        GreasePencilToolsPanel,
+        )
 
 
 class NODE_HT_header(Header):
@@ -439,6 +445,45 @@ class NODE_UL_interface_sockets(bpy.types.UIList):
             layout.template_node_socket(color)
 
 
+# Grease Pencil properties
+class NODE_PT_grease_pencil(GreasePencilDataPanel, Panel):
+    bl_space_type = 'NODE_EDITOR'
+    bl_region_type = 'UI'
+
+    # NOTE: this is just a wrapper around the generic GP Panel
+
+    @classmethod
+    def poll(cls, context):
+        snode = context.space_data
+        return snode is not None and snode.node_tree is not None
+
+
+class NODE_PT_grease_pencil_tools(GreasePencilToolsPanel, Panel):
+    bl_space_type = 'NODE_EDITOR'
+    bl_region_type = 'UI'
+    bl_options = {'DEFAULT_CLOSED'}
+
+    # NOTE: this is just a wrapper around the generic GP tools panel
+       # It contains access to some essential tools usually found only in
+       # toolbar, but which may not necessarily be open
+
+
+# Tool Shelf ------------------
+
+
+# Grease Pencil drawing tools
+class NODE_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
+    bl_space_type = 'NODE_EDITOR'
+    bl_region_type = 'TOOLS'
+
+
+# Grease Pencil stroke editing tools
+class NODE_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
+    bl_space_type = 'NODE_EDITOR'
+    bl_region_type = 'TOOLS'
+
+# -----------------------------
+
 def node_draw_tree_view(layout, context):
     pass
 
index 650c63140d22920c9c172096c4be34e24f15fc50..d84d7723e19f27ffdb78b965db9370a0c8f1568e 100644 (file)
@@ -19,6 +19,7 @@
 # <pep8 compliant>
 import bpy
 from bpy.types import Header, Menu, Panel
+from bl_ui.properties_grease_pencil_common import GreasePencilDataPanel, GreasePencilToolsPanel
 from bpy.app.translations import pgettext_iface as iface_
 
 
@@ -103,6 +104,17 @@ class SEQUENCER_HT_header(Header):
                     row = layout.row()
                     row.prop(st, "overlay_type", text="")
 
+        if st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}:
+            gpd = context.gpencil_data
+            toolsettings = context.tool_settings
+
+            # Proportional editing
+            if gpd and gpd.use_stroke_edit_mode:
+                row = layout.row(align=True)
+                row.prop(toolsettings, "proportional_edit", icon_only=True)
+                if toolsettings.proportional_edit != 'DISABLED':
+                    row.prop(toolsettings, "proportional_edit_falloff", icon_only=True)
+                                       
         row = layout.row(align=True)
         row.operator("render.opengl", text="", icon='RENDER_STILL').sequencer = True
         props = row.operator("render.opengl", text="", icon='RENDER_ANIMATION')
@@ -1017,5 +1029,22 @@ class SEQUENCER_PT_modifiers(SequencerButtonsPanel, Panel):
                     col.prop(mod, "contrast")
 
 
+class SEQUENCER_PT_grease_pencil(GreasePencilDataPanel, SequencerButtonsPanel_Output, Panel):
+    bl_space_type = 'SEQUENCE_EDITOR'
+    bl_region_type = 'UI'
+
+    # NOTE: this is just a wrapper around the generic GP Panel
+    # But, it should only show up when there are images in the preview region
+
+
+class SEQUENCER_PT_grease_pencil_tools(GreasePencilToolsPanel, SequencerButtonsPanel_Output, Panel):
+    bl_space_type = 'SEQUENCE_EDITOR'
+    bl_region_type = 'UI'
+
+    # NOTE: this is just a wrapper around the generic GP tools panel
+       # It contains access to some essential tools usually found only in
+       # toolbar, which doesn't exist here...
+
+
 if __name__ == "__main__":  # only for live edit.
     bpy.utils.register_module(__name__)
index d0cfd190f0e22b04d3412551a9a3dcf30a858e0d..cc46f25656b552cf9e62f92468d8f0581623e097 100644 (file)
@@ -19,6 +19,7 @@
 # <pep8 compliant>
 import bpy
 from bpy.types import Header, Menu, Panel
+from bl_ui.properties_grease_pencil_common import GreasePencilDataPanel
 from bl_ui.properties_paint_common import UnifiedPaintPanel
 from bpy.app.translations import contexts as i18n_contexts
 
@@ -56,7 +57,12 @@ class VIEW3D_HT_header(Header):
                 row.prop(view, "use_occlude_geometry", text="")
 
             # Proportional editing
-            if mode in {'EDIT', 'PARTICLE_EDIT'}:
+            if context.gpencil_data and context.gpencil_data.use_stroke_edit_mode:
+                row = layout.row(align=True)
+                row.prop(toolsettings, "proportional_edit", icon_only=True)
+                if toolsettings.proportional_edit != 'DISABLED':
+                    row.prop(toolsettings, "proportional_edit_falloff", icon_only=True)
+            elif mode in {'EDIT', 'PARTICLE_EDIT'}:
                 row = layout.row(align=True)
                 row.prop(toolsettings, "proportional_edit", icon_only=True)
                 if toolsettings.proportional_edit != 'DISABLED':
@@ -2703,6 +2709,12 @@ class VIEW3D_MT_edit_armature_roll(Menu):
 
 # ********** Panel **********
 
+class VIEW3D_PT_grease_pencil(GreasePencilDataPanel, Panel):
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'UI'
+
+    # NOTE: this is just a wrapper around the generic GP Panel
+
 
 class VIEW3D_PT_view3d_properties(Panel):
     bl_space_type = 'VIEW_3D'
index 990697f74fe9f4fa3f323c76c83f3328d8f44bfb..b7fe9c69c1f550425b111b2a8977030e38cf9f95 100644 (file)
 # <pep8 compliant>
 import bpy
 from bpy.types import Menu, Panel, UIList
-from bl_ui.properties_grease_pencil_common import GreasePencilPanel
+from bl_ui.properties_grease_pencil_common import (
+        GreasePencilDrawingToolsPanel,
+        GreasePencilStrokeEditPanel
+        )
 from bl_ui.properties_paint_common import (
         UnifiedPaintPanel,
         brush_texture_settings,
@@ -1805,11 +1808,14 @@ class VIEW3D_PT_tools_particlemode(View3DPanel, Panel):
             sub.prop(pe, "fade_frames", slider=True)
 
 
-# Grease Pencil tools
-class VIEW3D_PT_tools_grease_pencil(GreasePencilPanel, Panel):
+# Grease Pencil drawing tools
+class VIEW3D_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
+    bl_space_type = 'VIEW_3D'
+
+
+# Grease Pencil stroke editing tools
+class VIEW3D_PT_tools_grease_pencil_edit(GreasePencilStrokeEditPanel, Panel):
     bl_space_type = 'VIEW_3D'
-    bl_region_type = 'TOOLS'
-    bl_category = "Grease Pencil"
 
 
 # Note: moved here so that it's always in last position in 'Tools' panels!
index 877e376b343a52070dc6d88d55dffd10f4e8e89b..ae0ef9ce3140b8ccba70e231f9c06ab4ba786c74 100644 (file)
@@ -56,6 +56,9 @@ struct Text;
 struct ImBuf;
 struct EditBone;
 struct bPoseChannel;
+struct bGPdata;
+struct bGPDlayer;
+struct bGPDframe;
 struct wmWindow;
 struct wmWindowManager;
 struct SpaceText;
@@ -275,6 +278,14 @@ struct bPoseChannel *CTX_data_active_pose_bone(const bContext *C);
 int CTX_data_selected_pose_bones(const bContext *C, ListBase *list);
 int CTX_data_visible_pose_bones(const bContext *C, ListBase *list);
 
+struct bGPdata *CTX_data_gpencil_data(const bContext *C);
+struct bGPDlayer *CTX_data_active_gpencil_layer(const bContext *C);
+struct bGPDframe *CTX_data_active_gpencil_frame(const bContext *C);
+int CTX_data_visible_gpencil_layers(const bContext *C, ListBase *list);
+int CTX_data_editable_gpencil_layers(const bContext *C, ListBase *list);
+int CTX_data_editable_gpencil_strokes(const bContext *C, ListBase *list);
+
+
 #ifdef __cplusplus
 }
 #endif
index c21207c042fa142f3726a33339d5db8672944a21..084c5527f211076bdb2fdb5ce37a3c64a0304242 100644 (file)
@@ -35,6 +35,7 @@ struct ListBase;
 struct bGPdata;
 struct bGPDlayer;
 struct bGPDframe;
+struct bGPDstroke;
 
 /* ------------ Grease-Pencil API ------------------ */
 
@@ -43,6 +44,8 @@ void free_gpencil_frames(struct bGPDlayer *gpl);
 void free_gpencil_layers(struct ListBase *list);
 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 bGPDlayer *gpencil_layer_addnew(struct bGPdata *gpd, const char *name, int setactive);
 struct bGPdata *gpencil_data_addnew(const char name[]);
index 08b443614504a1ae3e704053faed8cb537c730f0..5bf458805bd5d7d4aeefc72ec65db82d11817f4a 100644 (file)
@@ -100,6 +100,7 @@ bool id_type_can_have_animdata(ID *id)
                case ID_SCE:
                case ID_MC:
                case ID_MSK:
+               case ID_GD:
                {
                        return 1;
                }
@@ -1031,6 +1032,9 @@ void BKE_animdata_main_cb(Main *mainptr, ID_AnimData_Edit_Callback func, void *u
 
        /* line styles */
        ANIMDATA_IDS_CB(mainptr->linestyle.first);
+       
+       /* grease pencil */
+       ANIMDATA_IDS_CB(mainptr->gpencil.first);
 }
 
 /* Fix all RNA-Paths throughout the database (directly access the Global.main version)
@@ -1119,6 +1123,9 @@ void BKE_all_animdata_fix_paths_rename(ID *ref_id, const char *prefix, const cha
        /* linestyles */
        RENAMEFIX_ANIM_IDS(mainptr->linestyle.first);
        
+       /* grease pencil */
+       RENAMEFIX_ANIM_IDS(mainptr->gpencil.first);
+       
        /* scenes */
        RENAMEFIX_ANIM_NODETREE_IDS(mainptr->scene.first, Scene);
 }
@@ -2680,6 +2687,9 @@ void BKE_animsys_evaluate_all_animation(Main *main, Scene *scene, float ctime)
        /* linestyles */
        EVAL_ANIM_IDS(main->linestyle.first, ADT_RECALC_ANIM);
        
+       /* grease pencil */
+       EVAL_ANIM_IDS(main->gpencil.first, ADT_RECALC_ANIM);
+       
        /* objects */
        /* ADT_RECALC_ANIM doesn't need to be supplied here, since object AnimData gets
         * this tagged by Depsgraph on framechange. This optimization means that objects
index 5ffc5c52eaddf1255200c4434d9ec1c689449e77..8f6c9735aaf81bbc0874c40ce090d225c9e4acfa 100644 (file)
@@ -37,6 +37,7 @@
 #include "DNA_windowmanager_types.h"
 #include "DNA_object_types.h"
 #include "DNA_linestyle_types.h"
+#include "DNA_gpencil_types.h"
 
 #include "BLI_listbase.h"
 #include "BLI_string.h"
@@ -1090,3 +1091,34 @@ int CTX_data_visible_pose_bones(const bContext *C, ListBase *list)
 {
        return ctx_data_collection_get(C, "visible_pose_bones", list);
 }
+
+bGPdata *CTX_data_gpencil_data(const bContext *C)
+{
+       return ctx_data_pointer_get(C, "gpencil_data");
+}
+
+bGPDlayer *CTX_data_active_gpencil_layer(const bContext *C)
+{
+       return ctx_data_pointer_get(C, "active_gpencil_layer");
+}
+
+bGPDframe *CTX_data_active_gpencil_frame(const bContext *C)
+{
+       return ctx_data_pointer_get(C, "active_gpencil_frame");
+}
+
+int CTX_data_visible_gpencil_layers(const bContext *C, ListBase *list)
+{
+       return ctx_data_collection_get(C, "visible_gpencil_layers", list);
+}
+
+int CTX_data_editable_gpencil_layers(const bContext *C, ListBase *list)
+{
+       return ctx_data_collection_get(C, "editable_gpencil_layers", list);
+}
+
+int CTX_data_editable_gpencil_strokes(const bContext *C, ListBase *list)
+{
+       return ctx_data_collection_get(C, "editable_gpencil_strokes", list);
+}
+
index 2011f41622d673913709d14808bc2e980a50b618..dd2155505fbcec1171e1892b26af6fa9e3a8f826 100644 (file)
@@ -45,6 +45,7 @@
 #include "DNA_gpencil_types.h"
 #include "DNA_userdef_types.h"
 
+#include "BKE_animsys.h"
 #include "BKE_global.h"
 #include "BKE_gpencil.h"
 #include "BKE_library.h"
@@ -115,6 +116,12 @@ void BKE_gpencil_free(bGPdata *gpd)
 {
        /* free layers */
        free_gpencil_layers(&gpd->layers);
+       
+       /* free animation data */
+       if (gpd->adt) {
+               BKE_free_animdata(&gpd->id);
+               gpd->adt = NULL;
+       }
 }
 
 /* -------- Container Creation ---------- */
@@ -307,6 +314,31 @@ bGPdata *gpencil_data_duplicate(bGPdata *src, bool internal_copy)
        return dst;
 }
 
+/* -------- GP-Stroke API --------- */
+
+/* ensure selection status of stroke is in sync with its points */
+void gpencil_stroke_sync_selection(bGPDstroke *gps)
+{
+       bGPDspoint *pt;
+       int i;
+       
+       /* error checking */
+       if (gps == NULL)
+               return;
+       
+       /* we'll stop when we find the first selected point,
+        * so initially, we must deselect
+        */
+       gps->flag &= ~GP_STROKE_SELECT;
+       
+       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+               if (pt->flag & GP_SPOINT_SELECT) {
+                       gps->flag |= GP_STROKE_SELECT;
+                       break;
+               }
+       }
+}
+
 /* -------- GP-Frame API ---------- */
 
 /* delete the last stroke of the given frame */
@@ -366,7 +398,7 @@ bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew)
                /* do not allow any changes to layer's active frame if layer is locked from changes
                 * or if the layer has been set to stay on the current frame
                 */
-               if (gpl->flag & (GP_LAYER_LOCKED | GP_LAYER_FRAMELOCK))
+               if (gpl->flag & GP_LAYER_FRAMELOCK)
                        return gpf;
                /* do not allow any changes to actframe if frame has painting tag attached to it */
                if (gpf->flag & GP_FRAME_PAINT) 
@@ -475,16 +507,23 @@ bGPDframe *gpencil_layer_getframe(bGPDlayer *gpl, int cframe, short addnew)
 bool gpencil_layer_delframe(bGPDlayer *gpl, bGPDframe *gpf)
 {
        bool changed = false;
-
+       
        /* error checking */
        if (ELEM(NULL, gpl, gpf))
                return false;
-               
+       
+       /* if this frame was active, make the previous frame active instead 
+        * since it's tricky to set active frame otherwise
+        */
+       if (gpl->actframe == gpf)
+               gpl->actframe = gpf->prev;
+       else
+               gpl->actframe = NULL;
+       
        /* free the frame and its data */
        changed = free_gpencil_strokes(gpf);
        BLI_freelinkN(&gpl->frames, gpf);
-       gpl->actframe = NULL;
-
+       
        return changed;
 }
 
index 7c606fe072c7391a465856a438d1c5db6c633736..ac903c7a8d26dd7374da9befb1fa7b3c4923e91d 100644 (file)
@@ -5732,6 +5732,21 @@ static void lib_link_windowmanager(FileData *fd, Main *main)
 
 /* ****************** READ GREASE PENCIL ***************** */
 
+/* relink's grease pencil data's refs */
+static void lib_link_gpencil(FileData *fd, Main *main)
+{
+       bGPdata *gpd;
+       
+       for (gpd = main->gpencil.first; gpd; gpd = gpd->id.next) {
+               if (gpd->id.flag & LIB_NEED_LINK) {
+                       gpd->id.flag -= LIB_NEED_LINK;
+                       
+                       if (gpd->adt)
+                               lib_link_animdata(fd, &gpd->id, gpd->adt);
+               }
+       }
+}
+
 /* relinks grease-pencil data - used for direct_link and old file linkage */
 static void direct_link_gpencil(FileData *fd, bGPdata *gpd)
 {
@@ -5743,6 +5758,10 @@ static void direct_link_gpencil(FileData *fd, bGPdata *gpd)
        if (gpd == NULL)
                return;
        
+       /* relink animdata */
+       gpd->adt = newdataadr(fd, gpd->adt);
+       direct_link_animdata(fd, gpd->adt);
+       
        /* relink layers */
        link_list(fd, &gpd->layers);
        
@@ -7714,6 +7733,7 @@ static void lib_link_all(FileData *fd, Main *main)
        lib_link_movieclip(fd, main);
        lib_link_mask(fd, main);
        lib_link_linestyle(fd, main);
+       lib_link_gpencil(fd, main);
 
        lib_link_mesh(fd, main);                /* as last: tpage images with users at zero */
        
@@ -8819,6 +8839,12 @@ static void expand_linestyle(FileData *fd, Main *mainvar, FreestyleLineStyle *li
        }
 }
 
+static void expand_gpencil(FileData *fd, Main *mainvar, bGPdata *gpd)
+{
+       if (gpd->adt)
+               expand_animdata(fd, mainvar, gpd->adt);
+}
+
 void BLO_main_expander(void (*expand_doit_func)(void *, Main *, void *))
 {
        expand_doit = expand_doit_func;
@@ -8913,6 +8939,9 @@ void BLO_expand_main(void *fdhandle, Main *mainvar)
                                        case ID_LS:
                                                expand_linestyle(fd, mainvar, (FreestyleLineStyle *)id);
                                                break;
+                                       case ID_GD:
+                                               expand_gpencil(fd, mainvar, (bGPdata *)id);
+                                               break;
                                        }
                                        
                                        do_it = true;
index bad84949cab327c134a3d5dcabc98cf32e50ba19..3dc1fed46c81366db7dd2823645e4364d47dae53 100644 (file)
@@ -2465,6 +2465,8 @@ static void write_gpencils(WriteData *wd, ListBase *lb)
                        /* write gpd data block to file */
                        writestruct(wd, ID_GD, "bGPdata", 1, gpd);
                        
+                       if (gpd->adt) write_animdata(wd, gpd->adt);
+                       
                        /* write grease-pencil layers to file */
                        writelist(wd, DATA, "bGPDlayer", &gpd->layers);
                        for (gpl= gpd->layers.first; gpl; gpl= gpl->next) {
index 92c6779770c57dcd5f8f8005f86d66eae5cdf2fe..711ad4e9c561fd017209fdb2f0e8502c5757d99d 100644 (file)
@@ -2365,6 +2365,83 @@ static bAnimChannelType ACF_DSSPK =
        acf_dsspk_setting_ptr                   /* pointer for setting */
 };
 
+/* GPencil Expander  ------------------------------------------- */
+
+// TODO: just get this from RNA?
+static int acf_dsgpencil_icon(bAnimListElem *UNUSED(ale))
+{
+       return ICON_GREASEPENCIL;
+}
+
+/* get the appropriate flag(s) for the setting when it is valid  */
+static int acf_dsgpencil_setting_flag(bAnimContext *UNUSED(ac), eAnimChannel_Settings setting, bool *neg)
+{
+       /* clear extra return data first */
+       *neg = false;
+       
+       switch (setting) {
+               case ACHANNEL_SETTING_EXPAND: /* expanded */
+                       return GP_DATA_EXPAND;
+               
+               case ACHANNEL_SETTING_MUTE: /* mute (only in NLA) */
+                       return ADT_NLA_EVAL_OFF;
+               
+               case ACHANNEL_SETTING_VISIBLE: /* visible (only in Graph Editor) */
+                       *neg = true;
+                       return ADT_CURVES_NOT_VISIBLE;
+               
+               case ACHANNEL_SETTING_SELECT: /* selected */
+                       return ADT_UI_SELECTED;
+               
+               default: /* unsupported */
+                       return 0;
+       }
+}
+
+/* get pointer to the setting */
+static void *acf_dsgpencil_setting_ptr(bAnimListElem *ale, eAnimChannel_Settings setting, short *type)
+{
+       bGPdata *gpd = (bGPdata *)ale->data;
+       
+       /* clear extra return data first */
+       *type = 0;
+       
+       switch (setting) {
+               case ACHANNEL_SETTING_EXPAND: /* expanded */
+                       return GET_ACF_FLAG_PTR(gpd->flag, type);
+               
+               case ACHANNEL_SETTING_SELECT: /* selected */
+               case ACHANNEL_SETTING_MUTE: /* muted (for NLA only) */
+               case ACHANNEL_SETTING_VISIBLE: /* visible (for Graph Editor only) */
+                       if (gpd->adt)
+                               return GET_ACF_FLAG_PTR(gpd->adt->flag, type);
+                       return NULL;
+               
+               default: /* unsupported */
+                       return NULL;
+       }
+}
+
+/* grease pencil expander type define */
+static bAnimChannelType ACF_DSGPENCIL =
+{
+       "GPencil DS Expander",          /* type name */
+       ACHANNEL_ROLE_EXPANDER,         /* role */
+       
+       acf_generic_dataexpand_color,   /* backdrop color */
+       acf_generic_dataexpand_backdrop, /* backdrop */
+       acf_generic_indention_1,        /* indent level */
+       acf_generic_basic_offset,       /* offset */
+
+       acf_generic_idblock_name,       /* name */
+       acf_generic_idblock_name_prop,  /* name prop */
+       acf_dsgpencil_icon,             /* icon */
+
+       acf_generic_dataexpand_setting_valid,   /* has setting */
+       acf_dsgpencil_setting_flag,             /* flag for setting */
+       acf_dsgpencil_setting_ptr               /* pointer for setting */
+};
+
 /* ShapeKey Entry  ------------------------------------------- */
 
 /* name for ShapeKey */
@@ -3162,6 +3239,7 @@ static void ANIM_init_channel_typeinfo_data(void)
                animchannelTypeInfo[type++] = &ACF_DSLAT;        /* Lattice Channel */
                animchannelTypeInfo[type++] = &ACF_DSLINESTYLE;  /* LineStyle Channel */
                animchannelTypeInfo[type++] = &ACF_DSSPK;        /* Speaker Channel */
+               animchannelTypeInfo[type++] = &ACF_DSGPENCIL;    /* GreasePencil Channel */
                
                animchannelTypeInfo[type++] = &ACF_SHAPEKEY;     /* ShapeKey */
                
@@ -3407,6 +3485,7 @@ void ANIM_channel_draw(bAnimContext *ac, bAnimListElem *ale, float yminc, float
        /* step 4) draw special toggles  .................................
         *      - in Graph Editor, checkboxes for visibility in curves area
         *      - in NLA Editor, glowing dots for solo/not solo...
+        *      - in Grease Pencil mode, color swatches for layer color
         */
        if (ac->sl) {
                if ((ac->spacetype == SPACE_IPO) && acf->has_setting(ac, ale, ACHANNEL_SETTING_VISIBLE)) {
@@ -3431,6 +3510,10 @@ void ANIM_channel_draw(bAnimContext *ac, bAnimListElem *ale, float yminc, float
                        /* just skip - drawn as widget now */
                        offset += ICON_WIDTH; 
                }
+               else if (ale->type == ANIMTYPE_GPLAYER) {
+                       /* just skip - drawn as a widget */
+                       offset += ICON_WIDTH;
+               }
        }
 
        /* step 5) draw name ............................................... */
@@ -3866,6 +3949,7 @@ void ANIM_channel_draw_widgets(bContext *C, bAnimContext *ac, bAnimListElem *ale
        /* step 3) draw special toggles  .................................
         *      - in Graph Editor, checkboxes for visibility in curves area
         *      - in NLA Editor, glowing dots for solo/not solo...
+        *      - in Grease Pencil mode, color swatches for layer color
         */
        if (ac->sl) {
                if ((ac->spacetype == SPACE_IPO) && acf->has_setting(ac, ale, ACHANNEL_SETTING_VISIBLE)) {
@@ -3878,6 +3962,23 @@ void ANIM_channel_draw_widgets(bContext *C, bAnimContext *ac, bAnimListElem *ale
                        draw_setting_widget(ac, ale, acf, block, offset, ymid, ACHANNEL_SETTING_SOLO);
                        offset += ICON_WIDTH; 
                }
+               else if (ale->type == ANIMTYPE_GPLAYER) {
+                       /* color swatch for layer color */
+                       bGPDlayer *gpl = (bGPDlayer *)ale->data;
+                       PointerRNA ptr;
+                       
+                       RNA_pointer_create(ale->id, &RNA_GPencilLayer, ale->data, &ptr);
+                       
+                       UI_block_emboss_set(block, UI_EMBOSS);
+                       
+                       uiDefButR(block, UI_BTYPE_COLOR, 1, "", offset, yminc, ICON_WIDTH, ICON_WIDTH, 
+                                 &ptr, "color", -1, 
+                                         0, 0, 0, 0, gpl->info);
+                       
+                       UI_block_emboss_set(block, UI_EMBOSS_NONE);
+                       
+                       offset += ICON_WIDTH;
+               }
        }
        
        /* step 4) draw text - check if renaming widget is in use... */
index 7ee1b8cbf9c6d0e905d5dbc80bccea6237db1d02..d8ad6506186751fc0d0cafe5687850367fe12299 100644 (file)
@@ -129,6 +129,7 @@ void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datat
                        case ANIMTYPE_DSLAT:
                        case ANIMTYPE_DSLINESTYLE:
                        case ANIMTYPE_DSSPK:
+                       case ANIMTYPE_DSGPENCIL:
                        {
                                /* need to verify that this data is valid for now */
                                if (ale->adt) {
@@ -136,6 +137,13 @@ void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datat
                                }
                                break;
                        }
+                       case ANIMTYPE_GPLAYER:
+                       {
+                               bGPDlayer *gpl = (bGPDlayer *)ale->data;
+                               
+                               ACHANNEL_SET_FLAG(gpl, ACHANNEL_SETFLAG_CLEAR, GP_LAYER_ACTIVE);
+                               break;
+                       }
                }
        }
        
@@ -176,6 +184,7 @@ void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datat
                        case ANIMTYPE_DSSPK:
                        case ANIMTYPE_DSNTREE:
                        case ANIMTYPE_DSTEX:
+                       case ANIMTYPE_DSGPENCIL:
                        {
                                /* need to verify that this data is valid for now */
                                if (ale && ale->adt) {
@@ -184,8 +193,14 @@ void ANIM_set_active_channel(bAnimContext *ac, void *data, eAnimCont_Types datat
                                break;
                        }
                        
-                       /* unhandled currently, but may be interesting */
                        case ANIMTYPE_GPLAYER:
+                       {
+                               bGPDlayer *gpl = (bGPDlayer *)channel_data;
+                               gpl->flag |= GP_LAYER_ACTIVE;
+                               break;
+                       }
+                       
+                       /* unhandled currently, but may be interesting */
                        case ANIMTYPE_MASKLAYER:
                        case ANIMTYPE_SHAPEKEY:
                        case ANIMTYPE_NLAACTION:
@@ -268,6 +283,7 @@ void ANIM_deselect_anim_channels(bAnimContext *ac, void *data, eAnimCont_Types d
                                case ANIMTYPE_DSLAT:
                                case ANIMTYPE_DSLINESTYLE:
                                case ANIMTYPE_DSSPK:
+                               case ANIMTYPE_DSGPENCIL:
                                {
                                        if ((ale->adt) && (ale->adt->flag & ADT_UI_SELECTED))
                                                sel = ACHANNEL_SETFLAG_CLEAR;
@@ -361,6 +377,7 @@ void ANIM_deselect_anim_channels(bAnimContext *ac, void *data, eAnimCont_Types d
                        case ANIMTYPE_DSLAT:
                        case ANIMTYPE_DSLINESTYLE:
                        case ANIMTYPE_DSSPK:
+                       case ANIMTYPE_DSGPENCIL:
                        {
                                /* need to verify that this data is valid for now */
                                if (ale->adt) {
@@ -843,6 +860,13 @@ static void rearrange_animchannel_add_to_islands(ListBase *islands, ListBase *sr
                        is_sel = SEL_NLT(nlt);
                        break;
                }
+               case ANIMTYPE_GPLAYER:
+               {
+                       bGPDlayer *gpl = (bGPDlayer *)channel;
+                       
+                       is_sel = SEL_GPL(gpl);
+                       break;
+               }
                default:
                        printf("rearrange_animchannel_add_to_islands(): don't know how to handle channels of type %d\n", type);
                        return;
@@ -1167,6 +1191,47 @@ static void rearrange_action_channels(bAnimContext *ac, bAction *act, eRearrange
 
 /* ------------------- */
 
+static void rearrange_gpencil_channels(bAnimContext *ac, eRearrangeAnimChan_Mode mode)
+{
+       ListBase anim_data = {NULL, NULL};
+       bAnimListElem *ale;
+       int filter;
+       
+       /* get rearranging function */
+       AnimChanRearrangeFp rearrange_func = rearrange_get_mode_func(mode);
+       
+       if (rearrange_func == NULL)
+               return;
+       
+       /* get Grease Pencil datablocks */
+       filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA);
+       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+       
+       for (ale = anim_data.first; ale; ale = ale->next) {
+               ListBase anim_data_visible = {NULL, NULL};
+               bGPdata *gpd = ale->data;
+               
+               /* only consider layers if this datablock is open */
+               BLI_assert(ale->type == ANIMTYPE_GPDATABLOCK);
+               if ((gpd->flag & GP_DATA_EXPAND) == 0)
+                       continue;
+               
+               /* Filter visible data. */
+               rearrange_animchannels_filter_visible(&anim_data_visible, ac, ANIMTYPE_GPLAYER);
+               
+               /* rearrange datablock's layers */
+               rearrange_animchannel_islands(&gpd->layers, rearrange_func, mode, ANIMTYPE_GPLAYER, &anim_data_visible);
+               
+               /* free visible layers data */
+               BLI_freelistN(&anim_data_visible);
+       }
+       
+       /* free GPD channel data */
+       ANIM_animdata_freelist(&anim_data);
+}
+
+/* ------------------- */
+
 static int animchannels_rearrange_exec(bContext *C, wmOperator *op)
 {
        bAnimContext ac;
@@ -1182,7 +1247,7 @@ static int animchannels_rearrange_exec(bContext *C, wmOperator *op)
        /* method to move channels depends on the editor */
        if (ac.datatype == ANIMCONT_GPENCIL) {
                /* Grease Pencil channels */
-               printf("Grease Pencil not supported for moving yet\n");
+               rearrange_gpencil_channels(&ac, mode);
        }
        else if (ac.datatype == ANIMCONT_MASK) {
                /* Grease Pencil channels */
@@ -2567,6 +2632,7 @@ static int mouse_anim_channels(bContext *C, bAnimContext *ac, int channel_index,
                case ANIMTYPE_DSLAT:
                case ANIMTYPE_DSLINESTYLE:
                case ANIMTYPE_DSSPK:
+               case ANIMTYPE_DSGPENCIL:
                {
                        /* sanity checking... */
                        if (ale->adt) {
@@ -2728,7 +2794,13 @@ static int mouse_anim_channels(bContext *C, bAnimContext *ac, int channel_index,
                                gpl->flag |= GP_LAYER_SELECT;
                        }
                        
-                       notifierFlags |= (ND_ANIMCHAN | NA_EDITED);
+                       /* change active layer, if this is selected (since we must always have an active layer) */
+                       if (gpl->flag & GP_LAYER_SELECT) {
+                               ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, gpl, ANIMTYPE_GPLAYER);
+                       }
+                       
+                       WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); /* Grease Pencil updates */
+                       notifierFlags |= (ND_ANIMCHAN | NA_EDITED); /* Animation Ediotrs updates */
                        break;
                }
                case ANIMTYPE_MASKDATABLOCK:
index e1b13e0cfca21142618e81e76244cd83cb3fc272..d6daa64a9f22e9c46f3781b82a216895aefd3993 100644 (file)
@@ -771,6 +771,21 @@ static bAnimListElem *make_new_animlistelem(void *data, short datatype, ID *owne
                                ale->adt = BKE_animdata_from_id(data);
                                break;
                        }
+                       case ANIMTYPE_DSGPENCIL:
+                       {
+                               bGPdata *gpd = (bGPdata *)data;
+                               AnimData *adt = gpd->adt;
+                               
+                               /* NOTE: we just reuse the same expand filter for this case */
+                               ale->flag = EXPANDED_GPD(gpd);
+                               
+                               // XXX: currently, this is only used for access to its animation data
+                               ale->key_data = (adt) ? adt->action : NULL;
+                               ale->datatype = ALE_ACT;
+                               
+                               ale->adt = BKE_animdata_from_id(data);
+                               break;
+                       }
                        case ANIMTYPE_GROUP:
                        {
                                bActionGroup *agrp = (bActionGroup *)data;
@@ -1413,27 +1428,77 @@ static size_t animdata_filter_gpencil(ListBase *anim_data, void *UNUSED(data), i
                /* 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);
+               }
+               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;
                        
-               /* add gpencil animation channels */
-               BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_GPD(gpd))
-               {
-                       tmp_items += animdata_filter_gpencil_data(&tmp_data, gpd, filter_mode);
+                       /* 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;
+                       }
                }
-               END_ANIMFILTER_SUBCHANNELS;
+       }
+       
+       /* return the number of items added to the list */
+       return items;
+}
+
+/* Helper for Grease Pencil data integrated with main DopeSheet */
+static size_t animdata_filter_ds_gpencil(bAnimContext *ac, ListBase *anim_data, bDopeSheet *ads, bGPdata *gpd, int filter_mode)
+{
+       ListBase tmp_data = {NULL, NULL};
+       size_t tmp_items = 0;
+       size_t items = 0;
+       
+       /* add relevant animation channels for Grease Pencil */
+       BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_GPD(gpd))
+       {
+               /* add animation channels */
+               tmp_items += animfilter_block_data(ac, &tmp_data, ads, &gpd->id, filter_mode);
                
-               /* 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);
+               /* add Grease Pencil layers */
+               // TODO: do these need a separate expander?
+               // XXX:  what order should these go in?
+       }
+       END_ANIMFILTER_SUBCHANNELS;
+       
+       /* did we find anything? */
+       if (tmp_items) {
+               /* include data-expand widget first */
+               if (filter_mode & ANIMFILTER_LIST_CHANNELS) {
+                       /* check if filtering by active status */
+                       // XXX: active check here needs checking
+                       if (ANIMCHANNEL_ACTIVEOK(gpd)) {
+                               ANIMCHANNEL_NEW_CHANNEL(gpd, ANIMTYPE_DSGPENCIL, gpd);
                        }
-                       
-                       /* now add the list of collected channels */
-                       BLI_movelisttolist(anim_data, &tmp_data);
-                       BLI_assert(BLI_listbase_is_empty(&tmp_data));
-                       items += tmp_items;
                }
+               
+               /* 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 the number of items added to the list */
@@ -2225,6 +2290,11 @@ static size_t animdata_filter_dopesheet_ob(bAnimContext *ac, ListBase *anim_data
                if ((ob->particlesystem.first) && !(ads->filterflag & ADS_FILTER_NOPART)) {
                        tmp_items += animdata_filter_ds_particles(ac, &tmp_data, ads, ob, filter_mode);
                }
+               
+               /* grease pencil */
+               if ((ob->gpd) && !(ads->filterflag & ADS_FILTER_NOGPENCIL)) {
+                       tmp_items += animdata_filter_ds_gpencil(ac, &tmp_data, ads, ob->gpd, filter_mode);
+               }
        }
        END_ANIMFILTER_SUBCHANNELS;
        
@@ -2359,6 +2429,7 @@ static size_t animdata_filter_dopesheet_scene(bAnimContext *ac, ListBase *anim_d
        BEGIN_ANIMFILTER_SUBCHANNELS(EXPANDED_SCEC(sce))
        {
                bNodeTree *ntree = sce->nodetree;
+               bGPdata *gpd = sce->gpd;
                World *wo = sce->world;
                
                /* Action, Drivers, or NLA for Scene */
@@ -2381,6 +2452,11 @@ static size_t animdata_filter_dopesheet_scene(bAnimContext *ac, ListBase *anim_d
                        tmp_items += animdata_filter_ds_linestyle(ac, &tmp_data, ads, sce, filter_mode);
                }
                
+               /* grease pencil */
+               if ((gpd) && !(ads->filterflag & ADS_FILTER_NOGPENCIL)) {
+                       tmp_items += animdata_filter_ds_gpencil(ac, &tmp_data, ads, gpd, filter_mode);
+               }
+               
                /* TODO: one day, when sequencer becomes its own datatype, perhaps it should be included here */
        }
        END_ANIMFILTER_SUBCHANNELS;
index f3dbf5d645babf1c99789fb061ce7185771de094..d2dbe961b4277418cd4208231e81b027ea2c2a57 100644 (file)
@@ -154,6 +154,7 @@ static DLRBT_Node *nalloc_ak_gpframe(void *data)
        /* store settings based on state of BezTriple */
        ak->cfra = gpf->framenum;
        ak->sel = (gpf->flag & GP_FRAME_SELECT) ? SELECT : 0;
+       ak->key_type = gpf->key_type;
        
        /* set 'modified', since this is used to identify long keyframes */
        ak->modified = 1;
@@ -170,6 +171,10 @@ static void nupdate_ak_gpframe(void *node, void *data)
        /* set selection status and 'touched' status */
        if (gpf->flag & GP_FRAME_SELECT) ak->sel = SELECT;
        ak->modified += 1;
+       
+       /* for keyframe type, 'proper' keyframes have priority over breakdowns (and other types for now) */
+       if (gpf->key_type == BEZT_KEYTYPE_KEYFRAME)
+               ak->key_type = BEZT_KEYTYPE_KEYFRAME;
 }
 
 /* ......... */
@@ -731,6 +736,21 @@ void draw_action_channel(View2D *v2d, AnimData *adt, bAction *act, float ypos)
        BLI_dlrbTree_free(&blocks);
 }
 
+void draw_gpencil_channel(View2D *v2d, bDopeSheet *ads, bGPdata *gpd, float ypos)
+{
+       DLRBT_Tree keys;
+       
+       BLI_dlrbTree_init(&keys);
+       
+       gpencil_to_keylist(ads, gpd, &keys);
+       
+       BLI_dlrbTree_linkedlist_sync(&keys);
+       
+       draw_keylist(v2d, &keys, NULL, ypos, 0);
+       
+       BLI_dlrbTree_free(&keys);
+}
+
 void draw_gpl_channel(View2D *v2d, bDopeSheet *ads, bGPDlayer *gpl, float ypos)
 {
        DLRBT_Tree keys;
@@ -923,6 +943,20 @@ void action_to_keylist(AnimData *adt, bAction *act, DLRBT_Tree *keys, DLRBT_Tree
 }
 
 
+void gpencil_to_keylist(bDopeSheet *ads, bGPdata *gpd, DLRBT_Tree *keys)
+{
+       bGPDlayer *gpl;
+       
+       if (gpd && keys) {
+               /* for now, just aggregate out all the frames, but only for visible layers */
+               for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+                       if ((gpl->flag & GP_LAYER_HIDE) == 0) {
+                               gpl_to_keylist(ads, gpl, keys);
+                       }
+               }
+       }
+}
+
 void gpl_to_keylist(bDopeSheet *UNUSED(ads), bGPDlayer *gpl, DLRBT_Tree *keys)
 {
        bGPDframe *gpf;
index 5dc9679777f0b204d6ec77404d3a1fc746137027..581344ba8bcc75f6e57763bacfdd0eda324338fd 100644 (file)
@@ -43,7 +43,9 @@ set(SRC
        gpencil_edit.c
        gpencil_ops.c
        gpencil_paint.c
+       gpencil_select.c
        gpencil_undo.c
+       gpencil_utils.c
 
        gpencil_intern.h
 )
index 76502a43eb78f12d5a8f0f556b1b4005a48ae0f5..eb2ffb13f20a094df783dd10e31acb1aaba2695f 100644 (file)
 #include "ED_gpencil.h"
 #include "ED_view3d.h"
 
+#include "UI_resources.h"
+
+#include "gpencil_intern.h"
+
 /* ************************************************** */
 /* GREASE PENCIL DRAWING */
 
@@ -71,6 +75,9 @@ typedef enum eDrawStrokeFlags {
        GP_DRAWDATA_ONLYI2D     = (1 << 3),   /* only draw 'image' strokes */
        GP_DRAWDATA_IEDITHACK   = (1 << 4),   /* special hack for drawing strokes in Image Editor (weird coordinates) */
        GP_DRAWDATA_NO_XRAY     = (1 << 5),   /* don't draw xray in 3D view (which is default) */
+       GP_DRAWDATA_NO_ONIONS   = (1 << 6),       /* no onionskins should be drawn (for animation playback) */
+       GP_DRAWDATA_VOLUMETRIC  = (1 << 7),   /* draw strokes as "volumetric" circular billboards */
+       GP_DRAWDATA_FILL        = (1 << 8),   /* fill insides/bounded-regions of strokes */
 } eDrawStrokeFlags;
 
 
@@ -142,6 +149,200 @@ static void gp_draw_stroke_buffer(tGPspoint *points, int totpoints, short thickn
        }
 }
 
+/* --------- 2D Stroke Drawing Helpers --------- */
+
+/* helper function to calculate x-y drawing coordinates for 2D points */
+static void gp_calc_2d_stroke_xy(bGPDspoint *pt, short sflag, int offsx, int offsy, int winx, int winy, float r_co[2])
+{
+       if (sflag & GP_STROKE_2DSPACE) {
+               r_co[0] = pt->x;
+               r_co[1] = pt->y;
+       }
+       else if (sflag & GP_STROKE_2DIMAGE) {
+               const float x = (float)((pt->x * winx) + offsx);
+               const float y = (float)((pt->y * winy) + offsy);
+               
+               r_co[0] = x;
+               r_co[1] = y;
+       }
+       else {
+               const float x = (float)(pt->x / 100 * winx) + offsx;
+               const float y = (float)(pt->y / 100 * winy) + offsy;
+               
+               r_co[0] = x;
+               r_co[1] = y;
+       }
+}
+
+/* ----------- Volumetric Strokes --------------- */
+
+/* draw a 2D buffer stroke in "volumetric" style
+ * NOTE: the stroke buffer doesn't have any coordinate offsets/transforms
+ */
+static void gp_draw_stroke_volumetric_buffer(tGPspoint *points, int totpoints, short thickness, short dflag, short sflag)
+{
+       GLUquadricObj *qobj = gluNewQuadric();
+       float modelview[4][4];
+       
+       tGPspoint *pt;
+       int i;
+       
+       /* error checking */
+       if ((points == NULL) || (totpoints <= 0))
+               return;
+       
+       /* check if buffer can be drawn */
+       if (dflag & (GP_DRAWDATA_ONLY3D | GP_DRAWDATA_ONLYV2D))
+               return;
+       
+       /* get basic matrix - should be camera space (i.e "identity") */
+       glGetFloatv(GL_MODELVIEW_MATRIX, (float *)modelview);
+       
+       /* draw points */
+       glPushMatrix();
+       
+       for (i = 0, pt = points; i < totpoints; i++, pt++) {
+               /* set the transformed position */
+               // TODO: scale should change based on zoom level, which requires proper translation mult too!
+               modelview[3][0] = pt->x;
+               modelview[3][1] = pt->y;
+               
+               glLoadMatrixf((float *)modelview);
+               
+               /* draw the disk using the current state... */
+               gluDisk(qobj, 0.0,  pt->pressure * thickness, 32, 1);
+               
+               
+               modelview[3][0] = modelview[3][1] = 0.0f;
+       }
+       
+       glPopMatrix();
+       gluDeleteQuadric(qobj);
+}
+
+/* draw a 2D strokes in "volumetric" style */
+static void gp_draw_stroke_volumetric_2d(bGPDspoint *points, int totpoints, short thickness, short dflag, short sflag,
+                                         int offsx, int offsy, int winx, int winy)
+{
+       GLUquadricObj *qobj = gluNewQuadric();
+       float modelview[4][4];
+       float baseloc[3];
+       
+       bGPDspoint *pt;
+       int i;
+       
+       
+       /* get basic matrix */
+       glGetFloatv(GL_MODELVIEW_MATRIX, (float *)modelview);
+       copy_v3_v3(baseloc, modelview[3]);
+       
+       /* draw points */
+       glPushMatrix();
+       
+       for (i = 0, pt = points; i < totpoints; i++, pt++) {
+               /* set the transformed position */
+               float co[2];
+               
+               gp_calc_2d_stroke_xy(pt, sflag, offsx, offsy, winx, winy, co);
+               translate_m4(modelview, co[0], co[1], 0.0f);
+               
+               glLoadMatrixf((float *)modelview);
+               
+               /* draw the disk using the current state... */
+               gluDisk(qobj, 0.0,  pt->pressure * thickness, 32, 1);
+               
+               /* restore matrix */
+               copy_v3_v3(modelview[3], baseloc);
+       }
+       
+       glPopMatrix();
+       gluDeleteQuadric(qobj);
+}
+
+/* draw a 3D stroke in "volumetric" style */
+static void gp_draw_stroke_volumetric_3d(bGPDspoint *points, int totpoints, short thickness, short dflag, short sflag)
+{
+       GLUquadricObj *qobj = gluNewQuadric();
+       
+       float base_modelview[4][4], modelview[4][4];
+       float base_loc[3];
+       
+       bGPDspoint *pt;
+       int i;
+       
+       
+       /* Get the basic modelview matrix we use for performing calculations */
+       glGetFloatv(GL_MODELVIEW_MATRIX, (float *)base_modelview);
+       copy_v3_v3(base_loc, base_modelview[3]);
+       
+       /* Create the basic view-aligned billboard matrix we're going to actually draw qobj with:
+        * - We need to knock out the rotation so that we are 
+        *   simply left with a camera-facing billboard
+        * - The scale factors here are chosen so that the thickness
+        *   is relatively reasonable. Otherwise, it gets far too
+        *   large!
+        */
+       scale_m4_fl(modelview, 0.1f);
+       
+       /* draw each point as a disk... */
+       glPushMatrix();
+       
+       for (i = 0, pt = points; i < totpoints && pt; i++, pt++) {
+               /* apply translation to base_modelview, so that the translated point is put in the right place */
+               translate_m4(base_modelview, pt->x, pt->y, pt->z);
+               
+               /* copy the translation component to the billboard matrix we're going to use,
+                * then reset the base matrix to the original values so that we can do the same
+                * for the next point without accumulation/pollution effects
+                */
+               copy_v3_v3(modelview[3], base_modelview[3]); /* copy offset value */
+               copy_v3_v3(base_modelview[3], base_loc);     /* restore */
+               
+               /* apply our billboard matrix for drawing... */
+               glLoadMatrixf((float *)modelview);
+               
+               /* draw the disk using the current state... */
+               gluDisk(qobj, 0.0,  pt->pressure * thickness, 32, 1);
+       }
+       
+       glPopMatrix();
+       gluDeleteQuadric(qobj);
+}
+
+
+/* --------------- Stroke Fills ----------------- */
+
+/* draw fills for shapes */
+static void gp_draw_stroke_fill(bGPDspoint *points, int totpoints, short thickness, short dflag, short sflag,
+                                int offsx, int offsy, int winx, int winy)
+{
+       bGPDspoint *pt;
+       int i;
+       
+       BLI_assert(totpoints >= 3);
+       
+       /* As an initial implementation, we use the OpenGL filled polygon drawing 
+        * here since it's the easiest option to implement for this case. It does
+        * come with limitations (notably for concave shapes), though it shouldn't
+        * be much of an issue in most cases.
+        */
+       glBegin(GL_POLYGON);
+       
+       for (i = 0, pt = points; i < totpoints; i++, pt++) {
+               if (sflag & GP_STROKE_3DSPACE) {
+                       glVertex3fv(&pt->x);
+               }
+               else {
+                       float co[2];
+                       
+                       gp_calc_2d_stroke_xy(pt, sflag, offsx, offsy, winx, winy, co);
+                       glVertex2fv(co);
+               }
+       }
+       
+       glEnd();
+}
+
 /* ----- Existing Strokes Drawing (3D and Point) ------ */
 
 /* draw a given stroke - just a single dot (only one point) */
@@ -158,18 +359,7 @@ static void gp_draw_stroke_point(bGPDspoint *points, short thickness, short dfla
                float co[2];
                
                /* get coordinates of point */
-               if (sflag & GP_STROKE_2DSPACE) {
-                       co[0] = points->x;
-                       co[1] = points->y;
-               }
-               else if (sflag & GP_STROKE_2DIMAGE) {
-                       co[0] = (points->x * winx) + offsx;
-                       co[1] = (points->y * winy) + offsy;
-               }
-               else {
-                       co[0] = (points->x / 100 * winx) + offsx;
-                       co[1] = (points->y / 100 * winy) + offsy;
-               }
+               gp_calc_2d_stroke_xy(points, sflag, offsx, offsy, winx, winy, co);
                
                /* if thickness is less than GP_DRAWTHICKNESS_SPECIAL, simple dot looks ok
                 *  - also mandatory in if Image Editor 'image-based' dot
@@ -198,7 +388,7 @@ static void gp_draw_stroke_point(bGPDspoint *points, short thickness, short dfla
 }
 
 /* draw a given stroke in 3d (i.e. in 3d-space), using simple ogl lines */
-static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness, short debug)
+static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness, bool debug, short sflag)
 {
        bGPDspoint *pt;
        float curpressure = points[0].pressure;
@@ -231,6 +421,7 @@ static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness
        glEnd();
        
        /* draw debug points of curve on top? */
+       /* XXX: for now, we represent "selected" strokes in the same way as debug, which isn't used anymore */
        if (debug) {
                glBegin(GL_POINTS);
                for (i = 0, pt = points; i < totpoints && pt; i++, pt++)
@@ -242,8 +433,8 @@ static void gp_draw_stroke_3d(bGPDspoint *points, int totpoints, short thickness
 /* ----- Fancy 2D-Stroke Drawing ------ */
 
 /* draw a given stroke in 2d */
-static void gp_draw_stroke(bGPDspoint *points, int totpoints, short thickness_s, short dflag, short sflag,
-                           short debug, int offsx, int offsy, int winx, int winy)
+static void gp_draw_stroke_2d(bGPDspoint *points, int totpoints, short thickness_s, short dflag, short sflag,
+                              bool debug, int offsx, int offsy, int winx, int winy)
 {
        /* otherwise thickness is twice that of the 3D view */
        float thickness = (float)thickness_s * 0.5f;
@@ -259,21 +450,10 @@ static void gp_draw_stroke(bGPDspoint *points, int totpoints, short thickness_s,
                
                glBegin(GL_LINE_STRIP);
                for (i = 0, pt = points; i < totpoints && pt; i++, pt++) {
-                       if (sflag & GP_STROKE_2DSPACE) {
-                               glVertex2f(pt->x, pt->y);
-                       }
-                       else if (sflag & GP_STROKE_2DIMAGE) {
-                               const float x = (pt->x * winx) + offsx;
-                               const float y = (pt->y * winy) + offsy;
-                               
-                               glVertex2f(x, y);
-                       }
-                       else {
-                               const float x = (pt->x / 100 * winx) + offsx;
-                               const float y = (pt->y / 100 * winy) + offsy;
-                               
-                               glVertex2f(x, y);
-                       }
+                       float co[2];
+                       
+                       gp_calc_2d_stroke_xy(pt, sflag, offsx, offsy, winx, winy, co);
+                       glVertex2fv(co);
                }
                glEnd();
        }
@@ -297,22 +477,8 @@ static void gp_draw_stroke(bGPDspoint *points, int totpoints, short thickness_s,
                        float pthick;           /* thickness at segment point */
                        
                        /* get x and y coordinates from points */
-                       if (sflag & GP_STROKE_2DSPACE) {
-                               s0[0] = pt1->x;      s0[1] = pt1->y;
-                               s1[0] = pt2->x;      s1[1] = pt2->y;
-                       }
-                       else if (sflag & GP_STROKE_2DIMAGE) {
-                               s0[0] = (pt1->x * winx) + offsx;
-                               s0[1] = (pt1->y * winy) + offsy;
-                               s1[0] = (pt2->x * winx) + offsx;
-                               s1[1] = (pt2->y * winy) + offsy;
-                       }
-                       else {
-                               s0[0] = (pt1->x / 100 * winx) + offsx;
-                               s0[1] = (pt1->y / 100 * winy) + offsy;
-                               s1[0] = (pt2->x / 100 * winx) + offsx;
-                               s1[1] = (pt2->y / 100 * winy) + offsy;
-                       }
+                       gp_calc_2d_stroke_xy(pt1, sflag, offsx, offsy, winx, winy, s0);
+                       gp_calc_2d_stroke_xy(pt2, sflag, offsx, offsy, winx, winy, s1);
                        
                        /* calculate gradient and normal - 'angle'=(ny/nx) */
                        m1[1] = s1[1] - s0[1];
@@ -446,52 +612,57 @@ static void gp_draw_stroke(bGPDspoint *points, int totpoints, short thickness_s,
                
                glBegin(GL_POINTS);
                for (i = 0, pt = points; i < totpoints && pt; i++, pt++) {
-                       if (sflag & GP_STROKE_2DSPACE) {
-                               glVertex2fv(&pt->x);
-                       }
-                       else if (sflag & GP_STROKE_2DIMAGE) {
-                               const float x = (float)((pt->x * winx) + offsx);
-                               const float y = (float)((pt->y * winy) + offsy);
-                               
-                               glVertex2f(x, y);
-                       }
-                       else {
-                               const float x = (float)(pt->x / 100 * winx) + offsx;
-                               const float y = (float)(pt->y / 100 * winy) + offsy;
-                               
-                               glVertex2f(x, y);
-                       }
+                       float co[2];
+                       
+                       gp_calc_2d_stroke_xy(pt, sflag, offsx, offsy, winx, winy, co);
+                       glVertex2fv(co);
                }
                glEnd();
        }
 }
 
-/* ----- General Drawing ------ */
+/* ----- Strokes Drawing ------ */
+
+/* Helper for doing all the checks on whether a stroke can be drawn */
+static bool gp_can_draw_stroke(const bGPDstroke *gps, const int dflag)
+{
+       /* skip stroke if it isn't in the right display space for this drawing context */
+       /* 1) 3D Strokes */
+       if ((dflag & GP_DRAWDATA_ONLY3D) && !(gps->flag & GP_STROKE_3DSPACE))
+               return false;
+       if (!(dflag & GP_DRAWDATA_ONLY3D) && (gps->flag & GP_STROKE_3DSPACE))
+               return false;
+               
+       /* 2) Screen Space 2D Strokes */
+       if ((dflag & GP_DRAWDATA_ONLYV2D) && !(gps->flag & GP_STROKE_2DSPACE))
+               return false;
+       if (!(dflag & GP_DRAWDATA_ONLYV2D) && (gps->flag & GP_STROKE_2DSPACE))
+               return false;
+               
+       /* 3) Image Space (2D) */
+       if ((dflag & GP_DRAWDATA_ONLYI2D) && !(gps->flag & GP_STROKE_2DIMAGE))
+               return false;
+       if (!(dflag & GP_DRAWDATA_ONLYI2D) && (gps->flag & GP_STROKE_2DIMAGE))
+               return false;
+               
+               
+       /* skip stroke if it doesn't have any valid data */
+       if ((gps->points == NULL) || (gps->totpoints < 1))
+               return false;
+               
+       /* stroke can be drawn */
+       return true;
+}
 
 /* draw a set of strokes */
 static void gp_draw_strokes(bGPDframe *gpf, int offsx, int offsy, int winx, int winy, int dflag,
-                            short debug, short lthick, const float color[4])
+                            bool debug, short lthick, const float color[4], const float fill_color[4])
 {
        bGPDstroke *gps;
        
-       /* set color first (may need to reset it again later too) */
-       glColor4fv(color);
-       
        for (gps = gpf->strokes.first; gps; gps = gps->next) {
-               /* check if stroke can be drawn - checks here generally fall into pairs */
-               if ((dflag & GP_DRAWDATA_ONLY3D) && !(gps->flag & GP_STROKE_3DSPACE))
-                       continue;
-               if (!(dflag & GP_DRAWDATA_ONLY3D) && (gps->flag & GP_STROKE_3DSPACE))
-                       continue;
-               if ((dflag & GP_DRAWDATA_ONLYV2D) && !(gps->flag & GP_STROKE_2DSPACE))
-                       continue;
-               if (!(dflag & GP_DRAWDATA_ONLYV2D) && (gps->flag & GP_STROKE_2DSPACE))
-                       continue;
-               if ((dflag & GP_DRAWDATA_ONLYI2D) && !(gps->flag & GP_STROKE_2DIMAGE))
-                       continue;
-               if (!(dflag & GP_DRAWDATA_ONLYI2D) && (gps->flag & GP_STROKE_2DIMAGE))
-                       continue;
-               if ((gps->points == NULL) || (gps->totpoints < 1))
+               /* check if stroke can be drawn */
+               if (gp_can_draw_stroke(gps, dflag) == false)
                        continue;
                
                /* check which stroke-drawer to use */
@@ -513,11 +684,27 @@ static void gp_draw_strokes(bGPDframe *gpf, int offsx, int offsy, int winx, int
 #endif
                        }
                        
-                       if (gps->totpoints == 1) {
-                               gp_draw_stroke_point(gps->points, lthick, dflag, gps->flag, offsx, offsy, winx, winy);
+                       /* 3D Fill */
+                       if ((dflag & GP_DRAWDATA_FILL) && (gps->totpoints >= 3)) {
+                               glColor4fv(fill_color);
+                               gp_draw_stroke_fill(gps->points, gps->totpoints, lthick, dflag, gps->flag, offsx, offsy, winx, winy);
+                       }
+                       
+                       /* 3D Stroke */
+                       glColor4fv(color);
+                       
+                       if (dflag & GP_DRAWDATA_VOLUMETRIC) {
+                               /* volumetric stroke drawing */
+                               gp_draw_stroke_volumetric_3d(gps->points, gps->totpoints, lthick, dflag, gps->flag);
                        }
                        else {
-                               gp_draw_stroke_3d(gps->points, gps->totpoints, lthick, debug);
+                               /* 3D Lines - OpenGL primitives-based */
+                               if (gps->totpoints == 1) {
+                                       gp_draw_stroke_point(gps->points, lthick, dflag, gps->flag, offsx, offsy, winx, winy);
+                               }
+                               else {
+                                       gp_draw_stroke_3d(gps->points, gps->totpoints, lthick, debug, gps->flag);
+                               }
                        }
                        
                        if (no_xray) {
@@ -532,14 +719,229 @@ static void gp_draw_strokes(bGPDframe *gpf, int offsx, int offsy, int winx, int
                        }
                }
                else {
-                       if (gps->totpoints == 1) {
-                               gp_draw_stroke_point(gps->points, lthick, dflag, gps->flag, offsx, offsy, winx, winy);
+                       /* 2D - Fill */
+                       if ((dflag & GP_DRAWDATA_FILL) && (gps->totpoints >= 3)) {
+                               gp_draw_stroke_fill(gps->points, gps->totpoints, lthick, dflag, gps->flag, offsx, offsy, winx, winy);
+                       }
+                       
+                       /* 2D Strokes... */
+                       glColor4fv(color);
+                       
+                       if (dflag & GP_DRAWDATA_VOLUMETRIC) {
+                               /* blob/disk-based "volumetric" drawing */
+                               gp_draw_stroke_volumetric_2d(gps->points, gps->totpoints, lthick, dflag, gps->flag, offsx, offsy, winx, winy);
+                       }
+                       else {
+                               /* normal 2D strokes */
+                               if (gps->totpoints == 1) {
+                                       gp_draw_stroke_point(gps->points, lthick, dflag, gps->flag, offsx, offsy, winx, winy);
+                               }
+                               else {
+                                       gp_draw_stroke_2d(gps->points, gps->totpoints, lthick, dflag, gps->flag, debug, offsx, offsy, winx, winy);
+                               }
+                       }
+               }
+       }
+}
+
+/* Draw selected verts for strokes being edited */
+static void gp_draw_strokes_edit(bGPDframe *gpf, int offsx, int offsy, int winx, int winy, short dflag, const float tcolor[3])
+{
+       bGPDstroke *gps;
+       
+       const int no_xray = (dflag & GP_DRAWDATA_NO_XRAY);
+       int mask_orig = 0;
+       
+       /* set up depth masks... */
+       if (dflag & GP_DRAWDATA_ONLY3D) {
+               if (no_xray) {
+                       glGetIntegerv(GL_DEPTH_WRITEMASK, &mask_orig);
+                       glDepthMask(0);
+                       glEnable(GL_DEPTH_TEST);
+                       
+                       /* first arg is normally rv3d->dist, but this isn't
+                        * available here and seems to work quite well without */
+                       bglPolygonOffset(1.0f, 1.0f);
+#if 0
+                       glEnable(GL_POLYGON_OFFSET_LINE);
+                       glPolygonOffset(-1.0f, -1.0f);
+#endif
+               }
+       }
+       
+       
+       /* draw stroke verts */
+       for (gps = gpf->strokes.first; gps; gps = gps->next) {
+               bGPDspoint *pt;
+               float vsize, bsize;
+               int i;
+               
+               /* check if stroke can be drawn */
+               if (gp_can_draw_stroke(gps, dflag) == false)
+                       continue;
+               
+               /* Optimisation: only draw points for selected strokes
+                * We assume that selected points can only occur in
+                * strokes that are selected too.
+                */
+               if ((gps->flag & GP_STROKE_SELECT) == 0)
+                       continue;
+                       
+               /* Get size of verts:
+                * - The selected state needs to be larger than the unselected state so that
+                *   they stand out more.
+                * - We use the theme setting for size of the unselected verts
+                */
+               bsize = UI_GetThemeValuef(TH_VERTEX_SIZE);
+               if ((int)bsize > 8) {
+                       vsize = 10.0f;
+                       bsize = 8.0f;
+               }
+               else {
+                       vsize = bsize + 2;
+               }
+               
+               /* First Pass: Draw all the verts (i.e. these become the unselected state) */
+               if (tcolor != NULL) {
+                       /* for now, we assume that the base color of the points is not too close to the real color */
+                       glColor3fv(tcolor);
+               }
+               else {
+                       /* this doesn't work well with the default theme and black strokes... */
+                       UI_ThemeColor(TH_VERTEX);
+               }
+               glPointSize(bsize);
+               
+               glBegin(GL_POINTS);
+               for (i = 0, pt = gps->points; i < gps->totpoints && pt; i++, pt++) {
+                       if (gps->flag & GP_STROKE_3DSPACE) {
+                               glVertex3fv(&pt->x);
                        }
                        else {
-                               gp_draw_stroke(gps->points, gps->totpoints, lthick, dflag, gps->flag, debug, offsx, offsy, winx, winy);
+                               float co[2];
+                               
+                               gp_calc_2d_stroke_xy(pt, gps->flag, offsx, offsy, winx, winy, co);
+                               glVertex2fv(co);
+                       }
+               }
+               glEnd();
+               
+               
+               /* Second Pass: Draw only verts which are selected */
+               UI_ThemeColor(TH_VERTEX_SELECT);
+               glPointSize(vsize);
+               
+               glBegin(GL_POINTS);
+               for (i = 0, pt = gps->points; i < gps->totpoints && pt; i++, pt++) {
+                       if (pt->flag & GP_SPOINT_SELECT) {
+                               if (gps->flag & GP_STROKE_3DSPACE) {
+                                       glVertex3fv(&pt->x);
+                               }
+                               else {
+                                       float co[2];
+                                       
+                                       gp_calc_2d_stroke_xy(pt, gps->flag, offsx, offsy, winx, winy, co);
+                                       glVertex2fv(co);
+                               }
+                       }
+               }
+               glEnd();
+       }
+       
+       
+       /* clear depth mask */
+       if (dflag & GP_DRAWDATA_ONLY3D) {
+               if (no_xray) {
+                       glDepthMask(mask_orig);
+                       glDisable(GL_DEPTH_TEST);
+                       
+                       bglPolygonOffset(0.0, 0.0);
+#if 0
+                       glDisable(GL_POLYGON_OFFSET_LINE);
+                       glPolygonOffset(0, 0);
+#endif
+               }
+       }
+}
+
+/* ----- General Drawing ------ */
+
+/* draw onion-skinning for a layer */
+static void gp_draw_onionskins(bGPDlayer *gpl, bGPDframe *gpf, int offsx, int offsy, int winx, int winy, 
+                               int cfra, int dflag, short debug, short lthick)
+{
+       const float alpha = gpl->color[3];
+       float color[4];
+       
+       /* 1) Draw Previous Frames First */
+       if (gpl->flag & GP_LAYER_GHOST_PREVCOL) {
+               copy_v3_v3(color, gpl->gcolor_prev);
+       }
+       else {
+               copy_v3_v3(color, gpl->color);
+       }
+       
+       if (gpl->gstep) {
+               bGPDframe *gf;
+               float fac;
+               
+               /* draw previous frames first */
+               for (gf = gpf->prev; gf; gf = gf->prev) {
+                       /* check if frame is drawable */
+                       if ((gpf->framenum - gf->framenum) <= gpl->gstep) {
+                               /* alpha decreases with distance from curframe index */
+                               fac = 1.0f - ((float)(gpf->framenum - gf->framenum) / (float)(gpl->gstep + 1));
+                               color[3] = alpha * fac * 0.66f;
+                               gp_draw_strokes(gf, offsx, offsy, winx, winy, dflag, debug, lthick, color, color);
+                       }
+                       else 
+                               break;
+               }
+       }
+       else {
+               /* draw the strokes for the ghost frames (at half of the alpha set by user) */
+               if (gpf->prev) {
+                       color[3] = (alpha / 7);
+                       gp_draw_strokes(gpf->prev, offsx, offsy, winx, winy, dflag, debug, lthick, color, color);
+               }
+       }
+       
+       
+       /* 2) Now draw next frames */
+       if (gpl->flag & GP_LAYER_GHOST_NEXTCOL) {
+               copy_v3_v3(color, gpl->gcolor_next);
+       }
+       else {
+               copy_v3_v3(color, gpl->color);
+       }
+       
+       if (gpl->gstep_next) {
+               bGPDframe *gf;
+               float fac;
+               
+               /* now draw next frames */
+               for (gf = gpf->next; gf; gf = gf->next) {
+                       /* check if frame is drawable */
+                       if ((gf->framenum - gpf->framenum) <= gpl->gstep_next) {
+                               /* alpha decreases with distance from curframe index */
+                               fac = 1.0f - ((float)(gf->framenum - gpf->framenum) / (float)(gpl->gstep_next + 1));
+                               color[3] = alpha * fac * 0.66f;
+                               gp_draw_strokes(gf, offsx, offsy, winx, winy, dflag, debug, lthick, color, color);
                        }
+                       else 
+                               break;
+               }
+       }
+       else {
+               /* draw the strokes for the ghost frames (at half of the alpha set by user) */
+               if (gpf->next) {
+                       color[3] = (alpha / 4);
+                       gp_draw_strokes(gpf->next, offsx, offsy, winx, winy, dflag, debug, lthick, color, color);
                }
        }
+       
+       /* 3) restore alpha */
+       glColor4fv(gpl->color);
 }
 
 /* draw grease-pencil datablock */
@@ -561,9 +963,8 @@ static void gp_draw_data(bGPdata *gpd, int offsx, int offsy, int winx, int winy,
        for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
                bGPDframe *gpf;
                
-               short debug = (gpl->flag & GP_LAYER_DRAWDEBUG) ? 1 : 0;
+               bool debug = (gpl->flag & GP_LAYER_DRAWDEBUG) ? true : false;
                short lthick = gpl->thickness;
-               float color[4], tcolor[4];
                
                /* don't draw layer if hidden */
                if (gpl->flag & GP_LAYER_HIDE) 
@@ -576,72 +977,54 @@ static void gp_draw_data(bGPdata *gpd, int offsx, int offsy, int winx, int winy,
                
                /* set color, stroke thickness, and point size */
                glLineWidth(lthick);
-               copy_v4_v4(color, gpl->color); // just for copying 4 array elements
-               copy_v4_v4(tcolor, gpl->color); // additional copy of color (for ghosting)
-               glColor4fv(color);
                glPointSize((float)(gpl->thickness + 2));
                
-               /* apply xray layer setting */
-               if (gpl->flag & GP_LAYER_NO_XRAY) dflag |=  GP_DRAWDATA_NO_XRAY;
-               else dflag &= ~GP_DRAWDATA_NO_XRAY;
+               /* Add layer drawing settings to the set of "draw flags" 
+                * NOTE: If the setting doesn't apply, it *must* be cleared,
+                *       as dflag's carry over from the previous layer
+                */
+#define GP_DRAWFLAG_APPLY(condition, draw_flag_value)     { \
+                       if (condition) dflag |= (draw_flag_value);      \
+                       else           dflag &= ~(draw_flag_value);     \
+               } (void)0
+               
+               /* xray... */
+               GP_DRAWFLAG_APPLY((gpl->flag & GP_LAYER_NO_XRAY), GP_DRAWDATA_NO_XRAY);
+               
+               /* volumetric strokes... */
+               GP_DRAWFLAG_APPLY((gpl->flag & GP_LAYER_VOLUMETRIC), GP_DRAWDATA_VOLUMETRIC);
+               
+               /* fill strokes... */
+               // XXX: this is not a very good limit
+               GP_DRAWFLAG_APPLY((gpl->fill[3] > 0.001f), GP_DRAWDATA_FILL);
+#undef GP_DRAWFLAG_APPLY
                
                /* draw 'onionskins' (frame left + right) */
-               if (gpl->flag & GP_LAYER_ONIONSKIN) {
-                       /* drawing method - only immediately surrounding (gstep = 0),
-                        * or within a frame range on either side (gstep > 0)*/
-                       if (gpl->gstep) {
-                               bGPDframe *gf;
-                               float fac;
-                               
-                               /* draw previous frames first */
-                               for (gf = gpf->prev; gf; gf = gf->prev) {
-                                       /* check if frame is drawable */
-                                       if ((gpf->framenum - gf->framenum) <= gpl->gstep) {
-                                               /* alpha decreases with distance from curframe index */
-                                               fac = 1.0f - ((float)(gpf->framenum - gf->framenum) / (float)(gpl->gstep + 1));
-                                               tcolor[3] = color[3] * fac * 0.66f;
-                                               gp_draw_strokes(gf, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor);
-                                       }
-                                       else 
-                                               break;
-                               }
-                               
-                               /* now draw next frames */
-                               for (gf = gpf->next; gf; gf = gf->next) {
-                                       /* check if frame is drawable */
-                                       if ((gf->framenum - gpf->framenum) <= gpl->gstep) {
-                                               /* alpha decreases with distance from curframe index */
-                                               fac = 1.0f - ((float)(gf->framenum - gpf->framenum) / (float)(gpl->gstep + 1));
-                                               tcolor[3] = color[3] * fac * 0.66f;
-                                               gp_draw_strokes(gf, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor);
-                                       }
-                                       else 
-                                               break;
-                               }
-                               
-                               /* restore alpha */
-                               glColor4fv(color);
-                       }
-                       else {
-                               /* draw the strokes for the ghost frames (at half of the alpha set by user) */
-                               if (gpf->prev) {
-                                       tcolor[3] = (color[3] / 7);
-                                       gp_draw_strokes(gpf->prev, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor);
-                               }
-                               
-                               if (gpf->next) {
-                                       tcolor[3] = (color[3] / 4);
-                                       gp_draw_strokes(gpf->next, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor);
-                               }
-                               
-                               /* restore alpha */
-                               glColor4fv(color);
-                       }
+               if ((gpl->flag & GP_LAYER_ONIONSKIN) && !(dflag & GP_DRAWDATA_NO_ONIONS)) {
+                       /* Drawing method - only immediately surrounding (gstep = 0),
+                        * or within a frame range on either side (gstep > 0)
+                        */
+                       gp_draw_onionskins(gpl, gpf, offsx, offsy, winx, winy, cfra, dflag, debug, lthick);
                }
                
                /* draw the strokes already in active frame */
-               tcolor[3] = color[3];
-               gp_draw_strokes(gpf, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor);
+               gp_draw_strokes(gpf, offsx, offsy, winx, winy, dflag, debug, lthick, gpl->color, gpl->fill);
+               
+               /* Draw verts of selected strokes 
+                *  - when doing OpenGL renders, we don't want to be showing these, as that ends up flickering
+                *      - locked layers can't be edited, so there's no point showing these verts
+                *    as they will have no bearings on what gets edited
+                *  - only show when in editmode, since operators shouldn't work otherwise
+                *    (NOTE: doing it this way means that the toggling editmode shows visible change immediately)
+                */
+               /* XXX: perhaps we don't want to show these when users are drawing... */
+               if ((G.f & G_RENDER_OGL) == 0 &&
+                   (gpl->flag & GP_LAYER_LOCKED) == 0 && 
+                   (gpd->flag & GP_DATA_STROKE_EDITMODE))
+               {
+                       gp_draw_strokes_edit(gpf, offsx, offsy, winx, winy, dflag, 
+                                            (gpl->color[3] < 0.95f) ? gpl->color : NULL);
+               }
                
                /* Check if may need to draw the active stroke cache, only if this layer is the active layer
                 * that is being edited. (Stroke buffer is currently stored in gp-data)
@@ -649,9 +1032,21 @@ static void gp_draw_data(bGPdata *gpd, int offsx, int offsy, int winx, int winy,
                if (ED_gpencil_session_active() && (gpl->flag & GP_LAYER_ACTIVE) &&
                    (gpf->flag & GP_FRAME_PAINT))
                {
+                       /* Set color for drawing buffer stroke - since this may not be set yet */
+                       glColor4fv(gpl->color);
+                       
                        /* Buffer stroke needs to be drawn with a different linestyle
-                        * to help differentiate them from normal strokes. */
-                       gp_draw_stroke_buffer(gpd->sbuffer, gpd->sbuffer_size, lthick, dflag, gpd->sbuffer_sflag);
+                        * to help differentiate them from normal strokes.
+                        * 
+                        * It should also be noted that sbuffer contains temporary point types
+                        * i.e. tGPspoints NOT bGPDspoints
+                        */
+                       if (gpl->flag & GP_LAYER_VOLUMETRIC) {
+                               gp_draw_stroke_volumetric_buffer(gpd->sbuffer, gpd->sbuffer_size, lthick, dflag, gpd->sbuffer_sflag);
+                       }
+                       else {
+                               gp_draw_stroke_buffer(gpd->sbuffer, gpd->sbuffer_size, lthick, dflag, gpd->sbuffer_sflag);
+                       }
                }
        }
        
index f05efff211c3b0d105699a15028ec7c3c4f4fe8b..73b2b033e2404490ecebd51a236369d6455e9a07 100644 (file)
@@ -251,6 +251,23 @@ void ED_gplayer_frames_duplicate(bGPDlayer *gpl)
        }
 }
 
+/* Set keyframe type for selected frames from given gp-layer 
+ * \param type The type of keyframe (eBezTriple_KeyframeType) to set selected frames to
+ */
+void ED_gplayer_frames_keytype_set(bGPDlayer *gpl, short type)
+{
+       bGPDframe *gpf;
+       
+       if (gpl == NULL)
+               return;
+       
+       for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
+               if (gpf->flag & GP_FRAME_SELECT) {
+                       gpf->key_type = type;
+               }
+       }
+}
+
 #if 0 // XXX disabled until grease pencil code stabilises again
 /* -------------------------------------- */
 /* Copy and Paste Tools */
index d305cfed00971b3acd244022a8568cea359e66bb..12612be1548eee90665f95c73c61a846355c5d4d 100644 (file)
@@ -143,13 +143,12 @@ static void gp_drawui_layer(uiLayout *layout, bGPdata *gpd, bGPDlayer *gpl, cons
        /* active */
        block = uiLayoutGetBlock(sub);
        icon = (gpl->flag & GP_LAYER_ACTIVE) ? ICON_RADIOBUT_ON : ICON_RADIOBUT_OFF;
-       but = uiDefIconButBitI(block, UI_BTYPE_TOGGLE, GP_LAYER_ACTIVE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y,
+       but = uiDefIconButBitS(block, UI_BTYPE_TOGGLE, GP_LAYER_ACTIVE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y,
                               &gpl->flag, 0.0, 0.0, 0.0, 0.0, TIP_("Set active layer"));
        UI_but_func_set(but, gp_ui_activelayer_cb, gpd, gpl);
        
        /* locked */
-       icon = (gpl->flag & GP_LAYER_LOCKED) ? ICON_LOCKED : ICON_UNLOCKED;
-       uiItemR(sub, &ptr, "lock", 0, "", icon);
+       uiItemR(sub, &ptr, "lock", 0, "", ICON_NONE);
        
        /* when layer is locked or hidden, only draw header */
        if (gpl->flag & (GP_LAYER_LOCKED | GP_LAYER_HIDE)) {
@@ -157,7 +156,7 @@ static void gp_drawui_layer(uiLayout *layout, bGPdata *gpd, bGPDlayer *gpl, cons
                
                /* visibility button (only if hidden but not locked!) */
                if ((gpl->flag & GP_LAYER_HIDE) && !(gpl->flag & GP_LAYER_LOCKED))
-                       uiItemR(sub, &ptr, "hide", 0, "", ICON_RESTRICT_VIEW_ON);
+                       uiItemR(sub, &ptr, "hide", 0, "", ICON_NONE);
                
                /* name */
                if (gpl->flag & GP_LAYER_HIDE)
@@ -182,7 +181,7 @@ static void gp_drawui_layer(uiLayout *layout, bGPdata *gpd, bGPDlayer *gpl, cons
        else {
                /* draw rest of header -------------------------------- */
                /* visibility button */
-               uiItemR(sub, &ptr, "hide", 0, "", ICON_RESTRICT_VIEW_OFF);
+               uiItemR(sub, &ptr, "hide", 0, "", ICON_NONE);
                
                /* frame locking */
                /* TODO: this needs its own icons... */
index 133344489410661289de3216c72019137aa4b6bf..a6efeef8716199aa66c3c14ea3263c9a6a197e42 100644 (file)
 /* ************************************************ */
 /* Context Wrangling... */
 
-/* Get pointer to active Grease Pencil datablock, and an RNA-pointer to trace back to whatever owns it */
-bGPdata **ED_gpencil_data_get_pointers(const bContext *C, PointerRNA *ptr)
+/* Get pointer to active Grease Pencil datablock, and an RNA-pointer to trace back to whatever owns it,
+ * when context info is not available.
+ */
+bGPdata **ED_gpencil_data_get_pointers_direct(ID *screen_id, Scene *scene, ScrArea *sa, Object *ob, PointerRNA *ptr)
 {
-       ID *screen_id = (ID *)CTX_wm_screen(C);
-       Scene *scene = CTX_data_scene(C);
-       ScrArea *sa = CTX_wm_area(C);
-
        /* if there's an active area, check if the particular editor may
         * have defined any special Grease Pencil context for editing...
         */
        if (sa) {
+               SpaceLink *sl = sa->spacedata.first;
+               
                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 */
                        {
-                               Object *ob = CTX_data_active_object(C);
-
-                               /* TODO: we can include other data-types such as bones later if need be... */
-
-                               /* just in case no active/selected object */
-                               if (ob && (ob->flag & SELECT)) {
-                                       /* for now, as long as there's an object, default to using that in 3D-View */
-                                       if (ptr) RNA_id_pointer_create(&ob->id, ptr);
+                               /* default to using scene's data, unless it doesn't exist (and object's does instead) */
+                               /* XXX: this will require a toggle switch later to be more predictable */
+                               bool scene_ok = (scene != NULL);
+                               bool ob_ok    = ((ob) && (ob->flag & SELECT) && (ob->gpd));
+                               
+                               if (ob_ok || !scene_ok) {
+                                       /* Object Case (not good for users):
+                                        * - For existing files with object-level already, 
+                                        *   or where user has explicitly assigned to object,
+                                        *   we can use the object as the host...
+                                        *
+                                        * - If there is no scene data provided (rare/impossible)
+                                        *   we will also be forced to use the object
+                                        */
+                                       if (ptr) RNA_id_pointer_create((ID *)ob, ptr);
                                        return &ob->gpd;
                                }
+                               else {
+                                       /* Scene Case (default):
+                                        * This is the new (as of 2014-Oct-13, for 2.73) default setting
+                                        * which should work better for most users.
+                                        */
+                                       if (ptr) RNA_id_pointer_create((ID *)scene, ptr);
+                                       return &scene->gpd;
+                               }
                                break;
                        }
                        case SPACE_NODE: /* Nodes Editor */
                        {
-                               SpaceNode *snode = (SpaceNode *)CTX_wm_space_data(C);
+                               SpaceNode *snode = (SpaceNode *)sl;
 
                                /* return the GP data for the active node block/node */
                                if (snode && snode->nodetree) {
@@ -128,7 +144,7 @@ bGPdata **ED_gpencil_data_get_pointers(const bContext *C, PointerRNA *ptr)
                        }
                        case SPACE_SEQ: /* Sequencer */
                        {
-                               SpaceSeq *sseq = (SpaceSeq *)CTX_wm_space_data(C);
+                               SpaceSeq *sseq = (SpaceSeq *)sl;
 
                                /* for now, Grease Pencil data is associated with the space (actually preview region only) */
                                /* XXX our convention for everything else is to link to data though... */
@@ -137,7 +153,7 @@ bGPdata **ED_gpencil_data_get_pointers(const bContext *C, PointerRNA *ptr)
                        }
                        case SPACE_IMAGE: /* Image/UV Editor */
                        {
-                               SpaceImage *sima = (SpaceImage *)CTX_wm_space_data(C);
+                               SpaceImage *sima = (SpaceImage *)sl;
 
                                /* for now, Grease Pencil data is associated with the space... */
                                /* XXX our convention for everything else is to link to data though... */
@@ -146,7 +162,7 @@ bGPdata **ED_gpencil_data_get_pointers(const bContext *C, PointerRNA *ptr)
                        }
                        case SPACE_CLIP: /* Nodes Editor */
                        {
-                               SpaceClip *sc = (SpaceClip *)CTX_wm_space_data(C);
+                               SpaceClip *sc = (SpaceClip *)sl;
                                MovieClip *clip = ED_space_clip_get_clip(sc);
 
                                if (clip) {
@@ -180,6 +196,26 @@ bGPdata **ED_gpencil_data_get_pointers(const bContext *C, PointerRNA *ptr)
        return (scene) ? &scene->gpd : NULL;
 }
 
+/* Get pointer to active Grease Pencil datablock, and an RNA-pointer to trace back to whatever owns it */
+bGPdata **ED_gpencil_data_get_pointers(const bContext *C, PointerRNA *ptr)
+{
+       ID *screen_id = (ID *)CTX_wm_screen(C);
+       Scene *scene = CTX_data_scene(C);
+       ScrArea *sa = CTX_wm_area(C);
+       Object *ob = CTX_data_active_object(C);
+       
+       return ED_gpencil_data_get_pointers_direct(screen_id, scene, sa, ob, ptr);
+}
+
+/* -------------------------------------------------------- */
+
+/* Get the active Grease Pencil datablock, when context is not available */
+bGPdata *ED_gpencil_data_get_active_direct(ID *screen_id, Scene *scene, ScrArea *sa, Object *ob)
+{
+       bGPdata **gpd_ptr = ED_gpencil_data_get_pointers_direct(screen_id, scene, sa, ob, NULL);
+       return (gpd_ptr) ? *(gpd_ptr) : NULL;
+}
+
 /* Get the active Grease Pencil datablock */
 bGPdata *ED_gpencil_data_get_active(const bContext *C)
 {
@@ -187,6 +223,8 @@ bGPdata *ED_gpencil_data_get_active(const bContext *C)
        return (gpd_ptr) ? *(gpd_ptr) : NULL;
 }
 
+/* -------------------------------------------------------- */
+
 bGPdata *ED_gpencil_data_get_active_v3d(Scene *scene, View3D *v3d)
 {
        Base *base = scene->basact;
@@ -211,6 +249,15 @@ static int gp_add_poll(bContext *C)
        return ED_gpencil_data_get_pointers(C, NULL) != NULL;
 }
 
+/* poll callback for checking if there is an active layer */
+static int gp_active_layer_poll(bContext *C)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *gpl = gpencil_layer_getactive(gpd);
+       
+       return (gpl != NULL);
+}
+
 /* ******************* Add New Data ************************ */
 
 /* add new datablock - wrapper around API */
@@ -327,13 +374,268 @@ void GPENCIL_OT_layer_add(wmOperatorType *ot)
        ot->name = "Add New Layer";
        ot->idname = "GPENCIL_OT_layer_add";
        ot->description = "Add new Grease Pencil layer for the active Grease Pencil datablock";
+       
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
+       
        /* callbacks */
        ot->exec = gp_layer_add_exec;
        ot->poll = gp_add_poll;
 }
 
+/* ******************* Remove Active Layer ************************* */
+
+static int gp_layer_remove_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *gpl = gpencil_layer_getactive(gpd);
+       
+       /* sanity checks */
+       if (ELEM(NULL, gpd, gpl))
+               return OPERATOR_CANCELLED;
+       
+       if (gpl->flag & GP_LAYER_LOCKED) {
+               BKE_report(op->reports, RPT_ERROR, "Cannot delete locked layers");
+               return OPERATOR_CANCELLED;
+       }       
+       
+       /* make the layer before this the new active layer 
+        * - use the one after if this is the first
+        * - if this is the only layer, this naturally becomes NULL
+        */
+       if (gpl->prev)
+               gpencil_layer_setactive(gpd, gpl->prev);
+       else
+               gpencil_layer_setactive(gpd, gpl->next);
+               
+       /* delete the layer now... */
+       gpencil_layer_delete(gpd, gpl);
+       
+       /* notifiers */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_layer_remove(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Remove Layer";
+       ot->idname = "GPENCIL_OT_layer_remove";
+       ot->description = "Remove active Grease Pencil layer";
+       
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* callbacks */
+       ot->exec = gp_layer_remove_exec;
+       ot->poll = gp_active_layer_poll;
+}
+
+/* ******************* Move Layer Up/Down ************************** */
+
+enum {
+       GP_LAYER_MOVE_UP   = -1,
+       GP_LAYER_MOVE_DOWN = 1
+};
+
+static int gp_layer_move_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *gpl = gpencil_layer_getactive(gpd);
+       
+       int direction = RNA_enum_get(op->ptr, "type");
+       
+       /* sanity checks */
+       if (ELEM(NULL, gpd, gpl))
+               return OPERATOR_CANCELLED;
+               
+       /* up or down? */
+       if (direction == GP_LAYER_MOVE_UP) {
+               /* up */
+               BLI_remlink(&gpd->layers, gpl);
+               BLI_insertlinkbefore(&gpd->layers, gpl->prev, gpl);
+       }
+       else {
+               /* down */
+               BLI_remlink(&gpd->layers, gpl);
+               BLI_insertlinkafter(&gpd->layers, gpl->next, gpl);
+       }
+       
+       /* notifiers */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_layer_move(wmOperatorType *ot)
+{
+       static EnumPropertyItem slot_move[] = {
+               {GP_LAYER_MOVE_UP, "UP", 0, "Up", ""},
+               {GP_LAYER_MOVE_DOWN, "DOWN", 0, "Down", ""},
+               {0, NULL, 0, NULL, NULL}
+       };
+
+       /* identifiers */
+       ot->name = "Move Grease Pencil Layer";
+       ot->idname = "GPENCIL_OT_layer_move";
+       ot->description = "Move the active Grease Pencil layer up/down in the list";
+
+       /* api callbacks */
+       ot->exec = gp_layer_move_exec;
+       ot->poll = gp_active_layer_poll;
+
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+       ot->prop = RNA_def_enum(ot->srna, "type", slot_move, 0, "Type", "");
+}
+
+/* ************************************************ */
+/* Stroke Editing Operators */
+
+/* poll callback for all stroke editing operators */
+static int gp_stroke_edit_poll(bContext *C)
+{
+       /* NOTE: this is a bit slower, but is the most accurate... */
+       return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
+}
+
+/* ************** Duplicate Selected Strokes **************** */
+
+/* Make copies of selected point segments in a selected stroke */
+static void gp_duplicate_points(const bGPDstroke *gps, ListBase *new_strokes)
+{
+       bGPDspoint *pt;
+       int i;
+       
+       int start_idx = -1;
+       
+       
+       /* Step through the original stroke's points:
+        * - We accumulate selected points (from start_idx to current index)
+        *   and then convert that to a new stroke
+        */
+       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+               /* searching for start, are waiting for end? */
+               if (start_idx == -1) {
+                       /* is this the first selected point for a new island? */
+                       if (pt->flag & GP_SPOINT_SELECT) {
+                               start_idx = i;
+                       }
+               }
+               else {
+                       size_t len = 0;
+                       
+                       /* is this the end of current island yet? 
+                        * 1) Point i-1 was the last one that was selected
+                        * 2) Point i is the last in the array
+                        */
+                       if ((pt->flag & GP_SPOINT_SELECT) == 0) {
+                               len = i - start_idx;
+                       }
+                       else if (i == gps->totpoints - 1) {
+                               len = i - start_idx + 1;
+                       }
+                       //printf("copying from %d to %d = %d\n", start_idx, i, len);
+                       
+                       /* make copies of the relevant data */
+                       if (len) {
+                               bGPDstroke *gpsd;
+                               
+                               /* make a stupid copy first of the entire stroke (to get the flags too) */
+                               gpsd = MEM_dupallocN(gps);
+                               
+                               /* now, make a new points array, and copy of the relevant parts */
+                               gpsd->points = MEM_callocN(sizeof(bGPDspoint) * len, "gps stroke points copy");
+                               memcpy(gpsd->points, gps->points + start_idx, sizeof(bGPDspoint) * len);
+                               gpsd->totpoints = len;
+                               
+                               /* add to temp buffer */
+                               gpsd->next = gpsd->prev = NULL;
+                               BLI_addtail(new_strokes, gpsd);
+                               
+                               /* cleanup + reset for next */
+                               start_idx = -1;
+                       }
+               }
+       }
+}
+
+static int gp_duplicate_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* for each visible (and editable) layer's selected strokes,
+        * copy the strokes into a temporary buffer, then append
+        * once all done
+        */
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               ListBase new_strokes = {NULL, NULL};
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps;
+               
+               if (gpf == NULL)
+                       continue;
+               
+               /* make copies of selected strokes, and deselect these once we're done */
+               for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               if (gps->totpoints == 1) {
+                                       /* Special Case: If there's just a single point in this stroke... */
+                                       bGPDstroke *gpsd;
+                                       
+                                       /* make direct copies of the stroke and its points */
+                                       gpsd = MEM_dupallocN(gps);
+                                       gpsd->points = MEM_dupallocN(gps->points);
+                                       
+                                       /* add to temp buffer */
+                                       gpsd->next = gpsd->prev = NULL;
+                                       BLI_addtail(&new_strokes, gpsd);
+                               }
+                               else {
+                                       /* delegate to a helper, as there's too much to fit in here (for copying subsets)... */
+                                       gp_duplicate_points(gps, &new_strokes);
+                               }
+                               
+                               /* deselect original stroke, or else the originals get moved too 
+                                * (when using the copy + move macro)
+                                */
+                               gps->flag &= ~GP_STROKE_SELECT;
+                       }
+               }
+               
+               /* add all new strokes in temp buffer to the frame (preventing double-copies) */
+               BLI_movelisttolist(&gpf->strokes, &new_strokes);
+               BLI_assert(new_strokes.first == NULL);
+       }
+       CTX_DATA_END;
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_duplicate(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Duplicate Strokes";
+       ot->idname = "GPENCIL_OT_duplicate";
+       ot->description = "Duplicate the selected Grease Pencil strokes";
+       
+       /* callbacks */
+       ot->exec = gp_duplicate_exec;
+       ot->poll = gp_stroke_edit_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
 /* ******************* Delete Active Frame ************************ */
 
 static int gp_actframe_delete_poll(bContext *C)
@@ -378,6 +680,7 @@ void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
        ot->name = "Delete Active Frame";
        ot->idname = "GPENCIL_OT_active_frame_delete";
        ot->description = "Delete the active frame for the active Grease Pencil datablock";
+       
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
        /* callbacks */
@@ -385,6 +688,306 @@ void GPENCIL_OT_active_frame_delete(wmOperatorType *ot)
        ot->poll = gp_actframe_delete_poll;
 }
 
+/* ******************* Delete Operator ************************ */
+
+typedef enum eGP_DeleteMode {
+       /* delete selected stroke points */
+       GP_DELETEOP_POINTS          = 0,
+       /* delete selected strokes */
+       GP_DELETEOP_STROKES         = 1,
+       /* delete active frame */
+       GP_DELETEOP_FRAME           = 2,
+       /* delete selected stroke points (without splitting stroke) */
+       GP_DELETEOP_POINTS_DISSOLVE = 3,
+} eGP_DeleteMode;
+
+
+/* Delete selected strokes */
+static int gp_delete_selected_strokes(bContext *C)
+{
+       bool changed = false;
+       
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps, *gpsn;
+               
+               if (gpf == NULL)
+                       continue;
+               
+               /* simply delete strokes which are selected */
+               for (gps = gpf->strokes.first; gps; gps = gpsn) {
+                       gpsn = gps->next;
+                       
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               /* free stroke memory arrays, then stroke itself */
+                               if (gps->points) MEM_freeN(gps->points);
+                               BLI_freelinkN(&gpf->strokes, gps);
+                               
+                               changed = true;
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+               return OPERATOR_FINISHED;
+       }
+       else {
+               return OPERATOR_CANCELLED;
+       }
+}
+
+/* Delete selected points but keep the stroke */
+static int gp_dissolve_selected_points(bContext *C)
+{
+       bool changed = false;
+       
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps, *gpsn;
+               
+               if (gpf == NULL)
+                       continue;
+               
+               /* simply delete points from selected strokes
+                * NOTE: we may still have to remove the stroke if it ends up having no points!
+                */
+               for (gps = gpf->strokes.first; gps; gps = gpsn) {
+                       gpsn = gps->next;
+                       
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               bGPDspoint *pt;
+                               int i;
+                               
+                               int tot = gps->totpoints; /* number of points in new buffer */
+                               
+                               /* First Pass: Count how many points are selected (i.e. how many to remove) */
+                               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                       if (pt->flag & GP_SPOINT_SELECT) {
+                                               /* selected point - one of the points to remove */
+                                               tot--;
+                                       }
+                               }
+                               
+                               /* if no points are left, we simply delete the entire stroke */
+                               if (tot <= 0) {
+                                       /* remove the entire stroke */
+                                       MEM_freeN(gps->points);
+                                       BLI_freelinkN(&gpf->strokes, gps);
+                               }
+                               else {  
+                                       /* just copy all unselected into a smaller buffer */
+                                       bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy");
+                                       bGPDspoint *npt        = new_points;
+                                       
+                                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                               if ((pt->flag & GP_SPOINT_SELECT) == 0) {
+                                                       *npt = *pt;
+                                                       npt++;
+                                               }
+                                       }
+                                       
+                                       /* free the old buffer */
+                                       MEM_freeN(gps->points);
+                                       
+                                       /* save the new buffer */
+                                       gps->points = new_points;
+                                       gps->totpoints = tot;
+                                       
+                                       /* deselect the stroke, since none of its selected points will still be selected */
+                                       gps->flag &= ~GP_STROKE_SELECT;
+                               }
+                               
+                               changed = true;
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+               return OPERATOR_FINISHED;
+       }
+       else {
+               return OPERATOR_CANCELLED;
+       }
+}
+
+/* Split selected strokes into segments, splitting on selected points */
+static int gp_delete_selected_points(bContext *C)
+{
+       bool changed = false;
+       
+       CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+       {
+               bGPDframe *gpf = gpl->actframe;
+               bGPDstroke *gps, *gpsn;
+               
+               if (gpf == NULL)
+                       continue;
+               
+               /* simply delete strokes which are selected */
+               for (gps = gpf->strokes.first; gps; gps = gpsn) {
+                       gpsn = gps->next;
+                       
+                       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);
+                               
+                               /* Delete the old stroke */
+                               MEM_freeN(gps->points);
+                               BLI_freelinkN(&gpf->strokes, gps);
+                               
+                               changed = true;
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+               return OPERATOR_FINISHED;
+       }
+       else {
+               return OPERATOR_CANCELLED;
+       }
+}
+
+
+static int gp_delete_exec(bContext *C, wmOperator *op)
+{      
+       eGP_DeleteMode mode = RNA_enum_get(op->ptr, "type");
+       int result = OPERATOR_CANCELLED;
+       
+       switch (mode) {
+               case GP_DELETEOP_STROKES:       /* selected strokes */
+                       result = gp_delete_selected_strokes(C);
+                       break;
+               
+               case GP_DELETEOP_POINTS:        /* selected points (breaks the stroke into segments) */
+                       result = gp_delete_selected_points(C);
+                       break;
+               
+               case GP_DELETEOP_POINTS_DISSOLVE: /* selected points (without splitting the stroke) */
+                       result = gp_dissolve_selected_points(C);
+                       break;
+                       
+               case GP_DELETEOP_FRAME:         /* active frame */
+                       result = gp_actframe_delete_exec(C, op);
+                       break;
+       }
+       
+       return result;
+}
+
+void GPENCIL_OT_delete(wmOperatorType *ot)
+{
+       static EnumPropertyItem prop_gpencil_delete_types[] = {
+               {GP_DELETEOP_POINTS, "POINTS", 0, "Points", "Delete selected points and split strokes into segments"},
+               {GP_DELETEOP_STROKES, "STROKES", 0, "Strokes", "Delete selected strokes"},
+               {GP_DELETEOP_FRAME, "FRAME", 0, "Frame", "Delete active frame"},
+               {0, "", 0, NULL, NULL},
+               {GP_DELETEOP_POINTS_DISSOLVE, "DISSOLVE_POINTS", 0, "Dissolve Points", "Delete selected points without splitting strokesp"},
+               {0, NULL, 0, NULL, NULL}
+       };
+       
+       /* identifiers */
+       ot->name = "Delete...";
+       ot->idname = "GPENCIL_OT_delete";
+       ot->description = "Delete selected Grease Pencil strokes, vertices, or frames";
+       
+       /* callbacks */
+       ot->invoke = WM_menu_invoke;
+       ot->exec = gp_delete_exec;
+       ot->poll = gp_stroke_edit_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
+       
+       /* props */
+       ot->prop = RNA_def_enum(ot->srna, "type", prop_gpencil_delete_types, 0, "Type", "Method used for deleting Grease Pencil data");
+}
+
 /* ************************************************ */
 /* Grease Pencil to Data Operator */
 
index 28eb1355caf902ec1eabb61fdb2d07a49e31ea4b..b6b07c6001fc4a632be75e413b420f8235df6275 100644 (file)
 #ifndef __GPENCIL_INTERN_H__
 #define __GPENCIL_INTERN_H__
 
-/* internal exports only */
 
+#include "DNA_vec_types.h"
 
-/* ***************************************************** */
-/* Operator Defines */
 
+/* internal exports only */
 struct bGPdata;
+struct bGPDstroke;
+struct bGPDspoint;
+
+struct ARegion;
+struct View2D;
 struct wmOperatorType;
 
+
+/* ***************************************************** */
+/* Internal API */
+
+/* Stroke Coordinates API ------------------------------ */
+/* gpencil_utils.c */
+
+typedef struct GP_SpaceConversion {
+       struct bGPdata *gpd;
+       struct bGPDlayer *gpl;
+       
+       struct ScrArea *sa;
+       struct ARegion *ar;
+       struct View2D *v2d;
+       
+       rctf *subrect;       /* for using the camera rect within the 3d view */
+       rctf subrect_data;
+       
+       float mat[4][4];     /* transform matrix on the strokes (introduced in [b770964]) */
+} GP_SpaceConversion;
+
+
+/** 
+ * Check whether a given stroke segment is inside a circular brush 
+ *
+ * \param mval     The current screen-space coordinates (midpoint) of the brush
+ * \param mvalo    The previous screen-space coordinates (midpoint) of the brush (NOT CURRENTLY USED)
+ * \param rad      The radius of the brush
+ *
+ * \param x0, y0   The screen-space x and y coordinates of the start of the stroke segment
+ * \param x1, y1   The screen-space x and y coordinates of the end of the stroke segment
+ */
+bool gp_stroke_inside_circle(const int mval[2], const int UNUSED(mvalo[2]),
+                             int rad, int x0, int y0, int x1, int y1);
+
+
+/**
+ * Init settings for stroke point space conversions 
+ *
+ * \param[out] r_gsc  The space conversion settings struct, populated with necessary params
+ */
+void gp_point_conversion_init(struct bContext *C, GP_SpaceConversion *r_gsc);
+
+/**
+ * Convert a Grease Pencil coordinate (i.e. can be 2D or 3D) to screenspace (2D)
+ *
+ * \param[out] r_x  The screen-space x-coordinate of the point
+ * \param[out] r_y  The screen-space y-coordinate of the point
+ */
+void gp_point_to_xy(GP_SpaceConversion *settings, struct bGPDstroke *gps, struct bGPDspoint *pt,
+                    int *r_x, int *r_y);
+
+/* ***************************************************** */
+/* Operator Defines */
+
 /* drawing ---------- */
 
 void GPENCIL_OT_draw(struct wmOperatorType *ot);
@@ -52,12 +111,28 @@ typedef enum eGPencil_PaintModes {
        GP_PAINTMODE_DRAW_POLY
 } eGPencil_PaintModes;
 
+/* stroke editing ----- */
+
+void GPENCIL_OT_select(struct wmOperatorType *ot);
+void GPENCIL_OT_select_all(struct wmOperatorType *ot);
+void GPENCIL_OT_select_circle(struct wmOperatorType *ot);
+void GPENCIL_OT_select_border(struct wmOperatorType *ot);
+
+void GPENCIL_OT_select_linked(struct wmOperatorType *ot);
+void GPENCIL_OT_select_more(struct wmOperatorType *ot);
+void GPENCIL_OT_select_less(struct wmOperatorType *ot);
+
+void GPENCIL_OT_duplicate(struct wmOperatorType *ot);
+void GPENCIL_OT_delete(struct wmOperatorType *ot);
+
 /* buttons editing --- */
 
 void GPENCIL_OT_data_add(struct wmOperatorType *ot);
 void GPENCIL_OT_data_unlink(struct wmOperatorType *ot);
 
 void GPENCIL_OT_layer_add(struct wmOperatorType *ot);
+void GPENCIL_OT_layer_remove(struct wmOperatorType *ot);
+void GPENCIL_OT_layer_move(struct wmOperatorType *ot);
 
 void GPENCIL_OT_active_frame_delete(struct wmOperatorType *ot);
 
index 351ca4a7fe1bc65754203e239aa8a7bb410b3098..ac3ef5f35e34cd222e356217adb548720f20bbb7 100644 (file)
 
 #include "BLI_sys_types.h"
 
+#include "BLI_blenlib.h"
+
+#include "BKE_context.h"
+
+#include "DNA_gpencil_types.h"
+
 #include "WM_api.h"
 #include "WM_types.h"
 
 #include "RNA_access.h"
 
 #include "ED_gpencil.h"
+#include "ED_object.h"
+#include "ED_transform.h"
 
 #include "gpencil_intern.h"
 
 /* ****************************************** */
-/* Generic Editing Keymap */
+/* Grease Pencil Keymaps */
 
-void ED_keymap_gpencil(wmKeyConfig *keyconf)
+/* Generic Drawing Keymap */
+static void ed_keymap_gpencil_general(wmKeyConfig *keyconf)
 {
        wmKeyMap *keymap = WM_keymap_find(keyconf, "Grease Pencil", 0, 0);
        wmKeyMapItem *kmi;
        
-       /* Draw */
-
+       /* Draw  --------------------------------------- */
        /* draw */
        kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", LEFTMOUSE, KM_PRESS, 0, DKEY);
        RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_DRAW);
-
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+       
+#if 0 // XXX: disabled, since they won't be of any use anymore
        /* draw - straight lines */
        kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", LEFTMOUSE, KM_PRESS, KM_CTRL, DKEY);
        RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_DRAW_STRAIGHT);
-
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+       
        /* draw - poly lines */
-       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", RIGHTMOUSE, KM_PRESS, KM_CTRL, DKEY);
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", LEFTMOUSE, KM_PRESS, KM_ALT, DKEY);
        RNA_enum_set(kmi->ptr, "mode", GP_PAINTMODE_DRAW_POLY);
-
+       RNA_boolean_set(kmi->ptr, "wait_for_input", false);
+#endif 
+       
        /* erase */
        kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_draw", RIGHTMOUSE, KM_PRESS, 0, DKEY);
        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");
+       
+       /* Pie Menu - For standard tools */
+       WM_keymap_add_menu_pie(keymap, "GPENCIL_PIE_tool_palette", DKEY, KM_PRESS, KM_CTRL, 0);
+       WM_keymap_add_menu_pie(keymap, "GPENCIL_PIE_settings_palette", QKEY, KM_PRESS, 0, DKEY);
+}
+
+/* ==================== */
+
+/* Poll callback for stroke editing mode */
+static int gp_stroke_editmode_poll(bContext *C)
+{
+       bGPdata *gpd = CTX_data_gpencil_data(C);
+       return (gpd && (gpd->flag & GP_DATA_STROKE_EDITMODE));
+}
+
+/* Stroke Editing Keymap - Only when editmode is enabled */
+static void ed_keymap_gpencil_editing(wmKeyConfig *keyconf)
+{
+       wmKeyMap *keymap = WM_keymap_find(keyconf, "Grease Pencil Stroke Edit Mode", 0, 0);
+       wmKeyMapItem *kmi;
+       
+       /* set poll callback - so that this keymap only gets enabled when stroke editmode is enabled */
+       keymap->poll = gp_stroke_editmode_poll;
+       
+       /* Selection ------------------------------------- */
+       /* select all */
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_select_all", AKEY, KM_PRESS, 0, 0);
+       RNA_enum_set(kmi->ptr, "action", SEL_TOGGLE);
+       
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_select_all", IKEY, KM_PRESS, KM_CTRL, 0);
+       RNA_enum_set(kmi->ptr, "action", SEL_INVERT);
+       
+       /* circle select */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_select_circle", CKEY, KM_PRESS, 0, 0);
+       
+       /* border select */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_select_border", BKEY, KM_PRESS, 0, 0);
+       
+       /* normal select */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_select", SELECTMOUSE, KM_PRESS, 0, 0);
+       
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_select", SELECTMOUSE, KM_PRESS, KM_SHIFT, 0);
+       RNA_boolean_set(kmi->ptr, "extend", true);
+       RNA_boolean_set(kmi->ptr, "toggle", true);
+       
+       /* whole stroke select */
+       kmi = WM_keymap_add_item(keymap, "GPENCIL_OT_select", SELECTMOUSE, KM_PRESS, KM_ALT, 0);
+       RNA_boolean_set(kmi->ptr, "entire_strokes", true);
+       
+       /* select linked */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_select_linked", LKEY, KM_PRESS, KM_CTRL, 0);
+       
+       /* select more/less */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_select_more", PADPLUSKEY, KM_PRESS, KM_CTRL, 0);
+       WM_keymap_add_item(keymap, "GPENCIL_OT_select_less", PADMINUS, KM_PRESS, KM_CTRL, 0);
+       
+       
+       /* Editing ----------------------------------------- */
+       
+       /* duplicate and move selected points */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_duplicate_move", DKEY, KM_PRESS, KM_SHIFT, 0);
+       
+       /* delete */
+       WM_keymap_add_item(keymap, "GPENCIL_OT_delete", XKEY, KM_PRESS, 0, 0);
+       
+       
+       /* 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_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);
+       
+       /* Proportional Editing */
+       ED_keymap_proportional_cycle(keyconf, keymap);
+       ED_keymap_proportional_editmode(keyconf, keymap, true);
+}
+
+/* ==================== */
+
+void ED_keymap_gpencil(wmKeyConfig *keyconf)
+{
+       ed_keymap_gpencil_general(keyconf);
+       ed_keymap_gpencil_editing(keyconf);
 }
 
 /* ****************************************** */
@@ -78,12 +197,28 @@ void ED_operatortypes_gpencil(void)
        
        WM_operatortype_append(GPENCIL_OT_draw);
        
+       /* Editing (Strokes) ------------ */
+       
+       WM_operatortype_append(GPENCIL_OT_select);
+       WM_operatortype_append(GPENCIL_OT_select_all);
+       WM_operatortype_append(GPENCIL_OT_select_circle);
+       WM_operatortype_append(GPENCIL_OT_select_border);
+       
+       WM_operatortype_append(GPENCIL_OT_select_linked);
+       WM_operatortype_append(GPENCIL_OT_select_more);
+       WM_operatortype_append(GPENCIL_OT_select_less);
+       
+       WM_operatortype_append(GPENCIL_OT_duplicate);
+       WM_operatortype_append(GPENCIL_OT_delete);
+       
        /* Editing (Buttons) ------------ */
        
        WM_operatortype_append(GPENCIL_OT_data_add);
        WM_operatortype_append(GPENCIL_OT_data_unlink);
        
        WM_operatortype_append(GPENCIL_OT_layer_add);
+       WM_operatortype_append(GPENCIL_OT_layer_remove);
+       WM_operatortype_append(GPENCIL_OT_layer_move);
        
        WM_operatortype_append(GPENCIL_OT_active_frame_delete);
        
@@ -92,4 +227,17 @@ void ED_operatortypes_gpencil(void)
        /* Editing (Time) --------------- */
 }
 
+void ED_operatormacros_gpencil(void)
+{
+       wmOperatorType *ot;
+       wmOperatorTypeMacro *otmacro;
+
+       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_enum_set(otmacro->ptr, "gpencil_strokes", true);
+}
+
 /* ****************************************** */
index 7c1976df3e0eed3e439a0bab2a5482d0fee7e136..692523f3ed608c3b6303ae99b7bc2c4732309d20 100644 (file)
@@ -86,6 +86,8 @@ typedef struct tGPsdata {
        rctf *subrect;      /* for using the camera rect within the 3d view */
        rctf subrect_data;
        
+       GP_SpaceConversion gsc; /* settings to pass to gp_points_to_xy() */
+       
        PointerRNA ownerPtr; /* pointer to owner of gp-datablock */
        bGPdata *gpd;       /* gp-datablock layer comes from */
        bGPDlayer *gpl;     /* layer we're working on */
@@ -875,58 +877,6 @@ static bool gp_stroke_eraser_is_occluded(tGPsdata *p,
        return false;
 }
 
-/* eraser tool - check if part of stroke occurs within last segment drawn by eraser */
-static short gp_stroke_eraser_strokeinside(const int mval[2], const int UNUSED(mvalo[2]),
-                                           int rad, int x0, int y0, int x1, int y1)
-{
-       /* simple within-radius check for now */
-       const float mval_fl[2]     = {mval[0], mval[1]};
-       const float screen_co_a[2] = {x0, y0};
-       const float screen_co_b[2] = {x1, y1};
-       
-       if (edge_inside_circle(mval_fl, rad, screen_co_a, screen_co_b)) {
-               return true;
-       }
-       
-       /* not inside */
-       return false;
-} 
-
-static void gp_point_to_xy(tGPsdata *p, bGPDstroke *gps, bGPDspoint *pt,
-                           int *r_x, int *r_y)
-{
-       ARegion *ar = p->ar;
-       View2D *v2d = p->v2d;
-       rctf *subrect = p->subrect;
-       int xyval[2];
-
-       if (gps->flag & GP_STROKE_3DSPACE) {
-               if (ED_view3d_project_int_global(ar, &pt->x, xyval, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
-                       *r_x = xyval[0];
-                       *r_y = xyval[1];
-               }
-               else {
-                       *r_x = V2D_IS_CLIPPED;
-                       *r_y = V2D_IS_CLIPPED;
-               }
-       }
-       else if (gps->flag & GP_STROKE_2DSPACE) {
-               float vec[3] = {pt->x, pt->y, 0.0f};
-               mul_m4_v3(p->mat, vec);
-               UI_view2d_view_to_region_clip(v2d, vec[0], vec[1], r_x, r_y);
-       }
-       else {
-               if (subrect == NULL) { /* normal 3D view */
-                       *r_x = (int)(pt->x / 100 * ar->winx);
-                       *r_y = (int)(pt->y / 100 * ar->winy);
-               }
-               else { /* camera view, use subrect */
-                       *r_x = (int)((pt->x / 100) * BLI_rctf_size_x(subrect)) + subrect->xmin;
-                       *r_y = (int)((pt->y / 100) * BLI_rctf_size_y(subrect)) + subrect->ymin;
-               }
-       }
-}
-
 
 /* eraser tool - evaluation per stroke */
 /* TODO: this could really do with some optimization (KD-Tree/BVH?) */
@@ -945,7 +895,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p,
                BLI_freelinkN(&gpf->strokes, gps);
        }
        else if (gps->totpoints == 1) {
-               gp_point_to_xy(p, gps, gps->points, &x0, &y0);
+               gp_point_to_xy(&p->gsc, gps, gps->points, &x0, &y0);
                
                /* do boundbox check first */
                if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) {
@@ -966,8 +916,8 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p,
                        pt1 = gps->points + i;
                        pt2 = gps->points + i + 1;
                        
-                       gp_point_to_xy(p, gps, pt1, &x0, &y0);
-                       gp_point_to_xy(p, gps, pt2, &x1, &y1);
+                       gp_point_to_xy(&p->gsc, gps, pt1, &x0, &y0);
+                       gp_point_to_xy(&p->gsc, gps, pt2, &x1, &y1);
                        
                        /* 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)) ||
@@ -977,7 +927,7 @@ static void gp_stroke_eraser_dostroke(tGPsdata *p,
                                 * eraser region  (either within stroke painted, or on its lines)
                                 *  - this assumes that linewidth is irrelevant
                                 */
-                               if (gp_stroke_eraser_strokeinside(mval, mvalo, rad, x0, y0, x1, y1)) {
+                               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))
                                        {
@@ -1068,6 +1018,7 @@ static int gp_session_initdata(bContext *C, tGPsdata *p)
        p->win = CTX_wm_window(C);
 
        unit_m4(p->imat);
+       unit_m4(p->mat);
        
        switch (curarea->spacetype) {
                /* supported views first */
@@ -1154,7 +1105,9 @@ static int gp_session_initdata(bContext *C, tGPsdata *p)
                                p->imat[3][0] -= marker->pos[0];
                                p->imat[3][1] -= marker->pos[1];
                        }
+                       
                        invert_m4_m4(p->mat, p->imat);
+                       copy_m4_m4(p->gsc.mat, p->mat);
                        break;
                }
                /* unsupported views */
@@ -1290,7 +1243,21 @@ static void gp_paint_initstroke(tGPsdata *p, short paintmode)
                        }
                }
        }
-
+       
+       /* init stroke point space-conversion settings... */
+       p->gsc.gpd = p->gpd;
+       p->gsc.gpl = p->gpl;
+       
+       p->gsc.sa = p->sa;
+       p->gsc.ar = p->ar;
+       p->gsc.v2d = p->v2d;
+       
+       p->gsc.subrect_data = p->subrect_data;
+       p->gsc.subrect = p->subrect;
+       
+       copy_m4_m4(p->gsc.mat, p->mat);
+       
+       
        /* check if points will need to be made in view-aligned space */
        if (p->gpd->flag & GP_DATA_VIEWALIGN) {
                switch (p->sa->spacetype) {
@@ -1772,11 +1739,8 @@ static int gpencil_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event
        else
                WM_cursor_modal_set(win, BC_PAINTBRUSHCURSOR);
        
-       /* special hack: if there was an initial event, then we were invoked via a hotkey, and 
-        * painting should start immediately. Otherwise, this was called from a toolbar, in which
-        * case we should wait for the mouse to be clicked.
-        */
-       if (event->val == KM_PRESS) {
+       /* only start drawing immediately if we're allowed to do so... */
+       if (RNA_boolean_get(op->ptr, "wait_for_input") == false) {
                /* hotkey invoked - start drawing */
                /* printf("\tGP - set first spot\n"); */
                p->status = GP_STATUS_PAINTING;
@@ -1868,8 +1832,11 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
        /* we don't pass on key events, GP is used with key-modifiers - prevents Dkey to insert drivers */
        if (ISKEYBOARD(event->type)) {
-               if (ELEM(event->type, LEFTARROWKEY, DOWNARROWKEY, RIGHTARROWKEY, UPARROWKEY)) {
-                       /* allow some keys - for frame changing: [#33412] */
+               if (ELEM(event->type, LEFTARROWKEY, DOWNARROWKEY, RIGHTARROWKEY, UPARROWKEY, ZKEY)) {
+                       /* allow some keys:
+                        *   - for frame changing [#33412]
+                        *   - for undo (during sketching sessions)
+                        */
                }
                else {
                        estate = OPERATOR_RUNNING_MODAL;
@@ -2053,6 +2020,8 @@ void GPENCIL_OT_draw(wmOperatorType *ot)
        
        /* settings for drawing */
        ot->prop = RNA_def_enum(ot->srna, "mode", prop_gpencil_drawmodes, 0, "Mode", "Way to interpret mouse movements");
-       
        RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+       
+       /* NOTE: wait for input is enabled by default, so that all UI code can work properly without needing users to know about this */
+       RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "Wait for first click instead of painting immediately");
 }
diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c
new file mode 100644 (file)
index 0000000..2be5b8d
--- /dev/null
@@ -0,0 +1,839 @@
+/*
+ * ***** 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) 2014, Blender Foundation
+ * This is a new part of Blender
+ *
+ * Contributor(s): Joshua Leung
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/gpencil/gpencil_select.c
+ *  \ingroup edgpencil
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <math.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_blenlib.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_context.h"
+#include "BKE_curve.h"
+#include "BKE_depsgraph.h"
+#include "BKE_global.h"
+#include "BKE_gpencil.h"
+#include "BKE_library.h"
+#include "BKE_object.h"
+#include "BKE_report.h"
+#include "BKE_scene.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 "UI_view2d.h"
+
+#include "ED_gpencil.h"
+#include "ED_view3d.h"
+#include "ED_keyframing.h"
+
+#include "gpencil_intern.h"
+
+/* ********************************************** */
+/* Polling callbacks */
+
+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);
+}
+
+/* ********************************************** */
+/* Select All Operator */
+
+static int gpencil_select_all_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       int action = RNA_enum_get(op->ptr, "action");
+       
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* for "toggle", test for existing selected strokes */
+       if (action == SEL_TOGGLE) {
+               action = SEL_SELECT;
+               
+               CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+               {
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               action = SEL_DESELECT;
+                               break; // XXX: this only gets out of the inner loop...
+                       }
+               }
+               CTX_DATA_END;
+       }
+       
+       /* if deselecting, we need to deselect strokes across all frames
+        *  - Currently, an exception is only given for deselection
+        *    Selecting and toggling should only affect what's visible,
+        *    while deselecting helps clean up unintended/forgotten
+        *    stuff on other frames
+        */
+       if (action == SEL_DESELECT) {
+               /* deselect strokes across editable layers
+                * NOTE: we limit ourselves to editable layers, since once a layer is "locked/hidden
+                *       nothing should be able to touch it
+                */
+               CTX_DATA_BEGIN(C, bGPDlayer *, gpl, editable_gpencil_layers)
+               {
+                       bGPDframe *gpf;
+                       
+                       /* deselect all strokes on all frames */
+                       for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
+                               bGPDstroke *gps;
+                               
+                               for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                                       bGPDspoint *pt;
+                                       int i;
+                                       
+                                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                               pt->flag &= ~GP_SPOINT_SELECT;
+                                       }
+                                       
+                                       gps->flag &= ~GP_STROKE_SELECT;
+                               }
+                       }
+               }
+               CTX_DATA_END;
+       }
+       else {
+               /* select or deselect all strokes */
+               CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+               {
+                       bGPDspoint *pt;
+                       int i;
+                       bool selected = false;
+                       
+                       /* Change selection status of all points, then make the stroke match */
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               switch (action) {
+                                       case SEL_SELECT:
+                                               pt->flag |= GP_SPOINT_SELECT;
+                                               break;
+                                       //case SEL_DESELECT:
+                                       //      pt->flag &= ~GP_SPOINT_SELECT;
+                                       //      break;
+                                       case SEL_INVERT:
+                                               pt->flag ^= GP_SPOINT_SELECT;
+                                               break;
+                               }
+                               
+                               if (pt->flag & GP_SPOINT_SELECT)
+                                       selected = true;
+                       }
+                       
+                       /* Change status of stroke */
+                       if (selected)
+                               gps->flag |= GP_STROKE_SELECT;
+                       else
+                               gps->flag &= ~GP_STROKE_SELECT;
+               }
+               CTX_DATA_END;
+       }
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_select_all(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "(De)select All Strokes";
+       ot->idname = "GPENCIL_OT_select_all";
+       ot->description = "Change selection of all Grease Pencil strokes currently visible";
+       
+       /* callbacks */
+       ot->exec = gpencil_select_all_exec;
+       ot->poll = gpencil_select_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       WM_operator_properties_select_all(ot);
+}
+
+/* ********************************************** */
+/* Select Linked */
+
+static int gpencil_select_linked_exec(bContext *C, wmOperator *op)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       
+       if (gpd == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No Grease Pencil data");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* select all points in selected strokes */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               if (gps->flag & GP_STROKE_SELECT) {
+                       bGPDspoint *pt;
+                       int i;
+                       
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               pt->flag |= GP_SPOINT_SELECT;
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_select_linked(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Select Linked";
+       ot->idname = "GPENCIL_OT_select_linked";
+       ot->description = "Select all points in same strokes as already selected points";
+       
+       /* callbacks */
+       ot->exec = gpencil_select_linked_exec;
+       ot->poll = gpencil_select_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* ********************************************** */
+/* Select More */
+
+static int gpencil_select_more_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               if (gps->flag & GP_STROKE_SELECT) {
+                       bGPDspoint *pt;
+                       int i;
+                       bool prev_sel;
+                       
+                       /* First Pass: Go in forward order, expanding selection if previous was selected (pre changes)... 
+                        * - This pass covers the "after" edges of selection islands
+                        */
+                       prev_sel = false;
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               if (pt->flag & GP_SPOINT_SELECT) {
+                                       /* selected point - just set flag for next point */
+                                       prev_sel = true;
+                               }
+                               else {
+                                       /* unselected point - expand selection if previous was selected... */
+                                       if (prev_sel) {
+                                               pt->flag |= GP_SPOINT_SELECT;
+                                       }
+                                       prev_sel = false;
+                               }
+                       }
+                       
+                       /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) 
+                        * - This pass covers the "before" edges of selection islands
+                        */
+                       prev_sel = false;
+                       for (pt -= 1; i > 0; i--, pt--) {
+                               if (pt->flag & GP_SPOINT_SELECT) {
+                                       prev_sel = true;
+                               }
+                               else {
+                                       /* unselected point - expand selection if previous was selected... */
+                                       if (prev_sel) {
+                                               pt->flag |= GP_SPOINT_SELECT;
+                                       }
+                                       prev_sel = false;
+                               }
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_select_more(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Select More";
+       ot->idname = "GPENCIL_OT_select_more";
+       ot->description = "Grow sets of selected Grease Pencil points";
+       
+       /* callbacks */
+       ot->exec = gpencil_select_more_exec;
+       ot->poll = gpencil_select_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* ********************************************** */
+/* Select Less */
+
+static int gpencil_select_less_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               if (gps->flag & GP_STROKE_SELECT) {
+                       bGPDspoint *pt;
+                       int i;
+                       bool prev_sel;
+                       
+                       /* First Pass: Go in forward order, shrinking selection if previous was not selected (pre changes)... 
+                        * - This pass covers the "after" edges of selection islands
+                        */
+                       prev_sel = false;
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               if (pt->flag & GP_SPOINT_SELECT) {
+                                       /* shrink if previous wasn't selected */
+                                       if (prev_sel == false) {
+                                               pt->flag &= ~GP_SPOINT_SELECT;
+                                       }
+                                       prev_sel = true;
+                               }
+                               else {
+                                       /* mark previous as being unselected - and hence, is trigger for shrinking */
+                                       prev_sel = false;
+                               }
+                       }
+                       
+                       /* Second Pass: Go in reverse order, doing the same as before (except in opposite order) 
+                        * - This pass covers the "before" edges of selection islands
+                        */
+                       prev_sel = false;
+                       for (pt -= 1; i > 0; i--, pt--) {
+                               if (pt->flag & GP_SPOINT_SELECT) {
+                                       /* shrink if previous wasn't selected */
+                                       if (prev_sel == false) {
+                                               pt->flag &= ~GP_SPOINT_SELECT;
+                                       }
+                                       prev_sel = true;
+                               }
+                               else {
+                                       /* mark previous as being unselected - and hence, is trigger for shrinking */
+                                       prev_sel = false;
+                               }
+                       }
+               }
+       }
+       CTX_DATA_END;
+       
+       /* updates */
+       WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_select_less(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Select Less";
+       ot->idname = "GPENCIL_OT_select_less";
+       ot->description = "Shrink sets of selected Grease Pencil points";
+       
+       /* callbacks */
+       ot->exec = gpencil_select_less_exec;
+       ot->poll = gpencil_select_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* ********************************************** */
+/* Circle Select Operator */
+
+/* Helper to check if a given stroke is within the area */
+/* NOTE: Code here is adapted (i.e. copied directly) from gpencil_paint.c::gp_stroke_eraser_dostroke()
+ *       It would be great to de-duplicate the logic here sometime, but that can wait...
+ */
+static bool gp_stroke_do_circle_sel(bGPDstroke *gps, GP_SpaceConversion *gsc,
+                                    const int mx, const int my, const int radius, 
+                                    const bool select, rcti *rect)
+{
+       bGPDspoint *pt1, *pt2;
+       int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
+       int i;
+       bool changed = false;
+       
+       if (gps->totpoints == 1) {
+               gp_point_to_xy(gsc, gps, gps->points, &x0, &y0);
+               
+               /* do boundbox check first */
+               if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(rect, x0, y0)) {
+                       /* only check if point is inside */
+                       if (((x0 - mx) * (x0 - mx) + (y0 - my) * (y0 - my)) <= radius * radius) {
+                               /* change selection */
+                               if (select) {
+                                       gps->points->flag |= GP_SPOINT_SELECT;
+                                       gps->flag |= GP_STROKE_SELECT;
+                               }
+                               else {
+                                       gps->points->flag &= ~GP_SPOINT_SELECT;
+                                       gps->flag &= ~GP_STROKE_SELECT;
+                               }
+                               
+                               return true;
+                       }
+               }
+       }
+       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;
+                       
+                       gp_point_to_xy(gsc, gps, pt1, &x0, &y0);
+                       gp_point_to_xy(gsc, gps, pt2, &x1, &y1);
+                       
+                       /* check that point segment of the boundbox of the selection 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)))
+                       {
+                               int mval[2]  = {mx, my};
+                               int mvalo[2] = {mx, my}; /* dummy - this isn't used... */
+                               
+                               /* 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, radius, x0, y0, x1, y1)) {
+                                       /* change selection of stroke, and then of both points 
+                                        * (as the last point otherwise wouldn't get selected
+                                        *  as we only do n-1 loops through) 
+                                        */
+                                       if (select) {
+                                               pt1->flag |= GP_SPOINT_SELECT;
+                                               pt2->flag |= GP_SPOINT_SELECT;
+                                               
+                                               changed = true;
+                                       }
+                                       else {
+                                               pt1->flag &= ~GP_SPOINT_SELECT;
+                                               pt2->flag &= ~GP_SPOINT_SELECT;
+                                               
+                                               changed = true;
+                                       }
+                               }
+                       }
+               }
+               
+               /* Ensure that stroke selection is in sync with its points */
+               gpencil_stroke_sync_selection(gps);
+       }
+       
+       return changed;
+}
+
+
+static int gpencil_circle_select_exec(bContext *C, wmOperator *op)
+{
+       ScrArea *sa = CTX_wm_area(C);
+       
+       const int mx = RNA_int_get(op->ptr, "x");
+       const int my = RNA_int_get(op->ptr, "y");
+       const int radius = RNA_int_get(op->ptr, "radius");
+       
+       const int gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
+       const bool select = (gesture_mode == GESTURE_MODAL_SELECT);
+       
+       GP_SpaceConversion gsc = {0};
+       rcti rect = {0};            /* for bounding rect around circle (for quicky intersection testing) */
+       
+       bool changed = false;
+       
+       
+       /* sanity checks */
+       if (sa == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No active area");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* init space conversion stuff */
+       gp_point_conversion_init(C, &gsc);
+       
+       
+       /* rect is rectangle of selection circle */
+       rect.xmin = mx - radius;
+       rect.ymin = my - radius;
+       rect.xmax = mx + radius;
+       rect.ymax = my + radius;
+       
+       
+       /* find visible strokes, and select if hit */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               changed |= gp_stroke_do_circle_sel(gps, &gsc, mx, my, radius, select, &rect);
+       }
+       CTX_DATA_END;
+       
+       /* updates */
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
+       }
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_select_circle(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Circle Select";
+       ot->description = "Select Grease Pencil strokes using brush selection";
+       ot->idname = "GPENCIL_OT_select_circle";
+       
+       /* callbacks */
+       ot->invoke = WM_gesture_circle_invoke;
+       ot->modal = WM_gesture_circle_modal;
+       ot->exec = gpencil_circle_select_exec;
+       ot->poll = gpencil_select_poll;
+       ot->cancel = WM_gesture_circle_cancel;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* properties */
+       RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
+       RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
+       RNA_def_int(ot->srna, "radius", 1, 1, INT_MAX, "Radius", "", 1, INT_MAX);
+       RNA_def_int(ot->srna, "gesture_mode", 0, INT_MIN, INT_MAX, "Gesture Mode", "", INT_MIN, INT_MAX);
+}
+
+/* ********************************************** */
+/* Box Selection */
+
+static int gpencil_border_select_exec(bContext *C, wmOperator *op)
+{
+       ScrArea *sa = CTX_wm_area(C);
+       
+       const int gesture_mode = RNA_int_get(op->ptr, "gesture_mode");
+       const bool select = (gesture_mode == GESTURE_MODAL_SELECT);
+       const bool extend = RNA_boolean_get(op->ptr, "extend");
+       
+       GP_SpaceConversion gsc = {0};
+       rcti rect = {0};
+       
+       bool changed = false;
+       
+       
+       /* sanity checks */
+       if (sa == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No active area");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* init space conversion stuff */
+       gp_point_conversion_init(C, &gsc);
+       
+       
+       /* deselect all strokes first? */
+       if (select && !extend) {
+               CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+               {
+                       bGPDspoint *pt;
+                       int i;
+                       
+                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                               pt->flag &= ~GP_SPOINT_SELECT;
+                       }
+                       
+                       gps->flag &= ~GP_STROKE_SELECT;
+               }
+               CTX_DATA_END;
+       }
+       
+       /* get settings from operator */
+       WM_operator_properties_border_to_rcti(op, &rect);
+       
+       /* select/deselect points */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       int x0, y0;
+                       
+                       /* convert point coords to screenspace */
+                       gp_point_to_xy(&gsc, gps, pt, &x0, &y0);
+                       
+                       /* test if in selection rect */
+                       if ((!ELEM(V2D_IS_CLIPPED, x0, y0)) && BLI_rcti_isect_pt(&rect, x0, y0)) {
+                               if (select) {
+                                       pt->flag |= GP_SPOINT_SELECT;
+                               }
+                               else {
+                                       pt->flag &= ~GP_SPOINT_SELECT;
+                               }
+                               
+                               changed = true;
+                       }
+               }
+               
+               /* Ensure that stroke selection is in sync with its points */
+               gpencil_stroke_sync_selection(gps);
+       }
+       CTX_DATA_END;
+       
+       /* updates */
+       if (changed) {
+               WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
+       }
+       
+       return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_select_border(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "Border Select";
+       ot->description = "Select Grease Pencil strokes within a rectangular region";
+       ot->idname = "GPENCIL_OT_select_border";
+       
+       /* callbacks */
+       ot->invoke = WM_border_select_invoke;
+       ot->exec = gpencil_border_select_exec;
+       ot->modal = WM_border_select_modal;
+       ot->cancel = WM_border_select_cancel;
+       
+       ot->poll = gpencil_select_poll;
+       
+       /* flags */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* rna */
+       WM_operator_properties_gesture_border(ot, true);
+}
+
+/* ********************************************** */
+/* Mouse Click to Select */
+
+static int gpencil_select_exec(bContext *C, wmOperator *op)
+{
+       ScrArea *sa = CTX_wm_area(C);
+       
+       /* "radius" is simply a threshold (screen space) to make it easier to test with a tolerance */
+       const float radius = 0.75f * U.widget_unit;
+       const int radius_squared = (int)(radius * radius);
+       
+       bool extend = RNA_boolean_get(op->ptr, "extend");
+       bool deselect = RNA_boolean_get(op->ptr, "deselect");
+       bool toggle = RNA_boolean_get(op->ptr, "toggle");
+       bool whole = RNA_boolean_get(op->ptr, "entire_strokes");
+       
+       int location[2] = {0};
+       int mx, my;
+       
+       GP_SpaceConversion gsc = {0};
+       
+       bGPDstroke *hit_stroke = NULL;
+       bGPDspoint *hit_point = NULL;
+       
+       /* sanity checks */
+       if (sa == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "No active area");
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* init space conversion stuff */
+       gp_point_conversion_init(C, &gsc);
+       
+       /* get mouse location */
+       RNA_int_get_array(op->ptr, "location", location);
+       
+       mx = location[0];
+       my = location[1];
+       
+       /* First Pass: Find stroke point which gets hit */
+       /* XXX: maybe we should go from the top of the stack down instead... */
+       CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+       {
+               bGPDspoint *pt;
+               int i;
+               int hit_index = -1;
+               
+               /* firstly, check for hit-point */
+               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                       int x0, y0;
+                       
+                       gp_point_to_xy(&gsc, gps, pt, &x0, &y0);
+               
+                       /* do boundbox check first */
+                       if (!ELEM(V2D_IS_CLIPPED, x0, x0)) {
+                               /* only check if point is inside */
+                               if (((x0 - mx) * (x0 - mx) + (y0 - my) * (y0 - my)) <= radius_squared) {                                
+                                       hit_stroke = gps;
+                                       hit_point  = pt;
+                                       break;
+                               }
+                       }
+               }
+               
+               /* skip to next stroke if nothing found */
+               if (hit_index == -1) 
+                       continue;
+       }
+       CTX_DATA_END;
+       
+       /* Abort if nothing hit... */
+       if (ELEM(NULL, hit_stroke, hit_point)) {
+               return OPERATOR_CANCELLED;
+       }
+       
+       /* adjust selection behaviour - for toggle option */
+       if (toggle) {
+               deselect = (hit_point->flag & GP_SPOINT_SELECT) != 0;
+       }
+       
+       /* If not extending selection, deselect everything else */
+       if (extend == false) {
+               CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
+               {                       
+                       /* deselect stroke and its points if selected */
+                       if (gps->flag & GP_STROKE_SELECT) {
+                               bGPDspoint *pt;
+                               int i;
+                       
+                               /* deselect points */
+                               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                       pt->flag &= ~GP_SPOINT_SELECT;
+                               }
+                               
+                               /* deselect stroke itself too */
+                               gps->flag &= ~GP_STROKE_SELECT;
+                       }
+               }
+               CTX_DATA_END;
+       }
+       
+       /* Perform selection operations... */
+       if (whole) {
+               bGPDspoint *pt;
+               int i;
+               
+               /* entire stroke's points */
+               for (i = 0, pt = hit_stroke->points; i < hit_stroke->totpoints; i++, pt++) {
+                       if (deselect == false)
+                               pt->flag |= GP_SPOINT_SELECT;
+                       else
+                               pt->flag &= ~GP_SPOINT_SELECT;
+               }
+               
+               /* stroke too... */
+               if (deselect == false)
+                       hit_stroke->flag |= GP_STROKE_SELECT;
+               else
+                       hit_stroke->flag &= ~GP_STROKE_SELECT;
+       }
+       else {
+               /* just the point (and the stroke) */
+               if (deselect == false) {
+                       /* we're adding selection, so selection must be true */
+                       hit_point->flag  |= GP_SPOINT_SELECT;
+                       hit_stroke->flag |= GP_STROKE_SELECT;
+               }
+               else {
+                       /* deselect point */
+                       hit_point->flag &= ~GP_SPOINT_SELECT;
+                       
+                       /* ensure that stroke is selected correctly */
+                       gpencil_stroke_sync_selection(hit_stroke);
+               }
+       }
+       
+       /* updates */
+       if (hit_point != NULL) {
+               WM_event_add_notifier(C, NC_GPENCIL | NA_SELECTED, NULL);
+       }
+       
+       return OPERATOR_FINISHED;
+}
+
+static int gpencil_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+       RNA_int_set_array(op->ptr, "location", event->mval);
+       return gpencil_select_exec(C, op);
+}
+
+void GPENCIL_OT_select(wmOperatorType *ot)
+{
+       PropertyRNA *prop;
+       
+       /* identifiers */
+       ot->name = "Select";
+       ot->description = "Select Grease Pencil strokes and/or stroke points";
+       ot->idname = "GPENCIL_OT_select";
+       
+       /* callbacks */
+       ot->invoke = gpencil_select_invoke;
+       ot->exec = gpencil_select_exec;
+       ot->poll = gpencil_select_poll;
+       
+       /* flag */
+       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+       
+       /* properties */
+       WM_operator_properties_mouse_select(ot);
+       
+       prop = RNA_def_boolean(ot->srna, "entire_strokes", false, "Entire Strokes", "Select entire strokes instead of just the nearest stroke vertex");
+       RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+       
+       prop = RNA_def_int_vector(ot->srna, "location", 2, NULL, INT_MIN, INT_MAX, "Location", "Mouse location", INT_MIN, INT_MAX);
+       RNA_def_property_flag(prop, PROP_HIDDEN);
+}
+
+/* ********************************************** */
+
\ No newline at end of file
index 9a71c65c105d4a9b9d9a1610eb9353cc7f7392ff..b1bbeabbd550163459249e3b511213fa4f775488 100644 (file)
@@ -133,7 +133,12 @@ void gpencil_undo_push(bGPdata *gpd)
 
                while (undo_node) {
                        bGPundonode *next_node = undo_node->next;
-
+                       
+                       /* HACK: animdata wasn't duplicated, so it shouldn't be freed here,
+                        * or else the real copy will segfault when accessed 
+                        */
+                       undo_node->gpd->adt = NULL;
+                       
                        BKE_gpencil_free(undo_node->gpd);
                        MEM_freeN(undo_node->gpd);
 
@@ -157,6 +162,11 @@ void gpencil_undo_finish(void)
        bGPundonode *undo_node = undo_nodes.first;
 
        while (undo_node) {
+               /* HACK: animdata wasn't duplicated, so it shouldn't be freed here,
+                * or else the real copy will segfault when accessed 
+                */
+               undo_node->gpd->adt = NULL;
+               
                BKE_gpencil_free(undo_node->gpd);
                MEM_freeN(undo_node->gpd);
 
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c
new file mode 100644 (file)
index 0000000..4a913c3
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * ***** 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) 2014, Blender Foundation
+ *
+ * Contributor(s): Joshua Leung
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/gpencil/gpencil_utils.c
+ *  \ingroup edgpencil
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <math.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_blenlib.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_gpencil.h"
+#include "BKE_library.h"
+#include "BKE_object.h"
+#include "BKE_report.h"
+#include "BKE_scene.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 "UI_view2d.h"
+
+#include "ED_gpencil.h"
+#include "ED_view3d.h"
+
+#include "gpencil_intern.h"
+
+/* ******************************************************** */
+
+/* Check if part of stroke occurs within last segment drawn by eraser */
+bool gp_stroke_inside_circle(const int mval[2], const int UNUSED(mvalo[2]),
+                             int rad, int x0, int y0, int x1, int y1)
+{
+       /* simple within-radius check for now */
+       const float mval_fl[2]     = {mval[0], mval[1]};
+       const float screen_co_a[2] = {x0, y0};
+       const float screen_co_b[2] = {x1, y1};
+       
+       if (edge_inside_circle(mval_fl, rad, screen_co_a, screen_co_b)) {
+               return true;
+       }
+       
+       /* not inside */
+       return false;
+}
+
+/* ******************************************************** */
+
+/* Init handling for space-conversion function (from passed-in parameters) */
+void gp_point_conversion_init(bContext *C, GP_SpaceConversion *r_gsc)
+{
+       ScrArea *sa = CTX_wm_area(C);
+       ARegion *ar = CTX_wm_region(C);
+       
+       /* zero out the storage (just in case) */
+       memset(r_gsc, 0, sizeof(GP_SpaceConversion));
+       unit_m4(r_gsc->mat);
+       
+       /* store settings */
+       r_gsc->sa = sa;
+       r_gsc->ar = ar;
+       r_gsc->v2d = &ar->v2d;
+       
+       /* init region-specific stuff */
+       if (sa->spacetype == SPACE_VIEW3D) {
+               wmWindow *win = CTX_wm_window(C);
+               Scene *scene = CTX_data_scene(C);
+               View3D *v3d = (View3D *)CTX_wm_space_data(C);
+               RegionView3D *rv3d = ar->regiondata;
+               
+               /* init 3d depth buffers */
+               view3d_operator_needs_opengl(C);
+               
+               view3d_region_operator_needs_opengl(win, ar);
+               ED_view3d_autodist_init(scene, ar, v3d, 0);
+               
+               /* for camera view set the subrect */
+               if (rv3d->persp == RV3D_CAMOB) {
+                       ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, &r_gsc->subrect_data, true); /* no shift */
+                       r_gsc->subrect = &r_gsc->subrect_data;
+               }
+       }
+}
+
+
+/* Convert Grease Pencil points to screen-space values */
+void gp_point_to_xy(GP_SpaceConversion *gsc, bGPDstroke *gps, bGPDspoint *pt,
+                    int *r_x, int *r_y)
+{
+       ARegion *ar = gsc->ar;
+       View2D *v2d = gsc->v2d;
+       rctf *subrect = gsc->subrect;
+       int xyval[2];
+
+       if (gps->flag & GP_STROKE_3DSPACE) {
+               if (ED_view3d_project_int_global(ar, &pt->x, xyval, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
+                       *r_x = xyval[0];
+                       *r_y = xyval[1];
+               }
+               else {
+                       *r_x = V2D_IS_CLIPPED;
+                       *r_y = V2D_IS_CLIPPED;
+               }
+       }
+       else if (gps->flag & GP_STROKE_2DSPACE) {
+               float vec[3] = {pt->x, pt->y, 0.0f};
+               mul_m4_v3(gsc->mat, vec);
+               UI_view2d_view_to_region_clip(v2d, vec[0], vec[1], r_x, r_y);
+       }
+       else {
+               if (subrect == NULL) { /* normal 3D view */
+                       *r_x = (int)(pt->x / 100 * ar->winx);
+                       *r_y = (int)(pt->y / 100 * ar->winy);
+               }
+               else { /* camera view, use subrect */
+                       *r_x = (int)((pt->x / 100) * BLI_rctf_size_x(subrect)) + subrect->xmin;
+                       *r_y = (int)((pt->y / 100) * BLI_rctf_size_y(subrect)) + subrect->ymin;
+               }
+       }
+}
+
+/* ******************************************************** */
index 956ec308daa943472e5ccfba60fd0c959bc27d05..cd0cfeb7e7f4a75c97520b573a1493cfc5792d86 100644 (file)
@@ -159,6 +159,7 @@ typedef enum eAnim_ChannelType {
        ANIMTYPE_DSLAT,
        ANIMTYPE_DSLINESTYLE,
        ANIMTYPE_DSSPK,
+       ANIMTYPE_DSGPENCIL,
        
        ANIMTYPE_SHAPEKEY,
        
index 05fb76b7aea177c98c09e54847fa84493fc97c91..1587582b924cdd7695b5cf0f0c603bf96c06b854 100644 (file)
@@ -30,6 +30,7 @@
 #ifndef __ED_GPENCIL_H__
 #define __ED_GPENCIL_H__
 
+struct ID;
 struct ListBase;
 struct bContext;
 struct bScreen;
@@ -38,6 +39,7 @@ struct ARegion;
 struct View3D;
 struct SpaceNode;
 struct SpaceSeq;
+struct Object;
 struct bGPdata;
 struct bGPDlayer;
 struct bGPDframe;
@@ -65,14 +67,26 @@ typedef struct tGPspoint {
 
 /* ----------- Grease Pencil Tools/Context ------------- */
 
+/* Context-dependent */
 struct bGPdata **ED_gpencil_data_get_pointers(const struct bContext *C, struct PointerRNA *ptr);
 struct bGPdata  *ED_gpencil_data_get_active(const struct bContext *C);
+
+/* Context independent (i.e. each required part is passed in instead) */
+struct bGPdata **ED_gpencil_data_get_pointers_direct(struct ID *screen_id, struct Scene *scene,
+                                                     struct ScrArea *sa, struct Object *ob,
+                                                     struct PointerRNA *ptr);
+struct bGPdata *ED_gpencil_data_get_active_direct(struct ID *screen_id, struct Scene *scene,
+                                                  struct ScrArea *sa, struct Object *ob);
+
+/* 3D View */
 struct bGPdata  *ED_gpencil_data_get_active_v3d(struct Scene *scene, struct View3D *v3d);
 
 /* ----------- Grease Pencil Operators ----------------- */
 
 void ED_keymap_gpencil(struct wmKeyConfig *keyconf);
+
 void ED_operatortypes_gpencil(void);
+void ED_operatormacros_gpencil(void);
 
 /* ------------ Grease-Pencil Drawing API ------------------ */
 /* drawgpencil.c */
@@ -99,6 +113,8 @@ void  ED_gpencil_select_frame(struct bGPDlayer *gpl, int selx, short select_mode
 bool  ED_gplayer_frames_delete(struct bGPDlayer *gpl);
 void  ED_gplayer_frames_duplicate(struct bGPDlayer *gpl);
 
+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
index 58a262e150a94d4548d46f39fa0aaed09348f772..0359153317baa93b9747e672577603eab3c324a2 100644 (file)
@@ -120,8 +120,9 @@ void draw_object_channel(struct View2D *v2d, struct bDopeSheet *ads, struct Obje
 void draw_scene_channel(struct View2D *v2d, struct bDopeSheet *ads, struct Scene *sce, float ypos);
 /* DopeSheet Summary */
 void draw_summary_channel(struct View2D *v2d, struct bAnimContext *ac, float ypos);
-/* Grease Pencil Layer */ 
-// XXX not restored 
+/* Grease Pencil datablock summary */
+void draw_gpencil_channel(struct View2D *v2d, struct bDopeSheet *ads, struct bGPdata *gpd, float ypos);
+/* Grease Pencil Layer */
 void draw_gpl_channel(struct View2D *v2d, struct bDopeSheet *ads, struct bGPDlayer *gpl, float ypos);
 /* Mask Layer */
 void draw_masklay_channel(struct View2D *v2d, struct bDopeSheet *ads, struct MaskLayer *masklay, float ypos);
@@ -139,11 +140,11 @@ void ob_to_keylist(struct bDopeSheet *ads, struct Object *ob, struct DLRBT_Tree
 void scene_to_keylist(struct bDopeSheet *ads, struct Scene *sce, struct DLRBT_Tree *keys, struct DLRBT_Tree *blocks);
 /* DopeSheet Summary */
 void summary_to_keylist(struct bAnimContext *ac, struct DLRBT_Tree *keys, struct DLRBT_Tree *blocks);
+/* Grease Pencil datablock summary */
+void gpencil_to_keylist(struct bDopeSheet *ads, struct bGPdata *gpd, struct DLRBT_Tree *keys);
 /* Grease Pencil Layer */
-// XXX not restored
 void gpl_to_keylist(struct bDopeSheet *ads, struct bGPDlayer *gpl, struct DLRBT_Tree *keys);
 /* Mask */
-// XXX not restored
 void mask_to_keylist(struct bDopeSheet *UNUSED(ads), struct MaskLayer *masklay, struct DLRBT_Tree *keys);
 
 /* ActKeyColumn API ---------------- */
index daa6864b5aace3db7339bc4cf7d90fc8811ab3df..cf8480981888617cb1311b63c90f6a388e73cf65 100644 (file)
@@ -99,6 +99,7 @@ enum TfmMode {
 #define CTX_MOVIECLIP       (1 << 6)
 #define CTX_MASK            (1 << 7)
 #define CTX_PAINT_CURVE     (1 << 8)
+#define CTX_GPENCIL_STROKES (1 << 9)
 
 /* Standalone call to get the transformation center corresponding to the current situation
  * returns 1 if successful, 0 otherwise (usually means there's no selection)
@@ -146,6 +147,7 @@ int BIF_countTransformOrientation(const struct bContext *C);
 #define P_CORRECT_UV    (1 << 8)
 #define P_NO_DEFAULTS   (1 << 10)
 #define P_NO_TEXSPACE   (1 << 11)
+#define P_GPENCIL_EDIT  (1 << 12)
 
 void Transform_Properties(struct wmOperatorType *ot, int flags);
 
index 1ebc7e85ad6affb22845e14bc6478128c624847e..433630f6ed43c7d405c97016aa4de40af983a46f 100644 (file)
@@ -186,7 +186,9 @@ static void screen_opengl_render_apply(OGLRender *oglrender)
                        wmOrtho2(0, sizex, 0, sizey);
                        glTranslatef(sizex / 2, sizey / 2, 0.0f);
 
+                       G.f |= G_RENDER_OGL;
                        ED_gpencil_draw_ex(gpd, sizex, sizey, scene->r.cfra);
+                       G.f &= ~G_RENDER_OGL;
 
                        gp_rect = MEM_mallocN(sizex * sizey * sizeof(unsigned char) * 4, "offscreen rect");
                        GPU_offscreen_read_pixels(oglrender->ofs, GL_UNSIGNED_BYTE, gp_rect);
index 08406302b7054db018252af43925d1f56b3567ac..23b15ea46f402fb1db1941ff1f065f1a83376a3d 100644 (file)
@@ -1351,8 +1351,18 @@ static void ed_default_handlers(wmWindowManager *wm, ScrArea *sa, ListBase *hand
        }
        if (flag & ED_KEYMAP_GPENCIL) {
                /* grease pencil */
-               wmKeyMap *keymap = WM_keymap_find(wm->defaultconf, "Grease Pencil", 0, 0);
-               WM_event_add_keymap_handler(handlers, keymap);
+               /* NOTE: This is now 2 keymaps - One for basic functionality, 
+                *       and one that only applies when "Edit Mode" is enabled 
+                *       for strokes.
+                *
+                *       For now, it's easier to just include both, 
+                *       since you hardly want one without the other.
+                */
+               wmKeyMap *keymap_general = WM_keymap_find(wm->defaultconf, "Grease Pencil", 0, 0);
+               wmKeyMap *keymap_edit = WM_keymap_find(wm->defaultconf, "Grease Pencil Stroke Edit Mode", 0, 0);
+               
+               WM_event_add_keymap_handler(handlers, keymap_general);
+               WM_event_add_keymap_handler(handlers, keymap_edit);
        }
        if (flag & ED_KEYMAP_HEADER) {
                /* standard keymap for headers regions */
index 18c9a94e46fb2eabc021a3890a3917485ea2504d..014fad57d3fe89529c64e367b2aa65f1a392510f 100644 (file)
  *  \ingroup edscr
  */
 
-
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "DNA_object_types.h"
 #include "DNA_armature_types.h"
+#include "DNA_gpencil_types.h"
 #include "DNA_sequence_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_screen_types.h"
 #include "BKE_object.h"
 #include "BKE_action.h"
 #include "BKE_armature.h"
+#include "BKE_gpencil.h"
 #include "BKE_sequencer.h"
 
 #include "RNA_access.h"
 
 #include "ED_armature.h"
+#include "ED_gpencil.h"
 
 #include "WM_api.h"
 #include "UI_interface.h"
@@ -66,12 +69,16 @@ const char *screen_context_dir[] = {
        "sculpt_object", "vertex_paint_object", "weight_paint_object",
        "image_paint_object", "particle_edit_object",
        "sequences", "selected_sequences", "selected_editable_sequences", /* sequencer */
+       "gpencil_data", "gpencil_data_owner", /* grease pencil data */
+       "visible_gpencil_layers", "editable_gpencil_layers", "editable_gpencil_strokes",
+       "active_gpencil_layer", "active_gpencil_frame",
        "active_operator",
        NULL};
 
 int ed_screen_context(const bContext *C, const char *member, bContextDataResult *result)
 {
        bScreen *sc = CTX_wm_screen(C);
+       ScrArea *sa = CTX_wm_area(C);
        Scene *scene = sc->scene;
        Base *base;
        unsigned int lay = scene->lay;
@@ -392,6 +399,112 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult
                        return 1;
                }
        }
+       else if (CTX_data_equals(member, "gpencil_data")) {
+               /* FIXME: for some reason, CTX_data_active_object(C) returns NULL when called from these situations
+                * (as outlined above - see Campbell's #ifdefs). That causes the get active function to fail when 
+                * called from context. For that reason, we end up using an alternative where we pass everything in!
+                */
+               bGPdata *gpd = ED_gpencil_data_get_active_direct((ID *)sc, scene, sa, obact);
+               
+               if (gpd) {
+                       CTX_data_id_pointer_set(result, &gpd->id);
+                       return 1;
+               }
+       }
+       else if (CTX_data_equals(member, "gpencil_data_owner")) {
+               /* pointer to which data/datablock owns the reference to the Grease Pencil data being used (as gpencil_data)
+                * XXX: see comment for gpencil_data case... 
+                */
+               bGPdata **gpd_ptr = NULL;
+               PointerRNA ptr;
+               
+               /* get pointer to Grease Pencil Data */
+               gpd_ptr = ED_gpencil_data_get_pointers_direct((ID *)sc, scene, sa, obact, &ptr);
+               
+               if (gpd_ptr) {
+                       CTX_data_pointer_set(result, ptr.id.data, ptr.type, ptr.data);
+                       return 1;
+               }
+       }
+       else if (CTX_data_equals(member, "active_gpencil_layer")) {
+               /* XXX: see comment for gpencil_data case... */
+               bGPdata *gpd = ED_gpencil_data_get_active_direct((ID *)sc, scene, sa, obact);
+               
+               if (gpd) {
+                       bGPDlayer *gpl = gpencil_layer_getactive(gpd);
+                       
+                       if (gpl) {
+                               CTX_data_pointer_set(result, &gpd->id, &RNA_GPencilLayer, gpl);
+                               return 1;
+                       }
+               }
+       }
+       else if (CTX_data_equals(member, "active_gpencil_frame")) {
+               /* XXX: see comment for gpencil_data case... */
+               bGPdata *gpd = ED_gpencil_data_get_active_direct((ID *)sc, scene, sa, obact);
+               
+               if (gpd) {
+                       bGPDlayer *gpl = gpencil_layer_getactive(gpd);
+                       
+                       if (gpl) {
+                               CTX_data_pointer_set(result, &gpd->id, &RNA_GPencilLayer, gpl->actframe);
+                               return 1;
+                       }
+               }
+       }
+       else if (CTX_data_equals(member, "visible_gpencil_layers")) {
+               /* XXX: see comment for gpencil_data case... */
+               bGPdata *gpd = ED_gpencil_data_get_active_direct((ID *)sc, scene, sa, obact);
+               
+               if (gpd) {
+                       bGPDlayer *gpl;
+                       
+                       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+                               if ((gpl->flag & GP_LAYER_HIDE) == 0) {
+                                       CTX_data_list_add(result, &gpd->id, &RNA_GPencilLayer, gpl);
+                               }
+                       }
+                       CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION);
+                       return 1;
+               }
+       }
+       else if (CTX_data_equals(member, "editable_gpencil_layers")) {
+               /* XXX: see comment for gpencil_data case... */
+               bGPdata *gpd = ED_gpencil_data_get_active_direct((ID *)sc, scene, sa, obact);
+               
+               if (gpd) {
+                       bGPDlayer *gpl;
+                       
+                       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+                               if ((gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) == 0) {
+                                       CTX_data_list_add(result, &gpd->id, &RNA_GPencilLayer, gpl);
+                               }
+                       }
+                       CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION);
+                       return 1;
+               }
+       }
+       else if (CTX_data_equals(member, "editable_gpencil_strokes")) {
+               /* XXX: see comment for gpencil_data case... */
+               bGPdata *gpd = ED_gpencil_data_get_active_direct((ID *)sc, scene, sa, obact);
+               
+               if (gpd) {
+                       bGPDlayer *gpl;
+                       
+                       for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+                               if ((gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) == 0 && (gpl->actframe)) {
+                                       bGPDframe *gpf = gpl->actframe;
+                                       bGPDstroke *gps;
+                                       
+                                       for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                                               CTX_data_list_add(result, &gpd->id, &RNA_GPencilStroke, gps);
+                                       }
+                               }
+                       }
+                       CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION);
+                       return 1;
+               }
+       }
        else if (CTX_data_equals(member, "active_operator")) {
                wmOperator *op = NULL;
 
index 90804776d056840149d2c9a61165aec8af5cf5d7..39321ec07702f8e8f5225f1e3be3854e0832bba2 100644 (file)
@@ -43,6 +43,7 @@
 #include "DNA_lattice_types.h"
 #include "DNA_object_types.h"
 #include "DNA_curve_types.h"
+#include "DNA_gpencil_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_meta_types.h"
 #include "DNA_mask_types.h"
@@ -2148,6 +2149,7 @@ 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;
@@ -2175,7 +2177,9 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
 
        if (ob)
                ob_to_keylist(&ads, ob, &keys, NULL);
-
+       
+       gpencil_to_keylist(&ads, gpd, &keys);
+       
        {
                Mask *mask = CTX_data_edit_mask(C);
                if (mask) {
index e55ce3ea8ebcfb5c86d0485eb3b2078728d2c7a2..47a29426192d12d58f8541f404dff01f08c0643d 100644 (file)
@@ -965,8 +965,11 @@ static int actkeys_clean_exec(bContext *C, wmOperator *op)
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
                return OPERATOR_CANCELLED;
-       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
+               
+       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
+               BKE_report(op->reports, RPT_ERROR, "Not implemented");
                return OPERATOR_PASS_THROUGH;
+       }
                
        /* get cleaning threshold */
        thresh = RNA_float_get(op->ptr, "threshold");
@@ -1025,15 +1028,18 @@ static void sample_action_keys(bAnimContext *ac)
 
 /* ------------------- */
 
-static int actkeys_sample_exec(bContext *C, wmOperator *UNUSED(op))
+static int actkeys_sample_exec(bContext *C, wmOperator *op)
 {
        bAnimContext ac;
        
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
                return OPERATOR_CANCELLED;
-       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
+               
+       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
+               BKE_report(op->reports, RPT_ERROR, "Not implemented");
                return OPERATOR_PASS_THROUGH;
+       }
        
        /* sample keyframes */
        sample_action_keys(&ac);
@@ -1138,8 +1144,11 @@ static int actkeys_expo_exec(bContext *C, wmOperator *op)
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
                return OPERATOR_CANCELLED;
-       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
+               
+       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
+               BKE_report(op->reports, RPT_ERROR, "Not implemented");
                return OPERATOR_PASS_THROUGH;
+       }
                
        /* get handle setting mode */
        mode = RNA_enum_get(op->ptr, "type");
@@ -1209,8 +1218,11 @@ static int actkeys_ipo_exec(bContext *C, wmOperator *op)
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
                return OPERATOR_CANCELLED;
-       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
+               
+       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
+               BKE_report(op->reports, RPT_ERROR, "Not implemented");
                return OPERATOR_PASS_THROUGH;
+       }
                
        /* get handle setting mode */
        mode = RNA_enum_get(op->ptr, "type");
@@ -1288,8 +1300,11 @@ static int actkeys_handletype_exec(bContext *C, wmOperator *op)
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
                return OPERATOR_CANCELLED;
-       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
+               
+       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) {
+               BKE_report(op->reports, RPT_ERROR, "Not implemented");
                return OPERATOR_PASS_THROUGH;
+       }
                
        /* get handle setting mode */
        mode = RNA_enum_get(op->ptr, "type");
@@ -1324,7 +1339,7 @@ void ACTION_OT_handle_type(wmOperatorType *ot)
 
 /* ******************** Set Keyframe-Type Operator *********************** */
 
-/* this function is responsible for setting interpolation mode for keyframes */
+/* this function is responsible for setting keyframe type for keyframes */
 static void setkeytype_action_keys(bAnimContext *ac, short mode) 
 {
        ListBase anim_data = {NULL, NULL};
@@ -1349,6 +1364,29 @@ static void setkeytype_action_keys(bAnimContext *ac, short mode)
        ANIM_animdata_freelist(&anim_data);
 }
 
+/* this function is responsible for setting the keyframe type for Grease Pencil frames */
+static void setkeytype_gpencil_keys(bAnimContext *ac, short mode)
+{
+       ListBase anim_data = {NULL, NULL};
+       bAnimListElem *ale;
+       int filter;
+       
+       /* filter data */
+       filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
+       ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+       
+       /* loop through each layer */
+       for (ale = anim_data.first; ale; ale = ale->next) {
+               if (ale->type == ANIMTYPE_GPLAYER) {
+                       ED_gplayer_frames_keytype_set(ale->data, mode);
+                       ale->update |= ANIM_UPDATE_DEPS;
+               }
+       }
+
+       ANIM_animdata_update(ac, &anim_data);
+       ANIM_animdata_freelist(&anim_data);
+}
+
 /* ------------------- */
 
 static int actkeys_keytype_exec(bContext *C, wmOperator *op)
@@ -1359,14 +1397,22 @@ static int actkeys_keytype_exec(bContext *C, wmOperator *op)
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
                return OPERATOR_CANCELLED;
-       if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK))
+               
+       if (ac.datatype == ANIMCONT_MASK) {
+               BKE_report(op->reports, RPT_ERROR, "Not implemented for Masks");
                return OPERATOR_PASS_THROUGH;
+       }
                
        /* get handle setting mode */
        mode = RNA_enum_get(op->ptr, "type");
        
        /* set handle type */
-       setkeytype_action_keys(&ac, mode);
+       if (ac.datatype == ANIMCONT_GPENCIL) {
+               setkeytype_gpencil_keys(&ac, mode);
+       }
+       else {
+               setkeytype_action_keys(&ac, mode);
+       }
        
        /* set notifier that keyframe properties have changed */
        WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, NULL);
index 7edd8cf7ab7f82f36155f7a63abf5f2676e198ac..7a9cd97c3c8458cccb7847b089297f949ba06d1f 100644 (file)
@@ -153,6 +153,7 @@ void ED_spacemacros_init(void)
        ED_operatormacros_mask();
        ED_operatormacros_sequencer();
        ED_operatormacros_paint();
+       ED_operatormacros_gpencil();
 
        /* register dropboxes (can use macros) */
        spacetypes = BKE_spacetypes_list();
index fe36d9a9685eae2185182aec7761dd4c8fdd5833..70a0481f23ed137a9de4ef69bdf252b34a52bd4e 100644 (file)
 
 /* Panels */
 
+#if 0
 static int clip_grease_pencil_panel_poll(const bContext *C, PanelType *UNUSED(pt))
 {
        SpaceClip *sc = CTX_wm_space_clip(C);
 
        return sc->view == SC_VIEW_CLIP;
 }
+#endif
 
 void ED_clip_buttons_register(ARegionType *art)
 {
+#if 0
        PanelType *pt;
 
        pt = MEM_callocN(sizeof(PanelType), "spacetype clip panel gpencil");
@@ -89,6 +92,7 @@ void ED_clip_buttons_register(ARegionType *art)
        pt->flag |= PNL_DEFAULT_CLOSED;
        pt->poll = clip_grease_pencil_panel_poll;
        BLI_addtail(&art->paneltypes, pt);
+#endif
 }
 
 /********************* MovieClip Template ************************/
index bf9b55ba3dd7124685d8287713ef580a2f6d8513..fc2c0d3d45cce7c57bfe11dd0f8f4bd20862bfc8 100644 (file)
@@ -420,6 +420,9 @@ static void clip_listener(bScreen *UNUSED(sc), ScrArea *sa, wmNotifier *wmn)
                                clip_scopes_check_gpencil_change(sa);
                                ED_area_tag_redraw(sa);
                        }
+                       else if (wmn->data & ND_GPENCIL_EDITMODE) {
+                               ED_area_tag_redraw(sa);
+                       }
                        break;
        }
 }
@@ -1245,6 +1248,8 @@ static void clip_main_area_listener(bScreen *UNUSED(sc), ScrArea *UNUSED(sa), AR
                case NC_GPENCIL:
                        if (wmn->action == NA_EDITED)
                                ED_region_tag_redraw(ar);
+                       else if (wmn->data & ND_GPENCIL_EDITMODE)
+                               ED_region_tag_redraw(ar);
                        break;
        }
 }
@@ -1495,7 +1500,7 @@ static void clip_properties_area_listener(bScreen *UNUSED(sc), ScrArea *UNUSED(s
        /* context changes */
        switch (wmn->category) {
                case NC_GPENCIL:
-                       if (wmn->data == ND_DATA)
+                       if (ELEM(wmn->data, ND_DATA, ND_GPENCIL_EDITMODE))
                                ED_region_tag_redraw(ar);
                        break;
                case NC_BRUSH:
index be65cdb2d4f6564349f986a3cc111f61d266c463..3651419ee8877b71b8f9447d24b7215109b8760a 100644 (file)
@@ -979,6 +979,7 @@ void uiTemplateImageLayers(uiLayout *layout, bContext *C, Image *ima, ImageUser
 
 void image_buttons_register(ARegionType *art)
 {
+#if 0
        PanelType *pt;
        const char *category = "Grease Pencil";
 
@@ -990,6 +991,7 @@ void image_buttons_register(ARegionType *art)
        pt->draw = ED_gpencil_panel_standard;
        BLI_strncpy(pt->category, category, BLI_strlen_utf8(category));
        BLI_addtail(&art->paneltypes, pt);
+#endif
 }
 
 static int image_properties_toggle_exec(bContext *C, wmOperator *UNUSED(op))
index 89cfd389a9dbc3587bd62e569fa2321c5b8d0f37..66023ce124357862a6872cd705b22ac7039ec655 100644 (file)
@@ -143,6 +143,7 @@ bool nla_panel_context(const bContext *C, PointerRNA *adt_ptr, PointerRNA *nlt_p
                        case ANIMTYPE_DSLAT:
                        case ANIMTYPE_DSLINESTYLE:
                        case ANIMTYPE_DSSPK:
+                       case ANIMTYPE_DSGPENCIL:
                        {
                                /* for these channels, we only do AnimData */
                                if (ale->adt && adt_ptr) {
index fbb4d273626d95e5a71e6e6f266dd38a372330b3..0abe5e42c3e39adafc027fa70113ef2ff1559fbd 100644 (file)
@@ -182,6 +182,7 @@ static int mouse_nla_channels(bContext *C, bAnimContext *ac, float x, int channe
                case ANIMTYPE_DSLAT:
                case ANIMTYPE_DSLINESTYLE:
                case ANIMTYPE_DSSPK:
+               case ANIMTYPE_DSGPENCIL:
                {
                        /* sanity checking... */
                        if (ale->adt) {
index 58d94a282265a6355e9afaf37e738620ecc29963..67882504d239ce627d15f6b6aa52057d026770e9 100644 (file)
@@ -57,6 +57,7 @@
 
 /* ******************* node space & buttons ************** */
 
+#if 0
 /* poll for active nodetree */
 static int active_nodetree_poll(const bContext *C, PanelType *UNUSED(pt))
 {
@@ -64,6 +65,7 @@ static int active_nodetree_poll(const bContext *C, PanelType *UNUSED(pt))
        
        return (snode && snode->nodetree);
 }
+#endif
 
 static int node_sockets_poll(const bContext *C, PanelType *UNUSED(pt))
 {
@@ -198,6 +200,7 @@ void node_buttons_register(ARegionType *art)
        pt->poll = node_tree_interface_poll;
        BLI_addtail(&art->paneltypes, pt);
 
+#if 0
        pt = MEM_callocN(sizeof(PanelType), "spacetype node panel gpencil");
        strcpy(pt->idname, "NODE_PT_gpencil");
        strcpy(pt->label, N_("Grease Pencil"));
@@ -206,6 +209,7 @@ void node_buttons_register(ARegionType *art)
        pt->draw = ED_gpencil_panel_standard;
        pt->poll = active_nodetree_poll;
        BLI_addtail(&art->paneltypes, pt);
+#endif
 }
 
 static int node_properties_toggle_exec(bContext *C, wmOperator *UNUSED(op))
index df6652d7620f9c7c6b37b11d999273b455790612..3c91b33f7b89e805315a8f4ce655516f92888963 100644 (file)
@@ -513,6 +513,11 @@ static void node_area_listener(bScreen *sc, ScrArea *sa, wmNotifier *wmn)
                                ED_area_tag_refresh(sa);
                        }
                        break;
+               case NC_GPENCIL:
+                       if (ELEM(wmn->action, NA_EDITED, NA_SELECTED)) {
+                               ED_area_tag_redraw(sa);
+                       }
+                       break;
        }
 }
 
@@ -766,6 +771,8 @@ static void node_region_listener(bScreen *UNUSED(sc), ScrArea *UNUSED(sa), ARegi
                case NC_GPENCIL:
                        if (wmn->action == NA_EDITED)
                                ED_region_tag_redraw(ar);
+                       else if (wmn->data & ND_GPENCIL_EDITMODE)
+                               ED_region_tag_redraw(ar);
                        break;
        }
 }
index 2d8693381a0394160cf442279225cee4768b08df..7ced96bfaa2f973e3fff7499ce612a52937e44b0 100644 (file)
@@ -51,6 +51,7 @@
 
 /* **************************** buttons ********************************* */
 
+#if 0
 static int sequencer_grease_pencil_panel_poll(const bContext *C, PanelType *UNUSED(pt))
 {
        SpaceSeq *sseq = CTX_wm_space_seq(C);
@@ -58,9 +59,11 @@ static int sequencer_grease_pencil_panel_poll(const bContext *C, PanelType *UNUS
        /* don't show the gpencil if we are not showing the image */
        return ED_space_sequencer_check_show_imbuf(sseq);
 }
+#endif
 
 void sequencer_buttons_register(ARegionType *art)
 {
+#if 0
        PanelType *pt;
        
        pt = MEM_callocN(sizeof(PanelType), "spacetype sequencer panel gpencil");
@@ -71,6 +74,7 @@ void sequencer_buttons_register(ARegionType *art)
        pt->draw = ED_gpencil_panel_standard;
        pt->poll = sequencer_grease_pencil_panel_poll;
        BLI_addtail(&art->paneltypes, pt);
+#endif
 }
 
 /* **************** operator to open/close properties view ************* */
index 3f9e2f7257885326437162128e90f6fd6bbcb6e9..6231f02907acabd08bda197f522af028a04b5a5d 100644 (file)
@@ -352,6 +352,10 @@ static void sequencer_listener(bScreen *UNUSED(sc), ScrArea *sa, wmNotifier *wmn
                        if (wmn->data == ND_SPACE_SEQUENCER)
                                sequencer_scopes_tag_refresh(sa);
                        break;
+               case NC_GPENCIL:
+                       if (wmn->data & ND_GPENCIL_EDITMODE)
+                               ED_area_tag_redraw(sa);
+                       break;
        }
 }
 
@@ -585,7 +589,7 @@ static void sequencer_preview_area_listener(bScreen *UNUSED(sc), ScrArea *UNUSED
        /* context changes */
        switch (wmn->category) {
                case NC_GPENCIL:
-                       if (wmn->action == NA_EDITED) {
+                       if (ELEM(wmn->action, NA_EDITED, NA_SELECTED)) {
                                ED_region_tag_redraw(ar);
                        }
                        break;
@@ -641,7 +645,7 @@ static void sequencer_buttons_area_listener(bScreen *UNUSED(sc), ScrArea *UNUSED
        /* context changes */
        switch (wmn->category) {
                case NC_GPENCIL:
-                       if (wmn->data == ND_DATA) {
+                       if (ELEM(wmn->action, NA_EDITED, NA_SELECTED)) {
                                ED_region_tag_redraw(ar);
                        }
                        break;
index d26ee982b20104db93d22074a5de8d8eb8e04551..69a1853b7f126ea6317e87c9177127c4f57962ed 100644 (file)
@@ -32,6 +32,7 @@
 #include <string.h>
 #include <stdio.h>
 
+#include "DNA_gpencil_types.h"
 #include "DNA_object_types.h"
 #include "DNA_scene_types.h"
 
@@ -310,6 +311,9 @@ static void time_draw_idblock_keyframes(View2D *v2d, ID *id, short onlysel)
                case ID_OB:
                        ob_to_keylist(&ads, (Object *)id, &keys, NULL);
                        break;
+               case ID_GD:
+                       gpencil_to_keylist(&ads, (bGPdata *)id, &keys);
+                       break;
        }
                
        /* build linked-list for searching */
@@ -339,9 +343,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) {
+               glColor3ub(0xB5, 0xE6, 0x1D);
+               time_draw_idblock_keyframes(v2d, (ID *)gpd, onlysel);
+       }
+       
        /* draw scene keyframes first 
         *      - don't try to do this when only drawing active/selected data keyframes,
         *        since this can become quite slow
index 95b11d1731f97f9194ca1d54059f149523b702b1..78961b0e8e040f8ad4b32c9f4cf09566ffd8e512 100644 (file)
@@ -950,8 +950,9 @@ static void view3d_main_area_listener(bScreen *sc, ScrArea *sa, ARegion *ar, wmN
 
                        break;
                case NC_GPENCIL:
-                       if (wmn->action == NA_EDITED)
+                       if (ELEM(wmn->action, NA_EDITED, NA_SELECTED)) {
                                ED_region_tag_redraw(ar);
+                       }
                        break;
        }
 }
@@ -1007,6 +1008,10 @@ static void view3d_header_area_listener(bScreen *UNUSED(sc), ScrArea *UNUSED(sa)
                        if (wmn->data == ND_SPACE_VIEW3D)
                                ED_region_tag_redraw(ar);
                        break;
+               case NC_GPENCIL:
+                       if (wmn->data & ND_GPENCIL_EDITMODE)
+                               ED_region_tag_redraw(ar);
+                       break;
        }
 }
 
@@ -1104,7 +1109,7 @@ static void view3d_buttons_area_listener(bScreen *UNUSED(sc), ScrArea *UNUSED(sa
                                ED_region_tag_redraw(ar);
                        break;
                case NC_GPENCIL:
-                       if (wmn->data == ND_DATA || wmn->action == NA_EDITED)
+                       if ((wmn->data & (ND_DATA | ND_GPENCIL_EDITMODE)) || (wmn->action == NA_EDITED))
                                ED_region_tag_redraw(ar);
                        break;
                case NC_IMAGE:
index 6191a49082daab4d4f7261a1728bf6aa6b24d509..f0bc3021e41d1953d567e7b2553a27b812621a97 100644 (file)
@@ -1185,6 +1185,7 @@ void view3d_buttons_register(ARegionType *art)
        pt->poll = view3d_panel_transform_poll;
        BLI_addtail(&art->paneltypes, pt);
 
+#if 0
        pt = MEM_callocN(sizeof(PanelType), "spacetype view3d panel gpencil");
        strcpy(pt->idname, "VIEW3D_PT_gpencil");
        strcpy(pt->label, N_("Grease Pencil"));  /* XXX C panels are not available through RNA (bpy.types)! */
@@ -1192,6 +1193,7 @@ void view3d_buttons_register(ARegionType *art)
        pt->draw_header = ED_gpencil_panel_standard_header;
        pt->draw = ED_gpencil_panel_standard;
        BLI_addtail(&art->paneltypes, pt);
+#endif
 
        pt = MEM_callocN(sizeof(PanelType), "spacetype view3d panel vgroup");
        strcpy(pt->idname, "VIEW3D_PT_vgroup");
index b0e334345ecb0e599f762249bf726d13e86dc420..b87a823ce928d7e5e030ef7e801625c94d4f2b96 100644 (file)
@@ -2013,6 +2013,12 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
                        options |= CTX_TEXTURE;
                }
        }
+       
+       if ((prop = RNA_struct_find_property(op->ptr, "gpencil_strokes")) && RNA_property_is_set(op->ptr, prop)) {
+               if (RNA_property_boolean_get(op->ptr, prop)) {
+                       options |= CTX_GPENCIL_STROKES;
+               }
+       }
 
        t->options = options;
 
index 5b3d5b776be29608abfc2a5b9e3796d991efbb8e..104120448c9de3703fcf8b9b4a0bacf2a3a2b166 100644 (file)
 #include "ED_uvedit.h"
 #include "ED_clip.h"
 #include "ED_mask.h"
+#include "ED_gpencil.h"
 
 #include "WM_api.h"  /* for WM_event_add_notifier to deal with stabilization nodes */
 #include "WM_types.h"
@@ -5613,7 +5614,10 @@ void special_aftertrans_update(bContext *C, TransInfo *t)
        }
 
 
-       if (t->spacetype == SPACE_SEQ) {
+       if (t->options & CTX_GPENCIL_STROKES) {
+               /* pass */
+       }
+       else if (t->spacetype == SPACE_SEQ) {
                /* freeSeqData in transform_conversions.c does this
                 * keep here so the else at the end wont run... */
 
@@ -7280,6 +7284,230 @@ void flushTransPaintCurve(TransInfo *t)
 }
 
 
+static void createTransGPencil(bContext *C, TransInfo *t)
+{
+       bGPdata *gpd = ED_gpencil_data_get_active(C);
+       bGPDlayer *gpl;
+       TransData *td = NULL;
+       float mtx[3][3], smtx[3][3];
+       
+       const Scene *scene = CTX_data_scene(C);
+       const int cfra = CFRA;
+       
+       const int propedit = (t->flag & T_PROP_EDIT);
+       const int propedit_connected = (t->flag & T_PROP_CONNECTED);
+       
+       
+       /* == Grease Pencil Strokes to Transform Data ==
+        * Grease Pencil stroke points can be a mixture of 2D (screen-space),
+        * or 3D coordinates. However, they're always saved as 3D points.
+        * For now, we just do these without creating TransData2D for the 2D
+        * strokes. This may cause issues in future though.
+        */
+       t->total = 0;
+       
+       if (gpd == NULL)
+               return;
+       
+       /* First Pass: Count the number of datapoints required for the strokes, 
+        * (and additional info about the configuration - e.g. 2D/3D?)
+        */
+       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;
+                       bGPDstroke *gps;
+                       
+                       for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                               if (propedit) {
+                                       /* Proportional Editing... */
+                                       if (propedit_connected) {
+                                               /* connected only - so only if selected */
+                                               if (gps->flag & GP_STROKE_SELECT)
+                                                       t->total += gps->totpoints;
+                                       }
+                                       else {
+                                               /* everything goes - connection status doesn't matter */
+                                               t->total += gps->totpoints;
+                                       }
+                               }
+                               else {
+                                       /* only selected stroke points are considered */
+                                       if (gps->flag & GP_STROKE_SELECT) {
+                                               bGPDspoint *pt;
+                                               int i;
+                                               
+                                               // TODO: 2D vs 3D?
+                                               for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                                       if (pt->flag & GP_SPOINT_SELECT)
+                                                               t->total++;
+                                               }
+                                       }
+                               }                               
+                       }
+               }
+       }
+       
+       /* Stop trying if nothing selected */
+       if (t->total == 0) {
+               return;
+       }
+       
+       /* Allocate memory for data */
+       t->data = MEM_callocN(t->total * sizeof(TransData), "TransData(GPencil)");
+       td = t->data;
+       
+       unit_m3(smtx);
+       unit_m3(mtx);
+       
+       /* Second Pass: Build transdata array */
+       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;
+                       bGPDstroke *gps;
+                       
+                       /* 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 realise you've
+                        *   spent too much time editing the wrong frame...
+                        */
+                       // 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;
+                               }
+                       }
+                       
+                       /* Loop over strokes, adding TransData for points as needed... */
+                       for (gps = gpf->strokes.first; gps; gps = gps->next) {
+                               TransData *head = td;
+                               TransData *tail = td;
+                               bool stroke_ok;
+                               
+                               /* What we need to include depends on proportional editing settings... */
+                               if (propedit) {
+                                       if (propedit_connected) {
+                                               /* A) "Connected" - Only those in selected strokes */
+                                               stroke_ok = (gps->flag & GP_STROKE_SELECT) != 0;
+                                       }
+                                       else {
+                                               /* B) All points, always */
+                                               stroke_ok = true;
+                                       }
+                               }
+                               else {
+                                       /* C) Only selected points in selected strokes */
+                                       stroke_ok = (gps->flag & GP_STROKE_SELECT) != 0;
+                               }
+                               
+                               /* Do stroke... */
+                               if (stroke_ok && gps->totpoints) {
+                                       bGPDspoint *pt;
+                                       int i;
+                                       
+#if 0  /* XXX: this isn't needed anymore; cannot calculate center this way or propedit breaks */                               
+                                       const float ninv = 1.0f / gps->totpoints;
+                                       float center[3] = {0.0f};
+                                       
+                                       /* compute midpoint of stroke */
+                                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                               madd_v3_v3v3fl(center, center, &pt->x, ninv);
+                                       }
+#endif
+                                       
+                                       /* add all necessary points... */
+                                       for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+                                               bool point_ok;
+                                               
+                                               /* include point? */
+                                               if (propedit) {
+                                                       /* Always all points in strokes that get included */
+                                                       point_ok = true;
+                                               }
+                                               else {
+                                                       /* Only selected points in selected strokes */
+                                                       point_ok = (pt->flag & GP_SPOINT_SELECT) != 0;
+                                               }
+                                               
+                                               /* do point... */
+                                               if (point_ok) {
+                                                       copy_v3_v3(td->iloc, &pt->x);
+                                                       copy_v3_v3(td->center, &pt->x); // XXX: what about  t->around == local?
+                                                       
+                                                       td->loc = &pt->x;
+                                                       
+                                                       td->flag = 0;
+                                                       
+                                                       if (pt->flag & GP_SPOINT_SELECT)
+                                                               td->flag |= TD_SELECTED;
+                                                       
+                                                       /* configure 2D points so that they don't play up... */
+                                         &