Wren ROPの使い方とSOHOによる外部アプリとのパイプ

![wren.png](https://qiita-image-store.s3.amazonaws.com/0/154191/f59bd78f-df73-2c0c-9bbe-ad63dc85c6c0.png)

ここでは、Wren ROPの使い方とSOHOによる外部アプリとのパイプの構築について説明します。
おおまかな流れは、

1. Wren ROPを使ってHoudiniの3Dシーンから輪郭線やエッジをベクトルデータとして生成する
1. Houdiniで作成したベクトルデータをドローソフトに読み込む
1. HoudiniでポリゴンカーブをNURBSカーブに変換する
1. SOHOを使ってHoudiniからFreeCADへのパイプを組み込んでNURBSカーブをSVGに変換する
1. おまけ1(HoudiniからSOHOを介してMayaに接続する)
1. おまけ2(MayaからHoudiniに接続する)

について解説しています。
尚、Houdini16.5の時点における解説ですので、バージョンによって内容が異なることがあります。
Wren ROP改良されないかなぁ:dancer_tone2:そんな期待を抱きながら、この記事を書いています。

トップのWren ROPのアイコンの鳥はwrenと呼ばれているスズメ科の鳥だそうです。
さて、需要のある内容なのかどうかわからないけど、他とかぶらなそうなネタを書きます。頑張ります:muscle:

#Wren ROPとは？
HoudiniにはMantraレンダラーの他にもベクトルレンダラーのWrenが用意されています。
この**Wren**("レン"と呼びます)は、**W**ireframe **Ren**dererのことで、シーン内のオブジェクトのシルエットや尖ったエッジ(今後は**カスプエッジ**と呼ぶことにします)をワイヤーフレームとしてレンダリングすることができます。
ベクトルレンダラーなので、レンダリング結果をベクトルデータとして出力することができます。

WrenはMantraと同様にRenderライセンスを消費するので、一度IFD(レンダリング用のシーン記述ファイル)ファイルを書き出してしまえばHoudiniライセンスを使わずに分散レンダリングさせることができます。
ここで注意しておかなければならないのは、Mantraが書き出すIFDとWrenが書き出すIFDは**まったく別物**ということです。
つまり、Wrenから書き出したIFDをMantraコマンドでレンダリングすることはできません。Wrenコマンドを使用します。
但し、Apprentice/IndieライセンスではIFDの書き出しは不可になっています。

まずは、テキトーにモデルを用意します。私の方はシェーダボール、ボックス、グリッドを配置しています。
![SampleModel.png](https://qiita-image-store.s3.amazonaws.com/0/154191/b58a7580-73fd-0292-0b3e-08041df4d6c6.png)
Wren ROPの使い方はMantraと同様にネットワークエディタのoutコンテキスト内にWren ROPを追加します。
Wren ROPのアイコンって可愛いっすよね。チュンチュン:hatched_chick:
![wrenROP.png](https://qiita-image-store.s3.amazonaws.com/0/154191/54d336e6-eaa5-3537-fec1-e3fd1f5b7f41.png)
Wren ROPのデフォルトのままの挙動を見てみたいので、Wren ROPのパラメータエディタの**Render to Disk**ボタンを実行します。するとMPlayerが立ち上がってレンダリング結果が表示されます。
![defaultResult.png](https://qiita-image-store.s3.amazonaws.com/0/154191/f3e0a39c-062e-d507-44ba-31a2ab93e488.png)
シルエットには線が出てますが、カスプエッジ部分が出てませんね。
Mantraレンダラーなら法線アトリビュートからカスプエッジを認識できるのに対して、Wrenレンダラーは法線アトリビュートを見ません。
Wrenでは線幅を意味したlinewidthアトリビュートしか認識することができません。
それ以外のアトリビュートを認識しないので、カスプエッジとして認識させる場合には **頂点のポイントを分離** (ポイント数が増えてしまう)させる必要があります。

これは、**Fuse SOP**、**Edge Cusp SOP**、**Facet SOP**を使えば可能です。
以下は**Fuse SOP**の"Unique"を有効にするか、または**Edge Cusp SOP**をすべてのオブジェクトに適用してみた結果です。
![FuseSOP.png](https://qiita-image-store.s3.amazonaws.com/0/154191/9d5ff8d6-ea07-c579-b0e2-196cfa819b3d.png)
![FuseResult.png](https://qiita-image-store.s3.amazonaws.com/0/154191/23fc79ef-6903-6c1d-be38-35595e0cc8be.png)
完全にポリゴンの輪郭が線として出たのが分かります。
カスプエッジの部分のポイントだけを分離させたい場合は**Facet SOP**を使ったほうが楽です。
![FacetSOP.png](https://qiita-image-store.s3.amazonaws.com/0/154191/c2976f18-9f61-2ed2-82cd-ca5efeae6a85.png)
**Cusp Polygons**のチェックを有効にして、**Cusp Angle**に閾値の角度を設定します。これはポリゴンの法線間の角度なので値が低いほど検出されるカスプエッジが増えます。
![FacetResult.png](https://qiita-image-store.s3.amazonaws.com/0/154191/1580824e-118d-f8ac-a86a-5ae37a1ae879.png)
Mantraで塗りの絵を出して、Wrenで線だけの絵を出して、それをコンプすればトゥーンな絵を作ることができます。
その場合にはWren ROPの**Properties**タブの**Render**サブタブの**Shading Model**を**black wire/matte fill**(線の色を黒、塗りつぶしなし)、好みに応じて**Line Width**に線幅を指定します。
![ShadingModel.png](https://qiita-image-store.s3.amazonaws.com/0/154191/bc063efc-3059-0027-d899-a738eb9b0b4f.png)
![Comp.png](https://qiita-image-store.s3.amazonaws.com/0/154191/ddea7598-e82f-cb94-44bd-a47072ab07b9.png)

Wren ROPの欠点は、

- スムーズシェーディングに対応していないので、滑らかにするためにモデルを**物理的に細分化**する必要がある
- ディスプレイスメントに対応していない
- シングルスレッド
- (Point Consolidationを有効にしていても)重複ポイントがちゃんと結合されない
- "N"や"creaseweight"といったアトリビュートが反映されない

が挙げられます。
ただどうしても
**線は滑らかにしたい。ポリゴンカーブなんていやだ。**
**重複ポイントは結合したい。**
ので、Wrenから曲線ジオメトリを書き出してHoudiniや他のドローソフト(IllustratorとかInkscape)でそれを編集できるようにしたいと思います。

##WrenからPostscriptを書き出す
Wren ROPの**Properties**タブの**Output**サブタブの**Output Picture**に拡張子を**.eps**にして出力ファイル先を指定します。Postscriptの拡張子は通常は.psなのですが**Houdiniでは.epsにしないとファイルを読み込むことができない**ので注意してください。
Wren ROPから出力されるPostscriptのフォーマットはPostScript Level 1です。
そして、
**Output Postscript**のチェックを付けるか
![EnablePostScript.png](https://qiita-image-store.s3.amazonaws.com/0/154191/0c30c497-7c3b-ddb1-176b-7e3720482c98.png)
または、**Main**タブの**Command**に**-p**オプションを指定します
![Commandline.png](https://qiita-image-store.s3.amazonaws.com/0/154191/1647c364-6c00-e947-c5f4-165c73b3cfeb.png)
のどちらかの方法でPostscriptを書き出すことができます。
前者だと出力されるPostscriptのサイズ(ページサイズのことです)を指定することができないので、
サイズを明示的に指定したい場合には後者の方法を使って**-w**(幅),**-h**(高さ)を指定します。

##IllustratorやInkscapeで読み込み可能なPostscriptファイルを作成する
_[Inkscape](https://inkscape.org/ja/download/)はデフォルトではPostscriptファイルを読み込むことはできません。
事前に[Ghostscript](https://www.ghostscript.com/download/gsdnld.html)をインストールし、Pathシステム環境変数を```C:\Program Files\gs\gs9.22\bin;C:\Program Files\gs\gs9.22\lib```のようにGhostscriptのbinとlibのパスを追加しておく必要があります。_

**Command**パラメータに指定する```wren -p -w 幅 -h 高さ```の
幅と高さの単位は`インチ`です。
Postscriptの1単位は`1インチ/72`です。
**-w**と**-h**のオプションを指定しなかった場合は、カメラ解像度のサイズ、例えば1280x720のカメラならページサイズが1280インチ、720インチのPostscriptが定義されてしまい、これはIllustratorでもInkscapeでも読み込むことはできません。そのため、ちゃんとページサイズを指定しておきましょう。
![Inkscape.png](https://qiita-image-store.s3.amazonaws.com/0/154191/369082f7-3ee9-11c6-fe3c-263b633f4ba4.png)

##HoudiniでPostscriptファイルを読み込む
上記ではIllustratorやInkscapeで読み込むにはページサイズを指定しておきましょうと言いましたが、Houdiniで編集する場合には都合が悪いです。実際にFile SOPを使ってそのepsファイルを読み込むとその線が非常に粗いことがわかります。
これはページサイズを指定したことで本来持っている精度が欠落してしまったからです。
例えば綺麗な画像の解像度を下げた後に解像度を上げるとめっちゃ粗くなるやつ。
![Pagesize.png](https://qiita-image-store.s3.amazonaws.com/0/154191/97ca41f0-495d-7ff3-89b1-690b64b6f88d.png)
そのため、wrenコマンドに-wと-hを指定しないでPostscriptを書き出します。
そして読み込んでみると・・・
![NoPagesize.png](https://qiita-image-store.s3.amazonaws.com/0/154191/bec07fcc-7def-e15a-506c-d97def84bf72.png)
まずは、このワイヤーフレームをカメラから見て元のシーンと重なるようにしたいと思います。
方法は色々あると思いますが、もっともわかりやすい方法でやってみます。
File SOPの後に、EPSファイルの中心がXY原点になるようにトランスフォームさせ、それをカメラの画角から均一スケールをかければできます。接続は以下のとおり。
![Transform.png](https://qiita-image-store.s3.amazonaws.com/0/154191/0fab3008-bc44-4cfa-3c1e-0aca1f322ef6.png)
1個目のTransform SOPで
Translate Xのエクスプレッション:```-0.5*ch("../../cam1/resx")*72```
Translate Yのエクスプレッション:```-0.5*ch("../../cam1/resy")*72```
2個目のTransform SOPで
Uniform Scaleのエクスプレッション:```-ch("tz")/ch("../../cam1/focal") * ch("../../cam1/aperture")/ (72*ch("../../cam1/resx"))```
それをCamノードの子にする。
![CameraTransform.png](https://qiita-image-store.s3.amazonaws.com/0/154191/998c00d2-b941-5c64-7ed1-5c10cf846d51.png)
これで、Postscriptをカメラの前に配置できるようになりました。
このワイヤーフレームの位置は、上記の2個目のTransform SOPのTranslate Zの値で調整することができます。
このZ値はカメラのNearクリップ平面よりは奥に位置するようにします。
![CamProject.png](https://qiita-image-store.s3.amazonaws.com/0/154191/81d74706-5eb5-e4bf-0951-3ed7eb1dd688.png)
カメラ視点でみると、こんな感じになります。
![CameraLook.png](https://qiita-image-store.s3.amazonaws.com/0/154191/b12b1303-abf8-f388-f313-7c8fad7fa3e0.png)

##HoudiniでポリゴンカーブをNURBSカーブに変換する
**Houdiniの良いところは、ポリゴン/Bezier/NURBSの双方向の変換やOpenVDBとポリゴンの双方向の変換が得意なところかと思います。**
Wrenで生成したカーブはポリゴンカーブなのでハンドリングしにくい上にカックカクです。
そのため、Houdiniを使ってポリゴンカーブをNURBSカーブに変換してみます。
まず、クリーンアップしていきます。
重複ポイントは**Fuse SOP**で結合します。
プリミティブの数が多いので**PolyPath SOP**を使って見た目が一本線に見えるプリミティブを結合しちゃいます。
以下のようにワイヤーフレームがすっきりしました。
![polypath.png](https://qiita-image-store.s3.amazonaws.com/0/154191/6b9d88e5-aa3a-573b-06f2-8b8765e1c6d2.png)
シェーダボールに何か微小な線があるので、それは**Delete SOP**でエクスプレッションで自動的に消去します。
```prim(ノードのパス,$PR,"intrinsic:measuredperimeter",0)<長さ```
線の長さは**intrinsic:measuredperimeter**で調べることができます。
Sort SOPでポイント番号の並びをカーブ沿いに揃えたいので**By Vertex Order**を実行します。
![CleanUp.png](https://qiita-image-store.s3.amazonaws.com/0/154191/412159ac-c8e3-b8e9-3228-a4cd4e67f18b.png)
ポリゴンカーブをNURBSに変換する上で、以下のような一本線は都合が悪いです。
![TroublesomeCurve.png](https://qiita-image-store.s3.amazonaws.com/0/154191/f1185201-5aef-9787-3414-e5f3be1a7f0d.png)
角張った一本線は分割したいです。線の分割は**PolyCut SOP**でできますが、パラメータを見てみると分割点を指定する必要がありそうです。んー手動でやりたくないんだよね:triumph:
![PolycutParam.png](https://qiita-image-store.s3.amazonaws.com/0/154191/b4d6846e-b886-826e-a8fd-f72fecb81a19.png)
一本線が折れている部分の点をここに自動的に指定できるようにしたいです。そのために、折れた角度が特定の角度よりも大きい箇所の点を調べる必要があります。
なので、ラングっちゃいましょうか。線間の角度をPointアトリビュートとして収めるPrimitive Wrangleを作ります。
![PrimWrangleSplit.png](https://qiita-image-store.s3.amazonaws.com/0/154191/6de3dda9-b977-a672-bd25-7ee7a2ec6fc5.png)

```python:PrimitiveWrangle
int listPoints[] = primpoints(0, @primnum);
for(int i=0;i<len(listPoints);i++){
    if( i==0 || i==len(listPoints)-1){
        setpointattrib(0, "primsplit", listPoints[i] , 0.0, "set");
    }
    else{
        vector curP = point(0, "P", listPoints[i]);
        vector preP = point(0, "P", listPoints[i-1]);
        vector postP = point(0, "P", listPoints[i+1]);
        float thresholdAngle = degrees( acos(dot(normalize(curP - preP) , normalize(postP - curP)) ) );
        setpointattrib(0, "primsplit", listPoints[i] , thresholdAngle, "set");
    }
}
```
ポイントに線間の角度が入ったprimsplitという名前の独自アトリビュートが作成されました。
![primsplit.png](https://qiita-image-store.s3.amazonaws.com/0/154191/3d515658-f62f-90db-7cac-a47314d1a71e.png)
このアトリビュートを**PolyCut SOP**に指定することで、角張った一本線が分割されました。
![primsplitResult.png](https://qiita-image-store.s3.amazonaws.com/0/154191/3ed20e79-9dea-d99e-967a-07be8da98317.png)
後は、**For Each**ブロックで各プリミティブ毎にConvert SOPでNURBSカーブに変換して、その後に**UV Texture SOP**でuv情報を付けます。
![Convert.png](https://qiita-image-store.s3.amazonaws.com/0/154191/3a0a3da0-fd1a-5843-0366-88e7ccbaa033.png)
後は、ワイヤーフレームがカメラの前に表示されるように**上記で説明した2個のTransform SOP**に接続して、
あとは必要に応じて線幅を定義するために**width**アトリビュートを**uv**アトリビュートに基づいて定義すればOKです。
![width.png](https://qiita-image-store.s3.amazonaws.com/0/154191/582cbb29-86a2-1dd2-73c1-e13d18d84ccf.png)
NURBSカーブのポイント(制御点)に基づいてランプで線幅を変化させているので、真っ直線な線は一定で曲線には線幅が変わるようにしています。これがポリゴンカーブだと制御が難しいですよね。
![CameraLookWire.png](https://qiita-image-store.s3.amazonaws.com/0/154191/6928634f-d9d1-9d4c-f260-3aaca6ddf613.png)

一度仕組みを作成してしまえば、オブジェクトを入れ替えるだけで自動的にラインが生成されます。
トポロジーに依存していないので、アニメーションキャラクタだろうが、ボリュームから変換したポリゴンモデルだろうが追従します。
あとは、モデルサイズに応じてライン幅を調整したりすればいいです。

_アニメーションキャラクタに適用した例:_
![Chara.gif](https://qiita-image-store.s3.amazonaws.com/0/154191/bfe13720-b555-76a6-a973-397308cb15b1.gif)

また同じ事を言いますが、**Houdiniの良いところは、ポリゴン/Bezier/NURBSの双方向の変換やOpenVDBとポリゴンの双方向の変換が得意なところです。**
Pyroで爆発を作成して、そのラインを作成したいのであれば、PyroのdenistyボリュームをOpenVDBに変換して、それをポリゴンに変換し、そのモデルに対してラインを作成することができます。
![VDBConvert.png](https://qiita-image-store.s3.amazonaws.com/0/154191/880abe82-4f8a-af92-aee2-1f5cc7367886.png)

_ボリュームから変換したポリゴンモデルに適用した例:_
塗り
![Fill.gif](https://qiita-image-store.s3.amazonaws.com/0/154191/a2113261-25a3-6871-9ea1-690189072eab.gif)
ライン
![Wire.gif](https://qiita-image-store.s3.amazonaws.com/0/154191/7ff28c8d-c6f8-ac8a-eb5c-1d6b88be24a4.gif)
これをコンプ
![Comp.gif](https://qiita-image-store.s3.amazonaws.com/0/154191/65805cc5-ac0a-8307-11e2-5147c2777f13.gif)
ラインはポリゴンカーブではなくNURBSカーブなので柔軟に微調整することができます。

ここまでがWren ROPの使い方の説明になります。
次は、このNURBSカーブをドローソフトで使用できるようにSVGに変換したいと思います。
変換は手動じゃなくて自動でやりたいのでSOHOを使ってみます。

#SOHOとは
MantraなりWrenなり触っているとsoho_なんちゃらのようなパラメータを見かけることがあります。
![sohoparams.png](https://qiita-image-store.s3.amazonaws.com/0/154191/1608bd92-9906-0dbf-f375-8d44c3c18cb1.png)
SOHOとは、**S**cripted **O**utput of **H**oudini **O**bjectsの略で、Houdiniオブジェクトの出力をスクリプト制御するものであることが予想できます。
[SidefxのSOHOのヘルプ](http://www.sidefx.com/docs/houdini/render/soho)を読んでみると、SOHOはHoudiniとレンダラーのPythonバインディングと書いてあります。**バインディング**とは分かりやすく日本語で説明すれば**橋渡し**のことで、要するにHoudiniはSOHOを介してMantraなどのレンダラーと通信します。
Mantra ROPやWren ROPは、デジタルアセットをUIとして、SOHOによってレンダリングが行なわれています。
では、Mantra ROPでsoho_なんちゃらのパラメータを見てみたいと思います。
**soho_program**パラメータが通常では非表示になっているのでそれを表示してみると、Mantra ROPではsoho_programパラメータに**IFD.py**が指定されていることが分かります。
![sohoProgram.png](https://qiita-image-store.s3.amazonaws.com/0/154191/76027e25-3d1a-b35a-9545-66c4e5d9be58.png)
Houdiniは、MantraでレンダリングまたはIFD出力をする時には、SOHOプログラムに対してIFD.pyを実行することが分かります。
`Houdiniインストールディレクトリ\houdini\soho\python2.7`には、Mantra用のIFD.pyやWren用のwren.pyなどのプログラムがあります。`Houdiniインストールディレクトリ\houdini\soho`直下にはSOHOに使用できるメソッドのヘルプが説明されているsoho.docがあります。

Houdiniとレンダラーを橋渡しする役割を担っているのがSOHOであり、独自でSOHOを利用したROPを作成することができます。
SOHOで用意されているメソッドは主にシーン内のどのオブジェクトをレンダリングするのかどうかやカメラの情報を取得といったレンダラー向けのメソッドがほとんどですが、別に**レンダラー以外の他のアプリケーションとHoudiniを橋渡しするためにSOHOを使うこともできます**。
SOHOを利用した独自のROPはPython ROPで作成します。
Python ROPによるSOHOの使い方は、
[SOHOのチュートリアル](https://www.sidefx.com/docs/hdk/_h_d_k__s_o_h_o__examples.html)
が参考になると思いますが、ここでもそのSOHOの使い方を説明したいと思います。
ちなみに、**Python ROPの実行はHoudini Apprentice/Indieのライセンスではできない**ようになっています。
これができてしまったらIFD.pyを使ってApprenticeでもIndieでもIFDの書き出しができちゃいますからね。仕方ない。

Houdiniと外部アプリケーションを橋渡しする方法には、

- ROPに用意されているPre/Post Scriptパラメータを利用する
- Shell ROPを利用する
- Python ROPを利用する

がありますが、今回は柔軟性の高いPython ROPを利用してみます。

##Python ROPを作成する
何かのPython ROPを作成してみたいと思います。
ここではNURBSカーブをSVGファイルに変換するパイプラインを構築したいと思います。
NURBSカーブをSVGに変換する機能はHoudiniには用意されていません。
そのため、他のアプリケーションでその処理をさせてみます。
[FreeCAD](https://www.freecadweb.org/?lang=ja)という無料のCADソフトではNURBSカーブが定義された**iges**というファイルフォーマットを読み込んでSVGにエクスポートすることができます。HoudiniはNURBSカーブをigesファイルにエクスポートすることができます。このFreeCADというソフトは3DCADでよく使用されるstepファイルフォーマットのインポート/エクスポートにも対応している素晴らしいソフトで、しかもPythonから外部制御することができます。
つまり、HoudiniとFreeCADをSOHOを介してパイプに組み込むことができます！
まずはFreeCADをインストールしておきます。

Python ROPを作成するには、**File**メニューから**New Asset**を選択します。
表示されるNew Assetダイアログで**Operator Name**(内部ノード名)と**Operator Label**(ラベル)を好きなように入力し、**Operator Style**を**Output Drive Type**に、**Save To Library**にHDAファイルの保存先を指定します。
![PythonROP2.png](https://qiita-image-store.s3.amazonaws.com/0/154191/424ad3b3-e35f-212c-a8e6-2b355853c138.png)
**Accept**を押すと**Edit Operator Type Properties**が表示されます。
ここでSOHOと通信するために必要なパラメータを作成します。
SOHOに最低でも必要なパラメータは**soho_program**と**soho_outputmode**です。
今回は以下のように4つのSOHOパラメータを追加しました。
![PythonROP3.png](https://qiita-image-store.s3.amazonaws.com/0/154191/c18e8c87-f835-6ed7-1f48-9082b06fad7e.png)
あとは、Python ROPをディペンデンシーに組むことができるように入力コネクタを持たせたいので**Maximum Inputs**を大きい値にしておきます。デフォルトでは、この**Maximum Inputs**が0になっています。それだと入力コネクタを持たないROPが作成されてしまうので注意してください。
**ディペンデンシー**って何？:baby:って人のために説明しておくと、日本語で"依存関係"のことです。
例えば、シミュレーションをするROP、ジオメトリを生成するROP、レンダリングするROPがあったら、それらを処理の順番で数珠繋ぎしたものがディペンデンシーです。ROPを接続するだけで、最後に接続されたROPを実行するだけで、一連のROPの処理が行なわれる便利な機能です。
もし、ディペンデンシーがなかったら、
パソコンの前でタスクAが終わるのを待って、終わったら手動でタスクBの準備をして実行し、またその処理を待って、それが終わったらタスクCを手動で準備して実行するという非常に生産性の悪いことをしなければなりません。
ディペンデンシーを組めば、ボタンポチってすれば、すべてのタスクが自動化されるので、パソコンの前で待ち構えることせずに昼寝できるわけです。:sleeping:

![sohoparamsBasic.png](https://qiita-image-store.s3.amazonaws.com/0/154191/b0cee858-df3f-ac44-30f3-37addad9affb.png)
今回はSOHOの使い方を学ぶのでそのまま**Accept**でHDAを作成します。
これでネットワークエディタの**out**コンテキスト内でTabキー→Digital Assetsから作成したHDAを読み込むことができるようになりましたので配置します。
![PutPythonROP.png](https://qiita-image-store.s3.amazonaws.com/0/154191/09cf8700-1016-4e62-66f9-24ba4dd13528.png)
以下のようなGUIが表示されました。
![PythonROPGUI.png](https://qiita-image-store.s3.amazonaws.com/0/154191/464b4231-7463-b6dc-7333-0d2415cca3a9.png)

##sohoパラメータの説明
追加した4つのパラメータが何のためにあるのかを説明します。

| パラメータ名    | パラメータタイプ | 目的                                                         |
|----------------|--------|---------------------------------------------------------------------|
| soho_program   | String | SOHOが実行するPythonファイル名(必須)                                   |
| soho_outputmode| Integer| soho_programの結果の出力方法(必須) 0,1,2の数値で指定                    |
| soho_pipecmd   | String | soho_programの結果を入力にするパイプコマンド(soho_outputmodeが0の時に必須)|
| soho_diskfile  | String | soho_programの結果の出力ファイル(soho_outputmodeが1の時に必須)          |

**soho_pipecmd**に指定する**パイプコマンド**とは一体なんでしょうか？
通常のexeコマンド(例えば、houdinifx.exeとかPhotoshop.exeとか)と何が違うのでしょうか？
パイプコマンドは、**常に何かしらの入力待ちで待機し続けるコマンド**のことです。
mantra.exe、wren.exeはパイプコマンドです。Houdiniシェル上でmantraを実行してみれば分かると思いますが、mantraを実行してもコマンドは終了せず、ずっと入力待ちの状態になり、そこではIFDの命令コマンドを入力して実行すると、また命令コマンドが入力されるまで待機します。
もっと身近なケースでいうならftpコマンドやtelnetコマンドがそうです。
RPCでプロセス間通信する時にプロンプトが出ますが、あれです。
_パイプコマンドのftpコマンドの例:_
![ftp.png](https://qiita-image-store.s3.amazonaws.com/0/154191/5655c833-625b-ff1f-ecd9-04a7063b50f5.png)
Mantra ROPのsoho_outputmodeパラメータはトグルボタンで制御されているので、そのトグルをオフ(0)/オン(1)に切り替えることでsoho_programに指定されているIFD.pyの結果をmantraパイプコマンドに渡すのか、IFDファイルに書き出すのか切り替えることができます。
_Mantra ROPの例:_
Mantra ROPのsoho_outputmodeが**0**の時は、soho_pipecmdで指定されているパイプコマンドが実行されるので、レンダーコマンドが次々に実行されていきます。
Mantra ROPのsoho_outputmodeが**1**の時は、soho_diskfileに指定されている場所にIFDファイルが生成されます。
![MantraOutputMode.png](https://qiita-image-store.s3.amazonaws.com/0/154191/087cb876-37c9-4614-5790-207504964024.png)

以上のことから、

- soho_programで指定したPythonの実行結果をパイプコマンドに渡す時はsoho_outputmodeを0に設定します。
- soho_programで指定したPythonの実行結果をディスクファイルに書き出す時はsoho_outputmodeを1に設定します。
- soho_programで指定したPythonを従来通りに実行するだけの時はsoho_outputmodeを2に設定します。

##soho用Pythonスクリプトの作成
では、SOHOに実行させるPythonプログラムを作成します。
カスタムのSOHO用Pythonプログラムは、基本的には```ホームディレクトリ\houdiniバージョン\soho```の直下に配置します。sohoフォルダがなければ手動で作成します。このディレクトリは、soho_programで指定したPythonファイルが探される場所になっています。

```python:soho用Pythonスクリプトの基本書式
import soho

#レンダラーとの通信ではinitializeでカメラを指定して初期化する必要があります。
#この記事ではレンダラー以外のプログラムと接続をするので基本的にはinitialize(0)でOK。
#initialize(0)を実行することで、printの結果はPythonコンソールではなく標準入力に出力されるようになります。
soho.initialize(0)
floatValue = soho.getDefaultedFloat("floatパラメータ名",[0.0])[0]#HDA上のfloatパラメータ値を参照
intValue = soho.getDefaultedInt("integerパラメータ名",[0])[0]#HDA上のintegerパラメータ値を参照
stringValue = soho.getDefaultedString("stringパラメータ名", [""])[0]#HDA上のstringパラメータ値を参照

print "書き出す文字列。soho_outputmodeが0ならパイプコマンドに渡す1行目のコマンド文字列"
print "書き出す文字列。soho_outputmodeが0ならパイプコマンドに渡す2行目のコマンド文字列"
print "書き出す文字列。soho_outputmodeが0ならパイプコマンドに渡す3行目のコマンド文字列"
```
です。sohoモジュールで使用できるメソッドは上記で説明しているsoho.docを参照するか、IFD.pyを参考にしてください。
**soho.getDefaultedなんちゃら**メソッドは、HDA上に追加したスペアパラメータの値を取得することができます。
**1番目の引数にはスペアパラメータの内部名**
**2番目の引数にはデフォルト値**
を指定します。2番目の引数がリストになっているのが変な感じがするかもしれません。
そもそも、スペアパラメータはサイズ1以上のコンポーネント(例えば、X,Y,Zコンポーネント)を定義することができるのでそのようになっています。
上記の例では、サイズ1のパラメータの値を変数に格納したいので、[0]で1番目のコンポーネント値を取得しています。

では、HoudiniからエクスポートしたigesファイルのパスをFreeCADに渡し、それをFreeCADでSVGにエクスポートするスクリプトを作成したいと思います。
FreeCADのインストールディレクトリ直下のbinフォルダには**FreeCADCmd.exe**というコマンドがあります。
![FreeCADCmd.png](https://qiita-image-store.s3.amazonaws.com/0/154191/71378d40-b627-525e-7584-19455a5fe7af.png)
これを実行してみると、Pythonコマンドを入力することができる**パイプコマンド**であることがわかります。
![FreeCADPipeCMD.png](https://qiita-image-store.s3.amazonaws.com/0/154191/dfc7cf93-0a86-cbad-4b9b-dc7a6e3b13ce.png)
そのため、soho_outputmodeを0、soho_pipecmdにFreeCADCmd.exeを指定し、
soho_programに指定するPythonスクリプトは、そのパイプコマンドに渡すコマンド群を出力すればよいので

```python:FreeCAD.py
import soho
import os
soho.initialize(0)
stringValue = soho.getDefaultedString("filepath", [""])[0]#filepathスペアパラメータの値を取得
if stringValue.endswith(".igs") and os.path.exists(stringValue):
	print "import Part"
	print "import importSVG"
	print "igesPath =" + "\""+stringValue+"\""
	print "svgPath = igesPath.rsplit(\".\",1)[0]+\".svg\""
	print "newDoc = FreeCAD.newDocument(\"new\")"
	print "Part.insert(igesPath,\"new\")"
	print "partObjects = newDoc.findObjects()"
	print "if len(partObjects)==0:"#Igesが空っぽの時はダミーの点を追加
	print "    partObjects.append(newDoc.addObject(\"Part::Vertex\"))"
	print ""#インデントから抜ける時に必要になります。
	print "importSVG.export( partObjects ,svgPath)"
	print "FreeCAD.closeDocument(\"new\")"
```
と記述することができます。
printステートメントが多いのが気になる方は、

```python:FreeCAD.py
import soho
import os
soho.initialize(0)
stringValue = soho.getDefaultedString("filepath", [""])[0]#filepathスペアパラメータの値を取得
if stringValue.endswith(".igs") and os.path.exists(stringValue):
    print "import Part"
    print "import importSVG"
    print "igesPath =" + "\""+stringValue+"\""
    print """
svgPath = igesPath.rsplit(".",1)[0]+".svg"
newDoc = FreeCAD.newDocument("new")
Part.insert(igesPath,"new")
partObjects = newDoc.findObjects()
if len(partObjects)==0:
    partObjects.append(newDoc.addObject("Part::Vertex"))

importSVG.export( partObjects ,svgPath)
FreeCAD.closeDocument("new")
"""
```
でもいいです。ボクは前者の方が好きです。

処理内容は、指定したigesをFreeCADに読み込みigesと同じ場所にSVGをエクスポートします。
これを```ホームディレクトリ\houdiniバージョン\soho```の直下に例えばFreeCAD.pyという名前で保存します。

Sample ROPに"filepath"という名前の文字列スペアパラメータを追加し、SOHO ProgramにFreeCAD.py
SOHO Output Modeを0、CommandをFreeCADCmd.exeを指定します。
![SampleROPsetting.png](https://qiita-image-store.s3.amazonaws.com/0/154191/9dd7fe61-2513-1729-1e5a-cc872429997b.png)
こんな感じでディペンデンシーを組みます
![Dependency.png](https://qiita-image-store.s3.amazonaws.com/0/154191/2a994f42-58c7-d3ba-602d-d8b341df5852.png)
3Dシーンから抽出したワイヤーフレームを読み込んだFileオブジェクトをWren ROPで再度レンダリングするとループに入ってしまってHoudiniが落ちてしまうので、そのFileオブジェクトはWren ROPでレンダリングされないように**Exclude Objects**に指定しておきます。
![dep1.png](https://qiita-image-store.s3.amazonaws.com/0/154191/ba085539-35f8-eac7-88be-74a32d87cb04.png)
Geometry ROPは、**SOP Path**にNURBSカーブに変換したSOPを、**Out File**にigesの出力先を指定します。
![dep2.png](https://qiita-image-store.s3.amazonaws.com/0/154191/75e4ea80-f617-392f-9338-60eab710a4e4.png)
Sample ROPは、**Iges File**にGeometry ROPの**Out File**のパラメータを参照するようにします。
![dep3.png](https://qiita-image-store.s3.amazonaws.com/0/154191/18247c09-89ed-8fe2-dd01-6600aeaf11c8.png)

ディペンデンシーで一番最後に接続されたSample ROPをレンダリングすることで、Wren ROPとGeometry ROPが連動して処理されていくので、手作業を介さずに、Wrenから生成したPostscriptをIgesに変換してSVGにエクスポートするというワークフローを自動化することができました。

出力されたSVGをドローソフトで開いてみると、アンカーを自由にいじることができて滑らかな曲線が生成されていることがわかります。
![Output.png](https://qiita-image-store.s3.amazonaws.com/0/154191/add63047-954e-23e1-1042-9619d0015f71.png)

#おまけ1:HoudiniからMayaへのバインディング
今回はSOHOを使ってHoudiniからFreeCADへのバインディングをしましたが、Mayaを起動した状態でMaya側で```maya.cmds.commandPort(name=":ポート番号", sourceType="python")```を実行しておけば、HoudiniからMayaへのバインディングも可能です。MELコマンドをMayaに送信する場合は、```maya.cmds.commandPort(name=":ポート番号", sourceType="mel")```を指定します。

これで、DOSコマンドプロンプトで**telnet**コマンド(標準ではインストールされていません)を実行し、
```open localhost 8888```コマンドでMayaに接続し、**Ctrl+]キーでコマンド入力モードに切り替え**、
```send```コマンドでPythonコードを送信することができます。
![telnet.png](https://qiita-image-store.s3.amazonaws.com/0/154191/8dcdefc7-ccdc-4783-ec55-9e6e2192e35f.png)
このtelnetコマンドをSOHOのパイプコマンドに指定したいのですが、その場合、telnetコマンドでMayaに接続した後にすぐにコマンド入力モードで入力待ち状態にする必要があります。
手動操作なら**Ctrl+]キー**を押せばできるけどtelnetのオプションを見る限りはopenで接続した後でもコマンド入力モードのままにする方法が用意されていない:scream:
仕方ないので、Pythonスクリプトでtelnetlibモジュールを利用します。

```python:HtoMconnect.py
# -*- coding:utf-8 -*-
#通常のPython.exeで日本語が記述されたスクリプトを実行するには、このcoding表記が必要
import sys
import telnetlib

host = sys.argv[1]
port = sys.argv[2]
terminal = telnetlib.Telnet(host,port)
inputString = raw_input(">")
while "quit" != inputString :#quitの命令を受け取ると終了する
	terminal.read_until(">",0.5)#ネットワーク環境によっては、1秒などの遅延に変更する
	terminal.write(inputString)
	inputString = raw_input(">")
```

あとは、telnet接続して、コマンドをMayaに送ればMayaを遠隔操作することができます。
![pythontelnet.png](https://qiita-image-store.s3.amazonaws.com/0/154191/308e4def-e45d-c75b-ea3a-e643a6ea0df3.png)

```python:sendCommand.py
import soho
soho.initialize(0)

fps = soho.getDefaultedInt("state:fps", [0.0])[0]
time = soho.getDefaultedFloat("state:time", [0.0])[0]
frame = fps*time + 1

print "maya.cmds.sphere()"#Mayaのコマンドを書き出す
print "maya.cmds.move(" + str(frame) + ", 0.0, 0.0 )"#Mayaのコマンドを書き出す
print "quit"#最後はquitで待ち入力ループから抜ける。
```

**soho_pipecmd**に```python Scriptのパス/HtoMconnect.py localhost ポート番号```
を指定(.pyファイルの場所を指定すること)してあげて、**soho_program**にMaya Pythonコマンドを書き出すPythonスクリプトを指定(sohoフォルダにあればファイル名のみでOK。)してあげればHoudiniからMayaへのワークフローも自動化することができます。
_例:_
![sohoMaya.png](https://qiita-image-store.s3.amazonaws.com/0/154191/238c8a37-e945-0d74-0afd-6630b1cc5b52.png)

ここで紹介したスクリプトは、SOHOを使わない単独で動くコードに修正して、何かしらのROPで用意されている**Pre/PostなんたらScript**パラメータに指定するという手もありますが、ディペンデンシーへの組み込みの柔軟性を考えれば、SOHOをレンダラーのバインディング用途以外にも使えるのではないでしょうか？
SOHOは便利っすよ:relaxed:

#おまけ2:MayaからHoudiniへのバインディング
HoudiniからMayaへのバインディングを説明しましたが、逆にMayaからHoudiniへのバインディングも説明しておきます。

やっぱ、SOHOについて説明したんだし、HoudiniとMayaのバインディングも**双方**に説明しとく必要あるかなと思って。:ghost:

HoudiniにもMayaでいうcommandPortがあります。
[SideFXのRPCのヘルプ](http://www.sidefx.com/docs/houdini/hom/rpc)を読めば、HoudiniでもRPC(リモートプロシージャルコントロール)ができることがわかります。

Houdini側でPythonシェルから以下のコードを実行するか、または、123.pyに登録します。

```python:RPCサーバー
import hrpyc
hrpyc.start_server(port=ポート番号)#portを指定しなかった場合は18811番が使われます。
```
このhrpycモジュールは、[RPyC](http://rpyc.readthedocs.io) PythonモジュールをHoudini用にカスタマイズされたものです。つまり、Houdini以外のプログラムからHoudiniに接続する時は、このRPyCモジュールを使用すれば接続することができます。

Maya側に[RPyC](http://rpyc.readthedocs.io) Pythonモジュールをインストールします。
これをダウンロードするよりも、```Houdniインストールディレクトリ\python27\lib\site-packages```直下にある**rpyc**フォルダを```ホームディレクトリ\maya\scripts```下にコピーすればMaya側のPython上でRPyCモジュールを使用できるようになります。

MayaのScript Editor上で以下のPythonスクリプトを実行すれば、Houdiniにその命令を送ることができます。

```python:Maya側で実行するスクリプトの例
import rpyc
connection = rpyc.classic.connect("localhost", 8888)
hou = connection.modules["hou"]#import houと同じ挙動になる。これでhouと同じPythonコードをそのまま記述できる
geo = hou.node("/obj").createNode("geo")
for i in geo.children():
	i.destroy()
sphere = geo.createNode("sphere")
grid = geo.createNode("grid")
grid.parm("rows").set(5)
grid.parm("cols").set(5)
copy = geo.createNode("copytopoints")
copy.setInput(0,sphere)
copy.setInput(1,grid)
copy.setDisplayFlag(True)
copy.setRenderFlag(True)
geo.layoutChildren()
```
これによって、Mayaを立ち上げた状態でHoudiniのPythonスクリプトを開発することができます。
Houdiniなんて嫌いだ、Mayaを使え！お前ら！って奴隷のように働かされてる隠れHoudinistも堂々とMaya上でスクリプトが書けるようになります。

おしまい:hugging: