Blenderとか3DCGとか

スクリプト寄りです。

Blenderでマクロを作る (Edit, Object Mode用マクロの例)

PhotoShopでは「アクション」という、既存機能をまとめて一発で実行できるようにする仕組みがあります。

Mayaでもそういった仕組みは作れますが、既存機能の登録に関して少しスクリプトの知識が必要になります。

  そして、Blenderの場合はもっと面倒だったりします。

  ここでは、メッシュエディットモードでの例をもとに、既存機能をまとめて実行するための方法を紹介します。

<本例でのシチュエーション>

『エディットモードでエッジを一つ選択。それを起点に一列飛ばしでループエッジを除去したい。』

これを実現するために、以下を一つにまとめて実行できるようにします。

  1. エッジ選択をしている状態をスタートとして、リング選択をする (Select > Edge Rings)
  2. 選択を一つ飛ばしにする (Select > Checker Deselect)
  3. 選択状態をもとにループ選択へ変換する (Select > Edge Loops)
  4. 選択してあるものを除去する (Mesh > Delete > Dissolve Edges)

f:id:masayuki-osaka-blend:20180118220613p:plain

<大まかな流れ>

以下の順序でアドオンを作成します。

  1. メニューから各既存機能を実行し、それに対応するスクリプトでのコマンドを見つける
  2. 見つけたコマンドを使ってアドオンをつくる(テンプレート使用)
  3. そのアドオンを登録する

<下準備>

InfoパネルとText Editorパネルを表示

まず、Blenderのレイアウトを変更し、下図のようにします。
「ctrl+右矢印」を何回か押すと早いかと思います。
f:id:masayuki-osaka-blend:20180107112830p:plain

  • Infoパネルは、操作の履歴が表示されます。右クリックで行の選択の on/off ができます。
  • Text Editorパネルは、スクリプトやメモを書いて保存するために使います。
  • Python Consoleパネルは今回、出番がありません。


今回の例では、

  1. 3DViewで一つずつ既存機能を実行
  2. Infoパネル表示された実行コマンドをコピー
  3. Text Editorパネルへペースト

という流れでアドオンを作成します。

<既存機能を実行してコマンドを得る>

今回の例では、一つのエッジを選択したあと、マクロを実行するという想定です。
まず、エッジを一つ選択してください。
f:id:masayuki-osaka-blend:20180114192055p:plain

これから既存コマンドを一つずつ実行しますが、何のコマンドが実行されたかを判りやすくするため、Infoパネルを綺麗にします。Infoパネル上でショートカット「A」→「X」でこれまでのコマンド履歴が削除されます。
f:id:masayuki-osaka-blend:20180114193249p:plain

選択をリング状に変換するため、Select > Edge Ring を実行します。
EdgeRingを実行した途端、Infoパネルに一行追加される事に注目してください。
f:id:masayuki-osaka-blend:20180114194715p:plain

この増えた行が、Edge Ringsのコマンドです。引き続き

  • 選択を一つ飛ばしにする (Select > Checker Deselect)
  • 選択をもとにループ選択をする (Select > Edge Loops)
  • 選択してあるものを除去する (Mesh > Delete > Dissolve Edges)

を実行すると、下図のように合計4個のコマンドが表示されます。
f:id:masayuki-osaka-blend:20180114200352p:plain

このコマンドは後に使用するので、コピーしてメモ帳などに控えてください。
f:id:masayuki-osaka-blend:20180114201820p:plain

<アドオンの作成>

1、コードのペースト

アドオンを1から作るのは骨がおれるので、ほとんどの部分を流用します。
Text Editorで新規テキストを作成し、下のコードを張り付けてください。

※ペースト用コードを表示

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####


bl_info = {
    "name": "My Addon",             # addon's name
    "description": "This is ...",   # addon's description
    "category": "Mesh",             # addon's category
}


class My_macro:

    # 1 to register in the menu. Otherwise 0.
    in_menu = 1

    # name in the menu
    menu_name = "Menu Name"

    # tooltip
    tooltip = "This is my macro"

    # command name. Only lowercase, number, and "_" are available.
    program_name = "my_command"

    # If you register shortcut now, select from the below list.
    shortcut_key = "NONE"

    # If you don't register shortcut, here is meaningless.
    shortcut_shift = 1      # If you hold down the Shift, 1. Otherwise 0.
    shortcut_ctrl = 1       # If you hold down the Ctrl, 0. Otherwise 0.
    shortcut_alt = 1        # If you hold down the Alt, 0. Otherwise 0.


    @staticmethod
    def my_commands():
        '''Paste commands here.'''
        # Align the head of commands with this line.
        
        
        
        



'''Available characters can be used as shortcut
"NONE",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",

"SEMI_COLON", "PERIOD", "COMMA", "QUOTE", "ACCENT_GRAVE", "MINUS", "PLUS",
"SLASH", "BACK_SLASH", "EQUAL", "LEFT_BRACKET", "RIGHT_BRACKET",

"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
"F13", "F14", "F15", "F16", "F17", "F18", "F19",

"ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE",

"NUMPAD_0", "NUMPAD_1", "NUMPAD_2", "NUMPAD_3", "NUMPAD_4", "NUMPAD_5",
"NUMPAD_6", "NUMPAD_7", "NUMPAD_8", "NUMPAD_9",
"NUMPAD_PERIOD", "NUMPAD_SLASH", "NUMPAD_ASTERIX", "NUMPAD_MINUS",
"NUMPAD_ENTER", "NUMPAD_PLUS",

"ESC", "TAB", "RET", "SPACE", "LINE_FEED", "BACK_SPACE", "DEL", "PAUSE",
"INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END",

"LEFT_ARROW", "DOWN_ARROW", "RIGHT_ARROW", "UP_ARROW",

"LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE",
"BUTTON6MOUSE", "BUTTON7MOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE",
"WHEELINMOUSE", "WHEELOUTMOUSE", "MOUSEROTATE", "MOUSEMOVE",
"INBETWEEN_MOUSEMOVE", "ACTIONMOUSE", "SELECTMOUSE",

"TRACKPADPAN", "TRACKPADZOOM", "PEN", "ERASER", "EVT_TWEAK_L", "EVT_TWEAK_M",
"EVT_TWEAK_R", "EVT_TWEAK_A", "EVT_TWEAK_S", "LEFT_CTRL", "LEFT_ALT",
"LEFT_SHIFT", "RIGHT_ALT", "RIGHT_CTRL", "RIGHT_SHIFT", "OSKEY", "GRLESS",
"MEDIA_PLAY", "MEDIA_STOP", "MEDIA_FIRST", "MEDIA_LAST", "TEXTINPUT",
"WINDOW_DEACTIVATE", "TIMER", "TIMER0", "TIMER1", "TIMER2", "TIMER_JOBS",
"TIMER_AUTOSAVE", "TIMER_REPORT", "TIMERREGION", "NDOF_MOTION",
"NDOF_BUTTON_MENU", "NDOF_BUTTON_FIT", "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM",
"NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT", "NDOF_BUTTON_FRONT",
"NDOF_BUTTON_BACK", "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2",
"NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW",
"NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CCW",
"NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT",
"NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", "NDOF_BUTTON_ESC", "NDOF_BUTTON_ALT",
"NDOF_BUTTON_SHIFT", "NDOF_BUTTON_CTRL", "NDOF_BUTTON_1", "NDOF_BUTTON_2",
"NDOF_BUTTON_3", "NDOF_BUTTON_4", "NDOF_BUTTON_5", "NDOF_BUTTON_6",
"NDOF_BUTTON_7", "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10",
"NDOF_BUTTON_A", "NDOF_BUTTON_B", "NDOF_BUTTON_C"
'''


# Don't edit under this line.

import bpy


class MyTool(bpy.types.Operator):

    __doc__ = My_macro.tooltip

    bl_idname = "mesh.{}".format(My_macro.program_name)
    bl_label = My_macro.menu_name
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        My_macro.my_commands()
        return {'FINISHED'}


# store keymaps here to access after registration
addon_keymaps = []


def menu_func(self, context):
    self.layout.operator(MyTool.bl_idname)


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

    # register menu
    if My_macro.in_menu:
        bpy.types.VIEW3D_MT_edit_mesh.append(menu_func)

    if My_macro.shortcut_key != 'NONE':
        # handle the keymap
        wm = bpy.context.window_manager
        km = wm.keyconfigs.addon.keymaps.new(name='Mesh', space_type='EMPTY')
        kmi = km.keymap_items.new(MyTool.bl_idname,
                                  My_macro.shortcut_key,
                                  'PRESS',
                                  ctrl=My_macro.shortcut_ctrl,
                                  shift=My_macro.shortcut_shift,
                                  alt=My_macro.shortcut_alt
                                  )
        addon_keymaps.append(km)


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

    # unregister menu
    if My_macro.in_menu:
        bpy.types.VIEW3D_MT_edit_mesh.remove(menu_func)

    # handle the keymap
    wm = bpy.context.window_manager
    for km in addon_keymaps:
        wm.keyconfigs.addon.keymaps.remove(km)
    # clear the list
    del addon_keymaps[:]


if __name__ == "__main__":
    register()


f:id:masayuki-osaka-blend:20180115011931p:plain
(文字の色が黒一色であったり、行番号が非表示の時は、上図のボタンで修正すると見易くなります)

続いて54行目以降に、先ほどメモ帳などへ控えたコマンドを張り付けます。
このとき、行の頭を53行目に揃えてください。

f:id:masayuki-osaka-blend:20180114213808p:plain

2、アドオン名などの変更

21行目から47行目にかけて、アドオンに関する情報を直接書き替えます。
(以下のアドオン登録後の画像は、まだ手元での確認はできません。後の項で出来るようになります)

<アドオンを見つけ易くするための情報>
  • 21行目 "name": アドオンの名前
  • 22行目 "description": アドオンの説明
  • 23行目 "category": アドオンを探す際のカテゴリ

f:id:masayuki-osaka-blend:20180115005326p:plain

<登録するメニューの内容>
  • 30行目 in_menu: メニューに追加するか(しないなら数字の0)
  • 33行目 menu_name: メニューに表示する際の名前
  • 36行目 tooltip: メニュー上での説明書き

f:id:masayuki-osaka-blend:20180115005512p:plain

<プログラム上でのコマンド名>(半角英数字と"_"のみで記入)
  • 39行目 program_name: Blenderがこのプログラムを実行するためのコマンド名
    (あまりシンプルな名前では既存のコマンドとかぶってしまう場合があります)

f:id:masayuki-osaka-blend:20180115005544p:plain

<ショートカット情報>

メニューを右クリックしてのショートカット設定など、後で登録するなら変更不要です。
アドオンの読み込み時に、自動的にショートカットも決まるようにしたい場合に変更してください。

  • 42行目 shortcut_key: 押すボタン(Shift, Ctrl, Altは除く)
      (60行目前後から書いてある、沢山の候補から選択して記入)
  • 45行目 shortcut_shift: Shiftキーと一緒に押すか(押さないなら数字の0)
  • 46行目 shortcut_ctrl: Ctrlキーと一緒に押すか(押さないなら数字の0)
  • 47行目 shortcut_alt: Altキーと一緒に押すか(押さないなら数字の0)

f:id:masayuki-osaka-blend:20180115010729p:plain
この図の例では、shift+Ctrl+右マウスクリックで実行されます。

3、コードを保存

アドオンとしてBlenderに読み込ませるために、これまで編集したスクリプトを保存します。
Text Editor のメニューから Text > Save As で、任意の場所に保存してください。

拡張子は .py とする必要があります。

C:\Program Files\ 以下など、管理者権限が必要な場所への保存はエラーになることがあります。
デスクトップなどに保存することをお勧めします。

f:id:masayuki-osaka-blend:20180118090453p:plain
今回の例では、ファイル名を「mesh_dissolve_skip_loops.py」としました。

4、アドオンとして登録

保存したファイルを以下のフォルダに移動させてください。

<Blenderのフォルダ>\<バージョン番号>\scripts\addons

f:id:masayuki-osaka-blend:20180118091612p:plain

これでアドオンとして読み込めます。Blender User PreferencesでRefresh ボタンを押すか、Blenderを再起動すると一覧に表示されますので、作成したアドオンを有効にしてください。
ここでアドオンが見つからない場合、スクリプトの記述や保存場所の間違いが考えられます。

f:id:masayuki-osaka-blend:20180118093218p:plain

<アドオンの確認>

エッジ選択をして、実際にアドオンが機能するかを確認します。

  • コード30行目の in_menu を1にしていた場合、Meshメニューの一番上に追加されます。
  • ショートカットを登録した場合は、そちらも機能しているかを確認してください。

f:id:masayuki-osaka-blend:20180118221633p:plain

<その他>

以上がひと通りの説明になります。以下は補足です。

日本語

 コードの21行目から47行目にかけて書き換えましたが、下図の5か所は日本語でも構いません。ただし、Text Editorは日本語で書き込むことが出来ないため、メモ帳などからコピー&ペーストする必要があります。
 また、インターフェースを日本語にしていないと正しく表示されません。
f:id:masayuki-osaka-blend:20180118225611p:plain

オブジェクトモード版

 今回の例ではエディットからスタートするアドオンを説明しましたが、オブジェクトモードを起点とする場合は以下のコードをお使いください。

※オブジェクトモード起点のコードを表示

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####


bl_info = {
    "name": "My Addon",             # addon's name
    "description": "This is ...",   # addon's description
    "category": "Object",             # addon's category
}


class My_macro:

    # 1 to register in the menu. Otherwise 0.
    in_menu = 1

    # name in the menu
    menu_name = "Menu Name"

    # tooltip
    tooltip = "This is my macro"

    # command name. Only lowercase, number, and "_" are available.
    program_name = "my_command"

    # If you register shortcut now, select from the below list.
    shortcut_key = "NONE"

    # If you don't register shortcut, here is meaningless.
    shortcut_shift = 1      # If you hold down the Shift, 1. Otherwise 0.
    shortcut_ctrl = 1       # If you hold down the Ctrl, 0. Otherwise 0.
    shortcut_alt = 1        # If you hold down the Alt, 0. Otherwise 0.


    @staticmethod
    def my_commands():
        '''Paste commands here.'''
        # Align the head of commands with this line.
        
        
        
        



'''Available characters can be used as shortcut
"NONE",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",

"SEMI_COLON", "PERIOD", "COMMA", "QUOTE", "ACCENT_GRAVE", "MINUS", "PLUS",
"SLASH", "BACK_SLASH", "EQUAL", "LEFT_BRACKET", "RIGHT_BRACKET",

"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
"F13", "F14", "F15", "F16", "F17", "F18", "F19",

"ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE",

"NUMPAD_0", "NUMPAD_1", "NUMPAD_2", "NUMPAD_3", "NUMPAD_4", "NUMPAD_5",
"NUMPAD_6", "NUMPAD_7", "NUMPAD_8", "NUMPAD_9",
"NUMPAD_PERIOD", "NUMPAD_SLASH", "NUMPAD_ASTERIX", "NUMPAD_MINUS",
"NUMPAD_ENTER", "NUMPAD_PLUS",

"ESC", "TAB", "RET", "SPACE", "LINE_FEED", "BACK_SPACE", "DEL", "PAUSE",
"INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END",

"LEFT_ARROW", "DOWN_ARROW", "RIGHT_ARROW", "UP_ARROW",

"LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE",
"BUTTON6MOUSE", "BUTTON7MOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE",
"WHEELINMOUSE", "WHEELOUTMOUSE", "MOUSEROTATE", "MOUSEMOVE",
"INBETWEEN_MOUSEMOVE", "ACTIONMOUSE", "SELECTMOUSE",

"TRACKPADPAN", "TRACKPADZOOM", "PEN", "ERASER", "EVT_TWEAK_L", "EVT_TWEAK_M",
"EVT_TWEAK_R", "EVT_TWEAK_A", "EVT_TWEAK_S", "LEFT_CTRL", "LEFT_ALT",
"LEFT_SHIFT", "RIGHT_ALT", "RIGHT_CTRL", "RIGHT_SHIFT", "OSKEY", "GRLESS",
"MEDIA_PLAY", "MEDIA_STOP", "MEDIA_FIRST", "MEDIA_LAST", "TEXTINPUT",
"WINDOW_DEACTIVATE", "TIMER", "TIMER0", "TIMER1", "TIMER2", "TIMER_JOBS",
"TIMER_AUTOSAVE", "TIMER_REPORT", "TIMERREGION", "NDOF_MOTION",
"NDOF_BUTTON_MENU", "NDOF_BUTTON_FIT", "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM",
"NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT", "NDOF_BUTTON_FRONT",
"NDOF_BUTTON_BACK", "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2",
"NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW",
"NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CCW",
"NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT",
"NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", "NDOF_BUTTON_ESC", "NDOF_BUTTON_ALT",
"NDOF_BUTTON_SHIFT", "NDOF_BUTTON_CTRL", "NDOF_BUTTON_1", "NDOF_BUTTON_2",
"NDOF_BUTTON_3", "NDOF_BUTTON_4", "NDOF_BUTTON_5", "NDOF_BUTTON_6",
"NDOF_BUTTON_7", "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10",
"NDOF_BUTTON_A", "NDOF_BUTTON_B", "NDOF_BUTTON_C"
'''


# Don't edit under this line.

import bpy


class MyTool(bpy.types.Operator):

    __doc__ = My_macro.tooltip

    bl_idname = "object.{}".format(My_macro.program_name)
    bl_label = My_macro.menu_name
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        My_macro.my_commands()
        return {'FINISHED'}


# store keymaps here to access after registration
addon_keymaps = []


def menu_func(self, context):
    self.layout.operator(MyTool.bl_idname)


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

    # register menu
    if My_macro.in_menu:
        bpy.types.VIEW3D_MT_object.append(menu_func)

    if My_macro.shortcut_key != 'NONE':
        # handle the keymap
        wm = bpy.context.window_manager
        km = wm.keyconfigs.addon.keymaps.new(name='Object', space_type='EMPTY')
        kmi = km.keymap_items.new(MyTool.bl_idname,
                                  My_macro.shortcut_key,
                                  'PRESS',
                                  ctrl=My_macro.shortcut_ctrl,
                                  shift=My_macro.shortcut_shift,
                                  alt=My_macro.shortcut_alt
                                  )
        addon_keymaps.append(km)


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

    # unregister menu
    if My_macro.in_menu:
        bpy.types.VIEW3D_MT_object.remove(menu_func)

    # handle the keymap
    wm = bpy.context.window_manager
    for km in addon_keymaps:
        wm.keyconfigs.addon.keymaps.remove(km)
    # clear the list
    del addon_keymaps[:]


if __name__ == "__main__":
    register()

<謝辞>

 本記事は、稲村JIN氏のツイートから着想を得て記述致しました。マクロ作成を体験するにあたり、過不足ないテーマをご提供頂いたことを一方的ながら感謝申し上げます。
 氏の作品はライティングが特に魅力的で、それはマジシャンゆえなのだろうと推測します。私は「バックボーンは作品を底上げする」と信じておりますが、それは間違いでないと感じさせてくださる、そんなかたです。
 いろんな形で、いろんなかたが関わっていく。それぞれ個性を重んじて。素敵な事だと思います。

以上、謝辞に替えて本記事の締めとさせて頂きます。