context の話
◆そもそもの始まり
script においてアクティブなオブジェクトの情報にアクセスするとき、context.object
と context.ative_object
の2つの方法がある。ところが、この2つの context の要素の厳密な
違いやそれによる適切な使い分けについて、どこにも情報がない。
↓
調べてみる
◆わかったこと1: context の Global と Local
公式より
The context members available depend on the area of Blender which is currently being accessed.
というわけで、context には Global context
とLocal context
がある。
○ 引用文中の "the area of Blender"とは?
『context にアクセスするスクリプトや関数が呼ばれるときのエディター 』のこと
→Local context
:特定のエディターで呼んだときのみアクセスできる context の要素
Local context
の分類[1]
○ - Screen Context :条件は不明、どのエディターでも確認できる
- View3D :3Dビューポート限定
- Buttons :プロパティエディター限定
- Image :画像 / UV エディター限定
- Node :ノード系エディター限定
- Text :テキストエディター限定
- Clip :クリップエディター限定
- Sequencer:シーケンサー限定
-
ただし Local な context 同士で重複する名前も多く、完全に隔離されているのは Text Context のみ ↩︎
context.object
と context.acitve_object
◆わかったこと2: - 基本的にBlender 内では、 どのエディターでアクセスしても、この2つの値は同じ。
- 例外的に、プロパティエディタで『ピン留め』を行うと、この2つの値に違いが生じる。
プロパティエディタの右上にあるやつ
○ 具体的な違い
『プロパティエディタでピン留めを行ったあと別のオブジェクトを選択』した状態で『プロパティエディタから context にアクセスする』と、以下の差異が生じる[1]。
-
context.acitve_object
:新しく選択されたオブジェクトに更新される -
context.object
:ピン留めしたときのもの、つまり以前に選択していたオブジェクトのまま
-
3Dビューにおける「アクティブなオブジェクト」が変化しても「プロパティエディタが表示するオブジェクト」は変化しない状態、つまり『オブジェクトがピン留めされた』状態になる ↩︎
というわけで、Blender 内のテキストエディタで「テンプレート → python → UI Panel Simple」のテンプレートを使った確認用のスクリプトを作ってみた。以下のように書き換えて実行すると "Hello World Panel" がプロパティエディタに追加されるので、通常時とピン留め時の挙動の違いを確認できる。
スクリプト
import bpy
class HelloWorldPanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "Hello World Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
def draw(self, context):
layout = self.layout
obj = context.object
row = layout.row()
row.label(text="Hello world!", icon='WORLD_DATA')
- row = layout.row()
- row.label(text="Active object is: " + obj.name)
- row = layout.row()
- row.prop(obj, "name")
+ try:
+ layout.label(text="Active object is :" + context.active_object.name)
+ except AttributeError:
+ layout.label(text="Active object is : None")
+ layout.label(text="Context object is :" + context.object.name)
row = layout.row()
row.operator("mesh.primitive_cube_add")
def register():
bpy.utils.register_class(HelloWorldPanel)
def unregister():
bpy.utils.unregister_class(HelloWorldPanel)
if __name__ == "__main__":
register()
context.object = None
のときはプロパティエディタ自体のオブジェクトタブ自体が表示されないのでエラー処理は不要(だったはず)
以上の結果より、context.object
はボタン/パネルで使うためのものと考えられる。
↓
では、なぜプロパティエディタ(Button Context)[1]以外にも context.object
があるのか?
↓
各エディターのサイドバーなどのパネル上で使うためと考えると、以下の予想が立てられる。
『__PT__
や__MT__
系のオペレーターでは context.object
、__OT__
系では.active_object
を使う』
↓
結果:そんなことなかった (調べたスクラップ)
-
Context のページで突然現れる『Button』という言葉だが、API整備前(ver. 2.4あたり)の時代の用語がそのまま残っているものっぽい (API Change Log を見ていた感じ、UI関連の要素はかつて Button というカテゴリ名?で扱われていたようだ) ↩︎
さらに Screen Context についてcontext.objext
と context.active_object
を定義している部分を見てみると、全く同じものだった
C (C++?)のコード
static eContextResult screen_ctx_active_object(const bContext *C, bContextDataResult *result)
{
wmWindow *win = CTX_wm_window(C);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
Object *obact = view_layer->basact ? view_layer->basact->object : NULL;
if (obact) {
CTX_data_id_pointer_set(result, &obact->id);
}
return CTX_RESULT_OK;
}
static eContextResult screen_ctx_object(const bContext *C, bContextDataResult *result)
{
wmWindow *win = CTX_wm_window(C);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
Object *obact = view_layer->basact ? view_layer->basact->object : NULL;
if (obact) {
CTX_data_id_pointer_set(result, &obact->id);
}
return CTX_RESULT_OK;
}
context.object
と context.acitve_object
とは?
今のとこのまとめ:- 基本的に同じものだが、「パネル」上においては特殊な状況下では異なる値になることもある
(context.object
が3Dビューでのアクティブオブジェクトに追随しない場合がある) - オペレーター内部でどちらを使うべきかについては、開発者側でも特に決まったルールはなさそうに見える
結論:スクリプト内で統一されていれば、好きな方を使って問題なさそう[1]
-
少なくとも
context.active_object
の方を使っていれば、3Dビュー上のアクティブなオブジェクトとずれることはないと思われる ↩︎
◆幕間
で、出発点の疑問は一応解決されたと言えなくもなくもない感じになったのだが、いろいろ調べていると API Doc の Context のページの投げやりっぷりというか「要素を羅列しただけ」感が気になったので、それぞれの具体的な内容を調べて注釈をつけてみることにした
その結果、半年前(と現在の)の惨状になったというのが流れ
◆これから
↑の後半の内容を基に、もうちょっと簡単そうな context で考えてみる