LoginSignup
0
0

More than 5 years have passed since last update.

マテリアルをアルファベット順に並べる (Python for Blender クックブック)

Last updated at Posted at 2018-11-05

マテリアルをアルファベット順に並べる

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文は、「後ろから小さいだけを移動してくる」という処理をします。
イメージとしては、

  1. 現在連れている一番小さい数と一緒に、後ろから前へと移動する
  2. 移動中により小さい数を見つけたら、それまで連れていた数と交換して、より小さい数を連れていく

このようにして、バブルソートでは「後々の処理のことも考えつつ、一番小さいのを持ってくる」という処理をします。

上の2.を実際に行っているのがcomp_swapという関数です。ここを書き換えることで、交換する基準や交換する方法が選べます。
実際のサンプルコードでは、交換する基準は「マテリアルの名前」、交換する方法が「マテリアルの上下移動」としています。

なお、ソートのアルゴリズムはこの他にもたくさんあります。
理解がしやすいものを挙げると選択ソートや挿入ソート、速いものを挙げるとクイックソートやマージソート、アホみたいなのがボゴソート。
その中でバブルソートを選んだのは

  • わかりやすい(既存のソートの中では2番目くらいにわかりやすい)
  • 入れ替えの動作が隣接する要素同士(material_slot_moveを使う上で便利)
  • 十分速い(クイックソートとかに比べたらだいぶ遅いけど)
  • 同名の要素でも順番を保ってくれる(安定である、といいます)

という理由です。
これを機会に他のソートも調べてみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0