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>
20import bpy
21from bpy.types import Menu, Panel, UIList
22from rna_prop_ui import PropertyPanel
23from bpy_extras.node_utils import find_node_input
24
25
26class MATERIAL_MT_context_menu(Menu):
27    bl_label = "Material Specials"
28
29    def draw(self, _context):
30        layout = self.layout
31
32        layout.operator("material.copy", icon='COPYDOWN')
33        layout.operator("object.material_slot_copy")
34        layout.operator("material.paste", icon='PASTEDOWN')
35        layout.operator("object.material_slot_remove_unused")
36
37
38class 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
55class 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        mat = context.material
64        return mat and (context.engine in cls.COMPAT_ENGINES) and not mat.grease_pencil
65
66
67class MATERIAL_PT_preview(MaterialButtonsPanel, Panel):
68    bl_label = "Preview"
69    bl_options = {'DEFAULT_CLOSED'}
70    COMPAT_ENGINES = {'BLENDER_EEVEE'}
71
72    def draw(self, context):
73        self.layout.template_preview(context.material)
74
75
76class MATERIAL_PT_custom_props(MaterialButtonsPanel, PropertyPanel, Panel):
77    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
78    _context_path = "material"
79    _property_type = bpy.types.Material
80
81
82class EEVEE_MATERIAL_PT_context_material(MaterialButtonsPanel, Panel):
83    bl_label = ""
84    bl_context = "material"
85    bl_options = {'HIDE_HEADER'}
86    COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
87
88    @classmethod
89    def poll(cls, context):
90        ob = context.object
91        mat = context.material
92
93        if (ob and ob.type == 'GPENCIL') or (mat and mat.grease_pencil):
94            return False
95
96        return (ob or mat) and (context.engine in cls.COMPAT_ENGINES)
97
98    def draw(self, context):
99        layout = self.layout
100
101        mat = context.material
102        ob = context.object
103        slot = context.material_slot
104        space = context.space_data
105
106        if ob:
107            is_sortable = len(ob.material_slots) > 1
108            rows = 3
109            if is_sortable:
110                rows = 5
111
112            row = layout.row()
113
114            row.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=rows)
115
116            col = row.column(align=True)
117            col.operator("object.material_slot_add", icon='ADD', text="")
118            col.operator("object.material_slot_remove", icon='REMOVE', text="")
119
120            col.separator()
121
122            col.menu("MATERIAL_MT_context_menu", icon='DOWNARROW_HLT', text="")
123
124            if is_sortable:
125                col.separator()
126
127                col.operator("object.material_slot_move", icon='TRIA_UP', text="").direction = 'UP'
128                col.operator("object.material_slot_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
129
130        row = layout.row()
131
132        if ob:
133            row.template_ID(ob, "active_material", new="material.new")
134
135            if slot:
136                icon_link = 'MESH_DATA' if slot.link == 'DATA' else 'OBJECT_DATA'
137                row.prop(slot, "link", icon=icon_link, icon_only=True)
138
139            if ob.mode == 'EDIT':
140                row = layout.row(align=True)
141                row.operator("object.material_slot_assign", text="Assign")
142                row.operator("object.material_slot_select", text="Select")
143                row.operator("object.material_slot_deselect", text="Deselect")
144
145        elif mat:
146            row.template_ID(space, "pin_id")
147
148
149def panel_node_draw(layout, ntree, _output_type, input_name):
150    node = ntree.get_output_node('EEVEE')
151
152    if node:
153        input = find_node_input(node, input_name)
154        if input:
155            layout.template_node_view(ntree, node, input)
156        else:
157            layout.label(text="Incompatible output node")
158    else:
159        layout.label(text="No output node")
160
161
162class EEVEE_MATERIAL_PT_surface(MaterialButtonsPanel, Panel):
163    bl_label = "Surface"
164    bl_context = "material"
165    COMPAT_ENGINES = {'BLENDER_EEVEE'}
166
167    def draw(self, context):
168        layout = self.layout
169
170        mat = context.material
171
172        layout.prop(mat, "use_nodes", icon='NODETREE')
173        layout.separator()
174
175        layout.use_property_split = True
176
177        if mat.use_nodes:
178            panel_node_draw(layout, mat.node_tree, 'OUTPUT_MATERIAL', "Surface")
179        else:
180            layout.prop(mat, "diffuse_color", text="Base Color")
181            layout.prop(mat, "metallic")
182            layout.prop(mat, "specular_intensity", text="Specular")
183            layout.prop(mat, "roughness")
184
185
186class EEVEE_MATERIAL_PT_volume(MaterialButtonsPanel, Panel):
187    bl_label = "Volume"
188    bl_context = "material"
189    bl_options = {'DEFAULT_CLOSED'}
190    COMPAT_ENGINES = {'BLENDER_EEVEE'}
191
192    @classmethod
193    def poll(cls, context):
194        engine = context.engine
195        mat = context.material
196        return mat and mat.use_nodes and (engine in cls.COMPAT_ENGINES) and not mat.grease_pencil
197
198    def draw(self, context):
199        layout = self.layout
200
201        layout.use_property_split = True
202
203        mat = context.material
204
205        panel_node_draw(layout, mat.node_tree, 'OUTPUT_MATERIAL', "Volume")
206
207
208def draw_material_settings(self, context):
209    layout = self.layout
210    layout.use_property_split = True
211    layout.use_property_decorate = False
212
213    mat = context.material
214
215    layout.prop(mat, "use_backface_culling")
216    layout.prop(mat, "blend_method")
217    layout.prop(mat, "shadow_method")
218
219    row = layout.row()
220    row.active = ((mat.blend_method == 'CLIP') or (mat.shadow_method == 'CLIP'))
221    row.prop(mat, "alpha_threshold")
222
223    if mat.blend_method not in {'OPAQUE', 'CLIP', 'HASHED'}:
224        layout.prop(mat, "show_transparent_back")
225
226    layout.prop(mat, "use_screen_refraction")
227    layout.prop(mat, "refraction_depth")
228    layout.prop(mat, "use_sss_translucency")
229    layout.prop(mat, "pass_index")
230
231
232class EEVEE_MATERIAL_PT_settings(MaterialButtonsPanel, Panel):
233    bl_label = "Settings"
234    bl_context = "material"
235    COMPAT_ENGINES = {'BLENDER_EEVEE'}
236
237    def draw(self, context):
238        draw_material_settings(self, context)
239
240
241class EEVEE_MATERIAL_PT_viewport_settings(MaterialButtonsPanel, Panel):
242    bl_label = "Settings"
243    bl_context = "material"
244    bl_parent_id = "MATERIAL_PT_viewport"
245    COMPAT_ENGINES = {'BLENDER_RENDER'}
246
247    def draw(self, context):
248        draw_material_settings(self, context)
249
250
251class MATERIAL_PT_viewport(MaterialButtonsPanel, Panel):
252    bl_label = "Viewport Display"
253    bl_context = "material"
254    bl_options = {'DEFAULT_CLOSED'}
255    bl_order = 10
256
257    @classmethod
258    def poll(cls, context):
259        mat = context.material
260        return mat and not mat.grease_pencil
261
262    def draw(self, context):
263        layout = self.layout
264        layout.use_property_split = True
265
266        mat = context.material
267
268        col = layout.column()
269        col.prop(mat, "diffuse_color", text="Color")
270        col.prop(mat, "metallic")
271        col.prop(mat, "roughness")
272
273
274classes = (
275    MATERIAL_MT_context_menu,
276    MATERIAL_UL_matslots,
277    MATERIAL_PT_preview,
278    EEVEE_MATERIAL_PT_context_material,
279    EEVEE_MATERIAL_PT_surface,
280    EEVEE_MATERIAL_PT_volume,
281    EEVEE_MATERIAL_PT_settings,
282    MATERIAL_PT_viewport,
283    EEVEE_MATERIAL_PT_viewport_settings,
284    MATERIAL_PT_custom_props,
285)
286
287
288if __name__ == "__main__":  # only for live edit.
289    from bpy.utils import register_class
290    for cls in classes:
291        register_class(cls)
292