32c0aa6ab0bd2a1318ecf7fecaa4bd7aca2a35b6
[blender.git] / release / scripts / startup / bl_ui / properties_grease_pencil_common.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22 from bpy.types import Menu, UIList
23 from bpy.app.translations import pgettext_iface as iface_
24
25
26 def gpencil_stroke_placement_settings(context, layout):
27     if context.space_data.type == 'VIEW_3D':
28         propname = "annotation_stroke_placement_view3d"
29     elif context.space_data.type == 'SEQUENCE_EDITOR':
30         propname = "annotation_stroke_placement_sequencer_preview"
31     elif context.space_data.type == 'IMAGE_EDITOR':
32         propname = "annotation_stroke_placement_image_editor"
33     else:
34         propname = "annotation_stroke_placement_view2d"
35
36     ts = context.tool_settings
37
38     col = layout.column(align=True)
39
40     if context.space_data.type != 'VIEW_3D':
41         col.label(text="Stroke Placement:")
42         row = col.row(align=True)
43         row.prop_enum(ts, propname, 'VIEW')
44         row.prop_enum(ts, propname, 'CURSOR', text="Cursor")
45
46
47 def gpencil_active_brush_settings_simple(context, layout):
48     brush = context.active_gpencil_brush
49     if brush is None:
50         layout.label(text="No Active Brush")
51         return
52
53     col = layout.column()
54     col.label(text="Active Brush:      ")
55
56     row = col.row(align=True)
57     row.operator_context = 'EXEC_REGION_WIN'
58     row.operator_menu_enum("gpencil.brush_change", "brush", text="", icon='BRUSH_DATA')
59     row.prop(brush, "name", text="")
60
61     col.prop(brush, "size", slider=True)
62     row = col.row(align=True)
63     row.prop(brush, "use_random_pressure", text="", icon='RNDCURVE')
64     row.prop(brush, "pen_sensitivity_factor", slider=True)
65     row.prop(brush, "use_pressure", text="", icon='STYLUS_PRESSURE')
66     row = col.row(align=True)
67     row.prop(brush, "use_random_strength", text="", icon='RNDCURVE')
68     row.prop(brush, "strength", slider=True)
69     row.prop(brush, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
70     row = col.row(align=True)
71     row.prop(brush, "jitter", slider=True)
72     row.prop(brush, "use_jitter_pressure", text="", icon='STYLUS_PRESSURE')
73     row = col.row()
74     row.prop(brush, "angle", slider=True)
75     row.prop(brush, "angle_factor", text="Factor", slider=True)
76
77
78 # XXX: To be replaced with active tools
79 class AnnotationDrawingToolsPanel:
80     # subclass must set
81     # bl_space_type = 'IMAGE_EDITOR'
82     bl_label = "Annotation"
83     bl_category = "Annotation"
84     bl_region_type = 'TOOLS'
85
86     @classmethod
87     def poll(cls, context):
88         return True
89
90     @staticmethod
91     def draw(self, context):
92         layout = self.layout
93
94         is_3d_view = context.space_data.type == 'VIEW_3D'
95         is_clip_editor = context.space_data.type == 'CLIP_EDITOR'
96
97         col = layout.column(align=True)
98
99         col.label(text="Draw:")
100         row = col.row(align=True)
101         row.operator("gpencil.annotate", icon='GREASEPENCIL', text="Draw").mode = 'DRAW'
102         row.operator("gpencil.annotate", icon='FORCE_CURVE', text="Erase").mode = 'ERASER'  # XXX: Needs a dedicated icon
103
104         row = col.row(align=True)
105         row.operator("gpencil.annotate", icon='LINE_DATA', text="Line").mode = 'DRAW_STRAIGHT'
106         row.operator("gpencil.annotate", icon='MESH_DATA', text="Poly").mode = 'DRAW_POLY'
107
108         col.separator()
109
110         sub = col.column(align=True)
111         sub.operator("gpencil.blank_frame_add", icon='FILE_NEW')
112         sub.operator("gpencil.active_frames_delete_all", icon='X', text="Delete Frame(s)")
113
114         #sub = col.column(align=True)
115         #sub.prop(context.tool_settings, "use_gpencil_draw_additive", text="Additive Drawing")
116         #sub.prop(context.tool_settings, "use_gpencil_continuous_drawing", text="Continuous Drawing")
117         #sub.prop(context.tool_settings, "use_gpencil_draw_onback", text="Draw on Back")
118
119         col.separator()
120         col.separator()
121
122         if context.space_data.type in {'CLIP_EDITOR'}:
123             col.separator()
124             col.label(text="Data Source:")
125             row = col.row(align=True)
126             if is_3d_view:
127                 row.prop(context.tool_settings, "grease_pencil_source", expand=True)
128             elif is_clip_editor:
129                 row.prop(context.space_data, "grease_pencil_source", expand=True)
130
131         # col.separator()
132         # col.separator()
133
134         gpencil_stroke_placement_settings(context, col)
135
136         gpd = context.gpencil_data
137
138         if gpd and not is_3d_view:
139             layout.separator()
140             layout.separator()
141
142             col = layout.column(align=True)
143             col.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", toggle=True)  # was: icon='EDIT'
144
145
146 class GreasePencilStrokeEditPanel:
147     # subclass must set
148     # bl_space_type = 'IMAGE_EDITOR'
149     bl_label = "Edit Strokes"
150     bl_category = "Tools"
151     bl_region_type = 'TOOLS'
152
153     @classmethod
154     def poll(cls, context):
155         if context.gpencil_data is None:
156             return False
157
158         gpd = context.gpencil_data
159         return bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
160
161     @staticmethod
162     def draw(self, context):
163         layout = self.layout
164
165         is_3d_view = context.space_data.type == 'VIEW_3D'
166
167         if not is_3d_view:
168             layout.label(text="Select:")
169             col = layout.column(align=True)
170             col.operator("gpencil.select_all", text="Select All")
171             col.operator("gpencil.select_box")
172             col.operator("gpencil.select_circle")
173
174             layout.separator()
175
176             col = layout.column(align=True)
177             col.operator("gpencil.select_linked")
178             col.operator("gpencil.select_more")
179             col.operator("gpencil.select_less")
180             col.operator("gpencil.select_alternate")
181
182         layout.label(text="Edit:")
183         row = layout.row(align=True)
184         row.operator("gpencil.copy", text="Copy")
185         row.operator("gpencil.paste", text="Paste").type = 'COPY'
186         row.operator("gpencil.paste", text="Paste & Merge").type = 'MERGE'
187
188         col = layout.column(align=True)
189         col.operator("gpencil.delete")
190         col.operator("gpencil.duplicate_move", text="Duplicate")
191         if is_3d_view:
192             col.operator("gpencil.stroke_cyclical_set", text="Toggle Cyclic").type = 'TOGGLE'
193
194         layout.separator()
195
196         if not is_3d_view:
197             col = layout.column(align=True)
198             col.operator("transform.translate")                # icon='MAN_TRANS'
199             col.operator("transform.rotate")                   # icon='MAN_ROT'
200             col.operator("transform.resize", text="Scale")     # icon='MAN_SCALE'
201
202             layout.separator()
203
204         layout.separator()
205         col = layout.column(align=True)
206         col.operator_menu_enum("gpencil.stroke_arrange", text="Arrange Strokes...", property="direction")
207         col.operator("gpencil.stroke_change_color", text="Assign Material")
208
209         layout.separator()
210         col = layout.column(align=True)
211         col.operator("gpencil.stroke_subdivide", text="Subdivide")
212         row = col.row(align=True)
213         row.operator("gpencil.stroke_simplify_fixed", text="Simplify")
214         row.operator("gpencil.stroke_simplify", text="Adaptative")
215
216         col.separator()
217
218         row = col.row(align=True)
219         row.operator("gpencil.stroke_join", text="Join").type = 'JOIN'
220         row.operator("gpencil.stroke_join", text="& Copy").type = 'JOINCOPY'
221
222         col.operator("gpencil.stroke_flip", text="Flip Direction")
223
224         if is_3d_view:
225             layout.separator()
226
227             col = layout.column(align=True)
228             col.operator_menu_enum("gpencil.stroke_separate", text="Separate...", property="mode")
229             col.operator("gpencil.stroke_split", text="Split")
230
231             col = layout.column(align=True)
232             col.label(text="Cleanup:")
233             col.operator_menu_enum("gpencil.reproject", text="Reproject Strokes...", property="type")
234             col.operator_menu_enum("gpencil.frame_clean_fill", text="Clean Boundary Strokes...", property="mode")
235
236
237 class GreasePencilStrokeSculptPanel:
238     # subclass must set
239     # bl_space_type = 'IMAGE_EDITOR'
240     bl_label = "Sculpt Strokes"
241     bl_category = "Tools"
242
243     @staticmethod
244     def draw(self, context):
245         layout = self.layout
246         layout.use_property_split = True
247         layout.use_property_decorate = False
248
249         settings = context.tool_settings.gpencil_sculpt
250         tool = settings.sculpt_tool
251         brush = settings.brush
252
253         layout.template_icon_view(settings, "sculpt_tool", show_labels=True)
254
255         row = layout.row(align=True)
256         row.prop(brush, "size", slider=True)
257         sub = row.row(align=True)
258         sub.enabled = tool not in {'GRAB', 'CLONE'}
259         sub.prop(brush, "use_pressure_radius", text="")
260
261         row = layout.row(align=True)
262         row.prop(brush, "strength", slider=True)
263         row.prop(brush, "use_pressure_strength", text="")
264
265         layout.prop(brush, "use_falloff")
266
267         layout.use_property_split = False
268         if tool in {'THICKNESS', 'STRENGTH'}:
269             layout.row().prop(brush, "direction", expand=True)
270         elif tool == 'PINCH':
271             row = layout.row(align=True)
272             row.prop_enum(brush, "direction", value='ADD', text="Pinch")
273             row.prop_enum(brush, "direction", value='SUBTRACT', text="Inflate")
274         elif tool == 'TWIST':
275             row = layout.row(align=True)
276             row.prop_enum(brush, "direction", value='ADD', text="CCW")
277             row.prop_enum(brush, "direction", value='SUBTRACT', text="CW")
278
279
280 class GreasePencilSculptOptionsPanel:
281     bl_label = "Sculpt Strokes"
282
283     @classmethod
284     def poll(cls, context):
285         settings = context.tool_settings.gpencil_sculpt
286         tool = settings.sculpt_tool
287
288         return bool(tool in {'SMOOTH', 'RANDOMIZE', 'SMOOTH'})
289
290     @staticmethod
291     def draw(self, context):
292         layout = self.layout
293         layout.use_property_split = True
294         layout.use_property_decorate = False
295
296         settings = context.tool_settings.gpencil_sculpt
297         tool = settings.sculpt_tool
298         brush = settings.brush
299
300         if tool in {'SMOOTH', 'RANDOMIZE'}:
301             layout.prop(settings, "use_edit_position", text="Affect Position")
302             layout.prop(settings, "use_edit_strength", text="Affect Strength")
303             layout.prop(settings, "use_edit_thickness", text="Affect Thickness")
304
305             if tool == 'SMOOTH':
306                 layout.prop(brush, "use_edit_pressure")
307
308             layout.prop(settings, "use_edit_uv", text="Affect UV")
309
310
311 # GP Object Tool Settings
312 class GreasePencilAppearancePanel:
313     bl_label = "Brush Appearance"
314     bl_options = {'DEFAULT_CLOSED'}
315
316     @classmethod
317     def poll(cls, context):
318         ob = context.active_object
319         return ob and ob.type == 'GPENCIL'
320
321     @staticmethod
322     def draw(self, context):
323         layout = self.layout
324         layout.use_property_split = True
325         layout.use_property_decorate = False
326
327         ob = context.active_object
328
329         if ob.mode == 'PAINT_GPENCIL':
330             brush = context.active_gpencil_brush
331             gp_settings = brush.gpencil_settings
332
333             sub = layout.column(align=True)
334             sub.enabled = not brush.use_custom_icon
335             sub.prop(gp_settings, "gp_icon", text="Icon")
336
337             layout.prop(brush, "use_custom_icon")
338             sub = layout.column()
339             sub.active = brush.use_custom_icon
340             sub.prop(brush, "icon_filepath", text="")
341
342             layout.prop(gp_settings, "use_cursor", text="Show Brush")
343
344             if brush.gpencil_tool == 'DRAW':
345                 layout.prop(gp_settings, "show_lasso", text="Show fill color while drawing")
346
347             if brush.gpencil_tool == 'FILL':
348                 layout.prop(brush, "cursor_color_add", text="Color")
349
350         elif ob.mode in {'SCULPT_GPENCIL', 'WEIGHT_GPENCIL'}:
351             settings = context.tool_settings.gpencil_sculpt
352             brush = settings.brush
353             tool = settings.sculpt_tool
354
355             col = layout.column(align=True)
356             col.prop(brush, "use_cursor", text="Show Brush")
357
358             if tool in {'THICKNESS', 'STRENGTH'}:
359                 col.prop(brush, "cursor_color_add", text="Add")
360                 col.prop(brush, "cursor_color_sub", text="Substract")
361             elif tool == 'PINCH':
362                 col.prop(brush, "cursor_color_add", text="Pinch")
363                 col.prop(brush, "cursor_color_sub", text="Inflate")
364             elif tool == 'TWIST':
365                 col.prop(brush, "cursor_color_add", text="CCW")
366                 col.prop(brush, "cursor_color_sub", text="CW")
367             else:
368                 col.prop(brush, "cursor_color_add", text="")
369
370
371 class GPENCIL_MT_pie_tool_palette(Menu):
372     """A pie menu for quick access to Grease Pencil tools"""
373     bl_label = "Grease Pencil Tools"
374
375     def draw(self, context):
376         layout = self.layout
377
378         pie = layout.menu_pie()
379         gpd = context.gpencil_data
380
381         # W - Drawing Types
382         col = pie.column()
383         col.operator("gpencil.draw", text="Draw", icon='GREASEPENCIL').mode = 'DRAW'
384         col.operator("gpencil.draw", text="Straight Lines", icon='LINE_DATA').mode = 'DRAW_STRAIGHT'
385         col.operator("gpencil.draw", text="Poly", icon='MESH_DATA').mode = 'DRAW_POLY'
386
387         # E - Eraser
388         # XXX: needs a dedicated icon...
389         col = pie.column()
390         col.operator("gpencil.draw", text="Eraser", icon='FORCE_CURVE').mode = 'ERASER'
391
392         # E - "Settings" Palette is included here too, since it needs to be in a stable position...
393         if gpd and gpd.layers.active:
394             col.separator()
395             col.operator("wm.call_menu_pie", text="Settings...", icon='SCRIPTWIN').name = "GPENCIL_MT_pie_settings_palette"
396
397         # Editing tools
398         if gpd:
399             if gpd.use_stroke_edit_mode and context.editable_gpencil_strokes:
400                 # S - Exit Edit Mode
401                 pie.operator("gpencil.editmode_toggle", text="Exit Edit Mode", icon='EDIT')
402
403                 # N - Transforms
404                 col = pie.column()
405                 row = col.row(align=True)
406                 row.operator("transform.translate", icon='MAN_TRANS')
407                 row.operator("transform.rotate", icon='MAN_ROT')
408                 row.operator("transform.resize", text="Scale", icon='MAN_SCALE')
409                 row = col.row(align=True)
410                 row.label(text="Proportional Edit:")
411                 row.prop(context.tool_settings, "proportional_edit", text="", icon_only=True)
412                 row.prop(context.tool_settings, "proportional_edit_falloff", text="", icon_only=True)
413
414                 # NW - Select (Non-Modal)
415                 col = pie.column()
416                 col.operator("gpencil.select_all", text="Select All", icon='PARTICLE_POINT')
417                 col.operator("gpencil.select_all", text="Select Inverse", icon='BLANK1')
418                 col.operator("gpencil.select_linked", text="Select Linked", icon='LINKED')
419                 col.operator("gpencil.palettecolor_select", text="Select Color", icon='COLOR')
420
421                 # NE - Select (Modal)
422                 col = pie.column()
423                 col.operator("gpencil.select_box", text="Box Select", icon='BORDER_RECT')
424                 col.operator("gpencil.select_circle", text="Circle Select", icon='META_EMPTY')
425                 col.operator("gpencil.select_lasso", text="Lasso Select", icon='BORDER_LASSO')
426                 col.operator("gpencil.select_alternate", text="Alternate Select", icon='BORDER_LASSO')
427
428                 # SW - Edit Tools
429                 col = pie.column()
430                 col.operator("gpencil.duplicate_move", icon='PARTICLE_PATH', text="Duplicate")
431                 col.operator("gpencil.delete", icon='X', text="Delete...")
432
433                 # SE - More Tools
434                 pie.operator("wm.call_menu_pie", text="More...").name = "GPENCIL_MT_pie_tools_more"
435             else:
436                 # Toggle Edit Mode
437                 pie.operator("gpencil.editmode_toggle", text="Enable Stroke Editing", icon='EDIT')
438
439
440 class GPENCIL_MT_pie_settings_palette(Menu):
441     """A pie menu for quick access to Grease Pencil settings"""
442     bl_label = "Grease Pencil Settings"
443
444     @classmethod
445     def poll(cls, context):
446         return bool(context.gpencil_data and context.active_gpencil_layer)
447
448     def draw(self, context):
449         layout = self.layout
450
451         pie = layout.menu_pie()
452         gpd = context.gpencil_data
453         gpl = context.active_gpencil_layer
454         palcolor = None  # context.active_gpencil_palettecolor
455         # brush = context.active_gpencil_brush
456
457         is_editmode = bool(gpd and gpd.use_stroke_edit_mode and context.editable_gpencil_strokes)
458
459         # W - Stroke draw settings
460         col = pie.column(align=True)
461         if palcolor is not None:
462             col.enabled = not palcolor.lock
463             col.label(text="Stroke")
464             col.prop(palcolor, "color", text="")
465             col.prop(palcolor, "alpha", text="", slider=True)
466
467         # E - Fill draw settings
468         col = pie.column(align=True)
469         if palcolor is not None:
470             col.enabled = not palcolor.lock
471             col.label(text="Fill")
472             col.prop(palcolor, "fill_color", text="")
473             col.prop(palcolor, "fill_alpha", text="", slider=True)
474
475         # S Brush settings
476         gpencil_active_brush_settings_simple(context, pie)
477
478         # N - Active Layer
479         col = pie.column()
480         col.label(text="Active Layer:      ")
481
482         row = col.row()
483         row.operator_context = 'EXEC_REGION_WIN'
484         row.operator_menu_enum("gpencil.layer_change", "layer", text="", icon='GREASEPENCIL')
485         row.prop(gpl, "info", text="")
486         row.operator("gpencil.layer_remove", text="", icon='X')
487
488         row = col.row()
489         row.prop(gpl, "lock")
490         row.prop(gpl, "hide")
491         col.prop(gpl, "use_onion_skinning")
492
493         # NW/NE/SW/SE - These operators are only available in editmode
494         # as they require strokes to be selected to work
495         if is_editmode:
496             # NW - Move stroke Down
497             col = pie.column(align=True)
498             col.label(text="Arrange Strokes")
499             col.operator("gpencil.stroke_arrange", text="Send to Back").direction = 'BOTTOM'
500             col.operator("gpencil.stroke_arrange", text="Send Backward").direction = 'DOWN'
501
502             # NE - Move stroke Up
503             col = pie.column(align=True)
504             col.label(text="Arrange Strokes")
505             col.operator("gpencil.stroke_arrange", text="Bring to Front").direction = 'TOP'
506             col.operator("gpencil.stroke_arrange", text="Bring Forward").direction = 'UP'
507
508             # SW - Move stroke to color
509             col = pie.column(align=True)
510             col.operator("gpencil.stroke_change_color", text="Move to Color")
511
512             # SE - Join strokes
513             col = pie.column(align=True)
514             col.label(text="Join Strokes")
515             row = col.row()
516             row.operator("gpencil.stroke_join", text="Join").type = 'JOIN'
517             row.operator("gpencil.stroke_join", text="Join & Copy").type = 'JOINCOPY'
518             col.operator("gpencil.stroke_flip", text="Flip Direction")
519
520             col.prop(gpd, "show_stroke_direction", text="Show Drawing Direction")
521
522
523 class GPENCIL_MT_pie_tools_more(Menu):
524     """A pie menu for accessing more Grease Pencil tools"""
525     bl_label = "More Grease Pencil Tools"
526
527     @classmethod
528     def poll(cls, context):
529         gpd = context.gpencil_data
530         return bool(gpd and gpd.use_stroke_edit_mode and context.editable_gpencil_strokes)
531
532     def draw(self, context):
533         layout = self.layout
534
535         pie = layout.menu_pie()
536         # gpd = context.gpencil_data
537
538         col = pie.column(align=True)
539         col.operator("gpencil.copy", icon='COPYDOWN', text="Copy")
540         col.operator("gpencil.paste", icon='PASTEDOWN', text="Paste")
541
542         col = pie.column(align=True)
543         col.operator("gpencil.select_more", icon='ADD')
544         col.operator("gpencil.select_less", icon='REMOVE')
545
546         pie.operator("transform.mirror", icon='MOD_MIRROR')
547         pie.operator("transform.bend", icon='MOD_SIMPLEDEFORM')
548         pie.operator("transform.shear", icon='MOD_TRIANGULATE')
549         pie.operator("transform.tosphere", icon='MOD_MULTIRES')
550
551         pie.operator("gpencil.convert", icon='OUTLINER_OB_CURVE', text="Convert...")
552         pie.operator("wm.call_menu_pie", text="Back to Main Palette...").name = "GPENCIL_MT_pie_tool_palette"
553
554
555 class GPENCIL_MT_pie_sculpt(Menu):
556     """A pie menu for accessing Grease Pencil stroke sculpt settings"""
557     bl_label = "Grease Pencil Sculpt"
558
559     @classmethod
560     def poll(cls, context):
561         gpd = context.gpencil_data
562         return bool(gpd and gpd.use_stroke_edit_mode and context.editable_gpencil_strokes)
563
564     def draw(self, context):
565         layout = self.layout
566
567         pie = layout.menu_pie()
568
569         settings = context.tool_settings.gpencil_sculpt
570         brush = settings.brush
571
572         # W - Launch Sculpt Mode
573         col = pie.column()
574         # col.label(text="Tool:")
575         col.prop(settings, "sculpt_tool", text="")
576         col.operator("gpencil.sculpt_paint", text="Sculpt", icon='SCULPTMODE_HLT')
577
578         # E - Common Settings
579         col = pie.column(align=True)
580         col.prop(brush, "size", slider=True)
581         row = col.row(align=True)
582         row.prop(brush, "strength", slider=True)
583         # row.prop(brush, "use_pressure_strength", text="", icon_only=True)
584         col.prop(brush, "use_falloff")
585         if settings.sculpt_tool in {'SMOOTH', 'RANDOMIZE'}:
586             row = col.row(align=True)
587             row.prop(settings, "use_edit_position", text="Position", icon='MESH_DATA', toggle=True)
588             row.prop(settings, "use_edit_strength", text="Strength", icon='COLOR', toggle=True)
589             row.prop(settings, "use_edit_thickness", text="Thickness", icon='LINE_DATA', toggle=True)
590
591         # S - Change Brush Type Shortcuts
592         row = pie.row()
593         row.prop_enum(settings, "tool", value='GRAB')
594         row.prop_enum(settings, "tool", value='PUSH')
595         row.prop_enum(settings, "tool", value='CLONE')
596
597         # N - Change Brush Type Shortcuts
598         row = pie.row()
599         row.prop_enum(settings, "tool", value='SMOOTH')
600         row.prop_enum(settings, "tool", value='THICKNESS')
601         row.prop_enum(settings, "tool", value='STRENGTH')
602         row.prop_enum(settings, "tool", value='RANDOMIZE')
603
604
605 class GPENCIL_MT_snap(Menu):
606     bl_label = "Snap"
607
608     def draw(self, context):
609         layout = self.layout
610
611         layout.operator("gpencil.snap_to_grid", text="Selection to Grid")
612         layout.operator("gpencil.snap_to_cursor", text="Selection to Cursor").use_offset = False
613         layout.operator("gpencil.snap_to_cursor", text="Selection to Cursor (Keep Offset)").use_offset = True
614
615         layout.separator()
616
617         layout.operator("gpencil.snap_cursor_to_selected", text="Cursor to Selected")
618         layout.operator("view3d.snap_cursor_to_center", text="Cursor to World Origin")
619         layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid")
620
621
622 class GPENCIL_MT_separate(Menu):
623     bl_label = "Separate"
624
625     def draw(self, context):
626         layout = self.layout
627         layout.operator("gpencil.stroke_separate", text="Selected Points").mode = 'POINT'
628         layout.operator("gpencil.stroke_separate", text="Selected Strokes").mode = 'STROKE'
629         layout.operator("gpencil.stroke_separate", text="Active Layer").mode = 'LAYER'
630
631
632 class GPENCIL_MT_gpencil_draw_specials(Menu):
633     bl_label = "GPencil Draw Specials"
634
635     def draw(self, context):
636         layout = self.layout
637
638         layout.operator_context = 'INVOKE_REGION_WIN'
639
640         layout.operator("gpencil.frame_duplicate", text="Duplicate Active Frame")
641         layout.operator("gpencil.frame_duplicate", text="Duplicate Active Frame All Layers").mode = 'ALL'
642
643         layout.separator()
644         layout.operator("gpencil.primitive", text="Line", icon='IPO_CONSTANT').type = 'LINE'
645         layout.operator("gpencil.primitive", text="Rectangle", icon='UV_FACESEL').type = 'BOX'
646         layout.operator("gpencil.primitive", text="Circle", icon='ANTIALIASED').type = 'CIRCLE'
647         layout.operator("gpencil.primitive", text="Arc", icon='SPHERECURVE').type = 'ARC'
648         layout.operator("gpencil.primitive", text="Curve", icon='CURVE_BEZCURVE').type = 'CURVE'
649
650
651 class GPENCIL_MT_gpencil_draw_delete(Menu):
652     bl_label = "GPencil Draw Delete"
653
654     def draw(self, context):
655         layout = self.layout
656
657         layout.operator_context = 'INVOKE_REGION_WIN'
658
659         layout.operator("gpencil.active_frames_delete_all", text="Delete Frame")
660
661
662 class GPENCIL_MT_cleanup(Menu):
663     bl_label = "Clean Up"
664
665     def draw(self, context):
666         layout = self.layout
667         layout.operator("gpencil.frame_clean_loose", text="Loose Points")
668         layout.separator()
669
670         layout.operator("gpencil.frame_clean_fill", text="Boundary Strokes").mode = 'ACTIVE'
671         layout.operator("gpencil.frame_clean_fill", text="Boundary Strokes all Frames").mode = 'ALL'
672         layout.separator()
673
674         layout.operator("gpencil.reproject")
675
676
677 class GPENCIL_UL_annotation_layer(UIList):
678     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
679         # assert(isinstance(item, bpy.types.GPencilLayer)
680         gpl = item
681
682         if self.layout_type in {'DEFAULT', 'COMPACT'}:
683             if gpl.lock:
684                 layout.active = False
685
686             split = layout.split(factor=0.2)
687             split.prop(gpl, "color", text="", emboss=True)
688             split.prop(gpl, "info", text="", emboss=False)
689
690             row = layout.row(align=True)
691             # row.prop(gpl, "lock", text="", emboss=False)
692             row.prop(gpl, "hide", text="", emboss=False)
693         elif self.layout_type == 'GRID':
694             layout.alignment = 'CENTER'
695             layout.label(text="", icon_value=icon)
696
697
698 class AnnotationDataPanel:
699     bl_label = "Annotations"
700     bl_region_type = 'UI'
701     bl_options = {'DEFAULT_CLOSED'}
702
703     @classmethod
704     def poll(cls, context):
705         # Show this panel as long as someone that might own this exists
706         # AND the owner isn't an object (e.g. GP Object)
707         if context.gpencil_data_owner is None:
708             return False
709         elif type(context.gpencil_data_owner) is bpy.types.Object:
710             return False
711         else:
712             return True
713
714     @staticmethod
715     def draw_header(self, context):
716         if context.space_data.type not in {'VIEW_3D', 'TOPBAR'}:
717             self.layout.prop(context.space_data, "show_annotation", text="")
718
719     @staticmethod
720     def draw(self, context):
721         layout = self.layout
722         layout.use_property_decorate = False
723
724         # Grease Pencil owner.
725         gpd_owner = context.gpencil_data_owner
726         gpd = context.gpencil_data
727
728         # Owner selector.
729         if context.space_data.type == 'CLIP_EDITOR':
730             layout.row().prop(context.space_data, "grease_pencil_source", expand=True)
731
732         layout.template_ID(gpd_owner, "grease_pencil", new="gpencil.data_add", unlink="gpencil.data_unlink")
733
734         # List of layers/notes.
735         if gpd and gpd.layers:
736             self.draw_layers(context, layout, gpd)
737
738     def draw_layers(self, context, layout, gpd):
739         row = layout.row()
740
741         col = row.column()
742         if len(gpd.layers) >= 2:
743             layer_rows = 5
744         else:
745             layer_rows = 3
746         col.template_list("GPENCIL_UL_annotation_layer", "", gpd, "layers", gpd.layers, "active_index",
747                           rows=layer_rows, reverse=True)
748
749         col = row.column()
750
751         sub = col.column(align=True)
752         sub.operator("gpencil.layer_add", icon='ADD', text="")
753         sub.operator("gpencil.layer_remove", icon='REMOVE', text="")
754
755         gpl = context.active_gpencil_layer
756         if gpl:
757             if len(gpd.layers) > 1:
758                 col.separator()
759
760                 sub = col.column(align=True)
761                 sub.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP'
762                 sub.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN'
763
764         tool_settings = context.tool_settings
765         if gpd and gpl:
766             layout.prop(gpl, "thickness")
767         else:
768             layout.prop(tool_settings, "annotation_thickness", text="Thickness")
769
770         if gpl:
771             # Full-Row - Frame Locking (and Delete Frame)
772             row = layout.row(align=True)
773             row.active = not gpl.lock
774
775             if gpl.active_frame:
776                 lock_status = iface_("Locked") if gpl.lock_frame else iface_("Unlocked")
777                 lock_label = iface_("Frame: %d (%s)") % (gpl.active_frame.frame_number, lock_status)
778             else:
779                 lock_label = iface_("Lock Frame")
780             row.prop(gpl, "lock_frame", text=lock_label, icon='UNLOCKED')
781             row.operator("gpencil.active_frame_delete", text="", icon='X')
782
783
784 class AnnotationOnionSkin:
785     bl_label = "Onion Skin"
786     bl_region_type = 'UI'
787     bl_options = {'DEFAULT_CLOSED'}
788
789     @classmethod
790     def poll(cls, context):
791         # Show this panel as long as someone that might own this exists
792         # AND the owner isn't an object (e.g. GP Object)
793         if context.gpencil_data_owner is None:
794             return False
795         elif type(context.gpencil_data_owner) is bpy.types.Object:
796             return False
797         else:
798             gpl = context.active_gpencil_layer
799             if gpl is None:
800                 return False
801
802             return True
803
804     @staticmethod
805     def draw_header(self, context):
806         gpl = context.active_gpencil_layer
807         self.layout.prop(gpl, "use_annotation_onion_skinning", text="")
808
809     @staticmethod
810     def draw(self, context):
811         layout = self.layout
812         layout.use_property_decorate = False
813
814         gpl = context.active_gpencil_layer
815         col = layout.column()
816         split = col.split(factor=0.5)
817         split.active = gpl.use_annotation_onion_skinning
818
819         # - Before Frames
820         sub = split.column(align=True)
821         row = sub.row(align=True)
822         row.prop(gpl, "annotation_onion_before_color", text="")
823         sub.prop(gpl, "annotation_onion_before_range", text="Before")
824
825         # - After Frames
826         sub = split.column(align=True)
827         row = sub.row(align=True)
828         row.prop(gpl, "annotation_onion_after_color", text="")
829         sub.prop(gpl, "annotation_onion_after_range", text="After")
830
831
832 class GreasePencilOnionPanel:
833     @staticmethod
834     def draw_settings(layout, gp):
835         col = layout.column()
836         col.prop(gp, "onion_mode")
837         col.prop(gp, "onion_factor", text="Opacity", slider=True)
838
839         if gp.onion_mode == 'ABSOLUTE':
840             col = layout.column(align=True)
841             col.prop(gp, "ghost_before_range", text="Frames Before")
842             col.prop(gp, "ghost_after_range", text="Frames After")
843         if gp.onion_mode == 'RELATIVE':
844             col = layout.column(align=True)
845             col.prop(gp, "ghost_before_range", text="Keyframes Before")
846             col.prop(gp, "ghost_after_range", text="Keyframes After")
847
848         layout.prop(gp, "use_ghost_custom_colors", text="Use Custom Colors")
849
850         if gp.use_ghost_custom_colors:
851             col = layout.column(align=True)
852             col.active = gp.use_ghost_custom_colors
853             col.prop(gp, "before_color", text="Color Before")
854             col.prop(gp, "after_color", text="After")
855
856         layout.prop(gp, "use_ghosts_always", text="View In Render")
857
858         col = layout.column(align=True)
859         col.prop(gp, "use_onion_fade", text="Fade")
860         if hasattr(gp, "use_onion_loop"):  # XXX
861             sub = layout.column()
862             sub.active = gp.onion_mode in ('RELATIVE', 'SELECTED')
863             sub.prop(gp, "use_onion_loop", text="Loop")
864
865
866 class GreasePencilToolsPanel:
867     # For use in "2D" Editors without their own toolbar
868     # subclass must set
869     # bl_space_type = 'IMAGE_EDITOR'
870     bl_label = "Grease Pencil Settings"
871     bl_region_type = 'UI'
872     bl_options = {'DEFAULT_CLOSED'}
873
874     @classmethod
875     def poll(cls, context):
876         # XXX - disabled in 2.8 branch.
877         return False
878
879         return (context.gpencil_data is not None)
880
881     @staticmethod
882     def draw(self, context):
883         layout = self.layout
884
885         gpd = context.gpencil_data
886
887         layout.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
888
889         layout.separator()
890
891         layout.label(text="Proportional Edit:")
892         row = layout.row()
893         row.prop(context.tool_settings, "proportional_edit", text="")
894         row.prop(context.tool_settings, "proportional_edit_falloff", text="")
895
896         layout.separator()
897         layout.separator()
898
899         gpencil_active_brush_settings_simple(context, layout)
900
901         layout.separator()
902
903         gpencil_stroke_placement_settings(context, layout)
904
905
906 class GreasePencilMaterialsPanel:
907     # Mix-in, use for properties editor and top-bar.
908
909     @classmethod
910     def poll(cls, context):
911         ob = context.object
912         return ob and ob.type == 'GPENCIL'
913
914     @staticmethod
915     def draw(self, context):
916         layout = self.layout
917         show_full_ui = (self.bl_space_type == 'PROPERTIES')
918
919         gpd = context.gpencil_data
920
921         ob = context.object
922
923         is_sortable = len(ob.material_slots) > 1
924         rows = 7
925
926         row = layout.row()
927
928         row.template_list("GPENCIL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=rows)
929
930         col = row.column(align=True)
931         if show_full_ui:
932             col.operator("object.material_slot_add", icon='ADD', text="")
933             col.operator("object.material_slot_remove", icon='REMOVE', text="")
934
935         col.menu("GPENCIL_MT_color_specials", icon='DOWNARROW_HLT', text="")
936
937         if is_sortable:
938             col.separator()
939
940             col.operator("object.material_slot_move", icon='TRIA_UP', text="").direction = 'UP'
941             col.operator("object.material_slot_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
942
943             col.separator()
944
945             sub = col.column(align=True)
946             sub.operator("gpencil.color_isolate", icon='LOCKED', text="").affect_visibility = False
947             sub.operator("gpencil.color_isolate", icon='RESTRICT_VIEW_ON', text="").affect_visibility = True
948
949         if show_full_ui:
950             row = layout.row()
951
952             row.template_ID(ob, "active_material", new="material.new", live_icon=True)
953
954             slot = context.material_slot
955             if slot:
956                 icon_link = 'MESH_DATA' if slot.link == 'DATA' else 'OBJECT_DATA'
957                 row.prop(slot, "link", icon=icon_link, icon_only=True)
958
959             if gpd.use_stroke_edit_mode:
960                 row = layout.row(align=True)
961                 row.operator("gpencil.stroke_change_color", text="Assign")
962                 row.operator("gpencil.color_select", text="Select").deselect = False
963                 row.operator("gpencil.color_select", text="Deselect").deselect = True
964
965
966 class GPENCIL_UL_layer(UIList):
967     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
968         # assert(isinstance(item, bpy.types.GPencilLayer)
969         gpl = item
970         gpd = context.gpencil_data
971
972         if self.layout_type in {'DEFAULT', 'COMPACT'}:
973             if gpl.lock:
974                 layout.active = False
975
976             row = layout.row(align=True)
977             row.label(
978                 text="",
979                 icon='BONE_DATA' if gpl.is_parented else 'BLANK1',
980             )
981             row.prop(gpl, "info", text="", emboss=False)
982
983             row = layout.row(align=True)
984             row.prop(gpl, "clamp_layer", text="",
985                         icon='MOD_MASK' if gpl.clamp_layer else 'LAYER_ACTIVE',
986                         emboss=False)
987
988             row.prop(gpl, "lock", text="", emboss=False)
989             row.prop(gpl, "hide", text="", emboss=False)
990             subrow = row.row(align=True)
991             subrow.prop(
992                 gpl,
993                 "use_onion_skinning",
994                 text="",
995                 icon='ONIONSKIN_ON' if gpl.use_onion_skinning else 'ONIONSKIN_OFF',
996                 emboss=False,
997             )
998         elif self.layout_type == 'GRID':
999             layout.alignment = 'CENTER'
1000             layout.label(
1001                 text="",
1002                 icon_value=icon,
1003             )
1004
1005
1006 classes = (
1007     GPENCIL_MT_pie_tool_palette,
1008     GPENCIL_MT_pie_settings_palette,
1009     GPENCIL_MT_pie_tools_more,
1010     GPENCIL_MT_pie_sculpt,
1011
1012     GPENCIL_MT_snap,
1013     GPENCIL_MT_separate,
1014     GPENCIL_MT_cleanup,
1015
1016     GPENCIL_MT_gpencil_draw_specials,
1017     GPENCIL_MT_gpencil_draw_delete,
1018
1019     GPENCIL_UL_annotation_layer,
1020     GPENCIL_UL_layer,
1021 )
1022
1023 if __name__ == "__main__":  # only for live edit.
1024     from bpy.utils import register_class
1025     for cls in classes:
1026         register_class(cls)