To move a material from one material slot to another, we can click the up or down arrow button in the object’s material panel. In this case, the material in the currently selected slot will move up (or down) by one position. This method of moving and sorting materials is very slow and not very convenient.
When we click the arrow button to move a material to the “up” or “down” slot, the object.material_slot_move() operator is executed, with the “UP” or “DOWN” direction passed in its parameters.
Unfortunately, this operator cannot move materials across slots more than one adjacent place.
In the simplest case, we can call this operator in a loop several times – as many as we need to move the material from the active slot.
For example, to move the active material up two slots, we need to call the operator twice, specifying the “UP” direction in its parameter. Don’t forget about overriding the context if we execute our code from the Text Edit area.
1 2 3 4 |
area = [area for area in bpy.context.screen.areas if area.type == "VIEW_3D"][0] with bpy.context.temp_override(area=area, region=area.regions[-1]): for i in range(2): bpy.ops.object.material_slot_move(direction='UP') |
In general, this solution works, but we can try to improve it a bit.
First, let’s pay attention to the fact that the order of the materials in the slots is identical to the order of the materials in the list of objects’ materials.
We can display the list of materials for the active object before manual rearrangement.
1 2 3 |
bpy.context.object.data.materials[:] # [bpy.data.materials['red'], bpy.data.materials['blue'], bpy.data.materials['cyan']] |
Then, for example, move the last material up one slot, and look at this list again.
1 2 3 |
bpy.context.object.data.materials[:] # [bpy.data.materials['red'], bpy.data.materials['cyan'], bpy.data.materials['blue']] |
The order remains in direct correspondence.
And so, the solution to quickly move materials in slots to the right place seems obvious – we can simply swap them in the list of materials.
Let’s try to move the material from the last slot straight to the first one. To do this, swap the last material with the first one in the list using the Python’s evaluation order.
1 2 |
bpy.context.active_object.data.materials[2], bpy.context.active_object.data.materials[0] = \ bpy.context.active_object.data.materials[0], bpy.context.active_object.data.materials[2] |
And, it works. We can see that the materials in the first and last slots have indeed swapped.
However, looking closely at our object, we notice that “something went wrong”. Along with the fact that the materials have swapped in the slots, they have also swapped on the object itself – on the polygons where we had the last material assigned, the first one is now assigned, and vice versa. This variant of moving materials in the slots, of course, does not suit us.
Let’s try to fix the situation. To do this, we need to understand how the material is assigned to the object’s polygons.
In fact, everything is very simple – each polygon of the object has a “material_index” property, which stores an integer. And this number is exactly the ordinal index of the material in the list of object materials.
For example, for the first polygon of the object:
1 2 |
bpy.context.object.data.polygons[0].material_index # 2 |
This means that the first polygon (with index 0) is assigned with the material located in the third place (index 2) in the list of object materials.
1 2 3 |
bpy.context.object.data.materials[2] # bpy.data.materials['cyan'] |
It becomes clear why our trick with swapping materials did not work as expected. We changed the materials in the list of object materials, which changed their indices, but the object’s polygons themselves retained references to the material indices as they were before the replacement.
So, to make it work, we need not only swap the materials in the list, but also re-assign the material indices for each object polygon to which these materials are assigned.
We can get a list of object polygons to which the material with the desired index is assigned before swapping, for example – 2.
1 2 3 |
[_p for _p in bpy.context.object.data.polygons if _p.material_index == 2] # [bpy.data.meshes['Suzanne'].polygons[0], bpy.data.meshes['Suzanne'].polygons[1], ...] |
And after swapping the materials, assign a new index to these polygons.
1 2 |
for polygon in [_p for _p in bpy.context.object.data.polygons if _p.material_index == 2]: polygon.material_index = 0 |
And as we can see, after this the material on the object returned to its original position.
Let’s put it all together and write a function that will swap two materials in the object’s material slots.
Define a function with three input parameters: the object we are performing actions on, the index (sequence number) of the material slot, and the second index – the number of the slot in which this material should be placed.
1 |
def switch_materials(obj, mat_1_idx, mat_2_idx): |
First, we swap the materials in the list of the object’s materials.
1 2 |
obj.data.materials[mat_1_idx], obj.data.materials[mat_2_idx] = \ obj.data.materials[mat_2_idx], obj.data.materials[mat_1_idx] |
Create two lists of polygons, with the first material assigned, and with the second.
1 2 |
polygons_mat_1_idx = [_p for _p in obj.data.polygons if _p.material_index == mat_1_idx] polygons_mat_2_idx = [_p for _p in obj.data.polygons if _p.material_index == mat_2_idx] |
And mutually change the material indices of these polygons.
1 2 3 4 |
for polygon in polygons_mat_1_idx: polygon.material_index = mat_2_idx for polygon in polygons_mat_2_idx: polygon.material_index = mat_1_idx |
All the function code:
1 2 3 4 5 6 7 8 9 |
def switch_materials(obj, mat_1_idx, mat_2_idx): obj.data.materials[mat_1_idx], obj.data.materials[mat_2_idx] = \ obj.data.materials[mat_2_idx], obj.data.materials[mat_1_idx] polygons_mat_1_idx = [_p for _p in obj.data.polygons if _p.material_index == mat_1_idx] polygons_mat_2_idx = [_p for _p in obj.data.polygons if _p.material_index == mat_2_idx] for polygon in polygons_mat_1_idx: polygon.material_index = mat_2_idx for polygon in polygons_mat_2_idx: polygon.material_index = mat_1_idx |
Now, in order to put the last material in the object slots on the first place, we just need to call our function.
1 |
switch_materials(bpy.context.active_object, 0, 2) |
The material of the active object from the slot with index 2 (the third in order) will move to the first place (with index 0).