84442f9b2d99b477c5850c7ba00d0b8427cbc40c
[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
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 = "gpencil_stroke_placement_view3d"
29     elif context.space_data.type == 'SEQUENCE_EDITOR':
30         propname = "gpencil_stroke_placement_sequencer_preview"
31     elif context.space_data.type == 'IMAGE_EDITOR':
32         propname = "gpencil_stroke_placement_image_editor"
33     else:
34         propname = "gpencil_stroke_placement_view2d"
35
36     ts = context.tool_settings
37
38     col = layout.column(align=True)
39
40     col.label(text="Stroke Placement:")
41
42     row = col.row(align=True)
43     row.prop_enum(ts, propname, 'VIEW')
44     row.prop_enum(ts, propname, 'CURSOR')
45
46     if context.space_data.type == 'VIEW_3D':
47         row = col.row(align=True)
48         row.prop_enum(ts, propname, 'SURFACE')
49         row.prop_enum(ts, propname, 'STROKE')
50
51         row = col.row(align=False)
52         row.active = getattr(ts, propname) in {'SURFACE', 'STROKE'}
53         row.prop(ts, "use_gpencil_stroke_endpoints")
54
55
56 def gpencil_active_brush_settings_simple(context, layout):
57     brush = context.active_gpencil_brush
58
59     col = layout.column()
60     col.label("Active Brush:      ")
61
62     row = col.row(align=True)
63     row.operator_context = 'EXEC_REGION_WIN'
64     row.operator_menu_enum("gpencil.brush_change", "brush", text="", icon='BRUSH_DATA')
65     row.prop(brush, "name", text="")
66
67     col.prop(brush, "line_width", slider=True)
68     row = col.row(align=True)
69     row.prop(brush, "use_random_pressure", text='', icon='RNDCURVE')
70     row.prop(brush, "pen_sensitivity_factor", slider=True)
71     row.prop(brush, "use_pressure", text='', icon='STYLUS_PRESSURE')
72     row = col.row(align=True)
73     row.prop(brush, "use_random_strength", text='', icon='RNDCURVE')
74     row.prop(brush, "strength", slider=True)
75     row.prop(brush, "use_strength_pressure", text='', icon='STYLUS_PRESSURE')
76     row = col.row(align=True)
77     row.prop(brush, "jitter", slider=True)
78     row.prop(brush, "use_jitter_pressure", text='', icon='STYLUS_PRESSURE')
79     row = col.row()
80     row.prop(brush, "angle", slider=True)
81     row.prop(brush, "angle_factor", text="Factor", slider=True)
82
83
84 class GreasePencilDrawingToolsPanel:
85     # subclass must set
86     # bl_space_type = 'IMAGE_EDITOR'
87     bl_label = "Grease Pencil"
88     bl_category = "Grease Pencil"
89     bl_region_type = 'TOOLS'
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.draw", icon='GREASEPENCIL', text="Draw").mode = 'DRAW'
103         row.operator("gpencil.draw", icon='FORCE_CURVE', text="Erase").mode = 'ERASER'  # XXX: Needs a dedicated icon
104
105         row = col.row(align=True)
106         row.operator("gpencil.draw", icon='LINE_DATA', text="Line").mode = 'DRAW_STRAIGHT'
107         row.operator("gpencil.draw", icon='MESH_DATA', text="Poly").mode = 'DRAW_POLY'
108
109         sub = col.column(align=True)
110         sub.prop(context.tool_settings, "use_gpencil_additive_drawing", text="Additive Drawing")
111         sub.prop(context.tool_settings, "use_gpencil_continuous_drawing", text="Continuous Drawing")
112         sub.prop(context.tool_settings, "use_gpencil_draw_onback", text="Draw on Back")
113
114         col.separator()
115         col.separator()
116
117         if context.space_data.type in {'VIEW_3D', 'CLIP_EDITOR'}:
118             col.separator()
119             col.label("Data Source:")
120             row = col.row(align=True)
121             if is_3d_view:
122                 row.prop(context.tool_settings, "grease_pencil_source", expand=True)
123             elif is_clip_editor:
124                 row.prop(context.space_data, "grease_pencil_source", expand=True)
125
126         col.separator()
127         col.separator()
128
129         gpencil_stroke_placement_settings(context, col)
130
131         gpd = context.gpencil_data
132
133         if gpd and not is_3d_view:
134             layout.separator()
135             layout.separator()
136
137             col = layout.column(align=True)
138             col.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
139
140         if is_3d_view:
141             col.separator()
142             col.separator()
143
144             col.label(text="Tools:")
145             col.operator_menu_enum("gpencil.convert", text="Convert to Geometry...", property="type")
146             col.operator("view3d.ruler")
147
148
149 class GreasePencilStrokeEditPanel:
150     # subclass must set
151     # bl_space_type = 'IMAGE_EDITOR'
152     bl_label = "Edit Strokes"
153     bl_category = "Grease Pencil"
154     bl_region_type = 'TOOLS'
155     bl_options = {'DEFAULT_CLOSED'}
156
157     @classmethod
158     def poll(cls, context):
159         if context.gpencil_data is None:
160             return False
161
162         gpd = context.gpencil_data
163         return bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
164
165     @staticmethod
166     def draw(self, context):
167         layout = self.layout
168
169         is_3d_view = context.space_data.type == 'VIEW_3D'
170
171         if not is_3d_view:
172             layout.label(text="Select:")
173             col = layout.column(align=True)
174             col.operator("gpencil.select_all", text="Select All")
175             col.operator("gpencil.select_border")
176             col.operator("gpencil.select_circle")
177
178             layout.separator()
179
180             col = layout.column(align=True)
181             col.operator("gpencil.select_linked")
182             col.operator("gpencil.select_more")
183             col.operator("gpencil.select_less")
184             col.operator("gpencil.palettecolor_select")
185
186         layout.label(text="Edit:")
187         row = layout.row(align=True)
188         row.operator("gpencil.copy", text="Copy")
189         row.operator("gpencil.paste", text="Paste").type = 'COPY'
190         row.operator("gpencil.paste", text="Paste & Merge").type = 'MERGE'
191
192         col = layout.column(align=True)
193         col.operator("gpencil.delete")
194         col.operator("gpencil.duplicate_move", text="Duplicate")
195         if is_3d_view:
196             col.operator("gpencil.stroke_cyclical_set", text="Toggle Cyclic").type = 'TOGGLE'
197
198         layout.separator()
199
200         if not is_3d_view:
201             col = layout.column(align=True)
202             col.operator("transform.translate")                # icon='MAN_TRANS'
203             col.operator("transform.rotate")                   # icon='MAN_ROT'
204             col.operator("transform.resize", text="Scale")     # icon='MAN_SCALE'
205
206             layout.separator()
207
208         col = layout.column(align=True)
209         col.operator("transform.bend", text="Bend")
210         col.operator("transform.mirror", text="Mirror")
211         col.operator("transform.shear", text="Shear")
212         col.operator("transform.tosphere", text="To Sphere")
213
214         layout.separator()
215         col = layout.column(align=True)
216         col.operator_menu_enum("gpencil.stroke_arrange", text="Arrange Strokes...", property="direction")
217         col.operator("gpencil.stroke_change_color", text="Move to Color")
218
219         if is_3d_view:
220             layout.separator()
221             col = layout.column(align=True)
222             col.operator("gpencil.interpolate", text="Interpolate")
223             col.operator("gpencil.interpolate_sequence", text="Sequence")
224             settings = context.tool_settings.gpencil_sculpt
225             col.prop(settings, "interpolate_all_layers")
226             col.prop(settings, "interpolate_selected_only")
227
228         layout.separator()
229         col = layout.column(align=True)
230         col.operator("gpencil.stroke_subdivide", text="Subdivide")
231         col.operator("gpencil.stroke_join", text="Join").type = 'JOIN'
232         col.operator("gpencil.stroke_join", text="Join & Copy").type = 'JOINCOPY'
233         col.operator("gpencil.stroke_flip", text="Flip Direction")
234
235         gpd = context.gpencil_data
236         if gpd:
237             col.prop(gpd, "show_stroke_direction", text="Show Directions")
238
239         if is_3d_view:
240             layout.separator()
241             layout.operator("gpencil.reproject")
242
243
244 class GreasePencilBrushPanel:
245     # subclass must set
246     # bl_space_type = 'IMAGE_EDITOR'
247     bl_label = "Drawing Brushes"
248     bl_category = "Grease Pencil"
249     bl_region_type = 'TOOLS'
250
251     @staticmethod
252     def draw(self, context):
253         layout = self.layout
254
255         row = layout.row()
256         col = row.column()
257         ts = context.scene.tool_settings
258         if len(ts.gpencil_brushes) >= 2:
259             brows = 3
260         else:
261             brows = 2
262         col.template_list("GPENCIL_UL_brush", "", ts, "gpencil_brushes", ts.gpencil_brushes, "active_index", rows=brows)
263
264         col = row.column()
265
266         sub = col.column(align=True)
267         sub.operator("gpencil.brush_add", icon='ZOOMIN', text="")
268         sub.operator("gpencil.brush_remove", icon='ZOOMOUT', text="")
269         sub.menu("GPENCIL_MT_brush_specials", icon='DOWNARROW_HLT', text="")
270         brush = context.active_gpencil_brush
271         if brush:
272             if len(ts.gpencil_brushes) > 1:
273                 col.separator()
274                 sub = col.column(align=True)
275                 sub.operator("gpencil.brush_move", icon='TRIA_UP', text="").type = 'UP'
276                 sub.operator("gpencil.brush_move", icon='TRIA_DOWN', text="").type = 'DOWN'
277
278         # Brush details
279         if brush is not None:
280             row = layout.row()
281             row.prop(brush, "line_width")
282             row = layout.row(align=True)
283             row.prop(brush, "use_random_pressure", text='', icon='RNDCURVE')
284             row.prop(brush, "pen_sensitivity_factor", slider=True)
285             row.prop(brush, "use_pressure", text='', icon='STYLUS_PRESSURE')
286             row = layout.row(align=True)
287             row.prop(brush, "use_random_strength", text='', icon='RNDCURVE')
288             row.prop(brush, "strength", slider=True)
289             row.prop(brush, "use_strength_pressure", text='', icon='STYLUS_PRESSURE')
290             row = layout.row(align=True)
291             row.prop(brush, "random_press", slider=True)
292
293             row = layout.row(align=True)
294             row.prop(brush, "jitter", slider=True)
295             row.prop(brush, "use_jitter_pressure", text='', icon='STYLUS_PRESSURE')
296             row = layout.row()
297             row.prop(brush, "angle", slider=True)
298             row.prop(brush, "angle_factor", text="Factor", slider=True)
299
300             box = layout.box()
301             col = box.column(align=True)
302             col.label(text="Stroke Quality:")
303             col.prop(brush, "pen_smooth_factor")
304             col.prop(brush, "pen_smooth_steps")
305             col.separator()
306             row = col.row(align=False)
307             row.prop(brush, "pen_subdivision_steps")
308             row.prop(brush, "random_subdiv", text='Randomness', slider=True)
309
310
311 class GreasePencilStrokeSculptPanel:
312     # subclass must set
313     # bl_space_type = 'IMAGE_EDITOR'
314     bl_label = "Sculpt Strokes"
315     bl_category = "Grease Pencil"
316     bl_region_type = 'TOOLS'
317
318     @classmethod
319     def poll(cls, context):
320         if context.gpencil_data is None:
321             return False
322
323         gpd = context.gpencil_data
324         return bool(context.editable_gpencil_strokes) and bool(gpd.use_stroke_edit_mode)
325
326     @staticmethod
327     def draw(self, context):
328         layout = self.layout
329
330         settings = context.tool_settings.gpencil_sculpt
331         tool = settings.tool
332         brush = settings.brush
333
334         layout.column().prop(settings, "tool")
335
336         col = layout.column()
337         col.prop(brush, "size", slider=True)
338         row = col.row(align=True)
339         row.prop(brush, "strength", slider=True)
340         row.prop(brush, "use_pressure_strength", text="")
341         col.prop(brush, "use_falloff")
342         if tool in {'SMOOTH', 'RANDOMIZE'}:
343             row = layout.row(align=True)
344             row.prop(settings, "affect_position", text="Position", icon='MESH_DATA', toggle=True)
345             row.prop(settings, "affect_strength", text="Strength", icon='COLOR', toggle=True)
346             row.prop(settings, "affect_thickness", text="Thickness", icon='LINE_DATA', toggle=True)
347
348         layout.separator()
349
350         if tool == 'THICKNESS':
351             layout.row().prop(brush, "direction", expand=True)
352         elif tool == 'PINCH':
353             row = layout.row(align=True)
354             row.prop_enum(brush, "direction", 'ADD', text="Pinch")
355             row.prop_enum(brush, "direction", 'SUBTRACT', text="Inflate")
356         elif settings.tool == 'TWIST':
357             row = layout.row(align=True)
358             row.prop_enum(brush, "direction", 'SUBTRACT', text="CW")
359             row.prop_enum(brush, "direction", 'ADD', text="CCW")
360
361         row = layout.row(align=True)
362         row.prop(settings, "use_select_mask")
363         row = layout.row(align=True)
364         row.prop(settings, "selection_alpha", slider=True)
365
366         if tool == 'SMOOTH':
367             layout.prop(brush, "affect_pressure")
368
369
370 class GreasePencilBrushCurvesPanel:
371     # subclass must set
372     # bl_space_type = 'IMAGE_EDITOR'
373     bl_label = "Brush Curves"
374     bl_category = "Grease Pencil"
375     bl_region_type = 'TOOLS'
376     bl_options = {'DEFAULT_CLOSED'}
377
378     @classmethod
379     def poll(cls, context):
380         if context.active_gpencil_brush is None:
381             return False
382
383         brush = context.active_gpencil_brush
384         return bool(brush)
385
386     @staticmethod
387     def draw(self, context):
388         layout = self.layout
389         brush = context.active_gpencil_brush
390         # Brush
391         layout.label("Sensitivity")
392         box = layout.box()
393         box.template_curve_mapping(brush, "curve_sensitivity", brush=True)
394
395         layout.label("Strength")
396         box = layout.box()
397         box.template_curve_mapping(brush, "curve_strength", brush=True)
398
399         layout.label("Jitter")
400         box = layout.box()
401         box.template_curve_mapping(brush, "curve_jitter", brush=True)
402
403
404 ###############################
405
406 class GPENCIL_PIE_tool_palette(Menu):
407     """A pie menu for quick access to Grease Pencil tools"""
408     bl_label = "Grease Pencil Tools"
409
410     def draw(self, context):
411         layout = self.layout
412
413         pie = layout.menu_pie()
414         gpd = context.gpencil_data
415
416         # W - Drawing Types
417         col = pie.column()
418         col.operator("gpencil.draw", text="Draw", icon='GREASEPENCIL').mode = 'DRAW'
419         col.operator("gpencil.draw", text="Straight Lines", icon='LINE_DATA').mode = 'DRAW_STRAIGHT'
420         col.operator("gpencil.draw", text="Poly", icon='MESH_DATA').mode = 'DRAW_POLY'
421
422         # E - Eraser
423         # XXX: needs a dedicated icon...
424         col = pie.column()
425         col.operator("gpencil.draw", text="Eraser", icon='FORCE_CURVE').mode = 'ERASER'
426
427         # E - "Settings" Palette is included here too, since it needs to be in a stable position...
428         if gpd and gpd.layers.active:
429             col.separator()
430             col.operator("wm.call_menu_pie", text="Settings...", icon='SCRIPTWIN').name = "GPENCIL_PIE_settings_palette"
431
432         # Editing tools
433         if gpd:
434             if gpd.use_stroke_edit_mode and context.editable_gpencil_strokes:
435                 # S - Exit Edit Mode
436                 pie.operator("gpencil.editmode_toggle", text="Exit Edit Mode", icon='EDIT')
437
438                 # N - Transforms
439                 col = pie.column()
440                 row = col.row(align=True)
441                 row.operator("transform.translate", icon='MAN_TRANS')
442                 row.operator("transform.rotate", icon='MAN_ROT')
443                 row.operator("transform.resize", text="Scale", icon='MAN_SCALE')
444                 row = col.row(align=True)
445                 row.label("Proportional Edit:")
446                 row.prop(context.tool_settings, "proportional_edit", text="", icon_only=True)
447                 row.prop(context.tool_settings, "proportional_edit_falloff", text="", icon_only=True)
448
449                 # NW - Select (Non-Modal)
450                 col = pie.column()
451                 col.operator("gpencil.select_all", text="Select All", icon='PARTICLE_POINT')
452                 col.operator("gpencil.select_all", text="Select Inverse", icon='BLANK1')
453                 col.operator("gpencil.select_linked", text="Select Linked", icon='LINKED')
454                 col.operator("gpencil.palettecolor_select", text="Select Color", icon='COLOR')
455
456                 # NE - Select (Modal)
457                 col = pie.column()
458                 col.operator("gpencil.select_border", text="Border Select", icon='BORDER_RECT')
459                 col.operator("gpencil.select_circle", text="Circle Select", icon='META_EMPTY')
460                 col.operator("gpencil.select_lasso", text="Lasso Select", icon='BORDER_LASSO')
461
462                 # SW - Edit Tools
463                 col = pie.column()
464                 col.operator("gpencil.duplicate_move", icon='PARTICLE_PATH', text="Duplicate")
465                 col.operator("gpencil.delete", icon='X', text="Delete...")
466
467                 # SE - More Tools
468                 pie.operator("wm.call_menu_pie", text="More...").name = "GPENCIL_PIE_tools_more"
469             else:
470                 # Toggle Edit Mode
471                 pie.operator("gpencil.editmode_toggle", text="Enable Stroke Editing", icon='EDIT')
472
473
474 class GPENCIL_PIE_settings_palette(Menu):
475     """A pie menu for quick access to Grease Pencil settings"""
476     bl_label = "Grease Pencil Settings"
477
478     @classmethod
479     def poll(cls, context):
480         return bool(context.gpencil_data and context.active_gpencil_layer)
481
482     def draw(self, context):
483         layout = self.layout
484
485         pie = layout.menu_pie()
486         gpd = context.gpencil_data
487         gpl = context.active_gpencil_layer
488         palcolor = context.active_gpencil_palettecolor
489         brush = context.active_gpencil_brush
490
491         is_editmode = bool(gpd and gpd.use_stroke_edit_mode and context.editable_gpencil_strokes)
492
493         # W - Stroke draw settings
494         col = pie.column(align=True)
495         if palcolor is not None:
496             col.enabled = not palcolor.lock
497             col.label(text="Stroke")
498             col.prop(palcolor, "color", text="")
499             col.prop(palcolor, "alpha", text="", slider=True)
500
501         # E - Fill draw settings
502         col = pie.column(align=True)
503         if palcolor is not None:
504             col.enabled = not palcolor.lock
505             col.label(text="Fill")
506             col.prop(palcolor, "fill_color", text="")
507             col.prop(palcolor, "fill_alpha", text="", slider=True)
508
509         # S Brush settings
510         gpencil_active_brush_settings_simple(context, pie)
511
512         # N - Active Layer
513         col = pie.column()
514         col.label("Active Layer:      ")
515
516         row = col.row()
517         row.operator_context = 'EXEC_REGION_WIN'
518         row.operator_menu_enum("gpencil.layer_change", "layer", text="", icon='GREASEPENCIL')
519         row.prop(gpl, "info", text="")
520         row.operator("gpencil.layer_remove", text="", icon='X')
521
522         row = col.row()
523         row.prop(gpl, "lock")
524         row.prop(gpl, "hide")
525         col.prop(gpl, "use_onion_skinning")
526
527         # NW/NE/SW/SE - These operators are only available in editmode
528         # as they require strokes to be selected to work
529         if is_editmode:
530             # NW - Move stroke Down
531             col = pie.column(align=True)
532             col.label("Arrange Strokes")
533             col.operator("gpencil.stroke_arrange", text="Send to Back").direction = 'BOTTOM'
534             col.operator("gpencil.stroke_arrange", text="Send Backward").direction = 'DOWN'
535
536             # NE - Move stroke Up
537             col = pie.column(align=True)
538             col.label("Arrange Strokes")
539             col.operator("gpencil.stroke_arrange", text="Bring to Front").direction = 'TOP'
540             col.operator("gpencil.stroke_arrange", text="Bring Forward").direction = 'UP'
541
542             # SW - Move stroke to color
543             col = pie.column(align=True)
544             col.operator("gpencil.stroke_change_color", text="Move to Color")
545
546             # SE - Join strokes
547             col = pie.column(align=True)
548             col.label("Join Strokes")
549             row = col.row()
550             row.operator("gpencil.stroke_join", text="Join").type = 'JOIN'
551             row.operator("gpencil.stroke_join", text="Join & Copy").type = 'JOINCOPY'
552             col.operator("gpencil.stroke_flip", text="Flip direction")
553
554             col.prop(gpd, "show_stroke_direction", text="Show drawing direction")
555
556
557 class GPENCIL_PIE_tools_more(Menu):
558     """A pie menu for accessing more Grease Pencil tools"""
559     bl_label = "More Grease Pencil Tools"
560
561     @classmethod
562     def poll(cls, context):
563         gpd = context.gpencil_data
564         return bool(gpd and gpd.use_stroke_edit_mode and context.editable_gpencil_strokes)
565
566     def draw(self, context):
567         layout = self.layout
568
569         pie = layout.menu_pie()
570         # gpd = context.gpencil_data
571
572         col = pie.column(align=True)
573         col.operator("gpencil.copy", icon='COPYDOWN', text="Copy")
574         col.operator("gpencil.paste", icon='PASTEDOWN', text="Paste")
575
576         col = pie.column(align=True)
577         col.operator("gpencil.select_more", icon='ZOOMIN')
578         col.operator("gpencil.select_less", icon='ZOOMOUT')
579
580         pie.operator("transform.mirror", icon='MOD_MIRROR')
581         pie.operator("transform.bend", icon='MOD_SIMPLEDEFORM')
582         pie.operator("transform.shear", icon='MOD_TRIANGULATE')
583         pie.operator("transform.tosphere", icon='MOD_MULTIRES')
584
585         pie.operator("gpencil.convert", icon='OUTLINER_OB_CURVE', text="Convert...")
586         pie.operator("wm.call_menu_pie", text="Back to Main Palette...").name = "GPENCIL_PIE_tool_palette"
587
588
589 class GPENCIL_PIE_sculpt(Menu):
590     """A pie menu for accessing Grease Pencil stroke sculpting settings"""
591     bl_label = "Grease Pencil Sculpt"
592
593     @classmethod
594     def poll(cls, context):
595         gpd = context.gpencil_data
596         return bool(gpd and gpd.use_stroke_edit_mode and context.editable_gpencil_strokes)
597
598     def draw(self, context):
599         layout = self.layout
600
601         pie = layout.menu_pie()
602
603         settings = context.tool_settings.gpencil_sculpt
604         brush = settings.brush
605
606         # W - Launch Sculpt Mode
607         col = pie.column()
608         # col.label("Tool:")
609         col.prop(settings, "tool", text="")
610         col.operator("gpencil.brush_paint", text="Sculpt", icon='SCULPTMODE_HLT')
611
612         # E - Common Settings
613         col = pie.column(align=True)
614         col.prop(brush, "size", slider=True)
615         row = col.row(align=True)
616         row.prop(brush, "strength", slider=True)
617         # row.prop(brush, "use_pressure_strength", text="", icon_only=True)
618         col.prop(brush, "use_falloff")
619         if settings.tool in {'SMOOTH', 'RANDOMIZE'}:
620             row = col.row(align=True)
621             row.prop(settings, "affect_position", text="Position", icon='MESH_DATA', toggle=True)
622             row.prop(settings, "affect_strength", text="Strength", icon='COLOR', toggle=True)
623             row.prop(settings, "affect_thickness", text="Thickness", icon='LINE_DATA', toggle=True)
624
625         # S - Change Brush Type Shortcuts
626         row = pie.row()
627         row.prop_enum(settings, "tool", value='GRAB')
628         row.prop_enum(settings, "tool", value='PUSH')
629         row.prop_enum(settings, "tool", value='CLONE')
630
631         # N - Change Brush Type Shortcuts
632         row = pie.row()
633         row.prop_enum(settings, "tool", value='SMOOTH')
634         row.prop_enum(settings, "tool", value='THICKNESS')
635         row.prop_enum(settings, "tool", value='STRENGTH')
636         row.prop_enum(settings, "tool", value='RANDOMIZE')
637
638
639 ###############################
640
641
642 class GPENCIL_MT_snap(Menu):
643     bl_label = "Snap"
644
645     def draw(self, context):
646         layout = self.layout
647
648         layout.operator("gpencil.snap_to_grid", text="Selection to Grid")
649         layout.operator("gpencil.snap_to_cursor", text="Selection to Cursor").use_offset = False
650         layout.operator("gpencil.snap_to_cursor", text="Selection to Cursor (Offset)").use_offset = True
651
652         layout.separator()
653
654         layout.operator("gpencil.snap_cursor_to_selected", text="Cursor to Selected")
655         layout.operator("view3d.snap_cursor_to_center", text="Cursor to Center")
656         layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid")
657
658
659 class GPENCIL_MT_gpencil_edit_specials(Menu):
660     bl_label = "GPencil Specials"
661
662     def draw(self, context):
663         layout = self.layout
664         is_3d_view = context.space_data.type == 'VIEW_3D'
665
666         layout.operator_context = 'INVOKE_REGION_WIN'
667
668         layout.operator("gpencil.stroke_subdivide", text="Subdivide")
669
670         layout.separator()
671
672         layout.operator("gpencil.stroke_join", text="Join").type = 'JOIN'
673         layout.operator("gpencil.stroke_join", text="Join & Copy").type = 'JOINCOPY'
674         layout.operator("gpencil.stroke_flip", text="Flip Direction")
675
676         if is_3d_view:
677             layout.separator()
678             layout.operator("gpencil.reproject")
679
680
681 ###############################
682
683
684 class GPENCIL_UL_brush(UIList):
685     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
686         # assert(isinstance(item, bpy.types.GPencilBrush)
687         brush = item
688
689         if self.layout_type in {'DEFAULT', 'COMPACT'}:
690             row = layout.row(align=True)
691             row.prop(brush, "name", text="", emboss=False, icon='BRUSH_DATA')
692         elif self.layout_type == 'GRID':
693             layout.alignment = 'CENTER'
694             layout.label(text="", icon_value=icon)
695
696
697 class GPENCIL_UL_palettecolor(UIList):
698     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
699         # assert(isinstance(item, bpy.types.PaletteColor)
700         palcolor = item
701
702         if self.layout_type in {'DEFAULT', 'COMPACT'}:
703             if palcolor.lock:
704                 layout.active = False
705
706             split = layout.split(percentage=0.25)
707             row = split.row(align=True)
708             row.enabled = not palcolor.lock
709             row.prop(palcolor, "color", text="", emboss=palcolor.is_stroke_visible)
710             row.prop(palcolor, "fill_color", text="", emboss=palcolor.is_fill_visible)
711             split.prop(palcolor, "name", text="", emboss=False)
712
713             row = layout.row(align=True)
714             row.prop(palcolor, "lock", text="", emboss=False)
715             row.prop(palcolor, "hide", text="", emboss=False)
716             if palcolor.ghost is True:
717                 icon = 'GHOST_DISABLED'
718             else:
719                 icon = 'GHOST_ENABLED'
720             row.prop(palcolor, "ghost", text="", icon=icon, emboss=False)
721
722         elif self.layout_type == 'GRID':
723             layout.alignment = 'CENTER'
724             layout.label(text="", icon_value=icon)
725
726
727 class GPENCIL_UL_layer(UIList):
728     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
729         # assert(isinstance(item, bpy.types.GPencilLayer)
730         gpl = item
731
732         if self.layout_type in {'DEFAULT', 'COMPACT'}:
733             if gpl.lock:
734                 layout.active = False
735
736             row = layout.row(align=True)
737             if gpl.is_parented:
738                 icon = 'BONE_DATA'
739             else:
740                 icon = 'BLANK1'
741
742             row.label(text="", icon=icon)
743             row.prop(gpl, "info", text="", emboss=False)
744
745             row = layout.row(align=True)
746             row.prop(gpl, "lock", text="", emboss=False)
747             row.prop(gpl, "hide", text="", emboss=False)
748             row.prop(gpl, "unlock_color", text="", emboss=False)
749         elif self.layout_type == 'GRID':
750             layout.alignment = 'CENTER'
751             layout.label(text="", icon_value=icon)
752
753
754 class GPENCIL_MT_layer_specials(Menu):
755     bl_label = "Layer"
756
757     def draw(self, context):
758         layout = self.layout
759
760         layout.operator("gpencil.layer_duplicate", icon='COPY_ID')  # XXX: needs a dedicated icon
761
762         layout.separator()
763
764         layout.operator("gpencil.reveal", icon='RESTRICT_VIEW_OFF', text="Show All")
765         layout.operator("gpencil.hide", icon='RESTRICT_VIEW_ON', text="Hide Others").unselected = True
766
767         layout.separator()
768
769         layout.operator("gpencil.lock_all", icon='LOCKED', text="Lock All")
770         layout.operator("gpencil.unlock_all", icon='UNLOCKED', text="UnLock All")
771
772         layout.separator()
773
774         layout.operator("gpencil.layer_merge", icon='NLA', text="Merge Down")
775
776
777 class GPENCIL_MT_brush_specials(Menu):
778     bl_label = "Layer"
779
780     def draw(self, context):
781         layout = self.layout
782         layout.operator("gpencil.brush_copy", icon='PASTEDOWN', text="Copy current drawing brush")
783         layout.operator("gpencil.brush_presets_create", icon='HELP', text="Create a set of predefined brushes")
784
785
786 class GPENCIL_MT_palettecolor_specials(Menu):
787     bl_label = "Layer"
788
789     def draw(self, context):
790         layout = self.layout
791
792         layout.operator("gpencil.palettecolor_reveal", icon='RESTRICT_VIEW_OFF', text="Show All")
793         layout.operator("gpencil.palettecolor_hide", icon='RESTRICT_VIEW_ON', text="Hide Others").unselected = True
794
795         layout.separator()
796
797         layout.operator("gpencil.palettecolor_lock_all", icon='LOCKED', text="Lock All")
798         layout.operator("gpencil.palettecolor_unlock_all", icon='UNLOCKED', text="UnLock All")
799         layout.operator("gpencil.palettecolor_copy", icon='PASTEDOWN', text="Copy Color")
800
801         layout.separator()
802
803         layout.operator("gpencil.palettecolor_select", icon='COLOR', text="Select Strokes")
804         layout.operator("gpencil.stroke_change_color", icon='MAN_TRANS', text="Move to Color")
805
806
807 class GreasePencilDataPanel:
808     # subclass must set
809     # bl_space_type = 'IMAGE_EDITOR'
810     bl_label = "Grease Pencil Layers"
811     bl_region_type = 'UI'
812
813     @staticmethod
814     def draw_header(self, context):
815         self.layout.prop(context.space_data, "show_grease_pencil", text="")
816
817     @staticmethod
818     def draw(self, context):
819         layout = self.layout
820
821         # owner of Grease Pencil data
822         gpd_owner = context.gpencil_data_owner
823         gpd = context.gpencil_data
824
825         # Owner Selector
826         if context.space_data.type == 'VIEW_3D':
827             layout.prop(context.tool_settings, "grease_pencil_source", expand=True)
828         elif context.space_data.type == 'CLIP_EDITOR':
829             layout.prop(context.space_data, "grease_pencil_source", expand=True)
830
831         # Grease Pencil data selector
832         layout.template_ID(gpd_owner, "grease_pencil", new="gpencil.data_add", unlink="gpencil.data_unlink")
833
834         # Grease Pencil data...
835         if (gpd is None) or (not gpd.layers):
836             layout.operator("gpencil.layer_add", text="New Layer")
837         else:
838             self.draw_layers(context, layout, gpd)
839
840     def draw_layers(self, context, layout, gpd):
841         row = layout.row()
842
843         col = row.column()
844         if len(gpd.layers) >= 2:
845             layer_rows = 5
846         else:
847             layer_rows = 2
848         col.template_list("GPENCIL_UL_layer", "", gpd, "layers", gpd.layers, "active_index", rows=layer_rows)
849
850         col = row.column()
851
852         sub = col.column(align=True)
853         sub.operator("gpencil.layer_add", icon='ZOOMIN', text="")
854         sub.operator("gpencil.layer_remove", icon='ZOOMOUT', text="")
855
856         gpl = context.active_gpencil_layer
857         if gpl:
858             sub.menu("GPENCIL_MT_layer_specials", icon='DOWNARROW_HLT', text="")
859
860             if len(gpd.layers) > 1:
861                 col.separator()
862
863                 sub = col.column(align=True)
864                 sub.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP'
865                 sub.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN'
866
867                 col.separator()
868
869                 sub = col.column(align=True)
870                 sub.operator("gpencil.layer_isolate", icon='LOCKED', text="").affect_visibility = False
871                 sub.operator("gpencil.layer_isolate", icon='RESTRICT_VIEW_OFF', text="").affect_visibility = True
872
873         if gpl:
874             self.draw_layer(context, layout, gpl)
875
876     def draw_layer(self, context, layout, gpl):
877         row = layout.row(align=True)
878         row.prop(gpl, "opacity", text="Opacity", slider=True)
879
880         # Layer options
881         split = layout.split(percentage=0.5)
882         split.active = not gpl.lock
883         split.prop(gpl, "show_x_ray")
884         split.prop(gpl, "show_points")
885
886         # Offsets + Parenting (where available)
887         if context.space_data.type == 'VIEW_3D':
888             split = layout.split(percentage=0.5)
889         else:
890             split = layout.column()  # parenting is not available in 2D editors...
891         split.active = not gpl.lock
892
893         # Offsets - Color Tint
894         col = split.column()
895         subcol = col.column(align=True)
896         subcol.label("Tint")
897         subcol.enabled = not gpl.lock
898         subcol.prop(gpl, "tint_color", text="")
899         subcol.prop(gpl, "tint_factor", text="Factor", slider=True)
900
901         # Offsets - Thickness
902         row = col.row(align=True)
903         row.prop(gpl, "line_change", text="Thickness Change", slider=True)
904         row.operator("gpencil.stroke_apply_thickness", icon='STYLUS_PRESSURE', text="")
905
906         # Parenting 
907         if context.space_data.type == 'VIEW_3D':
908             col = split.column(align=True)
909             col.label(text="Parent:")
910             col.prop(gpl, "parent", text="")
911
912             sub = col.column()
913             sub.prop(gpl, "parent_type", text="")
914             parent = gpl.parent
915             if parent and gpl.parent_type == 'BONE' and parent.type == 'ARMATURE':
916                 sub.prop_search(gpl, "parent_bone", parent.data, "bones", text="")
917
918         layout.separator()
919
920         # Full-Row - Frame Locking (and Delete Frame)
921         row = layout.row(align=True)
922         row.active = not gpl.lock
923
924         if gpl.active_frame:
925             lock_status = iface_("Locked") if gpl.lock_frame else iface_("Unlocked")
926             lock_label = iface_("Frame: %d (%s)") % (gpl.active_frame.frame_number, lock_status)
927         else:
928             lock_label = iface_("Lock Frame")
929         row.prop(gpl, "lock_frame", text=lock_label, icon='UNLOCKED')
930         row.operator("gpencil.active_frame_delete", text="", icon='X')
931
932         layout.separator()
933
934         # Onion skinning
935         col = layout.column(align=True)
936         col.active = not gpl.lock
937
938         row = col.row()
939         row.prop(gpl, "use_onion_skinning")
940         row.prop(gpl, "use_ghost_custom_colors", text="", icon='COLOR')
941
942         split = col.split(percentage=0.5)
943         split.active = gpl.use_onion_skinning
944
945         # - Before Frames
946         sub = split.column(align=True)
947         row = sub.row(align=True)
948         row.active = gpl.use_ghost_custom_colors
949         row.prop(gpl, "before_color", text="")
950         sub.prop(gpl, "ghost_before_range", text="Before")
951
952         # - After Frames
953         sub = split.column(align=True)
954         row = sub.row(align=True)
955         row.active = gpl.use_ghost_custom_colors
956         row.prop(gpl, "after_color", text="")
957         sub.prop(gpl, "ghost_after_range", text="After")
958
959
960 class GreasePencilPaletteColorPanel:
961     # subclass must set
962     bl_label = "Grease Pencil Colors"
963     bl_region_type = 'UI'
964
965     @classmethod
966     def poll(cls, context):
967         if context.gpencil_data is None:
968             return False
969
970         gpd = context.gpencil_data
971         return bool(gpd.layers.active)
972
973     @staticmethod
974     def draw(self, context):
975         layout = self.layout
976         palette = context.active_gpencil_palette
977
978         if palette:
979             row = layout.row(align=True)
980             row.operator_context = 'EXEC_REGION_WIN'
981             row.operator_menu_enum("gpencil.palette_change", "palette", text="", icon='COLOR')
982             row.prop(palette, "name", text="")
983             row.operator("gpencil.palette_add", icon='ZOOMIN', text="")
984             row.operator("gpencil.palette_remove", icon='X', text="")
985
986             # Palette colors
987             row = layout.row()
988             col = row.column()
989             if len(palette.colors) >= 2:
990                 color_rows = 5
991             else:
992                 color_rows = 2
993             col.template_list("GPENCIL_UL_palettecolor", "", palette, "colors", palette.colors, "active_index",
994                               rows=color_rows)
995
996             col = row.column()
997
998             sub = col.column(align=True)
999             sub.operator("gpencil.palettecolor_add", icon='ZOOMIN', text="")
1000             sub.operator("gpencil.palettecolor_remove", icon='ZOOMOUT', text="")
1001
1002             palcol = context.active_gpencil_palettecolor
1003             if palcol:
1004                 sub.menu("GPENCIL_MT_palettecolor_specials", icon='DOWNARROW_HLT', text="")
1005
1006             if len(palette.colors) > 1:
1007                 col.separator()
1008
1009                 sub = col.column(align=True)
1010                 sub.operator("gpencil.palettecolor_move", icon='TRIA_UP', text="").direction = 'UP'
1011                 sub.operator("gpencil.palettecolor_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
1012
1013                 row = layout.row()
1014                 sub = row.row(align=True)
1015                 sub.label(text="Isolate:") # based on active color only
1016                 sub.operator("gpencil.palettecolor_isolate", icon='LOCKED', text="").affect_visibility = False
1017                 sub.operator("gpencil.palettecolor_isolate", icon='RESTRICT_VIEW_OFF', text="").affect_visibility = True
1018                 sub = row.row(align=True)
1019                 sub.label(text="Lock:") # based on other stuff...
1020                 sub.operator("gpencil.stroke_lock_color", icon='BORDER_RECT', text="")
1021                 sub.operator("gpencil.palette_lock_layer", icon='COLOR', text="")
1022
1023             pcolor = palette.colors.active
1024             if pcolor:
1025                 self.draw_palettecolors(layout, pcolor)
1026
1027     # Draw palette colors
1028     def draw_palettecolors(self, layout, pcolor):
1029         # color settings
1030         split = layout.split(percentage=0.5)
1031         split.active = not pcolor.lock
1032
1033         # Column 1 - Stroke
1034         col = split.column(align=True)
1035         col.enabled = not pcolor.lock
1036         col.label(text="Stroke:")
1037         col.prop(pcolor, "color", text="")
1038         col.prop(pcolor, "alpha", slider=True)
1039
1040         # Column 2 - Fill
1041         col = split.column(align=True)
1042         col.enabled = not pcolor.lock
1043         col.label(text="Fill:")
1044         col.prop(pcolor, "fill_color", text="")
1045         col.prop(pcolor, "fill_alpha", text="Opacity", slider=True)
1046
1047         # Options
1048         split = layout.split(percentage=0.5)
1049         split.active = not pcolor.lock
1050
1051         col = split.column(align=True)
1052         col.active = not pcolor.lock
1053         col.prop(pcolor, "use_volumetric_strokes")
1054         col = split.column(align=True)
1055         col.active = not pcolor.lock
1056         col.prop(pcolor, "use_hq_fill")
1057
1058
1059 class GreasePencilToolsPanel:
1060     # For use in "2D" Editors without their own toolbar
1061     # subclass must set
1062     # bl_space_type = 'IMAGE_EDITOR'
1063     # bl_options = {'DEFAULT_CLOSED'}
1064     bl_label = "Grease Pencil Settings"
1065     bl_region_type = 'UI'
1066
1067     @classmethod
1068     def poll(cls, context):
1069         return (context.gpencil_data is not None)
1070
1071     @staticmethod
1072     def draw(self, context):
1073         layout = self.layout
1074
1075         # gpd_owner = context.gpencil_data_owner
1076         gpd = context.gpencil_data
1077
1078         layout.prop(gpd, "use_stroke_edit_mode", text="Enable Editing", icon='EDIT', toggle=True)
1079
1080         layout.separator()
1081
1082         layout.label("Proportional Edit:")
1083         row = layout.row()
1084         row.prop(context.tool_settings, "proportional_edit", text="")
1085         row.prop(context.tool_settings, "proportional_edit_falloff", text="")
1086
1087         layout.separator()
1088         layout.separator()
1089
1090         gpencil_active_brush_settings_simple(context, layout)
1091
1092         layout.separator()
1093
1094         gpencil_stroke_placement_settings(context, layout)