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