5
$\begingroup$

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?

$\endgroup$
1

4 Answers 4

8
$\begingroup$

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:

enter image description here

$\endgroup$
9
  • $\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$
    – TTTTTTa
    Commented 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$
    – Jake Duth
    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$
    – Levitikon
    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$
    – Jake Duth
    Commented Jul 16, 2020 at 20:38
11
$\begingroup$

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 :

  1. Select the first UV Map.
  2. Click on the Plus button.
  3. Delete the first UV map.
  4. Rename the last UV Map to not contain the .001 suffix.

enter image description here

$\endgroup$
5
  • $\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$
    – TTTTTTa
    Commented 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$
    – Jake Duth
    Commented Nov 15, 2016 at 15:12
  • $\begingroup$ @pycoder nice, good luck :) $\endgroup$ Commented Nov 15, 2016 at 15:30
  • 2
    $\begingroup$ @ŠtefanSchindler Thanks, take a look. $\endgroup$
    – Jake Duth
    Commented Nov 16, 2016 at 4:59
3
$\begingroup$

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()
$\endgroup$
1
  • 1
    $\begingroup$ just go to the GitHub repo like I said in my answer...that's what will get updated $\endgroup$
    – Jake Duth
    Commented Jul 17, 2020 at 20:00
2
$\begingroup$

Another variation with options to sort UVs by name enter image description here

# ### 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()
$\endgroup$
1
  • $\begingroup$ Thank you for this code! thank you a lot ! $\endgroup$ Commented Aug 22, 2022 at 14:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .