一つのマテリアルが同じオブジェクトで重複しているのをまとめる
Pythonを使ってBlenderでできる事をまとめています。解説もあります。
はじめに
レシピ一覧
Pythonの難易度: ★★★★☆
動機
「こことここは別のマテリアルにしようかなぁ」と思っていたけど、完成してみたらマテリアルが重複していることはありませんか?
編集モードでいちいち作業をするよりも、pythonにやらせましょう。
細かい仕様
- 対象のオブジェクトはアクティブと仮定できます。
- マテリアル一覧(マテリアルスロット)の中で重複しているものを除いてください。
- 各面が参照しているマテリアルは変えないでください。
- マテリアルのリンクは全てDataになっています。
- マテリアルが設定されていないもの(空のマテリアルスロット)も、まとめてください。
サンプルコード
import bpy
import bmesh
obj = bpy.context.active_object
# material_indexの新旧の参照テーブル(Look Up Table)を作成
new_mats = []
mat_lut = []
for mat_slot in obj.material_slots:
mat = mat_slot.material
if not mat in new_mats:
new_mats.append(mat)
mat_lut.append(new_mats.index(mat))
# materialを並べ替える
for i, mat in enumerate(new_mats):
obj.material_slots[i].material = mat
# 面が参照するスロットを変更
bm = bmesh.new()
bm.from_mesh(obj.data)
for face in bm.faces:
face.material_index = mat_lut[face.material_index]
bm.to_mesh(obj.data)
bm.free()
# スロットを選択して削除する
while len(obj.material_slots) > len(new_mats):
obj.active_material_index = len(new_mats)
bpy.ops.object.material_slot_remove()
上記サンプルコードを、Blender内蔵のテキストエディタに貼り付けた後、Alt-Pを実行してください。
なお、手作業の内容をそのまま再現したものも作ることが出来ます。(そのうち書く)
解説
マテリアルスロット
python APIにはオブジェクトが利用しているマテリアルを知る方法が2つあります。(それ以上あるかもしれないけど知らない)
-
object.data.materials
object.data
はメッシュを指すため、マテリアルがメッシュに紐付けられていることを示します。これはアウトラインで「メッシュの下にマテリアルがある様子」と同じです。 -
object.material_slots[i].material
各オブジェクトはmaterial_slot
というリスト様の構造を所有しており、メッシュ内のマテリアルを順番に格納しています。これはマテリアルの設定画面の一番上にある表と同じです。
今回の「マテリアルが重複している」というのは、マテリアルスロット内の異なる場所に同じマテリアルが入っていることを指します。
ですので、2を主に使っていくことに成ります。
全体の流れ
このコードは
- マテリアルスロットの新旧対応表を作る
- 対応表に基づいて、各面に割り当てられたマテリアルスロットを変更する
bmeshを使用 - 不要なマテリアルスロットを削除する
の3部からなっています。
新旧対応表 (look up table)
(look up tableはよく使われる用語ですが、新旧対応表は私の造語です)
簡単な例として、
[Mat.001, Mat.002, Mat.001, Mat.002, Mat.000]
という長さ5のマテリアルスロットを考えます。
重複を除くと
[Mat.001, Mat.002, Mat.000]
の3つだけになるので、さっきの5つはこの3つの内のどれか一つと同じものです。
例えば、先頭を0番目として数えると、元のスロットでMat.002
なのは、1番目と3番目です。
これは、新しいスロットでは1番目なので
[?, 1, ?, 1, ?]
を参照すれば良いことが分かります。他の2つに対しても同じことをすると
[0, 1, 0, 1, 2]
という対応表が得られます。
bmesh
PythonからBlenderを使うには基本的にbpy
を使いますが、それ以外にもいくつかのライブラリがあります。
例えば、bmesh
はメッシュを扱うためのライブラリです。
bpy
はオブジェクトモード用、bmesh
は編集モード用のライブラリ、と考えてください。
bmeshを使うには
bm = bmesh.new()
bm.from_mesh(obj.data)
# 処理
bm.to_mesh(obj.data)
bm.free()
という流れを踏みます。
bmesh.new()
bmesh
で主に使うBMesh
というオブジェクトを作成しています。
メッシュの実体というべきものです。
この段階では空っぽです。
bm.from_mesh(obj.data)
BMesh
のfrom_mesh(メッシュ)
というメソッドはメッシュ(data-block)から実際のメッシュを引き出して、保存します。
オブジェクトモード->編集モードの切り替えに似ています。
この段階でメッシュの様々なデータが格納されています。
bm.to_mesh(obj.data)
from_mesh
とは逆に、実際のメッシュからメッシュ(data-block)をつくります。
編集モード->オブジェクトモードの切り替えに似ています。
bm
の中身はもう不要ですが、(場合によっては大きいデータが)残っています。
bm.free
bm
の中身を消します。
del bm
と同じだと思われますが、公式サンプルではこうやってます。
bm.faces
・face.material_index
BMesh
のプロパティーfaces
にはBMFace
という面を指すオブジェクトがリスト様に入っています。
BMFace
にはその面積などを指す様々なプロパティーがありますが、今回はその面が参照しているマテリアルスロットの番号を指すmaterial_index
を書き換えました。
obj.active_material_index
そのオブジェクトのアクティブなマテリアルスロットは意外なことに各オブジェクトに格納されています。
bpy.ops.object.material_slot_remove
アクティブなオブジェクトのアクティブなマテリアルスロットを消す関数です。