※Blender 2.93以降ではPython 3.9が使用されていてこの記事の方法では動かなくなっています。
Blender 2.93: Python API
#Houdini 18.5でPython 3がProduction Buildsに
Production Buildsの項目にHoudini 18.5 - Python 3が追加されました。
Python3が使えるようになったのでPython3系にしかないライブラリが使えるようになったはず!
なので今回はPython3.7で使用できるbpy(Blender Python)をHoudiniに入れてみます。
使用したのは以下のWindows版のHoudiniです。
houdini-py3-18.5.351-win64-vc141.exe
bpyをビルド
Python Moduleとしてのbpyは現状配布されていないので以下を参考にソースコードからビルドする必要があります。
Building Blender as a Python Module
ビルド環境を整備
Blenderをソースコードからビルドする日本語記事は検索すればすぐ見つかると思いますのでより詳しい情報が必要な場合はそちらを探してみてください。
- Visual Studio 2019
- CMake
- Git
- Subversion
あたりがインストールされていれば多分大丈夫なはず・・・?
ソースコードを落としてくる
https://developer.blender.org/diffusion/B/ を参考にソースコードを落としてきます。
githubのミラーからだとzipで落とせると思います。
ビルドする
ソースコードを展開すると以下の用のディレクトリ構成になっていると思います。
このディレクトリ(make.batがある)でコマンドプロンプトを開いて
make bpy
を実行するとビルドに必要な他のライブラリをSubversionで落としてきてビルドされます
(開始してすぐに必要な他のライブラリを落として良いか聞かれるので y を押して進めます)。
初回はすごく時間がかかります。
ビルド成果物をHoudiniで読めるように配置
ビルド成果物は
(blenderのソースコードのディレクトリ)..\build_windows_Bpy_x64_vc16_Release\bin\Release
にあります。
bpy.pyd, libfftw3-3.dll
この2つはHoudiniのpythonが読めるディレクトリに配置します。
必要なdllはblenderのバージョンが変わると増減するかもしれないです。
ここでは以下に配置しました。
C:\Users\(ユーザー名)\Documents\houdini18.5\python3.7libs
2.90(ビルドしたblenderのバージョン)フォルダ
このフォルダはインストールしたHoudiniのexeがあるディレクトリに配置します。
環境変数を設定すると別の場所でも良くなるかもしれません(未検証)。
ここでは以下に配置しました。
C:\Program Files\Side Effects Software\Houdini 18.5.351\bin
Blender Python API を使う
import bpyの確認
やっと使用準備が整いました。Python ShellやPython(SOP)などでimport bpy
がエラーなく実行できることを確認します。ビルド成果物や配置場所に問題があるとエラーを出したりHoudiniが落ちたりします。
.blendファイルを読み込む
Python(SOP)で.blendファイルを読み込む処理を作成します。
テストデータの用意
キューブとスザンヌを配置した.blendを用意しました。
スザンヌは半分に切ってMirror, Subdivisionモディファイアを設定しています。
Python(SOP)でPythonコードを書く
Primitiveアトリビュートでname
Vertexアトリビュートで N, uv
を読み込むコードを書きました。
パラメータは以下の通りです。
Label | Name | 説明 |
---|---|---|
Blender File | blend_file | 読み込む.blendの指定 |
Object Name | objects | 読み込むオブジェクトの指定 |
Apply Modifier | apply_modifier | モディファイアを適用するかの指定 |
import os
import bpy
from bpy_extras.io_utils import axis_conversion
node = hou.pwd()
geo = node.geometry()
# 設定するアトリビュートを準備
name_attrib = geo.addAttrib(hou.attribType.Prim, 'name', '')
normal_attrib = geo.addAttrib(hou.attribType.Vertex, 'N', (0.0, 0.0, 0.0))
uv_attrib = geo.addAttrib(hou.attribType.Vertex, 'uv', (0.0, 0.0, 0.0))
# パラメータの読み取り
blend_file = node.parm('blend_file').evalAsString()
apply_modifier = node.parm('apply_modifier').evalAsInt()
object_names = [ s.strip() for s in node.parm('objects').evalAsString().split() ]
if len(blend_file)>0:
# .blendを開く
bpy.ops.wm.open_mainfile(filepath=blend_file)
# 名前が設定されてなければすべてのオブジェクトを読む
if len(object_names)==0:
object_names = bpy.data.objects.keys()
else:
# blenderの初期ファイルを開いてスザンヌを出す
bpy.ops.wm.read_homefile(app_template='')
bpy.ops.mesh.primitive_monkey_add()
object_names = ['Suzanne']
depsgraph = bpy.context.evaluated_depsgraph_get()
# blenderの軸の向きをHoudiniの軸の向きに変換する行列
axis_conv_mat = axis_conversion(
from_forward='-Y', from_up='Z',
to_forward='Z', to_up='Y'
).to_4x4()
# 指定された名前のオブジェクトを開く
for obj_name in object_names:
obj = bpy.data.objects[obj_name]
if obj.type!='MESH':
continue
# モディファイアを必要なら適用
ob_for_convert = obj.evaluated_get(depsgraph) if apply_modifier else obj.original
# オブジェクトからメッシュを取り出す
try:
me = ob_for_convert.to_mesh()
except:
me = None
if me is None:
continue
# 軸変換とオブジェクトのトランスフォームをメッシュに適用
me.transform( axis_conv_mat @ obj.matrix_world )
# マイナスのスケールがかかっている場合の対応
if obj.matrix_world.determinant() < 0.0:
me.flip_normals()
# Vertex Normalを計算する
me.calc_normals_split()
# UVのデータを取得
uv_layer = me.uv_layers.active.data[:] if len(me.uv_layers) > 0 else None
# ポイントを作成
points = [ hou.Vector3(v.co) for v in me.vertices ]
pt_list = geo.createPoints(points)
# blenderとhoudiniでポリゴンの頂点順が異なるので変換する
loop_indices_list = list()
for mpoly in me.polygons:
count = len(mpoly.loop_indices)
loop_indices_list.append( [ mpoly.loop_indices[(count-i)%count] for i in range(0, count) ] )
for loop_indices in loop_indices_list:
poly = geo.createPolygon()
poly.setAttribValue(name_attrib, obj_name)
for i in loop_indices:
# ポリゴンを作る
v = poly.addVertex( pt_list[ me.loops[i].vertex_index ] )
# N attribute
v.setAttribValue(normal_attrib, me.loops[i].normal)
# uv attribute
if uv_layer:
uv = uv_layer[i].uv
v.setAttribValue(uv_attrib, (uv[0], uv[1], 0.0))
Object Nameのパラメータは以下のようにMenu Scriptで▽から選べるようにしました。
import os
import bpy
name_list = list()
node = hou.pwd()
blend_file = node.parm('blend_file').evalAsString()
# ファイルの存在チェック
if not os.path.exists(blend_file):
return name_list
# Meshのオブジェクトの名前を列挙
objects = bpy.data.objects
for obj in objects:
if obj.type == 'MESH':
name_list.extend( [obj.name]*2 )
return name_list
コードにコメントを書いているので詳細な説明は省略します。
Blender Python APIでメッシュ情報を取得するコードは
(Blenderインストールディレクトリ)\(Blenderバージョン)\scripts\addons
以下でio_で始まるものがImporter/Exporterのアドオンなのでそちらが参考になります。
↑のコードはio_scene_objを参考に書きました。
その他
bpyで.blendファイルを読み込んだ際に以下のようなログが出力されます。
これを外から止める方法は分かりませんでした。
どうしても邪魔だという時はbpyをビルドする際にこのログ出力コードをコメントアウトすれば出力されなくなります。
int BKE_blendfile_read(bContext *C,
const char *filepath,
const struct BlendFileReadParams *params,
ReportList *reports)
{
BlendFileData *bfd;
bool success = false;
/* Don't print startup file loading. */
if (params->is_startup == false) {
printf("Read blend: %s\n", filepath);
}
...
}