Blender2.80のスクリプトで恒星系を生み出す

![untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/1634c64d-6545-0011-8126-4386d4a9252c.png)

　みんな大好きBlenderスクリプトで恒星系（のように見える）配置を生み出そうという記事です。Blender2.80完全対応！

　基本的にはイテレーションを回して物体に大量に配置する系のスクリプトですが、今回はマテリアルの設定などもしています。

# 1.　球体を大量に配置

　とりあえず簡単なコードから。プリミティブの球体を自動的に並べてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/d0bc2c29-9ba5-dd44-58be-5e0eb81325ac.png)

```Python
import bpy
import numpy as np

#現状をリセット
for mat in bpy.data.materials:
    bpy.data.materials.remove(mat)
for obj in bpy.data.objects:
        bpy.data.objects.remove(obj)
for obj in bpy.context.scene.objects:
    bpy.data.objects.remove(obj)

#球体を生み出す数
number = 10

for i in range(number):
    x = np.random.rand()*number*10-50
    y = np.random.rand()*number*10-50
    bpy.ops.mesh.primitive_uv_sphere_add(radius=np.random.rand()+1,location=(x,y,0))
```

# 2. なんちゃって惑星軌道を追加

　それぞれの球体に、原点を中心としてその球体を通るようなリングを設定したら、なんとなく恒星系のようになりそうです。元々`x`と`y`で座標を指定していましたが、今後の計算の簡便のため、`r`と`theta`の極座標で座標をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/aab42874-b233-fae0-31f6-2badfec41f9f.png)

```Python
import bpy
import numpy as np

#現状をリセット
for mat in bpy.data.materials:
    bpy.data.materials.remove(mat)
for obj in bpy.data.objects:
        bpy.data.objects.remove(obj)
for obj in bpy.context.scene.objects:
    bpy.data.objects.remove(obj)

#球体を生み出す数
number = 10

bpy.data.materials.new(name = 'orbit')
mat = bpy.data.materials['orbit']
mat.use_nodes = False
mat.diffuse_color = (1,1,0,1)

for i in range(number):
    #極座標で計算
    r = np.random.rand()*number*10+10
    theta = np.random.rand()*np.pi*2
    x = r*np.cos(theta)
    y = r*np.sin(theta)

    #こっちは惑星
    bpy.ops.mesh.primitive_uv_sphere_add(radius=np.random.rand()+1,location=(x,y,0))
    
    #こっちは軌道
    bpy.ops.mesh.primitive_torus_add(major_radius=256,minor_radius=.1)
    bpy.context.object.scale = (r/256,r/256,1)
```

　ここでは`bpy.ops.mesh.primitive_torus_add`のAPIを用いています。リファレンスは[ここ](https://docs.blender.org/api/current/bpy.ops.mesh.html?highlight=torus#bpy.ops.mesh.primitive_torus_add)。ドーナツの輪の半径を`major_radius`、ドーナツの断面の半径を`minor_radius`で指定しています。ここで`major_radius`を0としているのは、なぜかこのAPIでは**257以上の値を指定できない**というよくわからない仕様があるせいで（最初わからなくてハマった）、半径256のドーナツを`bpy.context.object.scale`によって拡大縮小することで`r`に合わせるという風にしています。

# 3. マテリアルの指定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/921c3647-29d7-6f7c-46e5-087a18147419.png)

　今回はマテリアルも設定していきます。Blender2.80になってから、マテリアルを設定する場合はほぼシェーダ必須となってしまいましたが、**そこまでスクリプトの勉強が追い付いていない**ので今回はシェーダ使わないモードで解説させていただきます。スクリプトでマテリアルを設定する場合、`bpy.data.materials.new`でマテリアルのデータを作成した後、対象オブジェクトに`bpy.ops.object.material_slot_add()`でマテリアルスロットを追加し、そのスロットを対象に`bpy.context.object.active_material`でそのマテリアルを設定する、という流れになります。

　マテリアルのリファレンスは[ここ](https://docs.blender.org/api/current/bpy.types.Material.html#bpy.types.Material)ですが、**ノードを使う場合これは序の口であって**、更に細かいノードのAPIをいじる必要があります。とりあえず今回はノードを使わない設定でいくので（`mat.use_nodes = False`）、この中の`diffuse_color`、`metallic`、`roughness`だけ乱数で指定していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/cbc39362-bd95-6412-a757-5575bd3e00ac.png)

　GUIで言うとこの表示になります。なおこの表示はレンダーエンジンによっても変わり、上の図は`Cycles`のものになります。


```Python
import bpy
import numpy as np

#現状をリセット
for mat in bpy.data.materials:
    bpy.data.materials.remove(mat)
for obj in bpy.data.objects:
        bpy.data.objects.remove(obj)
for obj in bpy.context.scene.objects:
    bpy.data.objects.remove(obj)

#球体を生み出す数
number = 10

#軌道のマテリアルだけ先に設定
bpy.data.materials.new(name = 'orbit')
mat = bpy.data.materials['orbit']
mat.use_nodes = False
mat.diffuse_color = (1,1,0,1)

for i in range(number):
    r = np.random.rand()*number*10+10
    theta = np.random.rand()*np.pi*2
    x = r*np.cos(theta)
    y = r*np.sin(theta)
    #こっちは惑星
    bpy.ops.mesh.primitive_uv_sphere_add(radius=np.random.rand()+1,location=(x,y,0))

    #惑星の個別のマテリアルを設定
    mat_name = 'material' + str(i)
    bpy.data.materials.new(name = mat_name)
    mat = bpy.data.materials[mat_name]
    mat.use_nodes = False
    mat.diffuse_color = np.random.rand(4)
    mat.diffuse_color[3] = 1
    mat.metallic = np.random.rand()
    mat.roughness = np.random.rand()
    bpy.ops.object.material_slot_add()
    bpy.context.object.active_material=mat

    #こっちは軌道
    bpy.ops.mesh.primitive_torus_add(major_radius=100,minor_radius=.1)
    bpy.context.object.scale = (r/100,r/100,1)

    #前に設定した軌道マテリアルを適用
    mat = bpy.data.materials['orbit']
    bpy.ops.object.material_slot_add()
    bpy.context.object.active_material=mat
```

#4 太陽（っぽいもの）の作成＆レンダリング

　せっかくなので、中心に太陽（っぽいもの）を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/6cf42c35-891e-471e-d355-447fe1ee2a89.png)


```Python:上のコードに追記
bpy.ops.mesh.primitive_uv_sphere_add(radius=4)
bpy.data.materials.new(name = 'sun')
mat = bpy.data.materials['sun']
mat.use_nodes = False
mat.diffuse_color = (1,.5,0,1)
bpy.ops.object.material_slot_add()
bpy.context.object.active_material=mat
```

　惑星の半径や位置は乱数で指定しているので、スクリプトを回す度に違う恒星系らしきものが生成されます。Blenderのソリッド表示ではいい感じですが、この際レンダリングもしていきましょう。このままでは真っ暗なので中心にライトを設定してもよいですが、このニセ太陽のせいで光が遮られてしまいます。ここは手動でマテリアルを設定して、このニセ太陽自身が輝いているようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/d8d5add7-e0b1-a08f-cf2a-fc71819f6488.png)


　シェーダノード。我々はこれに嫌でも慣れていかなければならない。

　以下がカメラや背景等を設定してレンダリングしたもの。背景の星空は[CGBeginner](https://cgbeginner.net/space-stars-footage/)様よりお借りしました。

![untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/231830d5-6629-2bf1-520a-500ccf7e07a1.png)