カスタムノードシェイプを作る

ノードの形状には色々な種類があり、好きな形状に変更することができますが、このノードシェイプを自作して追加することもできます。
![nodeshape.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/576e5560-14ec-f163-f445-9a145135d6df.png)


作成方法は英語のまとめ記事がいくつかありますが、私はこちらの動画を参考にしました。
シーンファイルもリンク先から取得できます。
https://vimeo.com/221182957

今回はこの動画をベースに、自分なりの工夫も併せて解説させていただきます。

##ファイル

ノードシェイプのファイルは **JSON** 形式になっています。

デフォルトのノードシェイプのファイルは以下に入っています。
`$HFS（Houdiniインストールフォルダ）\houdini\config\NodeShapes`


新しく作成したファイルは、下記にフォルダを作成して配置すると、次回起動時から認識されます。（windows10）
`Documents\houdini17.0\config\NodeShapes`

###ノードシェイプの要素

JSONの仕様として、{}で囲んだ中に、要素の名前と値をセットで記述します。

```JSON
{
  "aaa": "hello",
  "bbb": 1,
  "ccc": true
}
```

デフォルトのノードシェイプのJSONファイルを見ると、要素として下記を定義していることが分かります。

1. ノードシェイプの名前
1. ノードシェイプのアウトライン
1. Bypass フラグ用アウトライン（黄）
1. Lock フラグ用アウトライン（赤）
1. Template フラグ用アウトライン（紫）
1. Display フラグ用アウトライン（水色）
1. input のドットが配置されるライン
1. Output のドットが配置されるライン
1. オペレータアイコンの位置

![node_flag.png](https://qiita-image-store.s3.amazonaws.com/0/183392/c47ed841-cbc5-098b-d6ad-e0d5ae94080e.png)

それでは2～6のアウトラインについて作成していきます。

##アウトラインの書き方

Viewを Front にして、Curve SOP でアウトラインを描いていきます。
大きさや位置はシーン内の他のサンプルを参考に、x軸は0-1の間、y軸は0より少し上あたりが中心のようです。
もっと大きいシェイプを描けば、巨大なノードシェイプも作成できます。

Viewに下書きを表示するには、Display Options の Background タブで front を選んで画像ファイルを選択します。

![background.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/9284dbad-6530-df5f-f5fb-3da16033ecf8.png)

ここが重要なのですが、 **アウトラインは一筆書き** で描きます。
セパレートされた形状を持つことはできません。
ただし、書き方次第で内側に線を追加したり、中ヌキにすることはできます。

ここに２重の円がありますが、
巻き込むように描けば内側に線が入り、三日月のように描いていけば中ヌキになります。
![circle.png](https://qiita-image-store.s3.amazonaws.com/0/183392/bf275d9b-aca9-d4d0-0ba1-9e4c4bc90222.png)

複数の形状を合成したアウトラインを作成する場合、booleanを使っても良いのですが、point番号が意図しない順番になってアウトラインが崩れやすいので、面倒ですが Snap を使って元形状の上から書き直すのが結果的には早いです。
![outline.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/ae32dea7-755f-4b7b-33a6-7f05e51e4f19.png)

###eps
adobe illustrator からの epsファイルも使用できます。
![eps.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/b9525f92-a37c-7f47-d0a1-f493dc2aa874.png)
ここでは Bypass フラグ用のアウトラインとして、星型のシェイプをepsで読み込んでみました。
![eps_flag.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/24a6be93-11ff-3668-3367-444412057d3a.png)
ついでに他のフラグ用のアウトラインも丸っぽい形状で作成しました。

flagの形状は、元のアウトラインからbooleanで切り抜いても構いません。
また、この作例の星形状や丸形状のように、元のアウトラインの上に乗せただけの状態だと、
フラグONの時だけ実体化し、OFFの時は線画のみ、のように見えますので、使い分けを考えるのも楽しいかもしれません。
（後述）


##カーブから2D座標を取得

動画のリンク先のシーンから"outline"の下にある Point Wrangle を見てみます。
![nodegraph.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/8dc5fa76-e85f-4dbb-c5af-83ad64d4533e.png)

```c:Point
s@pos = sprintf("[%g,%g]", @P.x, @P.y);

setdetailattrib(0, "outline", @pos, "append");

if (@ptnum < @numpt-1) {
    setdetailattrib(0, "outline", ",", "append");
}
```
ノードシェイプのJSONの書式に従った `[X座標,Y座標]` という記述になるように、Point のX座標とY座標を、`sprintf` を使って書き込んでいます。
通常の `printf` はコンソールに結果を出力しますが、 `sprintf` は結果を文字列として返します。
フォーマットを使って `%g` のところに後の引数が入りますので、 `[@P.x,@P.y]` が各頂点ごとに `@pos` として書き込まれます。
それをさらに Detail にカンマで結合して入れています。

同様に、他のアウトラインも座標を Detail に入れています。

###angle

inputs、outputs に関しては、座標のほかに `angle` という値も作成して加えています。ノード接続のラインの角度に使うようです。

![angle.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/aa2df4e6-b38f-acbf-9e99-329bf598836f.png)

polyframe SOP で
- Normal Name:up
- Tangent Name:N
を作成して

```c:Point
@N = cross(@N, {0,0,1});
```

```c:Point
@angle = degrees(atan2(@N.y, @N.x));
@angle = (@angle + 360) % 360;
```


オペレータアイコンの位置に関しては、対角線の２点のみで良いようです。

##出力

最後にこれらを merge したあと、Detail に入っている各座標情報を、JSONファイルの書式で結合しています。

```c:Detail
string fullpath = opfullpath("..");
string path[] = split(fullpath, "/");

string json_string = "{\n\"name\": \"";
json_string += path[len(path)-1];
json_string += "\",\n\"flags\": {\n\t\"0\": { \"outline\": [";
json_string += detail(1, "flag0_outline");
json_string += "] },\n\t\"1\": { \"outline\": [";
json_string += detail(1, "flag1_outline");
json_string += "] },\n\t\"2\": { \"outline\": [";
json_string += detail(1, "flag2_outline");
json_string += "] },\n\t\"3\": { \"outline\": [";
json_string += detail(1, "flag3_outline");
json_string += "] }\n},\n\"outline\": [";
json_string += detail(1, "outline");
json_string +=  "],\n\"inputs\": [";
json_string += detail(1, "inputs");
json_string +=  "],\n\"outputs\": [";
json_string += detail(1, "outputs");
json_string += "],\n\"icon\": [";
json_string += detail(1, "icon");
json_string += "]\n}";

setdetailattrib(0, "json_string", json_string, "set");
```
ノードシェイプの名前は親ノード名から取っています。
あとはひたすら、`"`をエスケープしながら結合していってます。強引で面白い！

こうして出来上がった文字列を、参考動画では右クリックからコピーして、新規のテキストファイルに貼り付けています。
![detail_copy.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/d7b039fa-7c37-42a4-b5de-a23ac47cf65b.png)

せっかくなのでそこも自動化したいなーと思い、Pythonで出力ノードを作成してみました。

```python
node = hou.pwd()
geo = node.geometry()

import os
hippath = hou.hipFile.path()
dirpath = os.path.dirname(hippath)
filename = "/"+str(node.parent())
extension = ".json"
filepath = dirpath + filename + extension

file = open(filepath,'w+')
json_string = geo.stringAttribValue('json_string')
file.write(json_string)
file.close()
```

このノードをDisplayすれば、シーンファイルと同階層にJSONファイルが出力されます。

出来たアイコンをNodeShapesのフォルダに入れて、
Houdiniを再起動すれば、ノードシェイプが増えました！
![fix_0.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/94818116-aa78-aa22-e9d4-1da544c90ee9.png)
![fix_1.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/94df5a54-183b-c93b-b47a-37479176d71f.png)

メリークリスマス！

##おまけのSVG

テキスト形式のベクター画像ならSVG、という事で、SVGファイルも出力してみます。

HoudiniのオペレータアイコンもSVGで出来ていますので、中身を拝見すると、Inkscape というサービスを使って作成されていることが分かりました。（https://inkscape.org/）
リッチで便利そうなのですが、今回は強引に手打ちをするため極力シンプルな構成にしてみます。

```XML
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" width="48px" height="48px" id="svg47377" xmlns="http://www.w3.org/2000/svg">
<path
    d="M 14.5609 23.7006 L 14.5609 23.7006 15.466 23.0671 （略） Z"
    fill="gray" />
</svg>
```

SVGの仕様について、ざっと調べた感じでは以下のようでした。（間違ってたらすみません）
最初の`<svg>`のところでカンバスの大きさやsvg公式へのリンクが書かれています。
次の`<path>`のところで描くカーブを座標で指定しています。座標は半角スペースで繋いでいきます。
`M`は、カーブの始点です。`L`はカーブを構成するラインです。`Z`はカーブ描画終了を意味します。
`fill`は塗りカラーです。


先程のカスタムノードシェイプのアウトラインのカーブを使って、0-1では小さすぎるので拡大したり、回転したりしてみます。

JSONの時と同じようにpoint座標を Detail に入れていきますが、今度は半角スペース区切りにします。

書式の構築の部分で、SVGの書式になるように変更し、
最後は同様に出力用Pythonノードを繋げて、拡張子を.svgにしたら完成です。

![svg.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/15e71a38-9026-87db-206c-878e29494589.png)

---
今回作成したJSONファイルは以下に置いておきますので、ご自由にどうぞ。
![tree.png](https://qiita-image-store.s3.amazonaws.com/0/183392/e0d90692-ec0c-c666-c20c-d1a8b4dfcad6.png)
flagのアウトラインをbooleanせずに配置すると上図のようになり、ONの時だけ実体化する印象になります。
https://www.dropbox.com/s/fp2au957dyo1rs2/tree.json?dl=0

![doggie.PNG](https://qiita-image-store.s3.amazonaws.com/0/183392/b800dc8a-9874-7223-5d2b-4a5cf568e52f.png)
flagを元アウトラインからbooleanで抜いた場合は、こちらのようになります。
こちらのわんこも配布しています。
https://www.dropbox.com/s/7vle26nzm34nfhx/doggie.json?dl=0

シーンファイルは動画のURLから取得できます。
https://vimeo.com/221182957

ありがとうございました (  °ᴗ°  )