MRが楽しい

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

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

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

Blender 2.8 Python API Documentation

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

今日は「落とし穴」の「古いデータ」です。
f:id:bluebirdofoz:20200112135855j:plain

古いデータ

Pythonの値を変更し、更新された値にすぐにアクセスしたい場合があります。

例えば、オブジェクトの bpy.types.Object.location を変更し、bpy.types.Object.matrix_world からすぐにその変更にアクセスする場合です。
これは期待通りには機能しません。

import bpy

# 選択中のオブジェクトのロケーションを(1, 2, 3)に変更する
bpy.context.object.location = 1, 2, 3

# オブジェクトのワールド座標を取得する
matrix = bpy.context.object.matrix_world

# 標準出力に取得した座標を表示する
print(matrix)

f:id:bluebirdofoz:20200112135925j:plain

オブジェクトの最終的な変換を計算する際の計算を考えてみます。
これには次のものが含まれます。

・アニメーションの関数曲線。
・ドライバーとそのPython
・制約
・親オブジェクトとその全てのFカーブ/制約など

プロパティが変更されるたびに高負荷な再計算を避けるために、Blenderは必要になるまで実際の計算を延期します。

しかしスクリプトの実行中に、更新された値にアクセスしたい場合があります。
この場合、値を変更した後に bpy.types.ViewLayer.update を呼び出す必要があります。

次に例を示します。

bpy.context.object.location = 1, 2, 3
bpy.context.view_layer.update()

これで、すべての依存データ(子オブジェクト、修飾子、ドライバーなど)が再計算されます。
アクティブビューレイヤー内のスクリプトで値を使用できるようになりました。

import bpy

# 選択中のオブジェクトのロケーションを(1, 2, 3)に変更する
bpy.context.object.location = 1, 2, 3

# 値を更新する
bpy.context.view_layer.update()

# オブジェクトのワールド座標を取得する
matrix = bpy.context.object.matrix_world

# 標準出力に取得した座標を表示する
print(matrix)

f:id:bluebirdofoz:20200112135940j:plain

スクリプト中に再描画できますか?

これに対する公式の答えは「いいえ」または「あなたはそれをすべきでない」です。

スクリプトが実行されている間、Blenderは終了するまで待機し、終了するまで事実上ロックされます。
この状態では、Blenderはユーザーの入力に対して再描画または応答しません。
通常、Blenderで配布されるスクリプトは長期間実行されない傾向があるため、これはそれほど問題ではありません。
しかしスクリプトの実行には時間がかかることがあり、ビューポートで何が起こっているのかを見るのは素晴らしいことです。

Blenderをループでロックして再描画するツールは推奨されません。
ツールの実行中、インターフェイスのさまざまな部分を更新するBlendersの機能と競合するためです。

したがって、ここでの解決策はモーダル演算子、つまり -modal()関数を定義する演算子を記述することです。
テキストエディタでモーダル演算子テンプレートを参照してください。

モーダル演算子はユーザー入力で実行するか、独自のタイマーをセットアップして実行します。
イベントを処理したり、キーマップや他のモーダル演算子で処理するためにパススルーすることもできます。
モーダル演算子の例として、変換、ペインティング、フライモード、およびファイル選択があります。

モーダル演算子の作成は再描画するだけの簡単な for ループよりも多くの労力を必要とします。
しかしより柔軟性があり、Blenderの設計との統合性が高いです。

それでもPythonから再描画したい

どうしても必要ならば可能です。
しかし、このハックを使用するスクリプトBlenderに含めることは考慮されず、これによる問題はバグとはみなされません。

bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
import bpy
import time

# 処理を5回ループする
for i in range(5):
    # Z軸方向に徐々に上昇する
    bpy.context.object.location[2] = bpy.context.object.location[2] + 1.0

    # 値を更新する
    bpy.context.view_layer.update()

    # 1秒間処理をスリープする
    time.sleep(1)

    # 再描画を行う
    bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)

f:id:bluebirdofoz:20200112135951j:plain

bluebirdofoz.hatenablog.com