(Read more interesting articles on Blender 2.49 Scripting here.)
Script links are scripts that may be associated with Blender objects (Meshes, Cameras, and so on, but also Scenes and World objects) and that can be set up to run automatically on the following occasions:
- Just before rendering a frame
- Just after rendering a frame
- When a frame is changed
- When an object is updated
- When the object data is updated
Scene objects may have script links associated with them that may be invoked on two additional occasions:
- On loading a .blend file
- On saving a .blend file
Space handlers are Python scripts that are invoked each time the 3D view window is redrawn or a key or mouse action is detected. Their primary use is to extend the capabilities of Blender's user interface.
Animating the visibility of objects
An often recurring issue in making an animation is the wish to make an object disappear or fade away at a certain frame, either for the sake of the effect itself or to replace the object by another one to achieve some dramatic impact (such as an explosion or a bunny rabbit changing into a ball).
There are many ways to engineer these effects, and most of them are not specifically tied to script links reacting on a frame change (many can simply be keyed as well). Nevertheless, we will look at two techniques that may easily be adapted to all sorts of situations, even ones that are not easily keyed. For example, we demand some specific behavior of a parameter that is easy to formulate in an expression but awkward to catch in an IPO.
Fading a material
Our first example will change the diffuse color of a material. It would be just as simple to change the transparency, but it is easier to see changes in diffuse color in illustrations.
Our goal is to fade the diffuse color from black to white and back again, spaced over a period of two seconds. We therefore define a function setcolor() that takes a material and changes its diffuse color (the rgbColor attribute). It assumes a frame rate of 25 frames per second and, therefore, the first line fetches the current frame number and performs a modulo operation to determine what fraction of the current whole second is elapsed.
The highlighted line in the following code snippet determines whether we are in an odd or even second. If we are in an even second, we ramp up the diffuse color to white so we just keep our computed fraction. If we are in an odd second, we tone down the diffuse color to black so we subtract the fraction from the maximum possible value (25). Finally, we scale our value to lie between 0 and 1 and assign it to all three color components to obtain a shade of gray:
s = Blender.Get('curframe')%25
if int(Blender.Get('curframe')/25.0)%2 == 0:
c = s
c = 25-s
c /= 25.0
mat.rgbCol = [c,c,c]
if Blender.bylink and Blender.event == 'FrameChanged':
The script ends with an important check: Blender.bylink is True only if this script is called as a script handler and in that case Blender.event holds the event type. We only want to act on frame changes so that is what we check for here. If these conditions are satisfied, we pass Blender.link to our setcolor() function as it holds the object our scriptlink script is associated with—in this case that will be a Material object. (This script is available as MaterialScriptLink.py in scriptlinks.blend- download full code from here)
The next thing on our list is to associate the script with the object whose material we want to change. We therefore select the object and in the Buttons Window we select the Script panel. In the Scriptlinks tab, we enable script links and select the MaterialScriptLinks button. (If there is no MaterialScriptLinks button then the selected object has no material assigned to it. Make sure it has.) There should now be a label Select Script link visible with a New button. Clicking on New will show a dropdown with available script links (files in the text editor). In this case, we will select MaterialScriptLink.py and we are done. We can now test our script link by changing the frame in the 3D view (with the arrow keys). The color of our object should change with the changing frame number. (If the color doesn't seem to change, check whether solid or shaded viewing is on in the 3D view.)
If we want to change the visibility of an object, changing the layer(s) it is assigned to is a more general and powerful technique than changing material properties. Changing its assigned layer has, for instance, the advantage that we can make the object completely invisible for lamps that are configured to illuminate only certain layers and many aspects of an animation (for example, deflection of particles by force fields) may be limited to certain layers as well. Also, changing layers is not limited to objects with associated materials. You can just as easily change the layer of a Lamp or Camera.
For our next example, we want to assign an object to layer 1 if the number of elapsed seconds is even and to layer 2 if the number of seconds is odd. The script to implement this is very similar to our material changing script. The real work is done by the function setlayer(). The first line calculates the layer the object should be on in the current frame and the next line (highlighted) assigns the list of layer indices (consisting of a single layer in this case) to the layers attribute of the object. The final two lines of the setlayer() function ensure that the layer's change is actually visible in Blender.
layer = 1+int(Blender.Get('curframe')/25.0)%2
ob.layers = [ layer ]
if Blender.bylink and Blender.event == 'FrameChanged':
As in our previous script, the final lines of our script check whether we are called as a script link and on a frame change event, and if so, pass the associated object to the setlayer() function. (The script is available as OddEvenScriptlink.py in scriptlinks.blend.)
All that is left to do is to assign the script as a scriptlink to a selected object. Again, this is accomplished in the Buttons Window | Script panel by clicking on Enabling Script Links in the Scriptlinks tab (if necessary, it might still be selected because of our previous example. It is a global choice, that is, it is enabled or disabled for all objects). This time, we select the object scriptlinks instead of the material scriptlinks and click on New to select OddEvenScriptlink.py from the dropdown.
Countdown—animating a timer with script links
One of the possibilities of using a script link that acts on frame changes is the ability to modify the actual mesh either by changing the vertices of a Mesh object or by associating a completely different mesh with a Blender object. This is not possible when using IPOs as these are limited to shape keys that interpolate between predefined shapes with the same mesh topology (the same number of vertices connected in the same way). The same is true for curves and text objects.
One application of that technique is to implement a counter object that will display the number of seconds since the start of the animation. This is accomplished by changing the text of a Text3d object by way of its setText() method. The setcounter() function in the following code does exactly that together with the necessary actions to update Blender's display. (The script is available as CounterScriptLink.py in scriptlinks.blend.)
seconds = int(Blender.Get('curframe')/25.0)+1
countertxt = Blender.Text3d.New(objectname)
scn = Blender.Scene.GetCurrent()
counterob = scn.objects.new(countertxt)
This script may be associated as a script link with any Text3d object as shown before. However, if run with Alt + P from the text editor it will create a new Text3d object and will associate itself to this object as a script link. The highlighted lines show how we check for this just like in the previous scripts, but in this case we take some action when not called as a script link as well (the else clause). The final two highlighted lines show how we associate the script with the newly created object. First, we remove (clear) any script links with the same name that might have been associated earlier. This is done to prevent associating the same script link more than once, which is valid but hardly useful. Next, we add the script as a script link that will be called when a frame change occurs. The screenshot shows the 3D view with a frame from the animation together with the Buttons window (top-left) that lists the association of the script link with the object.
Note that although it is possible to associate a script link with a Blender object from within a Python script, script links must be enabled manually for them to actually run! (In the ScriptLinks tab.) There is no functionality in the Blender Python API to do this from a script.
I'll keep an eye on you
Sometimes, when working with a complex object, it is difficult to keep track of a relevant feature as it may be obscured by other parts of the geometry. In such a situation, it would be nice to highlight certain vertices in a way that keeps them visible, no matter the orientation, and independent of the edit mode.
Space handlers provide us with a way to perform actions each time the 3D view window is redrawn or a key or mouse action is detected. These actions may include drawing inside the 3D view area as well, so we will be able to add highlights at any position we like.
How do we determine which vertices we would like to highlight? Blender already provides us with a uniform way to group collections of vertices as vertex groups so all we have to do is let the user indicate which vertex group he would like to highlight. We then store the name of this selected vertex group as an object property. Object properties are designed to be used in the game engine but there is no reason why we shouldn't reuse them as a way to persistently store our choice of vertex group.
So again, we have a script that will be called in two ways: as a space handler (that is, each time the 3D view window is redrawn to highlight our vertices) or by running it from the text editor with Alt + P to prompt the user to choose a vertex group to highlight.
Code outline: AuraSpaceHandler.py
The following outline shows which steps we will take in each situation:
- Get active object and mesh.
- If running standalone:
- Get list of vertex groups
- Prompt user for choice
- Store choice as property of object
- Get the property that holds the vertex group
- Get a list of vertex coordinates
- For each vertex:
- draw a small disk
The resulting code is available as AuraSpaceHandler.py in scriptlinks.blend:
It starts with a line of comment that is essential, as it signals to Blender that this is a space handler script that can be associated with the 3D view (no other area can have space handlers associated at present) and should be called on a redraw event.
from Blender import *
scn = Scene.GetCurrent()
ob = scn.objects.active
if ob.type == 'Mesh':
me = ob.getData(mesh = True)
vlist = me.getVertsFromGroup(p.getData())
matrix = ob.matrix
drawAuras([me.verts[vi].co*matrix for vi in vlist],p.getData())
groups = ['Select vertexgroup to highlight%t']
result = Draw.PupMenu( '|'.join(groups) )
The script proper then proceeds to retrieve the active object from the current scene and gets the object's mesh if it is a Mesh. At the highlighted line, we check if we are running as space handler and if so, we fetch the property that we named Highlight. The data of this property is the name of the vertex group that we want to highlight. We proceed by getting a list of all vertices in this vertex group and by getting the matrix of the object. We need this because vertex locations are stored relative to the object's matrix. We then construct a list of vertex locations and pass this along with the name of the vertex group to the drawAuras() function that will take care of the actual drawing.
The second highlighted line marks the beginning of the code that will be executed when we run the script from the text editor. It creates a string consisting of the names of all vertex groups associated with the active object separated by pipe characters (|) and prepended by a suitable title. This string is passed to PopMenu() which will display the menu, and will either return with the user's choice or with -1, if nothing was chosen.
If there was a vertex group chosen, we try to retrieve the Highlight property. If this succeeds we set its data to the name of the chosen vertex group. If the property did not yet exist, we add a new one with the name Highlight and again with the name of the chosen vertex group as data.
Next we have to make sure that scriptlinks are enabled (Buttons window Scripts panel | Scriptlinks|. Click on enable scriptlinks if not yet selected.). Note that to Blender it makes no difference whether we are dealing with space handlers or script links as far as enabling them is concerned.
The final step in using our space handler is associating it with the 3D view. To do this toggle the entry Draw: AuraSpaceHandler.py in the view (Space Handler Scripts menu of the 3D view).
The code we haven't seen yet deals with the actual drawing of the highlights and the name of the vertex group to identify what we are highlighting. It starts off by determining the colors we will use for the highlights and the text by retrieving these from the current theme. This way the user can customize these colors in a convenient way from the User Preferences window:
theme = Window.Theme.Get()
textcolor = [float(v)/255 for v in theme.get(
color = [float(v)/255 for v in
The first line will retrieve a list of themes that are present. The first one is the active theme. From this theme we retrieve the VIEW3D theme space and its text_hi attribute is a list of four integers representing a RGBA color. The list comprehension discards the alpha component and converts it to a list of three floats in the range [0, 1] that we will use as our text color. Likewise, we construct the color of the highlights from the active attribute.
Our next challenge is to draw a disk-shaped highlight at a specified location. As the size of the disk is quite small (it can be adjusted by altering the size variable), we can approximate it well enough by an octagonal shape. We store the list of x, y coordinates of such an octagon in the diskvertices list:
diskvertices=[( 0.0, 1.0),( 0.7, 0.7),
( 1.0, 0.0),( 0.7,-0.7),
(-1.0, 0.0),(-0.7, 0.7)]
for x,y in diskvertices:
The actual drawing of the octagon depends heavily on the functions provided by Blender's BGL module (highlighted in the previous code). We start by stating that we will be drawing a polygon and then add a vertex for each tuple in the diskvertices list. The location passed to drawDisk() will be the center and the vertices will all lie on a circle with a radius equal to size. When we call the glEnd() function, the filled-in polygon will be drawn in the current color.
You might wonder how these drawing functions know how to translate locations in 3D to coordinates on the screen and there is indeed more here than meets the eye as we will see in the next section of code. The necessary function calls to inform the graphics system how to convert 3D coordinates to screen coordinates is not implemented in the drawDisk() function (preceding code snippet). This is because calculating this information for each disk separately would incur an unnecessary performance penalty as this information is the same for each disk we draw.
We therefore define a function, drawAuras(), which will take a list of locations and a groupname argument (a string). It will calculate the transformation parameters, call drawDisk() for each location in the list, and will then add the group name as an on-screen label at approximately just right of the center the highlights. Blender's Window module provides us with the GetPerspMatrix() function that will retrieve the matrix that will correctly convert a point in 3D space to a point on the screen. This 4 by 4 matrix is a Python object that will have to be converted to a single list of floats that can be used by the graphics system. The highlighted lines in the following code take care of that. The next three lines reset the projection mode and tell the graphics system to use our suitably converted perspective matrix to calculate screen coordinates. Note that changing these projection modes and other graphics settings does not affect how Blender itself draws things on screen, as these settings are saved before calling our script handler and restored afterward:
viewMatrix = Window.GetPerspMatrix()
viewBuff = [viewMatrix[i][j] for i in xrange(4)
for j in xrange(4)]
viewBuff = BGL.Buffer(BGL.GL_FLOAT, 16, viewBuff)
for loc in locations:
x=sum([l for l in locations])/n
y=sum([l for l in locations])/n
z=sum([l for l in locations])/n
With the preliminary calculations out of the way we can set the color we will draw our disks in with the glColor3f() function. As we stored the color as a list of three floats and the glColor3f() function takes three separate arguments, we unpack this list with the asterisk operator. Next, we call drawDisk() for each item in locations.
Blender OpenGL functions:
The documentation of Blender's BGL module lists a large number of functions from the OpenGL library. Many of these functions come in a large number of variants that perform the same action but receive their arguments in different ways. For example, BGL.glRasterPos3f() is a close relation to BGL.glRasterPos3fv() that will take a list of three single-precision float values instead of three separate arguments. For more information, refer to the API documentation of the Blender.BGL and Blender.Draw modules and the OpenGL reference manual on http://www.opengl.org/sdk/docs/man/.
If the number of highlights we have drawn is not zero, we set the drawing color to textcolor and then calculate the average coordinates of all the highlights. We then use the glRasterPos3f() function to set the starting position of the text that we want to draw to these average coordinates with some extra space added to the x-coordinate to offset the text a little to the right. Blender's Draw.Text() function will then draw the group name in a small font at the chosen location.
In this article, we learned how to link change to the progress of the animation frames and how to associate state information with an object. We also saw how to change layers, for example to render an object invisible. Specifically we saw:
- What script links and space handlers are
- How to perform activities on each frame change in an animation
- How to associate additional information with an object
- How to make an object appear or disappear by changing lay or changing its transparency
In the next article we will learn how to implement a scheme to associate a different mesh with an object on each frame and how to augment the functionality of the 3DView
- Blender 2.49 Scripting: Impression using Different Mesh on Each Frame of Object
- Blender 2.49 Scripting: Shape Keys, IPOs, and Poses