2011-12-05

関数と引数 その1(python入門)

前回、「ToolSets と python の組み合わせ その1」の続きを書こうといきまいたのですが、どうやっても関数と引数の説明を避けて通れないので、今回は関数と引数の説明を。

自分でも、シッカリとプログラムを勉強したわけでもないですし、授業等で習ったわけでもないので、全て独学です。なので上手く説明できるか・・・

ともかく関数というのは 、何かを実行するための「機能」です。たとえば今回まずやりたいのは、ファイルのコピーとします。ファイルのコピーには最低でも

「どこにある何て名前のファイルを(コピー元ファイルパス)」を
「どこ(コピー先ファイルパス)」へコピーするのか。

この二つのことを、この「機能」を伝えてやる必要があります。
このある「機能」にたいして「最低限伝えなければならないこと」を引数といいます。「機能」そのものが関数になります。

関数は

def hoge(引数):
    実行文   (※hogeは適当にかいてます。)

という形で定義されます。
「def」 に続けて 関数名+()、でその後に:(コロン)です。
実行文はコロンの後に改行してtab(インデント)や複数の半角スペースで段差をつけます。この段差でこの関数がココからココまでと宣言しているので段差は重要です。

実際に書いてみます。
-------------------------------------------------------
def hoge():
    print "Hello!"
-------------------------------------------------------
実行する場合は、hoge() で実行します。
print はスクリプトエディタのアウトプット側に指定されたことを表示するってことです。
なので、この関数は実行すると Hello! と表示するスクリプトということになります。
また表示したいことは Hello! そのもので文字列(文字そのもの)なので、" " で表示したい文字列を囲っています。


上のアウトプット側には Hello! が出ていますが、" " は出ていません。
たとえば、

-------------------------------------------------------
A = "Hello!"
def hoge():
    print A
-------------------------------------------------------

として、hoge() を実行すると、Aが表示されず A = "Hello!" で定義された、Hello! が表示されます。


ここでもやはり、" " がなく Hello! と表示されます。

さっきは、A = "Hello!" であらかじめ A を定義しましたが、関数の魅力は後から変数を指定できるということでしょうか。つまり、

何を表示するかは未定だけど、とりあえず、あとから決まった事を表示する

といったところです。
具体的には

-------------------------------------------------------
def hoge(A):
    print A
-------------------------------------------------------

とします。何をprintするかは決まってないけどとりあえず A としておいて、関数として hoge(A) で、とりあえずprintすることは A で仮に置いておくけど、あとからちゃんと決めてね という感じです。

で hoge() をすると


となってなにやらエラーが出ます。
とりあえず、A で仮置きしたけど、その A って何よ? って感じのエラーでしょうか。
つまり、表示する A にあたるものを宣言してやる必要があります。
それは hoge() の ()カッコ中に書きます。
たとえば hoge("Good Bye!") とすると


結果として Good Bye! が表示されました。
コレが引数です。あらかじめ、関数内で後に宣言するであろう事を仮置きしておき、hoge(引数) とすることで、関数にその引数が渡されます。
この引数は一つに限られません。たとえば、

-------------------------------------------------------
def hoge(A, B):
    print A + " and " + B
-------------------------------------------------------
とします。引数としてAとBの二つを与えると、結果として A + " and " + B を返すということですが、この + は文字を足すってことです。
ここで、

hoge("johnson", "johnson")

とすると・・・


となり johnson and johnson を得ます。 hoge("Carl", "Lewis") とすると、


Carl and Lewis を結果として得ます。

と前置きが長かったですが、要するに コピー元のファイルパス と コピー先のファイルパス を引数とするコピーするための関数をつくろうって事です。
レンダリング結果を指定した場所にコピーする というものを作ろうと思います。

nukeでは連番を扱うことが多いので、ファイル単体をコピーというよりはフォルダごとのコピーを紹介します。
で、pythonでその「フォルダごとコピー」をするには copytree() という関数を用います。copytree() を実行するには shutil というライブラリ(モジュール)を読み込む必要があり、そういう場合は

import shutil

と最初に宣言すれば shutil 内で定義されてる関数を用いることが出来ます。実は、これは標準ですでに存在する .py なのですが、どうやら、プログラムというものは全てのライブラリ(モジュール)を一旦読み込んできて、それで実行コードを展開していくというものではなく、必要なライブラリだけ必要に応じて読んできて・・・ っていうものであるらしいです。


こんな感じで、pythonのインストールされたフォルダの Lib フォルダに shutil.py は保存されています。どうやら、pythonはディフォルトでこの Lib フォルダの中身を読む準備が出来ているようです。いわゆる環境パスってヤツでしょうか。
でこの shutil.py の中身ですが・・・


こんな感じで、def ~~(): で定義された関数が複数個収録されてます。実際は import shutil をして、続きは

-------------------------------------------------------
import shutil
shutil.copytree(src, dst)
-------------------------------------------------------

と書きます。そうすると src(ファイルパス) で指定されたファイルが dst(ファイルパス) にコピーされます。
pythonのディレクトリのLibに保存されている(もしくはpythonがデフォルトで読みにいけるディレクトリに保存されている) shutil.py の中で定義されてる、copytree() という関数に、引数として、コピー元としてsrc(ファイルパス)と、コピー先としてdst(ファイルパス)を与える ということです。

そこで、たとえばnuke内から

-------------------------------------------------------
import shutil
shutil.copytree(r'C:/render/111205', r'//192.168.1.5/z/works/hoge/render/111205_copy')
-------------------------------------------------------

を実行してみます。


すると、


指定したディレクトリにファイルがコピーされました。(WindowsのインターフェイスがXPになったり、Win7になったりなのは、このブログを家で書いたり、会社で書いたりしているせいです・・・)

でたとえば、コレをつかって、Writeノード で自動的にレンダリング結果を指定したディレクトリにコピーするという関数を作ってみたいと思います。

この関数はWriteノードから実行するので、レンダリング結果のファイルパスは

nuke.thisNode().knob('file').value()

であらわせます。

-------------------------------------------------------
import shutil

def rendercopy(dst):
    shutil.copytree(nuke.thisNode().knob('file').value(), dst)
-------------------------------------------------------
※dstは引数

であらわせます。これを .nuke((ドット)nuke) フォルダにたとえば mytool.py として保存します。nukeは起動時に .nukeフォルダにある .py を読み込んでいます。なのでWriteノードの「after render」には

-------------------------------------------------------
 import mytool;mytool.rendercopy( コピー先のディレクトリ )
-------------------------------------------------------

とします。実際には以下のような感じです。


本来ならば

-------------------------------------------------------
import mytool
mytool.rendercopy(r'//192.168.1.5/z/works/hoge/render/111205_copy')
-------------------------------------------------------

です。 ただ、このWriteノードの「after render」では改行がきかないので改行部分に ;(セミコロン) を入れてます。

menu.py であらかじめこの.pyをインポートして関数を使えるようにしておけば、最初の import hoge のくだりは不要で、

-------------------------------------------------------
mytool.rendercopy( コピー先のディレクトリ )
-------------------------------------------------------

だけの記述で動きます。
ただ、今回、連番でレンダリングの出力先が


というようになっていて現状の、rendercopy() で取得する、nuke.thisNode().knob('file').value() だと返ってくるファイルパスが 「C:/render/test_%04d.jpg」 となります。
shutil.copytree() ではフォルダを指定しないといけないので、実際には「C:/render」をshutil.copytree()の一個目の引数として渡してやる必要があります。
なので、 「C:/render/test_%04d.jpg」 を 「C:/render」に変えてやる必要があります。
そこで、
-------------------------------------------------------
import re

src = nuke.thisNode().knob('file').value()
srcSplit = src .split('/')
src = src.replace(srcSplit[-1], "")
-------------------------------------------------------

とします。reモジュールの中のreplace()という関数を使ってます。

replaceは
-------------------------------------------------------
hoge = "I want a pineapple"
hoge = hoge.replace("a pineapple", "an apple")
-------------------------------------------------------

とすると hoge = "I want a pineapple" で定義された hoge を print hoge をすると


replace(src, dst) によって src を dst に置き換えられたということになります。
 なので、srcでファイルパスを定義したら、最後の「test_%04d.jpg」を取り除くために、

-------------------------------------------------------
src = nuke.thisNode().knob('file').value()
srcSplit = src .split('/')
-------------------------------------------------------

としてやって /(スラッシュ) ごとに文字列を分けていってリスト化します。
そのリストの最後が「test_%04d.jpg」となるはずなので、それは、

-------------------------------------------------------
src = nuke.thisNode().knob('file').value()srcSplit = src .split('/')
print srcSplit[-1]
-------------------------------------------------------


とすると、「test_%04d.jpg」が返ってくるはずです。
なので、ここで、さっき説明したreplace()を用いて、入れ替える文字列を無し("")にしてやると

-------------------------------------------------------
import re


src = nuke.thisNode().knob('file').value()
srcSplit = src .split('/')
src = src.replace(srcSplit[-1], "")
-------------------------------------------------------

とすれば、当初  「C:/render/test_%04d.jpg」 だった src が 「C:/render/」 に変わります。

なので、mytool.pyを

-------------------------------------------------------
import nuke
import shutil
import re

def rendercopy(dst):
    src = nuke.thisNode().knob('file').value()
    srcSplit = src .split('/')
    src = src.replace(srcSplit[-1], "")
    shutil.copytree(src, dst)
-------------------------------------------------------

と修正してセーブしてnukeを立ち上げなおしてWriteノードを実行させます。

そうすると、レンダリング終了後、このWriteノードの「after render」で指定した
-------------------------------------------------------
 import mytool;mytool.rendercopy( r'//192.168.1.5/z/works/hoge/render/111205_copy' )
-------------------------------------------------------

//192.168.1.5/z/works/hoge/render/111205_copy

に 中身がコピーされていました。

とこんな感じで、nuke内のパラメーター、もしくはユーザーの指定のパラメーターを引数として自分のやりたい機能を関数化をしてやるとWriteノードのpythonタブから用いることが出来ます。

レンダリング終了後にメールを送るとかもできますね。(特にいらないけど・・・)

2011-11-30

メモ 反転選択

for n in aln:

    if n.knob('selected').value() == True:
        n.knob('selected').setValue(0)
    else:
        n.knob('selected').setValue('True')


追記:
ああああ!
alnを定義してない!!
nuke.allNodes()で全ノードを表せるので

正しくは

aln = nuke.allNodes()
for n in aln:

    if n.knob('selected').value() == True:
        n.knob('selected').setValue(0)
    else:
        n.knob('selected').setValue('True')

2011-11-27

ToolSets と python の組み合わせ その1

ver6.3 よりToolSetsという便利なものが搭載された。
要するに、ノードの組み合わせ、もしくは単体でもいいのであるが、パラメーター等をあらかじめ組んでおいたものを呼び出せるというツールだ。スタジオやもしくは各アーティストごとのカスタムツールが容易に組めるというモノである。


ツール化したいノード(群)を選択して、ToolSets → Create ってやると名前をつけるように求められるので、適当に名前をつけるとその後からはこの ToolSets にその名前で、その選択したノード(群)をワンアクションで呼び出せるようになります。Photoshopの アクション に近い感覚です。

実は、選んだノードだけが、「.nuke」ディレクトリにある「ToolSets」フォルダに.nk形式保存されているだけだ。


なので、後からでも、修正したり、ノードを追加したりも割りと簡単に出来る。

コレを用いてたとえば、Writeノードを作るツールを作ってみる。
Writeノードをつくるなんて簡単じゃないか! って思われるだろうけど、Writeノードは割りとメンドイ。フォルダ切って、名前付けて、フレーム数指定して・・・ など。
そのあたりを勝手に都合よくやってくれればトライアンドエラーを億劫にならずに出来るかもしれない。それがコンプのクオリティーアップにつながる可能性もある。なによりも、「どこにレンダーしたっけ?」みたいなケアレスミスも防げる。
といっても、それをやるためには各個人なのか各スタジオでなのか、ともかく法則性が、つまりは明確なルール付けが必要である、とくにディレクトリー構造においては。決まってさえいればそれに則ってやるだけなので、スクリプト等でツール化が可能だ。

nukeではノードのプロパティー上で右クリックをすると、ユーザー定義の変数を設けることが出来る。



その中に「python scripts button」というのがあり、ボタンを押すと指定したpythonスクリプトを実行するというなんとも便利なモノがある。


とりあえず適当なノード(ココではBackdropノード)にこの「python scripts button」を設けて、そのボタンを押すとWriteノードが出来るというものを作る。

単純にWriteノードを作るpythonスクリプトは・・・

nuke.createNode('Write')

である。なので、 「python scripts button」を選んで、出てくるエディットウィンドウのScript欄に上記のスクリプトを入力して

 
として、OKでボタンを作成する。


するとこんな感じでボタンができて、押すとWriteノードができる。
と、これでは出来るだけなので、このWriteノードにある各種の値を変えたいのだけど、できるWriteノードの名前は任意(というか同一スクリプト(シーン)内のWriteノードの数に応じて番号が付く)なので、名前を指定して値を設定するってことが出来ない。
こういうときは下記のようにできる

cw = nuke.createNode('Write')
cw.knob('file').setValue('hogehoge')

コレを同じく、「python scripts button」の設定ウィンドウのScript欄に入れて、


ボタンを実行すると・・・


となり、「hogehoge」がファイルパスに入っている。

ボクの場合、.nk を 各プロジェクトディレクトリにある「nukeScripts」というディレクトリに保存して、nukeのレンダー結果は「nukeRender」というディレクトリに保存している。
なので、たとえば、.nkの保存場所から判断して、Writeノードからのレンダー先をある程度決め込むことが出来る。とりあえず、「nukeRender」ディレクトリンに.nkと同じ名前のフォルダを切ってその中に連番をレンダリングするとする。
.nk の保存先は「Project Settings」の「name」というknobから取得できる。


(ちなみに、「Project Settings」はノードグラフ上で[s]キーを押すと呼び出せる)
これをpythonで取得するには

nuke.root().knob('name').value()

である。この.nkのファイルパス

C:/Users/Jean/Documents/test/nukeScripts/cut01_v001.nk

であるが、この文字列の

1.nukeScripts を nukeRender に変える。
2.「.nk」 を削除する。
3.最後尾に 「/(.nk名)_%04d.tif を足す。

とすると、4桁のtiff連番でレンダ先を指定できる。


1を行うにはpythonの機能 replace を用いる。
.nkのファイルパスが nuke.root().knob('name').value() なので、これを一旦 nkPath とかにして、

nkPath = nuke.root().knob('name').value()
nkPath.replace('nukeScripts', 'nukeRender')

とすると nkPath として C:/Users/Jean/Documents/test/nukeRender/cut01_v001.nk
を得ることが出来る。

(表記するためにprintしてます)

さらに、.nkを削除(空の文字列と入れ替える)して

nkPath = nuke.root().knob('name').value()
nkPath = nkPath.replace('nukeScripts', 'nukeRender')
nkPath = nkPath.replace('.nk', '')



となります。ここで.nkのファイル名は /(スラッシュ) 区切りで文字列を小分けにした一番最後の文字列なので、split を用います。ここで

nkPath.split('/')

とすると


['C:', 'Users', 'Jean', 'Documents', 'test', 'nukeRender', 'cut01_v001']

となり、/(スラッシュ)区切りで小分けにされたタプル型のリストを得ることが出来ます。
リストの最後は

(リスト名)[-1]



で表せるので、



この場合結果として.nk名である「cut01_v001」を得ることが出来ました。
なので、このファイル名を使ってレンダー先をファイルパス(renderPathとしました)を作成します。

nkPath = nuke.root().knob('name').value()
nkPath = nkPath.replace('nukeScripts', 'nukeRender')
nkPath = nkPath.replace('.nk', '')
list = nkPath.split('/')
renderPath = nkPath + '/' + list[-1] + '_%04d.tif'



C:/Users/Jean/Documents/test/nukeRender/cut01_v001/cut01_v001_%04d.tif
を取得できました。

でこれを、さっきのWriteノードに代入して(hogehogeの変わりにコレを入れる)

nkPath = nuke.root().knob('name').value()
nkPath = nkPath.replace('nukeScripts', 'nukeRender')
nkPath = nkPath.replace('.nk', '')
list = nkPath.split('/')
renderPath = nkPath + '/' + list[-1] + '_%04d.tif'
cw = nuke.createNode('Write')
cw.knob('file').setValue(renderPath)


で、ボタンを実行すると


ファイルパスに狙い通りのモノが入ったWriteノードが出来上がる。
ただ、 このレンダ先である、

C:/Users/Jean/Documents/test/nukeRender/cut01_v001

というディレクトリがない場合もあるので、それがない場合はそのディレクトリ(フォルダ)を作るという処理を加えたいと思う。

C:/Users/Jean/Documents/test/nukeRender/cut01_v001

はさっきのスクリプトの途中の結果

nkPath = nuke.root().knob('name').value()
nkPath = nkPath.replace('nukeScripts', 'nukeRender')
nkPath = nkPath.replace('.nk', '')

とまでやったところの、nkPath である。
なので、上記3行のあとに、

if not os.path.isdir(nkPath):
    os.mkdir(nkPath)

を加える。


この

os.path.isdir(hoge)

ってのいうのは、hoge というディレクトリがあれば True を返す。

if not os.path.isdir(hoge)

なので、このディレクトリが無ければ

os.mkdir(hoge)

で hoge というディレクトリを作成しなさい ということである。pythonではこのように、

条件文:
 実行文

とし、条件文の最後に「:(コロン)」加え、改行してインデント(tabもしくはいくつかのスペース)の後に実行文をおく。

これで、ボタンを押せば.nkの保存先から nukeRender/hoge/hoge_%04d.tif と自動的に出力先をファイルパス、及びそのディレクトリが無ければ作成するというボタンが出来た。


で、このボタンをもっている、Backdropノードを選択して、ToolSets に登録すれば次回からはコレを簡単に呼び出せる。また「python scripts button」はこのBackdropノードに複数設定できるので、ある程度のツール群を入れておくとかなり便利だ。

ウチでは


こんな感じで、ディスパッチャーである「deadline」に投げるために、ファイルパスをローカル⇔ファイルサーバーと入れ替えれたりなど色々な組み合わせで使っている。

次回はレンダ後プレビューファイルを作るまでを自動化させたり、そのプレビューファイルを外部から見れるように別のディレクトリにコピーしたり、それをメールで送ったりって言うことをやってみたいと思います。

2011-11-16

expression内での条件わけ(if)

簡単に説明


こんなグラデーションがあるとして、このグラデーションのピクセルに対して条件わけがしたい場合・・・(この場合rgbaのR値が0.5より大きい場合0にして、それ以外で1にするという例)

その下にExpressionノードをぶら下げ


Expressionノードで




r>0.5?0:1


の記述を入れる。

(条件) ? (結果) : (条件以外の場合の結果)

である。




こんな感じ。

2011-11-15

3Dコンポジット時のチャンネルについて

3Dコンポジットはとても有効。
たとえば演者がグリーンバック、ブルーバックで撮影された素材複数あるとして、それをキーアウトしてcardノードを用いて立体的に配置してやれば、ある程度のカメラワークにも耐えることもでき群集シーンにはとても適している。

それだけであれば、AfterEffectsでも可能だが、nukeの利点はカメラプロジェクション、プリミティブ以外のジオメトリの追加などいろいろと利点もある。

通常のrgbaだけならそんなに問題はないのだが、その他のエキストラのチャンネルの扱いには少し癖があるので、ちょっとした説明を。


こんな感じでアルファチャンネルを持ったcardノードが3枚ある。これらはすべて、通常のrgba以外に、[test]というチャンネルをRGBAで別に持っている。
ざっくりと説明して・・・

nodeA

nodeB

nodeC

それぞれ、上が[rgba]チャンネル 下が[test]チャンネル、左がRGA 右がA である。
nodeABCそれぞれ[rgba]として白色のマスクされた形状を持っており、nodeAは[test]チャンネルとして、同じ形状で赤でアルファありのもの。nodeBは[test]チャンネルとして何も持ってない。nodeCは[test]チャンネルとして青のアルファなしである。

これらをScanlineRenderノードを通してみると・・・

(ScanlineRender)

[rgba]チャンネルは上のようになる。おそらく「over」の計算式が適応されているようである。
[test]チャンネルはというと・・・
(ScanlineRender)


となり、

nodeAの円がnodeBの円によってくりぬかれてるのが確認できる。

nodeBの[test]チャンネルをもう一度見てみると・・・


下段[test]チャンネルにはRGBAともに何もないはずなのに、結果として、Bの円より後ろにあるAの円がくりぬかれている。
これは実は、「3Dコンプの合成計算式に用いるA(アルファ)はrgbaのAである」ということに起因している。
なので、nodeBのrgba.Aは真っ黒ではなく、ちゃんと塗り形状が存在し、overの計算式

output = A + B*(1-Aa)

ややこしいが、この場合で前景であるnodeBがA、背景であるnodeAがBである。
よって「Aa」の部分で

A が (nodeB.test.RGB)   a が (nodeB.rgba.A)

となる。
そのため、黒がアルファ値1によって上から塗られている状態である。
また、この3Dコンプのアルファチャンネルを見てみると

(ScanlineRender)

となり、どうやら、アルファの部分を別でさらに計算しているようで、

A が (nodeB.test.A)   a が (nodeB.rgba.A)

で計算しているようだ。
なので、nodeBのアルファを0にすると

nodeB

nodeBの[rgba]のアルファを0にした結果(ScanlineRender)
上記のアルファ(ScanlineRender)

となる。


nodeCの円弧のラインがAの円の上に透けているのが確認できる。



気をつけたいのはアルファは常に[rgba]のアルファを参照しているということである。
これをうまく使えば、グリーン素材とその背景の煙素材なども、普通にcardで配置して、煙のチャンネルだけを別に[smoke]チャンネルとかに格納して取り出せば、煙だけmerge operation(over, plus, screen等)を変えて乗せることが可能だ。



(ScanlineRender)
(ScanlineRender)

となり、mergeで結合すると



mergeの結果

が得られる。