UE4のPythonを使ったFBXインポート

まだベータ版ではありますが、UE4Pythonが使用できるようになっています。
主な使いみちはEditorでの作業の簡略化でしょう。
多くのDCCツールがPythonを採用しているため、作業効率化を図りたい人には馴染み深い言語でもあります。

UE4ではUIを含めた拡張機能プラグインで作成することになっていましたが、Pythonの導入で簡単な作業であればプラグインを作らなくても実行できるようになりました。
簡単な作業、ちょっとしたことというのは開発中はとても多く、これらを効率化出来るかどうかはイテレーションの回数に影響します。
その点でPythonは覚えておいて損がない言語だと思いますので、非プログラマの方もぜひ触ってみてください。

さて、今回はそんなちょっとした機能としてFBXファイルのインポートをやってみます。
FBXのインポートは1つのファイルだけなら普通にエディタを使えばいいのですが、大量のFBXを一度にインポートしたい場合などはエディタでやるのは大変です。
また、インポート後にマテリアルを変更したり、テクスチャも一緒にインポートしたり、インポートしたテクスチャをマテリアルインスタンスに割り当てたりと、作業は地味ですが大量にあります。
多くの場合、これらの作業は決められたルールで行われているので自動化もしやすいはず。
しかし、プラグインを作るのは少々面倒で、もっと簡単に処理したいと思ってしまうでしょう。

そこでPythonの登場です!
今回のサンプルはPythonを使ってFBXをインポート、それぞれのマテリアルにマスターマテリアルを継承したマテリアルインスタンスを設定するというものです。
テクスチャのインポートや割当は行っていませんが、これも対応は難しくないと思います。ただ、今回はマテリアルインスタンスの設定までです。

最初にPythonを使える状態にします。
プロジェクトを開いたら[編集]メニューの[プラグイン]を選択し、プラグインウィンドウを表示します。
そして以下の2つのプラグインを有効にしましょう。

f:id:monsho:20190518121215p:plain

Pythonは言わずもがなですが、[Editor Scripting Utilities]も今回は使用します。
2つのプラグインをONにしたらエディタを再起動します。

再起動後はアウトプットログウィンドウのコマンド入力部分に以下のコマンドで.pyファイルを実行できます。

py ファイル名
例)
 py "D:\test.py"

とても簡単です。当然ですが、.pyファイルは自前で用意しましょう。
今回はサンプルとして以下のようなスクリプトを書いてみました。

import unreal

# FBXインポート時の設定
op = unreal.FbxImportUI()
op.import_materials = True # マテリアルもインポート
op.static_mesh_import_data.combine_meshes = True # メッシュを1つにまとめる

# FBXインポートのタスクを生成
task = unreal.AssetImportTask()
task.automated = True
task.destination_name = 'TestMesh' # UE4上のアセット名
task.destination_path = '/Game/Test/' # アセットを保存するフォルダ
task.filename = "D:/test.fbx" # 読み込みたいFBXファイル名を指定する
task.options = op

tasks = [task]

# タスクを実行
# FBXとマテリアルがインポートされる
atool = unreal.AssetToolsHelpers.get_asset_tools()
atool.import_asset_tasks(tasks)

# インポートされたマテリアルを削除して、同名のマテリアルインスタンスを作成
# このインスタンスをインポートしたメッシュに割り当てる
mesh_data = unreal.EditorAssetLibrary.find_asset_data(task.destination_path + task.destination_name)
master_mat = unreal.EditorAssetLibrary.find_asset_data('/Game/Test/M_Master_base')
mesh = mesh_data.get_asset()
for i in range(mesh.get_num_sections(0)):
    mat = mesh.get_material(i)
    mat_path = unreal.EditorAssetLibrary.get_path_name_for_loaded_asset(mat)
    mat_name = mat_path[mat_path.rfind('/') + 1:mat_path.rfind('.')]
    mat_path = mat_path[:mat_path.rfind('/') + 1]
    unreal.EditorAssetLibrary.delete_loaded_asset(mat)
    mat_inst = atool.create_asset(mat_name, mat_path, unreal.MaterialInstanceConstant, None)
    unreal.MaterialEditingLibrary.set_material_instance_parent(mat_inst, master_mat.get_asset())
    mesh.set_material(i, mat_inst)

やっていることは簡単です。少しずつ見ていきましょう。

import unreal

UE4上のPythonの機能を使う場合はこのモジュールをインポートします。

op = unreal.FbxImportUI()

メッシュインポート時の設定情報を作成します。これは unreal.FbxImportUI クラスで作成し、必要なデータを設定しましょう。

task = unreal.AssetImportTask()
# ry
tasks = [task]

アセットのインポートを行う際には unreal.AssetImportTask クラスを作成します。
インポートするファイル1つにつき1つのオブジェクを使います。

atool = unreal.AssetToolsHelpers.get_asset_tools()
atool.import_asset_tasks(tasks)

アセットのインポートは unreal.AssetTools を利用しますが、このオブジェクトは unreal.AssetToolsHelpers.get_asset_tools() 関数を用います。
リストの先頭から順番に処理されるので複数のタスクを用意しても構いませんが、今回は1つだけです。

mesh_data = unreal.EditorAssetLibrary.find_asset_data(task.destination_path + task.destination_name)

unreal.EditorAssetLibrary を使用する場合に Editor Scripting Utilities プラグインが必要になります。
find_asset_data() 関数は指定パスのアセットを検索して1つだけ返します。
返り値の AssetData オブジェクトから get_asset() 関数を使うと、実際のアセットへのアクセサーを取得できます。
その後の forループで回すのはメッシュのセクション数、すなわちマテリアル数となります。

    mat = mesh.get_material(i)
    mat_path = unreal.EditorAssetLibrary.get_path_name_for_loaded_asset(mat)
    mat_name = mat_path[mat_path.rfind('/') + 1:mat_path.rfind('.')]
    mat_path = mat_path[:mat_path.rfind('/') + 1]
    unreal.EditorAssetLibrary.delete_loaded_asset(mat)
    mat_inst = atool.create_asset(mat_name, mat_path, unreal.MaterialInstanceConstant, None)
    unreal.MaterialEditingLibrary.set_material_instance_parent(mat_inst, master_mat.get_asset())
    mesh.set_material(i, mat_inst)

for文の中身です。
まず、マテリアルをインポートしているので、FBX内のマテリアル名と同一名のマテリアルアセットが作成されています。
マテリアルスロットに割り当てられているマテリアルからアセットパスを取得、これをパスとアセット名に分けています。

そしておもむろにマテリアルを削除します。同名のマテリアルインスタンスを作成するためです。
マテリアルインスタンスは create_asset() 関数で MaterialInstanceConstant を作成します。
このクラスのオブジェクトがコンテンツブラウザ上でのマテリアルインスタンスです。

このマテリアルインスタンスの親マテリアルとして取得済みのマスターマテリアルを設定します。
マテリアル名に何らかの識別子を用意し、名前に応じて使用するマスターマテリアルを変更させる、なんていうのも作業効率化になりそうですね。
親マテリアルを設定したらメッシュの対象番号のマテリアルとして設定します。

これだけ。 ちなみに、この状態だとアセットの保存ができてませんが、これも EditorAssetLibrary に存在しますので、メッシュとマテリアルインスタンスを保存すると良いでしょう。
ソースコード中のFBXファイル名を正しいファイル名に変更すれば動作するはずです。(UE4.22.1)

UE4Pythonは2.7だそうです。3系ではないので記述方法やモジュールに注意が必要です。
しかもUE4自身がPythonの実行ファイルを持っているので、必要なモジュールがない場合のインストールには注意が必要です。

とまあ、こんな感じです。
あとは自前のルールに合わせてコードを書き換えれば一括インポートは簡単になるのではないかと思います。