マテリアルをアルファベット順に並べる
Pythonを使ってBlenderでできる事をまとめています。解説もあります。
はじめに
レシピ一覧
Pythonの難易度: ★★★★☆
動機
様々にマテリアルを試していると、整理したくなりませんか?
なのに、Blenderのマテリアル(正確にはマテリアルスロット)の並べ替えって上下移動キーしかなくて不便じゃないですか?
(頂点グループとかはあるのに)
Pythonを使ってパッとやっちゃいましょうか!
細かい仕様
- 対象のオブジェクトはアクティブと仮定できます。
- 対象のオブジェクトには複数のマテリアルがセットされています。
- マテリアル名の比較はpythonの文字列比較に準じます。
- 重複しているマテリアル名は元の順番を維持してください。
- 空いているマテリアルスロットは最後にまとめてください。
サンプルコード
import bpy
obj = bpy.context.object
arr = obj.material_slots
def comp_swap(arr, i):
"""マテリアルスロットのマテリアルの名前を比較して、pythonの文字列比較上、若い方を上にする関数
引数
arr: iterable = 並べ替えるリスト(直接操作する)
i : int = arrのiとi+1番目を比較/交換する
返り値なし
"""
# マテリアルスロットが空だとNoneを返すのでその処理
are_none = (arr[i].material is None, arr[i+1].material is None)
if are_none in [(True, True), (False, True)]:
swap = False
elif are_none in [(True, False)]:
swap = True
else:
# マテリアル名に応じて入れ替えるかを決める
if arr[i+1].material.name < arr[i+1].material.name:
swap = True
else:
swap = False
if swap:
obj.active_material_index = i
bpy.ops.object.material_slot_move(direction='DOWN')
for i in range(len(arr)):
for j in range(len(arr)-2, i-1, -1): # range(i,len(arr)-1)をひっくり返したもの
comp_swap(arr, j)
上記サンプルコードを、Blender内蔵のテキストエディタに貼り付けた後、Alt-Pを実行してください。
解説
bpy.ops.object.material_slot_move
調べた限りでは、マテリアルスロットの順番を入れ替えられる唯一の方法です。
obj.material_slots = sorted(mat_slots, key = lambda ms: ms.material.name if ms.material is not None else "")
とか行けるのかな、と思ったら、material_slots
はread-onlyだと怒られました。
結局、自分でソートするしかない、ということになります。
obj
バブルソート
今回並べ替えに使ったのは、バブルソートと呼ばれるアルゴリズムです。
ソースコード中のcomp_swap
を次のように書き換えると、これは数字を並べ替えるプログラムに変身します。
def comp_swap(arr, i):
"""大小比較して、小さい方を前にする関数
引数
arr: iterable = 並べ替えるリスト(直接操作する)
i : int = arrのiとi+1番目を比較/交換する
返り値なし
"""
if arr[i+1] < arr[i]:
arr[i], arr[i+1] = arr[i+1], arr[i]
# ソート用の関数を作ってシャッフル
import random
arr = list(range(10))
random.shuffle(arr)
print("before:", arr)
for i in range(len(arr)):
print(arr[:i],arr[i:])
for j in range(len(arr)-2, i-1, -1): # range(i,len(arr)-1)をひっくり返したもの
comp_swap(arr,j)
print("after :", arr)
一旦、内側のfor文の中身が何なのかを無視して、出力だけ読むと
before: [5, 9, 4, 3, 1, 7, 6, 0, 8, 2]
[] [5, 9, 4, 3, 1, 7, 6, 0, 8, 2]
[0] [5, 9, 4, 3, 1, 7, 6, 2, 8]
[0, 1] [5, 9, 4, 3, 2, 7, 6, 8]
[0, 1, 2] [5, 9, 4, 3, 6, 7, 8]
[0, 1, 2, 3] [5, 9, 4, 6, 7, 8]
[0, 1, 2, 3, 4] [5, 9, 6, 7, 8]
[0, 1, 2, 3, 4, 5] [6, 9, 7, 8]
[0, 1, 2, 3, 4, 5, 6] [7, 9, 8]
[0, 1, 2, 3, 4, 5, 6, 7] [8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8] [9]
after : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
のようなのが出ていると思います。(randomを使っているので、出力が多少違っても気にしないでください)
最初のbefore:
というのは、ソート前のぐちゃぐちゃのリスト、after:
というのはソート後のキレイなリストです。
その間のは、forループの中で出力しています。ここに注目してみましょう。
リストの前半部分はfor文ごとに一つ大きくなります。
また、この前半部分はどうやら0
から順番に数字が入っています。
ここから内側のfor文が、どうやら「後ろのリストから一番小さいのを持ってきて、前のリストに追加する」関数、のようです。
しかし、よくよく見ると、後半のリストも若干変わっています。
では内側のfor文の中身は何でしょうか?
内側のfor文は、「後ろから小さいだけを移動してくる」という処理をします。
イメージとしては、
- 現在連れている一番小さい数と一緒に、後ろから前へと移動する
- 移動中により小さい数を見つけたら、それまで連れていた数と交換して、より小さい数を連れていく
このようにして、バブルソートでは「後々の処理のことも考えつつ、一番小さいのを持ってくる」という処理をします。
上の2.を実際に行っているのがcomp_swap
という関数です。ここを書き換えることで、交換する基準や交換する方法が選べます。
実際のサンプルコードでは、交換する基準は「マテリアルの名前」、交換する方法が「マテリアルの上下移動」としています。
なお、ソートのアルゴリズムはこの他にもたくさんあります。
理解がしやすいものを挙げると選択ソートや挿入ソート、速いものを挙げるとクイックソートやマージソート、アホみたいなのがボゴソート。
その中でバブルソートを選んだのは
- わかりやすい(既存のソートの中では2番目くらいにわかりやすい)
- 入れ替えの動作が隣接する要素同士(
material_slot_move
を使う上で便利) - 十分速い(クイックソートとかに比べたらだいぶ遅いけど)
- 同名の要素でも順番を保ってくれる(安定である、といいます)
という理由です。
これを機会に他のソートも調べてみてください。