MaterialXで遊ぶ

#はじめに
Houdiniはバージョン19.0からついにMaterialX(バージョンは**1.38**)に対応しました:v:
https://www.sidefx.com/docs/houdini/solaris/materialx
そこで、MaterialXについて何か書きたいと思います。
この記事はH19.0.455の時点での話となります。
現在のHoudiniではMaterialXのインポートには対応しているものの、~~エクスポートには対応していません。おそらく今後エクスポートには対応してくれるはず。でないと使いようないですからね。~~
H19.0.440のジャーナル見ると

>The MaterialX vops have a RMB > Save > MaterialX... menu entry to save the shader network to .mtlx files.

MaterialXにエクスポートできるようになってました。気づかんかった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/7d495116-9212-9bad-8604-df5d591e0754.png)


また、MaterialXで用意されている標準ノードが(公式のMaterialXだとインラインやOSLで記述されているものが)VOPで148個ほど実装されています。
ノードタイプ名を書き出してみると・・・
>UsdPreviewSurface,UsdPrimvarReader,UsdTransform2d,UsdUVTexture,absorption_vdf,absval,acos,add,ambientocclusion,anisotropic_vdf,arrayappend,artistic_ior,asin,atan2,bitangent,blackbody,blur,burley_diffuse_bsdf,burn,ceil,cellnoise2d,cellnoise3d,clamp,combine2,combine3,combine4,conductor_bsdf,conical_edf,constant,contrast,convert,cos,crossproduct,curveadjust,determinant,dielectric_bsdf,difference,directional_light,disjointover,disney_brdf_2012,disney_bsdf_2015,displacement,divide,dodge,dot,dotproduct,exp,extract,floor,fractal3d,frame,generalized_schlick_bsdf,geomcolor,geompropvalue,glossiness_anisotropy,heighttonormal,hsvadjust,hsvtorgb,ifequal,ifgreater,ifgreatereq,image,in,inside,invert,invertmatrix,layer,light,ln,luminance,magnitude,mask,matte,max,measured_edf,min,minus,mix,modulo,multiply,noise2d,noise3d,normal,normalize,normalmap,oren_nayar_diffuse_bsdf,out,outside,over,overlay,place2d,plus,point_light,position,power,premult,ramp4,ramplr,ramptb,range,remap,rgbtohsv,rotate2d,rotate3d,roughness_anisotropy,roughness_dual,saturate,screen,separate2,separate3c,separate3v,separate4c,separate4v,sheen_bsdf,sign,sin,smoothstep,splitlr,splittb,spot_light,sqrt,standard_surface,subsurface_bsdf,subtract,surface,surfacematerial,switch,swizzle,tan,tangent,texcoord,thin_film_bsdf,thin_surface,tiledimage,time,transformmatrix,transformnormal,transformpoint,transformvector,translucent_bsdf,transpose,triplanarprojection,uniform_edf,unpremult,volume,volumematerial,worleynoise2d,worleynoise3d

配置してみると・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/983b62ff-bc76-cdfb-fde1-5f35e8a57e2c.png)
MaterialXの標準で用意されているノードのうち、Lama系ノードを除いてほとんどVOPで実装されているように見えます。
用意されているノード、あまり多くないですね。魅力的なノードも少ないです。
ただ、注目したいのは`standard_surface`ですかね。
このMaterialX Standard Surfaceの仕様は https://autodesk.github.io/standard-surface/ が詳しいです。
KarmaのMaterialX Standard Surface:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/156e5007-6cca-f767-b2e3-dd8b4a96a2eb.png)
ArnoldのaiStandardSuraceのパラメータ:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/9417cbf1-5ecd-f2f8-e25d-b3310d4b92c8.png)
パラメータがまったく同じですね。
つまり、KarmaとArnoldで共通のUberシェーダが使えるようになりました。

また、AMD社のGPUOpenのサイト( https://matlib.gpuopen.com/main/materials/all )には膨大なMaterialXファイルが公開されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/677585bd-e673-39d3-c53d-2f35ec97e2bd.png)


#MaterialXって何？
そもそもMaterialXって何よ？ってなっている方もいらっしゃるかと思います。
そこで、紹介しておきたいサイトを載せておきます。

- MaterialXの公式サイト https://www.materialx.org/
  MaterialXの概要も仕様書もAPIドキュメントもすべてここに書かれています。仕様書の日本語訳は私が自分のサイトで公開しているので興味のある方は是非。

- MaterialXのgithub https://github.com/materialx/MaterialX
  ソースコードだけでなく、以降で説明するMaterialXViewerの使い方やダウンロードもすることができます。
- Autodeskのgithub https://github.com/autodesk-forks/MaterialX
  ArnoldでのMaterialXを調べるなら、ここもチェックです。

MaterialXは仕様書を何度も読まないと理解できないようなものなので簡単に説明するのが難しいですが、私が公開している翻訳したイントロダクションの一部を以下に載せます。

>
1.1. はじめに
多くのCGプロダクションスタジオでは、制作パイプライン毎に複数のソフトウェアツールを伴ったワークフローを使用しています。また、複数の設備にわたって共有や外注すべき作業量が非常に膨大にあることもあり、異なるソフトウェアパッケージとレンダリングシステムを使用している他の部門やスタジオに対しては完全にルックデブされたモデルを渡す必要があります。さらに、スタジオレンダリングパイプラインは以前は熟練プログラマーが開発したモノリシックシェーダを使用し、テクニカルディレクターがそれを修正していました。事前に決め打ちされたテクスチャとシェーダ間の接続とハードコーディングされたテクスチャカラー補正オプションは、今では、入力のテクスチャ画像やプロシージャルテクスチャジェネレータを画像処理やブレンド演算子のツリーを介してシェーダの色々な入力に接続して構築するより柔軟性のあるノードグラフベースのシェーダネットワークに移行されています。
>
CGオブジェクトの完全な”ルック”を指定するのに必要な明確な相関データの関係性が少なくとも4つあります:
>
    ソース、オペレータ、コネクション、パラメータによる 画像処理ネットワーク の組み方に応じて、出力される空間的可変なデータストリームの数が異なります。
    テクスチャファイル名またはIDと関連付けた ジオメトリ固有の情報 に応じてマップタイプが異なります。
    空間的可変データストリーム、ユニフォーム値、BxDFシェーダの入力との関連付け次第で、定義する マテリアル の数が異なります。
    マテリアルと特定のジオメトリの関連付け次第で、作成する ルック の数が異なります。
>
私共が知る限り、上記のデータの関係性すべてを転送するための共通のオープン規格は存在しません。アプリケーション毎にこの情報を格納するための独自のファイルフォーマットがありますが、それらのフォーマットは非公開や独自規格になっていて完全にアプリケーションを開いたり再現するのに十分な仕様化も実装化もされていません。そのため、シェーダネットワークによって構築されたCGオブジェクトの”ルック”を指定するには、そのようなルック(またはルックのサブコンポーネント)をあるソフトウェアパッケージから別のソフトウェアパッケージ間または設備間で渡るようにオープンでプラットフォームに依存しない明確に定義された規格が必要です。
>
1.2. 提案
>
新しいマテリアルコンテンツスキーマである MaterialX 、さらにそのMaterialXのコンテンツを読み書きするためのXMLベースのファイルを提案します。MaterialXスキーマは、いくつかの主要エレメントタイプといくつかの補足的サブエレメントタイプを定義します。 主要エレメントタイプは以下のとおり:
>
    データ処理グラフを定義した 標準ノード セット
    <nodedef> BxDFシェーダとカスタム処理オペレータによって標準ノードセットを拡張します。
    <material> 特定のユニフォームパラメータ値と空間的可変入力データストリームにバインドされたシェーダインスタンスを定義します。
    <geominfo> ノードグラフから参照可能なジオメトリアトリビュートを定義します。
    <look> マテリアルとプロパティをジオメトリに紐づけた特定の組み合わせを定義します。
>
MTLXファイルは、MaterialXドキュメントを表現した標準XMLファイルです。そこで使用されているXMLエレメントとアトリビュートがMaterialXのエレメントとアトリビュートに該当します。MTLXファイルは、完全に自己完結したファイルにしたり、コンポーネントの共有と再利用ができるように複数ファイルに分割することができます。

ざっくり言うと、シェーダグラフの定義やマテリアルのアサインメントをXML形式のファイルによって異なるDCC間でデータ交換するための仕組みです。

例えば、Mayaでキャラクタのモデルを作成し、Arnoldでシェーダグラフを構築して、そのマテリアルをモデルに割り当てたとします。
そのMayaシーンはMayaでレンダリングできるわけですが、別のDCC(HoudiniやKatana)でルックデブしたいとなった場合を想像すればMaterialXがなぜ必要なのかわかると思います。
別のDCCにMayaシーンをFBXやAlembic経由で渡すとどうなるでしょう？ジオメトリはまぁ渡るでしょうけども、シェーダグラフ渡らないですよね？
じゃぁ別のDCCでシェーダグラフを構築し直して、マテリアルを再割り当てするってのは生産性が非常に悪い作業です。
ましてや、プロキシ(Arnold用語だとStand-In、Maya用語だとGPUキャッシュ)だと直接ジオメトリにアクセスできないです。
レンダラーがArnoldで統一されているならば、MaterialXを使わなくてもArnold標準フォーマットのassやUSDを使えばこの問題を解決させることはできますがレンダラーが異なる場合だとどうでしょう？
レンダラーが異なれば用意されているシェーダが異なります。
公式のMaterialXではOSLをベースにした標準ノードが用意されているので、それらの標準ノードだけでシェーダグラフを組めば、MaterialXに対応したレンダラーでそのままレンダリングをすることができます。
HoudiniのKarmaはそのOSLの代わりにVOPでその標準ノードが用意されています。

#MaterialXViewerを使ってみよう
MaterialXのシェーダグラフをビューイングしたり、MaterialXからOSL/MDL/GLSLにシェーダ変換することができるビューアがあるので、まずはこれを使ってMaterialXを遊んでみましょう。
1. まずは、MaterialXViewerを https://github.com/materialx/MaterialX/releases から使用するマシンの環境に応じて必要なzipをダウンロードします。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/9187e7b3-35f8-a14e-96c0-926d35678679.png)
2.zipを任意の場所に解凍すると、中身が以下のようになっています。`bin`フォルダ内にあるMaterialXView.exeを実行すると**MaterialXViewer**が起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/3004190a-e352-ae21-75d0-febd483eb19d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/de92ca08-13e4-4139-5ddc-74b50fcdfb17.png)
3.**MaterialXViewer**の使い方は以下のドキュメントを参照してください。
https://github.com/materialx/MaterialX/blob/main/documents/DeveloperGuide/Viewer.md
4.**Load Material**ボタンをクリックして、`MaterialXViewerインストールディレクトリ/resources/Materials/Examples/StandardSurface`下にMaterialXファイルのサンプルが入っているのでどれかを選択してみましょう。ビューア内で`O`キーを押せば同じMaterialXファイルの場所にOSLファイルが、`G`キーでGLSLファイル、`M`キーでMDLファイルに変換されます。

MaterialXViewerは、MaterialXファイルを読み込んでOSL/GLSL/MDLファイルに変換することができます(元々はShaderXというプロジェクトによるものだったのですが、いつのまにかMaterialXプロジェクトと統合されました)。
このMaterialXViewerは、どのMaterialXファイルも読めるわけではなく、MaterialXGenGLSLによって内部的にGLSLを表示しているので、レイトレーシングな処理のノード例えばAmbient Occlusionが組まれたMaterialXファイルを表示することはできないです。またMaterialX標準ノード以外のノード例えばArnoldノードが組まれたMaterialXも表示できるわけではありません。例外としてaiStandardSurfaceは表示できます。それはMaterialX Standard Surfaceに呼応しているからです。

#SolarisでMaterialXファイルをインポートする
MaterialXファイルを読み込むには、Reference LOPでファイルを指定すればよいだけです。
これでシーングラフツリーにマテリアルが追加されるので、Assign Material LOPでターゲットのPrimにそのマテリアルパスを指定するとレンダリングされます。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/3fdb1f0c-bd10-f673-21d2-48175f82ba1f.png)

#MaterialXファイルをエクスポートする
~~現在のところ、HoudiniはMaterialXのエクスポートに対応していません。~~
MaterialXのエクスポートをHoudini標準のエクスポータではなく独自にエクスポートしたいと思います。
Pythonシェルで`import MaterialX`と打ってみるとMaterialXモジュールが標準で使えるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/7ff43b75-1196-e3c4-c8e3-44f017b8c790.png)
これならば、自分でエクスポータを作ることが可能です。
MaterialXファイルはXMLフォーマットなのでXMLパーサーを扱ったことがある人なら直感的に制御できるのではないかと思います。
今回は、Houdiniのmatコンテキスト内で構築されたMaterialXノードグラフをMaterialXファイルにエクスポートするPythonスクリプトを書きたいと思います。
#はじめてのMaterialXスクリプト
MaterialXエクスポータを作成する前に、MaterialXスクリプトの基本となるコードを紹介します。

```python:StandardSurfaceノードを追加し、パラメータを設定するシンプルなPythonスクリプト
import MaterialX as mx
doc = mx.createDocument()
#addNode(追加したいMaterialXノードタイプ名,追加されるノードの任意の名前)は、ドキュメント内にノードを追加します。
nodeElement = doc.addNode("standard_surface","SampleNode1")
#setType(追加したノードの出力タイプ)は、追加したノードの出力タイプを設定します。
#異なる出力タイプを持つノードタイプを追加した場合に必須。
nodeElement.setType("surfaceshader")
#setInputValue(デフォルト値から変更したいパラメータ名,設定する値,パラメータのタイプ)は、パラメータの値を変更します。
#これで設定しなかったパラメータはデフォルト値が使用されます。
nodeElement.setInputValue("base_color", "1.0,0.0,0.0" ,"color3")
nodeElement.setInputValue("specular", "0.0" ,"float")
#addMaterial(任意のマテリアル名)は、ノード/ノードグラフが格納される箱となるマテリアルを追加します。必須。
material = doc.addMaterial("SampleMaterial")
#setConnectedNode(接続する入力名,接続先のノード/ノードグラフ)は、入力をノード/ノードグラフに接続します。
material.setConnectedNode("surfaceshader", nodeElement)
#writeToXmlFile(MaterialXドキュメントオブジェクト,保存先のパス)はmtlxファイルを書き出します。
mx.writeToXmlFile(doc, "C:/MaterialX/helloworld.mtlx")
```
このPythonスクリプトを実行すると、以下のようなXML書式のファイルが書き出されます。

```xml:出力されたMaterialXファイル
<?xml version="1.0"?>
<materialx version="1.38">
  <standard_surface name="SampleNode1" type="surfaceshader">
    <input name="base_color" type="color3" value="1.0,0.0,0.0" />
    <input name="specular" type="float" value="0.0" />
  </standard_surface>
  <surfacematerial name="SampleMaterial" type="material">
    <input name="surfaceshader" type="surfaceshader" nodename="SampleNode1" />
  </surfacematerial>
</materialx>
```

このスクリプトを見ると、MaterialXノードのタイプ、そのノードが持つパラメータの名前とタイプ、ノードの出力タイプが分かればMaterialXファイルに書き出すことができることが理解できたかと思います。
これらの情報は、`Houdiniインストールディレクトリ/materialx/libraries`下にあるmtlxファイル群で記述されており、
例えば、standard_surfaceノードは、その`bxdf`サブフォルダ下の`standard_surface.mtlx`で定義されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/42892327-e633-1616-b138-4098ff23f314.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/b4ee2e14-9e27-2945-0e7d-ba68cb624b37.png)

MaterialXモジュールによって`/materialx/libraries`下にあるすべてのmtlxファイルを読み込んで、それらの情報を調べることもできます。

```python:MaterialXのノード定義を調べる
import MaterialX as mx
import hou
searchPath = hou.text.expandString("$HH/materialx")
ref = mx.createDocument()
#指定したパス下にあるすべてのmtlxファイルを読み込みます。
mx.loadLibraries(["libraries"],searchPath,ref)
#NodeDefオブジェクトからノード定義の名前、そのノードタイプ、出力タイプをプリントします。
for eachNodeDef in ref.getNodeDefs():
    print(eachNodeDef.getName(),",Type:",eachNodeDef.getNodeString(), ",Output:",eachNodeDef.getType())
```

このように情報を取得して、あとはMaterialX APIドキュメント( https://www.materialx.org/docs/api/index.html )とにらめっこすることで色々なMaterialXファイルを作ることができます。

#Houdiniで組んだMaterialXノードグラフをMaterialXファイルにエクスポートする
下図のようにMaterialXノードグラフを構築しました。
**MaterialXノードグラフを構築する上で注意してほしいのは、入力と出力のタイプを同じに合わせる必要があります。よくやりがちなのはvector3の出力をcolor3の入力に接続してしまうことです。これは他のノードグラフだとそのような操作をしがちですので注意してください。**
これを独自に組んだPythonスクリプトでMaterialXファイルにエクスポートしたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/5b7fbce9-37b0-fcb0-2c2b-f08804af44c4.png)
MaterialXの書式で見ると、
`MtlX Surface Material`ノードが`addMaterial`メソッドで追加されるsurfacematerialエレメントで、これは必須です。
単体のノードが`addNode`メソッドで追加されるNodeエレメントです。
MaterialXにはNodeGraphエレメントがありそれがHoudiniのSubnetに呼応します。
なのでSubnetノードは`addNodeGraph`メソッドで追加します。

Houdiniで組んだMaterialXノードグラフをMaterialXに書き出す上でまず必要なのはMaterialX系VOPノードからMaterialXのノードタイプ、出力タイプを調べる必要があります。今回組んだスクリプトはMaterialX系VOPのノードタイプ名とsignatureパラメータから判断して作りました。
以上のことを踏まえて組んだPythonスクリプトが以下です。
`/stage/`コンテキスト直下に配置された`materialx`という名前のMaterial Network内にある全ての`MtlX Surface Material`ノード毎にhipファイルと同じ場所にmtlxファイルを自動生成します。

```python:
import hou
import MaterialX as mx
import re

signatureLabeltoValueMap ={
	"Boolean (Float)":"boolean_float",
	"Boolean":"boolean",
	"BSDF (C)":"bsdfC",
	"BSDF (F)":"bsdfF",
	"BSDF":"bsdf",
	"Color (B)":"color3B",
	"Color (Color 4)":"color3_color4",
	"Color (Color Array)":"color3_color3array",
	"Color (Color)":"color3_color3",
	"Color (FA)":"color3FA",
	"Color (Float)":"color3_float",
	"Color (I)":"color3I",
	"Color (Vector 2)":"color3_vector2",
	"Color (Vector 3)":"color3_vector3",
	"Color (Vector 4)":"color3_vector4",
	"Color 4 (B)":"color4B",
	"Color 4 (CF)":"color4CF",
	"Color 4 (Color 4 Array)":"color4_color4array",
	"Color 4 (Color 4)":"color4_color4",
	"Color 4 (Color)":"color4_color3",
	"Color 4 (FA)":"color4FA",
	"Color 4 (Float)":"color4_float",
	"Color 4 (I)":"color4I",
	"Color 4 (Vector 2)":"color4_vector2",
	"Color 4 (Vector 3)":"color4_vector3",
	"Color 4 (Vector 4)":"color4_vector4",
	"Color 4 Array (Color 4 Array)":"color4array_color4array",
	"Color 4":"color4",
	"Color Array (Color Array)":"color3array_color3array",
	"Color":"color3",
	"Displacement Shader (F)":"displacementshaderF",
	"Displacement Shader (V)":"displacementshaderV",
	"Displacement Shader":"displacementshader",
	"EDF (C)":"edfC",
	"EDF (F)":"edfF",
	"EDF":"edf",
	"filename":"filename",
	"Float (B)":"floatB",
	"Float (Color 4)":"float_color4",
	"Float (Color)":"float_color3",
	"Float (Float Array)":"float_floatarray",
	"Float (I)":"floatI",
	"Float (Vector 2)":"float_vector2",
	"Float (Vector 3)":"float_vector3",
	"Float (Vector 4)":"float_vector4",
	"Float Array (Float Array)":"floatarray_floatarray",
	"Float":"float",
	"Integer (Float)":"integer_float",
	"Integer (Integer Array)":"integer_integerarray",
	"Integer Array (Integer Array)":"integerarray_integerarray",
	"Integer":"integer",
	"Light Shader":"lightshader",
	"Matrix 3x3 (FA)":"matrix33FA",
	"Matrix 3x3":"matrix33",
	"Matrix 4x4 (FA)":"matrix44FA",
	"Matrix 4x4":"matrix44",
	"String (String Array)":"string_stringarray",
	"String Array (String Array)":"stringarray_stringarray",
	"String":"string",
	"Surface Shader (C)":"surfaceshaderC",
	"Surface Shader (F)":"surfaceshaderF",
	"Surface Shader":"surfaceshader",
	"VDF (C)":"vdfC",
	"VDF (F)":"vdfF",
	"VDF":"vdf",
	"Vector 2 (B)":"vector2B",
	"Vector 2 (Color 4)":"vector2_color4",
	"Vector 2 (Color)":"vector2_color3",
	"Vector 2 (FA)":"vector2FA",
	"Vector 2 (Float)":"vector2_float",
	"Vector 2 (I)":"vector2I",
	"Vector 2 (M3)":"vector2M3",
	"Vector 2 (Vector 2 Array)":"vector2_vector2array",
	"Vector 2 (Vector 2)":"vector2_vector2",
	"Vector 2 (Vector 3)":"vector2_vector3",
	"Vector 2 (Vector 4)":"vector2_vector4",
	"Vector 2 Array (Vector 2 Array)":"vector2array_vector2array",
	"Vector 2":"vector2",
	"Vector 3 (B)":"vector3B",
	"Vector 3 (Color 4)":"vector3_color4",
	"Vector 3 (Color)":"vector3_color3",
	"Vector 3 (FA)":"vector3FA",
	"Vector 3 (Float)":"vector3_float",
	"Vector 3 (I)":"vector3I",
	"Vector 3 (M4)":"vector3M4",
	"Vector 3 (Vector 2)":"vector3_vector2",
	"Vector 3 (Vector 3 Array)":"vector3_vector3array",
	"Vector 3 (Vector 3)":"vector3_vector3",
	"Vector 3 (Vector 4)":"vector3_vector4",
	"Vector 3 Array (Vector 3 Array)":"vector3array_vector3array",
	"Vector 3":"vector3",
	"Vector 4 (B)":"vector4B",
	"Vector 4 (Color 4)":"vector4_color4",
	"Vector 4 (Color)":"vector4_color3",
	"Vector 4 (FA)":"vector4FA",
	"Vector 4 (Float)":"vector4_float",
	"Vector 4 (I)":"vector4I",
	"Vector 4 (Vector 2)":"vector4_vector2",
	"Vector 4 (Vector 3)":"vector4_vector3",
	"Vector 4 (Vector 4 Array)":"vector4_vector4array",
	"Vector 4 (Vector 4)":"vector4_vector4",
	"Vector 4 (VF)":"vector4VF",
	"Vector 4 (VV)":"vector4VV",
	"Vector 4 Array (Vector 4 Array)":"vector4array_vector4array",
	"Vector 4":"vector4",
	"Volume Shader (C)":"volumeshaderC",
	"Volume Shader (F)":"volumeshaderF",
	"Volume Shader":"volumeshader",
}

nosignatureOutputMap={
	"surface":"surfaceshader",
	"dispacement":"displacementshader",
	"vector2":"vector2",
	"float":"float",
	"color":"color3",
	"vector4":"vector4",
	"vdf":"vdf",
	"vector":"vector3",
	"bsdf":"bsdf",
	"edf":"edf",
}

def defineNodeAndNodeGraph(outputNode,doc,ref):
	for eachVop in outputNode.inputAncestors():
		nodeName = eachVop.name()
		nodeTypeName = eachVop.type().name()
		#ノードグラフを追加する
		if nodeTypeName == "subnet":
			subnetOutput = None
			for eachChild in eachVop.children():
				if eachChild.type().name()=="suboutput":
					subnetOutput = eachChild
					break
			nodeGraph = doc.addNodeGraph(nodeName)
			defineNodeAndNodeGraph(subnetOutput,nodeGraph,ref)
		#ノードを追加する
		else:
			if nodeTypeName.startswith("mtlx"):
				nodeTypeName = nodeTypeName.split("mtlx")[-1]
			else:
				continue
			#Separate系のノードにsignatureパラメータがないのでVOPノードタイプ名からMaterialXノードタイプを判断する
			if nodeTypeName.endswith(("3c","4c","3v","4v")):
				nodeTypeName = nodeTypeName[:-1]

			signatureValue = ""
			signature = eachVop.parm("signature")
			if signature != None:
				signatureLabel = signature.menuLabels()[signature.menuItems().index(signature.eval())]
				if signatureLabel in signatureLabeltoValueMap:
					signatureValue = signatureLabeltoValueMap[signatureLabel]

			refNodeDef = None
			if signatureValue != "":
				refNodeDef = ref.getNodeDef("ND_"+nodeTypeName+"_"+signatureValue)
			else:
				refNodeDef = ref.getNodeDef("ND_"+nodeTypeName)
			if refNodeDef == None:
				for outputType in eachVop.outputDataTypes():
					if outputType in nosignatureOutputMap:
						refNodeDef = ref.getNodeDef("ND_"+nodeTypeName+"_"+nosignatureOutputMap[outputType])
						if refNodeDef != None:
							break
			if refNodeDef == None:
				continue

			nodeElement = doc.addNode(refNodeDef.getNodeString(),nodeName)
			nodeElement.setType(refNodeDef.getType())
		
			for eachInput in refNodeDef.getInputs():
				parmType = eachInput.getType()
				value = eachInput.getValueString()
				eachParmName = eachInput.getName()
				
				if parmType in ["color2","color3","color4","matrix33","matrix44","vector2","vector3","vector4"]:
					tupleValues = 0
					if value != "":
						tupleValues = tuple([float(i.strip()) for i in value.split(",")])
					vopParmName = eachParmName+"_"+signatureValue
					parmCheck = eachVop.parmTuple(vopParmName)
					if parmCheck == None:
						vopParmName = eachParmName
					evaluatedValues = eachVop.parmTuple(vopParmName).eval()
					if len(evaluatedValues)==1:
						evaluatedValues = evaluatedValues*int(re.sub(r"\D","",parmType))
					if evaluatedValues != tupleValues:
						nodeElement.setInputValue(eachParmName, ",".join([str(i) for i in evaluatedValues]), parmType)
				elif parmType == "integer":
					vopParmName = eachParmName+"_"+signatureValue
					parmCheck = eachVop.parm(vopParmName)
					if parmCheck == None:
						vopParmName = eachParmName
					evaluatedValue = eachVop.parm(vopParmName).eval()
					if evaluatedValue != int(value):
						nodeElement.setInputValue(eachParmName, str(evaluatedValue), parmType)
				elif parmType == "float":
					vopParmName = eachParmName+"_"+signatureValue
					parmCheck = eachVop.parm(vopParmName)
					if parmCheck == None:
						vopParmName = eachParmName
					evaluatedValue = eachVop.parm(vopParmName).eval()
					if evaluatedValue != float(value):
						nodeElement.setInputValue(eachParmName, str(evaluatedValue), parmType)
				elif parmType in ["string","filename"]:
					vopParmName = eachParmName+"_"+signatureValue
					parmCheck = eachVop.parm(vopParmName)
					if parmCheck == None:
						vopParmName = eachParmName
					evaluatedValue = eachVop.parm(vopParmName).eval()
					if evaluatedValue != value:
						nodeElement.setInputValue(eachParmName, evaluatedValue, parmType)
				elif parmType == "boolean":
					if value == "true":
						value = True
					else:
						value = False
					vopParmName = eachParmName+"_"+signatureValue
					parmCheck = eachVop.parm(vopParmName)
					if parmCheck == None:
						vopParmName = eachParmName
					evaluatedValue = eachVop.parm(vopParmName).eval()
					if evaluatedValue != value:
						nodeElement.setInputValue(eachParmName, str(evaluatedValue).lower(), parmType)
	return

def connectNodeAndNodeGraph(outputNode,doc):
	for eachVop in outputNode.inputAncestors():
		if eachVop.type().name() == "subnet":
			eachNodeElement = doc.getNodeGraph(eachVop.name())
			suboutputNode = None
			for i in eachVop.children():
				if i.type().name()=="suboutput":
					suboutputNode = i
					break
			connectNodeAndNodeGraph(suboutputNode,eachNodeElement)
		else:
			eachNodeElement = doc.getNode(eachVop.name())
	
		for eachConnect in eachVop.inputConnections():
			inputNode = eachConnect.inputNode()
			inputNodeTypeName = inputNode.type().name()
			connectedParm = eachConnect.outputName()
			targetParm = eachConnect.inputName()
			
			if inputNodeTypeName == "subnet":
				targetNodeElement = doc.getNodeGraph(eachConnect.inputNode().name())
				targetOutput = targetNodeElement.getOutput(targetParm)
				if not targetOutput:
					targetOutput = targetNodeElement.addOutput(targetParm)
				subnetOutputType = eachConnect.outputDataType()
				if subnetOutputType == "color":
					targetOutput.setType("color3")
				elif subnetOutputType == "color2":
					targetOutput.setType("color2")
				elif subnetOutputType == "color4":
					targetOutput.setType("color4")
				elif subnetOutputType == "float":
					targetOutput.setType("float")
				elif subnetOutputType == "vector":
					targetOutput.setType("vector3")
				elif subnetOutputType == "vector2":
					targetOutput.setType("vector2")
				elif subnetOutputType == "vector4":
					targetOutput.setType("vector4")
				elif subnetOutputType == "int":
					targetOutput.setType("integer")
				elif subnetOutputType == "bool":
					targetOutput.setType("boolean")
				elif subnetOutputType == "surface":
					targetOutput.setType("surfaceshader")
				elif subnetOutputType == "displacement":
					targetOutput.setType("displacementshader")
				
				subnetOutputNode = inputNode.subnetTerminalChild(targetParm)[0]
				for i in subnetOutputNode.inputConnections():
					if i.outputName() == targetParm:
						outputNodeName = i.inputNode().name()
						break
				targetOutput.setConnectedNode(targetNodeElement.getNode(outputNodeName))
				
				eachNodeElement.setConnectedOutput(connectedParm,targetOutput)

			else:
				targetNodeElement = doc.getNode(eachConnect.inputNode().name())
				eachNodeElement.setConnectedNode(connectedParm,targetNodeElement)
	return

searchPath = hou.text.expandString("$HH/materialx")

ref = mx.createDocument()
mx.loadLibraries(["libraries"],searchPath,ref)

materialxNetwork = hou.node("/stage/materialx")

if materialxNetwork != None and materialxNetwork.type().name() == "matnet":
	for eachVop in materialxNetwork.recursiveGlob("*",filter=hou.nodeTypeFilter.Vop):
		if eachVop.type().name()!="mtlxsurfacematerial":
			continue
		outputNodeName = eachVop.name()
		surfaceshaderName = ""
		displacementshaderName = ""
		for eachConnect in eachVop.inputConnections():
			if eachConnect.outputName() == "surfaceshader":
				surfaceshaderName = eachConnect.inputNode().name()
			elif eachConnect.outputName() == "displacementshader":
				displacementshaderName = eachConnect.inputNode().name()
		if surfaceshaderName == "":
			continue
		doc = mx.createDocument()
		defineNodeAndNodeGraph(eachVop,doc,ref)
		connectNodeAndNodeGraph(eachVop,doc)

		material = doc.addMaterial(outputNodeName)
		material.setConnectedNode("surfaceshader", doc.getNode(surfaceshaderName))
		if displacementshaderName != "":
			material.setConnectedNode("displacementshader", doc.getNode(displacementshaderName))

		filePath = hou.text.expandString("$HIP") + "/" + hou.text.expandString("$HIPNAME") + "_" +outputNodeName + ".mtlx"
		mx.writeToXmlFile(doc, filePath)


```
#エクスポートしたMaterialXファイルで遊ぶ
MaterialXファイルをMayaのArnold環境(**MaterialX1.38に対応しているのはArnold7.0**)でaiMaterialXを使用して読み込んだり、MaterialXViewerで読み込んだりすると、このようにシェーダグラフを簡単に渡すことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154191/b109fd3b-d9f9-be86-244b-147150e4e51b.png)

今回使用した作成したhipファイルとpythonスクリプトはこちらです。
https://drive.google.com/file/d/1frXCG4R2Zv2MiFZl0cn2k2RvUgkoEmfc/view?usp=sharing