めもてう

忘れっぽいTIPs、HowToのメモ帳です。

【Blender 2.8 アドオン開発】004 Hello Blender AddOn

前回までで、ザックリとですが Blender 内のデータ構造、レイアウト構造、API の使い方、調べ方を説明しました。
今回から実際にアドオンを作っていきたいと思います。

今回はプログラミングの慣例にならいまずは、「Hello AddOn」をしましょう。

アドオンとは

Blender において、アドオンとは追加機能、拡張機能のことを指します。

Blender にはユーザが作法にのっとって作成した機能を受け入れる仕組みが備わっています。
つまり、アドオンを作るということは、

  1. 追加する機能を作成する
  2. Blender が機能を追加できるようにする

という2つの実装が必要になってきます。

機能の作成

まず、コンソールウィンドウを表示して、「Scripting」レイアウトで新規テキストを作成してください。
詳しい操作は第1回を参照してください。

続いて、以下のコードを書いてください。

import bpy

class TUTORIAL_OT_HELLOADDON(bpy.types.Operator):
  bl_idname = "tutorial.helloaddon"
  bl_label = "HelloAddOn"

  def execute(self, context):
    print("Hello AddOn")

    return {'FINISHED'}

bpy.utils.register_class(TUTORIAL_OT_HELLOADDON)

このスクリプトの説明をする前に、実行してみましょう。
テキストエリア右上の「Run Script」ボタンを押してください。

続いて Pythonコンソールに

bpy.ops.tutorial.helloaddon()

と、入力してエンターを押してください。
コンソールウィンドウにHello Addonと表示されたら成功です。
キーボードの「F3」を押して出てくるサーチメニューから実行もできます。この場合は「HelloAddOn」で検索すれば出てきます。

このスクリプトでは新たな機能を定義し、Blender へ登録し、使える状態へしました。

では、スクリプトの解説をしていきす。
まずは、bpyをインポートするところから全ては始まります。

機能を定義

bpy.opsへ登録されていることからもわかるように、今回作成した機能はオペレーターです。
Blender へ登録するオペレーターはbpy.types.Operatorを継承したクラスとして定義します。

まず、クラスの命名規則について触れます。
今回はオペレータークラスだけですが、Blender で用意されたいくつかのクラスを継承してクラスを定義する場合、命名規則が存在します。

公式リファレンス

リファレンスによると[A-Z][A-Z0-9_]*_{Separator}_[A-Za-z0-9_]+なのですが、ちょっと解りにくいので説明しますと。

XXX_{Seperator}_YYYとした場合、
XXXの部分は1文字目は大文字の英字(AからZ)2文字目以降は大文字の英字と数字とアンダースコア(AからZと0から9と_)で記載します。
YYYの部分は1文字目から大文字の英字と数字とアンダースコア(AからZと0から9と_)で記載します。
XXXYYYは最低1文字は必要です。

{Separator}の部分は継承元のクラスによって変わります。

継承元 Separator
Operator OT
Panel PT
Header HT
MENU MT
UIList UL

今回のスクリプトでいうとbpy.types.Operatorを継承しているのでOTを Separator としてTUTORIAL_OT_HELLOADDONです。
もちろんTUTORIAL_OT_HelloAddOnTUTORIAL_OT_hello_add_onとしても大丈夫です。

命名規則に従わない場合、現時点で Warning が出て、将来的にエラーとなるらしいので命名規則は守りましょう。
また、いくつか命名規則に従わなくても大丈夫なパターンがあるようですが、覚えるのがメンドクサイので順守しましょう。

では中身に触れていきましょう。
オペレータークラスは変数bl_idnamebl_labelと実行部分となるメンバー関数executeを定義しなければエラーとなります。

bl_idname

bl_idname = "tutorial.helloaddon"

bl_idnameは ID を指定するのですが、

  1. 一意でなければならない
  2. 小文字でなければならない
  3. {name space}.(ピリオド){operator name}の形式でなければならない

という規約があります。

すでに登録されているクラスとカブっていなければ、クラス名にある_OT_の両サイドを小文字にして使用していれば問題はありません。
tutorial.helloaddonを例にとると、bpy.opstutorialグループ(name space)へhelloaddonという形で登録されます。
このtutorialobjectとした場合bpy.ops.objectグループへhelloaddonが登録されます。

今回はtutorialとしましたが、本来は機能の内容によって既存のグループへ登録したほうが親切ではあります。また、クラス名とbl_idnameは違っていてもいいのですが、同じにしておくとなにかと間違えにくいので同じにしておきましょう。

bl_label

bl_label = "HelloAddOn"

bl_labelは表示名を指定します。特に規定はないです。サーチメニューへはここで指定した名前が使われます。

execute

def execute(self, context):
  print("Hello AddOn")

  return {'FINISHED'}

コアとなるexecuteメンバー関数です。
Blender から実行を指示されると最終的にこの関数が実行されます。
この関数は引数にselfcontextを取り、戻り値として状態結果の列挙体(enum)を返します。

self 自身のインスタンス(レシーバー)です。
context bpy.contextと同義で現在の状態を保持しています。
ですが、第3回でも触れたオーバーライドされた Context を指定されている場合など、bpy.contextで取得できる内容と、引数で渡ってきたcontextの内容は違う場合があります。
ですので、関数内で現在の状態を参照したい場合は引数の Context を使用するようにしましょう。
return その処理が正常に終わったのか、実行がキャンセルされたのか、という状態結果を定義されている列挙体で返します。
この列挙体はRUNNING_MODALCANCELLEDFINISHEDPASS_THROUGHINTERFACEが定義されています。
とはいえ、この列挙体を意識しないといけないのは継続して処理を実行する Modal 実行が絡んだ時ですので、今回の要にexecuteメンバー関数だけの場合は関数が完了したという意味のFINISHEDだけ覚えておけばひとまず問題ありません。

このexecuteメンバー関数内で Blender を使う上で便利な機能を書いていくことになります。

以上でオペレータークラスの定義は終了です。

Blender へ登録

定義したオペレータークラスを Blender へ登録します。
登録には Blender APIbpy.utils.register_classにクラスを指定して使用します。

クラス定義を更新して「Run Script」した場合、もう一度登録がされるのですが、

  • 同じbl_idnameだった場合上書きされます
  • 違うbl_idnameだった場合は新たに登録されて、以前登録していたオペレーターはそのまま残り、実行もできます

登録を解除したい場合は、スクリプト保存してBlender を再起動しましょう。

登録を解除する API としてbpy.utils.unregister_class()というのがあります。
これは指定したクラスを登録解除するというものですが、bpy.utils.register_class()と置き換えても動きません。それは、登録されているクラスと登録解除しようとしているクラスが同じものか判らないかららしいのです。
ですが、

bpy.utils.register_class(XXX)
bpy.utils.unregister_class(XXX)

と、いうふうに連続して書くと登録解除できたりします。

出来るのですが、これが Blender 的に正しいのかちょっと疑問ですので僕自身は避けています。

とはいえ、これで機能の作成は終わりました。
ですが、Blender を再起動したら登録は解除されてしまいます。
もちろん、スクリプトを読み込んでもう一度走らせれば登録はできますが、Blender を起動するたびにこの操作をするのは億劫です。

そこで、アドオンという仕組みが出てきます。
端的に言って、この仕組みは「インストールされているアドオンで指定してある機能を Blender へ登録する」というものです。

最小のアドオン

#
# bl_info
#
bl_info = {
  "name": "HelloAddOn",
  "description": "This AddOn is first AddOn",
  "author": "memoteu",
  "version": (1, 0, 0, 0),
  "blender": (2, 80, 0),
  "support": "TESTING",
  "category": "Tutorial",
  "location": "",
  "warning": "",
  "wiki_url": "",
  "tracker_url": ""
}

#
# register
#
def register():
  print("regist addon")

#
# unregister
#
def unregister():
  print("unregist addon")

#
# AddOn Entry
#
if __name__ == "__main__":
  register()

このスクリプトBlender アドオンの最小コードになります。
では、コードの説明をしていきます。

bl_info

このbl_infoへアドオンの説明を設定すると、アドオンをインストールした際 Blender が読み取り、追加されます。
逆を言えばこのbl_infoがないとアドオンとみなされずインストールすることは出来ません。

各項目については Blender アドオンで検索したら様々なサイトで説明がされていますが、一応書いておきます。

項目 説明
name 文字列 アドオンの名前
description 文字列 アドオンの説明文
author 文字列 アドオン作成者名
version 整数タプル アドオンのバージョンをタプルで指定。桁数の上限は不明ですが、4桁はいけました。
バージョンが 1.0 なら(1, 0)2.10.1 なら(2, 10, 1)と指定する。
blender 3桁整数のタプル アドオンが動作する Blender の最小バージョンをタプルで指定。
アドオンのバージョンと違い3桁で指定しなければなりません。
Blender バージョンが 2.79 なら(2, 79, 0)と指定する
今回の Blender バージョン 2.8 の場合、注意が必要で指定は(2, 80, 0)と表記しなければなりません。
support 文字列 アドオンのサポートレベルを指定します。
指定できる文字列はOFFICIALCOMMUNITYTESTINGの、3つです。OFFICIALは公式サポートのことなので、指定してはいけません
ですので、COMMUNITYTESTINGのどちらかを指定することになります。

デフォルトではCOMMUNITYですが、今回はTESTINGを指定しています。
category 文字列 アドオンの種類を指定します。

例えば 3D View に対してのアドオンであれば3D View、ファイルの読込み、書き出しに対してのアドオンであればImport-Exportなど Blender に組み込まれているアドオンがすでに設定しているカテゴリー名を指定すれば、そのカテゴリーに追加することも出来ます。

もちろん、今回のように自身でカテゴリーを新たに追加することも出来ます。
location 文字列 アドオンが主に実行される場所を指定します。
これはメインとなる UI の場所を指定するのですが、今回は UI は出てきませんので、空文字です。
warning 文字列 アドオンの注意文を書きます。(バグの有無や使用上の注意など)
wiki_url 文字列(アドレス) アドオンの説明、使い方などが記載されたサイトの URL を書きます。
tracker_url 文字列(アドレス) アドオンのバグ報告、改善要求など作成者とコンタクトが取れるサイトの URL を書きます。

register()

この関数はインストールされたアドオンが「有効化」されたときに呼び出されます。
関数の名前は変えてはいけません。
この中でbpy.utils.register_class()を呼んで機能を登録することになります。

unregister()

この関数はregister()とは逆で、インストールされたアドオンが「無効化」されたときに呼び出されます。
関数の名前は変えてはいけません。
この中でbpy.utils.unregister_class()を呼んで機能を登録解除することになります。

AddOn Entry

AddOn Entry とは書いてますが、アドオンとは関係ありません。

if __name__ == "__main__":
  register()

これは Python の話になるのですが、
モジュールが単体実行の場合__name__"__main__"となり、import 呼び出しの場合__name__はモジュール名となります。

Blender にインストールされたアドオンは import 呼び出しなのでこの if文に入ることは無く、Blender は直接 register()unregister()を呼びます。
if文に入るのは「Run Script」を押したときです。

このスクリプトを保存して、インストールすればアドオンとして動きます。
ですが、機能が備わっていないので意味は無いのです。

Hello AddOn

ここまでで、

  1. 追加する機能を作成するスクリプト
  2. Blender へ機能を追加するためのスクリプト

という2つのスクリプトを書いてきました。
あとは、この2つの実装を1つにしてやればアドオンの完成です。

#
# bl_info
#
bl_info = {
  "name": "HelloAddOn",
  "description": "This AddOn is first AddOn",
  "author": "memoteu",
  "version": (1, 0, 0, 0),
  "blender": (2, 80, 0),
  "support": "TESTING",
  "category": "Tutorial",
  "location": "",
  "warning": "",
  "wiki_url": "",
  "tracker_url": ""
}

import bpy

#
# TUTORIAL_OT_HELLOADDON
#
class TUTORIAL_OT_HELLOADDON(bpy.types.Operator):
  bl_idname = "tutorial.helloaddon"
  bl_label = "HelloAddOn"

  def execute(self, context):
    print("Hello AddOn")

    return {'FINISHED'}

#
# register
#
def register():
  print("regist addon")
  bpy.utils.register_class(TUTORIAL_OT_HELLOADDON)

#
# unregister
#
def unregister():
  print("unregist addon")
  bpy.utils.unregister_class(TUTORIAL_OT_HELLOADDON)

#
# AddOn Entry
#
if __name__ == "__main__":
  register()

本当に1つにしただけです。機能登録のbpy.utils.register()と機能登録解除のbpy.utils.unregister()の位置が移動していますが、ここまで読んでいただけたなら意味は解るはずです。

1点注釈を入れるならば、このスクリプトを「Run Script」で実行した場合、機能の登録はされますが、それはアドオンをインストールしたことにはなりません
つまり、Blender を再起動すれば登録は解除されています。

アドオンをインストールするには Blender の操作が必要です。

インストール

では、どこでもいいので、拡張子.pyを付けるのを忘れず一旦ファイルを保存してください。
ファイル名は仮に「HelloAddOn.py」とします。

ウィンドウのヘッダーにある「Edit」から「Preferences」を開いてください。

f:id:Hobbyist:20190410215704p:plain

続いて「Add-ons」ページの「Install」を押して、先ほど保存した「HelloAddOn.py」をインストールしてください。

f:id:Hobbyist:20190410215656p:plain

もし、エラー無くインストールされたアドオンが表示されていなければ、サポートフィルターが「Testing」になってるか見てください。

f:id:Hobbyist:20190410215645p:plain

チェックボックス横の三角ボタンを押すと詳細が見えます。

f:id:Hobbyist:20190410230048p:plain

詳細へはbl_infoに設定したいくつかの項目とアドオンをインストールした場所が記載されています。
一番下の「Remove」ボタンを押すとアドオンはアンインストールされます。

では、問題なくインストールされてアドオンが表示されたら、さっそくチェックボックスを押して有効化しましょう。

f:id:Hobbyist:20190410215734p:plain

このとき、コンソールウィンドウへregist addonと表示されているはずです。
チェックボックスを外すとunregist addonと表示されるはずです。

このことから、register()unregister()が実行されていることがわかります。
そして、register()unregister()内に書いたbpy.utils.register_class()bpy.utils.unregister_class()も実行されるので、アドオンを有効化して初めて、機能の登録が行われるということも解ります。

ちなみに、bl_info内のcategoryもカテゴリーフィルターとして登録されています。

f:id:Hobbyist:20190410215636p:plain

では「Save Preferences」を押して Preferences ウィンドウを閉じてください。
登録された、機能が動くか Pythonコンソール、またはサーチメニューから実行して、問題なければ Blender を再起動しましょう。

問題なければ再起動後も登録した機能が動くはずです。

これで、アドオンは完成です。

アップデート

アドオンを開発して使用していると、バグ、改善、機能追加など様々な理由からアップデートをかけることになります。
実はこのアップデートがちょっとしたクセ者なので説明をしたいと思います。

今回の例でいうと、「HelloAddOn.py」が完成し保存しました。
次にインストールをしました。
最後に有効化をしました。

この段階で「HelloAddOn.py」に書いたスクリプト

  1. 保存した場所
  2. インストールした場所
  3. メモリ上

と3ヶ所に存在していることになります。

インストールした場所はアドオンの詳細の「File」項目に書いてあります。

f:id:Hobbyist:20190410215631p:plain

あるとき、ふと気づきます、「アレ?コンソールウィンドウじゃなくて Blender に「Hello AddOn」て出した方がクールじゃね?」と。
そこでスクリプトを変更するわけです。

#
# TUTORIAL_OT_HELLOADDON in HelloAddOn.py
#
class TUTORIAL_OT_HELLOADDON(bpy.types.Operator):
  bl_idname = "tutorial.helloaddon"
  bl_label = "HelloAddOn"

  def execute(self, context):
    print("Hello AddOn")
    self.report({'INFO'}, "Hello AddOn") # added

    return {'FINISHED'}

そして、保存します。

この段階で、保存した場所のスクリプトはアップデートしたことになります。

次に、インストールした場所のスクリプトですが、アップデートはしていません。それは、アドオンのインストールは Blender が参照しているスクリプトディレクトリへのコピーだからです。
ではどうするか、「Prefereces」からもう一度再インストールしてやる必要があります。

ここで2つに分かれます、

  • 「Remove」してからインストールする
  • 「Remove」せずに上書きインストールする

「Remove」してからインストールする場合は問題ありません、有効化すればメモリ上もアップデートがかかります。
というか、アンインストール時にunregister()が呼ばれるので正確にはメモリ上も再インストールされるです。

「Remove」せずに上書きインストールする場合は2つ注意が必要です。

1つ目は元のスクリプトは消さないということです。これはファイルの名前へを変えたときなど、上書きインストールすると元のファイルは残ったままになります。

2つ目はunregister()が呼ばれず前のスクリプトをメモリ上へ残したまま、インストール先のスクリプトが更新されるため再インストールしたのに動作が更新されないということがおきることです。
これに関しての対処法は2つ

  1. 有効/無効のチェックボックスを切り替えてunregister()register()を呼んでやる
  2. API を使って更新する

です。
APIbpy.ops.script.reload()です。サーチメニューでは「Reload Script」で出てきます。
この API はメモリ上のスクリプトを再読込みするものです。スクリプトはインストール先のディレクトリから読み込まれるので、再インストールしていないと意味はありません。

有効/無効のチェックボックスを切り替えた方が手軽に見えますが、その内「Prefereces」を開くのメンドクサくなってきます。
そして、インストール先のファイルを直接イジるようになります。保存したらインストール先のスクリプトのアップデートど同義なので、API で更新してウキウキしながらアドオン開発をします。
そんな折、Blender が更新されて初回起動時のスプライトが更新されます。もちろん、見てみたくなります。
そこで、設定ファイルとかを消して一旦 Blender をフラットにします。開発中のアドオンがあるアドオンのインストール先ディレクトリごと消して。そう、僕です。

これに関しては手間を惜しまないか、気をつける他ありません。

まとめ

スクリプト自体は短く、機能を追加することの容易さを実感できると思います。
またシンプルな例題をと思いましたのでオペレータークラスの実装も最小限にしました。
ですので、説明できていない部分の方が多いです。例えば、初期設定を行うinvoke、実行可能か判断するpollなど色々です。
今後のエントリーでも必要になった時に説明できればと思います。

次回からは UI について説明できればと思います。

最後になりますが、ここで記載されているソースコードを使用する場合は自己責任でお願いします。

プライバシーポリシー (Privacy policy)