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