コンテンツへスキップ

mapを使おう (Blender Pythonと関数プログラミング #2)

先日の記事では関数プログラミング的な書き方でBlenderのpythonスクリプトを書いてみたけど、その続きです。今回はリスト操作の便利さと関数の改変・再利用について触れてみます。オブジェクトをいくつも扱うときはリスト操作の扱い方の習熟度で効率は随分変わりますから、pythonのリスト操作は是非とも慣れてください。

今回やること

前回はBlenderのPythonスクリプトで立方体の中に球体を詰め込んだ3DCGを作ってみたのですが、そのスクリプト内の関数を少し書き換えて、時計の時間(hour)のいちに球体を置くことをしてみます。前準備としてリストのmap操作を少し解説します。

map とリスト操作

mapというのはリスト操作をするときに欠かせない関数で、これを自在に扱えるかどうか?というのは生産性に大変影響が出るものです。簡単に言えば、 mapは引数に 関数とリストをとるのですが、その関数を自在に作ることで多様な操作が可能なんですね。

たとえば、次のようなmap操作をしてみます。

In [1]: l = range(4)

In [2]: list(l)
Out[2]: [0, 1, 2, 3]

In [3]: map(lambda e: e + 2, l)
Out[3]: <map at 0x23b9d9137c8>

In [4]: list(map(lambda e: e + 2, l))
Out[4]: [2, 3, 4, 5]
Code language: PHP (php)

これはipython (blenderのscriptingモードの左側にある >>> に対応するところでも良い)を使った出力例です。最初にlという変数に[0..3] の4つの要素からなるものを作ってます。中身を見る場合は、rangeはrange専用の型になってるのでlistに変換する操作でout[2]を出力しています。

次に、mapが出てきましたが、最初の引数がlambda e… となってるのが無名関数あるいはラムダ関数と呼ばれるものです。わざわざ関数を別に定義するほどでもない関数を表現するときに使います。この場合だったら、変数eに2を足すということです。つまり、[0..3]を[2..5]に変更する操作です。mapの操作でもmap専用の型になるためにそのままではOut[3]のように表現されます。このへんは今は深く知る必要はないからおまじないと思ってください。mapやrangeの中身を見るときはlist関数で変換しないと見えないってだけです。

In [5]: l2 = [10,20,30,40]

In [6]: list(map(lambda e1,e2: e1 + e2, l,l2))
Out[6]: [10, 21, 32, 43]
Code language: PHP (php)

無名関数は引数はいくつも取ることは可能なことを示してます。l2という2つ目のリストを作ってます。ここでは数字の2桁のものにしています。ここで気をつけなきゃいけないのはlもl2も要素の数が同じ4つにしている点です。処理は要素の数が少ない方に合わせたものになります。たとえば、lが4つの要素のリスト,l2が5つの要素のリストでも4つの要素のリストを返すようになってます。これがかんたんな注意点ですねん。

In [7]: l2.append(50)

In [8]: l2
Out[8]: [10, 20, 30, 40, 50]

In [9]: list(map(lambda e1,e2: e1 + e2, l,l2))
Out[9]: [10, 21, 32, 43]
Code language: CSS (css)

なれてきたらいろんな事ができるから、本当にipythonなどを利用して、リスト操作をなら親しむように学んでみてください。ここでappendというのを使ってますが、リストの後ろに新たな要素50を加えるという操作をしています。

リスト操作の基本関数は説明してないけど、公式のマニュアル(日本語) で一覧があって、どれも基本操作で習熟しておくものなのでぜひともipythonでいろんなリストを作って自分で処理してみて動作を身に着けてください。

blender pythonスクリプトで円周上に球体を置く


今回は円周上に球体を置くということでsin,cosといった正弦関数とラジアンの変換関数を利用します。これらはpythonの標準mathライブラリに含まれています。だからimport mathを含めます。

'''
uv_spheres2.py / Copyright 2020 Yasuto Takenaka
'''
import bpy
import math

def create_objs(locs):
    '''
    create uv spheres. 
    '''
    for l in locs :
        bpy.ops.mesh.primitive_uv_sphere_add(radius= 0.8, location=l)

def object_locs(n0,n,step=2):
    '''
    create a list of each location of objects.
    n0, n1 and step depend on the rule of the function, range. 
    '''
    return [(x,y,z) for x in range(n0,n,step) for y in range(n0,n,step) for z in range(n0,n,step)]

def object_locs2(r=4, step_degree=30):
    div_n = step_degree / 360
    l = map(lambda x: (r*math.sin(math.radians(x)),r*math.cos(math.radians(x)),0), range(0,360, step_degree))
    return l

def main():
    locs = object_locs2()
    create_objs(locs)

main()Code language: PHP (php)

前回の記事のソースを使うのですが、今回は立方体の中に球体を埋めるようなリストを作る関数ではなくて、円周上に置く球体の3次元座標をリスト化するものになります。下記のソースを見てわかるようにobject_locsからobject_locs2というものを作ってます。

関数の引数は、円周の半径r(デフォルト4)と何度ごとに球体を置くか(デフォルト30度)をかいてあります。

def object_locs2(r=4, step_degree=30):
    div_n = step_degree / 360
    l = map(lambda x: (r*math.sin(math.radians(x)),r*math.cos(math.radians(x)),0), range(0,360, step_degree))
    return l
Code language: JavaScript (javascript)

このように関数を変えてるけど、各行は次のような意味です。

  • 360度を30度ごとに配置するので12個(div_n)作ります。
  • このときにxという要素を使った無名関数を作ってるのですが、半径rの円周上に置くということなので xyz座標は (r sin(x), r cos(x),0)でかけるんですね。math.sin,math.cosというのは角度をラジアン単位を使うので、math.radiansをりようして角度をラジアンに変更するんですね。最後の引数はrangeで360度を12等分した数字のリスト(0,30,60,…)をつくってます。
  • 結果のリストを返り値にしています。

ギャラリー

上記のスクリプトを少し変えるとこのギャラリーのようなものを作れます。右側はパラメータを変えたので、main関数のlocs = object_locs2()をlocs = object_locs2(r=10,step_degree=10)にしてやれば再現できます。

左側はobject_locs3というのを新たに作成して、object_locs2のmapの引数にある無名関数のz座標を正弦関数と半径の大きさを少し変えてつくったものです。ソースは公開しないけど、ちょっと書き換えたら作れるので、上記のソースコードを少し改造して同じようなものが作れるか自身で確かめてください。 上下4周するように 無名関数の角度のところも少し手を入れています。

最後に

今回mapと並んでobjectを扱う上のリスト操作で重要になってくるfilter関数についても書こうと思ったのですが、記事が長くなったので次にします。

最終更新日 : 2022-07-01