Для того чтобы переместить материал из одного слота материала в другой, мы можем в панели материалов объекта нажать на кнопку со стрелкой вверх или вниз. При этом материал в текущем выделенным слоте поднимется (или опуститься) на одну позицию. Такой способ сортировки и перемещения материалов очень медленный и не очень удобный.
При нажатии на кнопку со стрелкой для перемещения материала в “слот вверх” или в “слот вниз” вызывается оператор object.material_slot_move(), в параметрах которого передается направление перемещения UP (вверх) или DOWN (вниз).
К сожалению этот оператор не может перемещать материалы по слотам более чем на одно соседнее место.
В самом простом случае мы можем вызывать этот оператор в цикле несколько раз – столько, на сколько позиций нам нужно передвинуть материал в активном слоте.
Например, для того чтобы передвинуть текущий материал на два слота вверх, нужно вызвать оператор два раза, указывая в его параметре направление “UP”. Не забудем про необходимость переопределения контекста, если мы исполняем наш код из текстового редактора Text Edit.
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') |
В целом, такое решение работает, но мы можем немного его улучшить.
Для начала обратим внимание на то, что порядок расположения материалов по слотам идентичен порядку расположения материалов в списке материалов объекта.
Мы можем вывести список материалов для активного объекта до ручной перестановки.
1 2 3 |
bpy.context.object.data.materials[:] # [bpy.data.materials['red'], bpy.data.materials['blue'], bpy.data.materials['cyan']] |
После чего, например, поднять последний материал на один слот вверх, и снова посмотреть на этот список.
1 2 3 |
bpy.context.object.data.materials[:] # [bpy.data.materials['red'], bpy.data.materials['cyan'], bpy.data.materials['blue']] |
Порядок остается в прямом соответствии.
И вот, кажется очевидным решение, как можно быстро перекидывать материалы в слотах на нужное место – мы можем просто менять их местами в списке материалов.
Давайте попробуем переместить материал из последнего слота сразу в первый. Для этого поменяем местами последний материал с первым в списке, используя 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] |
И, у нас получилось. Мы видим, что материалы с первом и последнем слотах действительно поменялись местами.
Однако, внимательно посмотрев на наш объект, мы замечаем, что у нас “что-то пошло не так”. Вместе с тем, что материалы поменялись в слотах, они заодно поменялись и на самом объекте – на полигонах, на которых у нас был назначен последний материал, теперь назначен первый, и наоборот. Такой вариант перемещения материалов в слотах нас, конечно, не устраивает.
Попробуем исправить ситуацию. Для этого нужно понять, каким образом материал назначается на полигоны объекта.
На самом деле все очень просто – каждый полигон объекта имеет свойство material_index, в котором хранится целое число. И это число как раз является порядковым индексом материала в списке материалов объекта.
Например, для первого полигона объекта:
1 2 |
bpy.context.object.data.polygons[0].material_index # 2 |
Это означает, что на первый полигон (с индексом 0) назначен материал, расположенный на третьем месте (индекс 2) в списке материалов объекта.
1 2 3 |
bpy.context.object.data.materials[2] # bpy.data.materials['cyan'] |
Становится понятным, почему наш трюк с перекидыванием материалов сработал не так как ожидалось. Мы поменяли материалы в списке материалов объекта, что изменило их индексы, но на самих полигонах объекта остались отсылки к индексам материалов так, как было до замены.
Итак, чтобы у нас все получилось, нам нужно не только поменять местами материалы в списке, но также и поменять местами индексы материалов для каждого полигона объекта, на которые эти материалы назначены.
Мы можем до замены получить список полигонов объекта на которые назначен материал с нужным индексом, например – 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], ...] |
И после перекидывания материалов назначить этим полигонам новый индекс.
1 2 |
for polygon in [_p for _p in bpy.context.object.data.polygons if _p.material_index == 2]: polygon.material_index = 0 |
И как мы видим, после этого материал на объекте вернулся в свое исходное положение.
Давайте соберем все вместе и напишем функцию, которая будет менять местами два материала в слотах.
Определим функцию с тремя входными параметрами: объект, над которым мы проводим действия, индекс (порядковый номер) слота материала и второй индекс – номер слота, в который нужно поставить этот материал.
1 |
def switch_materials(obj, mat_1_idx, mat_2_idx): |
Сначала мы меняем материалы в списке материалов объекта.
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] |
Создадим два списка полигонов, с первым материалом, и со вторым.
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] |
И взаимно поменяем индексы материала у этих полигонов.
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 |
Все вместе:
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 |
Теперь, для того чтобы поставить последний материал, в слотах объекта на первое место, нам нужно просто вызвать нашу функцию.
1 |
switch_materials(bpy.context.active_object, 0, 2) |
Материал активного объекта из слота с индексом 2 (третий по порядку) переместится на первое место (индекс 0).