※頂いたコメントを元にソースコードを改良しました(2021.01.26)※
おはPython! 皆様、今日も元気にモデリングしてますか!
私はといえば、BlenderをPythonスクリプトで動かそうとしたところ、謎のエラーに苦しめられていました。悪戦苦闘した結果、その原因はAPIの仕様変更にあることがわかりました。現在ネット上に公開されているコード(≒2.79以前用)はほとんどコピペしただけでは動かないと考えてよいでしょう。
そこで、現在公開されているコードについて、ここをこうすれば2.80でも動くよ! という形を直したものを公開しようと思います。変更前と変更後のコードを並べた後で、変更があった個所には該当リファレンスを参照してこのように変更されていますよ、という形で解説していこうと思います。キーとなる変更ポイントはどのコードでもだいたい同じなので、おそらく他のコードでエラーに見舞われた人の役にも立つかと思われます。
コードを書いてくださった方々は何も悪くないのですが、仕様変更があったので仕方がありません。
#第1例
以上のようなメッシュを作成するスクリプトです。
コード
オリジナル
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 改稿※
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)
##変更箇所
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))
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
に。
bpy.context.scene.objects.active = bpy.data.objects[item.name]
bpy.context.view_layer.objects.active = bpy.data.objects[item.name]
アクティブなオブジェクトを選択。オリジナルではbpy.context.scene~
となっていた部分をbpy.context.view_layer~
とする必要があります。scene
のAPIも残っているのでどういう使い分けかは正直よくわかりませんが、ここら辺はリファレンスのこのあたりを読んで勉強するしかなさそうです。
bpy.ops.object.lamp_add(type='HEMI',location=(0.0,0.0,2.0))
bpy.ops.object.light_add(location=(0.0,0.0,2.0))
ランプの追加。lamp
の名前をlight
に変更。またHEMI
(半球)タイプのライトも消滅。
#world
bpy.context.scene.world.horizon_color=(0.0,0.0,0.0)
#world
bpy.context.scene.world.node_tree.nodes["Background"].inputs["Color"].default_value = (0,0,0,1)
これについては代替APIが見つかりませんでした。GUIでは
ここを操作することで変更できます。一応bpy.context.scene.world.color
というAPIがあるのですが、ここを指定しても変わらず。ここは力及ばずです。再現実装したい人は手動で変更してください。すいません……。
(2021.01.26追記)コメントで正しいAPIを教えていただきました。(追記終)
#第2例
blenderのpythonスクリプト入門してみた_その01
以上のようなメッシュを、プリミティブを使わずに生成するスクリプトです。
##ソースコード
オリジナル
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)
]
# この添字を使って面を定義
# 各面は4つの整数の並びで定義
# 三角形の面は最初の頂点と4つ目の頂点が同じになる必要
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 改稿※
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)
]
# この添字を使って面を定義
# 各面は4つの整数の並びで定義
# 三角形の面は最初の頂点と4つ目の頂点が同じになる必要
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)
##変更箇所
for item in bpy.context.scene.objects:
bpy.context.scene.objects.unlink(item)
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
はシーン直下にあるマスターコレクションを参照するようです。個別にコレクションが生成されている場合、更に個別コレクションごとに探索をする必要があります。(追記終)
ob.location = bpy.context.scene.cursor_location
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
bpy.context.scene.objects.link(ob)
bpy.context.scene.collection.objects.link(ob)
先ほどのcollection
の話と同様。
#使えるAPIをみつけるコツ
このように、変更点がありすぎて必要となる情報もまだ少ない、という状況なので、使えるAPIは自分で探すことも必要になります。そのような時には、
BlenderのPythonコンソール上で~~Ctrl
+Space
キー~~ Tab
キー(いつの間にかキーマップが変わっていました※。2.90以降?)を押すと候補となるコマンドの一覧が出てきますので(途中まで入力していればそれに応じて候補が絞られます)役に立つかもしれないです。
※2021年7月追記
それでは皆様よきBlender 2.80ライフを。