Blender内部構造(1) オペレータ

Blenderのアドオンを開発していると、Python APIの仕様を理解しているだけでは理解できないBlenderの振る舞いに悩まされることがあります。例えば、APIリファレンスに書かれていないAPIの仕様とか キーボードやマウスの具体的なイベント制御とか。
このような問題に出くわしたとき、Pythonからは意識する必要がなかったBlenderの内部構造を理解しておくと非常に心強いですよね。しかし残念なことに、アドオン開発と同様にBlenderの内部構造を解説している記事も非常に少ないです。

そこでBlenderの内部を詳しく知るために、Blenderのソースコードを読みながら、理解したことを少しずつ記事としてまとめていこうと考えています。 Blenderの内部構造というニッチなところですが、本記事が少しでも皆様のお役に立てれば幸いです。

記念すべき第1回は、Blenderのオペレータ実行処理について書きたいと思います。 今回は、単純な例としてPlaneオブジェクトを生成する処理(オブジェクトモードで、[Add] > [Mesh] > [Plane])を実行したときのBlender内部の処理を見ていきたいと思います。 モーダルモードを理解するために必要なイベント処理や、描画処理などがないシンプルなオペレータであり、Blenderの内部構造理解の導入として適しているのではないでしょうか?

Planeオブジェクトの生成のbl_idname

Planeオブジェクトを生成する [Add] > [Mesh] > [Plane] について詳しくなるためには、そのbl_idnameについて理解しておき必要があります。

Planeオブジェクトを生成するためには、[3D Viewport] エリアでメニュー項目 [Add] > [Mesh] > [Plane] を実行する必要がありますが、まずそのメニュー項目上で、マウスオーバーしてみましょう。 すると、薄めの色で「Python: 」の文字列書かれたあとに、「bpy.ops.mesh.primitive_plane_add」と表示されています。

表示された文字列のうち「mesh.primitive_plane_add」と書かれているのは、1つのオペレータに対して1つだけ割りあてられた識別子になります。 この識別子のことをPython APIではbl_idnameと呼ぶため、以降はこれにしたがって、bl_idnameと呼ぶことにします。 また [Add] > [Mesh] > [Plane] の処理を、primitive_plane_addと呼ぶことにします。

primitive_plane_addのソースコードの在り処

primitive_plane_addは、APIリファレンス に記載されているとおり、Python APIとして提供されていますが、その実装はC言語で書かれています。 このため、C言語で記述されたBlenderのソースコードの中からprimitive_plane_addの処理を探す必要があります。 しかし、Blenderのソースコードはそれなりに巨大であるため、探すのがとても大変です。

そこで役立つのが、bl_idnameです。 bl_idnameは、オペレータ1つに対して1つ割り当てられた識別子であるため、bl_idnameでソースコードを検索することで、調べたいオペレータのソースコードを確認できます。 実際に primitive_plane_add を検索してみると、MESH_OT_primitive_plane_add の関数内に ot->idname = "MESH_OT_primitive_plane_add" を見つけることができます。

void MESH_OT_primitive_plane_add(wmOperatorType *ot)
{
  /* identifiers */
  ot->name = "Add Plane";
  ot->description = "Construct a filled planar mesh with 4 vertices";
  ot->idname = "MESH_OT_primitive_plane_add";   // ★ bl_idname

  /* api callbacks */
  ot->exec = add_primitive_plane_exec;
  ot->poll = ED_operator_scene_editable;

  /* flags */
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  ED_object_add_unit_props_size(ot);
  ED_object_add_mesh_props(ot);
  ED_object_add_generic_props(ot, true);
}

wmOperatorType

MESH_OT_primitive_plane_add 関数の中では、wmOperatorType 構造体に対して様々な値を設定していることがわかります。

wmOperatorType 構造体は、Blender内の各オペレータの情報を保持するための構造体で、bl_idnameに加えてオペレータ実行時に呼ばれる execute 関数や、モーダルモード時に呼ばれる modal 関数などを登録することができます。

typedef struct wmOperatorType {
  const char *name;                   // UIに表示する名前
  const char *idname;                 // bl_idname
  const char *translation_context;    // 翻訳向けのコンテキスト
  const char *description;            // UIに表示する説明
  const char *undo_group;             // 複数のオペレータをグループ化し、まとめてUNDOするための識別子

  // オペレータ実行関数
  // Python APIのbpy.types.Operatorクラスでは、executeメソッドに相当する
  int (*exec)(struct bContext *, struct wmOperator *) ATTR_WARN_UNUSED_RESULT;
  // オペレータプロパティ変更時に呼ばれるコールバック関数
  bool (*check)(struct bContext *, struct wmOperator *);
  // モーダルモード移行時に呼ばれる関数
  // Python APIのbpy.types.Operatorクラスでは、invokeメソッドに相当する
  int (*invoke)(struct bContext *,
                struct wmOperator *,
                const struct wmEvent *) ATTR_WARN_UNUSED_RESULT;
  // モーダルモードがキャンセルされた時に呼ばれる関数
  // Python APIのbpy.types.Operatorクラスでは、cancelメソッドに相当する
  void (*cancel)(struct bContext *, struct wmOperator *);
  // モーダルモード中に呼ばれる関数
  // Python APIのbpy.types.Operatorクラスでは、modalメソッドに相当する
  int (*modal)(struct bContext *,
               struct wmOperator *,
               const struct wmEvent *) ATTR_WARN_UNUSED_RESULT;
  // オペレータを実行可能か確認するための関数
  // Python APIのbpy.types.Operatorクラスでは、pollメソッドに相当する
  bool (*poll)(struct bContext *) ATTR_WARN_UNUSED_RESULT;
  // 自動生成されたUIにオペレータプロパティを表示する時に利用される関数(詳細は不明)
  bool (*poll_property)(const struct bContext *C,
                        struct wmOperator *op,
                        const PropertyRNA *prop) ATTR_WARN_UNUSED_RESULT;
  // RedoやRepeat時に作られるパネルのUIを記述する。
  // もしなにも設定されていなければ、自動生成されたUIとなる。
  void (*ui)(struct bContext *, struct wmOperator *);
  // プロパティの値によって設定された、UI上で見せる名前
  const char *(*get_name)(struct wmOperatorType *, struct PointerRNA *);
  // オペレータプロパティ
  struct StructRNA *srna;
  // 詳細不明
  struct IDProperty *last_properties;
  // 詳細不明
  PropertyRNA *prop;
  // マクロのために利用するリスト
  ListBase macro;
  // モーダル用に使用されるキー(詳細不明)
  struct wmKeyMap *modalkeymap;
  // 詳細不明
  bool (*pyop_poll)(struct bContext *, struct wmOperatorType *ot) ATTR_WARN_UNUSED_RESULT;
  // 詳細不明
  ExtensionRNA ext;
  // 'UNDO' などのオペレータフラグ
  // Pythonのbpy.types.Operatorのbl_optionsに相当する
  short flag;
} wmOperatorType;

primitive_plane_addのwmOperatorType

さて、wmOperatorType の中身が判明したので、MESH_OT_primitive_plane_add をより詳しく見ていきましょう。

まず最初に、上記のコードはメニュー項目 [Add] > [Mesh] > [Plane] をマウスオーバーした時に表示されるオペレータ名と説明文であることがわかります。

  /* identifiers */
  ot->name = "Add Plane";
  ot->description = "Construct a filled planar mesh with 4 vertices";

続いて、メンバ変数 execpoll には、add_primitive_plane_exec 関数と ED_operator_scene_editable 関数が登録されています。 exec はオペレータを実行した時に実行される関数、poll はオペレータを実行可能か判断するための関数であるため、ED_operator_scene_editable 関数を満たす状況下においてprimitive_plane_addを実行したとき、add_primitive_plane_exec 関数が呼び出されることになります。

  ot->exec = add_primitive_plane_exec;
  ot->poll = ED_operator_scene_editable;

最後に、フラグとオペレータプロパティを登録して完了です。

  // Pythonにおけるbpy.types.Operatorの{'REGISTER', 'UNDO'}に相当
  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

  // オペレータプロパティを設定
  ED_object_add_unit_props_size(ot);      // Size
  ED_object_add_mesh_props(ot);           // Generate UVs
  ED_object_add_generic_props(ot, true);  // Location, Rotationなど

add_primitive_plane_exec

primitive_plane_add実行時の処理 add_primitive_plane_exec 関数では、最初にユーザから指定されたオペレータプロパティを取得します。 ここで取得したオペレータプロパティは、以降のPlaneオブジェクト作成に使います。

// source/blender/editors/mesh/editmesh_add.c

static int add_primitive_plane_exec(bContext *C, wmOperator *op)
{
  MakePrimitiveData creation_data;
  Object *obedit;
  BMEditMesh *em;
  float loc[3], rot[3];
  bool enter_editmode;
  ushort local_view_bits;
  const bool calc_uvs = RNA_boolean_get(op->ptr, "calc_uvs");

  WM_operator_view3d_unit_defaults(C, op);
  // 各オペレータプロパティを取得する
  ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, &enter_editmode, &local_view_bits, NULL);

つぎに make_prim_init 関数を呼び出して、Planeオブジェクトを作成します。 この段階では、頂点情報などメッシュを構成するデータは作らず、空のオブジェクトのみが作られている状態です。
第2引数の CTX_DATA_ はロケールに応じた文字列を返すマクロで、ここでは翻訳対象の文字列として "Plane" が指定されています。 CTX_DATA_ は、"Plane" をキーとして英語環境の場合は「Plane」、日本語環境の場合は「平面」に置きかえるため、Blender上では新しいPlaneオブジェクトを作成したときに、ロケールに応じて生成されるオブジェクト名について、日本語か英語かを切りかえることができます。

  // Planeメッシュオブジェクトを作成する
  // 第2引数のCTX_DATA_は、各ロケールに対応した文字列を返すマクロ
  obedit = make_prim_init(
      C, CTX_DATA_(BLT_I18NCONTEXT_ID_MESH, "Plane"), loc, rot, local_view_bits, &creation_data);

BKE_editmesh_from_object は、オブジェクトからメッシュを取得するためのものです。 取得したメッシュを使い、のちの処理で平面のメッシュを構築します。

  // オブジェクトからメッシュを取得する
  em = BKE_editmesh_from_object(obedit);

ユーザがオペレータプロパティ Generate UVs にチェックを入れている場合、ED_mesh_uv_texture_ensure 関数でテクスチャを作成します。

  // オペレータプロパティ「Generate UVs」が設定されていたら、テクスチャを割り当てる
  if (calc_uvs) {
    ED_mesh_uv_texture_ensure(obedit->data, NULL);
  }

先ほど、BKE_editmesh_from_object 関数で取得したメッシュを使って EDBM_op_call_and_selectf 関数を呼び出すことで、bmeshベースでメッシュを構築します。

  // メッシュを構築
  if (!EDBM_op_call_and_selectf(
          em,
          op,
          "verts.out",
          false,
          "create_grid x_segments=%i y_segments=%i size=%f matrix=%m4 calc_uvs=%b",
          1,
          1,
          RNA_float_get(op->ptr, "size") / 2.0f,
          creation_data.mat,
          calc_uvs)) {
    return OPERATOR_CANCELLED;
  }

最後に make_prim_finish は、作成したオブジェクトの頂点を選択した状態にしたり、Blenderに対して再描画通知を行ったりしたあと、オペレータが終了します。

  // 後処理(頂点の選択や再描画通知など)
  make_prim_finish(C, obedit, &creation_data, enter_editmode);

  // オペレータが成功したことを上位に通知
  return OPERATOR_FINISHED;
}

おわりに

ここまでで、オペレータ「primitive_plane_add」を通して、Blenderのオペレータに関する内部処理の一部を見てきましたが、いかがでしょうか。 特にBlenderのアドオンを書いたことがある人は、wmOperatorType 構造体のメンバ変数「exec」などに馴染みがあるのではないでしょうか。 Blenderのアドオンの作り方を理解していると、Blenderの内部構造がさらに把握しやすくなります。 Blenderのアドオン開発に興味がある方は、どうぞ「はじめてのBlenderアドオン開発」もご覧ください。

なお本記事で説明したオペレータ処理には、モーダルモードやイベント処理など、ここで説明した内容以上に多くの話題があります。 それぞれの処理については、別途記事としてまとめたいとおもいますので、どうぞお楽しみに。