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