UI: add material settings in shader editor sidebar.
[blender.git] / release / scripts / startup / bl_ui / properties_material.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 import bpy
21 from bpy.types import Menu, Panel, UIList
22 from rna_prop_ui import PropertyPanel
23 from bpy.app.translations import pgettext_iface as iface_
24 from bpy_extras.node_utils import find_node_input
25
26
27 class MATERIAL_MT_specials(Menu):
28     bl_label = "Material Specials"
29
30     def draw(self, context):
31         layout = self.layout
32
33         layout.operator("material.copy", icon='COPYDOWN')
34         layout.operator("object.material_slot_copy")
35         layout.operator("material.paste", icon='PASTEDOWN')
36
37
38 class MATERIAL_UL_matslots(UIList):
39
40     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
41         # assert(isinstance(item, bpy.types.MaterialSlot)
42         # ob = data
43         slot = item
44         ma = slot.material
45         if self.layout_type in {'DEFAULT', 'COMPACT'}:
46             if ma:
47                 layout.prop(ma, "name", text="", emboss=False, icon_value=icon)
48             else:
49                 layout.label(text="", icon_value=icon)
50         elif self.layout_type == 'GRID':
51             layout.alignment = 'CENTER'
52             layout.label(text="", icon_value=icon)
53
54
55 class MaterialButtonsPanel:
56     bl_space_type = 'PROPERTIES'
57     bl_region_type = 'WINDOW'
58     bl_context = "material"
59     # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
60
61     @classmethod
62     def poll(cls, context):
63         return context.material and (context.engine in cls.COMPAT_ENGINES)
64
65
66 class MATERIAL_PT_preview(MaterialButtonsPanel, Panel):
67     bl_label = "Preview"
68     bl_options = {'DEFAULT_CLOSED'}
69     COMPAT_ENGINES = {'BLENDER_EEVEE'}
70
71     def draw(self, context):
72         self.layout.template_preview(context.material)
73
74
75 class MATERIAL_PT_custom_props(MaterialButtonsPanel, PropertyPanel, Panel):
76     COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
77     _context_path = "material"
78     _property_type = bpy.types.Material
79
80
81 class EEVEE_MATERIAL_PT_context_material(MaterialButtonsPanel, Panel):
82     bl_label = ""
83     bl_context = "material"
84     bl_options = {'HIDE_HEADER'}
85     COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
86
87     @classmethod
88     def poll(cls, context):
89         if context.active_object and context.active_object.type == 'GPENCIL':
90             return False
91         else:
92             engine = context.engine
93             return (context.material or context.object) and (engine in cls.COMPAT_ENGINES)
94
95     def draw(self, context):
96         layout = self.layout
97
98         mat = context.material
99         ob = context.object
100         slot = context.material_slot
101         space = context.space_data
102
103         if ob:
104             is_sortable = len(ob.material_slots) > 1
105             rows = 3
106             if (is_sortable):
107                 rows = 5
108
109             row = layout.row()
110
111             row.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=rows)
112
113             col = row.column(align=True)
114             col.operator("object.material_slot_add", icon='ADD', text="")
115             col.operator("object.material_slot_remove", icon='REMOVE', text="")
116
117             col.separator()
118
119             col.menu("MATERIAL_MT_specials", icon='DOWNARROW_HLT', text="")
120
121             if is_sortable:
122                 col.separator()
123
124                 col.operator("object.material_slot_move", icon='TRIA_UP', text="").direction = 'UP'
125                 col.operator("object.material_slot_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
126
127         row = layout.row()
128
129         if ob:
130             row.template_ID(ob, "active_material", new="material.new")
131
132             if slot:
133                 icon_link = 'MESH_DATA' if slot.link == 'DATA' else 'OBJECT_DATA'
134                 row.prop(slot, "link", icon=icon_link, icon_only=True)
135
136             if ob.mode == 'EDIT':
137                 row = layout.row(align=True)
138                 row.operator("object.material_slot_assign", text="Assign")
139                 row.operator("object.material_slot_select", text="Select")
140                 row.operator("object.material_slot_deselect", text="Deselect")
141
142         elif mat:
143             row.template_ID(space, "pin_id")
144
145
146 def panel_node_draw(layout, ntree, output_type, input_name):
147     node = ntree.get_output_node('EEVEE')
148
149     if node:
150         input = find_node_input(node, input_name)
151         if input:
152             layout.template_node_view(ntree, node, input)
153         else:
154             layout.label(text="Incompatible output node")
155     else:
156         layout.label(text="No output node")
157
158
159 class EEVEE_MATERIAL_PT_surface(MaterialButtonsPanel, Panel):
160     bl_label = "Surface"
161     bl_context = "material"
162     COMPAT_ENGINES = {'BLENDER_EEVEE'}
163
164     @classmethod
165     def poll(cls, context):
166         engine = context.engine
167         return context.material and (engine in cls.COMPAT_ENGINES)
168
169     def draw(self, context):
170         layout = self.layout
171
172         mat = context.material
173
174         layout.prop(mat, "use_nodes", icon='NODETREE')
175         layout.separator()
176
177         if mat.use_nodes:
178             panel_node_draw(layout, mat.node_tree, 'OUTPUT_MATERIAL', "Surface")
179         else:
180             layout.use_property_split = True
181             layout.prop(mat, "diffuse_color", text="Base Color")
182             layout.prop(mat, "metallic")
183             layout.prop(mat, "specular_intensity", text="Specular")
184             layout.prop(mat, "roughness")
185
186
187 class EEVEE_MATERIAL_PT_volume(MaterialButtonsPanel, Panel):
188     bl_label = "Volume"
189     bl_context = "material"
190     bl_options = {'DEFAULT_CLOSED'}
191     COMPAT_ENGINES = {'BLENDER_EEVEE'}
192
193     @classmethod
194     def poll(cls, context):
195         engine = context.engine
196         mat = context.material
197         return mat and mat.use_nodes and (engine in cls.COMPAT_ENGINES)
198
199     def draw(self, context):
200         layout = self.layout
201
202         mat = context.material
203
204         panel_node_draw(layout, mat.node_tree, 'OUTPUT_MATERIAL', "Volume")
205
206
207 class EEVEE_MATERIAL_PT_settings(MaterialButtonsPanel, Panel):
208     bl_label = "Settings"
209     bl_context = "material"
210     COMPAT_ENGINES = {'BLENDER_EEVEE'}
211
212     @classmethod
213     def poll(cls, context):
214         engine = context.engine
215         return context.material and (engine in cls.COMPAT_ENGINES)
216
217     @staticmethod
218     def draw_shared(self, mat):
219         layout = self.layout
220         layout.use_property_split = True
221
222         layout.prop(mat, "blend_method")
223
224         if mat.blend_method != 'OPAQUE':
225             layout.prop(mat, "transparent_shadow_method")
226
227             row = layout.row()
228             row.active = ((mat.blend_method == 'CLIP') or (mat.transparent_shadow_method == 'CLIP'))
229             row.prop(mat, "alpha_threshold")
230
231         if mat.blend_method not in {'OPAQUE', 'CLIP', 'HASHED'}:
232             layout.prop(mat, "show_transparent_backside")
233
234         layout.prop(mat, "use_screen_refraction")
235         layout.prop(mat, "refraction_depth")
236         layout.prop(mat, "use_sss_translucency")
237
238     def draw(self, context):
239         self.draw_shared(self, context.material)
240
241
242 class MATERIAL_PT_viewport(MaterialButtonsPanel, Panel):
243     bl_label = "Viewport Display"
244     bl_context = "material"
245     bl_options = {'DEFAULT_CLOSED'}
246
247     @classmethod
248     def poll(cls, context):
249         return context.material
250
251     @staticmethod
252     def draw_shared(self, mat):
253         layout = self.layout
254         layout.use_property_split = True
255
256         col = layout.column()
257         col.prop(mat, "diffuse_color", text="Color")
258         col.prop(mat, "metallic")
259         col.prop(mat, "roughness")
260
261     def draw(self, context):
262         self.draw_shared(self, context.material)
263
264
265 classes = (
266     MATERIAL_MT_specials,
267     MATERIAL_UL_matslots,
268     MATERIAL_PT_preview,
269     EEVEE_MATERIAL_PT_context_material,
270     EEVEE_MATERIAL_PT_surface,
271     EEVEE_MATERIAL_PT_volume,
272     EEVEE_MATERIAL_PT_settings,
273     MATERIAL_PT_viewport,
274     MATERIAL_PT_custom_props,
275 )
276
277
278 if __name__ == "__main__":  # only for live edit.
279     from bpy.utils import register_class
280     for cls in classes:
281         register_class(cls)