├── PBR_MB_TestScene.blend ├── README.md └── PBR_texture_multi_baker_script.py /PBR_MB_TestScene.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ASLGalileiVerona/PBR-texture-baker/HEAD/PBR_MB_TestScene.blend -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PBR-texture-baker 2 | An addon for blender which allowes you to bake more than one texture at the same time. 3 | This addon must be used with the "UE4" shader node which is conteined into the blend file. 4 | You have to install the .py file in the user preferences as an addon to use the PBR texture baker. 5 | -------------------------------------------------------------------------------- /PBR_texture_multi_baker_script.py: -------------------------------------------------------------------------------- 1 | ''' 2 | PBR texture multi baker v. 0.1 Beta (06/28/2018) 3 | 4 | Developed by: 5 | 6 | Cavicchini Davide 7 | De Bianchi Giacomo 8 | Giaretta Leonardo 9 | Ricci Francesco 10 | 11 | ''' 12 | 13 | 14 | bl_info = { 15 | "name": "PBR Texture Multi Baker", 16 | 'description': 'This addon can bake more than one texture simultaneously', 17 | 'author': '', 18 | 'license': 'GPL', 19 | 'deps': 'UE4 Shader node', 20 | 'version': (0 , 1), 21 | 'blender': (2, 7, 9), 22 | 'location': 'Properties > Render > PBR Texture Multi Baker', 23 | 'warning': 'While baking blender will freeze', 24 | 'wiki_url': 'https://github.com/ASLGalileiVerona/PBR-texture-baker', 25 | 'tracker_url': 'https://github.com/ASLGalileiVerona/PBR-texture-baker/issues', 26 | 'link': 'https://github.com/ASLGalileiVerona/PBR-texture-baker', 27 | 'support': 'COMMUNITY', 28 | "category": "Material" 29 | } 30 | 31 | 32 | import bpy 33 | import os 34 | 35 | from bpy.props import (StringProperty, 36 | BoolProperty, 37 | IntProperty, 38 | FloatProperty, 39 | FloatVectorProperty, 40 | EnumProperty, 41 | PointerProperty, 42 | ) 43 | from bpy.types import (Panel, 44 | Operator, 45 | AddonPreferences, 46 | PropertyGroup, 47 | ) 48 | def cr(self, context): 49 | bpy.context.scene.my_tool.xresolution = int(bpy.context.scene.my_tool.Presets) 50 | bpy.context.scene.my_tool.yresolution = int(bpy.context.scene.my_tool.Presets) 51 | return None 52 | 53 | def cd(self, context): 54 | if (bpy.context.scene.my_tool.cd8): 55 | bpy.context.scene.render.image_settings.color_depth = '8' 56 | bpy.context.scene.my_tool.cd16 = False 57 | if (not bpy.context.scene.my_tool.cd8 and not bpy.context.scene.my_tool.cd16): 58 | bpy.context.scene.render.image_settings.color_depth = '16' 59 | bpy.context.scene.my_tool.cd16 = True 60 | return None 61 | 62 | def cde(self, context): 63 | if (bpy.context.scene.my_tool.cd16): 64 | bpy.context.scene.render.image_settings.color_depth = '16' 65 | bpy.context.scene.my_tool.cd8 = False 66 | if (not bpy.context.scene.my_tool.cd8 and not bpy.context.scene.my_tool.cd16): 67 | bpy.context.scene.render.image_settings.color_depth = '8' 68 | bpy.context.scene.my_tool.cd8 = True 69 | return None 70 | 71 | def main(context): 72 | bpy.context.active_object.data.uv_textures.active = bpy.context.active_object.data.uv_textures[bpy.context.scene.my_tool.UV_selection] 73 | filepath = bpy.data.filepath 74 | filedir = os.path.dirname(filepath) 75 | BakeResolutionx = bpy.context.scene.my_tool.xresolution 76 | BakeResolutiony = bpy.context.scene.my_tool.yresolution 77 | BakeDir = bpy.path.abspath(bpy.context.scene.my_tool.filedir) 78 | BakeChannel = [[bpy.context.scene.my_tool.Base_color,bpy.context.scene.my_tool.Base_Suffix,'Base Color'], 79 | [bpy.context.scene.my_tool.Metallic,bpy.context.scene.my_tool.Metal_Suffix,'Metallic'], 80 | [bpy.context.scene.my_tool.Specular,bpy.context.scene.my_tool.Spec_Suffix,'Specular'], 81 | [bpy.context.scene.my_tool.Roughness,bpy.context.scene.my_tool.Rough_Suffix,'Roughness'], 82 | [bpy.context.scene.my_tool.Emissive,bpy.context.scene.my_tool.Emit_Suffix,'Emissive Color'], 83 | [bpy.context.scene.my_tool.Normal,bpy.context.scene.my_tool.Norm_Suffix,'Normal (Non-Color)'], 84 | [bpy.context.scene.my_tool.Alfa,bpy.context.scene.my_tool.Alfa_Suffix,'Opacity']] 85 | 86 | 87 | # Get the Active Object. 88 | ActObj = bpy.context.active_object 89 | 90 | 91 | # Get the first Active Object Material. 92 | ActMat = ActObj.active_material 93 | print ('Active Material: ' + ActMat.name) #for debug. 94 | 95 | # Change its parameters - example viewport diffuse color. 96 | 97 | # Duplicate Active Object Material to create a new temp material and link it to the Active Object. 98 | TempMat = ActMat.copy() 99 | TempMat.name = 'Temp Material' 100 | ActObj.data.materials[0] = TempMat 101 | print ('Temp Material: ' + TempMat.name) #for debug. 102 | 103 | # Get the nodes. 104 | nodes = TempMat.node_tree.nodes 105 | print ('Nodes: ' + str(nodes)) #for debug. 106 | 107 | # Get some specific node: 108 | # Returns None if the node does not exist. 109 | MatOut = nodes.get('Material Output') 110 | print (MatOut) #for debug 111 | 112 | # Go look for node named "UE4 Opaque". 113 | MainShader = [node for node in bpy.data.materials['Temp Material'].node_tree.nodes if node.type=='GROUP' and node.node_tree==bpy.data.node_groups['UE4']] 114 | print ('Main Shader ' + str(MainShader)) #for debug 115 | 116 | 117 | # Select correct Node Slot. 118 | Sampling_op= bpy.context.scene.cycles.samples 119 | Bake_op=[ 120 | bpy.context.scene.cycles.bake_type, 121 | bpy.context.scene.render.bake.margin, 122 | ] 123 | Lights_Paths_op = [ 124 | bpy.context.scene.cycles.transparent_max_bounces, 125 | bpy.context.scene.cycles.transparent_min_bounces, 126 | bpy.context.scene.cycles.max_bounces, 127 | bpy.context.scene.cycles.min_bounces, 128 | bpy.context.scene.cycles.caustics_reflective, 129 | bpy.context.scene.cycles.caustics_refractive, 130 | ] 131 | bpy.context.scene.cycles.samples = 1 132 | bpy.context.scene.cycles.transparent_max_bounces = 1 133 | bpy.context.scene.cycles.transparent_min_bounces = 1 134 | bpy.context.scene.cycles.max_bounces = 1 135 | bpy.context.scene.cycles.min_bounces = 1 136 | bpy.context.scene.cycles.caustics_reflective = False 137 | bpy.context.scene.cycles.caustics_refractive = False 138 | media = (bpy.context.scene.my_tool.xresolution + bpy.context.scene.my_tool.yresolution) // 2 139 | if(media<=512): 140 | bpy.context.scene.render.bake.margin = 0 141 | elif(media>=4096): 142 | bpy.context.scene.render.bake.margin = 16 143 | else: 144 | bpy.context.scene.render.bake.margin = media // 256 145 | for i in range(0,7,1): 146 | if(BakeChannel[i][0]): 147 | CurrentSlot = 0 148 | print ('Baking: ' + BakeChannel[i][2]) 149 | for k in range(len(MainShader[0].outputs)): 150 | print(MainShader[0].outputs[k].name) 151 | if MainShader[0].outputs[k].name == BakeChannel[i][2]: 152 | CurrentSlot = k 153 | print('Gotcha! Slot N. ' + str(CurrentSlot)) 154 | break 155 | else: 156 | print ('No object found.') 157 | # Link nodes. 158 | TempMat.node_tree.links.new(MainShader[0].outputs[CurrentSlot], MatOut.inputs[0]) 159 | 160 | 161 | # Create the correct resolution Image Node. 162 | BakeNode = TempMat.node_tree.nodes.new('ShaderNodeTexImage') 163 | format_list = [["PNG","png"], 164 | ["TIFF","tiff"], 165 | ["OPEN_EXR","exr"], 166 | ["DPX","dpx"], 167 | ["CINEON","cin"], 168 | ["TARGA","tga"], 169 | ["JPEG2000","jp2"], 170 | ["BMP","bmp"], 171 | ["IRIS","rgb"], 172 | ["JPEG","jpeg"]] 173 | for p in range(0,10,1): 174 | if (p==int(bpy.context.scene.my_tool.FileType)): 175 | BakeFilename = bpy.context.scene.my_tool.Base_Name + BakeChannel[i][1] + '.' + format_list[p][1] 176 | BakeNode.image = bpy.data.images.new(BakeFilename,BakeResolutionx,BakeResolutiony,alpha=False) 177 | for p in range(0,10,1): 178 | if (p==int(bpy.context.scene.my_tool.FileType)): 179 | BakeNode.image.file_format = format_list[p][0] 180 | 181 | # Non Color for Normal 182 | if (i == 5): 183 | BakeNode.image.colorspace_settings.name = 'Non-Color' 184 | print ('Setting Non-Color for Normal Bake') 185 | 186 | # Non Color for Roughness. 187 | if (i == 3): 188 | BakeNode.image.colorspace_settings.name = 'Non-Color' 189 | print ('Setting Non-Color for Roughness Bake') 190 | 191 | # Non Color for Opacity. 192 | if (i == 6): 193 | BakeNode.image.colorspace_settings.name = 'Non-Color' 194 | print ('Setting Non-Color for Roughness Bake') 195 | 196 | # Make the BakeNode Active. 197 | BakeNode.select = True 198 | TempMat.node_tree.nodes.active = BakeNode 199 | 200 | # Set bake options. 201 | bpy.context.scene.render.bake.use_selected_to_active = 0 202 | bpy.context.scene.render.bake.use_clear = 0 203 | 204 | # Baking. 205 | print ('Baking ' + str(BakeChannel[i][2]) + ' Texture at ' + str(BakeResolutionx) + ' x '+ str(BakeResolutiony) + ' pixel...') 206 | bpy.ops.object.bake(type='EMIT') 207 | 208 | # Save the texture with the correct filename. 209 | if not os.path.exists(BakeDir): 210 | os.makedirs(BakeDir) 211 | print ('Saving ' + str(BakeChannel[i][2]) + ' Texture as: ' + str(BakeFilename)) 212 | BakeNode.image.save_render(BakeDir + BakeFilename) 213 | 214 | 215 | # Back to the original Shader. 216 | bpy.data.images.remove(bpy.data.images[BakeFilename]) 217 | 218 | bpy.data.materials.remove(TempMat) 219 | ActObj.data.materials[0] = ActMat 220 | 221 | bpy.context.scene.cycles.samples = Sampling_op 222 | 223 | bpy.context.scene.cycles.bake_type = Bake_op[0] 224 | bpy.context.scene.render.bake.margin = Bake_op[1] 225 | 226 | bpy.context.scene.cycles.transparent_max_bounces = Lights_Paths_op[0] 227 | bpy.context.scene.cycles.transparent_min_bounces = Lights_Paths_op[1] 228 | bpy.context.scene.cycles.max_bounces = Lights_Paths_op[2] 229 | bpy.context.scene.cycles.min_bounces = Lights_Paths_op[3] 230 | bpy.context.scene.cycles.caustics_reflective = Lights_Paths_op[4] 231 | bpy.context.scene.cycles.caustics_refractive = Lights_Paths_op[5] 232 | 233 | class SimpleOperator(bpy.types.Operator): 234 | """Tooltip""" 235 | bl_idname = "object.simple_operator" 236 | bl_label = "Simple Object Operator" 237 | 238 | @classmethod 239 | def poll(cls, context): 240 | return context.active_object is not None 241 | 242 | def execute(self, context): 243 | if(len(bpy.context.active_object.material_slots.items())==1): 244 | if(len(bpy.context.active_object.data.uv_layers.items())>1): 245 | self.report({'INFO'},'more than one uv_layers were detected baking '+bpy.context.active_object.data.uv_layers.active.name) 246 | main(context) 247 | return {'FINISHED'} 248 | else: 249 | self.report({'ERROR'},'More than one material slots were detected') 250 | return {'CANCELLED'} 251 | 252 | def item_cb(self, context): 253 | return[(uv.name,uv.name,"")for uv in bpy.context.active_object.data.uv_layers] 254 | 255 | def active_UV(self, context): 256 | bpy.context.active_object.data.uv_textures.active = bpy.context.active_object.data.uv_textures[bpy.context.scene.my_tool.UV_selection] 257 | print(bpy.context.active_object.data.uv_layers.active) 258 | return None 259 | 260 | class MySettings(PropertyGroup): 261 | 262 | UV_selection = EnumProperty( 263 | name = 'UVs', 264 | items = item_cb, 265 | update = active_UV 266 | ) 267 | 268 | Base_color = BoolProperty( 269 | name="base color", 270 | description="Select if you want to bake it", 271 | ) 272 | Metallic = BoolProperty( 273 | name="metallic", 274 | description="Select if you want to bake it", 275 | ) 276 | Specular = BoolProperty( 277 | name="specular", 278 | description="Select if you want to bake it", 279 | ) 280 | Roughness = BoolProperty( 281 | name="roughness", 282 | description="Select if you want to bake it", 283 | ) 284 | Emissive = BoolProperty( 285 | name="emissive", 286 | description="Select if you want to bake it", 287 | ) 288 | Normal = BoolProperty( 289 | name="normal", 290 | description="Select if you want to bake it", 291 | ) 292 | Alfa = BoolProperty( 293 | name="alfa", 294 | description="Select if you want to bake it", 295 | ) 296 | cd8 = BoolProperty( 297 | name="cd8", 298 | description="Select the color depth", 299 | default = True, 300 | update = cd 301 | ) 302 | cd16 = BoolProperty( 303 | name="cd16", 304 | description="Select the color depth", 305 | default = False, 306 | update = cde 307 | ) 308 | Custom = BoolProperty( 309 | name="custom", 310 | description="Select if you want to choose different resolutions", 311 | update = cr 312 | ) 313 | Base_Suffix = StringProperty( 314 | default = "_COL" 315 | ) 316 | Metal_Suffix = StringProperty( 317 | default = "_MET" 318 | ) 319 | Spec_Suffix = StringProperty( 320 | default = "_SPE" 321 | ) 322 | Rough_Suffix = StringProperty( 323 | default = "_ROU" 324 | ) 325 | Emit_Suffix = StringProperty( 326 | default = "_EMI" 327 | ) 328 | Norm_Suffix = StringProperty( 329 | default = "_NOR" 330 | ) 331 | Alfa_Suffix = StringProperty( 332 | default = "_ALFA" 333 | ) 334 | Base_Name = StringProperty( 335 | default = "bake" 336 | ) 337 | filedir = StringProperty( 338 | name="", 339 | description="choose a directory", 340 | default="", 341 | subtype='DIR_PATH' 342 | ) 343 | xresolution = IntProperty( 344 | name = "x", 345 | description="", 346 | default = 512, 347 | min = 2, 348 | max = 16384 349 | ) 350 | yresolution = IntProperty( 351 | name = "y", 352 | description="", 353 | default = 512, 354 | min = 2, 355 | max = 16384 356 | ) 357 | Presets = EnumProperty( 358 | name = 'Presets', 359 | items = [('512', '512 x 512', ""), 360 | ('1024', '1024 x 1024', ""), 361 | ('2048', '2048 x 2048', ""), 362 | ('4096', '4096 x 4096', ""), 363 | ('8192', '8192 x 8192', ""), 364 | ], 365 | update = cr 366 | ) 367 | FileType = EnumProperty( 368 | name = 'FileType', 369 | items = [('0', 'PNG', ""), 370 | ('9', 'JPG', ""), 371 | ('8', 'Iris', ""), 372 | ('7', 'BMP', ""), 373 | ('6', 'JPEG 2000', ""), 374 | ('5', 'Targa', ""), 375 | ('4', 'Cineon', ""), 376 | ('3', 'DPX', ""), 377 | ('2', 'OpenEXR', ""), 378 | ('1', 'Tiff', "") 379 | ] 380 | ) 381 | 382 | class LayoutDemoPanel(bpy.types.Panel): 383 | """Creates a Panel in the scene context of the properties editor""" 384 | bl_label = 'PBR Texture Multi Baker' 385 | bl_idname = "SCENE_PT_layout" 386 | bl_space_type = 'PROPERTIES' 387 | bl_region_type= 'WINDOW' 388 | bl_context = 'render' 389 | def draw(self, context): 390 | layout = self.layout 391 | 392 | scene = context.scene 393 | rd = scene.render 394 | split = layout.split() 395 | 396 | # Resolution selection. 397 | 398 | layout.label(text = "Resolution :") 399 | row = layout.row() 400 | 401 | row.prop(scene.my_tool, "Presets", text="") 402 | 403 | row = layout.row() 404 | row = layout.row() 405 | row = layout.row() 406 | row.prop(scene.my_tool, "Custom", text = "Custom") 407 | 408 | if (bpy.context.scene.my_tool.Custom): 409 | row = layout.row() 410 | row = layout.row() 411 | row = layout.row() 412 | row.prop(scene.my_tool, "xresolution", text="X custom") 413 | row.prop(scene.my_tool, "yresolution", text="Y custom") 414 | row = layout.row() 415 | row = layout.row() 416 | 417 | # Bake directory. 418 | 419 | split = layout.split() 420 | col = split.column() 421 | col.label(text = "Bake dir:") 422 | col = split.column() 423 | col.prop(scene.my_tool, "filedir", text="") 424 | row = layout.row() 425 | 426 | # Base name. 427 | 428 | split = layout.split() 429 | col = split.column() 430 | col.label(text = "Base name:") 431 | col = split.column() 432 | col.prop(scene.my_tool, "Base_Name", text = "") 433 | row = layout.row() 434 | 435 | # File type. 436 | 437 | split = layout.split() 438 | col = split.column() 439 | col.label(text = "File type:") 440 | col = split.column() 441 | col.prop(scene.my_tool, "FileType", text="", icon = "IMAGE_DATA") 442 | row = layout.row() 443 | 444 | # Color Depth. 445 | 446 | split = layout.split() 447 | col = split.column() 448 | col.label(text = "Color depth:") 449 | col = split.column() 450 | col = split.column() 451 | col.prop(scene.my_tool, "cd8", text = "8 bit") 452 | col = split.column() 453 | col.prop(scene.my_tool, "cd16", text = "16 bit") 454 | row = layout.row() 455 | row = layout.row() 456 | 457 | # UV selection. 458 | 459 | split = layout.split() 460 | me = context.mesh 461 | col = split.column() 462 | col.label(text = "UV selection:") 463 | col = split.column() 464 | col.prop(scene.my_tool, "UV_selection", text="") 465 | row = layout.row() 466 | 467 | # Texture selection. 468 | 469 | split = layout.split() 470 | col = split.column() 471 | col.label(text="Texture:") 472 | col.prop(scene.my_tool, "Base_color", text = "Base color") 473 | col.prop(scene.my_tool, "Metallic", text = "Metallic") 474 | col.prop(scene.my_tool, "Specular", text = "Specular") 475 | col.prop(scene.my_tool, "Roughness", text = "Roughness") 476 | col.prop(scene.my_tool, "Emissive", text = "Emissive") 477 | col.prop(scene.my_tool, "Alfa", text = "Opacity") 478 | col.prop(scene.my_tool, "Normal", text = "Normal") 479 | 480 | # Suffix selection. 481 | 482 | col = split.column() 483 | col.label(text="Suffix:") 484 | col.prop(scene.my_tool, "Base_Suffix", text = "") 485 | col.prop(scene.my_tool, "Metal_Suffix", text = "") 486 | col.prop(scene.my_tool, "Spec_Suffix", text = "") 487 | col.prop(scene.my_tool, "Rough_Suffix", text = "") 488 | col.prop(scene.my_tool, "Emit_Suffix", text = "") 489 | col.prop(scene.my_tool, "Alfa_Suffix", text = "") 490 | col.prop(scene.my_tool, "Norm_Suffix", text = "") 491 | 492 | # Bake button. 493 | 494 | row = layout.row() 495 | row = layout.row() 496 | row = layout.row() 497 | row.operator("object.simple_operator",text = "Multi Bake", icon='RENDER_STILL') 498 | 499 | 500 | def register(): 501 | bpy.utils.register_class(LayoutDemoPanel) 502 | bpy.utils.register_class(SimpleOperator) 503 | bpy.utils.register_module(__name__) 504 | bpy.types.Scene.my_tool = PointerProperty(type=MySettings) 505 | 506 | 507 | 508 | def unregister(): 509 | bpy.utils.unregister_class(LayoutDemoPanel) 510 | bpy.utils.unregister_class(SimpleOperator) 511 | bpy.utils.unregister_module(__name__) 512 | del bpy.types.Scene.my_tool 513 | 514 | if __name__ == "__main__": 515 | register() 516 | --------------------------------------------------------------------------------