Creating a Blender 2.6 Python add-on

Copyright and example code availability

The text and illustrations in this article are © 2012 Michel Anders, all rights reserved.

The code and example .blend files accompanying this article is distributed under a GPL license; see the code itself for details. It may be downloaded from GitHub

Prerequisites and audience

The examples given in this article were developed with Blender 2.64, the latest version of Blender at the time of writing. Because some of the later examples use new functionality introduced in Blender 2.64 (the Skin Modifier) it strongly recommend downloading and installing the latest version from http://www.blender.org if you have not already done so.

The goal of this article is to give you a fairly thorough understanding how Python add-ons in Blender work and provide you with enough practical examples to develop actual add-ons. As such you should be quite familiar with Blender and its user interface and some experience with Python is necessary but you absolutely don't have to be an expert programmer to follow the examples and expand upon them. It is sufficient if you understand the basics of object oriented programming.

Table of contents

Introduction

Python in Blender is an important tool to enhance and extend Blenders already vast functionality. Python is bundled with Blender so there is no need to install it separately and indeed it is an integral part of Blender: Blenders user interface itself is implemented mostly in Python.

Because the user interface itself is written in Python and is designed to be extended and because virtually all internal data structures are accessible through a well documented interface it is pretty straight forward to implement add-ons that are written in Python. This is illustrated by the many add-ons that come bundled with Blender and by the many add-ons people publish online.

Much of what a user can do in Blender is centered on the concept of an operator. And operator essentially represents a basic unit of work, like scaling an object, subdividing a selected edge or calculating a UV-map for example. An operator is often (but not always) invoked through a menu entry or a hotkey and if it has additional properties like size or color, it provides user interface components to alter these properties in an area in the toolbar (on the left of the 3D window, made visible with pressing T )

There are other areas where Python plays an important role and which may be extended by the user, for example defining themes and driving animated properties but in this article we will focus on implementing operators that act on objects and meshes and typically are part of a menu in the 3D-window.

What you will learn

Besides gaining understanding of the way Blenders user interface works, you will learn

  • how to write an operator that is fully integrated with Blender native user interface,
  • how to layout a collection of properties in the toolbar,
  • how to add your operator to a menu,
  • how to install your operator so it will be available from the user preferences,
  • how to manipulate Blender objects, including
  • how to create mesh objects from scratch,
  • how to add modifiers,
  • how to add uv-maps,
  • how to parent objects to another object.

In the next section we see what an operator really is and will implement our first operator.

Anatomy of an operator

Basically a new operator add-on for Blender consists of the following parts:

  • properties, that is all sorts of numbers, vectors, strings and colors that determine the behavior of the add-on
  • a user interface where the user can manipulate the properties
  • code to determine if it is sensible to show the add-on
  • code to do the actual work
  • a few lines of code to insert the add-on in an existing menu and to register an operator

The Blender developers designed the Operator base class in a very clever way: almost anything you need is already provided by the either the Operator base class or the various Property classes, including most functionality to draw a user interface. In many situations you will only have to provide an execute() function and define properties (if applicable).

Example: The basic operator

About the simplest operator that you can imagine is one that moves the active object one Blender unit along the x-axis. The code shown below does just that.

code: addon-move.py, part of addon-move.blend

import bpy

class MoveOperator(bpy.types.Operator):
    bl_idname = "object.move_operator"
    bl_label = "Move Operator"

    def execute(self, context):
        context.active_object.location.x += 1.0
        return {'FINISHED'}

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

if __name__ == "__main__":
    register()

You can simply type this into the text editor and click the run script button



If there were no syntax errors the operator is now available to the user. Make sure you have an active object to test the new add-on. In the 3D-window you may now press the space-bar and type 'Move O' in the search box. Probably the only selection left in the list of choices is now our new operator. Click it to execute it and see how your active object is translated along the x-axis. Hurray!






Tip: The Blender console

If there was a syntax error or if something else went wrong during the execution of the operator a message is shown briefly in Blenders information bar at the top of the screen and additional information is listed in the console.



This console is not shown by default but in the Windows version of Blender this can be toggled from the main menu: Window->Toggle System Console. For other operating systems it is necessary to start Blender from a command line terminal instead of clicking an icon. Any messages will now be shown inside this terminal.

The Basic Operator: Example Breakdown

Our new operator is defined as a class MoveOperator that is derived from Blenders Operator class. The first lines define two class variables: bl_idname, which provides an internal name for our operator and bl_label, which provides a descriptive name that is used in for example menus. The names of the class variables start with bl_ to identify them as Blender specific and to distinguish them from any class variables you may define for your own purposes. We will encounter some additional Blender variables later on. Note that the bl_idname we have chosen starts with 'object'. This is a convention that identifies this operator as acting on a Blender object instead of for instance on a mesh or a texture.

The next thing we encounter is the definition of the function execute(). This function is the one that is called when the user invokes the operator. It is passed a context argument which holds information on the context the operator was invoked in. The context effectively records the state of Blender, for example which is the active object or whether we are in object mode or in edit mode. Our execute function refers to the active object in the context and adds one to the x component of its location.

To signal that it is done, the execute() function returns a set with a single string 'FINISHED'. This somewhat peculiar behavior is Blender specific and we will encounter other examples later on.

At this point we have a class but we still have to inform Blender to make it available to the user. That is what the definition of the register() function is for. The final lines make sure that if we click the Run Script button in the text editor we indeed register the operator. We could have called the register_class() function directly but by providing it at the file level we provide a means to register our operator class automatically when we install our script as an add-on (see the section Example: Distribution of an add-on)


Tip: using an external editor

Before we start adding properties it is a good idea to set up the way we develop our add-on in a slightly different way. An operator with properties will appear in the toolbar on the left of the 3d window once it is invoked and there we can change the properties and undo them if we are not satisfied with the results. This is nice but undoing something will also undo changes in the text editor if they were made after the invocation. This may sound confusing and it is because it can lead to unexpected annoyances when text you typed in the text editor is reverted without you wanting it. Therefore it is a good idea to edit your add-on in an external editor and run it during development from the text editor with the few lines shown below.

You might run it directly from any directory (for example the add-on installation directory we will encounter later on) or from the same directory as where your .blend file is stored. The code below does the latter: it runs the script 'addon-move3.py' from the same directory as the .blend file.

Code: runscript.py, part of add-move3.blend


import bpy
import os

filename = os.path.join(os.path.dirname(bpy.data.filepath), "addon-move3.py")
exec(compile(open(filename).read(), filename, 'exec'))

Example: Add an operator to a menu

Invoking an operator from the space-bar menu is fine, but it is far more convenient to add the operator a meaningful menu. Our move operator acts on an object in the 3D View window so it would be logical to add our operator to the object menu in the 3d View.

All these menus are available from within scripts and Blender offers a convenient way to determine what a menu is called. If you let the mouse cursor hover above a menu the name of the menu is shown in the tooltip:

In this case we see that the object menu in the 3D View is called 'VIEW3D_MT_object', something we encounter again when we analyze the code for our new add-on. But let us first run the script addon-move2.py (part of addon-move2.blend).

When we run the script we can now invoke our operator from the object menu to let the active object move as shown below:


Code: addon-move2.py, part of addon-move2.blend


import bpy

class Move2Operator(bpy.types.Operator):
    """Move2 Operator"""
    bl_idname = "object.move2_operator"
    bl_label = "Move2 Operator"

    def execute(self, context):
        context.active_object.location.x += 1.0
        return {'FINISHED'}

def add_object_button(self, context):
    self.layout.operator(
        Move2Operator.bl_idname,
        text=Move2Operator.__doc__,
        icon='PLUGIN')

def register():
    bpy.utils.register_class(Move2Operator)
    bpy.types.VIEW3D_MT_object.append(add_object_button)
if __name__ == "__main__":
    register()

Add an operator to a menu: Example Breakdown

In order to make our operator part of a menu we have to append it. Here this is done in the register() function. The add_object_button argument is a function that takes a self argument that refers to the menu being displayed on screen and a context argument. It adds the operator to the menu layout (more on layouts in the section on properties) by calling the operator() function of that layout. The three arguments passed are the internal name of our operator, the text that should appear in the menu and the icon to use.

The docstring of the operator will appear as the hover text when you leave your mouse over the menu entry and we use this same sting here to determine the actual text of the entry but there is no reason why these should be the same, in fact normally the hover text would be a bit more instructional.

Example: Add properties to an operator

Many operators change their behavior based on properties set by the user. We would like our operator to move in a direction and amount that we specify. We therefore define two properties, distance and direction, and let our execute() function use those values to effect its move.

Blender's bpy.data module contains a number of useful attributes and her we use filepath to get the full filename of the .blend file we are using. We use Python's platform neutral filename manipulation functions get at the directory part and append the name of the script we want to run. This script is then compiled and executed.

Now let's have a look how we can augment our operator with distance and direction properties that might be manipulated by the user. The idea is that once selected from the 3D View object menu the operator's properties will appear in the toolbar on the left side of the 2d View as shown below:



code: addon-move3.py

import bpy
from bpy.types import Operator
from bpy.props import FloatVectorProperty, FloatProperty

class Move3Operator(bpy.types.Operator):
 """Move3 Operator"""
 bl_idname = "object.move3_operator"
 bl_label = "Move3 Operator"
 bl_options = {'REGISTER', 'UNDO'}

 direction = FloatVectorProperty(
   name="direction",
   default=(1.0, 1.0, 1.0),
   subtype='XYZ',
   description="move direction"
   )
 
 distance = FloatProperty(
   name="distance",
   default=1.0,
   subtype='DISTANCE',
   unit='LENGTH',
   description="distance"
   )

 def execute(self, context):
  dir = self.direction.normalized()
  context.active_object.location += self.distance * dir
  return {'FINISHED'}
 
 @classmethod
 def poll(cls, context):
  ob = context.active_object
  return ob is not None and ob.mode == 'OBJECT'

def add_object_button(self, context):
 self.layout.operator(
  Move3Operator.bl_idname,
  text=Move3Operator.__doc__,
  icon='PLUGIN')

def register():
 bpy.utils.register_class(Move3Operator)
 bpy.types.VIEW3D_MT_object.append(add_object_button)

if __name__ == "__main__":
 register()

Add properties to an operator: Example Breakdown

Registering an operator is not sufficient to make it appear in the toolbar area when it is selected from a menu. For that we need to set bl_options to a set containing the string 'REGISTER'. We also add 'UNDO' to provide the user with a rest button for this operator.

Our properties are defined as class variables that are initialized using appropriate property factory functions. Blender had a whole host of these, documented in the API documentation of the bpy.props module. We define direction as a FloatVectorProperty, that is a vector with x y an z components and although the FloatVectorProperty offers a subtype 'DISTANCE' that displays a sphere like widget that is intuitive for things like surface normals, we stick with the 'XYZ' here.

We define distance as a FloatProperty. A FloatProperty can have (among other things) a name, a default value and minimum and maximum values. It also takes subtype and unit arguments which won't change the value itself but will alter the display of the widget and add appropriate units, if this is selected in the units tab of scene properties (see image below)



Our new execute() function retrieves the distance and the direction and after normalizing the direction changes the active object's location.

If we now click Run Script and invoke our operator form Object -> Move3 Operator the toolbar in the 3D window now displays our properties without us having anything to code to achieve this. Pretty neat, isn't it?

You may have noted that the Operator class takes care of displaying the properties we have defined. It is possible to determine the layout ourselves by providing a draw() function and that is what we'll do in the final example but for simple operators this is hardly ever necessary.

The final thing to take into account is the poll() function. This class method provides us with a way to control whether the operator is applicable. Here we have defined it to check if there is an active object at all and if so if it is in object mode. If not we return False. If an operator provides a poll() function and it returns False, any menu entry for this operator will be grayed out.

Example: Distribution of an add-on

Up until now we have run our script from the text editor but normally we would want the script to be available for all .blend files and the user should have the choice to enable the add-on from the user preferences.

To provide Blender and the user with relevant information about our add-on we add some extra information at the beginning of our code as shown below:

Code: addon-move4.py (part)

bl_info = {
 "name": "Move4 Object",
 "author": "Michel Anders (varkenvarken)",
 "version": (1, 0),
 "blender": (2, 6, 4),
 "location": "View3D > Object > Move4 Object",
 "description": "Moves the active Object",
 "warning": "",
 "wiki_url": "",
 "tracker_url": "",
 "category": "Object"}

If we now save our script to the add-ons directory and restart Blender, the add-on can be enabled from File->User Preferences by ticking the check box:



Because we defined the category to be 'Object' it shows up in the Object section of the add-ons.

In the next section we step up the tempo and we will create an add-on that does not simple modify some attributes of an existing object but creates a completely new configurable object.

Example: Generating trees: the L-system add-on

Add-ons can do a lot more than just move the active object about and in fact Blender comes bundled with a set of most spectacular add-ons which are 'just' operators. Clever add-ons like for example the Bolt Factory, Masonry wall and Sapling nicely show what might be achieved.

With these fine examples as our motivation, let's try our hand at an add-on that will create all sorts of tree and plant-like objects based on L-systems.

L-systems

L-systems are basically a set of string rewriting rules, but the interesting part is that we can interpret these strings as instructions to draw branches and leaves. Varying those rules or changing the number of iterations can provide us quickly with a whole host of different tree structures. If the rules (or 'productions') are chosen well, the results mimic the structures of real world plants or trees (you can read more on this on http://en.wikipedia.org/wiki/L-system or go in depth by reading the freely available book http://algorithmicbotany.org/papers/#abop )

In this final section we will develop an add-on that

  • let's us specify the rules for an l-system
  • creates a mesh object that represent the resulting tree
  • adds skin and subdivision modifiers to this mesh to create solid looking branches
  • adds UV-mapped leaves parented to this mesh and
  • lets the user save interesting results as operator presets.



Even though there is a lot more functionality than in our move operator the l-system operator has the same general outline:

  • the add-on provides information about itself in the bl_info dictionary
  • properties are defined as class attributes of the operator
  • the actual work of creating a new object is done in the execute() method
  • module level register() and unregister() functions are provided to register the operator and to create a menu entry

Of course interpreting a string a series of instructions to create a mesh is somewhat more involved than just moving an object so the execute() method is significantly larger than the previous versions we encountered. The same goes for properties, although the properties used here are by no means more difficult, there are quite a lot more that we want to arrange in an orderly fashion.

Using the l-system add-on

Before we look at the code in detail let's see how we can actually use the add-on. The add-on consists of more than one Python source file so it is distributed as a package: a directory called addon-lsystem that contains two files __init__.py and lsystem.py. The first contains the code of the operator the latter the code to interpret the strings.

To use the add-on:

  • Install the addon-lsystem directory and its containing files in the scripts\addons directory
  • Restart Blender and enable the add-on (it is in the Add Mesh section)



  • Invoke the operator from the File -> Add -> Mesh -> New Lsystem Object



  • The (initially mostly empty) properties will show up in the toolbar. If the toolbar isn't present in the 3d View you should press T



  • Change the properties anyway you want. For example by changing the number of productions to 2 and entering those productions, the start string, the iterations and the angle as shown



You will get the famous dragon curve (http://en.wikipedia.org/wiki/Dragon_curve)



When interpreting the result of the iterated productions the characters in the result string map to the following actions:

+, -

rotate around the right axis

/, \

rotate around the up axis (yaw)

<, >

rotate around the forward axis (roll)

the amount of rotation is set by the angle property

[, ]

push or pop state

&

rotate a random amount

!, @

expand or shrink the size of a forward step ( a branch segment or leaf)

#, %

fatten or slink the radius of a branch

F

produce an edge (a branch segment)

Q

produce an instance of a uv-mapped square ( a leaf )

Characters not in the list are simply ignored.

The object(s) produced can be influenced by the properties in the interpretation section:

iterations

the number of times the productions should be applied

seed

the random seed to be used (affects the & operator)

angle

the angle used for rotations

tropism

the direction of the drift added to F operators (used to simulate for example gravity)

tropism size

the size of the additional drift

Properties and layout

To manipulate an l-system we need to provide the user with quite a number of options. Our first task is to provide a layout that orders these properties in a logical way. This is what it will look like:



The layout of most options is straightforward and in previous examples we did not bother to provide a draw() function for our operator. But here we have quite a number of options so it makes sense to separate these visually. And there is another special issue here: the rewriting rules are presented as StringProperty attributes but we want to give the user the option to increase or decrease the number of rewriting rules. In the code below we will see how we can add a property dynamically to an operator class.

code snippet: properties, from addon-lsystem/__init__.py


class OBJECT_OT_add_lsystem(Operator):
 """Add an L-system Object"""
 bl_idname  = "mesh.add_lsystem"
 bl_label  = "Add Lsystem Object"
 bl_description = "Create a new Lsystem Object"
 bl_options  = {'REGISTER', 'UNDO', 'PRESET'}

 nproductions = IntProperty(
  name = "productions",
  min = 0,
  max = 50,
  update = nupdate )
 
 niterations = IntProperty(
  name = "iterations",
  min = 0,
  max = 20 )
 
 seed = IntProperty(name="seed")
 
 start = StringProperty(name='start')
 
 angle = FloatProperty(
  name  = 'angle',
  default  = radians(30),
  subtype = 'ANGLE',
  description = "size in degrees of angle operators" )
 
 tropism = FloatVectorProperty(
  name  = 'tropism',
  subtype = 'DIRECTION',
  description = "direction of tropism" )
 
 tropismsize = FloatProperty(
  name  = 'tropism size',
  description ="size of tropism" )

Properties and layout: Example breakdown

The class starts out by defining a number of properties:

  • nproductions, an IntProperty that allows us to set the number of rewriting rules. The rewriting rules themselves are pairs of StringProperty attributes that get added as needed by the nupdate() function that we will see later on. The nupdate() function is passed as the value of de update parameter of this InTProperty and will be called each time the nproduction property is changed and on its initialization,
  • niterations, an IntProperty that specifies how many time we should apply the rewriting rules to the given start string,
  • seed, an IntProperty used a a random seed for the & operator in rewrite rules
  • start, a StringProperty that specified the initial string to apply the rewriting rules to
  • angle, a FloatProperty specifying the angle in degrees of the > < + - \ and / operators in rewrite rules,
  • tropism, a FloatVectorProperty indicating the direction of the drift applied to the F module in rewriting rules, for example to simulate the effect of gravity or light directed growth,
  • tropismsize, a FloatProperty with the amount or weight of the tropism.

The nupdate() function is called each time the value of the nproductions property changes and is implemented as a function at the module level:

code snippet: the nupdate() function, from addon-lsystem/__init__.py


def nupdate(self,context):

 for n in range(self.nproductions):
  namep = 'prod' + str(n+1)
  namem = 'mod' + str(n+1)

  try:
   s=getattr(self,namep)
  except AttributeError:
   setattr(self.__class__, namep,
    StringProperty(
     name = namep,
     description = "replacement string")
    )
  try:
   s=getattr(self,namem)
  except AttributeError:
   setattr(self.__class__, namem,
    StringProperty(
     name = str(n+1),
     description = "a single character module",
     maxlen=1)
    )

For as many productions as needed it checks whether the prod<n> and mod<n> attributes are present and if not it catches the AttributeError exception and adds the appropriate StringProperty. Note that we never remove these attributes but the draw() method shown below simply doesn't display them:

code snippet: the draw() function, from addon-lsystem/__init__.py


def draw(self, context):
  layout = self.layout
  
  box = layout.box()
  box.prop(self, 'nproductions')
  box = layout.box()
  if getattr(self,'start')=='':
   box.alert=True
  box.prop(self, 'start')

  for i in range(self.nproductions):
   namep = 'prod' + str(i+1)
   namem = 'mod' + str(i+1)
   
   box = layout.box()
   row = box.row(align=True)
   if getattr(self,namem) == '' or \
    getattr(self,namem) == '':
     row.alert=True
   row.prop(self,namem)
   row.prop(self,namep,text="")
  
  box = layout.box()
  box.label(text="Interpretation section")
  box.prop(self,'niterations')
  box.prop(self,'seed')
  box.prop(self,'angle')
  box.prop(self,'tropism')
  box.prop(self,'tropismsize') 

As we saw earlier we present all these properties visually arranged in boxes. The nproductions property is presented in a box of its own. The start string has its own box as well but the alert attribute of this box is set when the start property is empty. This will render the box with a red background if we forget to supply a start string for out l-system.

Next we draw a box for each production present. A production consists of a left hand side (the single letter 'module name' as it is called in most l-systems) and a right hand side, a possibly multi character string. To align these two side by side we add a row layout to the box and set the alert attribute of this row if either of these properties is empty. The right hand property is added with its text attribute set to the empty string to suppress using its name as a label. This way the left and right hand side will be really flush together as shown in the image below:



The rest the properties is all about the interpretation of the result string and rendering it as a Blender object so these are grouped in a single box with a descriptive label at the top.



Tip: Presets

By adding the option 'PRESET' to the bl_options attribute of an operator is becomes possible for the user to save a set of property values she likes as a preset:

bl_options = {'REGISTER', 'UNDO', 'PRESET'}

Presets are saved in the user's profile, on Windows for example in:

C:\Users\Michel\AppData\Roaming\Blender Foundation\Blender\2.64\scripts\presets\operator

For each operator's presets a subdirectory is created and each preset is saved as a small Python scripts. It is possible to distribute these presets, other users can simple copy the directory plus Python files to their own profile or the scripts\presets\operator directory inside Blender's installation directory.

The lsystem class

Now that we have the rewriting rules and other properties available we can implement the execute() function that will iterate the l-system and create an object based on the end result. A complete description of the class is out of scope of this article but let's have a quick look at how execute() utilizes this class.

code snippet: the execute() function, from addon-lsystem/__init__.py


def iterate(self):
  s=self.start
  prod={}
  for i in range(self.nproductions):
   namep = 'prod' + str(i+1)
   namem = 'mod' + str(i+1)
   prod[getattr(self,namem)] = getattr(self,namep)
  for i in range(self.niterations):
   s = "".join(prod[c] if c in prod else c for c in s)
  return s
 
 def execute(self, context):
  s=self.iterate()
  obj=self.interpret(s,context)

  <<< code ommited >>>

The execute() function first calls the interpret() method which takes the contents of the start property and applies the rewrite rules the required number of times before returning the end result as a single string.

code snippet: the interpret() function, from addon-lsystem/__init__.py


def interpret(self, s, context):
  q = None
  qv = ((0.5,0,0),(0.5,1,0),(-0.5,1,0),(-0.5,0,0))
  verts = []
  edges = []
  quads = []
  self.radii = []
  t = Turtle( self.tropism,
     self.tropismsize,
     self.angle,
     self.seed )
  for e in t.interpret(s):
   if isinstance(e,Edge):
    if e.start in verts:
     si = verts.index(e.start)
    else:
     si=len(verts)
     verts.append(e.start)
     self.radii.append(e.radius)
    if e.end in verts:
     ei = verts.index(e.end)
    else:
     ei=len(verts)
     verts.append(e.end)
     self.radii.append(e.radius) 
    edges.append((si,ei))
    <<< code ommited >>>
    
  mesh = bpy.data.meshes.new('lsystem')
  mesh.from_pydata(verts, edges, [])
  mesh.update()
  obj,base = self.add_obj(mesh, context)
  for ob in context.scene.objects:
   ob.select = False
  base.select = True
  context.scene.objects.active = obj
  
  <<< code parenting Quad to the main object ommited >>>
  
  return base

The interpret() method will return a Blender mesh object based on a Turtle interpretation of the string passed to it as an argument. This mesh object will consist of vertices and edges only but the object will be rendered solid later on by adding a skin modifier.

The Turtle object (the class is defined in the lsystem module not shown here) may be iterated over to return different kind of named tuples. For any F character in the string it is interpreting it will return a named tuple of the class Edge. Its start and end attributes hold vertices. If either of these vertices is not yet present in the verts list we add it. In both cases we retain the indices into this list to construct an edge (a tuple of indices) that we append to the edge array. (We also save the radius attribute that will later be used to control the local thickness of the skin modifier. This radius is increased and decreased by the # and % operators respectively in the string that is being interpreted.)

When all the characters in the string are interpreted we proceed by creating a new mesh called lsystem (of course in Blender it may end up with a numerical suffix if a mesh with that name is already present) and call its from_pydata() method to construct the mesh from our lists of vertices and edges.

A mesh is not yet a Blender object linked to a scene and to that end we call the add_obj() method which returns both the Blender object and the base object (a base object links an object to a scene, allowing the same object to be used in more than one scene. add-obj() adds the object it constructs to the current scene). The final steps are to deselect all objects in the current scene and select our newly added object and to indicate in the context that the new object is the active one.

The add_obj() method is pretty straight forward:

code snippet: the add_obj() function, from addon-lsystem/__init__.py

@staticmethod
 def add_obj(obdata, context):
  scene = context.scene
  obj_new = bpy.data.objects.new(obdata.name, obdata)
  base = scene.objects.link(obj_new)
  return obj_new,base

It retrieves the current scene from the context and then creates a new Blender object. The new Blender object is given the same name as the mesh data it contains. Next we link the new object to the current scene and return both the object and the base object.

Creating a tree like mesh with a skin modifier

The interpreter part of the lsystem provides us with a tuple of start and end points for each F character in the end result. We want to create a mesh object with vertices and connecting edges based on these end points and add a skin modifier to this mesh object to add some volume:

code snippet: the execute() function, from addon-lsystem/__init__.py


def execute(self, context):
  s=self.iterate()
  obj=self.interpret(s,context)
  
  bpy.ops.object.modifier_add(type='SKIN')
  context.active_object.modifiers[0].use_smooth_shade=True
  
  skinverts = \
   context.active_object.data.skin_vertices[0].data
  
  for i,v in enumerate(skinverts):
   v.radius = [self.radii[i],self.radii[i]]
  
  bpy.ops.object.modifier_add(type='SUBSURF')
  context.active_object.modifiers[1].levels = 2

  return {'FINISHED'}

We use the modifier_add() operator to add a skin modifier to the active object which is our newly created object. Because this is the first modifier we add we can access it with index 0 in the modifiers attribute and set its use_smooth_shade attribute to True. This necessary because an object with a skin modifier doesn't heed the regular smooth shading attribute normally used for an object.

The skin modifier also added a so called vertex layer, skin_vertices, that we iterate over to replace the default radii with the radii we calculated using # or % operators.

We finish by adding a second modifier, a subsurface modifier with two levels of subdivisions, making everything look really smooth.

Adding leaves with UV-maps

When the lsystem interpreter encounters a Q character in the end result, it hands us the current position and orientation vectors (up, right, forward) as a namedtuple of type Quad. We use this to position and orient a mesh object consisting of a single quad face. We do not want to replicate this geometry for possibly thousands of objects so we let the objects that we instantiate point to the same mesh.

The omitted code in the execute() method that deals with Quads looks like this:

code snippet: execute() part 2

elif isinstance(e, Quad):
    if q is None:
     q = bpy.data.meshes.new('lsystem-leaf')
     q.from_pydata(qv, [], [(0,1,2,3)])
     q.update()
     q.uv_textures.new()
    obj,base = self.add_obj(q, context)
    r=Matrix()
    for i in (0,1,2):
     r[i][0] = e.right[i]
     r[i][1] = e.up[i]
     r[i][2] = e.forward[i]
    obj.matrix_world = Matrix.Translation(e.pos)*r
    quads.append(obj)

We only want to create the mesh data once so our first check is to see if we already did so.

We also add a uv-layer to the single mesh. This way, if we add a material with a uv mapped texture to the mesh, all objects are equipped with a fitting image in one go. Note that we not actually set any uv coordinates in this new uv-layer, we simply use the default coordinates that are created when the new layer is added. This will ensure that a uv-mapped texture will perfectly fit our square.

Then we add a new object to the scene that refers to the mesh data. So we end up with an object for every leaf but just one mesh.

We orient this object by creating a Matrix instance that represents the orientation in the world and set the object's matrix_world attribute to the product of that rotation and a translation to the current position.

We end by appending a reference to this object the quads list.

Parenting objects

The end result of the l-system operator might not be a single mesh object but a whole bunch of objects if our productions generate a number of leaves as well. In such a situation it might be a good idea to parent the leaves to the main mesh object to make the life of the user a little easier when she wants to move the tree and the leaves. That was the purpose of the quads list that holds references to all the leaves. In the final part of the execute() method we omitted earlier we iterate over this list and set the parent attribute of each object to the main object:

code snippet: execute() part 3

mesh = bpy.data.meshes.new('lsystem')
  mesh.from_pydata(verts, edges, [])
  mesh.update()
  obj,base = self.add_obj(mesh, context)
  for ob in context.scene.objects:
   ob.select = False
  base.select = True
  context.scene.objects.active = obj
  for q in quads:
   q.parent=obj
  return base

Exercises

This section provides you with some exercises so you can test yourself if you grasp the concept of operator completely. Don't be alarmed if you don't get it to work in one go, each exercise comes example code that is part of the code distribution.

Exercise 1: Making the skin modifier optional

When the result of our lsystem consists of many edges and leaves the interaction might feel sluggish on less powerful PCs so it might be a good idea to add a checkbox to make this optional so that you can manipulate the mesh until you are are satisfied and only mark a checkbox when you're done.

A checkbox may be implemented by adding a BoolProperty attribute and changing the part of the execute() method that deals with adding modifiers to act on the contents of this attribute. It should look something like:



Hint: when you add a property to an operator that provides a draw() method you should not forget to add the new property to your layout otherwise it will not be visible. Full code that implements this functionality can be found in the Examples\Example 1 directory of the code distribution.



Exercise 2: Adding arbitrary objects instead of just leaves

The main object of our lsystem is formed by skinning edges of a mesh and we have the possibility of adding leaves in the form of simple square meshes but in some scenarios this might not be enough. For example, we might want to add fruits or blooms or create structures that have nothing to do with trees.

A useful addition therefore might be to enhance the l-system add-on with the possibility to add arbitrary Blender objects. The idea is that productions might contain references to any existing object like {Cube}, for example as shown on the left:




This example will produce an object like the one shown on the right.

Hint: the lsystem.py module already will return a namedtuple of type BObject if it encounters a string like {Cube}. Besides pos, up, right and forward attributes like the Quad type, it also has a name attribute. Your challenge is to enhance the interpret() method of the lsystem operator to recognize that a BObject is being returned and act accordingly by creating a new object with a duplicate of the original's object data.

Full code that implements this functionality can be found in the Examples\Example 2 directory of the code distribution.

References and further reading

When developing add-ons in Blender you cannot do without the API documentation that you can find on http://www.blender.org/education-help/. Not only will you find information on bundled scripts which can be a source of education on its own but there is also a full reference to the API at http://www.blender.org/documentation/250PythonDoc (note that this link will redirect you to the latest version of the API docs despite its name).It also provides several links with tips and tricks and an overview of the API.

Another excellent source of examples is hidden under the Templates button in the Text Editor menu. Clicking on any of the options listed there will create a new text with sample code. Many examples are provided here, not only for operators but also code for all kinds of other situations where Python is used in Blender.

38 comments:

  1. Wow great work! Have not read everything yet, but seems like a very comprehensive and complete tutorial. I'll be sure to follow it as son as I have some free time. I am trying to learn blender scripting and python coding in general.

    Many thanks, this will come in handy

    BTW: I believe you made some small markup mistake with your last image in the article, it seems wrongly formatted

    ReplyDelete
    Replies
    1. Hi Duarte,

      fixed the markup, thanks for your comment!

      Delete
  2. What version of Python are you using? Can't get even the simple code to work with 3.2.

    ReplyDelete
  3. nice work! is it possible to pack this into a pdf for offline reference?

    ReplyDelete
    Replies
    1. Yes a pdf is possible, I might post one in the (near) future, but this week I am bit busy so please don't hold your breath :-)

      Delete
    2. click at top of the page, hold and drag to bottom. Control-c, open up libreoffice-writer, control-v, export to pdf. done :)

      Delete
    3. its been a while. Any nes of a pdf?

      Delete
  4. Found the problem:
    in your blog examples, you need:

    an underscore between bl and idname and one between move and operator in the same line

    an underscore between active and object in the execute block

    and two underscores on each side of the word name in the last block.

    ReplyDelete
    Replies
    1. Hi Ray, I don't get exactly: if I search for idname if find every instance preceeded by an underscore. Did I miss one?

      Delete
  5. Excellent and very well tutorial. Thank you!

    ReplyDelete
  6. Very nice and useful.
    What i would like you to add is all what you have to do when your plugin is ready : publishing, adding to the addons list, help page and so on...
    Thanks in advance if you think you can do that !
    Rimpotche

    ReplyDelete
  7. Hi. Very nice addon. I like it much, but it is not as good as l-system used in Houdini. Do you think about develop it more, because it have a potential. I started work to make it a little better by adding same function. For example declare angle in side string. I have same ideas haw make it better.

    Here is link to my version
    https://dl.dropbox.com/u/53698526/skrypty/blender/lsystem.zip

    ReplyDelete
  8. In addon-move3.py, the line...
    dir = self.direction.normalized()
    does not work because a FloatVectorProperty has no such attribute. I have spent a few hours trying to make it work by researching mathutils. I tried including that and since there is no FloatVector.normalize() I tried things like...
    dir = mathutils.Vector(self.direction).normalized()
    but the length of whatever sequence 'self.direction' returns is only 1.

    Don't think I'll get through the rest if I'm stumped so early!

    ReplyDelete
    Replies
    1. Found it. Strange error to get but I had an extraneous comma after the closing parenthesis of one of the properties. Very strange error!

      Delete
  9. In the code snippet of the draw function is standing a small mistake. There is standing 2 times namem instead of namem and namep. In the complete source code from the zip file you provided it is correct.

    if getattr(self,namem) == '' or \
    getattr(self,namem) == '':

    ReplyDelete
  10. what is the the name for the "Add" Menu (Shift + A) ??
    I'd like to append my operator there.

    ReplyDelete
    Replies
    1. found it:

      INFO_MT_mesh_add for adding op in "Add > Mesh"

      more paths:
      http://fossies.org/dox/blender-2.66a/namespacebl__ui_1_1space__info.html

      Delete
  11. I really liked your tutorial.I hope there will be more.

    I'm currently trying to inpruve a blender addon but problem is that i don't really understand how and where textures are stored in pyhon. I tryed to to use the Blender API documentation but is so huge that i got lost in it. Can you give explain me textured are stored ? O give me a shorter documentation where i can get o hold of it.

    ReplyDelete
  12. I've read quite a few of your comments(and learned quite a bit) about scripting in Blender in different forums and it was difficult to figure out how to contact you with a question.

    Any way, I've found a method to get blender to render and composite my scenes about 10 to 20 times faster. I wrote a script so I would not have to manually set it up each time. That went well.

    However with attempting to make an addon, my programming skills fell short.

    What I'm attempting to create is an addon similar to the Properties/Modifier interface that is already available, with the Add, Stack and arrange items in the stack.

    One type of item in the stack would have a dropdown that would allow selection of a certain scene, the other type would allow the selection of a certain node and set it's input image.

    If you or anyone you know of is capable of and has the time to assist with this, please let me know. I will certainly be able to compensate any one for their time.

    Your time and consideration are greatly appreciate.

    jkcinci@yahoo.com

    ReplyDelete
  13. Very helpful thankyou. Nice l-system example too

    ReplyDelete
  14. Hi there - is there a way to calculate the surface area and volume of the trees?

    ReplyDelete
  15. Hi, I just tried the first example (add-move.py) with selected cube (standard template after opening blender) and it does nothing at all. I checked blender console, but no error there. I use Blender 2.68a on Windows Vista.

    I was wondering, did somebody else tried it too? Did it work? If not, maybe some line is missing in the example. The register() procedure looks quite short to me. U called only register_class(). Something like class.execute() is missing to me.

    ReplyDelete
  16. Hi everyone,

    I just want to run python scrypt: bpy.ops.render.netclientstart()
    when the blend file is opening (everything is set, need only start service)

    How to create a script to "automatically click" the button "start network rendering service".

    Just that. I couldn't find the answer here.

    ReplyDelete
  17. Is the accompanying code for this article still available? It looks like a great tutorial, but the download link at the top is broken.

    ReplyDelete
  18. @tim tylor: i just checked https://github.com/varkenvarken/blenderaddons/tree/master/lsystem
    and it is very much alive...

    ReplyDelete
  19. This comment has been removed by a blog administrator.

    ReplyDelete
  20. This comment has been removed by a blog administrator.

    ReplyDelete
  21. This comment has been removed by a blog administrator.

    ReplyDelete
  22. Programming is combination of intelligent and creative work. Programmers can do anything with code. The entire Programming tutorials that you mention here on this blog are awesome. Beginners Heap also provides latest tutorials of Programming from beginning to advance level.
    Be with us to learn programming in new and creative way.

    ReplyDelete
  23. Thanks for sharing the information about the Python and keep updating us.This information is really useful to me.

    ReplyDelete
  24. the article provided by you is very nice and it is very helpful to know about the python ..i found a article related to you..once you can check it out.
    python online training

    ReplyDelete
  25. That is very interesting; you are a very skilled blogger. I have shared your website in my social networks! A very nice guide. I will definitely follow these tips. Thank you for sharing such detailed article.

    python online training

    ReplyDelete
  26. Well done,its a great knowledge. Nice post to read this article because your writing style is too good.

    Python Training in Chennai
    Python Training Centres in Chennai

    ReplyDelete
  27. This Carbonite is a map and quest tracking addon. The map feature will go from your standard map to a more google maps type terrain map. more info

    ReplyDelete