├── BRM_bake_instructions.png ├── EasyBake.py └── README.md /BRM_bake_instructions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leukbaars/EasyBake/466576f814cfc44ccfec13ab3dee158d7bc0d109/BRM_bake_instructions.png -------------------------------------------------------------------------------- /EasyBake.py: -------------------------------------------------------------------------------- 1 | #this script is dedicated to the public domain under CC0 (https://creativecommons.org/publicdomain/zero/1.0/) 2 | #do whatever you want with it! -Bram 3 | 4 | bl_info = { 5 | "name": "EasyBake", 6 | "category": "3D View", 7 | "blender": (2, 80, 0), 8 | "author": "Bram Eulaers", 9 | "description": "Simple texture baking UI for fast iteration. Can be found in the 3D View Sidebar under 'bake'." 10 | } 11 | 12 | import bpy 13 | import os 14 | import bmesh 15 | from bpy.props import EnumProperty, BoolProperty, StringProperty, FloatProperty, IntProperty 16 | 17 | 18 | 19 | def unhide(objectType): 20 | if objectType is None: 21 | for o in objectType.objects: 22 | o.hide_viewport = False 23 | else: 24 | objectType.hide_viewport = False 25 | 26 | def hide(objectType): 27 | if objectType is None: 28 | for o in objectType.objects: 29 | o.hide_viewport = True 30 | else: 31 | objectType.hide_viewport = True 32 | 33 | 34 | class EasyBakeUIPanel(bpy.types.Panel): 35 | """EasyBakeUIPanel Panel""" 36 | bl_label = "EasyBake" 37 | bl_space_type = 'VIEW_3D' 38 | bl_region_type = "UI" 39 | bl_category = "EasyBake" 40 | 41 | 42 | def draw_header(self, _): 43 | layout = self.layout 44 | layout.label(text="", icon='SCENE') 45 | 46 | def draw(self, context): 47 | layout = self.layout 48 | 49 | box = layout.box() 50 | col = box.column(align=True) 51 | 52 | row = col.row(align = True) 53 | row.prop(context.scene, "lowpolyGroup", text="", icon="GROUP") 54 | if context.scene.lowpolyGroup is True: 55 | row.prop_search(context.scene, "lowpoly", bpy.data, "collections", text="", icon="MESH_ICOSPHERE") 56 | if context.scene.lowpolyGroup is False: 57 | row.prop_search(context.scene, "lowpoly", context.scene, "objects", text="", icon="MESH_ICOSPHERE") 58 | 59 | 60 | 61 | 62 | 63 | if context.scene.lowpolyActive is True: 64 | hideicon = "HIDE_OFF" 65 | if context.scene.lowpolyActive is False: 66 | hideicon = "HIDE_ON" 67 | op = row.operator("brm.bakeuihide", text="", icon=hideicon) 68 | op.targetmesh = "lowpoly" 69 | 70 | row = col.row(align = True) 71 | 72 | row.prop(context.scene, "hipolyGroup", text="", icon="GROUP") 73 | if context.scene.hipolyGroup is True: 74 | row.prop_search(context.scene, "hipoly", bpy.data, "collections", text="", icon="MESH_UVSPHERE") 75 | if context.scene.hipolyGroup is False: 76 | row.prop_search(context.scene, "hipoly", context.scene, "objects", text="", icon="MESH_UVSPHERE") 77 | 78 | row.enabled = not context.scene.UseLowOnly 79 | 80 | 81 | 82 | if context.scene.hipolyActive is True: 83 | hideicon = "HIDE_OFF" 84 | if context.scene.hipolyActive is False: 85 | hideicon = "HIDE_ON" 86 | op = row.operator("brm.bakeuihide", text="", icon=hideicon) 87 | op.targetmesh = "hipoly" 88 | 89 | 90 | 91 | col = box.column(align=True) 92 | row = col.row(align = True) 93 | row.operator("brm.bakeuitoggle", text="Toggle hi/low", icon="FILE_REFRESH") 94 | #row.prop(context.scene, "UseBlenderGame", icon="MESH_UVSPHERE", text="") 95 | 96 | col = layout.column(align=True) 97 | 98 | col.separator() 99 | row = col.row(align = True) 100 | row.prop(context.scene.render.bake, "cage_extrusion", text="Ray Distance") 101 | #row.prop(context.scene, "cageEnabled", icon="OBJECT_DATAMODE", text="") 102 | row = col.row(align = True) 103 | #row.enabled = context.scene.cageEnabled 104 | 105 | #if context.scene.cageEnabled: 106 | # op = row.prop_search(context.scene, "cage", bpy.data, "objects", text="", icon="MESH_UVSPHERE") 107 | #op.enabled = context.scene.cageEnabled 108 | 109 | col.separator() 110 | 111 | box = layout.box() 112 | col = box.column(align=True) 113 | 114 | row = col.row(align = True) 115 | row.label(text="Width:") 116 | row.operator("brm.bakeuiincrement", text="", icon="REMOVE").target = "width/2" 117 | row.prop(context.scene, "bakeWidth", text="") 118 | row.operator("brm.bakeuiincrement", text="", icon="ADD").target = "width*2" 119 | 120 | row = col.row(align = True) 121 | row.label(text="Height:") 122 | row.operator("brm.bakeuiincrement", text="", icon="REMOVE").target = "height/2" 123 | row.prop(context.scene, "bakeHeight", text="") 124 | row.operator("brm.bakeuiincrement", text="", icon="ADD").target = "height*2" 125 | row = col.row(align = True) 126 | row.label(text="Padding:") 127 | row.prop(context.scene.render.bake, "margin", text="") 128 | 129 | 130 | 131 | col = layout.column(align=True) 132 | col.separator() 133 | col.prop(context.scene, 'bakeFolder', text="") 134 | row = col.row(align = True) 135 | row.label(text="Filename:") 136 | row.prop(context.scene, "bakePrefix", text="") 137 | 138 | col.separator() 139 | 140 | box = layout.box() 141 | col = box.column(align=True) 142 | 143 | row = col.row(align = True) 144 | #row.enabled = not context.scene.UseLowOnly 145 | 146 | if not context.scene.bakeNormal: 147 | row.prop(context.scene, "bakeNormal", icon="SHADING_RENDERED", text="Tangent Normal") 148 | if context.scene.bakeNormal: 149 | row.prop(context.scene, "bakeNormal", icon="SHADING_RENDERED", text=" ") 150 | row.prop(context.scene, "affixNormal", text="") 151 | row.prop(context.scene, "samplesNormal", text="") 152 | 153 | row = col.row(align = True) 154 | #row.enabled = not context.scene.UseLowOnly 155 | if not context.scene.bakeObject: 156 | row.prop(context.scene, "bakeObject", icon="SHADING_RENDERED", text="Object Normal") 157 | if context.scene.bakeObject: 158 | row.prop(context.scene, "bakeObject", icon="SHADING_RENDERED", text=" ") 159 | row.prop(context.scene, "affixObject", text="") 160 | row.prop(context.scene, "samplesObject", text="") 161 | 162 | row = col.row(align = True) 163 | if not context.scene.bakeAO: 164 | row.prop(context.scene, "bakeAO", icon="SHADING_SOLID", text="Occlusion") 165 | if context.scene.bakeAO: 166 | row.prop(context.scene, "bakeAO", icon="SHADING_SOLID", text=" ") 167 | row.prop(context.scene, "affixAO", text="") 168 | row.prop(context.scene, "samplesAO", text="") 169 | 170 | row = col.row(align = True) 171 | #row.enabled = not context.scene.UseLowOnly 172 | if not context.scene.bakeColor: 173 | row.prop(context.scene, "bakeColor", icon="SHADING_TEXTURE", text="Color") 174 | if context.scene.bakeColor: 175 | row.prop(context.scene, "bakeColor", icon="SHADING_TEXTURE", text=" ") 176 | row.prop(context.scene, "affixColor", text="") 177 | row.prop(context.scene, "samplesColor", text="") 178 | 179 | row = col.row(align = True) 180 | #row.enabled = not context.scene.UseLowOnly 181 | if not context.scene.bakeRoughness: 182 | row.prop(context.scene, "bakeRoughness", icon="SHADING_TEXTURE", text="Roughness") 183 | if context.scene.bakeRoughness: 184 | row.prop(context.scene, "bakeRoughness", icon="SHADING_TEXTURE", text=" ") 185 | row.prop(context.scene, "affixRoughness", text="") 186 | row.prop(context.scene, "samplesRoughness", text="") 187 | 188 | row = col.row(align = True) 189 | #row.enabled = not context.scene.UseLowOnly 190 | if not context.scene.bakeEmission: 191 | row.prop(context.scene, "bakeEmission", icon="SHADING_TEXTURE", text="Emission") 192 | if context.scene.bakeEmission: 193 | row.prop(context.scene, "bakeEmission", icon="SHADING_TEXTURE", text=" ") 194 | row.prop(context.scene, "affixEmission", text="") 195 | row.prop(context.scene, "samplesEmission", text="") 196 | row.prop(context.scene, "bakeEmissionLinear", icon="NODE_TEXTURE", text="") 197 | 198 | row = col.row(align = True) 199 | if not context.scene.bakeUV: 200 | row.prop(context.scene, "bakeUV", icon="SHADING_WIRE", text="UV Snapshot") 201 | if context.scene.bakeUV: 202 | row.prop(context.scene, "bakeUV", icon="SHADING_WIRE", text=" ") 203 | row.prop(context.scene, "affixUV", text="") 204 | row.prop(context.scene, "bakeUV", icon="BLANK1", text=" ") 205 | 206 | 207 | col = layout.column(align=True) 208 | col.separator() 209 | row = col.row(align = True) 210 | op = row.operator("brm.bake", text="BAKE", icon="RENDER_RESULT") 211 | row.prop(context.scene, "UseLowOnly", icon="MESH_ICOSPHERE", text="") 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | class EasyBakeUIToggle(bpy.types.Operator): 225 | """toggle lowpoly/hipoly""" 226 | bl_idname = "brm.bakeuitoggle" 227 | bl_label = "Toggle" 228 | bl_options = {"UNDO"} 229 | 230 | def execute(self, context): 231 | 232 | if bpy.context.object.mode == 'EDIT': 233 | bpy.ops.object.mode_set(mode='OBJECT') 234 | 235 | #test lowpoly/hipoly exists 236 | 237 | if context.scene.lowpoly is None and not context.scene.lowpoly in bpy.data.collections: 238 | self.report({'WARNING'}, "Select a valid lowpoly object or group!") 239 | return {'FINISHED'} 240 | if context.scene.hipoly is None and not context.scene.hipoly in bpy.data.collections: 241 | self.report({'WARNING'}, "Select a valid hipoly object or group!") 242 | return {'FINISHED'} 243 | 244 | if context.scene.lowpolyActive is True: 245 | context.scene.lowpolyActive = False 246 | hide(context.scene.lowpoly) 247 | context.scene.hipolyActive = True 248 | unhide(context.scene.hipoly) 249 | else: 250 | context.scene.lowpolyActive = True 251 | unhide(context.scene.lowpoly) 252 | context.scene.hipolyActive = False 253 | hide(context.scene.hipoly) 254 | 255 | return {'FINISHED'} 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | class EasyBakeUIIncrement(bpy.types.Operator): 264 | """multiply/divide value""" 265 | bl_idname = "brm.bakeuiincrement" 266 | bl_label = "increment" 267 | 268 | target : bpy.props.StringProperty() 269 | 270 | def execute(self, context): 271 | if self.target == "width/2" and context.scene.bakeWidth > 4: 272 | context.scene.bakeWidth = context.scene.bakeWidth // 2 273 | if self.target == "width*2": 274 | context.scene.bakeWidth = context.scene.bakeWidth * 2 275 | if self.target == "height/2" and context.scene.bakeHeight > 4: 276 | context.scene.bakeHeight = context.scene.bakeHeight // 2 277 | if self.target == "height*2": 278 | context.scene.bakeHeight = context.scene.bakeHeight * 2 279 | return {'FINISHED'} 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | class EasyBakeUIHide(bpy.types.Operator): 293 | """hide object""" 294 | bl_idname = "brm.bakeuihide" 295 | bl_label = "hide" 296 | bl_options = {"UNDO"} 297 | 298 | targetmesh : bpy.props.StringProperty() 299 | 300 | def execute(self, context): 301 | 302 | 303 | #test if collection: 304 | if context.scene.hipoly.bl_rna.name == "Collection": 305 | print("i am a collection!") 306 | 307 | if context.scene.hipoly.bl_rna.name == "Object": 308 | print("i am an object!") 309 | #print(context.scene.hipoly) 310 | #print(context.scene.lowpoly) 311 | 312 | #test lowpoly/hipoly exists 313 | if bpy.context.object.mode == 'EDIT': 314 | bpy.ops.object.mode_set(mode='OBJECT') 315 | 316 | 317 | if self.targetmesh == "lowpoly": 318 | 319 | if context.scene.lowpoly is None and not context.scene.lowpoly in bpy.data.collections: 320 | self.report({'WARNING'}, "Select a valid lowpoly object or collection!") 321 | return {'FINISHED'} 322 | 323 | else: 324 | if context.scene.lowpolyActive is True: 325 | context.scene.lowpolyActive = False 326 | hide(context.scene.lowpoly) 327 | else: 328 | context.scene.lowpolyActive = True 329 | unhide(context.scene.lowpoly) 330 | 331 | if self.targetmesh == "hipoly": 332 | 333 | if context.scene.hipoly is None and not context.scene.hipoly in bpy.data.collections: 334 | self.report({'WARNING'}, "Select a valid hipoly object or collection!") 335 | return {'FINISHED'} 336 | 337 | else: 338 | if context.scene.hipolyActive is True: 339 | context.scene.hipolyActive = False 340 | hide(context.scene.hipoly) 341 | else: 342 | context.scene.hipolyActive = True 343 | unhide(context.scene.hipoly) 344 | 345 | return {'FINISHED'} 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | class EasyBake(bpy.types.Operator): 360 | """Bake and save textures""" 361 | bl_idname = "brm.bake" 362 | bl_label = "set normal" 363 | bl_options = {"UNDO"} 364 | 365 | 366 | def execute(self, context): 367 | 368 | #test if everything is set up OK first: 369 | #test folder 370 | hasfolder = os.access(context.scene.bakeFolder, os.W_OK) 371 | if hasfolder is False: 372 | self.report({'WARNING'}, "Select a valid export folder!") 373 | return {'FINISHED'} 374 | 375 | 376 | #test lowpoly/hipoly/cage exists 377 | 378 | if context.scene.lowpoly is None and not context.scene.lowpoly in bpy.data.collections: 379 | self.report({'WARNING'}, "Select a valid lowpoly object or collection!") 380 | return {'FINISHED'} 381 | 382 | if context.scene.hipoly is None and not context.scene.hipoly in bpy.data.collections and not context.scene.UseLowOnly: 383 | self.report({'WARNING'}, "Select a valid hipoly object or collection!") 384 | return {'FINISHED'} 385 | 386 | #if bpy.data.objects.get(context.scene.cage) is None and context.scene.cageEnabled: 387 | # self.report({'WARNING'}, "Select a valid cage object!") 388 | # return {'FINISHED'} 389 | 390 | 391 | #test if lowpoly, highpoly and cage objects are actually models 392 | lowpolymeshes = 0 393 | 394 | if context.scene.lowpolyGroup == True: 395 | for o in bpy.context.scene.lowpoly.all_objects: 396 | if o.type == 'MESH': 397 | lowpolymeshes+=1 398 | else: 399 | if context.scene.lowpoly.type == 'MESH': 400 | lowpolymeshes = 1 401 | if lowpolymeshes == 0: 402 | self.report({'WARNING'}, "lowpoly needs to have a mesh!") 403 | return {'FINISHED'} 404 | 405 | 406 | 407 | if not context.scene.UseLowOnly: 408 | hipolymeshes = 0 409 | if context.scene.hipolyGroup == True: 410 | for o in bpy.context.scene.hipoly.all_objects: 411 | if o.type == 'MESH': 412 | hipolymeshes+=1 413 | else: 414 | if context.scene.hipoly.type == 'MESH': 415 | hipolymeshes = 1 416 | if hipolymeshes == 0: 417 | self.report({'WARNING'}, "hipoly needs to have a mesh!") 418 | return {'FINISHED'} 419 | 420 | if context.scene.cageEnabled and bpy.data.objects[context.scene.cage].type != 'MESH': 421 | self.report({'WARNING'}, "cage needs to be a mesh!") 422 | return {'FINISHED'} 423 | 424 | 425 | #setup 426 | 427 | #HOTFIX get out of local view 428 | if context.space_data.local_view: 429 | bpy.ops.view3d.localview() 430 | 431 | #1 unhide everything to be baked 432 | if not context.scene.UseLowOnly: 433 | unhide(context.scene.hipoly) 434 | unhide(context.scene.lowpoly) 435 | bpy.ops.object.hide_view_clear() #temporary until I figure out how hiding is actually handled 436 | 437 | 438 | 439 | #2 make sure we are in object mode and nothing is selected 440 | if bpy.context.object.mode == 'EDIT': 441 | bpy.ops.object.mode_set(mode='OBJECT') 442 | bpy.ops.object.select_all(action='DESELECT') 443 | 444 | 445 | 446 | #3 setup lowpoly for baking 447 | lowpolyobject = "null" 448 | orig_lowpoly = None 449 | 450 | #print("still working") 451 | #return {'FINISHED'} 452 | 453 | #if collection, create temporary lowpoly object 454 | if context.scene.lowpolyGroup == True: 455 | 456 | #print("still working") 457 | #return {'FINISHED'} 458 | context.scene.lowpoly.hide_render = False 459 | 460 | #collections crash fix 461 | low_objects_names = [obj.name for obj in bpy.context.scene.lowpoly.all_objects] 462 | for o in low_objects_names: 463 | 464 | #for o in bpy.context.scene.lowpoly.all_objects: 465 | #if context.scene.lowpoly is None: 466 | # context.scene.lowpoly.hide_render = False 467 | # for o in context.scene.lowpoly.objects: 468 | 469 | 470 | if bpy.data.objects[o].type == 'MESH': 471 | #print("its a mesh") 472 | #return {'FINISHED'} 473 | 474 | bpy.data.objects[o].hide_viewport = False 475 | 476 | bpy.data.objects[o].select_set(state=True) 477 | 478 | context.view_layer.objects.active = bpy.data.objects[o] 479 | 480 | bpy.data.objects[o].hide_render = True 481 | 482 | #print(bpy.context.scene.lowpoly.all_objects) 483 | #print("still working") 484 | 485 | #return {'FINISHED'} 486 | 487 | #duplicate selected and combine into new object 488 | bpy.ops.object.duplicate() 489 | bpy.ops.object.join() 490 | bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) 491 | lowpolyobject = bpy.context.selected_objects[0].name 492 | bpy.data.objects[lowpolyobject].hide_render = False 493 | else: 494 | bpy.ops.object.select_all(action='DESELECT') 495 | 496 | #context.scene.lowpoly.select_set(state=True) 497 | context.scene.lowpoly.hide_viewport = False 498 | context.scene.lowpoly.hide_render = False 499 | context.scene.lowpoly.select_set(state=True) 500 | 501 | orig_lowpoly = context.scene.lowpoly 502 | lowpolyobject = context.scene.lowpoly 503 | 504 | #print("still working") 505 | #return {'FINISHED'} 506 | 507 | # if bake to self, create a temporary lowpoly duplicate to bake self to: 508 | if context.scene.UseLowOnly: 509 | bpy.ops.object.duplicate() 510 | bpy.context.active_object.name = "temp_hipoly" 511 | context.scene.hipoly = bpy.context.active_object 512 | #return {'FINISHED'} 513 | 514 | # test if cage has same tri count: 515 | if context.scene.cageEnabled: 516 | vcount_low = len(bpy.data.objects[lowpolyobject].data.vertices) 517 | vcount_cage = len(bpy.data.objects[context.scene.cage].data.vertices) 518 | if vcount_low != vcount_cage: 519 | if context.scene.lowpolyGroup: 520 | bpy.ops.object.select_all(action='DESELECT') 521 | bpy.data.objects[lowpolyobject].select_set(state=True) 522 | bpy.ops.object.delete(use_global=False) 523 | self.report({'WARNING'}, "cage and low poly vertex count don't match!") 524 | return {'FINISHED'} 525 | 526 | #4 test if lowpoly has a material and UV 527 | #if len(context.scene.lowpoly.data.materials) == 0: 528 | # if context.scene.lowpolyGroup: 529 | # bpy.ops.object.select_all(action='DESELECT') 530 | # bpy.data.objects[lowpolyobject].select_set(state=True) 531 | # bpy.ops.object.delete(use_global=False) 532 | # self.report({'WARNING'}, "Material required on low poly mesh!") 533 | # return {'FINISHED'} 534 | 535 | #if len(context.scene.lowpoly.data.uv_layers) == 0: 536 | # if context.scene.lowpolyGroup: 537 | # bpy.ops.object.select_all(action='DESELECT') 538 | # bpy.data.objects[lowpolyobject].select_set(state=True) 539 | # bpy.ops.object.delete(use_global=False) 540 | # self.report({'WARNING'}, "low poly mesh has no UV!") 541 | # return {'FINISHED'} 542 | 543 | #5 remember render engine and switch to CYCLES for baking 544 | orig_renderer = bpy.data.scenes[bpy.context.scene.name].render.engine 545 | bpy.data.scenes[bpy.context.scene.name].render.engine = "CYCLES" 546 | 547 | #6 create temporary bake image and material 548 | bakeimage = bpy.data.images.new("BakeImage", width=context.scene.bakeWidth, height=context.scene.bakeHeight) 549 | bakemat = bpy.data.materials.new(name="bakemat") 550 | bakemat.use_nodes = True 551 | 552 | #7 select hipoly target 553 | #if not context.scene.UseLowOnly: 554 | #select hipoly object or collection: 555 | if context.scene.hipoly.bl_rna.name == "Collection": 556 | context.scene.hipoly.hide_render = False 557 | for o in bpy.context.scene.hipoly.all_objects: 558 | if o.type == 'MESH': 559 | o.hide_viewport = False 560 | o.hide_render = False 561 | o.select_set(state=True) 562 | else: 563 | context.scene.hipoly.hide_viewport = False 564 | context.scene.hipoly.hide_render = False 565 | 566 | context.scene.hipoly.select_set(state=True) 567 | 568 | #8 select lowpoly target 569 | print("whats happening here?") 570 | print(context.scene.lowpoly) 571 | print(lowpolyobject) 572 | #bpy.context.view_layer.objects.active = bpy.data.objects[lowpolyobject] 573 | #print(bpy.data.objects[lowpolyobject]) 574 | if context.scene.lowpolyGroup == True: 575 | bpy.context.view_layer.objects.active = bpy.data.objects[lowpolyobject] 576 | else: 577 | bpy.context.view_layer.objects.active = lowpolyobject 578 | 579 | #9 select lowpoly material and create temporary render target 580 | orig_mat = bpy.context.active_object.data.materials[0] 581 | bpy.context.active_object.data.materials[0] = bakemat 582 | node_tree = bakemat.node_tree 583 | node = node_tree.nodes.new("ShaderNodeTexImage") 584 | node.select = True 585 | node_tree.nodes.active = node 586 | node.image = bakeimage 587 | 588 | #10 check if theres a cage to be used 589 | if context.scene.cageEnabled: 590 | bpy.context.scene.render.bake.use_cage = True 591 | bpy.context.scene.render.bake.cage_object = bpy.data.objects[context.scene.cage] 592 | else: 593 | bpy.context.scene.render.bake.use_cage = False 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | #11 bake all maps! 603 | if context.scene.bakeNormal: 604 | 605 | bpy.context.scene.cycles.samples = context.scene.samplesNormal 606 | bpy.ops.object.bake(type='NORMAL', use_clear=True, use_selected_to_active=True, normal_space='TANGENT') 607 | bakeimage.filepath_raw = context.scene.bakeFolder+context.scene.bakePrefix+context.scene.affixNormal+".tga" 608 | bakeimage.file_format = 'TARGA' 609 | bakeimage.save() 610 | 611 | if context.scene.bakeObject: 612 | 613 | bpy.context.scene.cycles.samples = context.scene.samplesObject 614 | bpy.ops.object.bake(type='NORMAL', use_clear=True, use_selected_to_active=True, normal_space='OBJECT') 615 | bakeimage.filepath_raw = context.scene.bakeFolder+context.scene.bakePrefix+context.scene.affixObject+".tga" 616 | bakeimage.file_format = 'TARGA' 617 | bakeimage.save() 618 | 619 | if context.scene.bakeAO: 620 | 621 | bpy.context.scene.cycles.samples = context.scene.samplesAO 622 | bpy.ops.object.bake(type='AO', use_clear=True, use_selected_to_active=True) 623 | bakeimage.filepath_raw = context.scene.bakeFolder+context.scene.bakePrefix+context.scene.affixAO+".tga" 624 | bakeimage.file_format = 'TARGA' 625 | bakeimage.save() 626 | 627 | if context.scene.bakeColor: 628 | 629 | bpy.context.scene.cycles.samples = context.scene.samplesColor 630 | bpy.context.scene.render.bake.use_pass_direct = False 631 | bpy.context.scene.render.bake.use_pass_indirect = False 632 | bpy.context.scene.render.bake.use_pass_color = True 633 | bpy.ops.object.bake(type='DIFFUSE', use_clear=True, use_selected_to_active=True) 634 | bakeimage.filepath_raw = context.scene.bakeFolder+context.scene.bakePrefix+context.scene.affixColor+".tga" 635 | bakeimage.file_format = 'TARGA' 636 | bakeimage.save() 637 | 638 | if context.scene.bakeRoughness: 639 | 640 | bpy.context.scene.cycles.samples = context.scene.samplesRoughness 641 | bpy.ops.object.bake(type='ROUGHNESS', use_clear=True, use_selected_to_active=True) 642 | bakeimage.filepath_raw = context.scene.bakeFolder+context.scene.bakePrefix+context.scene.affixRoughness+".tga" 643 | bakeimage.file_format = 'TARGA' 644 | bakeimage.save() 645 | 646 | if context.scene.bakeEmission: 647 | 648 | bpy.context.scene.cycles.samples = context.scene.samplesEmission 649 | 650 | print("colorspace") 651 | print(bakeimage.colorspace_settings.name) 652 | 653 | if context.scene.bakeEmissionLinear: 654 | bakeimage.colorspace_settings.name="Linear" 655 | 656 | bpy.ops.object.bake(type='EMIT', use_clear=True, use_selected_to_active=True) 657 | bakeimage.filepath_raw = context.scene.bakeFolder+context.scene.bakePrefix+context.scene.affixEmission+".tga" 658 | bakeimage.file_format = 'TARGA' 659 | bakeimage.save() 660 | 661 | #UV SNAPSHOT 662 | if context.scene.bakeUV: 663 | bpy.ops.object.editmode_toggle() 664 | bpy.ops.mesh.select_all(action='SELECT') 665 | bpy.ops.object.editmode_toggle() 666 | original_type = bpy.context.area.type 667 | bpy.context.area.type = "IMAGE_EDITOR" 668 | uvfilepath = context.scene.bakeFolder+context.scene.bakePrefix+context.scene.affixUV+".png" 669 | bpy.ops.uv.export_layout(filepath=uvfilepath, size=(context.scene.bakeWidth, context.scene.bakeHeight)) 670 | bpy.context.area.type = original_type 671 | 672 | 673 | 674 | #cleanup temporary objects and materials 675 | bpy.ops.object.select_all(action='DESELECT') 676 | if not context.scene.lowpolyGroup: 677 | orig_lowpoly.select_set(state=True) 678 | bpy.data.images.remove(bakeimage) 679 | bakemat.node_tree.nodes.remove(node) 680 | bpy.data.materials.remove(bakemat) 681 | bpy.context.active_object.data.materials[0] = orig_mat 682 | bpy.data.scenes[bpy.context.scene.name].render.engine = orig_renderer 683 | 684 | if context.scene.lowpolyGroup: 685 | bpy.ops.object.select_all(action='DESELECT') 686 | bpy.data.objects[lowpolyobject].select_set(state=True) 687 | bpy.ops.object.delete(use_global=False) 688 | 689 | if context.scene.UseLowOnly: 690 | #bpy.ops.object.select_all(action='DESELECT') 691 | context.scene.hipoly.select_set(state=True) 692 | #return {'FINISHED'} 693 | bpy.ops.object.delete(use_global=False) 694 | #return {'FINISHED'} 695 | 696 | #reload all textures 697 | for image in bpy.data.images: 698 | image.reload() 699 | 700 | 701 | #rehide back to original state 702 | if context.scene.lowpolyActive is True: 703 | if context.scene.lowpoly is None: 704 | for o in context.scene.lowpoly.objects: 705 | o.hide_viewport = False 706 | context.view_layer.objects.active = o 707 | else: 708 | context.scene.lowpoly.hide_viewport = False 709 | #context.view_layer.objects.active = context.scene.lowpoly 710 | 711 | 712 | else: 713 | if context.scene.lowpoly is None: 714 | for o in context.scene.lowpoly.objects: 715 | o.hide_viewport = True 716 | else: 717 | context.scene.lowpoly.hide_viewport = True 718 | 719 | if not context.scene.UseLowOnly: 720 | if context.scene.hipolyActive is True: 721 | if context.scene.hipoly is None: 722 | for o in context.scene.hipoly.objects: 723 | o.hide_viewport = False 724 | context.view_layer.objects.active = o 725 | else: 726 | context.scene.hipoly.hide_viewport = False 727 | context.view_layer.objects.active =context.scene.hipoly 728 | else: 729 | if context.scene.hipoly is None: 730 | for o in context.scene.hipoly.objects: 731 | o.hide_viewport = True 732 | else: 733 | context.scene.hipoly.hide_viewport = True 734 | 735 | return {'FINISHED'} 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | classes = ( 746 | EasyBake, 747 | EasyBakeUIHide, 748 | EasyBakeUIPanel, 749 | EasyBakeUIToggle, 750 | EasyBakeUIIncrement, 751 | ) 752 | 753 | def register(): 754 | for cls in classes: 755 | bpy.utils.register_class(cls) 756 | 757 | bpy.types.Scene.lowpoly = bpy.props.PointerProperty (name = "lowpoly", type=bpy.types.Object, description = "lowpoly object") 758 | bpy.types.Scene.lowpolyActive = bpy.props.BoolProperty (name = "lowpolyActive", default = True, description = "lowpolyActive") 759 | bpy.types.Scene.lowpolyGroup = bpy.props.BoolProperty (name = "lowpolyGroup",default = False,description = "enable lowpoly collection") 760 | bpy.types.Scene.hipoly = bpy.props.PointerProperty (name = "hipoly",type=bpy.types.Object,description = "hipoly object or group") 761 | bpy.types.Scene.hipolyActive = bpy.props.BoolProperty (name = "hipolyActive",default = True,description = "hipolyActive") 762 | bpy.types.Scene.hipolyGroup = bpy.props.BoolProperty (name = "hipolyGroup",default = False,description = "enable hipoly collection") 763 | bpy.types.Scene.cage = bpy.props.StringProperty (name = "cage",default = "cage",description = "cage object") 764 | bpy.types.Scene.cageActive = bpy.props.BoolProperty (name = "cageActive",default = True,description = "cageActive") 765 | bpy.types.Scene.cageEnabled = bpy.props.BoolProperty (name = "cageEnabled",default = False,description = "Enable cage object for baking") 766 | 767 | bpy.types.Scene.bakeNormal = bpy.props.BoolProperty (name = "bakeNormal",default = False,description = "Bake Tangent Space Normal Map") 768 | bpy.types.Scene.bakeObject = bpy.props.BoolProperty (name = "bakeObject",default = False,description = "Bake Object Space Normal Map") 769 | bpy.types.Scene.bakeAO = bpy.props.BoolProperty (name = "bakeAO",default = False,description = "Bake Ambient Occlusion Map") 770 | bpy.types.Scene.bakeColor = bpy.props.BoolProperty (name = "bakeColor",default = False,description = "Bake Albedo Color Map") 771 | bpy.types.Scene.bakeRoughness = bpy.props.BoolProperty (name = "bakeRoughness",default = False,description = "Bake Roughness Map") 772 | bpy.types.Scene.bakeEmission = bpy.props.BoolProperty (name = "bakeEmission",default = False,description = "Bake Emission Map") 773 | bpy.types.Scene.bakeEmissionLinear = bpy.props.BoolProperty (name = "bakeEmissionLinear",default = False,description = "Use Linear") 774 | bpy.types.Scene.bakeUV = bpy.props.BoolProperty (name = "bakeUV",default = False,description = "Bake UV Wireframe Snapshot of Lowpoly Mesh") 775 | 776 | bpy.types.Scene.samplesNormal = bpy.props.IntProperty (name = "samplesNormal",default = 8,description = "Tangent Space Normal Map Sample Count") 777 | bpy.types.Scene.samplesObject = bpy.props.IntProperty (name = "samplesObject",default = 8,description = "Object Space Normal Map Sample Count") 778 | bpy.types.Scene.samplesAO = bpy.props.IntProperty (name = "samplesAO",default = 128,description = "Ambient Occlusion Map Sample Count") 779 | bpy.types.Scene.samplesColor = bpy.props.IntProperty (name = "samplesColor",default = 1,description = "Color Map Sample Count") 780 | bpy.types.Scene.samplesEmission = bpy.props.IntProperty (name = "samplesEmission",default = 1,description = "Emission Map Sample Count") 781 | bpy.types.Scene.samplesRoughness = bpy.props.IntProperty (name = "samplesRoughness",default = 1,description = "Roughness Map Sample Count") 782 | 783 | bpy.types.Scene.bakeWidth = bpy.props.IntProperty (name = "bakeWidth",default = 512,description = "Export Texture Width") 784 | bpy.types.Scene.bakeHeight = bpy.props.IntProperty (name = "bakeHeight",default = 512,description = "Export Texture Height") 785 | bpy.types.Scene.bakePrefix = bpy.props.StringProperty (name = "bakePrefix",default = "export",description = "export filename") 786 | bpy.types.Scene.bakeFolder = bpy.props.StringProperty (name = "bakeFolder",default = "C:\\export\\",description = "destination folder",subtype = 'DIR_PATH') 787 | bpy.types.Scene.UseLowOnly = bpy.props.BoolProperty (name = "UseLowOnly",default = False,description = "Only bake lowpoly on itself") 788 | 789 | bpy.types.Scene.affixNormal = bpy.props.StringProperty (name = "affixNormal",default = "_normal",description = "normal map affix") 790 | bpy.types.Scene.affixObject = bpy.props.StringProperty (name = "affixObject",default = "_object",description = "object normal map affix") 791 | bpy.types.Scene.affixAO = bpy.props.StringProperty (name = "affixAO",default = "_ao",description = "AO map affix") 792 | bpy.types.Scene.affixColor = bpy.props.StringProperty (name = "affixColor",default = "_color",description = "color map affix") 793 | bpy.types.Scene.affixRoughness = bpy.props.StringProperty (name = "affixRoughness",default = "_rough",description = "Roughness map affix") 794 | bpy.types.Scene.affixEmission = bpy.props.StringProperty (name = "affixEmission",default = "_emit",description = "Emission map affix") 795 | bpy.types.Scene.affixUV = bpy.props.StringProperty (name = "affixUV",default = "_uv",description = "UV map affix") 796 | 797 | 798 | def unregister(): 799 | for cls in reversed(classes): 800 | bpy.utils.unregister_class(cls) 801 | 802 | 803 | if __name__ == "__main__": 804 | register() 805 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyBake 2 | Texture baking UI for Blender that lives in the 3D Viewport. 3 | Small but powerful! Fast one-click baking and iteration. 4 | 5 | Gas canister asset example, all maps baked using EasyBake: 6 | ![screenshot](http://www.brameulaers.net/blender/addons/github_images/easybake_example.jpg) 7 | 8 | **instructions:** 9 | 10 | 1. put EasyBake.py in your addon folder 11 | 2. install add-on in user preferences (look for EasyBake) 12 | 3. you can find the UI under the "bake" tab in the tools UI (top right in the 3D viewport) 13 | 14 | UI functions: 15 | 16 | ![UI screenshot](http://www.brameulaers.net/blender/addons/github_images/easybake_instructions.png) 17 | 18 | **currently supports:** 19 | 20 | - tangent normals 21 | - object normals 22 | - color 23 | - roughness 24 | - occlusion 25 | - uv wireframe 26 | 27 | **future plans:** 28 | 29 | - metalness map (once this is bakeable in 2.8) 30 | - position map 31 | --------------------------------------------------------------------------------