├── README.md └── multi_object_uv_edit.py /README.md: -------------------------------------------------------------------------------- 1 | # Donate 2 | You like this addon and want to thank me. This could be a way ;-) 3 | [![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=LA44SUYK5Z2P8) 4 | 5 | # Multi-Object-UV-Editing 6 | This Blender Addon enables a quick way to create one UV Layout for multiple objects. 7 | 8 | # Installation 9 | - If you download the zip from this repository, make sure you extract the zipfile before you try to install it via blenders addon manager. Github packages every files in a subfolder, which results in a not functioning addon installation. So install the multi_object_uv_edit.py directly. 10 | 11 | # Usage 12 | - Just select multiple mesh objects and then press the "Multi Object UV Editing" Button in the Object Tools Panel. 13 | This will bring you into editmode of all selected mesh objects and lets you create one UV Layout for all objects. If no 14 | UV Map is existent, just create one. 15 | - Start editing your UV's. 16 | - Once you are done, just press tab to leave the edit mode and get back to object mode. Thats it. All your selected objects are updated with new UV's. 17 | 18 | Here is a video presentation of how the addon works: 19 | [Youtube Video](https://www.youtube.com/watch?v=vxAbh2o4XC0&feature=youtu.be) 20 | -------------------------------------------------------------------------------- /multi_object_uv_edit.py: -------------------------------------------------------------------------------- 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 | bl_info = { 20 | "name": "Multi Object UV Editing", 21 | "author": "Andreas Esau", 22 | "version": (0,9,8), 23 | "blender": (2, 7, 4), 24 | "location": "Object Tools", 25 | "description": "This Addon enables a quick way to create one UV Layout for multiple objects.", 26 | "warning": "", 27 | "wiki_url": "", 28 | "tracker_url": "", 29 | "category": "UV"} 30 | 31 | import bpy 32 | from bpy.props import IntProperty, FloatProperty 33 | 34 | def deselect_all(context): 35 | for obj in context.selected_objects: 36 | obj.select = False 37 | 38 | def get_selected_mesh_objects(context): 39 | return [obj for obj in context.selected_objects if obj.type=='MESH'] 40 | 41 | 42 | class MultiObjectUVEdit(bpy.types.Operator): 43 | """This operator gives you the ability to edit the uv of multiple objects at once.""" 44 | bl_idname = "object.multi_object_uv_edit" 45 | bl_label = "Multi object UV edit" 46 | bl_options = {"REGISTER","UNDO"} 47 | 48 | multi_object = None 49 | initial_objects = [] 50 | initial_objects_hide_render = [] 51 | active_object = None 52 | 53 | def leave_editing_mode(self,context): 54 | mesh_select_mode = list(context.tool_settings.mesh_select_mode) 55 | context.tool_settings.mesh_select_mode = (True,False,False) 56 | self.multi_object.select = True 57 | context.scene.objects.active = self.multi_object 58 | 59 | ### if edit mode is left with ui interface instead of tab key go back into edit mode first 60 | if self.multi_object.mode == "OBJECT": 61 | bpy.ops.object.mode_set(mode="EDIT") 62 | 63 | ### unhide all vertices 64 | bpy.ops.mesh.reveal() 65 | 66 | ### copy uvs based on the vertex groups to its final object 67 | for v_group in self.multi_object.vertex_groups: 68 | 69 | ### select object vertex group and separate mesh into its own object 70 | num_verts = self.select_vertex_group(self.multi_object,v_group.name) 71 | if num_verts > 0: 72 | bpy.ops.mesh.separate(type="SELECTED") 73 | tmp_obj = context.selected_objects[0] 74 | 75 | ### go into object mode select newely created object and transfer the uv's to its final object 76 | bpy.ops.object.mode_set(mode='OBJECT') 77 | 78 | deselect_all(context) 79 | 80 | tmp_obj.select = True 81 | context.scene.objects.active = tmp_obj 82 | original_object = bpy.data.objects[v_group.name] 83 | original_object.hide = False 84 | original_object.select = True 85 | 86 | if len(tmp_obj.data.uv_textures) > 0: 87 | if tmp_obj.data.uv_textures.active.name not in bpy.data.objects[v_group.name].data.uv_textures: 88 | new_uv_layer = bpy.data.objects[v_group.name].data.uv_textures.new(tmp_obj.data.uv_textures.active.name) 89 | original_object.data.uv_textures.active = new_uv_layer 90 | else: 91 | original_object.data.uv_textures.active = original_object.data.uv_textures[self.multi_object.data.uv_textures.active.name] 92 | 93 | bpy.ops.object.join_uvs() 94 | self.assign_tex_to_uv(tmp_obj.data.uv_textures.active,original_object.data.uv_textures.active) 95 | 96 | ### delete the tmp object 97 | original_object.select = False 98 | tmp_obj.select = False 99 | context.scene.objects.active = self.multi_object 100 | bpy.context.scene.objects.unlink(tmp_obj) 101 | bpy.data.objects.remove(tmp_obj) 102 | bpy.ops.object.mode_set(mode='EDIT') 103 | 104 | ### restore everything 105 | context.tool_settings.mesh_select_mode = mesh_select_mode 106 | bpy.ops.object.mode_set(mode="OBJECT") 107 | bpy.context.scene.objects.unlink(self.multi_object) 108 | bpy.data.objects.remove(self.multi_object) 109 | for i,object in enumerate(self.initial_objects): 110 | object.select = True 111 | object.hide_render = self.initial_objects_hide_render[i] 112 | 113 | context.scene.objects.active = self.active_object 114 | bpy.ops.ed.undo_push(message="Multi UV edit") 115 | 116 | def assign_tex_to_uv(self,src_uv,dst_uv): 117 | if len(src_uv.data) == len(dst_uv.data): 118 | for i,data in enumerate(src_uv.data): 119 | image = data.image 120 | dst_uv.data[i].image = image 121 | else: 122 | self.report({'INFO'}, "Mesh has been edited. Modifying UVS is not possible for edited meshes.") 123 | 124 | def select_vertex_group(self,ob,group_name): 125 | bpy.ops.object.mode_set(mode='EDIT') 126 | bpy.ops.mesh.select_all(action='DESELECT') 127 | bpy.ops.object.mode_set(mode='OBJECT') 128 | 129 | num_selected_verts = 0 130 | for i,vert in enumerate(ob.data.vertices): 131 | try: 132 | ob.vertex_groups[group_name].weight(i) 133 | vert.select = True 134 | num_selected_verts += 1 135 | except: 136 | pass 137 | bpy.ops.object.mode_set(mode='EDIT') 138 | return num_selected_verts 139 | 140 | def merge_selected_objects(self,context): 141 | objects = list(context.selected_objects) 142 | dupli_objects = [] 143 | ### deselect objects 144 | for ob in objects: 145 | ob.select = False 146 | 147 | for i,ob in enumerate(objects): 148 | if ob.type == 'MESH': 149 | dupli_ob = ob.copy() 150 | context.scene.objects.link(dupli_ob) 151 | dupli_me = dupli_ob.data.copy() 152 | dupli_ob.data = dupli_me 153 | 154 | dupli_objects.append(dupli_ob) 155 | for group in dupli_ob.vertex_groups: 156 | dupli_ob.vertex_groups.remove(group) 157 | v_group = dupli_ob.vertex_groups.new(name=ob.name) 158 | v_group.add(range(len(dupli_ob.data.vertices)),1,"REPLACE") 159 | 160 | ### select all the new objects, and make the first one active, so we can do a join 161 | for ob in dupli_objects: 162 | if ob.type == 'MESH': 163 | ob.select = True 164 | self.multi_object = context.scene.objects.active = dupli_objects[0] 165 | ### copy the mesh, because we will join into that mesh 166 | self.multi_object.data = self.multi_object.data.copy() 167 | bpy.ops.object.join() 168 | self.multi_object.name = "Multi_UV_Object" 169 | 170 | 171 | def modal(self, context, event): 172 | if (event.type in ['TAB'] and not event.ctrl and not event.shift and not event.oskey) or self.multi_object.mode == "OBJECT": 173 | self.report({'INFO'}, "Multi Object UV Editing done.") 174 | self.leave_editing_mode(context) 175 | return {'CANCELLED'} 176 | return {'PASS_THROUGH'} 177 | 178 | def invoke(self, context, event): 179 | ### leave local view, prevents blender to crash when joining objects 180 | if context.area.spaces.active.local_view != None: 181 | override = bpy.context.copy() 182 | override["area"] = context.area 183 | bpy.ops.view3d.localview(override) 184 | 185 | ### reset variables 186 | self.multi_object = None 187 | context.window_manager.modal_handler_add(self) 188 | 189 | ### store active and selected objects 190 | self.initial_objects = context.selected_objects 191 | self.initial_objects_hide_render = [obj.hide_render for obj in self.initial_objects] 192 | self.active_object = context.scene.objects.active 193 | 194 | ### make merged copy of all selected objects, that we can edit 195 | self.merge_selected_objects(context) 196 | self.multi_object.hide_render = False 197 | self.multi_object.hide = False 198 | 199 | ### hide the initial objects 200 | for obj in self.initial_objects: 201 | if obj.type == 'MESH': 202 | obj.hide = True 203 | obj.hide_render = True 204 | 205 | ###switch to edit mode 206 | bpy.ops.object.mode_set(mode="EDIT") 207 | bpy.ops.mesh.select_all(action='SELECT') 208 | if len(self.multi_object.data.uv_textures) > 0: 209 | bpy.ops.uv.select_all(action='SELECT') 210 | 211 | return {'RUNNING_MODAL'} 212 | 213 | def add_object_tools(self,context): 214 | if len(get_selected_mesh_objects(context)) > 1: 215 | self.layout.operator_context = "INVOKE_DEFAULT" 216 | self.layout.separator() 217 | self.layout.label("UV Tools:") 218 | self.layout.operator("object.multi_object_uv_edit",text="Multi Object UV Editing",icon="IMAGE_RGB") 219 | 220 | def add_object_specials(self,context): 221 | if len(get_selected_mesh_objects(context)) > 1: 222 | self.layout.operator_context = "INVOKE_DEFAULT" 223 | self.layout.operator("object.multi_object_uv_edit",text="Multi Object UV Editing",icon="IMAGE_RGB") 224 | 225 | def register(): 226 | bpy.types.VIEW3D_PT_tools_object.append(add_object_tools) 227 | bpy.types.VIEW3D_MT_object_specials.append(add_object_specials) 228 | bpy.utils.register_class(MultiObjectUVEdit) 229 | 230 | 231 | def unregister(): 232 | bpy.types.VIEW3D_PT_tools_object.remove(add_object_tools) 233 | bpy.types.VIEW3D_MT_object_specials.remove(add_object_specials) 234 | bpy.utils.unregister_class(MultiObjectUVEdit) 235 | 236 | 237 | if __name__ == "__main__": 238 | register() 239 | --------------------------------------------------------------------------------