YORIK’S COFFEE CORNER

You are currently viewing a single post of this guestblog.

Click here to go back the complete page. I would be glad to hear your comments, so don’t hesitate to leave me your feedback below. It will appear on the main guestblog page.


in categories  blender  opensource  permalink:  314   posted on 04.09.2013 22:38
From Yorik

Python for architects - Part 2: Blender

This is a series of 3 tutorials for architects who wish to use opensource 3D tools (mainly Blender and FreeCAD more effectively, or simply who are curious about programming, and would like a gentle introduction. This is the second tutorial, explaining how to use python inside Blender. Read also the Part 1: introduction. The third part (about FreeCAD) is yet to be written.

This second part assumes you read the first one, or that you have at least a very basic understanding of the Python language. It will focus on Blender, and show with simple example how python can be useful, and what you can do with it in Blender. This tutorial doesn't assume that you have any experience with Blender, but if you haven't any at all, be prepared to need to go look elsewhere for reference, specially on the Blender wiki, since most trivial steps won't be explained here.

The Blender Python editor

Blender, like FreeCAD, is a python beast. Although it is mainly programmed in the C language, since version 2.5, the python API (which is basically the python tools that blender provides) allows you to access and modify almost anything in Blender, including all the data of your model (scenes, objects, mesh data, lights, materials, textures, nodes, animation, etc...) and the blender interface itself (3D view, buttons windows, menus, etc...). In fact, today, many of the features of Blender are entirely programmed in python. So you can basically modify objects, create objects, and create tools with your own controls that will appear wherever you want in the Blender interface. And finally, if you do something awesome, you can pack your script as an addon, and distribute it to other blender users.

The very first thing to do to start playing with python in Blender, is to open the python console, by subdividing one window and setting it to "Python Console":

This is a python console that behaves exactly like the standard python terminal we used in the Part 1 : Introduction. Except that it has a few more goodies: Special keyboard shortcuts, like it is written when you start the console, and some modules are already conveniently imported for you (remember about importing modules in Part 1?), especially the module called "bpy", which contains about all that blender offers to python. So normally you should start any blender python script with:

import bpy

But in this console it is not necessary. Another very important window is the text editor:

This one serves to write text (or python scripts of course), and behaves a bit like any text editor like notepad, except that it can show line numbers (useful when python tells you there is an error on line X) and can paint special python words in another color, very comfortable to read your script.

When writing python scripts, have always both windows open, so you can test stuff in the python console, and copy/paste it (and save it) to the text window when you are sure that it works.

The python scripts you write in the text editor stay saved inside the blender files when you save them, but can also be saved in separate .py files. Usually I keep my scripts inside a blender file while I am developing them, and when they work perfectly I export them as .py files.

A last cool trick, CTRL + wheel mouse will zoom the text inside both python console and text editor, like most other blender windows.

Accessing and modifying existing objects

One of the first uses you'll want to do with python is to manipulate existing objects, that you have been drawing with the user interface controls. Let's for example, access the contents of our 3D scene, and list the objects contained in it. For this, we'll use the primary and most used element of bpy: the context.

The context means basically: "What is currently on stage". It will contain the current scene, the current selected object, the current view, the current state of the interface, etc. You got the picture. Remember you can always explore the contents of any python object with the dir() function. Here we'll take our current scene, and list the objects inside:

scene = bpy.context.scene
for o in scene.objects:
    print o

And it gives you this:

Now we know we want the cube. There are several ways to access it inside the current scene. The easiest is to get it by name:

cube = scene.objects.get("Cube")

But we could also select it with the mouse, then simply get the active object :

cube = bpy.context.active_object

There are always many ways to achieve something in Blender, just for the fun let's try, for example, to take all the objects of the scene and filter out those which are not meshes (the lamp and the camera):

for o in bpy.context.scene.objects:
if o.type == "MESH":
    cube = o

Remember, dir() is your friend, for example it showed me what's inside objects, so I could find the "type" property. It is also very common practice in python to document classes and functions, so you can normally expect that everything has a __doc__ (that 2 x 2 underscores) that tells you what that thing serves for and hopefully how to use it. For example:

print(cube.is_visible.__doc__)

You might have noticed that the Python console also has an autocomplete system, so when you are in the middle of typing the above command, you can at any time press Ctrl + Space to show possible completions of what you're typing. If you use that just after the "visible" word, you'll see that autocomplete shows you the contents of the __doc__.

Also don't forget there is always the complete API documentation online.

But let's get back to our example. Now that we got our cube, let's examine the contents of its Mesh (remember the len() from Part 1?):

me = cube.data
print (len (me.vertices) )
print (len (me.edges) )
print (len (me.polygons) )

Nice, isn't it? Let's see the coordinates of each vertex:

for v in me.vertices:
    print (v.co)

If you know Blender well, even without knowing Python, you already know that mesh-based objects contain mesh data, which contains vertices, edges and faces, and you know that vertices have coordinates. All this helps you when exploring the contents of the python structures inside the bpy module.

Now let's be bold and try something audacious:

v = me.vertices[0]
print (v.co)
print (v.co.x)
v.co.x = 5

"Amazing", you must be thinking. Indeed Python allows you to change about any property of anything in Blender, in real-time, no further operation required. In older versions you had to work on a copy of the mesh, then replace the mesh by its copy. Thanks to the big python-friendly recode that occured in version 2.50, things are now much easier.

Now let's study a bit better how Blender mesh are structured in python. We already saw that our mesh data contains subelements called vertices, edges and polygons. These elements can also inform you of their vertices. But instead of giving you a vertice object, they give you the index number of those vertices in the general list of vertices of the mesh:

face = me.polygons[0]
for v in face.vertices:
    print (v)

The above code shows you 4 numbers, which are the index numbers of the 4 vertices of that face. With those numbers, we can get the actual vertices from the vertices table:

for v in face.vertices:
    print me.vertices[v].co

The reason why things are like that is obvious: A same vertex can be shared by several edges or faces. So it is easier to have them stored at one central place, and then reference them as needed inside the mesh structure. Most mesh-based 3D applications do that. If we want, for example, to move a face, we actually move all its vertices:

for v in face.vertices:
    coordz = me.vertices[v].co.z
    coordz = coordz + 1
    me.vertices[v].co.z = coordz

A note about vectors

You already know that points in the 3D space have x, y and z coordinates, right? In mathematics, such group of 3 numbers (or more) is called a vector. You certainly heard about that at school (I know, you forgot)... Anyway, when dealing with programming in 3D, vectors are everywhere. You can do awesome things with vectors, multiply them, add them, find perpendicular directions, etc...

If you want to refresh your vector math skills, have a look for tutorials on the internet (I might try to do one too one day), it will be precious when you need to perform more complex operations on vertices. Blender also has a special module with tools to help you to perform operations on vectors (cross products, etc...):

from mathutils import Vector
dir(Vector)

Operators

The tools that Blender offers you to manipulate objects go far further than simply allowing you to access their components. Almost all the tools available on the User Interface are also available to the python scriptwriter. The python version of those tools is called -in Blender slang- an Operator. So all usual things such as moving an object, performing boolean operations, etc... are available in a compact, single-line way. Most of them behave the way their corresponding GUI tool behave, that is, they act on what is currently selected. That is, our famous context.

Operator are all stored inside bpy.ops, and they are grouped by category, such as object or mesh, depending on what they act on, being whole objects or their subcomponents.

For example, this moves the selected object(s) 2 units in the X direction:

bpy.ops.transform.translate(value=(2,0,0))

This scales it 2 times in the X direction:

bpy.ops.transform.resize(value=(2,1,1))

This enters edit mode, goes into face select mode, and selects a face of our cube:

bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_mode(type="FACE")
me.polygons[0].select = True

This deletes the selected face:

bpy.ops.mesh.delete(type="Face")

I suppose now you saw how it works and how useful it can be. And also how much you need to know in order to use them! Don't worry too much, though, it is fairly easy to find your way between all the operators, and how exactly they must be used, thanks to the Blender python console's autocomplete feature (Ctrl+Space). Try for example writing this, then pressing Ctrl+Space after ops. , after object. , and after mode:

bpy.ops.object.mode

Adding data

Now that you begin to see how things work, adding basic objects can be as simple as this:

bpy.ops.mesh.primitive_cube_add()

In the mesh section you'll find other default primitives to add. But this is a bit too simple, isn't it? We would like to have more control over the object we want to add. A better path would be to respect the way Blender objects are constructed: First we add the data, then we create an object to contain the data, then add (link) the object to the current scene:

scene = bpy.context.scene
lamp_data = bpy.data.lamps.new(name="New Lamp", type='POINT' )
lamp_object = bpy.data.objects.new(name="New Lamp", object_data=lamp_data)
scene.objects.link(lamp_object)
lamp_object.location = (5.0, 5.0, 5.0)
lamp_object.select = True
scene.objects.active = lamp_object

The code above (borrowed from here) shows very well how things work internally. You can do exactly the same with a mesh instead of the lamp:

mymesh = bpy.data.meshes.new(name="New mesh")

We now have an empty mesh (no vertices, no faces), that is not bound to any object,but it exists in memory. We can for example add geometry to it, then when it is ready, we'll add it to the scene. What about constructing a pyramid? Let's make a pyramid that is centered on the origin point (0,0,0). Its base would be a square of 4x4 units, and its top would be at a height of 3 units:

# first we define the 5 points of our pyramid: the 4 corners of the base, and the top point
p1 = [-2,-2,0]
p2 = [2,-2,0]
p3 = [2,2,0]
p4 = [-2,2,0]
p5 = [0,0,3]
verts = [p1,p2,p3,p4,p5]
# no need to define the edges, they will be created automatically
edges = []
# then we define the 5 faces: the base, and the 4 triangles. 
# We need to give the position of each vertex in the list:
f1 = [0,1,2,3]
f2 = [0,1,4]
f3 = [1,2,4]
f4 = [2,3,4]
f5 = [3,0,4]
faces = [f1,f2,f3,f4,f5]
mymesh.from_pydata(verts, edges, faces)
# we always need to do this after adding or removing elements in a mesh:
mymesh.update()

The from_pydata method is a very convenient way to create a mesh from scratch in one sole operation, but you can also do it a more detailed way by adding the verts to mymesh.vertices, and the faces to mymesh.polygons (see here). When our mesh is ready, the last thing to do is to create an object from it, and add it to the scene:

obj = bpy.data.objects.new("Pyramid", mymesh)
scene.objects.link(obj)

Removing data

Removing data is even easier than creating. To delete an object, you just have to unlink it from its scene. Next time Blender will save the file, the unused data (in this case, our object, if it is not linked by any scene), will be deleted automatically (this is the same behaviour as when you delete an object from the GUI):

scn = Scene.GetCurrent()
myobj = Object.Get("Pyramid")
scn.unlink(myobj)

When we want to removing subelements (vertices, faces) from an object, we face a bigger problem, the same as when modifying something: How to know which face to delete? We saw that the faces in the list of faces can be accessed by index number, but how do we know which number corresponds to which face in the mesh?

The blender API itself responds this question: If we explore the contents of the mesh data, we notice that vertices and polygons have add() functions, but no delete() functions. You don't delete subelements like that in Blender. You select them first, then delete what is selected, all with operators:

First, we deselct everything in the scene, and set our object as the active object:

bpy.ops.object.select_all(action="DESELECT")
obj = bpy.context.scene.objects['Cube']
obj.select = True
bpy.context.scene.objects.active = obj

Then we deselect everything (because some faces might be selected already), and select the first face. Warning, in Blender you HAVE to be in object mode to mark subcomponents as selected. I know, it seems very illogical, but it's the way it works...

bpy.ops.object.mode_set(mode = 'EDIT' )
bpy.ops.mesh.select_all(action="DESELECT")
bpy.ops.object.mode_set(mode = 'OBJECT' )
obj.data.polygons[0].select = True

Then, all we need to do is enter edit mode again, and delete (using Face mode, of course.)

bpy.ops.object.mode_set(mode = 'EDIT' )
bpy.ops.mesh.delete(type="FACE")
bpy.ops.object.mode_set(mode = 'OBJECT' )

But as I wrote above, the big difficulty is to know which one is polygon[0]. So in a real-world case, probably you will already have the faces to be deleted selected, so you would only use the 3 lines above to delete them.

Making an operator

Now that we are quickly becoming experts at manipulating Blender data, let's imagine we have an operation we repeat often, and want to create an operator with it, so we don't need to retype all the sequence everytime, we can just call our operator, one line of code, and it's done!

To make an operator is not difficult when we already know what it will do and how to do it. For example, let's say we want to make an operator that deletes the first face of a selected object. Easy, we just did this above, right? Then, to define an operator with it, we'll just need to pack the above code in a special structure. For this, it is better to use the text editor (see above), because we don't want to execute the code line by line, we want to save it and execute it in one block later (yes, I'm already also thinking to use it as an addon later!)

First, don't forget that the bpy module is only imported by default in the console. Everywhere else, we need to import it:

import bpy

Then we can create our operator:

class Delete_first_face(bpy.types.Operator):
    bl_idname = "mesh.delete_first_face"
    bl_label = "Deletes the first face of the active object"
    
    def execute(self, context):
        obj = bpy.context.active_object
        bpy.ops.object.mode_set(mode = 'EDIT' )
        bpy.ops.mesh.select_all(action="DESELECT")
        bpy.ops.object.mode_set(mode = 'OBJECT' )
        obj.data.polygons[0].select = True
        bpy.ops.object.mode_set(mode = 'EDIT' )
        bpy.ops.mesh.delete(type="FACE")
        bpy.ops.object.mode_set(mode = 'OBJECT' )
        return {'FINISHED'}

Simple, no? With the keyword "class", we defined a python class. I won't enter the details (read here to know more), but think of it as a kind of blueprint that can be used to create objects (python objects, not blender objects). Those objects inherit what we define inside the class. Here, the important parts to understand is that we create a custom class that we called "Delete_first_face", and it is a modified copy of another existing class, called bpy.types.Operator. The blender developers have created that class exactly for that purpose, to be used by scriptwriters like us as a base for their own operators.

So we really need to add very few info to such class: define a couple of parameters, such as bl_idname, which is the name of the operator (where it will live inside bpy.ops), and a more human-friendly description of what it does. Then, one last piece has to be defined, the most important, what will that operator do? That is what goes inside the execute() function. All operators have that function, it gets executed when the operator is called somewhere. So all we need to do is put our code inside, and finish with return {"FINISHED"}.

Now that we have our operator, we need one last bit: add it to the list of Blender operators. For that, we do this:

bpy.utils.register_class(Delete_first_face)

After that, simply run the script inside the text editor (Alt+P), and our operator will be available in the console:

Of course this is a very simple example. If you close blender, the operator is gone, and we'll need to run this code again. But that is why addons exist, we'ĺl come to that now.

One last thing, the blender text editor has several easy-to-use templates to create such operators quickly. Look in the "Templates" menu of the text editor...

Making an addon

Making an addon can be something very complex. Here is a very good tutorial on creating a more complex one. Here, we will do simple: We will just reuse our operator above, transform it into an addon, and add an item to the "Object" menu in Blender. I realize now that we should have called our operator object.delete_first_face instead of mesh.delete_first_face, so we will correct that too. To turn our script above into an addon, we first need to add a header with some info:

bl_info = {
    "name": "Delete first face",
    "author": "Yorik van Havre ",
    "description": "Deletes the first face of the active object",
    "version": (0, 1),
    "blender": (2, 5, 8),
    "category": "Object",
    "location": "Object > Delete first face",
    "warning": "",
    "wiki_url": "",
    "tracker_url": ""
    }

If we are going to distribute our addon, it's a good idea to also include a license text. GPL is a good choice. it is the license used by Blender itself. Then we add our operator itself, and a couple of additional functions to register it and add a menu entry. Also I modified the class name to respect the convention that other blender addons are using. The complete code looks like this:

bl_info = {
    "name": "Delete first face",
    "author": "Yorik van Havre ",
    "description": "Deletes the first face of the active object",
    "version": (0, 1),
    "blender": (2, 5, 8),
    "category": "Object",
    "location": "Object > Delete first face",
    "warning": "",
    "wiki_url": "",
    "tracker_url": ""
    }
    
# ***** 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 th
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****

import bpy

class OBJECT_OT_delete_first_face(bpy.types.Operator):
    bl_idname = "object.delete_first_face"
    bl_label = "Deletes the first face of the active object"
    
    def execute(self, context):
        obj = bpy.context.active_object
        bpy.ops.object.mode_set(mode = 'EDIT' )
        bpy.ops.mesh.select_all(action="DESELECT")
        bpy.ops.object.mode_set(mode = 'OBJECT' )
        obj.data.polygons[0].select = True
        bpy.ops.object.mode_set(mode = 'EDIT' )
        bpy.ops.mesh.delete(type="FACE")
        bpy.ops.object.mode_set(mode = 'OBJECT' )
        return {'FINISHED'}
        
def add_operator(self, context):
    # Register our operator
    self.layout.operator(OBJECT_OT_delete_first_face.bl_idname, text="Delete first face", icon='PLUGIN' )

def register():
    # Add our operator to the "Object" menu
    bpy.utils.register_class(OBJECT_OT_delete_first_face)
    bpy.types.VIEW3D_MT_object.append(add_operator)

def unregister():
    # Remove our operator from the "Object" menu
    bpy.utils.unregister_class(OBJECT_OT_delete_first_face)
    bpy.types.VIEW3D_MT_object.remove(add_operator)

if __name__ == "__main__":
    # This allows us to import the script without running it
    register()

Then, we can run that script from the text editor (Text -> Run script, or Alt+P). It will not be permanent, but we can check if all went okay. If no error arises, and if the script does what it is meant to do, then we can consider it finished, and install it permanently, from the Preferences screen (addons -> from file...)


Conclusion

That's about it for this tutorial, I hope you liked, don't forget to leave a comment! Next one will be about FreeCAD!


First and foremost, your name:

And your message:

To publish it, just press this ...