はじめてのBlenderアドオン開発 (Blender 2.7版)

Last Update: 2019.4.2

2-4. ツール・シェルフのオプションを活用する②

本節では、オブジェクトを複製するアドオンのサンプルを紹介し、前節で解説したツール・シェルフについてより深く理解します。

作成するアドオンの仕様

本節で作成するアドオンの仕様を以下に示します。

アドオンを作成する

1-5節 を参考にして以下のソースコードを入力し、ファイル名 sample_2_4.py で保存してください。

import bpy
from bpy.props import FloatVectorProperty, EnumProperty
from mathutils import Vector


bl_info = {
    "name": "サンプル2-4: オブジェクトを複製するアドオン",
    "author": "Nutti",
    "version": (2, 0),
    "blender": (2, 75, 0),
    "location": "3Dビュー > オブジェクト",
    "description": "選択したオブジェクトを複製するアドオン",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}


# EnumPropertyで表示したい項目リストを作成する関数
def location_list_fn(scene, context):
    items = [
        ('3D_CURSOR', "3Dカーソル", "3Dカーソル上に配置します"),
        ('ORIGIN', "原点", "原点に配置します")
    ]
    items.extend([
        ('OBJ_' + o.name, o.name, "オブジェクトに配置します")
        for o in bpy.data.objects
    ])

    return items


# 選択したオブジェクトを複製するアドオン
class ReplicateObject(bpy.types.Operator):

    bl_idname = "object.replicate_object"
    bl_label = "選択オブジェクトの複製"
    bl_description = "選択中のオブジェクトを複製します"
    bl_options = {'REGISTER', 'UNDO'}

    location = EnumProperty(
        name="配置位置",
        description="複製したオブジェクトの配置位置",
        items=location_list_fn
    )
    scale = FloatVectorProperty(
        name="拡大率",
        description="複製したオブジェクトの拡大率を設定します",
        default=(1.0, 1.0, 1.0),
        subtype='XYZ',
        unit='LENGTH'
    )
    rotation = FloatVectorProperty(
        name="回転角度",
        description="複製したオブジェクトの回転角度を設定します",
        default=(0.0, 0.0, 0.0),
        subtype='AXISANGLE',
        unit='ROTATION'
    )
    offset = FloatVectorProperty(
        name="オフセット",
        description="複製したオブジェクトの配置位置からのオフセットを設定します",
        default=(0.0, 0.0, 0.0),
        subtype='TRANSLATION',
        unit='LENGTH'
    )

    def execute(self, context):
        # bpy.ops.object.duplicate()実行後に複製オブジェクトが選択されるため、
        # 選択中のオブジェクトを保存
        # context.active_object.name:選択中のオブジェクトの名前
        src_obj_name = context.active_object.name
        # bpy.ops.object.duplicate():オブジェクトの複製
        bpy.ops.object.duplicate()
        active_obj = context.active_object

        # 複製したオブジェクトを配置位置に移動
        # context.active_object.location:選択中のオブジェクトの位置
        if self.location == '3D_CURSOR':
            # context.scene.cursor_location:3Dカーソルの位置
            # Shallow copyを避けるため、copy()によるDeep copyを実行
            active_obj.location = context.scene.cursor_location.copy()
        elif self.location == 'ORIGIN':
            active_obj.location = Vector((0.0, 0.0, 0.0))
        elif self.location[0:4] == 'OBJ_':
            # bpy.data.objects:配置されているオブジェクトのリスト
            objs = bpy.data.objects
            active_obj.location = objs[self.location[4:]].location.copy()

        # 複製したオブジェクトの拡大率を設定
        # context.active_object.scale:選択中のオブジェクトの拡大率
        active_obj.scale.x = active_obj.scale.x * self.scale[0]
        active_obj.scale.y = active_obj.scale.y * self.scale[1]
        active_obj.scale.z = active_obj.scale.z * self.scale[2]

        # 複製したオブジェクトの回転角度を設定
        # context.active_object.rotation_euler:選択中のオブジェクトの回転角度
        #                                      (ラジアン)
        rot_euler = active_obj.rotation_euler
        active_obj.rotation_euler.x = rot_euler.x + self.rotation[0]
        active_obj.rotation_euler.y = rot_euler.y + self.rotation[1]
        active_obj.rotation_euler.z = rot_euler.z + self.rotation[2]

        # 複製したオブジェクトの最終位置を設定
        active_obj.location = active_obj.location + Vector(self.offset)

        self.report({'INFO'}, "サンプル2-4: 「%s」を複製しました。" % (src_obj_name))
        print("サンプル2-4: オペレーション「%s」が実行されました。" % (self.bl_idname))

        return {'FINISHED'}


def menu_fn(self, context):
    self.layout.separator()
    self.layout.operator(ReplicateObject.bl_idname)


def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_object.append(menu_fn)
    print("サンプル2-4: アドオン「サンプル2-4」が有効化されました。")


def unregister():
    bpy.types.VIEW3D_MT_object.remove(menu_fn)
    bpy.utils.unregister_module(__name__)
    print("サンプル2-4: アドオン「サンプル2-4」が無効化されました。")


if __name__ == "__main__":
    register()

アドオンを使用する

アドオンを有効化する

1-5節 を参考にして、作成したアドオンを有効化すると、コンソールウィンドウに以下の文字列が出力されます。

サンプル2-4: アドオン「サンプル2-4」が有効化されました。

アドオン有効化後、[3Dビュー] エリアのメニューに [オブジェクト] > [選択オブジェクトの複製] が追加されていることを確認します。

アドオンの機能を使用する

以下の手順に従って、作成したアドオンの機能を使ってみます。

1 Blender起動直後に生成される [Cube] を選択した状態で追加されたメニューを実行すると、選択したCubeが複製されます。

また、スクリプト実行ログに以下のメッセージが表示されます。
サンプル2-4: 「Cube」を複製しました。
2 [3Dビュー] エリアのツール・シェルフに表示されたオプションの値を変更すると、変更した値に応じて複製されたオブジェクトの形が変化します。

本節のサンプルでは、以下に示すオプションが利用可能です。

オプション名 オプション指定による動作への影響
配置位置 3Dカーソル : 3Dカーソルの位置に複製オブジェクトを配置します
原点 : [3Dビュー] エリアの原点に複製オブジェクトを配置します
オブジェクト名 : 選択したオブジェクトの中心位置に複製オブジェクトを配置します
拡大率 複製したオブジェクトについて、複製元のオブジェクトに対する拡大率を設定します
回転角度 複製したオブジェクトについて、複製元オブジェクトからの回転角度の差分をオイラー角で設定します
オフセット 複製したオブジェクトについて、配置位置からのオフセット位置を指定します

アドオンを無効化する

1-5節 を参考にしてアドオンを無効化すると、コンソールウィンドウに以下の文字列が出力されます。

サンプル2-4: アドオン「サンプル2-4」が無効化されました。

ソースコードの解説

本節のサンプルは、2-3節 で紹介したサンプルを改造したものです。 ここでは、 2-3節 で解説していない部分について解説します。 なお、新しく登場するBlender APIの説明は、ソースコードのコメントとして記載しています。

セレクトボックスの追加

EnumProperty クラスを用いると、ユーザがセレクトボックスにより値を選択することが可能なUIを作成することできます。 EnumProperty クラスを用いてセレクトボックスに選択項目を追加するためには、EnumProperty クラス作成時の引数 items に選択項目のリストを渡す必要があります。

以下に、選択項目に [3Dカーソル] と [原点] を追加するコードを示します。

location_list = [
    ('3D_CURSOR', "3Dカーソル", "3Dカーソル上に配置します"),
    ('ORIGIN', "原点", "原点に配置します")]

location = EnumProperty(
    name = "配置位置",
    description = "複製したオブジェクトの配置位置",
    items = location_list
)

items に以下の要素からなるタプルの配列を渡しています。

要素 要素の意味
第1要素 識別子(項目選択時に変数 items に設定される値)
第2要素 セレクトボックスに表示される項目名
第3要素 セレクトボックスに表示される項目の説明

これでセレクトボックスを作ることができましたが、[3Dビュー] エリアに配置されているオブジェクトの位置に複製したオブジェクトを配置するための項目が足りません。

基本的には [3Dビュー] エリア上に存在するオブジェクト名一覧を項目に追加すれば良さそうですが、存在するオブジェクトは処理の実行状況に応じて変化するため、items に渡す項目リストを動的に生成する必要があります。 例えば、[3Dビュー] エリアのメニューである、[オブジェクト] > [選択オブジェクトの複製] を実行した後に、[オブジェクト] > [選択オブジェクトの複製] を再度実行した場合、最初に複製したオブジェクトを複製オブジェクトの配置先として選べる必要があります。 このため、本節のサンプルでは以下のようにして EnumProperty クラスにより作られるセレクトボックスの選択項目を動的に追加する処理を記載しています。

# EnumPropertyで表示したい項目リストを作成する関数
def location_list_fn(scene, context):
    items = [
        ('3D_CURSOR', "3Dカーソル", "3Dカーソル上に配置します"),
        ('ORIGIN', "原点", "原点に配置します")
    ]
    items.extend([
        ('OBJ_' + o.name, o.name, "オブジェクトに配置します")
        for o in bpy.data.objects
    ])

    return items
    location = EnumProperty(
        name="配置位置",
        description="複製したオブジェクトの配置位置",
        items=location_list_fn
    )

EnumProperty クラスの items に項目リストを渡す代わりに、項目リストを返す location_list_fn 関数を渡しています。

location_list_fn 関数は、3Dカーソルと原点についてセレクトボックスの選択項目リストを作成した後、[3Dビュー] 上に置かれている全オブジェクトについてセレクトボックスの選択項目を作成してリストに追加しています。 そして最後に、作成したリストを返して関数が復帰します。

このように、セレクトボックスの選択項目リストを返す関数を EnumProperty クラスの items に指定することで、選択項目を動的に追加することができます。

FloatVectorPropertyの引数subtypeとunit

本節のサンプルでは、配置位置の他にも拡大率・回転角度・配置位置からのオフセットを、ツール・シェルフから指定できます。 これを実現するため、FloatVectorProperty クラスのインスタンス化時に、目的に沿った値に応じて引数 subtype を指定しています。 subtype に指定可能な値とUIの例を以下に示します。

値の説明 UI例
NONE 3要素から構成されるプロパティグループ
COLOR カラーパレット(RGBの3要素から構成されるプロパティグループ)
TRANSLATION X軸、Y軸、Z軸の3要素から構成されるプロパティグループ(単位はcm、mなど)
VELOCITY X軸、Y軸、Z軸の3要素から構成されるプロパティグループ(単位はm/s)
ACCELERATION X軸、Y軸、Z軸の3要素から構成されるプロパティグループ(単位はm/s2
EULER X軸、Y軸、Z軸の3要素から構成されるプロパティグループ(単位は°)
QUATERNION 3要素(W、X、Y)から構成されるプロパティグループ
AXISANGLE 3要素(W、X、Y)から構成されるプロパティグループ(単位は°)
XYZ X軸、Y軸、Z軸の3要素から構成されるプロパティグループ

また引数 unit を指定すると、以下のような単位をUIに表示させることができます。

値の説明 UI上での表示単位
(表示単位を度数表記・メートル法にした場合)
NONE 引数 subtypeに指定した値に応じて表示される単位が決定
LENGTH 長さ cm
AREA 面積 cm2、m2など
VOLUME 長さ cm3、m3など
ROTATION 角度 °
TIME 時間 (単位なし)
VELOCITY 速度 m/s
ACCELERATION 加速度 m/s2

まとめ

2-3節 で紹介したツール・シェルフのプロパティを用いて、アドオンを作成しました。

ツール・シェルフのプロパティを用いると、直前に行った操作に対してユーザがより細かい調整を行った上で操作を再実行することができます。 ただし特定の機能に対してプロパティを追加しすぎると、指示できる項目が多すぎてかえってわかりづらくなるという問題もありますので、本当に必要な項目かどうかを見極めてプロパティを追加していきましょう。

ポイント