Blender 3D: Noob to Pro/Advanced Tutorials/Blender Scripting/Anatomy Of An Addon

Introduction

edit

An addon is a Python script that can extend the functionality of Blender in various ways. It can reside in a file within your Blender user preferences directory, or it can be stored in a text block within the Blender document. In the former case, the addon needs to be enabled in each Blender document where you want to use it, by ticking the checkbox in its entry in the Add-Ons list in the User Preferences window for that document. In the latter case, the script can be run by pressing  ALT + P  in the Text Editor window; or you can tick the “Register” checkbox for that script in the Text Editor, to have the addon be automatically enabled as soon as the document is loaded into Blender.

The main thing an addon script typically does is define one or more new operators. All the user-interface functions in Blender are performed by operators; these are invoked by being associated with menu items, buttons or hotkeys. Each operator is a subclass of the bpy.types.Operator class. The name you give your class in Python is immaterial outside the script where it is defined; it will be referred to from the rest of Blender via its operator name, which must be unique within the document. In the same way, your script can invoke other operators defined elsewhere via Blender API functions that take their operator name.

Text Blocks

edit

A Blender document can contain text blocks, which are not the same as text objects in a 3D scene (though the former can be converted to the latter). Besides generating text objects, a text block can serve any purpose you like; for example, use it to pass workflow instructions to a colleague along with the document; display a copyright or help message in the initial layout that a user sees on opening the document; or hold a Python script that can be run by the user to perform some useful action related to the document.

Your First Operator

edit

Open a new, empty Blender document. Bring up the Text Editor   in any convenient window; you will see an empty, grey rectangle. Before you can type in text, you need to create a text block; do so by clicking the large button labeled “New” in the window header. As soon as you do this, you should see a popup menu appear listing all the text blocks in your document, with the current (only) entry named “Text”. You should also see a red insertion cursor appear at the top left, indicating that you can start typing.

Unlike the Console Window, nothing is automatically imported for you. So as in any other Python script, you need to mention every module you want to access.

You define your operator as a subclass of a subclass of the bpy.types.Operator. It must have a bl_idname class attribute which gives the operator name, while bl_label gives the user-visible name that appears in the spacebar menu. The former must have valid Python syntax for a name, including a single dot; the part to the left of the dot must be the name of one of the valid categories of operators, which you can find by typing

dir(bpy.ops)

into the Console Window.

Let’s define an operator that will add a new tetrahedron object to the document. The class definition should start something like this:

class MakeTetrahedron(bpy.types.Operator) :
    bl_idname = "mesh.make_tetrahedron"
    bl_label = "Add Tetrahedron"

It has to define an invoke method, which starts off like this:

    def invoke(self, context, event) :

The invoke method performs the actual function of the operator; after it has finished, it must return a value which is a set of strings, telling Blender things like whether the operator is still executing in a modal state, or whether the operation has finished, and if so whether it was successful or not. To indicate successful completion: end the invoke method with this:

        return {"FINISHED"}

Anyway, let us compute the coordinates of the vertices for the tetrahedron: with edges with a length of 1 Blender unit, suitable values are  ,  ,   and  . Or in Python:

        Vertices = \
          [
            mathutils.Vector((0, -1 / math.sqrt(3),0)),
            mathutils.Vector((0.5, 1 / (2 * math.sqrt(3)), 0)),
            mathutils.Vector((-0.5, 1 / (2 * math.sqrt(3)), 0)),
            mathutils.Vector((0, 0, math.sqrt(2 / 3))),
          ]

Then we create the mesh datablock, giving it the name “Tetrahedron”:

        NewMesh = bpy.data.meshes.new("Tetrahedron")

Fill it in with the above vertex definitions and associated faces:

        NewMesh.from_pydata \
          (
            Vertices,
            [],
            [[0, 2, 1], [0, 1, 3], [1, 2, 3], [2, 0, 3]]
          )

The Mesh.from_pydata method is not currently well documented in the Blender API, but its first argument is an array of Vector defining the vertices, the second argument is an array of edge definitions, and the third argument is an array of face definitions. Each edge or face is defined as a list of vertex indices (2 elements in each list for an edge, 3 or 4 for a face), 0-based in usual Python style, being indices into the array of vertex definitions that you passed. Note that you pass either the edge definitions or the face definitions, but not both: the other one should be passed as the empty list. If this function is not used correctly, it will cause Blender to crash.

We also need to add the following, to tell Blender the mesh has changed and needs updating (as if it couldn’t figure it out itself):

        NewMesh.update()

(Omitting this produces a minor quirk: right-clicking the newly-created tetrahedron does not highlight its outline in orange as with other objects, though the problem goes away if you enter edit mode on the object and exit again.)

Now to create the object datablock (I also give it the name “Tetrahedron”), and link it to the mesh:

        NewObj = bpy.data.objects.new("Tetrahedron", NewMesh)

But the object will not appear to the user until it is linked into the scene:

        context.scene.objects.link(NewObj)

To recap, here is the complete script:

import math
import bpy
import mathutils

class MakeTetrahedron(bpy.types.Operator) :
    bl_idname = "mesh.make_tetrahedron"
    bl_label = "Add Tetrahedron"
    def invoke(self, context, event) :
        Vertices = \
          [
            mathutils.Vector((0, -1 / math.sqrt(3),0)),
            mathutils.Vector((0.5, 1 / (2 * math.sqrt(3)), 0)),
            mathutils.Vector((-0.5, 1 / (2 * math.sqrt(3)), 0)),
            mathutils.Vector((0, 0, math.sqrt(2 / 3))),
          ]
        NewMesh = bpy.data.meshes.new("Tetrahedron")
        NewMesh.from_pydata \
          (
            Vertices,
            [],
            [[0, 2, 1], [0, 1, 3], [1, 2, 3], [2, 0, 3]]
          )
        NewMesh.update()
        NewObj = bpy.data.objects.new("Tetrahedron", NewMesh)
        # Blender 2.79: context.scene.objects.link(NewObj)
        context.collection.objects.link(NewObj)
        return {"FINISHED"}
    #end invoke
#end MakeTetrahedron

bpy.utils.register_class(MakeTetrahedron)

Note the addition of the register_class call, to make Blender add your operator to its built-in collection.

Now execute your script by typing  ALT + P . If all goes well nothing should appear to happen; Blender will define the class and register it as a new operator as you requested, ready for use.

If you hit any syntax errors, Blender should display these in a popup window; go back and correct them, and re-execute the script with  ALT + P .

Invoking Your Operator

edit

We haven’t (yet) defined any user interface for this operator; so how do we invoke it? Simple.

Go to a 3D View window. Delete the default cube to avoid it obscuring things, and press  SPACE  (F3 in Blender 2.8, Blender 3). This brings up a searchable menu of every operator defined for the current document. Into the search box, type part or all of the string you defined for the bl_label attribute above (typing “tetra” should probably be enough). This will restrict the menu to only showing items that contain that string, which should include the name of your operator; click with  LMB  on this or highlight it and press  ENTER . If all goes well, you should see your tetrahedron object appear!. If it does not appear make sure that in the Preferences Editor, Interface/Display "Developer Extras" box is checked.

 

If You Hit An Error

edit

If there is any error compiling or running the script, Blender should display this in a popup. For example, the following simple one-line script

raise RuntimeError("Uh-oh")

displays this popup:

 

The full Python traceback message is written to Standard Error, and looks something like this:

Traceback (most recent call last):
  File "/Text", line 1, in <module>
RuntimeError: Uh-Oh!

On Linux/Unix systems, the message will appear in the terminal session if you invoked Blender from the command line; otherwise it will be appended to your ~/.xsessionerrors file if you launched Blender from a GUI. On Windows the message appears in the console window.