I have an object with named materials, for example

  • Material_2
  • Material_0
  • Material_1

Thus Material_2 has the material_index 0. But I need Material_2 to have index 2, Material_0 at index 0 and so on. When doing it manually, I can move up/down the materials such that they are properly sorted.

Because I didn't find a simple way to do it, I tried to sort the material names and then tried to move each entry to the correct position using bpy.ops.object.material_slot_move(direction='DOWN'). This is pretty weird and doesn't look great (and is not O(nlogn), although it doesn't matter).

How can I achieve this (preferably in a simple way)?

  • 1
    $\begingroup$ If you do not care about maintaining any reference to the old order. You can get the material names by ob_mats = [mat.name for mat in context.object.material_slots] then sort ob_mats.sort() and finally just replace the materials in the slots. for i, mat_slot in enumerate(ob.material_slots): mat_slot.material = bpy.data.materials.get(ob_mats[i]) $\endgroup$
    – Ratt
    Apr 9, 2022 at 21:19
  • $\begingroup$ I need the assigned parts of the model to stay the same for each material. $\endgroup$
    – xormenter
    Apr 9, 2022 at 21:21

3 Answers 3


It requires a lot of calls to bpy.ops that there are probably substitutes for but here's code that will do what you want. It assumes you want to modify the active object. You'll need to make some changes to override context if you want to modify some other object.

import bpy

obj = bpy.context.active_object
material_list = [material.name for material in obj.material_slots]

for i, slot in enumerate(obj.material_slots):
    obj.active_material_index = i
    slot.material = bpy.data.materials[material_list[i]]

The key here is that before changing a slot's material, the current faces assigned to that slot are selected. Since changing the material doesn't change the selection, we can then assign the slot to the selected faces and deselect them.

This will work in object or edit mode, but it must start will all of the faces deselected.


I'm not sure if I understood you correctly, based on your description that you're able to achieve the desired result manually simply by reordering the materials in slots, I think you don't want to do what Marty Fouts suggested, which changes the material index on faces (it modifies the geometry). But I think you simply want to change the mapping like this:

import bpy
from bpy import context as C, data as D

def key(mat):
    parts = mat.name.rsplit(sep="_", maxsplit=1)
        return (int(parts[-1]), parts[0])
    except ValueError:
        return mat.name

# for ob in C.selected_objects: 
for ob in D.objects:
    slots = ob.material_slots
    ordered_mats = sorted([s.material for s in slots], key=key)
    for slot, material in zip(slots, ordered_mats):
        slot.material = material

The sorting function is somewhat arbitrary, it treats the part after the last underscore as a number, positioning material_11 after material_2, but material_a11 will be positioned before material_a2, because the a part will make the conversion to number fail.


As an alternative.

enter image description here

import bpy

context = bpy.context
ob = context.object

ob_mats = [mat.name for mat in ob.material_slots]

for i, mat_name in enumerate(ob_mats):
    # set active material slot to end slot
    ob.active_material_index = len(ob.material_slots)-1
    while ob.active_material.name != mat_name:
        ob.active_material_index -=1
    while ob.active_material_index > i:
  • $\begingroup$ That's essentially what I did. Bit sad that there's no super easy way but it does the job. $\endgroup$
    – xormenter
    Apr 10, 2022 at 16:12

You must log in to answer this question.

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