LoginSignup
2
2

More than 5 years have passed since last update.

一つのマテリアルが同じオブジェクトで重複しているのをまとめる (Python for Blender クックブック)

Last updated at Posted at 2018-11-01

一つのマテリアルが同じオブジェクトで重複しているのをまとめる

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つあります。(それ以上あるかもしれないけど知らない)

  1. object.data.materials object.dataはメッシュを指すため、マテリアルがメッシュに紐付けられていることを示します。これはアウトラインで「メッシュの下にマテリアルがある様子」と同じです。
  2. object.material_slots[i].material 各オブジェクトはmaterial_slotというリスト様の構造を所有しており、メッシュ内のマテリアルを順番に格納しています。これはマテリアルの設定画面の一番上にある表と同じです。

今回の「マテリアルが重複している」というのは、マテリアルスロット内の異なる場所に同じマテリアルが入っていることを指します。
ですので、2を主に使っていくことに成ります。

全体の流れ

このコードは

  1. マテリアルスロットの新旧対応表を作る
  2. 対応表に基づいて、各面に割り当てられたマテリアルスロットを変更する bmeshを使用
  3. 不要なマテリアルスロットを削除する

の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)

BMeshfrom_mesh(メッシュ)というメソッドはメッシュ(data-block)から実際のメッシュを引き出して、保存します。
オブジェクトモード->編集モードの切り替えに似ています。
この段階でメッシュの様々なデータが格納されています。

bm.to_mesh(obj.data)

from_meshとは逆に、実際のメッシュからメッシュ(data-block)をつくります。
編集モード->オブジェクトモードの切り替えに似ています。
bmの中身はもう不要ですが、(場合によっては大きいデータが)残っています。

bm.free

bmの中身を消します。
del bmと同じだと思われますが、公式サンプルではこうやってます。

bm.facesface.material_index

BMeshのプロパティーfacesにはBMFaceという面を指すオブジェクトがリスト様に入っています。
BMFaceにはその面積などを指す様々なプロパティーがありますが、今回はその面が参照しているマテリアルスロットの番号を指すmaterial_indexを書き換えました。

obj.active_material_index

そのオブジェクトのアクティブなマテリアルスロットは意外なことに各オブジェクトに格納されています。

bpy.ops.object.material_slot_remove

アクティブなオブジェクトのアクティブなマテリアルスロットを消す関数です。

2
2
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
2
2