BlenderのPythonスクリプトのコードが2.80で動かなかった時に見る記事（2021.01.26改稿）

**※頂いたコメントを元にソースコードを改良しました（2021.01.26）※**

　おはPython！　皆様、今日も元気にモデリングしてますか！

　私はといえば、BlenderをPythonスクリプトで動かそうとしたところ、謎のエラーに苦しめられていました。悪戦苦闘した結果、その原因は**APIの仕様変更**にあることがわかりました。現在ネット上に公開されているコード（≒2.79以前用）はほとんど**コピペしただけでは動かない**と考えてよいでしょう。

　そこで、現在公開されているコードについて、ここをこうすれば2.80でも動くよ！　という形を直したものを公開しようと思います。変更前と変更後のコードを並べた後で、変更があった個所には該当リファレンスを参照してこのように変更されていますよ、という形で解説していこうと思います。キーとなる変更ポイントはどのコードでもだいたい同じなので、おそらく他のコードでエラーに見舞われた人の役にも立つかと思われます。

　コードを書いてくださった方々は何も悪くないのですが、仕様変更があったので仕方がありません。

#第1例

[Blender × Pythonでお気楽3DCG！](https://blog.engineer.adways.net/entry/advent_calendar_2017/04)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/a91f5448-82b3-4e5c-09a7-f337a1083759.png)

　以上のようなメッシュを作成するスクリプトです。

## コード

オリジナル

```python:2.79

import bpy
import math

#reset objects
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(True)

#world
bpy.context.scene.world.horizon_color=(0.0,0.0,0.0)

#plane_add
for i in range(0,100):
    bpy.ops.mesh.primitive_plane_add(radius = (i*1.1/100),location=(0,0,0),rotation=(math.pi*1/2,math.pi*i*8.2/360,math.pi*i*10/360))

for item in bpy.context.scene.objects:
    if item.type == 'MESH':
        bpy.context.scene.objects.active = bpy.data.objects[item.name]
        bpy.ops.object.modifier_add(type='WIREFRAME')
        bpy.context.object.modifiers['Wireframe'].thickness = 0.0025
        bpy.context.object.modifiers['Wireframe'].use_boundary = True

#lamp add
bpy.ops.object.lamp_add(type='HEMI',location=(0.0,0.0,2.0))

#camera add
bpy.ops.object.camera_add(location=(5.0,0.0,0.0))
bpy.data.objects['Camera'].rotation_euler = (math.pi*1/2, 0, math.pi*1/2)

#render
bpy.context.scene.render.resolution_x = 1000
bpy.context.scene.render.resolution_y = 1000
bpy.context.scene.render.resolution_percentage = 100
bpy.context.scene.camera = bpy.context.object
bpy.context.scene.render.image_settings.file_format = 'PNG'
bpy.data.scenes["Scene"].render.filepath = "tmp/plane.png"
bpy.ops.render.render(write_still=True)
```
変更後 **※2020.01.26 改稿※**

```python:2.80
import bpy
import math

#reset objects
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(True)

#world
bpy.context.scene.world.node_tree.nodes["Background"].inputs["Color"].default_value = (0,0,0,1)

#plane_add
for i in range(0,100):
    bpy.ops.mesh.primitive_plane_add(size = (i*1.1/100),location=(0,0,0),rotation=(math.pi*1/2,math.pi*i*8.2/360,math.pi*i*10/360))

for item in bpy.context.scene.objects:
    if item.type == 'MESH':
        bpy.context.view_layer.objects.active = bpy.data.objects[item.name]
        bpy.ops.object.modifier_add(type='WIREFRAME')
        bpy.context.object.modifiers['Wireframe'].thickness = 0.0025
        bpy.context.object.modifiers['Wireframe'].use_boundary = True

#lamp add
bpy.ops.object.light_add(location=(0.0,0.0,2.0))

#camera add
bpy.ops.object.camera_add(location=(5.0,0.0,0.0))
bpy.data.objects['Camera'].rotation_euler = (math.pi*1/2, 0, math.pi*1/2)

#render
bpy.context.scene.render.resolution_x = 1000
bpy.context.scene.render.resolution_y = 1000
bpy.context.scene.render.resolution_percentage = 100
bpy.context.scene.camera = bpy.context.object
bpy.context.scene.render.image_settings.file_format = 'PNG'
bpy.data.scenes["Scene"].render.filepath = "tmp/plane.png"
bpy.ops.render.render(write_still=True)
```

##変更箇所

```Python:2.79
    bpy.ops.mesh.primitive_plane_add(radius = (i*1.1/100),location=(0,0,0),rotation=(math.pi*1/2,math.pi*i*8.2/360,math.pi*i*10/360))
```
```Python:2.80
    bpy.ops.mesh.primitive_plane_add(size = (i*1.1/100),location=(0,0,0),rotation=(math.pi*1/2,math.pi*i*8.2/360,math.pi*i*10/360))
```

　平面のプリミティブを追加。スケールを示す`radius`が`size`に。

該当リファレンス：https://docs.blender.org/api/current/bpy.ops.mesh.html?highlight=primitive%20plane#bpy.ops.mesh.primitive_plane_add

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/8f3926f4-c645-2926-6b8a-1a0035d9caa8.png)

```Python:2.79
        bpy.context.scene.objects.active = bpy.data.objects[item.name]
```
```Python:2.80
        bpy.context.view_layer.objects.active = bpy.data.objects[item.name]
```

　アクティブなオブジェクトを選択。オリジナルでは`bpy.context.scene～`となっていた部分を`bpy.context.view_layer～`とする必要があります。`scene`のAPIも残っているのでどういう使い分けかは正直よくわかりませんが、ここら辺はリファレンスの[このあたり](https://docs.blender.org/api/current/bpy.types.Scene.html)を読んで勉強するしかなさそうです。


```Python:2.79
bpy.ops.object.lamp_add(type='HEMI',location=(0.0,0.0,2.0))
```
```Python:2.80
bpy.ops.object.light_add(location=(0.0,0.0,2.0))
```

　ランプの追加。`lamp`の名前を`light`に変更。また`HEMI`（半球）タイプのライトも消滅。

https://docs.blender.org/api/current/bpy.ops.object.html?highlight=light%20add#bpy.ops.object.light_add

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/103b0879-ee6e-d7d6-ee9b-9777e433281b.png)

```python:2.79
#world
bpy.context.scene.world.horizon_color=(0.0,0.0,0.0)
```
```python:2.80
#world
bpy.context.scene.world.node_tree.nodes["Background"].inputs["Color"].default_value = (0,0,0,1)
```
　
これについては代替APIが見つかりませんでした。GUIでは

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/5b62c70d-d8e4-a820-0e29-ab2795e014c2.png)

　
ここを操作することで変更できます。一応`bpy.context.scene.world.color`というAPIがあるのですが、ここを指定しても変わらず。ここは力及ばずです。再現実装したい人は手動で変更してください。すいません……。


　**（2021.01.26追記）**コメントで正しいAPIを教えていただきました。**（追記終）**

#第2例

[blenderのpythonスクリプト入門してみた_その01](https://qiita.com/muripo_life/items/42d7f49aa99077f52cb0)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/5258f840-8257-ccad-f0b3-568aca267eba.png)

　以上のようなメッシュを、プリミティブを使わずに生成するスクリプトです。

##ソースコード

オリジナル

```python:2.79
import bpy

# デフォルトのCubeを削除
def delete_all():
    for item in bpy.context.scene.objects:
        bpy.context.scene.objects.unlink(item)

    for item in bpy.data.objects:
        bpy.data.objects.remove(item)

    for item in bpy.data.meshes:
        bpy.data.meshes.remove(item)

    for item in bpy.data.materials:
        bpy.data.materials.remove(item)

delete_all()

# 頂点座標を定義
coords=[
    (-1.0, -1.0, -1.0),
    ( 1.0, -1.0, -1.0),
    ( 1.0,  1.0, -1.0),
    (-1.0,  1.0, -1.0),
    ( 0.0,  0.0,  1.0)
]

# この添字を使って面を定義
# 各面は４つの整数の並びで定義
# 三角形の面は最初の頂点と４つ目の頂点が同じになる必要
faces=[
    (2,1,0,3),
    (0,1,4,0),
    (1,2,4,1),
    (2,3,4,2),
    (3,0,4,3)
]

# 新規メッシュを作成
me          = bpy.data.meshes.new("PyramidMesh")
# メッシュでオブジェクトを作成
ob          = bpy.data.objects.new("Pyramid", me)
# オブジェクトを 3D カーソルの位置に配置
ob.location = bpy.context.scene.cursor_location
# オブジェクトをシーンにリンク
bpy.context.scene.objects.link(ob)
# メッシュの頂点、辺、面を埋めまる
me.from_pydata(coords,[],faces)
# 新たなデータでメッシュを更新
me.update(calc_edges=True)
```

変更後　**※2020.01.26 改稿※**

```python:2.80
import bpy

# デフォルトのCubeを削除
def delete_all():
    for col in bpy.data.collections:
        for item in col.objects:
            col.objects.unlink(item)
            bpy.data.objects.remove(item)

    for item in bpy.context.scene.collection.objects:
        bpy.context.scene.collection.objects.unlink(item)
        bpy.data.objects.remove(item)

    for item in bpy.data.meshes:
        bpy.data.meshes.remove(item)

    for item in bpy.data.materials:
        bpy.data.materials.remove(item)
        
delete_all()

# 頂点座標を定義
coords=[
    (-1.0, -1.0, -1.0),
    ( 1.0, -1.0, -1.0),
    ( 1.0,  1.0, -1.0),
    (-1.0,  1.0, -1.0),
    ( 0.0,  0.0,  1.0)
]

# この添字を使って面を定義
# 各面は４つの整数の並びで定義
# 三角形の面は最初の頂点と４つ目の頂点が同じになる必要
faces=[
    (2,1,0,3),
    (0,1,4,0),
    (1,2,4,1),
    (2,3,4,2),
    (3,0,4,3)
]

# 新規メッシュを作成
me          = bpy.data.meshes.new("PyramidMesh")
# メッシュでオブジェクトを作成
ob          = bpy.data.objects.new("Pyramid", me)
# オブジェクトを 3D カーソルの位置に配置
ob.location = bpy.context.scene.cursor.location
# オブジェクトをシーンにリンク
bpy.context.scene.collection.objects.link(ob)
# メッシュの頂点、辺、面を埋めまる
me.from_pydata(coords,[],faces)
# 新たなデータでメッシュを更新
me.update(calc_edges=True)
```

##変更箇所

```python:2.79
    for item in bpy.context.scene.objects:
        bpy.context.scene.objects.unlink(item)
```
```python:2.80
    for col in bpy.data.collections:
        for item in col.objects:
            col.objects.unlink(item)
            bpy.data.objects.remove(item)

    for item in bpy.context.scene.collection.objects:
        bpy.context.scene.collection.objects.unlink(item)
```

　現在のシーンの中にあるオブジェクトに関する命令ですが、`scene`の下に更に`collection`という階層が追加されたので、それを反映させる必要があります。`scene`の直下にも`objects`があって、それだとオブジェクトを`link`または`unlink`する時にエラーが出る、というのが中々の初見殺しですね。`AttributeError: 'bpy_prop_collection' object has no attribute 'link'`というエラーに泣かされた人も多いのではないでしょうか。
**（2021.01.26追記）**`context.scene.collection`はシーン直下にあるマスターコレクションを参照するようです。個別にコレクションが生成されている場合、更に個別コレクションごとに探索をする必要があります。**（追記終）**

```python:2.79
ob.location = bpy.context.scene.cursor_location
```
```python:2.80
ob.location = bpy.context.scene.cursor.location
```

　カーソル位置の指定。2.79以下では`curosr_location`というAPIがありましたが、2.80では`cursor`の下に`location`がある、という階層構造になってようです。

該当リファレンス：https://docs.blender.org/api/current/bpy.types.View3DCursor.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/59b3ef43-ed7c-d24e-437c-d7e8403a9fb1.png)

```python:2.79
bpy.context.scene.objects.link(ob)
```

```python:2.80
bpy.context.scene.collection.objects.link(ob)
```

　先ほどの`collection`の話と同様。

#使えるAPIをみつけるコツ

　このように、変更点がありすぎて必要となる情報もまだ少ない、という状況なので、使えるAPIは自分で探すことも必要になります。そのような時には、

　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/40a10cfe-763d-3adf-b084-52df598578d3.png)

　BlenderのPythonコンソール上で~~`Ctrl`+`Space`キー~~ `Tab`キー（いつの間にかキーマップが変わっていました※。2.90以降？）を押すと候補となるコマンドの一覧が出てきますので（途中まで入力していればそれに応じて候補が絞られます）役に立つかもしれないです。

※2021年7月追記

　それでは皆様よきBlender 2.80ライフを。