MRが楽しい

MRやVRについて学習したことを書き残す

Blender 2.8のPython APIドキュメントを少しずつ読み解く 落とし穴 その9

本日は Blender2.8 の調査枠です。
Blender 2.8 の Python API ドキュメントを少しずつ読みつつ試していきます。
前回記事の続きです。
bluebirdofoz.hatenablog.com

Blender 2.8 Python API Documentation

以下のページを日本語訳しつつ実際に試して記事を進めていきます。
docs.blender.org
docs.blender.org

今日は「落とし穴」の「Blenderのクラッシュ」です。
f:id:bluebirdofoz:20200119223818j:plain

Blenderのクラッシュ

以下の状況で、Blenderデータへの直接参照を保持しないでください。
・データのコンテナーを変更するとき
・何らかの元に戻す/やり直しが発生する可能性があるとき(例:モーダルオペレーターの実行中)
代わりに、目的のデータへのアクセスを可能にするインデックス(または文字列キーなど)を使用します。

API には Blender をクラッシュさせる可能性のあるいくつかの問題があります。

厳密に言えば、これはAPIのバグです。
ほとんどのクラッシュはPythonオブジェクトが Blender のメモリを直接参照するために発生します。
このため、これを改善するには全てのアクセスでメモリ検証を追加することを意味します。
ただし、スクリプトの実行が非常に遅くなるか、メモリを直接参照しない異なる種類の API を記述することになります。

これらの問題に遭遇しないようにするための一般的なヒントを次に示します。
Blenderはメモリが不足するだけでクラッシュする可能性があります。
 特に大きなリストを操作する場合は、メモリの制限に注意してください。
・修正が困難なクラッシュの多くは、解放されたデータを参照しているために発生します。
 データを削除するときは、参照を保持しないようにしてください。
・再割り当てによって同じ問題が発生する可能性があります。
 例えば、コレクションに多くのアイテムを追加すると、基礎となるコンテナのメモリが再割り当てされます。
 この場合、既存のアイテムへの以前の参照がすべて無効になります)。
Blender でアクティブであるモジュールまたはクラスは、ユーザーが削除する可能性のあるデータへの参照を保持しません。
 代わりに、スクリプトがアクティブ化されるたびにコンテキストからデータをフェッチします。
・クラッシュは必ず発生するわけではなく、一部の構成/オペレーティングシステムでのみ発生する可能性があります。
再帰的なパターンには注意してください。これらは問題が発生する可能性が高まります。
・既知の重大な例外については、以下の最後のサブセクションを参照してください。
docs.blender.org

注意

クラッシュするスクリプトの行を見つけるには、faulthandler モジュールを活用できます。
faulthandlerのドキュメントを参照してください。
docs.python.org

一部のコンテナの変更は、既存のデータを再割り当てしないため、基本的には安全です。
(例えばリンクリストコンテナは、他のアイテムを追加または削除するときに既存のアイテムを再割り当てしません)
しかし、どのケースが安全でどれが安全でないかを知ることは、Blenderの内部を深く理解することを意味します。
そのため、コンテナを変更するときにデータ参照が何らかの方法で無効になると常に仮定する方が安全です。

推奨されないコード
import bpy

class TestItems(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty()

bpy.utils.register_class(TestItems)
bpy.types.Scene.test_items = bpy.props.CollectionProperty(type=TestItems)

first_item = bpy.context.scene.test_items.add()
for i in range(100):
    bpy.context.scene.test_items.add()

# This is likely to crash, as internal code may re-allocate
# the whole container (the collection) memory at some point.
# 内部コードはある時点でコンテナ(コレクション)メモリ全体を再割り当てする可能性があります。
# このため、これはクラッシュする可能性があります。
first_item.name = "foobar"

f:id:bluebirdofoz:20200119223854j:plain

推奨されるコード
import bpy

class TestItems(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty()

bpy.utils.register_class(TestItems)
bpy.types.Scene.test_items = bpy.props.CollectionProperty(type=TestItems)

first_item = bpy.context.scene.test_items.add()
for i in range(100):
    bpy.context.scene.test_items.add()

# This is safe, we are getting again desired data *after*
# all modifications to its container are done.
# このアクセスは安全です。
# コンテナへのすべての変更が行われた「後」に、目的のデータを再度取得しています。
first_item = bpy.context.scene.test_items[0]
first_item.name = "foobar"

f:id:bluebirdofoz:20200119223904j:plain

bluebirdofoz.hatenablog.com