C4D.Python: グリッド配列する簡易クローナーを作る
前回は、立方体を作って移動するだけという単純なものでしたが、今回はもう少しプログラムらしくくり返し作業やインタラクティブ性のあるものとしてMoGraphのクローナーのグリッド配列をPythonoジェネレータで再現してみようと思います。Cinema 4Dには、配列ジェネレータがありますが、放射状にしか配列できませんので、MoGraphがないPrimeやVisualizeユーザーなら少し便利になりそうです。
目次
クローンされたオブジェクトの構造を考える
さて、実際にプログラムを書き始める前に、どういう構造のオブジェクトを作るかを先に考えます。下図のような構造です。
プログラム的な流れとしてこういう風にします。複製数がXYZそれぞれ3とした場合です。
- インスタンスを作成して、インスタンスに指定オブジェクトを割り当てる
- 次にヌルXを作成して、その中にX方向に3つのインスタンスを挿入
- 別のヌルYを作成して、ヌルXを子にする
- ヌルYには、ヌルXのインスタンスをY方向に2つ挿入(つまり指定数分より1少なくする。一つ少なくするのはXで1段目がすでにできているから)
- さらにヌルZを作成して、ヌルYを子にする
- ヌルZには、ヌルYのインスタンスをZ方向に2つ挿入(此方同じく一つ少なくする)
これであれば、希望の状態になりそうです。
ユーザデータを作成
簡易クローナーのパラメータはこんな風にします。Pythonジェネレータにユーザデータを追加していきます。
- オブジェクト:クローンにするオブジェクト
- レンダーインスタンス:レンダーインスタンスにするかどうかのチェック
- 複製数.xyz:XYZ各方向に複製する数
- サイズ:クローナーで配列するサイズ
- 角度:各クローンの角度
ユーザデータのデータタイプについて
作成するユーザデータのデータタイプは次の通りです。
オブジェクト
このデータタイプは、リンクを選びます。リンクはオブジェクトをドラッグして他に渡せるタイプのデータタイプになります。
レンダーインスタンス
ブール値を選びます。ブール値は、TureかFalseか出力するデータタイプです
複製数.xyz
ここでは整数を選び、XYZそれぞれ作成します。わたしは最初ここに3つの値が格納できるベクトルタイプを作成して、XYZの値を個別に取得して使おうとしましたがうまくいきませんでした。原因を調べたところ、ベクトルは出力する値が実数のため、見た目上の値が「1」であっても、出力されるのは「1.0」となります。しかし、くり返しを行うためには、同じ数字でも整数の「1」である必要があったため、上手く動作しないためデータタイプ整数にして3つのユーザデータを作りました。
後で調べたところ実数を整数に変えられるint()関数があったのでそれを使えば、ベクトルのデータタイプを使いインタフェースを少しすっきりさせることもできそうです。
サイズ
ベクトルで単位はメートルにします。
角度
ベクトルで単位は角度にします。これを角度にしておくと、値を自動的にラジアンに変換して出力してくれます。Cinema 4Dは角度の計算にはラジアンを使っているため、普通の角度として入力した値はラジアンに変換する必要がありますが、ベクトルの単位を角度にしておくと自動でやってくれます。
なお、角度をラジアンに変換するc4d.utils.DegToRad()のしくはc4d.utils.Rad()という関数を使うこともできます。
スクリプトを作成
これが、完成したスクリプトです。
import c4d #Welcome to the world of Python def main(): #ユーザデータを変数に入れる------------------- #オブジェクトの取得 clone = op[c4d.ID_USERDATA,1] #ユーザデータ:複製数の取得 numx = op[c4d.ID_USERDATA,5] numy = op[c4d.ID_USERDATA,6] numz = op[c4d.ID_USERDATA,7] #ユーザデータ:サイズの取得 size = op[c4d.ID_USERDATA,2] sizex = size[0] sizey = size[1] sizez = size[2] #ユーザデータ:レンダーインスタンスの取得 ri = op[c4d.ID_USERDATA,3] #角度の取得 rotate = op[c4d.ID_USERDATA,8] rotateh = rotate[0] rotatep = rotate[1] rotateb = rotate[2] #一番親のヌルの作成 null = c4d.BaseObject(c4d.Onull) #ヌルオブジェクトの作成 #X用ヌルの作成 nullx = c4d.BaseObject(c4d.Onull) nullx[c4d.ID_BASELIST_NAME] = "clonex" #Y用ヌルの作成 nully = c4d.BaseObject(c4d.Onull) nully[c4d.ID_BASELIST_NAME] = "cloney" #Z用ヌルの作成 nullz = c4d.BaseObject(c4d.Onull) nullz[c4d.ID_BASELIST_NAME] = "clonez" #作成したヌルXYZを階層ごとに挿入 nullx.InsertUnder(nully) nully.InsertUnder(nullz) nullz.InsertUnder(null) #X方向のインスタンスの作成 for x in xrange(numx): clonerx = c4d.BaseObject(5126) clonerx[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = rotateh clonerx[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y] = rotatep clonerx[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z] = rotateb clonerx[c4d.INSTANCEOBJECT_LINK] = clone clonerx[c4d.INSTANCEOBJECT_RENDERINSTANCE] = ri if numx==1: clonerx[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] = sizex/numx*x else: clonerx[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] = sizex/(numx-1)*x clonerx.InsertUnder(nullx) #Y方向のインスタンスの作成 for y in range(1,numy): clonery = c4d.BaseObject(5126) clonery[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Y] = sizey/numy*y clonery[c4d.INSTANCEOBJECT_LINK] = nullx clonery[c4d.INSTANCEOBJECT_RENDERINSTANCE] = ri clonery.InsertUnder(nully) #Z方向のインスタンスの作成 for z in xrange(1,numz): clonerz = c4d.BaseObject(5126) clonerz[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Z] = sizez/numz*z clonerz[c4d.INSTANCEOBJECT_LINK] = nully clonerz[c4d.INSTANCEOBJECT_RENDERINSTANCE] = ri clonerz.InsertUnder(nullz) return null
では、中身を見ていきましょう。
ユーザデータを変数に入れる
まずは、ユーザデータを変数に入れていきます。この辺は前回やっているので問題ないでしょう。
def main(): #ユーザデータを変数に入れる------------------- #オブジェクトの取得 clone = op[c4d.ID_USERDATA,1] #ユーザデータ:複製数の取得 numx = op[c4d.ID_USERDATA,5] numy = op[c4d.ID_USERDATA,6] numz = op[c4d.ID_USERDATA,7] #ユーザデータ:サイズの取得 size = op[c4d.ID_USERDATA,2] sizex = size[0] sizey = size[1] sizez = size[2] #ユーザデータ:レンダーインスタンスの取得 ri = op[c4d.ID_USERDATA,3] #角度の取得 rotate = op[c4d.ID_USERDATA,8] rotateh = rotate[0] rotatep = rotate[1] rotateb = rotate[2]
ヌルオブジェクトの作成
次はヌルオブジェクトを作成します。ヌルは一番上のヌル、その子にclonez、clonezの子にcloney、cloneyの子にclonezとなるようにします。コードはこんな感じになります。
#一番親のヌルの作成 null = c4d.BaseObject(c4d.Onull) #ヌルオブジェクトの作成 #X用ヌルの作成 nullx = c4d.BaseObject(c4d.Onull) nullx[c4d.ID_BASELIST_NAME] = "clonex" #Y用ヌルの作成 nully = c4d.BaseObject(c4d.Onull) nully[c4d.ID_BASELIST_NAME] = "cloney" #Z用ヌルの作成 nullz = c4d.BaseObject(c4d.Onull) nullz[c4d.ID_BASELIST_NAME] = "clonez" #作成したヌルXYZを階層ごとに挿入 nullx.InsertUnder(nully) nully.InsertUnder(nullz) nullz.InsertUnder(null)
実際の処理を見ていきましょう。
ヌルの作成は、各変数にc4d.BaseObject(c4d.Onull)と入れます。
nullx = c4d.BaseObject(c4d.Onull)
次に、ヌルに名前を付けます。名前を付けるコードは次の通りで、
ヌルが入った変数名[c4d.ID_BASELIST_NAME] = “名前を付ける文字”
というように記述します。
nullx[c4d.ID_BASELIST_NAME] = "clonex"
そして、オブジェクトを子にするためにはInsertUnder()関数を使います。名前の通り下に挿入ということで子として挿入されます。書き方は、
子にするオブジェクトの変数.InsertUnder(親にするオブジェクトの変数)
というように記述します。これを作成するヌルの数だけ行います。
#作成したヌルXYZを階層ごとに挿入 nullx.InsertUnder(nully) nully.InsertUnder(nullz) nullz.InsertUnder(null)
これはぞれぞれ、nullxをnullyの子にして、nullyはnullzの子にして、nullzはnullの子にしています。ここで一番親のnullをdoc.InsertObject()でシーンに入れていないのは、Pythonジェネレータのスクリプトの最後でnullをreturnして最後にオブジェクトを生成しているためです。
インスタンスの作成
インスタンスを作成します。作成されるインスタンスの数はユーザデータの複製数によって変わるので、くり返しの処理が必要になります。くり返しの処理はfor文と呼ばれる方法で記述します。for文は、リスト型のデータからデータをひとつづず抜き出して処理させるということができます。たとえば、都道府県名が入ったリストのデータを各県名ごとにコンソールに出したい場合なら、こんな感じになります。
def main(): for x kenmei: print(x)
この場合、kenmeiという変数に47都道府県の名前がリストとして入っているものとします。これを各県名が「x」という変数に割り当てられます。この変数xの内容をコンソールに出力する内容なので、リストの順番ごとに件名が出力されるので、47回くり返されることになります。
for文の構造
今回作りたいのは、ユーザ数の複製数(整数)に合わせた回数分、インスタンスを作成をくり返しさせることです。しかし、for文で使うためのリスト型のデータが特にありません。そのためリスト型のデータを作るため、xrange()という関数を使います。これはPythonの関数で、「xrange(整数)」と記述すると0からその整数分のリストを作ります。たとえば、xrange(10)と書くとデータとしては、[0,1,2,3,4,5,6,7,8,9]のあたりが入ります。これなら、10回くり返し処理ができるようになります。ですので、xrange(複製数)にすれば、複製数分のくり返し処理ができることになります。
なお、このリストを作る関数はrange()とxrange()と2つあり、大量のデータをがある場合はメモリ効率の良いxrange()の方がよいとありましたので、こちらを使いました。実験ではどちらでも動作はしました。
さて、今回作るコードのfor文の構造は次のようなものを考えました。
流れとしては、xrangeで複製数の値に応じたリストを作成しています。
くり返し処理としては
- インスタンスオブジェクトを作成
- インスタンスの相対位置は、サイズ÷複製数で一つ当たりの移動距離を割り出します。割り出し方の考え方は次の通りです。
- 各クローンの移動量はリストで作成した数値を掛けてクローンの位置をずらします。ゼロから始まるので最初のクローンは原点になります。
- 最後に、それぞれをヌルの子に挿入します。
さてこれを実行したところ、複製数が2以上の場合は正常に動いたのですが、複製数が1だった場合エラーが発生しました。
どうも、ゼロで割っているからですよということらしい。たしかに「複製数-1」という計算の場合、複製数が1の場合ゼロになってしまいます。
ですので、1の時はそのままで2以上はマイナス1するというコードにします。分岐させるにはif文を使います。if文の構造は次の通りです。
if 条件式: 真(Ture)のとき実行される命令 else: 偽(False)のときに実行される命令
ですので、条件式としては「変数=1のとき」としたいしたいので、変数==1と書きます。Pythonでは等しい場合は「==」と書くようです。ですので、こんな感じのコードにします。
if numx==1: clonerx[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] = sizex/numx*x else: clonerx[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] = sizex/(numx-1)*x clonerx.InsertUnder(nullx)
最終的には次のようになります。インスタンスを生成して、移動して、回転させて、インスタンスの対象の指定、レンダーインスタンスの設定、ヌルに挿入ということをやっています。
#X方向のインスタンスの作成 for x in xrange(numx): clonerx = c4d.BaseObject(5126) clonerx[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = rotateh clonerx[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y] = rotatep clonerx[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z] = rotateb clonerx[c4d.INSTANCEOBJECT_LINK] = clone clonerx[c4d.INSTANCEOBJECT_RENDERINSTANCE] = ri if numx==1: clonerx[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] = sizex/numx*x else: clonerx[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] = sizex/(numx-1)*x clonerx.InsertUnder(nullx) #Y方向のインスタンスの作成 for y in range(1,numy): clonery = c4d.BaseObject(5126) clonery[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Y] = sizey/numy*y clonery[c4d.INSTANCEOBJECT_LINK] = nullx clonery[c4d.INSTANCEOBJECT_RENDERINSTANCE] = ri clonery.InsertUnder(nully) #Z方向のインスタンスの作成 for z in xrange(1,numz): clonerz = c4d.BaseObject(5126) clonerz[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Z] = sizez/numz*z clonerz[c4d.INSTANCEOBJECT_LINK] = nully clonerz[c4d.INSTANCEOBJECT_RENDERINSTANCE] = ri clonerz.InsertUnder(nullz) return null
YとZ方向のインスタンスの作成のxrange()が、「xrange(1,numy)」とxのときと違う書き方になっているのは、yとzは複製元があるので実際の複製数は1つ少なくて済みます。また、相対位置も一つ目から移動させるので、変数yとxは、ゼロから始まるのではなく1から始めたいので、xrange(開始したい番号,リスト数)になっています。
ただ、xrange()の関数は、xrange(開始番号,リスト数)と書けば始まる番号を指定することができます。少しややこしいのが、開始番号が変わるとリストに入る数値の数が変わる点です。リストに入る数字は、こうなります。
- xrange(5) = [0,1,2,3,4] リストには5つの数字が入ります。
- xrange(1,5)= [1,2,3,4] リストには4つの数字がしか入りません。
- xrange(-2,5)= [-2,-1,0,1,2,3,4] リストには6つの数字が入ります。
ですので、YとZ方向のコードは、以下になります。
#Y方向のインスタンスの作成 for y in range(1,numy): print(y) clonery = c4d.BaseObject(5126) clonery[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Y] = sizey/numy*y clonery[c4d.INSTANCEOBJECT_LINK] = nullx clonery[c4d.INSTANCEOBJECT_RENDERINSTANCE] = ri clonery.InsertUnder(nully)
最後は、各ヌルとインスタンスを子に持ったヌルをreturnで生成して完了です。
これで完成です。実際動かしてみると処理はちょっと遅い気がしますね。何か早くする処理があるのかもしれませんが、まずは完成とします。