HoudiniではWavefront_.objファイルを、fileSOPからインポートする際に一緒に.mtlファイルをロードしてくれません。
たしかにプリミティブ上ではshop_materialpathのattributeはインポートされているので、手でshopに指定された名称と同じマテリアルを作れば読み込めることは読み込めるのですが、、、
正直面倒くさい!
Houdiniではテクスチャと一緒に読み込んでくれるファイル形式はFBXしかありません。FBXimportはシーンを読み込む形になるので、、、
正直面倒くさい!
となると、誰か海外の人がodforceかなんかに投稿しているはず…と、探しましたが…、全然見つかりませんでした。普通にありましたね・・・。
http://forums.odforce.net/topic/4029-python-telnetlib/
失礼しました。
うーん・・・これができないと前回のcolormapの後編の記事がいつまでたっても上げられない・・・(机とかのobjファイルをtf3dm.comからゲットして適当に散らす予定だった)
なので、前回写真を取り込んだ要領で、mtlをパースして、マテリアルをshopに生成してみます。*画像のバットマンとコンボイはtf3dm.comのものを利用しています。
mtlの内容は以下のようになります
newmtl:マテリアル名
Ka :ambient(ルミナンスの色)
Kd :diffuse(拡散反射)
Ks :specular(鏡面反射)
Ns :スペキュラー係数(スペキュラー量)
d or Tr:dissolve?transparent(透明度)
とするならば、だいたいこんな感じでしょうか?
mtlimporter.hda
(ベータ版1.00:Houdini14.0で作成)
■使い方
.objの読み方はアセット内のパラメータから読み込む方法と
.objに記述された.mtlがあればそれを使う。パラメータの.mtl pathがあれば上書きします。
ですので、input1に別のfileSOP(.obj)を入れて.mtlだけ読むこともできます。
でmake materialボタンを押すと、読み込み完了!マテリアルは/shop/か自分の階層ネットワークに出すか選べます。
①で取得する.objファイルを選びます。
②make material!
.mtlだけ読む場合
①Use First Context Geometryに別fileSOPをつなぎます。
②.mtl pathを設定
③make material!
■課題と注意点
課題は沢山あります。なぜかというとWavefrontの.objと.mtlは出力するソフトウェアによって出力結果が異なるからです。
また、私自身この形式について十分に精通しているわけではないので、意図せぬ動作が起こる可能性があります。
その際はご了承ください。
・そもそもパースに失敗して動かないかも
・ファイル名にスペースが入っている場合はsplitされてしまって読めません。.mtl pathから直接指定してください。
・バンプマップはまだ実装していません。(一応コメントアウトして値を使えそうな記述にはしているので、うまいこと組み合わせれば元のマテリアルを再現できると思います。)
・絶対パスで処理しているので、プロジェクトファイル(テクスチャ含む)を移動させると再ロードが必要、opchange で一括変換をかける必要あり
・スペキュラ関連が怪しい・・・鏡面反射角度はraughでいいのか?Houdiniの0.001~1は、Nsの0~128でいいのか?わからない・・・
(間違っていたら教えてください)
■python
def mtl_key_check(): lines = data.split('\n') mtllist = {} dictname='' for line in lines: if '#' in line:##comment pass elif 'newmtl' in line: parm=line.split(' ') while '' in parm: parm.remove('') if len(parm)>1: dictname=parm[1] mtllist[dictname]={} mtllist[dictname]['newmtl'] = parm[1] elif 'map_' in line:##Texture if 'map_Ke ' in line:##Texture ambient parm=line.rsplit(' ',1) while '' in parm: parm.remove('') if len(parm)>1: mtllist[dictname]['map_Ke'] = parm[1] elif 'map_Kd ' in line:##Texture ambient parm=line.rsplit(' ',1) while '' in parm: parm.remove('') if len(parm)>1: mtllist[dictname]['map_Kd'] = parm[1] #elif 'map_Bump ' in line:##Texture ambient # parm=line.rsplit(' ',1) # while '' in parm: # parm.remove('') # len(parm)>1: # mtllist[dictname]['map_Bump'] = parm[1] elif 'Ns ' in line:##Phong specular component parm=line.split(' ') while '' in parm: parm.remove('') if len(parm)>1: mtllist[dictname]['Ns'] = parm[1] #elif 'Ni ' in line:##Refraction index # parm=line.split(' ') # while '' in parm: # parm.remove('') # if len(parm)>1: # mtllist[dictname]['Ni'] = parm[1] #elif 'Tr ' in line: # parm=line.split(' ') # while '' in parm: # parm.remove('') # if len(parm)>1: # mtllist[dictname]['Tr'] = parm[1] #elif 'Tf ' in line:## transmission filter # parm=line.split(' ') # while '' in parm: # parm.remove('') # if len(parm)>1: # mtllist[dictname]['Tf'] = [parm[1],parm[2],parm[3]] #elif 'illum ' in line:##(0, 1, or 2) 0 to disable lighting # parm=line.split(' ') # while '' in parm: # parm.remove('') # if len(parm)>1: # mtllist[dictname]['illum'] = parm[1] elif 'Ka ' in line:##ambient reflectance parm=line.split(' ') while '' in parm: parm.remove('') if len(parm)>3: mtllist[dictname]['Ka'] = [parm[1],parm[2],parm[3]] elif 'Kd ' in line:##diffuse reflectance parm=line.split(' ') while '' in parm: parm.remove('') if len(parm)>3: mtllist[dictname]['Kd'] = [parm[1],parm[2],parm[3]] elif 'Ks ' in line:##specular reflectance parm=line.split(' ') while '' in parm: parm.remove('') if len(parm)>3: mtllist[dictname]['Ks'] = [parm[1],parm[2],parm[3]] #elif 'Ke ' in line: # parm=line.split(' ') # while '' in parm: # parm.remove('') # mtllist[dictname]['Ke'] = [parm[1],parm[2],parm[3]] elif 'd ' in line:##Dissolve factor (pseudo-transparency). Values are from 0-1. 0 is completely transparent, 1 is opaque. parm=line.split(' ') while '' in parm: parm.remove('') if len(parm)>1: mtllist[dictname]['d'] = parm[1] return mtllist def make_shopnetwork(): #shop############### if makeshopnetflg==1: if(hou.node('../../mtllib')==None): shaders = hou.node('../../').createNode('shopnet', 'mtllib') else: shaders = hou.node('../../mtllib') else: shaders = hou.node('op:/shop') shaders.moveToGoodPosition() delnode = shaders.children() #loop############### if overwriteflg==1: for mtl in mtllist: delSearch(mtllist[mtl]['newmtl'],shaders) #loop############### for mtl in mtllist: if debugflg: print('-----'+mtllist[mtl]['newmtl']+'-----') #material############## material = shaders.createNode('material', mtllist[mtl]['newmtl'] , run_init_scripts=False) material.moveToGoodPosition() suboutput = material.createNode('suboutput') v_layered = material.createNode('v_layered') #Name if debugflg: print(' Name newmtl: '+mtllist[mtl]['newmtl']) #Ambient if 'Ka' in mtllist[mtl]: if debugflg: print(' Ambient Car: '+mtllist[mtl]['Ka'][0]+' Cag: '+mtllist[mtl]['Ka'][1]+' Cab: '+mtllist[mtl]['Ka'][2]) v_layered.parm('Car').set(mtllist[mtl]['Ka'][0]) v_layered.parm('Cag').set(mtllist[mtl]['Ka'][1]) v_layered.parm('Cab').set(mtllist[mtl]['Ka'][2]) #Diffuse if 'Kd' in mtllist[mtl]: if debugflg: print(' Diffuse Cdr: '+mtllist[mtl]['Kd'][0]+' Cdg: '+mtllist[mtl]['Kd'][1]+' Cdb: '+ mtllist[mtl]['Kd'][2]) v_layered.parm('Cdr').set(mtllist[mtl]['Kd'][0]) v_layered.parm('Cdg').set(mtllist[mtl]['Kd'][1]) v_layered.parm('Cdb').set(mtllist[mtl]['Kd'][2]) #Specular if 'Ks' in mtllist[mtl]: if debugflg: print(' Specular Csr: '+mtllist[mtl]['Ks'][0]+' Csg: '+mtllist[mtl]['Ks'][1]+' Csb: '+mtllist[mtl]['Ks'][2]) v_layered.parm('Csr').set(mtllist[mtl]['Ks'][0]) v_layered.parm('Csg').set(mtllist[mtl]['Ks'][1]) v_layered.parm('Csb').set(mtllist[mtl]['Ks'][2]) #rough if 'Ns' in mtllist[mtl]: roughvalue = mtllist[mtl]['Ns'] if float(roughvalue) != 0: roughvalue = float(roughvalue)/128.0 #maybe... else: roughvalue=0.001 if debugflg: print(' rough rough: '+str(roughvalue)) v_layered.parm('rough').set(roughvalue) #Alpha if 'd' in mtllist[mtl]: if debugflg: print(' Alpha Alpha: '+mtllist[mtl]['d']) v_layered.parm('Alpha').set(mtllist[mtl]['d']) #Texture if 'map_Ke' in mtllist[mtl]: texture=mtllist[mtl]['map_Ke'].replace('\\\\' , '/') if debugflg: print(' Texture map_base: '+objpath+texture) v_layered.parm('map_base').set(objpath+texture) #Map Diffuse v_layered.parm('Cdr').set(1) v_layered.parm('Cdg').set(1) v_layered.parm('Cdb').set(1) if 'map_Kd' in mtllist[mtl]: texture=mtllist[mtl]['map_Kd'].replace('\\\\' , '/') if debugflg: print(' Texture map_base: '+objpath+texture) v_layered.parm('map_base').set(objpath+texture) #Map Diffuse v_layered.parm('Cdr').set(1) v_layered.parm('Cdg').set(1) v_layered.parm('Cdb').set(1) if debugflg: print('-------------------------------') #wire suboutput.setFirstInput(v_layered) v_layered.moveToGoodPosition() suboutput.moveToGoodPosition() def delSearch(patt,shaders): results = [] for n in shaders.children(): if n.name().startswith(patt): results.append(n.path()) if len(results): for node_name in results: hou.node(node_name).destroy() #main import re node = hou.pwd() geo = node.geometry() OBJFile = node.parm('../objfile/file').evalAsString(); MTLFile = node.parm('mtlpath').evalAsString() makeshopnetflg = hou.Node.evalParm(node,'makeshopnet') overwriteflg = hou.Node.evalParm(node,'overwrite') debugflg = hou.Node.evalParm(node,'debugflg') #Check path objpath='' regexp = re.compile(r'(.*)/.*?$') if not MTLFile: if not(OBJFile.endswith('.obj')): print('Bad file type. Input .obj file') else: file = open(OBJFile) data = file.read() file.close() o=regexp.match(OBJFile) if o != None: objpath = o.group(1)+'/' lines = data.split('\n') mtllibflag = False for line in lines: if mtllibflag: break elif 'mtllib' in line: parm=line.rsplit(' ',1) MTLFile=objpath+parm[1] mtllibflag = True if debugflg: print(objpath+parm[1]) else: o=regexp.match(MTLFile) if o != None: objpath = o.group(1)+'/' if not MTLFile: print('.mtl path is empty') elif not(MTLFile.endswith('.mtl')): print('Bad file type. Input .mtl file') else: file = open(MTLFile) data = file.read() file.close() mtllist = mtl_key_check() make_shopnetwork()
■コールバックについて
このアセットのpythonはコールバックで実行させています。前回の写真を配置するスクリプトでも書くべきだったのですが、
マテリアルをインポートするような処理は、最初に1回手動でボタンを押したときだけ実行させたいわけです。
でないと、ファイルを開きなおすたびに処理が走ってしまいます。
ボタンを押してpythonを実行させる方法はおそらく3つあります。
■1:アセットのcodeを利用する手法
・Create Digital Assetでアセットを追加
・ScriptsタブからEvent Handler:python
・右側のエディタ内で関数を書き込み
def hoge(): print("callback OK")
・ParameterタブでButton追加
・Parameter Description のCallback Scriptを HScript からPythonに変更
・Callback Scriptに
hou.pwd().hdaModule().hoge()
(昔はhou.phm()でした)
もしくは
kwargs["node"].hdaModule().hoge()
*kwargsに何が入っているか知りたい場合はCallback Scriptに
print kwargs
と入れるて実行すると、「あー・・・うん、タプルがいっぱいね」て感じでなんとなくわかります。
*コールバックでの呼び出しの場合、ジオメトリの操作はできません。読み込みのみ可能です。たとえば、
hou.pwd().geometry().addAttrib(hou.attribType.Point, “myatr”, 1.0)
などを実行しようとすると
GeometryPermissionError: Geometry is read-only.
というエラーが出て怒られることになります。
あきらめてwrangleで実行するのが良いかと思います。
■2:Stringを利用する手法
・nullノード追加、Edit Parameter InterfaceからButtonとStringを追加。
・StringのMulti-line StringをON、Languageをpython
・Stringのnameをpyscriptに変更(なんでもOK)
print("callback OK")
・buttonのCallback Scriptを HScript からPythonに変更
・Callback Scriptに
exec(kwargs['node'].parm('pyscript').eval())
と入れて実行する
*コールバックでの呼び出しの場合、ジオメトリの操作はできません。読み込みのみ可能です。
■3:addEventCallback(event_types, callback) を利用する
特定のイベントがノードで起きた時にHoudiniがコールするコールバックを登録することができます。
しかし、他のノードを監視して、パラメータに変更があった場合コールバックを呼ぶということは、常に監視状態ということです。
イベントハンドラの管理も意外と面倒で、あまりお勧めできません。
*やはりコールバックでの呼び出しの場合、ジオメトリの操作はできません。読み込みのみ可能です。
・nullノードを作成。ノード名をreloadに変更、Edit Parameter InterfaceからButtonを追加。
・pythonノードを作成、
node = hou.node("../reload") def setUpCallback(node): node.addEventCallback(tuple([hou.nodeEventType.ParmTupleChanged]), onNodeChange) def onNodeChange(**kwargs): if kwargs["event_type"] == hou.nodeEventType.ParmTupleChanged: if kwargs["parm_tuple"] is not None: onParmTupleChange(kwargs["parm_tuple"],kwargs["node"]) def onParmTupleChange(parm_tuple,node): print "Callback OK", parm_tuple setUpCallback(node)
・pythonを1度だけ実行(注意:何かの拍子でイベントが複数登録される可能性があります。イベントの削除はremoveEventCallback(event_types, callback)です。)
・Buttonを押すと、ParmTupleChangedのイベントが発生し、onParmTupleChangeがコールされる。
■HScriptのコールバックで処理更新
アセットでパラメータを変えたのに処理結果がビューポートに反映されないことがあります。
レンダリングしたり他のノードをアクティブにして戻ってくると反映されるのですが・・・
対処法はここでもコールバックです。更新されない原因は状況に応じて違うと思いますが、
さきほどはpythonのコールバックでしたが、今回はHScriptです。
・オブジェクトを再クックするように命令(強制)します。
opcook -F
・内部ジオメトリキャッシュをクリアします。
geocache -c
・OpenGLキャッシュをクリアします。また、古くなったテクスチャファイルを再読み込みします。
glcache -c
・内部テクスチャキャッシュをクリアします
texcache -c
他にもいろいろなコマンドがあります。詳しくはヘルプのHScriptコマンドを参照してください。
ちなみに今回のアセットでは
opcook -F ./*
をボタン押下時にコールバックすることで画面を再描画させています。
とりあえず今回は以上です。