├── README.md ├── easy_lightmap.py └── shot1.png /README.md: -------------------------------------------------------------------------------- 1 | Easy Light Map 2 | ============== 3 | 4 | #### An addon for blender 2.7 to bake light map easily! #### 5 | ![panel](https://github.com/mese79/easy_lightmap/raw/master/shot1.png) 6 | 7 | For baking your object light map to use in another engine like [three.js](http://threejs.org), first you have to add two uv layers and unwrap them. Then you must decide that do you want to bake diffuse color or object texture. if no then you have to un-check texture box and reset diffuse color into pure white color... . After baking was done you may want to revert your object material back to before baking state. 8 | 9 | Well with this addon you can do all these steps just by one click! 10 | 11 | 12 | ###### Changes in v0.2 ###### 13 | - There is no need to Imagemagick anymore. 14 | - Bake process doesn't lock up UI and you can see baking progress. 15 | 16 | 17 | ###### Note: ###### 18 | Since i couldn't find any way to get when baking process has been finished normally and when it has been cancelled by user, you have to save the baked image manually. 19 | 20 | -------------------------------------------------------------------------------- /easy_lightmap.py: -------------------------------------------------------------------------------- 1 | import os 2 | import bpy 3 | 4 | from bpy.props import StringProperty, BoolProperty, IntProperty 5 | from bpy.app.handlers import persistent 6 | 7 | 8 | bl_info = { 9 | "name": "Easy Light Map", 10 | "author": "Mehdi Seifi", 11 | "version": (0, 2), 12 | "blender": (2, 7), 13 | "location": "Render Tab > Easy Light Map", 14 | "description": "Bakes light map for selected object easily!", 15 | "warning": "", 16 | "wiki_url": "https://github.com/mese79/easy_lightmap", 17 | "category": "Render" 18 | } 19 | 20 | 21 | class General(object): 22 | 23 | is_baking_started = False 24 | original_color = None 25 | textures_use = [] 26 | 27 | def __init__(self): 28 | pass 29 | 30 | 31 | class EasyLightMapProperties(bpy.types.PropertyGroup): 32 | 33 | bake_path = StringProperty( 34 | name="Bake folder:", default="", subtype="DIR_PATH", description="Path for saving baked maps.") 35 | image_w = IntProperty(name="Width", default=1024, min=1, description="Image width") 36 | image_h = IntProperty(name="Height", default=1024, min=1, description="Image height") 37 | bake_diffuse = BoolProperty(name="Bake diffuse color", default=False, description="Bake material diffuse color into map.") 38 | bake_textures = BoolProperty(name="Bake textures", default=False, description="Bake material textures into map.") 39 | 40 | 41 | class EasyLightMapPrepare(bpy.types.Operator): 42 | 43 | bl_idname = "object.easy_light_map_prepare" 44 | bl_label = "Only Prepare For Baking" 45 | bl_description = "Create two uv layers if there is not any then add an empty texture slot for baking." 46 | 47 | settings = None 48 | selected_object = None 49 | material = None 50 | 51 | @classmethod 52 | def poll(cls, context): 53 | return True 54 | 55 | def execute(self, context): 56 | self.selected_object = context.active_object 57 | if self.selected_object is None or self.selected_object.type != "MESH": 58 | self.report({"WARNING"}, "No mesh object was selected.") 59 | 60 | self.material = self.selected_object.active_material 61 | 62 | self.settings = context.scene.easyLightMap 63 | 64 | # Check/Add UVs. 65 | check_uv_layers(self.selected_object) 66 | 67 | # Add new texture slot and new image for baking. 68 | name = "Baked_" + self.material.name 69 | 70 | img_path = bpy.path.abspath(self.settings.bake_path) 71 | if not img_path: 72 | img_path = bpy.path.abspath("//") 73 | img_path = os.path.join(img_path, name + ".png") 74 | 75 | img = bpy.data.images.get(name) 76 | if img is None: 77 | img = bpy.data.images.new(name, width=self.settings.image_w, height=self.settings.image_h, alpha=True) 78 | img.file_format = "PNG" 79 | img.filepath = img_path 80 | elif img.size != [self.settings.image_w, self.settings.image_h]: 81 | img.scale(self.settings.image_w, self.settings.image_h) 82 | 83 | baked_tex = bpy.data.textures.get(name) 84 | if baked_tex is None: 85 | baked_tex = bpy.data.textures.new(name, type="IMAGE") 86 | baked_tex.image = img 87 | baked_tex.use_alpha = True 88 | 89 | baked_slot = self.material.texture_slots.get(name) 90 | if baked_slot is None: 91 | baked_slot = self.material.texture_slots.add() 92 | baked_slot.use = False 93 | baked_slot.texture = baked_tex 94 | baked_slot.texture_coords = "UV" 95 | baked_slot.uv_layer = self.selected_object.data.uv_textures.active.name 96 | baked_slot.blend_type = "MULTIPLY" 97 | 98 | # Apply image to active UV layer. 99 | uv_layer = self.selected_object.data.uv_textures.active 100 | for uv in uv_layer.data: 101 | uv.image = baked_tex.image 102 | 103 | # Done! 104 | return {"FINISHED"} 105 | 106 | 107 | class EasyLightMapBake(bpy.types.Operator): 108 | 109 | bl_idname = "object.easy_light_map_bake" 110 | bl_label = "Bake It!" 111 | bl_description = "Bake light map into new texture and add it into object material." 112 | 113 | settings = None 114 | selected_object = None 115 | material = None 116 | #original_color = None 117 | #textures_use = [] 118 | 119 | @classmethod 120 | def poll(cls, context): 121 | return True 122 | 123 | # def invoke(self, context, event): 124 | # return self.execute(context) 125 | 126 | def execute(self, context): 127 | self.selected_object = context.active_object 128 | if self.selected_object is None or self.selected_object.type != "MESH": 129 | self.report({"WARNING"}, "No mesh object was selected.") 130 | 131 | self.settings = context.scene.easyLightMap 132 | self.material = self.selected_object.active_material 133 | 134 | # Check UV layers. 135 | check_uv_layers(self.selected_object) 136 | 137 | # Add a new texture slot and a new image for baking. 138 | name = "Baked_" + self.material.name 139 | 140 | img_path = bpy.path.abspath(self.settings.bake_path) 141 | if not img_path: 142 | img_path = bpy.path.abspath("//") 143 | img_path = os.path.join(img_path, name + ".png") 144 | 145 | img = bpy.data.images.get(name) 146 | if img is None: 147 | img = bpy.data.images.new(name, width=self.settings.image_w, height=self.settings.image_h, alpha=True) 148 | img.file_format = "PNG" 149 | img.filepath = img_path 150 | elif img.size != [self.settings.image_w, self.settings.image_h]: 151 | img.scale(self.settings.image_w, self.settings.image_h) 152 | 153 | baked_tex = bpy.data.textures.get(name) 154 | if baked_tex is None: 155 | baked_tex = bpy.data.textures.new(name, type="IMAGE") 156 | baked_tex.image = img 157 | baked_tex.use_alpha = True 158 | 159 | baked_slot = self.material.texture_slots.get(name) 160 | if baked_slot is None: 161 | baked_slot = self.material.texture_slots.add() 162 | baked_slot.use = False 163 | baked_slot.texture = baked_tex 164 | baked_slot.texture_coords = "UV" 165 | baked_slot.uv_layer = self.selected_object.data.uv_textures.active.name 166 | baked_slot.blend_type = "MULTIPLY" 167 | 168 | # Set UV layer image. 169 | uv_layer = self.selected_object.data.uv_textures.active 170 | for uv in uv_layer.data: 171 | uv.image = baked_tex.image 172 | 173 | if not self.settings.bake_diffuse: 174 | # Save diffuse color 175 | General.original_color = self.material.diffuse_color.copy() 176 | # Change it to pure white. 177 | self.material.diffuse_color = [1.0, 1.0, 1.0] 178 | 179 | # Check which texture to use. 180 | General.textures_use = self.get_used_textures() 181 | 182 | # Bake it. 183 | bpy.app.handlers.scene_update_post.append(scene_update) 184 | bpy.ops.object.bake_image("INVOKE_DEFAULT") 185 | General.is_baking_started = True 186 | 187 | # Done! 188 | return {"FINISHED"} 189 | 190 | def get_used_textures(self): 191 | """ UnCheck textures if bake_textures is false. """ 192 | result = [] 193 | for slot in self.material.texture_slots: 194 | if slot is not None and slot.use: 195 | result.append(slot.use) 196 | if not self.settings.bake_textures: 197 | slot.use = False 198 | 199 | return result 200 | 201 | 202 | class EasyLightMapPanel(bpy.types.Panel): 203 | 204 | bl_idname = "RENDER_PT_easy_light_map" 205 | bl_label = "Easy Light Map" 206 | bl_region_type = "WINDOW" 207 | bl_space_type = "PROPERTIES" 208 | bl_context = "render" 209 | 210 | @classmethod 211 | def poll(cls, context): 212 | # if context.scene.render.engine == "BLENDER_RENDER": 213 | return True 214 | 215 | def draw(self, context): 216 | layout = self.layout 217 | props = context.scene.easyLightMap 218 | row = layout.row(True) 219 | row.prop(props, "bake_path") 220 | row = layout.row(True) 221 | row.prop(props, "image_w") 222 | row.prop(props, "image_h") 223 | layout.separator() 224 | row = layout.row(True) 225 | row.prop(props, "bake_diffuse") 226 | row = layout.row(True) 227 | row.prop(props, "bake_textures") 228 | layout.separator() 229 | layout.operator(EasyLightMapPrepare.bl_idname, text="Only Prepare For Baking") 230 | layout.operator(EasyLightMapBake.bl_idname, text="Bake it!") 231 | 232 | 233 | 234 | def check_uv_layers(selected_object): 235 | """ Object must have two uv sets. """ 236 | if len(selected_object.data.uv_textures) == 0: 237 | add_uv_map("Diffuse", selected_object) 238 | add_uv_map("LightMap", selected_object) 239 | elif len(selected_object.data.uv_textures) == 1: 240 | add_uv_map("LightMap", selected_object) 241 | 242 | 243 | def add_uv_map(name, selected_object): 244 | """ Add new UV Map to object and unwrap it. """ 245 | uv = selected_object.data.uv_textures.new(name) 246 | uv.active = True 247 | bpy.ops.object.mode_set(mode="EDIT") 248 | bpy.ops.mesh.select_all(action="SELECT") 249 | bpy.ops.uv.smart_project(island_margin=0.05) 250 | # Have to pass object, because context.object is None in render panel. 251 | # (in unwrap function is_editmode = (context.object.mode == 'EDIT') will need it.) 252 | # bpy.ops.uv.lightmap_pack({ 253 | # "object": selected_object, 254 | # "PREF_CONTEXT": "ALL_OBJECTS", 255 | # "PREF_PACK_IN_ONE": True, 256 | # "PREF_IMG_PX_SIZE": settings.image_w 257 | # }) 258 | bpy.ops.object.mode_set(mode="OBJECT") 259 | uv.active = True 260 | 261 | return uv 262 | 263 | 264 | @persistent 265 | def scene_update(context): 266 | # Just when baking started texture.is_updated is true, after that it'll becomes false. 267 | if bpy.context.active_object: 268 | material = bpy.context.active_object.active_material 269 | name = "Baked_" + material.name 270 | 271 | if General.is_baking_started and not bpy.data.textures[name].is_updated: 272 | # Baking is finished, so revert back the material color and texture slots use check boxes. 273 | if General.original_color is not None: 274 | material.diffuse_color = General.original_color 275 | 276 | for index in range(len(General.textures_use)): 277 | if material.texture_slots[index] is not None: 278 | material.texture_slots[index].use = General.textures_use.pop(0) 279 | 280 | # Remove handler 281 | bpy.app.handlers.scene_update_post.remove(scene_update) 282 | 283 | 284 | 285 | def register(): 286 | bpy.utils.register_class(EasyLightMapProperties) 287 | bpy.types.Scene.easyLightMap = bpy.props.PointerProperty(type=EasyLightMapProperties) 288 | bpy.utils.register_module(__name__) 289 | 290 | 291 | def unregister(): 292 | bpy.utils.unregister_module(__name__) 293 | 294 | 295 | if __name__ == "__main__": 296 | register() 297 | -------------------------------------------------------------------------------- /shot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mese79/easy_lightmap/3842faa8cde118f8990c2c57909aa8fcafe0caba/shot1.png --------------------------------------------------------------------------------