Dec 21, 2014

Simple Blender Add-on Template

If you are like most Blender users and only infrequently code add-ons in Blender, then you may find that after a break from coding, what you learnt about the process previously is mostly forgotten. You have to re-learn things each time.

To help make things easier, I've created an add-on template that can be used as a starting point for most of the relatively simple add-ons a Blender user might want to create.

The template is thoroughly researched and commented, with just enough information packed in to make a return to coding an add-on quite painless. Each time you want to create a new add-on, read the comments accompanying the code carefully, change what needs to be changed, and work on your code. In addition, key parts of the add-on feature links to relevant API documentation online.

When creating this template, I decided to include the following:

  • All code that's required to register the Add-on in the Preferences
  • An Operator class definition, because operating on objects is what most user-created add-ons do.
  • Menu item for the operator, so the user is not limited to searching for it by pressing the Spacebar.
  • Keyboard shortcut for the operator. This means the shortcut is defined in the separate add-on keymap that Blender provides, not the main keymap you see in the Preferences.
  • Optional definitions of operator properties. This may be necessary if user input of operator parameters is required. The neat thing about it is that Blender automatically creates the interface for the parameters in Toolshelf, negating the need for a Panel class definition.

Before you start using the template, let's quickly go over each part of it. For the sake of presentation, the comments and links have been removed, but they are there in the full script.

bl_info = {
    "name": "Add-on Name",
    "author": "Your Name",
    "version": (0,1),
    "blender": (2, 72, 0),
    "location": "View3D > Object",
    "description": "Add-on description for Preferences panel",
    "category": "Object"}
The bl_info block is required for every add-on. It is slightly confusing, in that it serves a dual purpose: providing information to the user in the add-on panel in Preferences, and setting the minimum Blender version required for it to work. I've set the "blender" value to (2, 72, 0), the version I'm using at the moment of writing. Feel free to change it according to requirements, but note the correct format - (2, 7, 2) would be incorrect. As for the last number, it exists for Blender developers' own needs and should always be zero.

Note also that the "location" value is advisory only, your add-on will not stop functioning if you mis-spell it or don't get the location exactly right. The "category", however, should be accurate. You can see available categories in the add-on pane in Preferences.

import bpy
#import mathutils
It may seem strange that we have to import Blender's bpy Python module, but that's actually standard Python. If it isn't in Python itself, you need to import it.

As a convenience, the mathutils import is underneath, commented out. Uncomment it if you're going to be doing vector or other advanced math.

#def MyFunction(context, boolArg, intArg, floatvecArg):
def MyFunction(context):
  print("Hello World")
The MyFunction() definition is where your own code will go. I have defined this function outside of the operator class definition below it, so as to clearly separate user-created code from the code required to set up the add-on. You will know what to do when your needs and skills outgrow this construction.

Note that you have the choice of another, commented-out function definition for those cases when user input is required as the operator is executed. Obviously, you can't have two definitions for the same function, one of them will always have to be commented out or deleted.

class MyOperator(bpy.types.Operator):
  '''Tooltip for MyOperator'''
  bl_idname = "object.my_operator"
  bl_label = "My Operator"
  bl_options = {"REGISTER", "UNDO"}
You might say that the MyOperator() class definition is the heart of the add-on. This is where the actual command you'll be using is set up, and your add-on can define more than one such command. In fairly simple add-ons, we only define one, as is the case here.

The operator definition must be set up in a certain way, as you can see. The tooltip text string and bl_label are self-explanatory, but bl_idname follows a certain convention. If you'll be operating on objects, then the first part before the dot should be "object". In other cases, it might get tricky. In Blender's Python Console window, enter bpy.ops. and use autocompletion (Ctrl-Spacebar) to find the right operator type for your own definition. On the other hand, bl_options can usually be left as is.

#  my_bool = bpy.props.BoolProperty(name="True or False", default=1, description="Boolean")
#  my_int = bpy.props.IntProperty(name="Enter Number:", default=1, description="Number", min=1, max=10000, subtype="NONE")
#  my_floatvec = bpy.props.FloatVectorProperty(name="XYZ Vector:", default=(0.0,0.0,0.0), min=0, max=10000, description="XYZ Vector", subtype="NONE")
Three commented-out operator properties are provided as a convenience if they are needed. If you use them in your code, you will also need to use the alternative MyFunction() definition as explained above, and the alternative call to it in execute() as explained below.

  @classmethod
  def poll(cls, context):
    if context.object != None:
      return context.object.select
    return False
The poll() method of the operator definition isn't strictly necessary, but it's good to have as it performs a hassle-free context check before the operator will execute. The @classmethod decorator is standard Python (it's telling us poll() is a class method, doh).

  def execute(self, context):
    MyFunction(context)
#    MyFunction(context, self.my_bool, self.my_int, self.my_floatvec)
    return {'FINISHED'}
As indicated earlier, you have the choice of calling MyFunction() with or without user input, default being without.

  def invoke(self, context, event):
    return self.execute(context)
The difference between execute() and invoke() is that when alone, execute() will only execute once, but if invoke() is present, execute() will run as often as the user changes operator parameters. This means that in the code as presented, invoke() isn't needed because we're not using operator parameters. It's just here because it's harmless and will be needed when parameters are used.

In addition, notice that invoke() has an event parameter, allowing your code to listen for mouse and other events. More info in the documentation link provided.

def menu_draw(self, context):
#  self.layout.operator_context = 'INVOKE_DEFAULT'
  self.layout.operator(MyOperator.bl_idname, "My Operator")

addon_keymaps = []
The menu_draw() definition is as simple as it gets, defining a menu item that will later be placed in a menu. Notice the commented-out first line, where operator context is set to 'INVOKE DEFAULT'. This isn't needed in the present code, but it might be needed if operator parameters are used. If that is the case and you're getting context errors, try uncommenting this line. That will ensure that invoke() rather than execute() is run first.

def register():
  bpy.utils.register_module(__name__)
  bpy.types.VIEW3D_MT_object.append(menu_draw)
In this standard block of code that is required for loading of the add-on into Blender, the second line appends the menu item we have defined to a menu. This line needs to be edited if you want to append to a menu other than the Object menu in the 3D View. Correct menu identifiers are displayed in the tooltip when you hover your mouse over a menu (provided you haven't disabled this feature in Preferences).

  wm = bpy.context.window_manager
  km = wm.keyconfigs.addon.keymaps.new(name='3D View', space_type='VIEW_3D')
  kmi = km.keymap_items.new(MyOperator.bl_idname, 'A', 'PRESS', shift=True, ctrl=True, alt=True)
  addon_keymaps.append((km, kmi))
This second part of the register() definition sets up a keyboard shortcut for our operator. In the second line, set the name parameter to the name of the group of keys where the key should go in the Input pane of Preferences. Set space_type to the correct enumerator (see link for details). In the next line Shift-Alt-Ctrl-A is set as shortcut; of course you'll want to change that.

def unregister():
  bpy.types.VIEW3D_MT_object.remove(menu_draw)
  bpy.utils.unregister_module(__name__)

  for km, kmi in addon_keymaps:
    km.keymap_items.remove(kmi)
  addon_keymaps.clear()
The first two lines of unregister() must mirror those of register(). The rest removes the keymap we have defined.

if __name__ == '__main__':
  register()
This last part is needed so we can use this code for testing in Blender's Text window. Copy and paste all of the code and press Run Script. If you do it multiple times, you will get multiple menu items, no harm done. You can always press F8 to reload add-ons for a fresh start.

And there you have it. You're now ready to copy and paste the code into your own file. One last thing, in case you ever want to distribute your add-on as GPL open-source code, I've added the standard license blurb at the top.

Note also that spaces are used for indentation. You may want to search-replace them to tabs, 2 spaces per tab.

# ##### 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 = { ### required info block for add-on panel in Preferences
    "name": "Add-on Name",
    "author": "Your Name",
    "version": (0,1),
    "blender": (2, 72, 0),
    "location": "View3D > Object",
    "description": "Add-on description for Preferences panel",
    "category": "Object"} ### "location" advises user where the new functionality can be found, "category" is add-on group in Preferences panel
#### Help: http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Guidelines/Addons

import bpy ### required for access to blender
#import mathutils ### you may need this for vector and other math
#### Help: http://www.blender.org/api/blender_python_api_2_72_release/mathutils.html

#def MyFunction(context, boolArg, intArg, floatvecArg): ### alternative function definition to use if arguments are passed, see execute() below
def MyFunction(context):
  print("Hello World")

class MyOperator(bpy.types.Operator): ### type of class definition - Operator, Panel, etc.
  '''Tooltip for MyOperator'''
  bl_idname = "object.my_operator" ### Internal ID name of this operator. Starts with operator group of bpy.ops., followed by dot, then lowercase name
  bl_label = "My Operator" ### interface name of this class
  bl_options = {"REGISTER", "UNDO"} ### register operator in info window, and enable undo. (To hide from search, use "INTERNAL" also.)
#### Help: http://www.blender.org/api/blender_python_api_2_72_release/bpy.types.Operator.html#bpy.types.Operator.bl_options

  ### use operator properties to display buttons in Toolshelf, allowing user to set values for execute()
#  my_bool = bpy.props.BoolProperty(name="True or False", default=1, description="Boolean")
#  my_int = bpy.props.IntProperty(name="Enter Number:", default=1, description="Number", min=1, max=10000, subtype="NONE")
#  my_floatvec = bpy.props.FloatVectorProperty(name="XYZ Vector:", default=(0.0,0.0,0.0), min=0, max=10000, description="XYZ Vector", subtype="NONE")
#### Help: http://www.blender.org/api/blender_python_api_2_72_release/bpy.props.html

  @classmethod
  def poll(cls, context): ### poll handles context check, preventing a RuntimeError
    if context.object != None: ### check that an object is selected
      return context.object.select
    return False

  def execute(self, context): ### executes after invoke, possibly repeating as user adjusts values
    MyFunction(context)
#    MyFunction(context, self.my_bool, self.my_int, self.my_floatvec) ### alternative call, using properties defined earlier (adjust MyFunction to use it)
#### Help: http://www.blender.org/api/blender_python_api_2_72_release/bpy.types.Operator.html#bpy.types.Operator.report
    return {'FINISHED'}

  def invoke(self, context, event): ### invoke usually defines properties for execute(), with user input
#### Help: http://www.blender.org/api/blender_python_api_2_72_release/bpy.types.Event.html
    return self.execute(context)

def menu_draw(self, context):
#  self.layout.operator_context = 'INVOKE_DEFAULT' ### be sure to run invoke() before execute() to take user input
#### Help: http://www.blender.org/api/blender_python_api_2_72_release/bpy.ops.html#execution-context
  self.layout.operator(MyOperator.bl_idname, "My Operator") ### menu item for this class

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

def register():
  bpy.utils.register_module(__name__) ### module registration registers all classes
  bpy.types.VIEW3D_MT_object.append(menu_draw) ### add module menu item to menu (identify menus with mouse hover)

  wm = bpy.context.window_manager ### register the keymap
  km = wm.keyconfigs.addon.keymaps.new(name='3D View', space_type='VIEW_3D') ### space type where shortcut will work
#### Help: http://www.blender.org/api/blender_python_api_2_72_release/bpy.types.KeyMaps.html
  kmi = km.keymap_items.new(MyOperator.bl_idname, 'A', 'PRESS', shift=True, ctrl=True, alt=True)
#### Help: http://www.blender.org/api/blender_python_api_2_72_release/bpy.types.KeyMapItems.html
  addon_keymaps.append((km, kmi))

def unregister():
  bpy.types.VIEW3D_MT_object.remove(menu_draw) ### unregister must mirror register
  bpy.utils.unregister_module(__name__)

  for km, kmi in addon_keymaps: ### remove the keymap
    km.keymap_items.remove(kmi)
  addon_keymaps.clear()

if __name__ == '__main__': ### for use of this script in Blender Text window
  register()
I know, it doesn't look great here, the way the lines break in some browsers. It looks and works just fine in a properly sized text window.

Alternatively, you can download the file (with tab indentation) from Google Drive: AddonTemplate.py.

Happy coding!

1 comment:

  1. Thankyou, this template will come in handy for those of us that are currently learning how to code our own addons for blender,
    Again Cheers for this template.

    ReplyDelete

Note: Only a member of this blog may post a comment.