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