Houdini – Python(HOM)によるパラメータ操作

 前回は、HOMによりノードを選択する操作に関する記事を書きました。
 ノードを探して選択するだけではあまりにも寂しいので、今回はそれに引き続き、ノードのパラメータを操作する部分に関するメモを書き残したいと思います。
 Houdiniでは、ノードパラメータへの操作パスが幾通りも存在しており、とても柔軟な作りになっていますが、今回は、自分がよくやる方法を取り上げます。
「もっと便利な方法があるよー」などの情報があればぜひ教えてください。
 ここで紹介する各種メソッドはあくまでも一例で、実際には更に多くのメソッドが用意されているので、ぜひマニュアルも参照してください。
http://www.sidefx.com/docs/houdini/hom/hou/Node.html
http://www.sidefx.com/docs/houdini/hom/hou/Parm.html
http://www.sidefx.com/docs/houdini/hom/hou/ParmTuple.html


パラメータ名の確認

 まず最初に、パラメータへアクセスする際はパラメータ名が重要になるので、名前の確認方法から。

Parameter Spread Sheetで確認

 Parameter Spread Sheetで、ノードとパラメータをツリー表示で確認できる。
 各項目名の右端にある、カッコで囲まれた名前が実際にアクセスする際に使用するパラメータ名。パラメータにはUniform Scaleのような単一の値を持つパラメータと、位置や回転など、複数の値をひとまとめにして持つ配列パラメータがある。
 下図を例にすると、Axis Divisionsという「ラベル」が付けられたパラメータは、実際の名前が「divrate」であり、3つの子要素(divratex/y/z)を持っている配列パラメータである事がわかる。

ドラッグ&ドロップで確認


 Python Source Editorに、パラメータラベルをドラッグ&ドロップすることで正しい配列パラメータのパスと名前が確認できる。

ポップアップウインドウで確認

 Parametersパネルなどでパラメータ名ラベルの上にマウスカーソルを置き、少し待つと、パラメータ情報がポップアップで表示される。この中の、Parametersの部分にある文字列が、このノードにおけるパラメータの正式な名前になる。

↑↑↑
 この場合、tx ty tz がパラメータ名であると確認できるが、あくまでも配列要素の単一パラメータ名のみがわかる。
 正確な親の配列パラメータ名が知りたければ、Parameter Spread Sheetを確認するなど一手間必要。(大体の場合、配列要素パラメータ名の末尾を削ったものが配列パラメータ名になっているっぽい)


おおまかなパラメータアクセスの方法

パラメータへのアクセス方法

  1. パスを指定して直接パラメータオブジェクト(hou.Parm)へアクセス
  2. 任意のノードのパラメータオブジェクト(hou.Parm)へ、名前を指定してアクセス

パラメータの操作方法

  1. ノードオブジェクト(hou.Node)を直接操作
  2. パラメータオブジェクト(hou.Parm)を通して操作

単一パラメータオブジェクトの取得

import hou

# パラメータオブジェクトを直接取得
parm_direct = hou.parm('parm_path')

# ノードオブジェクトからパラメータ名を使用してパラメータオブジェクトを取得 
node = hou.node("node_path")
parm_from_node = node.parm('parm_name')

配列パラメータオブジェクトの取得

 translateやrotateのような複数要素で構成されるパラメータは、配列パラメータとしてまとめて各要素へアクセスすることができる。
 基本的に、これは単一のパラメータへのアクセスと同じように行うが、その際は、parmTupleやevalParmTupleといった配列パラメータを扱う専用のメソッドを使う。

import hou

# 配列パラメータオブジェクトの子要素配列を取得
tupleParm_direct = hou.parmTuple('tupleParm_path')

# 配列パラメータの値を取得
# ノードオブジェクトをつかまえておく
node = hou.node("node_path")
tupleParm_from_node = node.parmTuple('tupleParm_name')

# 子要素へは、tupleParmの配列要素へアクセスして行う
tupleParm_x = tupleParm_from_node[0]
tupleParm_y = tupleParm_from_node[1]
tupleParm_z = tupleParm_from_node[2]

単一パラメータの値を取得する

 eval~と名づけられたメソッドを使う。
 型を指定して型変換しながら値を取得するメソッドや、時間指定で値を取得するメソッド、それらを組み合わせて取得するメソッドなどもある。

import hou

# 単一パラメータオブジェクトから現在の値を取得
parm = hou.parm("parm_path")
parmValue = parm.eval()

# 型を指定ながら現在の値を取得
parmIntValue = parm.evalAsInt()
parmFloatValue = parm.evalAsFloat()
parmNodeValue = parm.evalAsNode()

# 指定時間での値を取得
parmAtFrameValue = parm.evalAtFrame( FRAME_NUMBER )

# 型指定と時間指定の組み合わせ
parmIntAtFrameValue = parm.evalAsIntAtFrame( FRAME_NUMBER )
parmFloatAtFrameValue = parm.evalAsFloatAtFrame( FRAME_NUMBER )

配列パラメータの値を取得する

 配列パラメータの値取得も、単一パラメータとほぼ同様。

import hou

# 配列パラメータオブジェクトから現在の値を取得
tupleParm = hou.parmTuple("parm_path")
tupleParmValue = tupleParm.eval()

# 型を指定ながら現在の値を取得
parmIntValues = tupleParm.evalAsInts()
parmFloatValues = tupleParm.evalAsFloats()
parmNodeValues = tupleParm.evalAsNodes()

# 指定時間での値を取得
parmAtTimeValue = parm.evalAtTime( TIME )
parmAtFrameValue = parm.evalAtFrame( FRAME_NUMBER )

# 型指定と時間指定の組み合わせ
parmIntAtFrameValues = parm.evalAsIntsAtFrame( FRAME_NUMBER )
parmFloatAtFrameValues = parm.evalAsFloatsAtFrame( FRAME_NUMBER )

パラメータをセットする

 値のセットは、set系メソッドを使う。
 事前に組み立てたパラメータ辞書をsetParmsメソッドに与え、一括で値をセットするのがとても楽なのでおすすめ。
 その際、setParmsメソッドでは配列パラメータ名は認識できないので、各要素を個別の単一パラメータとして辞書に値を用意します。

import hou

# 単一パラメータに値をセットする
parm = hou.parm('parm_path')
parm.set( value )

# 配列パラメータにシンプルな値の配列をセットする
tupleParm = hou.parmTuple('parm_path')
tupleParm.set( (value1, value2, value3, ...) )

# ノードオブジェクトを使い、複数のパラメータをまとめてセットする
# セットしたいパラメータを辞書に溜め込み、setParms()に与える
parmDict = { 'parm_name1':value1, 'parm_name2':value2, ... }
node = hou.node('node_path')
node.setParms(parmDict)

 以上、何か不明点などあれば気軽にご質問ください。
 間違いがあればツッコミも大歓迎です。

Houdini – Python(HOM)によるノード選択(改題)

 あるシーンを開いた時、膨大なオブジェクト数とグループ数により階層が深く、なおかつすべてのノードに命名規則が全く見当たらないという、ツール作成者視点でシーンを眺めた時、ついつい汚い床の上をのたうち回りたくなるような状況は往々にしてあることなのですが、そのような状況下でのHoudiniの選択操作は、他のDCCツールよりも遅れを取っている気がします。

 例えば、ビューポートで選択しているすべてのノードの親を簡単にまとめて再選択したかったり、同様に、選択アイテムのすべての下流ノードをまとめて選択したかったりと言った状況です。

 ノードを右クリックして表示されるポップアップメニューから、Inputs/Outputsサブメニューを駆使してみるも、単一の選択アイテムのみがノード検索の起点になるので、複数のノードツリーをまとめて選択できなかったりします。

 このように、意外とHoudiniデフォルトの選択機能に痒いところが多かったので、選択補助スクリプトを書いてみました。

 ついでと言ってしまうと何だか申し訳なく感じてしまうのですが、簡単なメモを書き残したいと思います。


基本的なノード検索

import hou
nodeObject = hou.node("node_path")
import hou
selectedItems = hou.selectedItems()

ノードの親子関係

import hou
node = hou.node('node_path')

# parent()は自身を含む外側のノードの取得
outerNode = node.parent()
print( outerNode )

# children()は自身が含む内側のノードリストを取得
innerNodes = node.children()
print( innerNodes )
import hou
node = hou.node('node_path')

# inputs()は自身へ入力している直接の上流ノードリストの取得
inputNodes = node.inputs()
print( inputNodes )

# outputs()は自身が直接出力しているノードリストを取得
outputNodes = node.outputs()
print( outputNodes )

# inputAncestors()は直系の祖先をまとめて取得
ansectorNodes = node.inputAncestors()
print( ansectorNodes )

かなり基本的なところではこんな感じ。
他にもたくさんあるので、その他のメソッドは要マニュアル参照。

出力の全子孫を取得するメソッドがパット見見つからない感じだったので、再帰的に子孫をたどる関数を作ってみる。

import hou

def getDescendents(node, *args, **kwargs):
    """
    DESCRIPTION : 引数で与えたノードから再帰的に子孫を辿って返す
      ARGUMENTS : node : 探索を開始するノード
         RETURN : descendents : 見つかった子孫ノードリスト
    """
    descendents = []

    direct_outputs = node.outputs()
    if direct_outputs:
        for direct_output in direct_outputs:
            descendents += [direct_output]
            descendents += getDescendents(direct_output)
   
    return descendents
    
node = hou.node('node_path')
descendents = getDescendents(node)
for descendent in descendents:
    print( descendent )

選択操作

import hou
node = hou.node("node_path")

# 選択 setSelectedメソッドにブール値をセット 1で選択0で非選択
node.setSelected(1)

# 選択解除
node.setSelected(0)

 上記を組み合わせて、現在のノードから1つ上流のノードを探し、再度全下流ノードを探して全選択を行うなどの便利なコマンドを作成できます。

KINGSGLAIVEセミナーおさらい プロテス – サンプルhiplcファイルあり

先週と同様、Indyzone主催のHoudiniセミナー「KINGSGLAIVE FINAL FANTASY XV メイキングストーリー」のおさらいをしていました。

というわけで、今日はプロテスのエフェクトです。

今回もサンプルファイルを付けてみました。
興味のある方はぜひ覗いてみてください。

■ダウンロード(One Drive)
MYAM_imitation_FF_KINGSGLAIVE_Protes.zip

zipファイル解凍後に出てくるHoudiniProjectフォルダにSetProjectしてからシーンを開いてください。


■シーンファイルに関して

うろ覚えではあるのですが、セミナーの説明では、@Cdアトリビュートを利用してプロテスエフェクトの「生成、再生、破壊」の振る舞いを決める部分で、ベースになるプロテス障壁の形状を分流してそれぞれを個別のグループに入れながらPaintSOPなどで@Cdの設定を行い、最後にMergeSOPで各データフローを統合してから、最終的に使用しないグループに含まれる成分を削除するという解説だったように思います。

今回は、この部分を極力簡略化し、カラーを独自アトリビュートに変換したあとでAttributeTransferSOPでレンダーモデルに転送する方法を試してみました。
空間転写は処理負荷が高いので、もう少し上手に軽い方法を使えばよかったと思っていたりします。


何か間違いなどあればご遠慮無くツッコミください。
とても喜びます。

Houdiniセミナーおさらい – サンプルhiplcファイルあり

先日、「Houdini プロシージャルモデリングテクニカルセミナー」が開催されました。

セミナー中に、CG World 2016年09月号のHOUDINI Cook Bookで掲載された作品「Spine」がどのように作られたかを、著者の秋元氏が自ら解説するという貴重なセッションがありました。

そこで感銘を受け、自分なりの解釈も踏まえつつ真似してみたくなり、早速作ってみました。
記事と解説をすべてトレースするのではなく、VOPで処理されていた場所をVEXで処理するなど、ちょっとだけアレンジしています。
シェーダーまでは手を付けられなかったので、簡易モデルと動きのみです。

今回は実験的に、Houdini Indie 15.5.523 で作成した hiplcファイルを公開してみます。
ファイルには、簡単なモデルと本体モデルとシリンダーの挙動を組み込んであります。
時間経過に伴いウネウネ動く仕込みもしてあるので、再生してみると楽しいかもしれません。

■ダウンロード(One Drive)
MYAM_imitation_spine_hiplc

“NATURE OF CODE” in Houdini – 000 – はじめに

長い間、仕事ではパイプライン系ツールを書くことが多くなり、幾何学や物理などの数学とはだいぶ疎遠になってしまいました。

最近になって、やっとHoudiniを業務で使用させてもらえる環境が与えられたこともあり、これも良い機会ということで、これからしばらくの間、数学の勉強や復習とリハビリを兼ね、Processingの名著 [NATURE OF CODE] の内容を、Houdiniを使って追いかけていきたいと思います。



はじめに

I.1 ランダムウォーク

オブジェクトが累積的にランダムな方向に動く動作。


I.2 ランダム・ウォーカークラス

・ランダム・ウォーカークラスを定義

Houdiniでカスタムノードを定義することはクラスを定義することと言えそう。
ただし、現時点では継承等の仕組みがあるか不明なので、あくまでもクラスもどきの定義と解釈しています。

ここでは、まずSubnetをクラスとしてとらえつつ、その内部に、ランダムウォークする点群を複数含むオブジェクトを定義する。

・擬似ランダム値

一般的に、コンピュータ上で乱数を生成するランダム関数は実際には周期性があるので擬似的なランダム関数と言える。
ただし、周期が非常に長いため、実質的に真の乱数計算関数と言って差し支えない。


I.3 確率と一様分布(離散)

HoudiniでVEX関数のrandom()を使った場合、特定の値が出現する確率はほぼ一定。
具体例として、4種類の値を生成するランダム関数があるなら、それぞれの値が出現する確率はほぼ均等で約25%になる。

bandicam 2016-06-12 22-55-14-514

random()から出力される0~1のfloat値を0~9のint値にマッピング。
乱数を1200回発生させ、マッピング後の整数値に対応するIDを持つポイントをその都度+Y方向に移動した結果、多少のばらつきはあるものの、ほぼ均等な値が生成されていることがわかる。
このように、起こりうる結果がほぼ均等に現れる確率の分布を、一様分布と呼ぶ。
他の例としては下記のようなものもある。

  • コイントスの確率
    1/2 = 0.5 = 50%
  • 全トランプ52枚からA4枚のいずれかを引く確率
    4/52 = 0.077 = 約8%
  • 全トランプ52枚からダイヤのカード13枚のいずれかを引く確率
    13/52 = 0.25 = 25%
  • 1つのサイコロを振り、1の目が出る確率。
    1/6 = 0.166 = 約17%

・確率と非一様分布

bandicam 2016-06-12 22-55-21-874

一様分布の項で作成したランダム関数を2つ足しあわせ、値が出現する回数を計測してみる。
結果、グラフは中央値が高く、山なりになるのがわかる。

bandicam 2016-06-12 22-55-26-468

今度は、3つのランダム関数を使って同様の計算をしてみる。
より顕著に、山なりになる。

サイコロを3個振る場合を考えてみると理由は簡単。
合計値が最小の3または最大の18になるのは1または6のゾロ目の時のみで非常に確率が低いのに対し、合計値が中央付近の10になるケースはより多くの組み合わせが考えられる。

・乱数に偏りを持たせる

乱数を元に、予め用意された動作を選択する際、結果に偏りを持たせるには。

  1. 乱数を元に得たい結果がリストにまとめられているケース
    結果がリスト内に登場する回数を操作して確率に偏りを持たせる

    <<擬似コード>>
    results = [1,2,3,4,5] → [1,1,1,2,3,3,4,5,5,5]
  2. 乱数の値の範囲に応じて結果が決定するケース
    結果に対応する乱数の範囲を操作して偏りを持たせる

    <<擬似コード>>
    if random < 0.1:
        return 10
    elif random < 0.2:
        return 20
    else:
        return 30

I.4 ランダム値の正規分布(ガウス分布)

ランダムな要素が平均値付近に集中する確率分布を正規分布やガウス分布(Gaussian)、ラプラス分布(Laplacian)などと呼ぶ。

[参考サイト] NtRand – 正規分布
http://www.ntrand.com/jp/normal-distribution-single/

 例として、無作為に選んだ人々の身長の分布を考える。
百歩譲っていくら確率が0ではないと言っても、身長0.1cmの人や身長10mの人はそうそういない。
平均慎重と言われている165cm付近が最も多くなるはず。

・ベル型曲線

確率の平均[μ]と標準偏差[σ]により求められる、確率分布を表すグラフ。

  • 平均:μ
    起こりうる値の平均値。
  • 標準偏差:σ
    偏差とは、値と平均値の差。
    平均偏差は全偏差の平均。
    標準偏差は、分散の平均の平方根。(※分散は偏差の2乗)

・乱数の生成(random)

HoudiniにおけるVEX関数のrandom()は、0~1の範囲で一様分布の乱数を発生させる事ができる。(その他にも、3Dノイズなども出力できる)

・乱数の生成(noise)

HoudiniにおけるVEX関数のnoise()はシンプルなパーリンノイズ。
正規分布をもとに乱数を発生させる。
ループ中で新たなポイントを作成しながら乱数を発生させ、@P.xに与えていくと、各ポイントは下図のように配置されていく。

図では色が薄く見づらいが、μ=0.5、σ=1の正規分布で乱数が生成され、x=0.5の付近でより高密度にポイントが作成されていることがわかる。
※1マス=0.25


I.5 ランダム値のカスタム分布

  • 分岐条件を乱数をもとに判定(レヴィフライト風アルゴリズム)
    乱数の発生を一様分布でも正規分布でもない独自のものにしたい場合のテクニック。正規分布の乱数中で、時折「突発的」にレンジの大きな乱数を発生させたい場合は、現在の乱数生成の機会が「突発的」なものであるか判定し、突発的であると判断された場合、結果の範囲が大きな乱数を発生させるようにする。

    ## 擬似コード
    incident = random(1)
    if incident < 0.1:
        randValue = random(-100,100)
    else:
        randValue = random(-1,1)

    上の例では、incidentが0.1以下(10%)の確率でレンジの大きな乱数を生成している。

  • 出力が条件に適合するまで乱数の生成を試行する(モンテカルロ法風アルゴリズム)
    値が大きければ大きいほど選ばれやすくしたい場合のテクニック。
    乱数を2つ使い、片方がもう片方より大きかった場合に乱数を出力する。

    def montecarlo():
        while 1:
            r1 = random(0,1)
            r2 = random(0,1)
    
            if r1 <= r2:
                return r2

I.6 パーリンノイズ(よりスムーズな手法)

有機的なものの表現には、ランダム値の中にも連続性が必要になる。
その場合、ランダムかつ連続的に変化する値を出力するnoise関数を使用するとよい。
HoudiniのVEX関数 noise()は、正規分布に近い連続的でランダムな値を返すパーリンノイズ関数で、整数を与えるとすべて同じ値(0.5)を返すようになっているので、引数は極力整数値にしないように注意する。

## noise()の使用例
f@dx = fit(noise(@Time+(@ptnum*0.339)+offset),0,1,-1,1);

・疑問

Mayaの場合、noise()関数は周期性を感じさせない上に、出力される値も-1~1の範囲にきっちり収まるようになっている。
そのため、単純なフレーム番号を引数にするだけで延々と連続的なランダム値を生成し続け、その値を係数にして角度の変化などを計算する際、変化のレンジにただ掛け合わせるだけでよかった。

Houdiniのnoise()関数は、整数値を与えると全く同じ値が返る上に、結果が正規分布で得られるため、Mayaのnoise()関数と全く同じ感覚では使えない。

正規分布のおかげで結果を有機的にしやすい反面、変化幅の上下限をきっちり決めづらくとても気持ち悪く感じてしまう。(例えば毎フレーム何らかのオブジェクトの角度を変化させたい時、0~180度の角度範囲を上下限とし、-1~1の範囲の係数をかけて使いたい場合など)
もしかしてMayaのnoise()に近い関数があるんだろうか?(誰かご存知でしたら教えて下さい)

・パーリンノイズによるランダムウォーク

・2次元のノイズ

パーリンノイズは1~4次元の値を引数に取り、同様に1~4次元の値を出力できる。

・@Cdにnoise(x,y)の結果を適用
832

・@P.yにもnoise(x,y)の結果を適用

※わかりやすくするため、頂点数とカラーのレンジを調整済み


I.7 展望

この章では、自然現象をシミュレートする際に必要となる確率や乱数を学習した。
ランダム性を取り入れることで、より自然で複雑な要素を作成できる。
だがしかし、同じアルゴリズムにばかり頼ってしまうと結果的にパターンが透けて見えてしまい、結果が退屈な仕上がりになる場合もあるので、より多くの手法を身につけ、引き出しを増やし、様々なニーズに臨機応変に対応できるスキルを磨くことが大事。


[資料]

NtRand – 確率分布Navi

確率分布 Navi


http://www.ntrand.com/jp/gallary-of-distributions/


何か間違いなどあればツッコミいただけると喜びます。
よろしくお願いします。

Houdini Crowd System – 003 / Ragdoll + Custom force テスト

また、絵的に少しどうかしている映像をアップしました。

今回は、エージェントをただラグドール化するのではなく、Stateの遷移を引き起こすボリュームにエージェントが含まれたらラグドール化し、そのボリュームから発せられる力を受けて、任意の方向に飛ばされるように設定するようにしてみました。
動画では非表示にしていますが、足元からエネルギー球がせり上がり、それに触れたUnityちゃんエージェントが吹き飛ばされているように設定してあります。

左に見えている点群がイベントボリューム兼カスタム外力発生器です。
各点から出ている黄色のラインは力の方向です。

この点群を大型キャラの四肢にアタッチするなどしておけば、自然と、大型キャラに暴れるモーションを付けるだけで、群がる群衆をを次々となぎ倒していくような絵が作れますね。

Houdiniのforループを理解する

■ループの種類と用途

  1. forループ
    入力したジオメトリデータ全体を対象に、複数回の処理を繰り返し行う

    (例)
    Copy SOPで複製されたジオメトリ全体を対象に、Mountain SOPの処理を累積的に10回行う。

  2. for-eachループ
    入力したジオメトリ内の個別のピースに対し、同一の処理を行う

    (例)
    複数のグループを複数含むジオメトリデータから、特定のルールにマッチするグループを1つずつ個別に取得し、個別にバウンディングボックスに置き換える。

  3. Fetch FeedbackとFetch Pieceの組み合わせ
    ループに入力されたジオメトリに対し、別の入力されたジオメトリのピースを使って反復的かつ累積的に処理を行う事ができる。

    (例)
    穴あきチーズ
    複数の気泡オブジェクトを入力し、気泡オブジェクトのピースごとに、チーズの本体に累積的に繰り返し削り取る。


■forループ

Tab -> For loop

オレンジ色に囲まれた部分がforループのブロック

MountainSOPを挿入しHeightを0.1に変更
Block EndのIterationパラメータを変化させると、各ボックスが累積的に変形することが確認できる。

※同じ設定のMountainSOPで同一の処理を累積的に10回繰り返すことと同じなので、CopySOPの下流にMountainSOPを10個作って直列で繋げても同じ結果が得られます。


■for-eachループ

Tab -> For-Each loop

ピース単位の処理。
ピースは、ピースを表現するアトリビュート(通例では@name)の値の同一性により表現される。

For-Each loopを作成した直後は、上流に@pieceや@nameがないためにエラーが出る場合がある。このような場合は、自分でアトリビュートを作成する。

Voronoi Fracture SOPなど、自動的に@nameを作るSOPもある。
Voronoi Fracture SOPのデフォルト設定では、破片ごとにPrimitiveクラスの@nameを作成し、piece+番号の命名規則で各Primitiveが所属するピースを表現する。

@name以外のアトリビュートを使用してピースを表現したい場合は、Block End SOPのPiece Attributeを変更し、任意のアトリビュート名を指定する。

CopySOPで複製したBoxをVoronoi Fracture SOPで分割

For-Each loopにより、各破片のピースをバウンディングボックスに置き換え


■Block Begin SOP

・Methodパラメータ

  1. Fetch Feedback
    累積的に処理を行う場合に選択。
    ループが行われる度、前回のループで出力された結果を現在の処理対象としてフィードバックし、再Fetchする。
  2. Fetch Piece
    入力されたジオメトリ内のピースごとに処理を行う場合に選択。
    ループ毎に、認識されているピースの中から新たなピースがFetchされる。
  3. Fetch Metadata
    現在のループに関する情報をDetailsアトリビュートに持つジオメトリを作成する。
    detail関数を使ってこれらの値へアクセスし、ループ内で使用することができる。

・Block Pathパラメータ

このループの終点を示すBlock End SOPへのパス

・Create Meta Import Node

Metaデータを持つBlock Begin SOPを作成する。
この実態は、ModeがFetch Metadataに設定され、Block Pathが現在のループの終点にセットされているBlock Begin SOP。


■Block End SOP

・Gather Method

  1. Fetch Each Iteration
    ループが終わるたびに結果を再Fetchする
    ループのBlock Begin SOPでMethodがFetch Feedbackの場合に使用する。
  2. Merge Each Iteration
    ループ毎にジオメトリを記録し、最後に結合する。
    ループのBlock Begin SOPでMethodがFetch Pieceの場合に使用する。
    ピースは個別に処理され最後に結合され、結果として出力される。

・Iterations/StartValue/Increment

ループ回数の指定
一般的なforループで使用する変数

・Python風に書くならこんな感じ
for i in range(startValue , iterations , increment):


■Block Begin SOP – Fetch Feedback と Fetch Pieceの組み合わせ

あるジオメトリに対し、別のジオメトリを使って複数回の処理を累積的に行う事ができる。
Block BeginのヘルプにあるSwiss Cheeseサンプルでは、チーズ本体のVDBオブジェクトを一つと、気泡のVDBオブジェクトを複数入力し、チーズ本体オブジェクトを気泡オブジェクトごとに削り取る処理を行う。

  1. チーズ本体のジオメトリを作成
  2. 気泡ジオメトリを作成
    SphereSOPをPolygonなどではなくPrimitiveモードで作成。
  3. Scatter SOP + Copy SOPでチーズ表面に気泡をばらまく
  4. For loopを作成
  5. チーズ本体を受け取るBlock Begin SOPの設定を確認
    Methodパラメータ:Fetch Feedback
    Block Pathパラメータ : Block Endへのパス
  6. 気泡を受け取るBlock Begin SOPを追加
    Block Begin SOPを作成後、以下のパラメータをセット。
    Methodパラメータ : Fetch Piece
    Block Pathパラメータ : 1で作ったBlock Endへのパスをセット
  7. Block End SOPを確認
    Iteration Method:By Pieces(またはAuto Detect from Inputs)
    Gather Method:Feedback Each Iteration
    Piece Elements:Primitive
    Piece Name:オフ
    Default Block Path/Piece Block Path:Block Begin

 


■Block Begin SOP – Fetch Metadataの使い方

  1. Block Begin SOPを作成
    Method:Fetch Metadata
    Block Path:末端のBlock End SOPへのパス

・Detailsアトリビュート

  • numiterations
    最大のループ回数
  • iteration
    現在のループ回数
  • value
    Pieceループ時、現在ループ中のピースを識別するためのアトリビュート値。
  • ivalue
    valueの整数版。
    valueがfloatの精度範囲を超える場合などに使う。
    特別な理由がなければ普段はこっちを使うのがよさそう

・Metadataを取得する

ループ内でdetailエクスプレッション関数を使う。

  1. detailエクスプレッション関数を使う
    detail(“../foreach_begin1_metadata”, “iteration”, 0)
  2. detail VEX関数を使う
  3. Import detail attribute VOPを使う
  4. Pythonで取得する
    node(“../foreach_begin1_metadata”).geometry().attribValue(“iteration”)

■ループの停止条件

Block End SOP の Stop Condition を1にセットすると強制的にループを終了できる。


■デバッグとテストループ

・Feedbackループの場合

Block End SOP の Max Iterationsパラメータで最大ループ数を設定できる。
数回のループだけである程度結果が判断できる場合は、このパラメータを有効化することでループ処理を制限し、素早く調整が行えるので便利。

・Pieceループの場合

Block End SOP の Single Passパラメータでピース番号を指定して、ピースごとの処理結果を確認できる。


■グループからピースを作成する

グループパラメータを使ってピースを作成したい場合は、Name SOPを使用して@nameを作成し、グループ名をセットしておく。


何か間違いがあれば突っ込んでいただけると嬉しいです

Ricoh THETA S でIBL画像を作成するためのスクリプトを書きました

簡易的なIBL用のHDRIを作成するため、素材になる天球マップ画像をRicoh THETA SをiPhoneで遠隔操作して手軽にオートブラケット撮影できるようにしてみました。

撮影方法は、ISOを固定しEVを変化させて行う露出ブラケット撮影になります。
今の所、使用する撮影モードではシャッタースピードを同時に操作できなそうなので、自動設定にしてあります。

THETA SはRAWデータでの撮影ができず、また撮影時に厳密なホワイトバランス設定などもできないので精度は落ちますが、簡易的に使用するにはそこそこ使えるHDRIが得られると思います。

iOSデバイスに限らず、THETA Sとネットワーク接続できるデバイスで、なおかつ標準的なPythonを実行できるなら同じように使用できるはずです。

というわけで、需要がありそうなのでソースコードを公開します。

時間の関係で良い作例の用意ができていませんが、そのうち貼りたいと思います。

・使い方

1:デバイスとTHETA Sをネットワーク接続接続します

2:下記スクリプトをデバイス内のPython実行環境で実行します
当方は、iPhone6S+上で iOS用のPython 2.7 というアプリを使用しています。

3:撮影されたJPEG画像をHDR ShopLuminance HDRなどを使ってHDRIにします

#******************************************************************************
'''
Tool Name     : MYAM_ThetaAutoBracket.py
Description   : 
Copyright     : (c) Yamabe Michiyoshi
Author Name   : Yamabe Michiyoshi
'''
#******************************************************************************

import httplib
import json
import time

# 
class ThetaSettings(object):
    # Full
    # evs = [-2.0, -1.7, -1.3, -1.0, -0.7, -0.3, 0.0, 0.3, 0.7, 1.0, 1.3, 1.7, 2.0]

    # Simple
    evs = [-2.0, -1.0, 0.0, 1.0, 2.0]

    exposureProgram = 9
    iso = 100
    whiteBalance = "auto"
    _shutterVolume = 50

    shutterInterval = 1

#
class Theta(object):
    def __init__(self):
        self.headers = {"Content-Type":"application/json", "Accept":"application/json"}

    # Auto Bracket
    def takePicturesByAutoBracket(self):

        print "--------------------"
        print "START AUTO BRACKET"
        print "--------------------"

        # Connect to THETA 
        connection = self.connect()

        # Start Session
        self.startSession(connection)

        # Get Session ID
        responseData = self.getResponseData(connection)
        sessionId = responseData["results"]["sessionId"]

        # Take Pictures
        for i in range(len(ThetaSettings.evs)):
            
            # Set options
            ev = ThetaSettings.evs[i]
            
            print "----------"
            print "%i / %i" % ((i + 1), len(ThetaSettings.evs))
            print "iso : %i" % ThetaSettings.iso
            print "EV  : %d" % ev

            picOpt = {"exposureProgram":ThetaSettings.exposureProgram,
                      "exposureCompensation":ev,
                      "iso":ThetaSettings.iso,
                      "whiteBalance":ThetaSettings.whiteBalance,
                      "_shutterVolume":ThetaSettings._shutterVolume}

            self.setOptions(connection, sessionId, optionParams=picOpt)
            responseData = self.getResponseData(connection)

            # Take Pictute
            self.takePicture(connection, sessionId)
            
            # Wait to finish idle time
            self.waitFinishCurrentCommand(connection, interval=ThetaSettings.shutterInterval)

        # Close Session
        self.closeSession(connection, sessionId)

        # Disconnect from THETA
        self.disconnect(connection)

        print "--------------------"
        print "FINISH AUTO BRACKET"
        print "--------------------"

    # HTTPConnection Control
    def connect(self):
        connection = httplib.HTTPConnection("192.168.1.1",80)
        return connection

    def disconnect(self,connection):
        connection.close()

    # Common Commands
    def postExecCommand(self,connection,params):
        connection.request("POST", "/osc/commands/execute", params, self.headers)

    def postStatusCommand(self, connection, commandId):
        params = json.dumps({ "id":commandId })
        connection.request("POST", "/osc/commands/status", params, self.headers)

    def waitFinishCurrentCommand(self, connection, interval=1):
        responseData = self.getResponseData(connection)
        commandId = responseData["id"]

        while True:
            self.postStatusCommand(connection, commandId)
            responseData = self.getResponseData(connection)
            commandStatus = responseData["state"]

            if "inProgress" == commandStatus:
                print "commandId [%s] : Processing..." % ( commandId )
                time.sleep(interval)
            else:
                print "commandId [%s] : Finish!" % ( commandId )
                break

    # Command Control
    def startSession(self, connection):
        params = json.dumps({ "name":"camera.startSession", "parameters":{} })
        self.postExecCommand(connection, params)

    def updateSession(self,connection,sessionId):
        params = json.dumps({ "name":"camera.updateSession", "parameters":{"sessionId":sessionId} })
        self.postExecCommand(connection, params)

    def closeSession(self, connection, sessionId):
        params = json.dumps({ "name":"camera.closeSession", "parameters":{"sessionId":sessionId} })
        self.postExecCommand(connection, params)

    def takePicture(self,connection , sessionId):
        params = json.dumps({ "name":"camera.takePicture", "parameters":{"sessionId":sessionId} })
        self.postExecCommand(connection, params)

    def setOptions(self, connection, sessionId, optionParams):
        params = { "name":"camera.setOptions" , "parameters":{"sessionId":sessionId, "options":{} } }

        for key , value in optionParams.items():
            params["parameters"]["options"][key] = value

        jsonParams = json.dumps(params)
        self.postExecCommand(connection, jsonParams )

    def getOptions(self, connection, sessionId, optionNames):
        params = { "name":"camera.setOptions" , "parameters":{"sessionId":sessionId, "optionNames":{} } }

        for key , value in optionParams.items():
            params["parameters"]["options"][key] = value

        jsonParams = json.dumps(params)
        self.postExecCommand(connection, jsonParams )

        self.postExecCommand(connection, params)

    # Support Functions
    def getResponseString(self,connection):
        response = connection.getresponse()
        responseString = response.read()
        return responseString

    def getResponseData(self,connection):
        responseString = self.getResponseString(connection)
        responseData = self.convertJSONToPythonData(responseString)
        return responseData

    def convertJSONToPythonData(self,jsonDataString):
        jsonDecoder = json.JSONDecoder()
        pythonData = jsonDecoder.decode(jsonDataString)
        return pythonData

    def convertPythonDataToJSON(self,pythonData):
        jsonEncoder = json.JSONEncoder()
        jsonDataString = jsonEncoder.encode(pythonData)
        return jsonDataString

def main():
    theta = Theta()
    theta.takePicturesByAutoBracket()

if __name__ == "__main__":
    main()

・設定

設定の変更を行うにはコードを直接書き換える必要があります。
撮影枚数は、ThetaSettingsの evs で指定されているEV値の数で指定されます。
撮影ごとにリスト内のEV値がそれぞれ使用されます。

・EV値と撮影枚数の設定

class ThetaSettings(object):
    # Full
    # evs = [-2.0, -1.7, -1.3, -1.0, -0.7, -0.3, 0.0, 0.3, 0.7, 1.0, 1.3, 1.7, 2.0]

    # Simple
    evs = [-2.0, -1.0, 0.0, 1.0, 2.0]

指定できるEV値は、上記の Full で指定されている13個の値になります。
THETA Sは一枚撮影するごとに内部処理に約8秒ほどかかるようなので、撮影枚数が多くなると時間がかかります。
必要に応じ、不要な値を削除するなどして調整してください。

・各種オプションの設定

    exposureProgram = 9
    iso = 100
    whiteBalance = "auto"
    _shutterVolume = 50

現在は、上記を指定できます。
必要に応じ、RICOH THETA API v2 Referenceを参照して値を設定してください。
といいつつおそらく上記の中では iso と _shutterVolume くらいしかいじらないと思います。

・_shutterVolume
0−100の間で指定します。0が無音です。

・iso
以下の値が使えます
100, 125, 160, 200, 250, 320, 400, 500, 640, 800, 1000, 1250, 1600

Fabric Engine 1.15.3 プラグインをMayaへ導入する

Fabric Engine 1.15.3 を Maya 2014 64bit(EN) に導入するまでの手順を解説します。


インストール


 ・Fabric Engine公式サイトでファイルをダウンロード

 まずは、Fabric Engineのダウンロードです。
Fabric Engine公式サイトへ行きダウンロードします。

【Fabric Engine】http://fabricengine.com/

 今回は個人的に評価/研究のために使用するので、Evaluation ライセンスでダウンロードしました。


REQUEST LICENSEを押します。
すると、ユーザー情報の登録フォームページが開きます。


・ユーザー情報登録フォームに必要事項を入力

各項目を入力します。

First Name : 姓
Last Name : 名
E-Mail : メールアドレス
JOB Title : 役職
Company : 会社名
WebSite : 会社のWebサイトURL(個人であれば個人サイト)

Please tell us a little about what you
do and how you plan to use Fabric Engine.

Fabric Engineを使用する目的を簡単に記入します。

入力が終わったらSUBMITを押します。
すると、ダウンロードページが開きます。


・ダウンロードページ

使用するOSのブロックにあるDOWNLOADボタンを押し、ダウンロードを開始します。

※当方はWindows版を使用するので、Windows環境での解説をします。


・ファイルの解凍と配置

 ダウンロードした「FabricEngine-1.15.3-Windows-x86_64.zip」を解凍します。
解凍して出来たフォルダ「FabricEngine-1.15.3-Windows-x86_64」を任意の場所へ移動します。
今回は、分かりやすい C:\Program Files 直下に移動する事にしました。


・Maya.envを編集しモジュールパスを通す

Maya.envファイルはMaya2014の場合、以下のパスにあります。

C:\Users\USER_NAME\Documents\maya\2014-x64\Maya.env

上記.envファイルをテキストエディタで開き、以下の行を追加します

MAYA_MODULE_PATH=C:\Program Files\FabricEngine-1.15.3-Windows-x86_64\SpliceIntegrations\FabricSpliceMaya2014SP3;

※各パスは各々の環境に合わせて適宜書き換えてください。
※当方の環境はMaya2014ですので、FabricSpliceMaya2014SP3にします
※良いエディタがない場合、ワードパッドは使わずメモ帳を使用すると安心です。


・MayaにFabric Engine(FabricSpliceMaya.mll)をロードする

 Mayaを起動し、Plugin Managerを開きます。
前の手順でMaya.envに追記された「MAYA_MODULE_PATH」で示されるパスの中にFabricSpliceMaya.mll があることを確認し、Loadedにチェックを入れます。問題なくチェックできればロード完了です。
必要に応じてAuto Loadをチェックしておくと、次回以降のMaya起動時に自動ロードされるようになり便利です。

 緑色の i ボタンを押し、プラグインにより追加されたノードやコマンドを確認してみます。

 以上でMayaへのFabric Engineの導入は完了です。


・ノードを作ってみる

 試しに、Mayaで以下のPythonコードを実行してみます。

import maya.cmds as cmds
cmds.createNode("spliceMayaNode")

 Maya起動後の初回ノード作成時は、Fabric Engineのシステムをロードするためか、実際にノードが作成されるまで結構時間がかかります。

 先ほど作成したspliceMayaNodeノードをアトリビュートエディターで表示してみました。

 実際にコードを編集する際は、Open Splice Editor ボタンで開くSplice Editorを使用することになります。

 具体的な使い方やTIPSは追々勉強しながら書いていこうと思います。

Maya – リソースイメージを抽出

import maya.cmds as cmds
resources = cmds.resourceManager()
for resource in resources:
  saveDirPath  = "D:/MYAM_temp"
  saveFilePath = "%s/%s" % ( saveDirPath , resource )
  cmds.resourceManager( saveAs=(resource , saveFilePath ) )

ツールのGUIを作ってる時、Mayaのリソースイメージをそのまま流用したいことがある。
わざわざキャプチャしたりするのは面倒なので、上記のコマンドでごっそりイメージファイルを抜き取って、必要なアイコンを使ってしまおう。


こんな感じで簡単にアイコンを抽出出来た。