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 pythoninside 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 leasta very basic understanding of the Python language. It will focus on Blender, and showwith simple example how python can be useful, and what you can do with it inBlender. 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, sincemost 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 pythontools that blender provides) allows you to access and modify almost anythingin Blender, including all the data of your model (scenes, objects, mesh data, lights,materials, textures, nodes, animation, etc...) and the blender interface itself (3Dview, buttons windows, menus, etc...). In fact, today, many of the features of Blender are entirely programmed in python. So you can basically modify objects, createobjects, and create tools with your own controls that will appear wherever you wantin the Blender interface. And finally, if you do something awesome, you can pack yourscript 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 openthe python console, by subdividing one window and setting it to "Python Console":
This is a python console that behaves exactly like the standard python terminalwe used in the Part 1 : Introduction. Except that it has a few more goodies: Specialkeyboard shortcuts, like it is written when you start the console, and some modulesare already conveniently imported for you (remember about importing modules in Part 1?), especially the module called "bpy", which contains about all that blenderoffers to python. So normally you should start any blender python script with:
But in this console it is not necessary. Another very important window is thetext editor:
This one serves to write text (or python scripts of course), and behaves a bitlike any text editor like notepad, except that it can show line numbers (usefulwhen python tells you there is an error on line X) and can paint special pythonwords in another color, very comfortable to read your script.
When writing python scripts, have always both windows open, so you can teststuff in the python console, and copy/paste it (and save it) to the text windowwhen you are sure that it works.
The python scripts you write in the text editor stay saved inside the blenderfiles when you save them, but can also be saved in separate .py files. Usually Ikeep my scripts inside a blender file while I am developing them, and when theywork perfectly I export them as .py files.
A last cool trick, CTRL + wheel mouse will zoom the text inside both pythonconsole 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 existingobjects, that you have been drawing with the user interface controls. Let's forexample, 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 thecurrent scene, the current selected object, the current view, the current stateof the interface, etc. You got the picture. Remember you can always explore thecontents of any python object with the dir() function. Here we'll take ourcurrent scene, and list the objects inside:
scene = bpy.context.scenefor 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 thecurrent 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'stry, for example, to take all the objects of the scene and filter out those which arenot 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 couldfind 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:
You might have noticed that the Python console also has an autocomplete system, so when you arein the middle of typing the above command, you can at any time press Ctrl + Space to show possiblecompletions of what you're typing. If you use that just after the "visible" word, you'll see thatautocomplete 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 itsMesh (rememberthe len() from Part 1?):
me = cube.dataprint (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 objectscontain 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.verticesprint (v.co)print (v.co.x)v.co.x = 5
"Amazing", you must be thinking. Indeed Python allows you to change about any property ofanything in Blender, in real-time, no further operation required. In older versions you hadto work on a copy of the mesh, then replace the mesh by its copy. Thanks to the big python-friendlyrecode 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 thatour mesh data contains subelements called vertices, edges and polygons. These elements canalso inform you of their vertices. But instead of giving you a vertice object, they give youthe index number of those vertices in the general list of vertices of the mesh:
face = me.polygonsfor v in face.vertices: print (v)
The above code shows you 4 numbers, which are the index numbers of the 4 vertices of thatface. 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 severaledges or faces. So it is easier to have them stored at one central place, and then referencethem as needed inside the mesh structure. Most mesh-based 3D applications do that. If wewant, 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 aboutthat at school (I know, you forgot)... Anyway, when dealing with programmingin 3D, vectors are everywhere. You can do awesome things with vectors, multiplythem, 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 toperform more complex operations on vertices. Blender also has a special module withtools to help you to perform operations on vectors (cross products, etc...):
from mathutils import Vectordir(Vector)
The tools that Blender offers you to manipulate objects go far further than simplyallowing you to access their components. Almost all the tools available on the UserInterface are also available to the python scriptwriter. The python version of thosetools is called -in Blender slang- an Operator. So all usual things such as movingan object, performing boolean operations, etc... are available in a compact, single-lineway. Most of them behave the way their corresponding GUI tool behave, that is, theyact on what is currently selected. That is, our famous context.
Operator are all stored inside bpy.ops, and they are grouped by category, such asobject 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:
This scales it 2 times in the X direction:
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.select = True
This deletes the selected face:
I suppose now you saw how it works and how useful it can be. And also how much you needto know in order to use them! Don't worry too much, though, it is fairly easy to find yourway between all the operators, and how exactly they must be used, thanks to the Blenderpython console's autocomplete feature (Ctrl+Space). Try for example writing this, thenpressing Ctrl+Space after ops. , after object. , and after mode:
Now that you begin to see how things work, adding basic objects can be as simple as this:
In the mesh section you'll find other default primitives to add. But this is a bittoo 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 weadd the data, then we create an object to contain the data, then add (link) the objectto the current scene:
scene = bpy.context.scenelamp_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 = Truescene.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 anyobject,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 centeredon the origin point (0,0,0). Its base would be a square of 4x4 units, and its top would be at aheight of 3 units:
# first we define the 5 points of our pyramid: the 4 corners of the base, and the top pointp1 = [-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 automaticallyedges = # 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 soleoperation, 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 is even easier than creating. To delete an object, you just have to unlink itfrom its scene. Next time Blender will save the file, the unused data (in this case, ourobject, if it is not linked by any scene), will be deleted automatically (this is the samebehaviour 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 = Truebpy.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.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. So in a real-worldcase, probably you will already have the faces to be deleted selected, so you would only usethe 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 anoperation we repeat often, and want to create an operator with it, so we don't need to retype allthe 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. Forexample, 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 wedon'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:
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.select = True bpy.ops.object.mode_set(mode = 'EDIT' ) bpy.ops.mesh.delete(type="FACE") bpy.ops.object.mode_set(mode = 'OBJECT' ) return
Simple, no? With the keyword "class", we defined a python class. I won't enter the details (readhere 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 anotherexisting class, called bpy.types.Operator. The blender developers have created that class exactlyfor 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, suchas bl_idname, which is the name of the operator (where it will live inside bpy.ops), and a morehuman-friendly description of what it does. Then, one last piece has to bedefined, 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 calledsomewhere. So all we need to do is put our code inside, and finish with return .
Now that we have our operator, we need one last bit: add it to the list of Blender operators. For that, we do this:
After that, simply run the script inside the text editor (Alt+P), and our operator willbe 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 suchoperators quickly. Look in the "Templates" menu of the text editor...
Making an addon
Making an addon can be something very complex. Hereis a very good tutorial on creating a more complex one. Here, we will do simple: We willjust 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_faceinstead of mesh.delete_first_face, so we will correct that too. To turn our script aboveinto an addon, we first need to add a header with some info:
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 conventionthat other blender addons are using. The complete code looks like this:
bl_info = # ***** 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 bpyclass 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.select = True bpy.ops.object.mode_set(mode = 'EDIT' ) bpy.ops.mesh.delete(type="FACE") bpy.ops.object.mode_set(mode = 'OBJECT' ) return 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 willnot be permanent, but we can check if all went okay. If no error arises, and if the scriptdoes what it is meant to do, then we can consider it finished, and install it permanently,from the Preferences screen (addons -> from file...)
That's about it for this tutorial, I hope you liked, don't forget to leave a comment!Next one will be about FreeCAD!