12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

HoudiniAdvent Calendar 2016

Day 13

PythonによるAlembicの情報取得

Last updated at Posted at 2016-12-12

SOP、DOP、Wrangleあたりはネタがかぶりそう:dancer_tone5:だったのでPythonネタを書きたいと思います。
バージョンはHoudini15.5時点での話です。
Houdiniがノードベースなシステムであることは、言い換えればビジュアルプログラミングができるということなので、他の3D CGソフトウェアよりはPythonによるスクリプティングの機会は少ないのではないかと思います。
では、どこでPythonを使用するんだ!:punch_tone1:って話になるんですが、やはりパイプライン開発がメインになります。
例えばHoudini標準のディスパッチャーであるHQueueは、標準のままつかったとしたら、全フレームのシミュレーションが終わった後にレンダリングジョブを別クライアントに渡すという流れになりますが、Pythonを使えば、1フレームのシミュレーションが終わったら別のクライアントにそのレンダリングジョブを投げると言ったことが可能になります。
また、Mantraコマンドの-PオプションでPythonスクリプトファイルを指定することができるので、既に書き出したIFDのレンダリングプロパティ(例えば、サブピクセル)を上書きしてレンダリングすることができるので、サブピクセルを変更して再度IFDを出力するといった手間を省くことができます。:wink:
他にもAlembicファイルを読み込んだ時にその中のオブジェクトトランスフォーム情報を読み込むといったこともPythonを使うことで可能です。
HQueue自体はフリーですがHoudini Indie以上のライセンスの有無をチェックします(ライセンスは掴まない)、IFDの書き出しは商用版Houdini/Engineでないとだめなので、ApprenticeユーザーでもIndieユーザーでも役立つ情報としてPythonによるAlembicの情報取得の方法を紹介したいと思います。:relaxed:

HoudiniのPythonには、_alembic_hom_extensionsと呼ばれるモジュールが用意されています。
通常ではPythonでHoudiniを制御する時はhouモジュールから始めていくわけなのですが、この**_alembic_hom_extensionshou**モジュールとは独立しています。つまり、このモジュールを使用するだけであればライセンス不要。
でも、HoudiniにAlembicの情報を渡して処理することがほとんどなのであまり意味ない:scream_cat:
このモジュールを使用することで、主に指定したAlmembicファイルのオブジェクト階層、オブジェクトトランスフォーム、可視性を取得することができます。
他にもオブジェクトのユーザープロパティの値(Houdiniから出すAlembicならカメラオブジェクトに追加設定されている解像度がそれですね)やオブジェクトアニメーションの時間範囲を取得することができます。
このモジュールが持っている関数は以下のとおりです。
読み流す:zzz:程度で次の説明にいきましょう。


alembicArbGeometry(abcPath, objectPath, name, sampleTime)  (value, isConstant, scope)

Noneまたは(value, isConstant, scope)のタプルを返します。このタプルの中身は、アトリビュートの値、そのアトリビュートが時間軸で一定値かどうかを示したブール値、スコープ('varying', 'vertex', 'facevarying', 'uniform', 'constant', 'unknown')です。

alembicClearArchiveCache()

Alembicファイルの内部キャッシュをクリアします。

alembicGetArchiveMaxCacheSize()

Alembicファイルキャッシュサイズを返します。

alembicGetCameraDict(abcPath, objectPath, sampleTime)

指定したオブジェクトのカメラパラメータの辞書を返します。

alembicGetCameraResolution(abcPath, objectPath, sampleTime)

Noneまたは2個のfloatを含んだタプルを返します。1番目の値は、Houdini CameraのX解像度です。2番目の値は、Houdini CameraのY解像度です。なにかしらのカメラ(例えばMaya Camera)には解像度がないので、この場合にはNoneが返されます。

alembicGetObjectPathListForMenu(abcPath)

メニューコールバックに必要な形式の文字列をタプルで返します。

alembicGetSceneHierarchy(abcPath, objectPath)  (object_name, object_type, (children))

3つのタプルを返します。各タプルは、以下の構成になっています:
(object_name, object_type, (children))
(children)は、子ノードを含んだタプルです。
object_typeは以下のどれかが含まれます(他のタイプを含む場合もあります):

  • cxform 一定トランスフォームノード
  • xform アニメーショントランスフォームノード
  • camera カメラノード
  • polymesh Polygon Meshシェイプノード
  • subdmesh Subdivision Surface Meshシェイプノード
  • faceset Face Setシェイプノード
  • curves Curvesシェイプノード
  • points Pointsシェイプノード
  • nupatch NuPatchシェイプノード
  • unknown 不明なノード
alembicHasUserProperties(abcPath, objectPath)

オブジェクトにユーザープロパティがなければNoneを返します。ある場合は、そのユーザープロパティが時間軸で一定かどうかを返します。

alembicSetArchiveMaxCacheSize(size)

一度にキャッシュ化されるAlembicファイルの最大数を設定します。

alembicTimeRange(abcPath, [objectPath=None])  (start_time, end_time)

Noneまたは(start_time, end_time)のタプルを返します。このタプルは、Alembicアーカイブ内のFPS情報によるそのアーカイブのグローバル開始/終了時間を含んでいます。objectPathを指定すれば、そのオブジェクトの開始/終了時間を計算します。そのアーカイブが一定であればNoneを返します。

alembicUserProperty(abcPath, objectPath, name, sampleTime)  (value, isConstant)

Noneまたは(value,isConstant)のタプルを返します。このタプルは、アトリビュートの値、そのアトリビュートが時間軸で一定値かどうかを示したブール値を含んでいます。

alembicUserPropertyMetadata(abcPath, objectPath, sampleTime)

NoneまたはJSON辞書を返します。このJSON辞書は、ユーザープロパティ名 --> ユーザープロパティメタデータのマップを含んでいます。

alembicUserPropertyDictionary(abcPath, objectPath, sampleTime)

NoneまたはJSON辞書を返します。このJSON辞書は、ユーザープロパティ名 --> ユーザープロパティ値のマップを含んでいます。

alembicUserPropertyValuesAndMetadata(abcPath, objectPath, sampleTime)

Noneまたはタプルを返します。このタプルは2つのJSON辞書を含んでいます。1番目の辞書は、ユーザープロパティと値のマップを含んでいます。2番目の辞書は、ユーザープロパティと1番目の辞書の解釈に使用されるメタデータのマップを含んでいます。

alembicVisibility(abcPath, objectPath, sampleTime, [check_ancestor=False])  (value, isConstant)

Noneまたは(value,isConstant)のタプルを返します。このタプルは、オブジェクトの可視性、その可視性が時間軸で一定かどうかを示したブール値を含んでいます。可視性の戻り値の0は非表示、1は可視、-1はディファー(親の可視性に依存)を意味します。

getLocalXform(abcPath, objectPath, sampleTime)  (xform, isConstant, inherit)

(xform, isConstant, inherits)のタプルを返します。このタプルは、ローカルトランスフォーム、そのトランスフォームが時間軸で一定かどうかを示したブール値、ノードが親のトランスフォームを継承しているか(またはトランスフォーム階層と繋がっているか)を示したブール値を含んでいます。

getWorldXform(abcPath, objectPath, sampleTime)  (xform, isConstant, inherit)

(xform, isConstant, inherits)のタプルを返します。このタプルは、ワールドトランスフォーム、そのトランスフォームが時間軸で一定かどうかを示したブール値、ノードが親のトランスフォームを継承しているか(またはトランスフォーム階層と繋がっているか)を示したブール値を含んでいます。


実際には上記のすべての関数を使用するわけではないので、注目すべき関数だけにスポットを当てたいと思います。

alembicGetSceneHierarchy(abcPath, objectPath)
alembicTimeRange(abcPath, [objectPath=None])
getWorldXform(abcPath, objectPath, sampleTime)

かなり少なくなりました。この3つの関数で何ができるのかを見てみましょう。
Scene.gif
NetworkEditor.png
cam1、Toy、Subnet(この中にPig、ShaderBall)を作成しました。
ShaderBall以外にはオブジェクトレベルのアニメーションを設定しています。
このシーンを丸ごとAlembicに出力し、それを新規シーンに読み込んでみます。
NetworkEditorAlembic.png
Houdiniと同じオブジェクト階層でデータが取り込まれており、アニメーションもします。
でもアニメーションするcam1オブジェクトを選択してもAnimation Editorには何もアニメーションカーブが表示されません。
CHOPに詳しい人であればObject CHOPを使えばMotion FX Viewにてサンプリングされたアニメーションカーブが表示されるじゃん?って思われるかと思います。元のAlembicのアニメーションデータがベイクされていて線形補間されているものであればそれでいいんですが、そうじゃないアニメーションの場合は浮動小数点フレームの値もちゃんと取得しておきたいものです。
なので、やっぱりアニメーションカーブを取得したいですよね?
そこで登場するのが**_alembic_hom_extensions**モジュールです。
大まかな流れとしては、

  • Alembic内のオブジェクト階層を取得し、どのオブジェクトにアニメーション情報が付いているのか判断する
  • そのオブジェクトのアニメーション範囲を調べる
  • オブジェクトトランスフォーム情報を取得

で色々とパイプライン開発ができます。

####Alembicのオブジェクト階層とアニメーションタイプを取得する方法
コードは以下のとおりです。

import _alembic_hom_extensions as abc

abcPath = "C:/data/alembicFile.abc"

def expandChild(root,child,objectHierarchy,objectType):
    objectHierarchy.append(root+child[0])
    objectType.append(child[1])
    if len(child[2])==0:
        return
    else:
        return expandChild(root+child[0]+"/",child[2][0],objectHierarchy,objectType)

objectHierarchy=[]
objectType=[]    
childNodes = abc.alembicGetSceneHierarchy(abcPath, "/")[2]
for eachChildNode in childNodes:
    expandChild("/",eachChildNode,objectHierarchy,objectType)
print objectHierarchy
print objectType
#出力結果
['/Toy', '/Toy/testgeometry_rubbertoy1', '/cam1', '/cam1/cameraProperties', '/Subnet', '/Subnet/Pig', '/Subnet/Pig/testgeometry_pighead1', '/Subnet/Pig/testgeometry_pighead1/PigFace']
['xform', 'polymesh', 'xform', 'camera', 'cxform', 'xform', 'polymesh', 'faceset']

xformは、そのオブジェクトにアニメーション情報を持っていることを意味し、cxformは、アニメーション情報を持っていないことがわかります。

####アニメーション範囲を取得する方法
では、cam1のアニメーション範囲を調べてみましょう。

import _alembic_hom_extensions as abc

abcPath = "C:/data/alembicFile.abc"
objectPath = "/cam1"
print abc.alembicTimeRange(abcPath, objectPath)
#出力結果
(0.041666666666666664, 2.0)

この関数で出力されるアニメーション範囲の単位は秒です。
このカメラはアニメーション範囲が1フレームから48フレームでFPS=24で取り込んでいるので
(1.0/24.0,48.0/24.0)の結果が表示されています。
####現行フレームにおけるカメラの移動、回転、スケールの値を取得する方法
では、オブジェクトのトランスフォームを調べてみましょう。

import _alembic_hom_extensions as abc

abcPath = "C:/data/alembicFile.abc"

objectPath = "/cam1"

sampleTime = hou.frame()/hou.fps()

#getWorldXformで返されたタプルの1番目の要素は16個のfloatタプル
xform = abc.getWorldXform(abcPath, objectPath, sampleTime)[0]
#マトリックス形式に変換するためにhou.Matrix4を使用する
xformMatrix = hou.Matrix4(xform)
#移動、回転、スケールを抽出する
explodedDictionary = xformMatrix.explode()
print "translate:",explodedDictionary["translate"]
print "rotate",explodedDictionary["rotate"]
print "scale",explodedDictionary["scale"]
#出力結果
translate: [5.75595, 3.34021, 10.2852]
rotate [-11.6801, 28.4249, -1.25354e-06]
scale [1, 1, 1]
```

上記の単一スクリプトで利用するのもありですが**HoudiniのエクスプレッションはHScriptだけでなくPythonも使用することができます**
なので次はAlembicで取り込んだカメラノードに以下のようにユーザーパラメータ(HoudiniではSpareパラメータと言います)を追加してみましょう
ここではFloat Vector3を2つ追加しTranslateパラメータとRotateパラメータを用意しました
そしてエクスプレッションのタイプをPythonに変更します
![UserParms.png](https://qiita-image-store.s3.amazonaws.com/0/154191/3c170758-182d-f349-6414-f89ccfb60957.png)
Alembic Xformノードでは既にFile NameObject PathFrameFrameのパラメータが用意されているのでこれらのパラメータの値を利用してカメラの移動回転の情報を取得してみたいと思います
パラメータ上でAlt+Eキーで表示されるEdit Expressionダイアログによって複数行のPythonコードを入力することができますがよく使用するコードをいちいち入力するのは面倒ですよね
よく使用するコードはカスタム関数として登録しておくのが便利です
カスタム関数を登録する方法は特定のディレクトリにPythonファイルを配置してそれをパスに通しておく方法がありますがそれだと環境依存のシーンファイルになってしまいます
それは回避したい
なのでカスタム関数をシーンファイル内で定義することにします

####カスタム関数をシーンファイルに登録する方法
カスタム関数はWindowsメニューのPython Source Editorで登録することができます
![PythonSourceEditor.png](https://qiita-image-store.s3.amazonaws.com/0/154191/6022a0b5-fa4d-97ad-e550-6a4fe3141500.png)
そして以下のようにコードを入力します
![PythonSourceEditorCode.png](https://qiita-image-store.s3.amazonaws.com/0/154191/6285343b-318f-656a-d176-36201529f123.png)
このコードはAlembic Xformノード上のパラメータで動作しそのノードのトランスフォーム情報を返す関数です

```python:
#modeが"translate"なら位置情報、"rotate"なら回転情報、"scale"ならスケール情報を返します
#indexが0ならX成分、1ならY成分、2ならZ成分
def getAlembicTransform(mode="translate",index=0):
    import _alembic_hom_extensions as abc
    currentNode = hou.pwd()
    abcPath = currentNode.parm('fileName').eval()
    objectPath = currentNode.parm('objectPath').eval()
    sampleTime = currentNode.parm('frame').eval()/currentNode.parm('fps').eval()
    xform = abc.getWorldXform(abcPath, objectPath, sampleTime)[0]
    xformMatrix = hou.Matrix4(xform)
    explodedDictionary = xformMatrix.explode()

    return explodedDictionary[mode][index]
```
Python Source Editorウィンドウでコードを登録したら
以下のように"getAlembicTransform"カスタム関数を呼び出すことができるようになります
![ParameterExpression.png](https://qiita-image-store.s3.amazonaws.com/0/154191/380f944f-fa7b-6f00-98e3-c6ffd7d9344a.png)
パラメータ名を左クリックして値がちゃんと入っているのかを確認します
![ParameterExpressionEval.png](https://qiita-image-store.s3.amazonaws.com/0/154191/c8e5cc41-04e2-4412-55ac-3d802a64680b.png)

これでAlembicオブジェクトのアニメーションカーブをAnimation Editorで確認できるようになります
![AnimationCurve.png](https://qiita-image-store.s3.amazonaws.com/0/154191/e4697ace-c750-d3b1-7f2a-64ff75d139f7.png)


これでAlembicのトランスフォーム情報をパラメータ値に入れることができました

おしまい:hugging:
12
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?