For my mesh I have two UV Maps. I'm importing the Blender file into an external program which requires the UVMaps to be in a certain order. In the UV Maps list there are no up/down arrows like there is in the Vertex Groups list to change the order of elements in the list. Is there a way to assign the order of UV Maps in Blender? Or could there be a Python script to do this?
4 Answers
Here is an add-on that does what Stefan Schindler mentioned (I can't say I didn't already know that trick, but he did mention it before I did...). I would recommend that you check it out at the GitHub page here so that you know that you have the most up-to-date version. It is also just fine to use this link if you'd prefer to copy/paste rather than download and unzip it.
Here is the original version of the code though (like I said, it is preferable to get a copy from GitHub because I won't be updating this).
# Copyright 2016 Jake Dube
#
# ### BEGIN GPL LICENSE BLOCK ###
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ### END GPL LICENSE BLOCK ###
import bpy
from bpy.types import Operator
bl_info = {
"name": "UV Tools",
"author": "Jake Dube",
"version": (1, 0),
"blender": (2, 77, 0),
"location": "UV maps in properties window",
"description": "Some tools for uv maps that should already be in Blender.",
"category": "UV"}
def make_active(name):
uvs = bpy.context.scene.objects.active.data.uv_textures
for uv in uvs:
if uv.name == name:
uvs.active = uv
return
print("Could not find:", name, "\n(this should never happen)")
def move_to_bottom(index):
uvs = bpy.context.scene.objects.active.data.uv_textures
uvs.active_index = index
new_name = uvs.active.name
bpy.ops.mesh.uv_texture_add()
# delete the "old" one
make_active(new_name)
bpy.ops.mesh.uv_texture_remove()
# set the name of the last one
uvs.active_index = len(uvs) - 1
uvs.active.name = new_name
class MoveUVMapDown(Operator):
bl_idname = "uv_tools.move_uvmap_down"
bl_label = "Move Down"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
uvs = context.scene.objects.active.data.uv_textures
# get the selected UV map
orig_ind = uvs.active_index
orig_name = uvs.active.name
if orig_ind == len(uvs) - 1:
return {'FINISHED'}
# use "trick" on the one after it
move_to_bottom(orig_ind + 1)
# use the "trick" on the UV map
move_to_bottom(orig_ind)
# use the "trick" on the rest that are after where it was
for i in range(orig_ind, len(uvs) - 2):
move_to_bottom(orig_ind)
make_active(orig_name)
return {'FINISHED'}
class MoveUVMapUp(Operator):
bl_idname = "uv_tools.move_uvmap_up"
bl_label = "Move Up"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
uvs = context.scene.objects.active.data.uv_textures
if uvs.active_index == 0:
return {'FINISHED'}
original = uvs.active.name
uvs.active_index -= 1
bpy.ops.uv_tools.move_uvmap_down()
make_active(original)
return {'FINISHED'}
def uv_tools_addition(self, context):
layout = self.layout
col = layout.column(align=True)
col.operator("uv_tools.move_uvmap_up", icon='TRIA_UP')
col.operator("uv_tools.move_uvmap_down", icon='TRIA_DOWN')
def register():
bpy.utils.register_class(MoveUVMapDown)
bpy.utils.register_class(MoveUVMapUp)
bpy.types.DATA_PT_uv_texture.append(uv_tools_addition)
def unregister():
bpy.utils.unregister_class(MoveUVMapDown)
bpy.utils.unregister_class(MoveUVMapUp)
bpy.types.DATA_PT_uv_texture.remove(uv_tools_addition)
if __name__ == "__main__":
register()
This add-on (at the time of this writing) simply adds two buttons below the uv maps list like this:
-
$\begingroup$ Just tried it, and it's working great :) $\endgroup$ Commented Nov 16, 2016 at 15:06
-
$\begingroup$ Thanks, this is very good. The ability to re-order the UVs is very useful for people who are importing the file into Unity. Some workflows require UVs in the following order: texture, baked lighting, realtime lighting. So the additional UVs can just be spare UVs for debugging. It's useful because the process of generating the light UVs involves a lot of trial-and-error to get right so it can be used by the lighting software, so this tools makes the process much easier. $\endgroup$– TTTTTTaCommented Nov 16, 2016 at 15:18
-
$\begingroup$ @ŠtefanSchindler Thanks. TTTTTTa You're welcome! Please consider accepting Stefan's or my answer. I totally understand about Unity as I have done most of my Blender work for developing game assets. I don't know why I didn't do this originally as it would have been a big help rather than the messy way of doing it manually. $\endgroup$ Commented Nov 16, 2016 at 15:30
-
$\begingroup$ So I'm pretty annoyed I needed to do this. 3 years later and no one can adjust the index of the uv maps. This seems like a must have feature when doing any sort of remapping with a game engine that looks at map index. However I'm very thankful this addon still works. Thank you! $\endgroup$ Commented Jul 7, 2019 at 18:58
-
1$\begingroup$ @NoobCat No, Blender automatically will copy the currently selected UV map when you make a new one. No need to do that explicitly when using
bpy.ops
command as I did here. I've got to ask: have you tried this? I used it for years without issues. The "algorithm" is what is shown here if you are curious: blender.stackexchange.com/a/67268/28015 $\endgroup$ Commented Jul 16, 2020 at 20:38
There is a little trick to it. When you have selected one UV Map and hit the plus button it duplicates the currently selected UV Map and puts it at the end.
So here are steps needed in order to reorder the order of UV Maps :
- Select the first UV Map.
- Click on the Plus button.
- Delete the first UV map.
- Rename the last UV Map to not contain the .001 suffix.
-
$\begingroup$ OK, thanks. That's a nifty trick. I wonder why there isn't just an up/down arrow on the GUI. One question: Why do you recommend to remove the ".001" suffix? Is there a reason? $\endgroup$– TTTTTTaCommented Nov 15, 2016 at 14:29
-
2$\begingroup$ I wonder too why there isn't up/down arrow, maybe because only few users need it. If you would leave the name as "UVMap.001" it would be a new UV map and all places where it was previously referenced as "UV Map" would be marked as the UV Map is missing. If you rename it to the same name as the original then all references are kept. $\endgroup$ Commented Nov 15, 2016 at 14:35
-
$\begingroup$ @ŠtefanSchindler I think I'll write an add-on that solves this problem :-) $\endgroup$ Commented Nov 15, 2016 at 15:12
-
$\begingroup$ @pycoder nice, good luck :) $\endgroup$ Commented Nov 15, 2016 at 15:30
-
2
Modified for 2.8
import bpy
from bpy.types import Operator
bl_info = {
"name": "UV Tools",
"author": "Jake Dube (modified by Denis Khomenko for 2.8)",
"version": (1, 0),
"blender": (2, 80, 75),
"location": "UV maps in properties window",
"description": "Some tools for uv maps that should already be in Blender.",
"category": "UV"}
def make_active(name):
uvs = bpy.context.active_object.data.uv_layers #bpy.context.scene.objects.active.data.uv_textures
for uv in uvs:
if uv.name == name:
uvs.active = uv
return
print("Could not find:", name, "\n(this should never happen)")
def move_to_bottom(index):
uvs = bpy.context.active_object.data.uv_layers #bpy.context.scene.objects.active.data.uv_textur
uvs.active_index = index
new_name = uvs.active.name
bpy.ops.mesh.uv_texture_add()
# delete the "old" one
make_active(new_name)
bpy.ops.mesh.uv_texture_remove()
# set the name of the last one
uvs.active_index = len(uvs) - 1
uvs.active.name = new_name
class MoveUVMapDown(Operator):
bl_idname = "uv_tools.move_uvmap_down"
bl_label = "Move Down"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
uvs = bpy.context.active_object.data.uv_layers #context.scene.objects.active.data.uv_textures
# get the selected UV map
orig_ind = uvs.active_index
orig_name = uvs.active.name
if orig_ind == len(uvs) - 1:
return {'FINISHED'}
# use "trick" on the one after it
move_to_bottom(orig_ind + 1)
# use the "trick" on the UV map
move_to_bottom(orig_ind)
# use the "trick" on the rest that are after where it was
for i in range(orig_ind, len(uvs) - 2):
move_to_bottom(orig_ind)
make_active(orig_name)
return {'FINISHED'}
class MoveUVMapUp(Operator):
bl_idname = "uv_tools.move_uvmap_up"
bl_label = "Move Up"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
uvs = bpy.context.active_object.data.uv_layers #context.scene.objects.active.data.uv_textures
if uvs.active_index == 0:
return {'FINISHED'}
original = uvs.active.name
uvs.active_index -= 1
bpy.ops.uv_tools.move_uvmap_down()
make_active(original)
return {'FINISHED'}
def uv_tools_addition(self, context):
layout = self.layout
col = layout.column(align=True)
col.operator("uv_tools.move_uvmap_up", icon='TRIA_UP')
col.operator("uv_tools.move_uvmap_down", icon='TRIA_DOWN')
def register():
bpy.utils.register_class(MoveUVMapDown)
bpy.utils.register_class(MoveUVMapUp)
bpy.types.DATA_PT_uv_texture.append(uv_tools_addition)
def unregister():
bpy.utils.unregister_class(MoveUVMapDown)
bpy.utils.unregister_class(MoveUVMapUp)
bpy.types.DATA_PT_uv_texture.remove(uv_tools_addition)
if __name__ == "__main__":
register()
-
1$\begingroup$ just go to the GitHub repo like I said in my answer...that's what will get updated $\endgroup$ Commented Jul 17, 2020 at 20:00
Another variation with options to sort UVs by name
# ### BEGIN GPL LICENSE BLOCK ###
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ### END GPL LICENSE BLOCK ###
import bpy
from bpy.types import Operator
bl_info = {
"name": "UV List Tools",
"author": "Jake Dube, cdeguise, bovesan, APEC",
"version": (1, 1),
"blender": (2, 80, 0),
"location": "UV maps in properties window",
"description": "Some tools for uv maps that should already be in Blender.",
"doc_url": "",
"tracker_url": "",
"category": "UV"
}
def make_active(name):
uvs = bpy.context.view_layer.objects.active.data.uv_layers
for uv in uvs:
if uv.name == name:
uvs.active = uv
return
print("Could not find:", name, "\n(this should never happen)")
def move_to_bottom(index):
uvs = bpy.context.view_layer.objects.active.data.uv_layers
uvs.active_index = index
new_name = uvs.active.name
bpy.ops.mesh.uv_texture_add()
# delete the "old" one
make_active(new_name)
bpy.ops.mesh.uv_texture_remove()
# set the name of the last one
uvs.active_index = len(uvs) - 1
uvs.active.name = new_name
class MESH_OT_uv_down(Operator):
bl_idname = "mesh.uv_texture_down"
bl_label = "Move Down"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
uvs = context.view_layer.objects.active.data.uv_layers
# get the selected UV map
orig_ind = uvs.active_index
orig_name = uvs.active.name
if orig_ind == len(uvs) - 1:
return {'FINISHED'}
# use "trick" on the one after it
move_to_bottom(orig_ind + 1)
# use the "trick" on the UV map
move_to_bottom(orig_ind)
# use the "trick" on the rest that are after where it was
for i in range(orig_ind, len(uvs) - 2):
move_to_bottom(orig_ind)
make_active(orig_name)
return {'FINISHED'}
class MESH_OT_uv_up(Operator):
bl_idname = "mesh.uv_texture_up"
bl_label = "Move Up"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
uvs = bpy.context.view_layer.objects.active.data.uv_layers
if uvs.active_index == 0:
return {'FINISHED'}
original = uvs.active.name
uvs.active_index -= 1
bpy.ops.mesh.uv_texture_down()
make_active(original)
return {'FINISHED'}
class MESH_OT_uv_a_to_z (Operator):
bl_idname = 'mesh.uv_texture_a_to_z'
bl_label = 'Sort A to Z'
bl_description = "Sorting UVs by name (A to Z)"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
uvs = context.view_layer.objects.active.data.uv_layers
orig_name = uvs.active.name
for j in range (len(uvs)):
for i in range (len(uvs)-1):
uvs.active_index = i
temp_name = uvs.active.name
uvs.active_index = i+1
if uvs.active.name < temp_name:
bpy.ops.mesh.uv_texture_up()
make_active(orig_name)
return {"FINISHED"}
class MESH_OT_uv_z_to_a (Operator):
bl_idname = 'mesh.uv_texture_z_to_a'
bl_label = 'Sort Z to A'
bl_description = "Sorting UVs by name (Z to A)"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
uvs = context.view_layer.objects.active.data.uv_layers
orig_name = uvs.active.name
for j in range (len(uvs)):
for i in range (len(uvs)-1):
uvs.active_index = i+1
temp_name = uvs.active.name
uvs.active_index = i
if uvs.active.name < temp_name:
bpy.ops.mesh.uv_texture_down()
make_active(orig_name)
return {"FINISHED"}
def uv_tools_addition(self, context):
layout = self.layout
layout.label(text="Active object UV Tools:")
row = layout.row()
col1 = row.column(align=True)
col1.operator("mesh.uv_texture_up", icon='TRIA_UP')
col1.operator("mesh.uv_texture_down", icon='TRIA_DOWN')
layout.separator()
col2 = row.column(align=True)
col2.operator("mesh.uv_texture_a_to_z", icon='TRIA_UP_BAR')
col2.operator("mesh.uv_texture_z_to_a", icon='TRIA_DOWN_BAR')
def register():
bpy.utils.register_class(MESH_OT_uv_down)
bpy.utils.register_class(MESH_OT_uv_up)
bpy.utils.register_class(MESH_OT_uv_a_to_z)
bpy.utils.register_class(MESH_OT_uv_z_to_a)
bpy.types.DATA_PT_uv_texture.append(uv_tools_addition)
def unregister():
bpy.utils.unregister_class(MESH_OT_uv_down)
bpy.utils.unregister_class(MESH_OT_uv_up)
bpy.utils.unregister_class(MESH_OT_uv_a_to_z)
bpy.utils.unregister_class(MESH_OT_uv_z_to_a)
bpy.types.DATA_PT_uv_texture.remove(uv_tools_addition)
if __name__ == "__main__":
register()
-
$\begingroup$ Thank you for this code! thank you a lot ! $\endgroup$ Commented Aug 22, 2022 at 14:21