UI: Make UV Editor contextual menu more consistent with 3D View
[blender.git] / release / scripts / startup / bl_ui / space_image.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 from bpy.types import (
22     Header,
23     Menu,
24     Panel,
25     UIList,
26 )
27 from .properties_paint_common import (
28     UnifiedPaintPanel,
29     brush_texture_settings,
30     brush_texpaint_common,
31     brush_mask_texture_settings,
32 )
33 from .properties_grease_pencil_common import (
34     AnnotationDataPanel,
35 )
36 from bpy.app.translations import pgettext_iface as iface_
37
38
39 class ImagePaintPanel(UnifiedPaintPanel):
40     bl_space_type = 'PROPERTIES'
41     bl_region_type = 'WINDOW'
42
43
44 class BrushButtonsPanel(UnifiedPaintPanel):
45     bl_space_type = 'PROPERTIES'
46     bl_region_type = 'WINDOW'
47
48     @classmethod
49     def poll(cls, context):
50         tool_settings = context.tool_settings.image_paint
51         return tool_settings.brush
52
53
54 class IMAGE_MT_view(Menu):
55     bl_label = "View"
56
57     def draw(self, context):
58         layout = self.layout
59
60         sima = context.space_data
61         uv = sima.uv_editor
62         tool_settings = context.tool_settings
63         paint = tool_settings.image_paint
64
65         show_uvedit = sima.show_uvedit
66         show_render = sima.show_render
67
68         layout.operator("image.properties", icon='MENU_PANEL')
69         layout.operator("image.toolshelf", icon='MENU_PANEL')
70
71         layout.separator()
72
73         layout.prop(sima, "use_realtime_update")
74         if show_uvedit:
75             layout.prop(tool_settings, "show_uv_local_view")
76
77         layout.prop(uv, "show_metadata")
78
79         if paint.brush and (context.image_paint_object or sima.mode == 'PAINT'):
80             layout.prop(uv, "show_texpaint")
81             layout.prop(tool_settings, "show_uv_local_view", text="Show Same Material")
82
83         layout.separator()
84
85         layout.operator("image.view_zoom_in")
86         layout.operator("image.view_zoom_out")
87
88         layout.separator()
89
90         layout.menu("IMAGE_MT_view_zoom")
91
92         layout.separator()
93
94         if show_uvedit:
95             layout.operator("image.view_selected")
96
97         layout.operator("image.view_all")
98         layout.operator("image.view_all", text="View Fit").fit_view = True
99
100         layout.separator()
101
102         if show_render:
103             layout.operator("image.render_border")
104             layout.operator("image.clear_render_border")
105
106             layout.separator()
107
108             layout.operator("image.cycle_render_slot", text="Render Slot Cycle Next")
109             layout.operator("image.cycle_render_slot", text="Render Slot Cycle Previous").reverse = True
110             layout.separator()
111
112         layout.menu("INFO_MT_area")
113
114
115 class IMAGE_MT_view_zoom(Menu):
116     bl_label = "Fractional Zoom"
117
118     def draw(self, context):
119         layout = self.layout
120
121         ratios = ((1, 8), (1, 4), (1, 2), (1, 1), (2, 1), (4, 1), (8, 1))
122
123         for i, (a, b) in enumerate(ratios):
124             if i in {3, 4}:  # Draw separators around Zoom 1:1.
125                 layout.separator()
126
127             layout.operator(
128                 "image.view_zoom_ratio",
129                 text=iface_(f"Zoom {a:d}:{b:d}"),
130                 translate=False,
131             ).ratio = a / b
132
133
134 class IMAGE_MT_select(Menu):
135     bl_label = "Select"
136
137     def draw(self, context):
138         layout = self.layout
139
140         layout.operator("uv.select_all", text="All").action = 'SELECT'
141         layout.operator("uv.select_all", text="None").action = 'DESELECT'
142         layout.operator("uv.select_all", text="Invert").action = 'INVERT'
143
144         layout.separator()
145
146         layout.operator("uv.select_box").pinned = False
147         layout.operator("uv.select_box", text="Box Select Pinned").pinned = True
148         layout.operator("uv.select_circle")
149
150         layout.separator()
151
152         layout.operator("uv.select_less", text="Less")
153         layout.operator("uv.select_more", text="More")
154
155         layout.separator()
156
157         layout.operator("uv.select_pinned")
158         layout.operator("uv.select_linked").extend = False
159
160         layout.separator()
161
162         layout.operator("uv.select_split")
163
164
165 class IMAGE_MT_brush(Menu):
166     bl_label = "Brush"
167
168     def draw(self, context):
169         layout = self.layout
170         tool_settings = context.tool_settings
171         settings = tool_settings.image_paint
172         brush = settings.brush
173
174         ups = context.tool_settings.unified_paint_settings
175         layout.prop(ups, "use_unified_size", text="Unified Size")
176         layout.prop(ups, "use_unified_strength", text="Unified Strength")
177         layout.prop(ups, "use_unified_color", text="Unified Color")
178         layout.separator()
179
180         # Brush tool.
181         layout.prop_menu_enum(brush, "image_tool")
182
183
184 class IMAGE_MT_image(Menu):
185     bl_label = "Image"
186
187     def draw(self, context):
188         layout = self.layout
189
190         sima = context.space_data
191         ima = sima.image
192         show_render = sima.show_render
193
194         layout.operator("image.new", text="New")
195         layout.operator("image.open", text="Open...", icon='FILE_FOLDER')
196
197         layout.operator("image.read_viewlayers")
198
199         if ima:
200             layout.separator()
201
202             if not show_render:
203                 layout.operator("image.replace", text="Replace...")
204                 layout.operator("image.reload", text="Reload")
205
206             layout.operator("image.external_edit", text="Edit Externally")
207
208         layout.separator()
209
210         if ima:
211             layout.operator("image.save", text="Save", icon='FILE_TICK')
212             layout.operator("image.save_as", text="Save As...")
213             layout.operator("image.save_as", text="Save a Copy...").copy = True
214
215         if ima and ima.source == 'SEQUENCE':
216             layout.operator("image.save_sequence")
217
218         layout.operator("image.save_dirty", text="Save All Images")
219
220         if ima:
221             layout.separator()
222
223             layout.menu("IMAGE_MT_image_invert")
224
225             if not show_render:
226                 if not ima.packed_file:
227                     layout.separator()
228                     layout.operator("image.pack", text="Pack")
229
230                 # Only for dirty && specific image types, perhaps
231                 # this could be done in operator poll too.
232                 if ima.is_dirty:
233                     if ima.source in {'FILE', 'GENERATED'} and ima.type != 'OPEN_EXR_MULTILAYER':
234                         if ima.packed_file:
235                             layout.separator()
236                         layout.operator("image.pack", text="Pack As PNG").as_png = True
237
238
239 class IMAGE_MT_image_invert(Menu):
240     bl_label = "Invert"
241
242     def draw(self, context):
243         layout = self.layout
244
245         props = layout.operator("image.invert", text="Invert Image Colors", icon='IMAGE_RGB')
246         props.invert_r = True
247         props.invert_g = True
248         props.invert_b = True
249
250         layout.separator()
251
252         layout.operator("image.invert", text="Invert Red Channel", icon='COLOR_RED').invert_r = True
253         layout.operator("image.invert", text="Invert Green Channel", icon='COLOR_GREEN').invert_g = True
254         layout.operator("image.invert", text="Invert Blue Channel", icon='COLOR_BLUE').invert_b = True
255         layout.operator("image.invert", text="Invert Alpha Channel", icon='IMAGE_ALPHA').invert_a = True
256
257
258 class IMAGE_MT_uvs_showhide(Menu):
259     bl_label = "Show/Hide Faces"
260
261     def draw(self, context):
262         layout = self.layout
263
264         layout.operator("uv.reveal")
265         layout.operator("uv.hide", text="Hide Selected").unselected = False
266         layout.operator("uv.hide", text="Hide Unselected").unselected = True
267
268
269 class IMAGE_MT_uvs_proportional(Menu):
270     bl_label = "Proportional Editing"
271
272     def draw(self, context):
273         layout = self.layout
274
275         layout.props_enum(context.tool_settings, "proportional_edit")
276
277         layout.separator()
278
279         layout.label(text="Falloff:")
280         layout.props_enum(context.tool_settings, "proportional_edit_falloff")
281
282
283 class IMAGE_MT_uvs_transform(Menu):
284     bl_label = "Transform"
285
286     def draw(self, context):
287         layout = self.layout
288
289         layout.operator("transform.translate")
290         layout.operator("transform.rotate")
291         layout.operator("transform.resize")
292
293         layout.separator()
294
295         layout.operator("transform.shear")
296
297
298 class IMAGE_MT_uvs_snap(Menu):
299     bl_label = "Snap"
300
301     def draw(self, context):
302         layout = self.layout
303
304         layout.operator_context = 'EXEC_REGION_WIN'
305
306         layout.operator("uv.snap_selected", text="Selected to Pixels").target = 'PIXELS'
307         layout.operator("uv.snap_selected", text="Selected to Cursor").target = 'CURSOR'
308         layout.operator("uv.snap_selected", text="Selected to Cursor (Offset)").target = 'CURSOR_OFFSET'
309         layout.operator("uv.snap_selected", text="Selected to Adjacent Unselected").target = 'ADJACENT_UNSELECTED'
310
311         layout.separator()
312
313         layout.operator("uv.snap_cursor", text="Cursor to Pixels").target = 'PIXELS'
314         layout.operator("uv.snap_cursor", text="Cursor to Selected").target = 'SELECTED'
315
316
317 class IMAGE_MT_uvs_mirror(Menu):
318     bl_label = "Mirror"
319
320     def draw(self, context):
321         layout = self.layout
322
323         layout.operator("mesh.faces_mirror_uv")
324
325         layout.separator()
326
327         layout.operator_context = 'EXEC_REGION_WIN'
328
329         layout.operator("transform.mirror", text="X Axis").constraint_axis[0] = True
330         layout.operator("transform.mirror", text="Y Axis").constraint_axis[1] = True
331
332
333 class IMAGE_MT_uvs_weldalign(Menu):
334     bl_label = "Weld/Align"
335
336     def draw(self, context):
337         layout = self.layout
338
339         layout.operator("uv.weld")  # W, 1.
340         layout.operator("uv.remove_doubles")
341         layout.operator_enum("uv.align", "axis")  # W, 2/3/4.
342
343
344 class IMAGE_MT_uvs(Menu):
345     bl_label = "UV"
346
347     def draw(self, context):
348         layout = self.layout
349
350         sima = context.space_data
351         uv = sima.uv_editor
352         tool_settings = context.tool_settings
353
354         layout.menu("IMAGE_MT_uvs_transform")
355         layout.menu("IMAGE_MT_uvs_mirror")
356         layout.menu("IMAGE_MT_uvs_snap")
357
358         layout.prop_menu_enum(uv, "pixel_snap_mode")
359         layout.prop(uv, "lock_bounds")
360
361         layout.separator()
362
363         layout.prop(tool_settings, "use_uv_sculpt")
364
365         layout.separator()
366
367         layout.prop(uv, "use_live_unwrap")
368         layout.operator("uv.unwrap")
369
370         layout.separator()
371
372         layout.operator("uv.pin").clear = False
373         layout.operator("uv.pin", text="Unpin").clear = True
374
375         layout.separator()
376
377         layout.operator("uv.mark_seam").clear = False
378         layout.operator("uv.mark_seam", text="Clear Seam").clear = True
379         layout.operator("uv.seams_from_islands")
380
381         layout.separator()
382
383         layout.operator("uv.pack_islands")
384         layout.operator("uv.average_islands_scale")
385
386         layout.separator()
387
388         layout.operator("uv.minimize_stretch")
389         layout.operator("uv.stitch")
390         layout.menu("IMAGE_MT_uvs_weldalign")
391
392         layout.separator()
393
394         layout.menu("IMAGE_MT_uvs_showhide")
395
396         layout.separator()
397
398
399 class IMAGE_MT_uvs_select_mode(Menu):
400     bl_label = "UV Select Mode"
401
402     def draw(self, context):
403         layout = self.layout
404
405         layout.operator_context = 'INVOKE_REGION_WIN'
406         tool_settings = context.tool_settings
407
408         # Do smart things depending on whether uv_select_sync is on.
409
410         if tool_settings.use_uv_select_sync:
411             props = layout.operator("wm.context_set_value", text="Vertex", icon='VERTEXSEL')
412             props.value = "(True, False, False)"
413             props.data_path = "tool_settings.mesh_select_mode"
414
415             props = layout.operator("wm.context_set_value", text="Edge", icon='EDGESEL')
416             props.value = "(False, True, False)"
417             props.data_path = "tool_settings.mesh_select_mode"
418
419             props = layout.operator("wm.context_set_value", text="Face", icon='FACESEL')
420             props.value = "(False, False, True)"
421             props.data_path = "tool_settings.mesh_select_mode"
422
423         else:
424             props = layout.operator("wm.context_set_string", text="Vertex", icon='UV_VERTEXSEL')
425             props.value = 'VERTEX'
426             props.data_path = "tool_settings.uv_select_mode"
427
428             props = layout.operator("wm.context_set_string", text="Edge", icon='UV_EDGESEL')
429             props.value = 'EDGE'
430             props.data_path = "tool_settings.uv_select_mode"
431
432             props = layout.operator("wm.context_set_string", text="Face", icon='UV_FACESEL')
433             props.value = 'FACE'
434             props.data_path = "tool_settings.uv_select_mode"
435
436             props = layout.operator("wm.context_set_string", text="Island", icon='UV_ISLANDSEL')
437             props.value = 'ISLAND'
438             props.data_path = "tool_settings.uv_select_mode"
439
440
441 class IMAGE_MT_uvs_context_menu(Menu):
442     bl_label = "UV Context Menu"
443
444     def draw(self, context):
445         layout = self.layout
446
447         sima = context.space_data
448
449         # UV Edit Mode
450         if sima.show_uvedit:
451             # Add
452             layout.operator("uv.unwrap")
453             layout.operator("uv.follow_active_quads")
454
455             layout.separator()
456
457             # Modify
458             layout.operator("uv.pin").clear = False
459             layout.operator("uv.pin", text="Unpin").clear = True
460
461             layout.separator()
462
463             layout.menu("IMAGE_MT_uvs_snap")
464
465             layout.operator("transform.mirror", text="Mirror X").constraint_axis[0] = True
466             layout.operator("transform.mirror", text="Mirror Y").constraint_axis[1] = True
467
468             layout.separator()
469
470             layout.operator_enum("uv.align", "axis")  # W, 2/3/4.
471
472             layout.separator()
473
474             # Remove
475             layout.operator("uv.remove_doubles", text="Remove Double UVs")
476             layout.operator("uv.stitch")
477             layout.operator("uv.weld")
478
479
480 class IMAGE_MT_pivot_pie(Menu):
481     bl_label = "Pivot Point"
482
483     def draw(self, context):
484         layout = self.layout
485         pie = layout.menu_pie()
486
487         pie.prop_enum(context.space_data, "pivot_point", value='CENTER')
488         pie.prop_enum(context.space_data, "pivot_point", value='CURSOR')
489         pie.prop_enum(context.space_data, "pivot_point", value='INDIVIDUAL_ORIGINS')
490         pie.prop_enum(context.space_data, "pivot_point", value='MEDIAN')
491
492
493 class IMAGE_MT_uvs_snap_pie(Menu):
494     bl_label = "Snap"
495
496     def draw(self, context):
497         layout = self.layout
498         pie = layout.menu_pie()
499
500         layout.operator_context = 'EXEC_REGION_WIN'
501
502         pie.operator("uv.snap_selected", text="Selected to Pixels", icon='RESTRICT_SELECT_OFF').target = 'PIXELS'
503         pie.operator("uv.snap_cursor", text="Cursor to Pixels", icon='PIVOT_CURSOR').target = 'PIXELS'
504         pie.operator("uv.snap_cursor", text="Cursor to Selected", icon='PIVOT_CURSOR').target = 'SELECTED'
505         pie.operator("uv.snap_selected", text="Selected to Cursor", icon='RESTRICT_SELECT_OFF').target = 'CURSOR'
506         pie.operator("uv.snap_selected", text="Selected to Cursor (Offset)", icon='RESTRICT_SELECT_OFF').target = 'CURSOR_OFFSET'
507         pie.operator("uv.snap_selected", text="Selected to Adjacent Unselected", icon='RESTRICT_SELECT_OFF').target = 'ADJACENT_UNSELECTED'
508
509
510 class IMAGE_HT_header(Header):
511     bl_space_type = 'IMAGE_EDITOR'
512
513     def draw(self, context):
514         layout = self.layout
515
516         sima = context.space_data
517         ima = sima.image
518         iuser = sima.image_user
519         tool_settings = context.tool_settings
520
521         show_render = sima.show_render
522         show_uvedit = sima.show_uvedit
523         show_maskedit = sima.show_maskedit
524
525         row = layout.row(align=True)
526         row.template_header()
527
528         if sima.mode != 'UV':
529             layout.prop(sima, "ui_mode", text="")
530
531         # UV editing.
532         if show_uvedit:
533             uvedit = sima.uv_editor
534
535             layout.prop(tool_settings, "use_uv_select_sync", text="")
536
537             if tool_settings.use_uv_select_sync:
538                 layout.template_edit_mode_selection()
539             else:
540                 layout.prop(tool_settings, "uv_select_mode", text="", expand=True)
541                 layout.prop(uvedit, "sticky_select_mode", icon_only=True)
542
543         MASK_MT_editor_menus.draw_collapsible(context, layout)
544
545         layout.separator_spacer()
546
547         layout.template_ID(sima, "image", new="image.new", open="image.open")
548
549         if show_maskedit:
550             row = layout.row()
551             row.template_ID(sima, "mask", new="mask.new")
552
553         if not show_render:
554             layout.prop(sima, "use_image_pin", text="")
555
556         layout.separator_spacer()
557
558         if show_uvedit:
559             uvedit = sima.uv_editor
560
561             mesh = context.edit_object.data
562             layout.prop_search(mesh.uv_layers, "active", mesh, "uv_layers", text="")
563
564         if show_uvedit or show_maskedit:
565             layout.prop(sima, "pivot_point", icon_only=True)
566
567         if show_uvedit:
568             # Snap.
569             row = layout.row(align=True)
570             row.prop(tool_settings, "use_snap", text="")
571             row.prop(tool_settings, "snap_uv_element", icon_only=True)
572             if tool_settings.snap_uv_element != 'INCREMENT':
573                 row.prop(tool_settings, "snap_target", text="")
574
575             # Proportional Editing
576             row = layout.row(align=True)
577             row.prop(tool_settings, "proportional_edit", icon_only=True)
578             # if tool_settings.proportional_edit != 'DISABLED':
579             sub = row.row(align=True)
580             sub.active = tool_settings.proportional_edit != 'DISABLED'
581             sub.prop(tool_settings, "proportional_edit_falloff", icon_only=True)
582
583         row = layout.row()
584         row.popover(
585             panel="IMAGE_PT_view_display",
586             text="Display"
587         )
588
589         if ima:
590             if ima.is_stereo_3d:
591                 row = layout.row()
592                 row.prop(sima, "show_stereo_3d", text="")
593
594             # layers.
595             layout.template_image_layers(ima, iuser)
596
597             # draw options.
598             row = layout.row()
599             row.prop(sima, "display_channels", icon_only=True)
600
601             row = layout.row(align=True)
602             if ima.type == 'COMPOSITE':
603                 row.operator("image.record_composite", icon='REC')
604             if ima.type == 'COMPOSITE' and ima.source in {'MOVIE', 'SEQUENCE'}:
605                 row.operator("image.play_composite", icon='PLAY')
606
607
608 class MASK_MT_editor_menus(Menu):
609     bl_idname = "MASK_MT_editor_menus"
610     bl_label = ""
611
612     def draw(self, context):
613         layout = self.layout
614         sima = context.space_data
615         ima = sima.image
616
617         show_uvedit = sima.show_uvedit
618         show_maskedit = sima.show_maskedit
619         show_paint = sima.show_paint
620
621         layout.menu("IMAGE_MT_view")
622
623         if show_uvedit:
624             layout.menu("IMAGE_MT_select")
625         if show_maskedit:
626             layout.menu("MASK_MT_select")
627         if show_paint:
628             layout.menu("IMAGE_MT_brush")
629
630         if ima and ima.is_dirty:
631             layout.menu("IMAGE_MT_image", text="Image*")
632         else:
633             layout.menu("IMAGE_MT_image", text="Image")
634
635         if show_uvedit:
636             layout.menu("IMAGE_MT_uvs")
637         if show_maskedit:
638             layout.menu("MASK_MT_add")
639             layout.menu("MASK_MT_mask")
640
641
642 # -----------------------------------------------------------------------------
643 # Mask (similar code in space_clip.py, keep in sync)
644 # note! - panel placement does _not_ fit well with image panels... need to fix.
645
646 from .properties_mask_common import (
647     MASK_PT_mask,
648     MASK_PT_layers,
649     MASK_PT_spline,
650     MASK_PT_point,
651     MASK_PT_display,
652 )
653
654
655 class IMAGE_PT_mask(MASK_PT_mask, Panel):
656     bl_space_type = 'IMAGE_EDITOR'
657     bl_region_type = 'UI'
658     bl_category = "Image"
659
660
661 class IMAGE_PT_mask_layers(MASK_PT_layers, Panel):
662     bl_space_type = 'IMAGE_EDITOR'
663     bl_region_type = 'UI'
664     bl_category = "Image"
665
666
667 class IMAGE_PT_mask_display(MASK_PT_display, Panel):
668     bl_space_type = 'IMAGE_EDITOR'
669     bl_region_type = 'UI'
670     bl_category = "Image"
671
672
673 class IMAGE_PT_active_mask_spline(MASK_PT_spline, Panel):
674     bl_space_type = 'IMAGE_EDITOR'
675     bl_region_type = 'UI'
676     bl_category = "Image"
677
678
679 class IMAGE_PT_active_mask_point(MASK_PT_point, Panel):
680     bl_space_type = 'IMAGE_EDITOR'
681     bl_region_type = 'UI'
682     bl_category = "Image"
683
684
685 # --- end mask ---
686
687
688 class IMAGE_PT_image_properties(Panel):
689     bl_space_type = 'IMAGE_EDITOR'
690     bl_region_type = 'UI'
691     bl_category = "Image"
692     bl_label = "Image"
693
694     @classmethod
695     def poll(cls, context):
696         sima = context.space_data
697         return (sima.image)
698
699     def draw(self, context):
700         layout = self.layout
701
702         sima = context.space_data
703         iuser = sima.image_user
704
705         layout.template_image(sima, "image", iuser, multiview=True)
706
707
708 class IMAGE_PT_view_display(Panel):
709     bl_space_type = 'IMAGE_EDITOR'
710     bl_region_type = 'HEADER'
711     bl_label = "Display"
712
713     @classmethod
714     def poll(cls, context):
715         sima = context.space_data
716         return (sima and (sima.image or sima.show_uvedit))
717
718     def draw(self, context):
719         layout = self.layout
720         layout.use_property_split = True
721
722         sima = context.space_data
723         ima = sima.image
724
725         show_uvedit = sima.show_uvedit
726         uvedit = sima.uv_editor
727
728         col = layout.column()
729
730         if ima:
731             col.prop(ima, "display_aspect", text="Aspect Ratio")
732             col.prop(sima, "show_repeat", text="Repeat Image")
733
734         if show_uvedit:
735             col.prop(uvedit, "show_pixel_coords", text="Pixel Coordinates")
736
737
738 class IMAGE_PT_view_display_uv_edit_overlays(Panel):
739     bl_space_type = 'IMAGE_EDITOR'
740     bl_region_type = 'HEADER'
741     bl_label = "Overlays"
742     bl_parent_id = 'IMAGE_PT_view_display'
743     bl_options = {'DEFAULT_CLOSED'}
744
745     @classmethod
746     def poll(cls, context):
747         sima = context.space_data
748         return (sima and (sima.show_uvedit))
749
750     def draw(self, context):
751         layout = self.layout
752         layout.use_property_split = True
753
754         sima = context.space_data
755         uvedit = sima.uv_editor
756
757         col = layout.column()
758
759         split = col.split(factor=0.6)
760         split.prop(uvedit, "show_edges", text="Edges")
761         split.prop(uvedit, "edge_display_type", text="")
762
763         col.prop(uvedit, "show_faces", text="Faces")
764
765         col = layout.column()
766         col.prop(uvedit, "show_smooth_edges", text="Smooth")
767         col.prop(uvedit, "show_modified_edges", text="Modified")
768
769
770 class IMAGE_PT_view_display_uv_edit_overlays_advanced(Panel):
771     bl_space_type = 'IMAGE_EDITOR'
772     bl_region_type = 'HEADER'
773     bl_label = "Advanced"
774     bl_parent_id = 'IMAGE_PT_view_display_uv_edit_overlays'
775     bl_options = {'DEFAULT_CLOSED'}
776
777     @classmethod
778     def poll(cls, context):
779         sima = context.space_data
780         return (sima and (sima.show_uvedit))
781
782     def draw(self, context):
783         layout = self.layout
784         layout.use_property_split = True
785
786         sima = context.space_data
787         uvedit = sima.uv_editor
788
789         col = layout.column()
790         col.prop(uvedit, "show_stretch", text="Stretch")
791
792         sub = col.column()
793         sub.active = uvedit.show_stretch
794         sub.prop(uvedit, "display_stretch_type", text="Type")
795
796
797 class IMAGE_UL_render_slots(UIList):
798     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
799         slot = item
800         layout.prop(slot, "name", text="", emboss=False)
801
802
803 class IMAGE_PT_render_slots(Panel):
804     bl_space_type = 'IMAGE_EDITOR'
805     bl_region_type = 'UI'
806     bl_category = "Image"
807     bl_label = "Render Slots"
808
809     @classmethod
810     def poll(cls, context):
811         sima = context.space_data
812         return (sima and sima.image and sima.show_render)
813
814     def draw(self, context):
815         layout = self.layout
816
817         sima = context.space_data
818         ima = sima.image
819
820         row = layout.row()
821
822         col = row.column()
823         col.template_list(
824             "IMAGE_UL_render_slots", "render_slots", ima,
825             "render_slots", ima.render_slots, "active_index", rows=3
826         )
827
828         col = row.column(align=True)
829         col.operator("image.add_render_slot", icon='ADD', text="")
830         col.operator("image.remove_render_slot", icon='REMOVE', text="")
831
832         col.separator()
833
834         col.operator("image.clear_render_slot", icon='X', text="")
835
836
837 class IMAGE_PT_paint(Panel, ImagePaintPanel):
838     bl_label = "Brush"
839     bl_context = ".paint_common_2d"
840     bl_category = "Tools"
841
842     def draw(self, context):
843         layout = self.layout
844
845         settings = context.tool_settings.image_paint
846         brush = settings.brush
847
848         col = layout.column()
849         col.template_ID_preview(settings, "brush", new="brush.add", rows=2, cols=6)
850
851         if brush:
852             brush_texpaint_common(self, context, layout, brush, settings)
853
854
855 class IMAGE_PT_tools_brush_display(BrushButtonsPanel, Panel):
856     bl_label = "Display"
857     bl_context = ".paint_common_2d"
858     bl_options = {'DEFAULT_CLOSED'}
859     bl_category = "Options"
860
861     def draw(self, context):
862         layout = self.layout
863
864         tool_settings = context.tool_settings.image_paint
865         brush = tool_settings.brush
866         tex_slot = brush.texture_slot
867         tex_slot_mask = brush.mask_texture_slot
868
869         col = layout.column()
870
871         col.label(text="Curve:")
872
873         row = col.row(align=True)
874         row.prop(
875             brush,
876             "use_cursor_overlay",
877             text="",
878             toggle=True,
879             icon='RESTRICT_VIEW_ON' if brush.use_cursor_overlay else 'RESTRICT_VIEW_OFF',
880         )
881
882         sub = row.row(align=True)
883         sub.prop(brush, "cursor_overlay_alpha", text="Alpha")
884         sub.prop(brush, "use_cursor_overlay_override", toggle=True, text="", icon='BRUSH_DATA')
885
886         col.active = brush.brush_capabilities.has_overlay
887         col.label(text="Texture:")
888         row = col.row(align=True)
889         if tex_slot.map_mode != 'STENCIL':
890             row.prop(
891                 brush,
892                 "use_primary_overlay",
893                 text="",
894                 toggle=True,
895                 icon='RESTRICT_VIEW_ON' if brush.use_primary_overlay else 'RESTRICT_VIEW_OFF',
896             )
897
898         sub = row.row(align=True)
899         sub.prop(brush, "texture_overlay_alpha", text="Alpha")
900         sub.prop(brush, "use_primary_overlay_override", toggle=True, text="", icon='BRUSH_DATA')
901
902         col.label(text="Mask Texture:")
903
904         row = col.row(align=True)
905         if tex_slot_mask.map_mode != 'STENCIL':
906             row.prop(
907                 brush,
908                 "use_secondary_overlay",
909                 text="",
910                 toggle=True,
911                 icon='RESTRICT_VIEW_ON' if brush.use_secondary_overlay else 'RESTRICT_VIEW_OFF',
912             )
913
914         sub = row.row(align=True)
915         sub.prop(brush, "mask_overlay_alpha", text="Alpha")
916         sub.prop(brush, "use_secondary_overlay_override", toggle=True, text="", icon='BRUSH_DATA')
917
918
919 class IMAGE_PT_tools_brush_texture(BrushButtonsPanel, Panel):
920     bl_label = "Texture"
921     bl_context = ".paint_common_2d"
922     bl_options = {'DEFAULT_CLOSED'}
923     bl_category = "Tools"
924
925     def draw(self, context):
926         layout = self.layout
927
928         tool_settings = context.tool_settings.image_paint
929         brush = tool_settings.brush
930
931         col = layout.column()
932         col.template_ID_preview(brush, "texture", new="texture.new", rows=3, cols=8)
933
934         brush_texture_settings(col, brush, 0)
935
936
937 class IMAGE_PT_tools_mask_texture(BrushButtonsPanel, Panel):
938     bl_label = "Texture Mask"
939     bl_context = ".paint_common_2d"
940     bl_options = {'DEFAULT_CLOSED'}
941     bl_category = "Tools"
942
943     def draw(self, context):
944         layout = self.layout
945
946         brush = context.tool_settings.image_paint.brush
947
948         col = layout.column()
949
950         col.template_ID_preview(brush, "mask_texture", new="texture.new", rows=3, cols=8)
951
952         brush_mask_texture_settings(col, brush)
953
954
955 class IMAGE_PT_paint_stroke(BrushButtonsPanel, Panel):
956     bl_label = "Stroke"
957     bl_context = ".paint_common_2d"
958     bl_options = {'DEFAULT_CLOSED'}
959     bl_category = "Tools"
960
961     def draw(self, context):
962         layout = self.layout
963
964         tool_settings = context.tool_settings.image_paint
965         brush = tool_settings.brush
966
967         col = layout.column()
968
969         col.label(text="Stroke Method:")
970
971         col.prop(brush, "stroke_method", text="")
972
973         if brush.use_anchor:
974             col.separator()
975             col.prop(brush, "use_edge_to_edge", text="Edge To Edge")
976
977         if brush.use_airbrush:
978             col.separator()
979             col.prop(brush, "rate", text="Rate", slider=True)
980
981         if brush.use_space:
982             col.separator()
983             row = col.row(align=True)
984             row.prop(brush, "spacing", text="Spacing")
985             row.prop(brush, "use_pressure_spacing", toggle=True, text="")
986
987         if brush.use_line or brush.use_curve:
988             col.separator()
989             row = col.row(align=True)
990             row.prop(brush, "spacing", text="Spacing")
991
992         if brush.use_curve:
993             col.separator()
994             col.template_ID(brush, "paint_curve", new="paintcurve.new")
995             col.operator("paintcurve.draw")
996
997         col = layout.column()
998         col.separator()
999
1000         row = col.row(align=True)
1001         row.prop(brush, "use_relative_jitter", icon_only=True)
1002         if brush.use_relative_jitter:
1003             row.prop(brush, "jitter", slider=True)
1004         else:
1005             row.prop(brush, "jitter_absolute")
1006         row.prop(brush, "use_pressure_jitter", toggle=True, text="")
1007
1008         col = layout.column()
1009         col.separator()
1010
1011         if brush.brush_capabilities.has_smooth_stroke:
1012             col.prop(brush, "use_smooth_stroke")
1013
1014             sub = col.column()
1015             sub.active = brush.use_smooth_stroke
1016             sub.prop(brush, "smooth_stroke_radius", text="Radius", slider=True)
1017             sub.prop(brush, "smooth_stroke_factor", text="Factor", slider=True)
1018
1019             col.separator()
1020
1021         col.prop(tool_settings, "input_samples")
1022
1023
1024 class IMAGE_PT_paint_curve(BrushButtonsPanel, Panel):
1025     bl_label = "Curve"
1026     bl_context = ".paint_common_2d"
1027     bl_options = {'DEFAULT_CLOSED'}
1028     bl_category = "Tools"
1029
1030     def draw(self, context):
1031         layout = self.layout
1032
1033         tool_settings = context.tool_settings.image_paint
1034         brush = tool_settings.brush
1035
1036         layout.template_curve_mapping(brush, "curve")
1037
1038         col = layout.column(align=True)
1039         row = col.row(align=True)
1040         row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
1041         row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
1042         row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
1043         row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
1044         row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
1045         row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
1046
1047
1048 class IMAGE_PT_tools_imagepaint_symmetry(BrushButtonsPanel, Panel):
1049     bl_category = "Tools"
1050     bl_context = ".imagepaint_2d"
1051     bl_label = "Tiling"
1052     bl_options = {'DEFAULT_CLOSED'}
1053
1054     def draw(self, context):
1055         layout = self.layout
1056
1057         tool_settings = context.tool_settings
1058         ipaint = tool_settings.image_paint
1059
1060         col = layout.column(align=True)
1061         row = col.row(align=True)
1062         row.prop(ipaint, "tile_x", text="X", toggle=True)
1063         row.prop(ipaint, "tile_y", text="Y", toggle=True)
1064
1065
1066 class IMAGE_PT_tools_brush_appearance(BrushButtonsPanel, Panel):
1067     bl_label = "Appearance"
1068     bl_context = ".paint_common_2d"
1069     bl_options = {'DEFAULT_CLOSED'}
1070     bl_category = "Options"
1071     bl_parent_id = "IMAGE_PT_tools_brush_display"
1072
1073     def draw(self, context):
1074         layout = self.layout
1075
1076         tool_settings = context.tool_settings.image_paint
1077         brush = tool_settings.brush
1078
1079         if brush is None:  # unlikely but can happen.
1080             layout.label(text="Brush Unset")
1081             return
1082
1083         col = layout.column(align=True)
1084
1085         col.prop(tool_settings, "show_brush")
1086         sub = col.column()
1087         sub.active = tool_settings.show_brush
1088         sub.prop(brush, "cursor_color_add", text="")
1089
1090         col.separator()
1091
1092         col.prop(brush, "use_custom_icon")
1093         sub = col.column()
1094         sub.active = brush.use_custom_icon
1095         sub.prop(brush, "icon_filepath", text="")
1096
1097
1098 class IMAGE_PT_uv_sculpt_curve(Panel):
1099     bl_space_type = 'PROPERTIES'
1100     bl_region_type = 'WINDOW'
1101     bl_context = ".uv_sculpt"  # dot on purpose (access from topbar)
1102     bl_category = "Options"
1103     bl_label = "UV Sculpt Curve"
1104     bl_options = {'DEFAULT_CLOSED'}
1105
1106     @classmethod
1107     def poll(cls, context):
1108         return (context.uv_sculpt_object is not None)
1109
1110     def draw(self, context):
1111         layout = self.layout
1112
1113         tool_settings = context.tool_settings
1114         uvsculpt = tool_settings.uv_sculpt
1115         brush = uvsculpt.brush
1116
1117         layout.template_curve_mapping(brush, "curve")
1118
1119         row = layout.row(align=True)
1120         row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
1121         row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
1122         row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
1123         row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
1124         row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
1125         row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
1126
1127
1128 class IMAGE_PT_uv_sculpt(Panel):
1129     bl_space_type = 'PROPERTIES'
1130     bl_region_type = 'WINDOW'
1131     bl_context = ".uv_sculpt"  # dot on purpose (access from topbar)
1132     bl_category = "Options"
1133     bl_label = "UV Sculpt"
1134
1135     @classmethod
1136     def poll(cls, context):
1137         return (context.uv_sculpt_object is not None)
1138
1139     def draw(self, context):
1140         from .properties_paint_common import UnifiedPaintPanel
1141         layout = self.layout
1142
1143         tool_settings = context.tool_settings
1144         uvsculpt = tool_settings.uv_sculpt
1145         brush = uvsculpt.brush
1146
1147         if not self.is_popover:
1148             if brush:
1149                 col = layout.column()
1150
1151                 row = col.row(align=True)
1152                 UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
1153                 UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
1154
1155                 row = col.row(align=True)
1156                 UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength", slider=True)
1157                 UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
1158
1159         col = layout.column()
1160         col.prop(tool_settings, "uv_sculpt_lock_borders")
1161         col.prop(tool_settings, "uv_sculpt_all_islands")
1162
1163         col.prop(tool_settings, "uv_sculpt_tool")
1164         if tool_settings.uv_sculpt_tool == 'RELAX':
1165             col.prop(tool_settings, "uv_relax_method")
1166
1167         col.prop(uvsculpt, "show_brush")
1168
1169
1170 class ImageScopesPanel:
1171     @classmethod
1172     def poll(cls, context):
1173         sima = context.space_data
1174
1175         if not (sima and sima.image):
1176             return False
1177
1178         # scopes are not updated in paint modes, hide.
1179         if sima.mode == 'PAINT':
1180             return False
1181
1182         ob = context.active_object
1183         if ob and ob.mode in {'TEXTURE_PAINT', 'EDIT'}:
1184             return False
1185
1186         return True
1187
1188
1189 class IMAGE_PT_view_histogram(ImageScopesPanel, Panel):
1190     bl_space_type = 'IMAGE_EDITOR'
1191     bl_region_type = 'UI'
1192     bl_category = "Scopes"
1193     bl_label = "Histogram"
1194
1195     def draw(self, context):
1196         layout = self.layout
1197
1198         sima = context.space_data
1199         hist = sima.scopes.histogram
1200
1201         layout.template_histogram(sima.scopes, "histogram")
1202
1203         row = layout.row(align=True)
1204         row.prop(hist, "mode", expand=True)
1205         row.prop(hist, "show_line", text="")
1206
1207
1208 class IMAGE_PT_view_waveform(ImageScopesPanel, Panel):
1209     bl_space_type = 'IMAGE_EDITOR'
1210     bl_region_type = 'UI'
1211     bl_category = "Scopes"
1212     bl_label = "Waveform"
1213     bl_options = {'DEFAULT_CLOSED'}
1214
1215     def draw(self, context):
1216         layout = self.layout
1217
1218         sima = context.space_data
1219
1220         layout.template_waveform(sima, "scopes")
1221         row = layout.split(factor=0.75)
1222         row.prop(sima.scopes, "waveform_alpha")
1223         row.prop(sima.scopes, "waveform_mode", text="")
1224
1225
1226 class IMAGE_PT_view_vectorscope(ImageScopesPanel, Panel):
1227     bl_space_type = 'IMAGE_EDITOR'
1228     bl_region_type = 'UI'
1229     bl_category = "Scopes"
1230     bl_label = "Vectorscope"
1231     bl_options = {'DEFAULT_CLOSED'}
1232
1233     def draw(self, context):
1234         layout = self.layout
1235
1236         sima = context.space_data
1237         layout.template_vectorscope(sima, "scopes")
1238         layout.prop(sima.scopes, "vectorscope_alpha")
1239
1240
1241 class IMAGE_PT_sample_line(ImageScopesPanel, Panel):
1242     bl_space_type = 'IMAGE_EDITOR'
1243     bl_region_type = 'UI'
1244     bl_category = "Scopes"
1245     bl_label = "Sample Line"
1246     bl_options = {'DEFAULT_CLOSED'}
1247
1248     def draw(self, context):
1249         layout = self.layout
1250
1251         sima = context.space_data
1252         hist = sima.sample_histogram
1253
1254         layout.operator("image.sample_line")
1255         layout.template_histogram(sima, "sample_histogram")
1256
1257         row = layout.row(align=True)
1258         row.prop(hist, "mode", expand=True)
1259         row.prop(hist, "show_line", text="")
1260
1261
1262 class IMAGE_PT_scope_sample(ImageScopesPanel, Panel):
1263     bl_space_type = 'IMAGE_EDITOR'
1264     bl_region_type = 'UI'
1265     bl_category = "Scopes"
1266     bl_label = "Samples"
1267     bl_options = {'DEFAULT_CLOSED'}
1268
1269     def draw(self, context):
1270         layout = self.layout
1271         layout.use_property_split = True
1272         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
1273
1274         sima = context.space_data
1275
1276         col = flow.column()
1277         col.prop(sima.scopes, "use_full_resolution")
1278
1279         col = flow.column()
1280         col.active = not sima.scopes.use_full_resolution
1281         col.prop(sima.scopes, "accuracy")
1282
1283
1284 class IMAGE_PT_uv_cursor(Panel):
1285     bl_space_type = 'IMAGE_EDITOR'
1286     bl_region_type = 'UI'
1287     bl_category = "Image"
1288     bl_label = "2D Cursor"
1289
1290     @classmethod
1291     def poll(cls, context):
1292         sima = context.space_data
1293
1294         return (sima and (sima.show_uvedit or sima.show_maskedit))
1295
1296     def draw(self, context):
1297         layout = self.layout
1298
1299         sima = context.space_data
1300         ima = sima.image
1301
1302
1303         uvedit = sima.uv_editor
1304
1305         col = layout.column()
1306
1307         col = layout.column()
1308         col.prop(sima, "cursor_location", text="Cursor Location")
1309
1310
1311 # Grease Pencil properties
1312 class IMAGE_PT_grease_pencil(AnnotationDataPanel, Panel):
1313     bl_space_type = 'IMAGE_EDITOR'
1314     bl_region_type = 'UI'
1315     bl_category = "Image"
1316
1317     # NOTE: this is just a wrapper around the generic GP Panel.
1318
1319 # Grease Pencil drawing tools.
1320
1321
1322 classes = (
1323     IMAGE_MT_view,
1324     IMAGE_MT_view_zoom,
1325     IMAGE_MT_select,
1326     IMAGE_MT_brush,
1327     IMAGE_MT_image,
1328     IMAGE_MT_image_invert,
1329     IMAGE_MT_uvs,
1330     IMAGE_MT_uvs_showhide,
1331     IMAGE_MT_uvs_proportional,
1332     IMAGE_MT_uvs_transform,
1333     IMAGE_MT_uvs_snap,
1334     IMAGE_MT_uvs_mirror,
1335     IMAGE_MT_uvs_weldalign,
1336     IMAGE_MT_uvs_select_mode,
1337     IMAGE_MT_uvs_context_menu,
1338     IMAGE_MT_pivot_pie,
1339     IMAGE_MT_uvs_snap_pie,
1340     IMAGE_HT_header,
1341     MASK_MT_editor_menus,
1342     IMAGE_PT_mask,
1343     IMAGE_PT_mask_layers,
1344     IMAGE_PT_mask_display,
1345     IMAGE_PT_active_mask_spline,
1346     IMAGE_PT_active_mask_point,
1347     IMAGE_PT_image_properties,
1348     IMAGE_UL_render_slots,
1349     IMAGE_PT_render_slots,
1350     IMAGE_PT_view_display,
1351     IMAGE_PT_view_display_uv_edit_overlays,
1352     IMAGE_PT_view_display_uv_edit_overlays_advanced,
1353     IMAGE_PT_paint,
1354     IMAGE_PT_tools_brush_display,
1355     IMAGE_PT_tools_brush_texture,
1356     IMAGE_PT_tools_mask_texture,
1357     IMAGE_PT_paint_stroke,
1358     IMAGE_PT_paint_curve,
1359     IMAGE_PT_tools_imagepaint_symmetry,
1360     IMAGE_PT_tools_brush_appearance,
1361     IMAGE_PT_uv_sculpt,
1362     IMAGE_PT_uv_sculpt_curve,
1363     IMAGE_PT_view_histogram,
1364     IMAGE_PT_view_waveform,
1365     IMAGE_PT_view_vectorscope,
1366     IMAGE_PT_sample_line,
1367     IMAGE_PT_scope_sample,
1368     IMAGE_PT_uv_cursor,
1369     IMAGE_PT_grease_pencil,
1370 )
1371
1372
1373 if __name__ == "__main__":  # only for live edit.
1374     from bpy.utils import register_class
1375     for cls in classes:
1376         register_class(cls)