「Dev:JA/2.5/Py/API/Overview」の版間の差分

提供: wiki
< Dev:JA‎ | 2.5
移動先: 案内検索
(クラスの統合)
 
(1版 をインポートしました)
 
(相違点なし)

2018年6月29日 (金) 04:41時点における最新版

in progress Inprogress25.jpg 5%

 

Python APIの全容

この資料は、pythonとblenderの連携方法を理解するためにあり、APIリファレンスやサンプルスクリプトを読んだだけでは分かりづらい機能性に関する記述もカバーしています。

BlenderでのPython

Blenderには、Blenderが起動してアクティブ状態を保つPythonインタープリタが組み込まれており、インターフェイスを設置したり、blenderの内部ツールを使ったりするためのスクリプトを動かしてくれます。

これは独特の環境で動くPythonですから、Blender上で機能するスクリプトを書くためのチュートリアルも独特のものになります。(This is a typical python environment so tutorials on how to write python scripts will work running the scripts in blender too. ) BlenderはPythonに、blenderのデータやクラスや統合関数をあてがう'bpy'モジュールを提供します。

Blenderのデータを操作するスクリプトは、この'bpy'モジュールをインポートする必要があります。 以下はオブジェクトの頂点を移動させる、簡単なスクリプトでの例です。

import bpy
bpy.data.objects["Cube"].data.vertices[0].co.x += 1.0

これはblenderの内部データを直接修正し、インタラクティブなコンソール上で動作し、3Dビューポート上で更新を確認することができます。(This modifies blenders internal data directly, running this in the interactive console you will see the 3D viewport update.

標準環境

自分でスクリプトを開発する際には、blenderがどのようにpythonの環境をセットアップするか理解していると役立つかもしれません。 blenderには沢山のPythonスクリプトがあらかじめバンドルされています。そういったスクリプトは、作者がツールを書くためにいくつかのAPIが使われているため、参考になるでしょう。 標準的なスクリプトの用法に、ユーザーインターフェイス、インポート/エクスポート、sceneの操作、自動化、独自ツールセットの定義づけやカスタマイズ、などがあります。

blenderはスタートアップ時にディレクトリからpythonモジュールを探しだし、インポートします。 現在それらのフォルダには以下のものがあります。

  • scripts/op - Operator
  • scripts/ui - ユーザインターフェイス
  • scripts/keyingsets - アニメーションキーフレームのセット

このディレクトリの正確な位置は、あなたのインストール方法にもよりますが、~/.blender/2.56/scripts や /usr/share/blender/2.56/scripts もしくは解凍したzipファイルのサブディレクトリにありえるでしょう。

スクリプトの読み込み

当たり前に思えますが、直接スクリプトを実行する場合と、モジュールとして読み込む場合の違いに気を配るのは重要なことです。 スクリプトがクラスを定義してblenderを拡張した場合、そのクラスはスクリプトの実行が終了した後も、存在し続けます。(Scripts which extend blender define classes that exist beyond the time when the script finishes executing)しかしクラスが機能し続けている間、再び(例えばクラスの登録を解除するために)そのクラスにアクセスするのは、モジュールとしてインポートした場合に比べ、難しくなります。

こうした理由から、スクリプトを直接実行するのは、blenderにクラスを登録して機能を拡張したりしない場合に限るのがよいでしょう。

スクリプトを直接実行する方法には、以下のようなものがあります。

  • テキストエディタに読み込んで実行する
  • インタラクティブコンソール上に打ち込む、もしくは貼り付ける
  • コマンドライン上から実行する。 eg: "blender --python /home/me/my_script.py"

モジュールとして実行する方法は……

  • 分かりやすい方法だと、テキストウィンドウかインタラクティブコンソールに 'import some_module' と命令する方法があります
  • "Register"オプションにチェックをした状態で、テキストブロックとして読み込むと、blendファイルと共に読み込まれます
  •  ~/.blender/2.56/scripts/op, ~/.blender/2.56/scripts/ui, のディレクトリにスクリプトをコピーしておくと、スタートアップ時にインポートされます

アドオン

いくつかの機能は、オプションとして管理できた方が良いでしょう。 スタートアップ時にスクリプトが読み込まれる際、blender自身のscripts/addonsディレクトリ内に存在するアドオンのうち、user preferencesで選択されているものも読み込まれます。

アドオンとビルトインされているpythonモジュールとの唯一の違いは、アドオンには名前や作者、カテゴリ、URL等のメタ情報をblenderが読み取るための変数、bl_infoが含まれている必要があるということです。

辞書配列bl_infoについての詳細はこちらをどうぞ Addons

クラスの統合

テキストエディタ上でpythonスクリプトを走らせるのは、テストには便利ですが、最終的にはビルトインされている他の機能の様にアクセス出来るツールを、blenderを拡張して作りたくなるでしょう。

blender python apiは以下への統合を許可しています。

これは意図的に制限されており、現時点では、メッシュモディファイアや、オブジェクトタイプ、シェーダノードの様な、発展的な機能はC/C++を使用する必要があります。

pythonをblenderに統合するのに、全てのタイプに共通する、blender独自の手法があります。 blenderのクラスのサブクラスを作ることです。 サブクラスは、blenderとのインターフェイスを実現するために、あらかじめ親クラス内で明示的に宣言されている関数や変数を含みます。

例:

import bpy
class SimpleOperator(bpy.types.Operator):
    bl_idname = "object.simple_operator"
    bl_label = "Tool Name"

    def execute(self, context):
        print("Hello World")
        return {'FINISHED'}

bpy.utils.register_class(SimpleOperator)

まず注意するのは、bpy.typesのメンバーのサブクラスを作成するということです。 これはblenderに統合される全てのクラスに共通したことで、これによって私達は、そのクラスがOperatorであってPanelでない、などと知ることが出来ます。

クラス内のプロパティはどちらも'bl_'の接頭辞で始まっています。 これはblenderのプロパティを、あなたが独自に付け加えたものと見分けるための慣習です。

次にexecute関数を見てみると、operator型のインスタンスと現在のコンテキストを取得しています。 共通の接頭辞は関数には使われません。

最後に、register関数が呼ばれます。これはクラスを取得して、blenderの中に取り込む関数です。(「クラス登録」を参照してください)

継承の評価ですが、blenderはどの種類の継承を使うかに、制限を課しません。 登録の確認には、親クラスの属性や関数を使用できます。

以下はmix-inクラスの例:

import bpy
class BaseOperator:
    def execute(self, context):
        print("Hello World BaseClass")
        return {'FINISHED'}

class SimpleOperator(bpy.types.Operator, BaseOperator):
    bl_idname = "object.simple_operator"
    bl_label = "Tool Name"

bpy.utils.register_class(SimpleOperator)

これらのクラスで__init__(self)関数を定義していないことに注意して下さい。 もし__init__()__del__()を定義して呼び出しても、これらクラスのインスタンスは、実行中しか存在していません。 ですから、例えばPanelでは、毎描画時に新しくインスタンスが生成されるため、パネルインスタンス内の変数へ格納できるのは稀ということになります。 その代わり、持続的に維持され、blenderが再スタートする際にはリストアされるような変数を持つべきです。Note: ModalなOperatirsは例外的に、インスタンスの変数をblenderが起動している間中維持し続けます。modal operatorのテンプレートを参照して下さい

ですから、一度blenderに登録されたクラスは、インスタンスの生成も関数の呼び出しも、blenderに任せることになります。 それどころか、ほとんどどのpython api を使ってスクリプト上からインスタンスを生成しようとしても、あなたの要求通りになることはありません。

Operatorを起動するには、Operator apiを通して呼び出せば良いのです。 eg:

import bpy
bpy.ops.object.simple_operator()

User interface classes are given a context in which to draw, buttons window, file header, toolbar etc. then they are drawn when that area is displayed so you never run them directly.

Registration

Module Registration

Blender modules loaded at startup require register() and unregister() functions. These are the _only_ functions which blender calls into your code, which is otherwise a regular python module.

A simple blender/python module can look like this:

import bpy

class SimpleOperator(bpy.types.Operator):
    """...see example above..."""

def register():
    bpy.utils.register_class(SimpleOperator)

def unregister():
    bpy.utils.unregister_class(SimpleOperator)    

if __name__ == "__main__":
    register()

These functions usually appear at the bottom of the script containing class registration sometimes adding menu items. You can also use them for internal purposes setting up data for your own tools but take care since register won't re-run when a new blend file is loaded.

The register/unregister calls are used so it's possible to toggle addons and reload scripts while blender runs. If the register calls were placed in the body of the script, they would be called on import, meaning there would be no distinction between importing a module or loading its data into blender.

This becomes problematic when a script imports classes from another making it difficult to manage which classes are being loaded and when.

The last 2 lines are only for testing...

if __name__ == "__main__":
    register()

This allows the script to be run directly in the text editor to test changes. This register() call won't run when the script is imported since __name__ is the module name.

Class Registration

Registering a class with blender results in the class definition being loaded into blender, where it becomes available alongside existing functionality.

Once loaded you can access this class from bpy.types, using the bl_idname rather then the classes original name.

When loading a class blender performs sanity checks making sure all required properties and functions are found, that properties have the correct type, functions have the right number of arguments.

Mostly you will not need to be concerned with this but if there is a problem with the class definition it will be raised here:

Changing the function arguments 'def execute(self, context, spam)', will raise an exception:

ValueError: expected Operator, SimpleOperator class "execute" function to have 2 args, found 3

Changing 'bl_idname = 1' will raise.

TypeError: validating class error: Operator.bl_idname expected a string type, not int

Multiple-Classes

Loading classes into blender is described above, for simple cases calling bpy.utils.register_class(SomeClass) is sufficient, but when there are many classes or a packages submodule has its own classes it can be tedious to collect them all for registration.

To solve this blender has a function bpy.utils.register_class(module) and bpy.utils.unregister_class(module).

A script which defines many of its own operators, panels menus etc. you only need to write...

def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

Internally blender collects subclasses on registrable types, storing them by the module in which they are defined. By passing the module name to bpy.utils.register_module() blender can register all classes created by this module and its submodules.

Inter-Classes-Dependencies

When customizing blender you may want to group your own settings together, after all they will likely have to co-exist with other scripts, to group these properties classes need to be defined, for groups within groups or collections within groups you can find yourself having to deal with order of registration/unregistration.

Custom properties groups are themselves classes which need to be registered.

Say you want to store material settings for a custom engine.

# Create new property
# bpy.data.materials[0].my_custom_props.my_float
import bpy

class MyMaterialProps(bpy.types.PropertyGroup):
    my_float = bpy.props.FloatProperty()

def register():
    bpy.utils.register_class(MyMaterialProps)
    bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialProps)

def unregister():
    del bpy.types.Material.my_custom_props
    bpy.utils.unregister_class(MyMaterialProps)

if __name__ == '__main__':
    register()

Notice that the class must be registered before being used in a property, failing to do so will raise an error like...

ValueError: bpy_struct "Material" registration error: my_custom_props could not register


# Create new property group with a sub property
# bpy.data.materials[0].my_custom_props.sub_group.my_float
import bpy

class MyMaterialSubProps(bpy.types.PropertyGroup):
    my_float = bpy.props.FloatProperty()

class MyMaterialGroupProps(bpy.types.PropertyGroup):
    sub_group = bpy.props.PointerProperty(type=MyMaterialSubProps)

def register():
    bpy.utils.register_class(MyMaterialSubProps)
    bpy.utils.register_class(MyMaterialGroupProps)
    bpy.types.Material.my_custom_props = bpy.props.PointerProperty(type=MyMaterialGroupProps)

def unregister():
    del bpy.types.Material.my_custom_props
    bpy.utils.unregister_class(MyMaterialGroupProps)
    bpy.utils.unregister_class(MyMaterialSubProps)

if __name__ == '__main__':
    register()

Notice that the lower most class needs to be registered first and that unregister() is a mirror of register().

Manipulating-Classes

Properties can be added and removed as blender runs, normally happens on register or unregister but for some special cases it may be useful to modify types as the script runs.

For example:

# add a new property to an existing type
bpy.types.Object.my_float = bpy.props.FloatProperty()
# remove
del bpy.types.Object.my_float

This works just as well for PropertyGroup subclasses you define yourself.

class MyPropGroup(bpy.types.PropertyGroup):
    pass
MyPropGroup.my_float = bpy.props.FloatProperty()

...this is equivalent to...

class MyPropGroup(bpy.types.PropertyGroup):
    my_float = bpy.props.FloatProperty()

Dynamic Defined-Classes (Advanced)

In some cases the specifier for data may not be in blender, renderman shader definitions for example and it may be useful to define types and remove them on the fly.

for i in range(10):
    idname = "object.operator_%d" % i
    def func(self, context):
        print("Hello World", self)
        return {'FINISHED'}
    opclass = type("DynOp%d" % i, (bpy.types.Operator, ), {"bl_idname": idname,
                                                            "bl_label": "Test",
                                                            "execute": func})
    bpy.utils.register_class(opclass)

Notice that type() is called rather then defining a class. This is an alternative syntax for the same purpose but is better suited to constructing classes on the fly.

Calling these operators...

>>> bpy.ops.object.operator_1()
Hello World <bpy_struct, OBJECT_OT_operator_1("OBJECT_OT_operator_1")>
{'FINISHED'}

>>> bpy.ops.object.operator_2()
Hello World <bpy_struct, OBJECT_OT_operator_2("OBJECT_OT_operator_2")>
{'FINISHED'}