Open13

context の話

nikogolinikogoli

◆そもそもの始まり

script においてアクティブなオブジェクトの情報にアクセスするとき、context.objectcontext.ative_object の2つの方法がある。ところが、この2つの context の要素の厳密な
違いやそれによる適切な使い分けについて、どこにも情報がない。
 ↓
調べてみる

nikogolinikogoli

◆わかったこと1: context の Global と Local

公式より

The context members available depend on the area of Blender which is currently being accessed.

というわけで、context には Global contextLocal contextがある。

○ 引用文中の "the area of Blender"とは?
 『context にアクセスするスクリプトや関数が呼ばれるときのエディター 』のこと
 →Local context:特定のエディターで呼んだときのみアクセスできる context の要素

Local contextの分類[1]

  • Screen Context :条件は不明、どのエディターでも確認できる
  • View3D  :3Dビューポート限定
  • Buttons  :プロパティエディター限定
  • Image   :画像 / UV エディター限定
  • Node    :ノード系エディター限定
  • Text    :テキストエディター限定
  • Clip    :クリップエディター限定
  • Sequencer:シーケンサー限定
脚注
  1. ただし Local な context 同士で重複する名前も多く、完全に隔離されているのは Text Context のみ ↩︎

nikogolinikogoli
  • 今のとこ『context について』のスクラップで見るべき内容はこれだけ
  • Global と Local が競合する場合、Local が優先される[1]
脚注
  1. というか、context の内容が見つかるまで段階的に探索を行う処理になっているので、Global の context の要素が呼び出されるのは Local context に context.hoge が存在しない場合のみ (だったはず) ↩︎

nikogolinikogoli

◆わかったこと2: context.objectcontext.acitve_object

  • 基本的にBlender 内では、 どのエディターでアクセスしても、この2つの値は同じ。
  • 例外的に、プロパティエディタで『ピン留め』を行うと、この2つの値に違いが生じる。

    プロパティエディタの右上にあるやつ

○ 具体的な違い

『プロパティエディタでピン留めを行ったあと別のオブジェクトを選択』した状態で『プロパティエディタから context にアクセスする』と、以下の差異が生じる[1]

  • context.acitve_object:新しく選択されたオブジェクトに更新される
  • context.object:ピン留めしたときのもの、つまり以前に選択していたオブジェクトのまま
脚注
  1. 3Dビューにおける「アクティブなオブジェクト」が変化しても「プロパティエディタが表示するオブジェクト」は変化しない状態、つまり『オブジェクトがピン留めされた』状態になる ↩︎

nikogolinikogoli

https://blender.stackexchange.com/questions/204074/python-bpy-context-object-vs-bpy-context-active-object
この話の元ネタは↑なのだが、そこにもあるようにプロパティエディタにカスタムパネルを追加すると簡単にこの結果が確認できる。
 

というわけで、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のときはプロパティエディタ自体のオブジェクトタブ自体が表示されないのでエラー処理は不要(だったはず)

nikogolinikogoli

以上の結果より、context.objectはボタン/パネルで使うためのものと考えられる。
  ↓
では、なぜプロパティエディタ(Button Context)[1]以外にも context.objectがあるのか?
  ↓
各エディターのサイドバーなどのパネル上で使うためと考えると、以下の予想が立てられる。
__PT____MT__系のオペレーターでは context.object__OT__系では.active_objectを使う』
  ↓
 結果:そんなことなかった (調べたスクラップ)

脚注
  1. Context のページで突然現れる『Button』という言葉だが、API整備前(ver. 2.4あたり)の時代の用語がそのまま残っているものっぽい (API Change Log を見ていた感じ、UI関連の要素はかつて Button というカテゴリ名?で扱われていたようだ) ↩︎

nikogolinikogoli

さらに Screen Context についてcontext.objextcontext.active_objectを定義している部分を見てみると、全く同じものだった

C (C++?)のコード
source/blender/editors/screen/screen_context.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;
}
nikogolinikogoli

今のとこのまとめ:context.objectcontext.acitve_object とは?

  • 基本的に同じものだが、「パネル」上においては特殊な状況下では異なる値になることもある
    (context.objectが3Dビューでのアクティブオブジェクトに追随しない場合がある)
  • オペレーター内部でどちらを使うべきかについては、開発者側でも特に決まったルールはなさそうに見える
     

結論:スクリプト内で統一されていれば、好きな方を使って問題なさそう[1]

脚注
  1. 少なくともcontext.active_objectの方を使っていれば、3Dビュー上のアクティブなオブジェクトとずれることはないと思われる ↩︎

nikogolinikogoli

◆幕間

で、出発点の疑問は一応解決されたと言えなくもなくもない感じになったのだが、いろいろ調べていると API Doc の Context のページの投げやりっぷりというか「要素を羅列しただけ」感が気になったので、それぞれの具体的な内容を調べて注釈をつけてみることにした

その結果、半年前(と現在の)の惨状になったというのが流れ

nikogolinikogoli

Develpoer Wiki にある Context の説明をもとに、整理を加えたやつ↓
https://zenn.dev/nikogoli/scraps/c355b8cdab930a

実装コードなどいろいろ掘った後で読むと納得できる情報が多いが、残念ながらこれだけでは具体的な context の要素についてはわからない

nikogolinikogoli

一番の問題は、Blender のpython 部分はコンソールを通していろいろ確認できるのに対し、C/C++ の部分はひたすらコード読んで想像するしかない点