忘却まとめ

Blenderの中級者・上級者向けの踏み込んだ情報や、アドオン・3DCGに関する情報を記事にします

【Blender】アドオンの作り方・構造の解説【サンプルコードあり / 初心者向け】

Python

更新日:

自分が多くのアドオン開発をしてきた中でよく使っている構造を元に、Blenderのアドオンの構造の解説や作り方を紹介します。
基本構造の他に、翻訳やパネルメニューのタブ位置変更機能なども紹介します。

ダウンロード

gumroad

bl_info アドオン情報

bl_info = {
	"name" : "BK Temp", #des アドオン名
	"author" : "Bookyakuno", #des 作者の名前
	"version" : (1, 0, 0), #des バージョン数
	"blender" : (2, 82, 0),#des 対応バージョン
	"location" : "hoge", #des アドオンの場所(ショートカットやパネルの表示場所など)
	"description" : "hoge", #des アドオンの解説
	"warning" : "",
	"wiki_url" : "",
	"tracker_url" : "",
	"category" : "UI"
}

バージョン名は、アップデートをした時には1, 1, 0などとする。
少しのバグ修正や変更などの小さなアップデートをした時には、 1, 0, 1 のように小さな桁を上げることができるようにする。

import ライブラリやモジュールの読み込み

import bpy
from bpy.props import *
from bpy.types import Operator, AddonPreferences, Panel, PropertyGroup
import rna_keymap_ui #des キーマップリストのメニューに必要
  • [import 〇〇] で、スクリプトで必要なライブラリを読み込む
    • 必要に応じて増やしていく
  • [from 〇〇 import ✕✕]は、「〇〇の場所にある✕✕を読み込む」という意味
    • [ * ] の場合は、〇〇の場所にあるものを全て読み込む
  • [bpy.props] は、bpy.props.BoolPropertyの、bpy.propsを省略できる
  • [bpy.types] は、classのbpy.types.Operatorの、bpy.typesを省略できる
    • 必須ではないが、ソースコードを少し簡略化することができる

複数ファイル構成

# 再読み込み用機能
if "bpy" in locals():
	import importlib
	reloadable_modules = [
	"other_file",
	]
	for module in reloadable_modules:
		if module in locals():
			importlib.reload(locals()[module])

# アドオン有効時にファイルを読み込む
from .other_file import *

複数ファイル構成のアドオンの場合に使えるデバッグ機能

複数ファイル構成のアドオンは、Blenderの画面内のアドオンで「更新」をしただけでは再読み込みできない。
そのため、この再読み込み用機能と、オペレーター"script.reload"を利用して、再読み込みできるようにする。

  • 必要分だけ"other_file",の行を複製して使う
  • "script.reload"はBlender2.8ではキーマップがないので、頻繁に使うなら自分でキー登録するとよい
  • [from .〇〇 import *] は、複数ファイル構成のアドオンの場合に必要。外部ファイルを読み込む
  • [ . ] が現ファイルがある同じ階層。相対パスで、[ . ] 区切りで書く
  • 単なる関数だけのファイルをreloadable_modules内に入れてリロードしようとしても、エラーが出る?

update_panel - パネルカテゴリーの変更機能

def update_panel(self, context):
	message = ": Updating Panel locations has failed"
	try:
		for panel in panels:
			if "bl_rna" in panel.__dict__:
				bpy.utils.unregister_class(panel)

		for panel in panels:
			panel.bl_category = context.preferences.addons[__name__].preferences.category
			bpy.utils.register_class(panel)

	except Exception as e:
		print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
		pass

アドオンのパネルメニューが表示されるcategoryを変更できるようにする関数。

仕組み

  1. アドオン設定のPropertyが変更された時に動作
  2. 一旦panelの登録を解除
  3. panelのbl_category を、アドオン設定の文字列に変更する
  4. 再度panelを登録する

アドオン設定のメニューのpropertyが更新された時に、"panels"にあるパネルの登録場所を変更する機能。
Propertyには、update でpropertyが更新された時に動作する関数を付けることができる。

propertyより上の行にないといけない模様。

try: とexcept: で囲っておけば、もしその内容が失敗してもエラーを出力して動作が停止することが無いようになる。

AddonPreferences - アドオン設定

class BKTEMP_MT_AddonPreferences(AddonPreferences):
	bl_idname = __name__ #des 複数ファイル構成にする場合は、__package__の方がよい

	#des TrueかFalseかを切り替えできるpropertyのサンプル。必須ではない。
	pref_bool : BoolProperty(default=True, name = "Bool", description = "Bool")
	#des アドオンのパネルメニューのcategoryを変更するproperty。
	category : StringProperty(name="Tab Category", description="Choose a name for the category of the panel", default="Tools", update=update_panel)

	tab_addon_menu : EnumProperty(name="Tab", description="", items=[('Option', "Option", "","DOT",0),('Keymap', "Keymap", "","KEYINGSET",1), ('Link', "Link", "","URL",2)], default='Option')

アドオンのpropertyと、アドオン設定のメニューを作る。
アドオンのpropertyは、Blenderの環境設定を保存した時に保存される。

アドオンのメニュー

def draw(self, context):
	layout = self.layout #des 通例的に最初にself.layoutはlayoutにしておく

	row = layout.row(align=True)
	row.prop(self, "tab_addon_menu",expand=True)

	################################################
	#des パネルメニューのカテゴリ変更
	if self.tab_addon_menu=="Option":
		row = layout.row()
		row.label(text="Tab Category:")
		row.prop(self, "category", text="")

	################################################
	#des キーマップのメニュー。"addon_keymaps" に登録されたアドオンのキーと一致するキーを、Blenderの設定内から検索して、表示するメニュー。オペレーターのpropertyの違いまでは対応できないので、同じオペレーター名で違うpropertyを複数登録する場合は表示がおかしくなる。注意。
	if self.tab_addon_menu=="Keymap":
		box = layout.box()
		col = box.column()
		col.label(text="Keymap List:",icon="KEYINGSET")

		wm = bpy.context.window_manager
		kc = wm.keyconfigs.user
		old_km_name = ""
		for km_add, kmi_add in addon_keymaps:
			for km_con in kc.keymaps:
				if km_add.name == km_con.name:
					km = km_con

			for kmi_con in km.keymap_items:
				if kmi_add.name == kmi_con.name:
					if kmi_add.type == kmi_con.type:
						kmi = kmi_con
			try:
				if not km.name == old_km_name:
					col.label(text=str(km.name),icon="DOT")
				col.context_pointer_set("keymap", km)
				rna_keymap_ui.draw_kmi([], kc, km, kmi, col, 0)
				col.separator()
				old_km_name = km.name
			except: pass

	################################################
	#des URL
	if self.tab_addon_menu=="Link":
		row = layout.row()
		row.label(text="Link:",icon="NONE")
		row.operator( "wm.url_open", text="gumroad", icon="URL").url = "https://gum.co/VLdwV"
レイアウト要素説明
row横並び。
column縦並び。
boxボックスで要素を囲う。
boxの中の要素は、外のrowやcolumnが初期化されるので注意。
separator空白。
labelテキスト。
operatorオペレーター。
表示したい機能をlayout.operator("〇〇.✕✕")のように書く。
propプロパティ。
表示したいプロパティをlayout.prop(✕✕,"〇〇")のように書く。

レイアウトのオプションの(align=True)では、要素同士の間隔が狭まる。

panel - パネルメニュー

class BKTEMP_PT_Panel(Panel):
	bl_space_type = 'VIEW_3D' #des 3Dビューに表示する
	bl_region_type = 'UI' #des Nキーのメニュー
	bl_category = 'Tools' #des パネルを追加するタブカテゴリ
	bl_label = "BK Temp" #des パネルの名前
	bl_options = {'DEFAULT_CLOSED'} #des デフォルトで閉じておく。タブ内にパネルメニューが複数表示されるのならデフォルトで閉じておいたほうがよい。必須ではない。

	def draw(self, context):
		layout = self.layout
		addon_prefs = bpy.context.preferences.addons[__name__].preferences #des アドオン設定のpropertyを使用する場合は、これからアクセスする。アドオン設定のメニューなら"self"でもよい

		layout.label(text="hoge",icon="NONE") #des 単なる文字列

		row = layout.row(align=True) #des レイアウトを横並びにする。align=Trueで隙間を詰めることができる。
		row.label(text="Panel",icon="NONE")
		row.prop(addon_prefs,"category",text="hoge",icon="NONE")

		col = layout.column(align=True) #レイアウトを縦並びにする。
		col.operator("bktemp.simple_operator")
		col.operator("bktemp.simple_operator")

Operator - オペレーター

class BKTEMP_OT_SimpleOperator(Operator):
	bl_idname = "bktemp.simple_operator" 
	bl_label = "BK Temp" #des 名前
	bl_description = "" #des 説明
	bl_options = {'REGISTER', 'UNDO'} #des 3Dビュー左下に出るやり直し機能が使えるようにする

	#des オペレーターのpropertyはここに書く。
	ope_string : StringProperty(default="hoge", name="String")

	def execute(self, context):
		#des ##### ここ中にメインの機能を書く #####


		prop_text = self.ope_string #des オペレーターのpropertyはselfから呼び出せる。
		self.report({'INFO'}, "TEST " + prop_text) #des ヘッダーにメッセージを表示する

		#des ##### ここまで #####
		return {'FINISHED'}

アドオンのメインと言える立ち位置の機能。
メニューのボタンを押したり、ショートカットから実行したりして呼び出す。

簡単に機能をつける

単にオペレーターを実行したいだけであれば、例えば
猿プリミティブの追加「bpy.ops.mesh.primitive_monkey_add()」を、

def execute(self, context): の中に、コピペするだけでも利用することができる。
複数のoperatorを貼り付けておけば、Photoshopのアクションのように一括して動作を実行できる。

その他

bl_idname がメニューやキー設定などで使われるオペレーター名。
"アドオンの固有名.オペレーター名"のように、meshやobjectなど標準名とかぶらないようにすること。

self.report()は、オペレーター処理がどのように行われているかがわかりやすいので、デバッグ作業でおすすめ。

print()のメッセージはコンソールに表示されるので注意。

オペレーター内の関数の説明

関数説明
def poll(self, context):オペレーターを実行できる状況かどうかを事前に判断する
実行できない状況だとメニューが薄暗くなり、実行できないようになる。
def invoke(self, context, event):executeの実行前に実行される処理。
eventが使えるので、ctrlやshiftなどの修飾キーによって動作を変えることができる。
def draw(self, context):オペレーター実行後に事後調整できるメニュー。
def execute(self, context):オペレーターのメインの内容。

これら以外にも自分で関数を作ることができる。
オペレーター内の関数で別のオペレーターの関数を呼び出す時は、
self.execute(context) のように、selfを使って呼び出す。

returnで、関数が終了する時に行う動作を指定できる。
return {'FINISHED'}ならば、オペレーターの処理が終了する。
return self.execute(context) ならば、終了後にexecuteを実行する。

PropertyGroup - アドオン全体で利用するプロパティ

class BKTEMP_Props(PropertyGroup):
	Bool        : BoolProperty(default=False, name="Check Box")
	String      : StringProperty(default="hoge", name="String")
	Int         : IntProperty(default=44, name="Int")
	Float       : FloatProperty(default=3.14, name="Float",min=0)
	FloatVector : FloatVectorProperty(default=(0.1,0.2,0.3), name="FloatVector")
	Enum        : EnumProperty(default="Scene",name="Enum", items= [ ("Selected","Selected","Selected","RESTRICT_SELECT_OFF",0),("Scene","Scene","Scene","SCENE_DATA",1), ("All_Data","All Data","All Data","FILE",2), ])
	Pointer     : PointerProperty(name="Target Object",type=bpy.types.Object)


# 登録
bpy.types.Scene.bktemp = PointerProperty(type=BKTEMP_Props)

# 呼び出しの例
bpy.context.scene.bktemp.prop_group_bool

# よく使うので、基本的に下記のように代入している。
props = bpy.context.scene.bktemp
props.prop_group_bool

アドオン全体で使用したいpropertyや、メニューで使う表示切り替え用propertyなどをここに登録する。
PropertyGroupで登録すれば、複数のPropertyを一括して登録することができる。

登録は、"bktemp"の部分を自分のアドオン固有のものに変えること。

プロパティ名説明
Bool真偽。
TrueかFalseか。if文で判定したい時に使える。
String文字列。
Int整数。
minで最小値を設定、maxで最大値を設定できる。
Float小数点も使える数。
minで最小値を設定、maxで最大値を設定できる。
FloatVector 小数点も使える数で、数を3つ持つ。
トランスフォームのXYZを格納できる。
Enum複数の文字列。
それぞれ「内部的な名前・名前・説明・メニューでのアイコン・順番(0始まり)」と書く。
Pointerオブジェクト・シーン・テクスチャなど、様々な型を格納できる。
type=bpy.types.Object のように、なに用なのかを指定する。

translation_dict - 翻訳

BKTEMP_translation_dict = {
	"en_US": {
	},
	"ja_JP": {
		("*", "hoge"):
		"ほげ",
	}
}


# 翻訳の登録
bpy.app.translations.register(__name__, BKTEMP_translation_dict)

#翻訳の登録解除
bpy.app.translations.unregister(__name__)

「ほげ」部分に翻訳結果の文字列と、「hoge」に対応する英名とを入れておく。
英名が完全一致していないといけないので注意。
登録と登録解除を、それぞれregisterとunregisterに入れておく。

class - クラス

classes = (
BKTEMP_MT_AddonPreferences,
BKTEMP_OT_SimpleOperator,
BKTEMP_Props,
BKTEMP_PT_Panel,
)

アドオンで作ったクラスをclassesにまとめておく。
後述のregister()関数内で、ここでまとめたクラスをbpy.utils.register_class()を使ってまとめて登録する。

クラス名は、[アドオンの名前_XX_クラスの内容名] とする。
ここのアドオンの名前は、大文字にする。bl_info内のアドオン名と完全一致である必要はない。
真ん中のXXは、下記のようにする。

文字内容
MTメニュー
OTオペレーター
PTパネル
ULリスト

register - 登録

def register():
	################################################
	#des クラスの登録。最低限これがあればよい。
	for cls in classes:
		bpy.utils.register_class(cls)

	################################################
	#des propertyの登録。"bktemp"の部分をアドオン固有のものに変えること
	bpy.types.Scene.bktemp = PointerProperty(type=BKTEMP_Props)

	################################################
	#des category変更する関数の実行。起動時にこれを実行し、デフォルトから変更されている場合は、登録するカテゴリを変える
	update_panel(None, bpy.context)
	bpy.app.translations.register(__name__, BKTEMP_translation_dict) # 辞書

	################################################
	#des キーマップの登録
	wm = bpy.context.window_manager.keyconfigs.addon.keymaps.new
	km = wm(name = '3D View Generic', space_type = 'VIEW_3D')
	kmi = km.keymap_items.new("bktemp.simple_operator", 'Y', 'PRESS', alt=True)
	addon_keymaps.append((km, kmi))
	kmi.active =  True

アドオン有効時・アドオン有効状態でBlenderを起動した時に実行される。
登録したいクラスやキーマップ、アドオン有効時や起動時に実行したい関数をここに書き込む。

unregister - 登録解除

def unregister():
	################################################
	#des クラスの登録解除。最低限これがあればよい。
	for cls in classes:
		bpy.utils.unregister_class(cls)

	################################################
	bpy.app.translations.unregister(__name__) #des 辞書の削除

	################################################
	#des キーマップの削除
	for km, kmi in addon_keymaps:
		km.keymap_items.remove(kmi)
	addon_keymaps.clear()



if __name__ == "__main__":
	register()

アドオン無工事に実行される。
登録したものを解除する。

アドオンの制作依頼はこちら

-Python
-,

Copyright© 忘却まとめ , 2024 All Rights Reserved Powered by STINGER.