Chapter 9. Expanding your Toolset

This chapter is less about rendering and more about making life easier for the day-to-day use of Blender by extending its functionality. It uses some external libraries that need to be installed, and at some point the Python scripting used is perhaps a little bit harder to read for a novice. Also, from an artist's point of view, it might be a little less visually pleasing as these scripts don't lend themselves to pretty illustrations. Nevertheless, these scripts do add genuine useful functionality, especially for a script developer, so please read on.

Expanding your Toolset

In this chapter, we will learn how to:

  • List and archive assets such as image maps
  • Publish a rendered image automatically with FTP
  • Extend the functionality of the built-in editor with regular expression searches
  • Speed up computations by using Psyco—a just-in-time compiler
  • Add version control to your scripts with Subversion

To the Web and beyond—publish a finished render with FTP

We can save a rendered image to any location as long as it is visible in the filesystem, but not all platforms offer the possibility to make a remote FTP server accessible via a local directory (folder). This script offers us a simple option to store a rendered image on a remote FTP server and remembers the server name, the username, and (optionally) the password for later reuse.

The File Transfer Protocol (FTP) that we will be using is somewhat more complicated than, for instance, the HTTP protocol as it uses more than one connection. Fortunately for us, all the intricacies of an FTP client are nicely encapsulated in the standard Python module ftplib. We not only import this module's FTP class but a number of other standard Python modules as well, notably those for pathname manipulation (os.path) and for reading the standard .netrc file (which enables us to store passwords outside our script if we need passwords to log in to the FTP server). We will discuss each module where necessary.

from ftplib import FTP
import os.path
import re
import netrc
import tempfile
from Blender import Image,Registry,Draw

Python is almost as platform independent as it gets, but of course, sometimes there are intricacies that are not fully covered. For example, we want to use usernames and passwords stored in a .netrc file that is commonly used by FTP programs (and others) and the FTP client expects this file to reside in the user's home directory, which it hopes to find in an environment variable HOME. On Windows, however, the concept of a home directory isn't that well defined and different schemes exist to store data that is restricted to a single user; not every implementation of Python resolves this in the same way.

We, therefore, define a small utility function that checks if there is a HOME variable present in the environment (always the case on Unix-like operating systems and on some versions of Windows). If not, it checks whether the USERPROFILE variable is present (present on most versions of Windows including XP where it typically points to a directory C:Documents and Settings<yourusername>). If it is present it sets the HOME variable to the contents of this USERPROFILE variable:

def sethome():
from os import environ
if not 'HOME' in environ:
if 'USERPROFILE'in environ:
environ['HOME'] = environ['USERPROFILE']

Our next task is to find out which FTP server the user wants to upload the rendered result to. We store this in a Blender registry key so that we don't have to bother the user with a prompt each time he wants to upload a render. The getftphost() function takes an argument reuse that may be used to clear this key if set to False (to allow for the possibility of choosing a different FTP server), but rewriting the user interface to offer the user such an option is left as an exercise to the reader.

The actual code starts with retrieving the key from the registry (from disk if necessary, hence the True argument, highlighted). If there isn't a key present or it doesn't contain a host entry, we prompt the user for the name of the FTP server with a pop up. If the user does not specify, one we bail out by raising an exception. Otherwise, we store the hostname in the host entry—first create the dictionary if it is not present and store this dictionary in Blender's registry. Finally, we return the stored hostname.

def getftphost(reuse=True):
dictname = 'ftp'
if reuse == False:
Registry.RemoveKey(dictname)
d = Registry.GetKey(dictname,True)

if d == None or not 'host' in d:
host = Draw.PupStrInput("Ftp hostname:", "", 45)
if host == None or len(host) == 0 :
raise Exception("no hostname specified")
if d == None :
d ={}
d['host'] = host
Registry.SetKey(dictname,d,True)
return d['host']

We need another utility function to make sure that a Blender image is stored on disk as the last rendered image is present as an image with the name Render Result, but this image isn't written to disk automatically. The function imagefilename() takes a Blender image as an argument and first checks if it has a valid filename associated with it (highlighted). If not, it creates a filename from the name of the image by appending a .tga extension (images can be saved as TARGA files only). The full path is then constructed from this filename and the path of the temp directory. Now when there is a valid filename present it is saved to call the save() method and return the filename:

def imagefilename(im):
filename = im.getFilename()
if filename == None or len(filename) == 0:

filename = im.getName()+'.tga'
filename = os.path.join(tempfile.gettempdir(),filename)
im.setFilename(filename)
im.save()
return filename

When we upload a file to an FTP server we want to make sure that we do not overwrite any existing file. If we do find that a file with a given name is already present we'd like to have a function that creates a new filename in a predictable fashion—much like the way Blender behaves when creating names for Blender objects. We'd like to preserve the extension of the filename so we cannot simply stick to a numerical suffix. The nextfile() function, therefore, starts by splitting the pathname and extension parts of the filename. It uses the split() and splitext() functions from the os.path module to leave us with the bare name.

If the name already ends in a suffix consisting of a dot and some number (for example, .42) we'd like to increment this number. This is exactly what the rather daunting highlighted lines accomplish. The sub() function of Python's re module takes a regular expression as a first argument (we use a raw string here so we don't have to escape any backslashes) and checks whether this regular expression matches its third argument (name in this case). The regular expression used here (.(d+)$) matches a dot followed by one or more decimal digits if and only if these digits are the last characters. If this pattern does match it is replaced by the second argument of the sub() function. In this case the replacement is not a simple string but a lambda (that is, unnamed) function that will be passed a match object and is expected to return a string.

As we surrounded the digits part of our regular expression with parentheses, we can retrieve just these digits—without the leading dot—with a call to the match object's group() method. We pass it a 1 as argument, as the first opening parenthesis marks the first group (group 0 would be the whole pattern). We convert this string of digits to an integer by using the built-in int() function, add 1 to it, and convert it back again to a string with the str() function. Before this result is automatically returned from the lambda function we prepend a dot again to conform to our desired pattern.

We finish by checking if the resulting name is different from the original one. If they are the same the original name did not match our pattern and we just append .1 to the name. Finally, we reconstruct the full filename by adding the extension and calling the join() function from os.path to add the path in a platform-independent way:

def nextfile(filename):
(path,base) = os.path.split(filename)
(name,ext) = os.path.splitext(base)
new = re.sub(r'.(d+)$',lambda m:'.'+str(1+int(m.group(1))),name)

if new == name :
new = name + '.1'
return os.path.join(path,new+ext)

Now, we are all set to do the real work of uploading a file to an FTP server. First, we make sure that our environment has a suitable HOME variable by calling the sethome() function. Then, we retrieve the hostname of the FTP server we want to upload to (it is perfectly valid, by the way, to enter an IP address instead of a hostname):

if __name__ == "__main__":
sethome()
host = getftphost()

Next, we retrieve the user's credentials for the selected host from the .netrc file if there is one present (highlighted). This may fail for various reasons (there might not be a .netrc file or the given host has no entry in this file); in which case an exception will be raised. If this happens we inform the user and ask for a username and password instead with suitable pop ups:

try:
(user,acct,password) = netrc.netrc().authenticators(host)

except:
acct=None
user = Draw.PupStrInput('No .netrc file found, enter username:',
"",75)
password = Draw.PupStrInput('Enter password:',"",75)

The rendered image will have been stored as a Blender Image object with the name Render Result. The next thing we do is retrieve a reference to this image and make sure it is stored on disk. The imagefilename() function that we defined earlier will return the filename of the stored image.

The next step is to connect to the FTP server by using the hostname and credentials we retrieved earlier (highlighted). Once the connection is established we retrieve a list of filenames with the nlst() method:

im = Image.Get('Render Result')
filename = imagefilename(im)
ftp = FTP(host,user,password,acct)

files = ftp.nlst()

Because we want to make sure that we do not overwrite any files on the FTP server, we strip the path from the filename of our stored image with the basename() function and compare the result to the list of filenames retrieved from the server (highlighted). If the filename is already present we generate a new filename with the nextfile() function and check again and keep on doing that until we finally have a filename that isn't used yet on the FTP server.

dstfilename = os.path.basename(filename)
while dstfilename in files:

dstfilename = nextfile(dstfilename)

Then, we upload our image file by calling the storbinary() method. This method will take the destination filename prefixed with STOR as the first argument and an open file descriptor as the second argument. We provide the latter by calling Python's built-in open() function with the name of our image file as the single argument. (For more details on the rather outlandish behavior of the ftplib module, refer to its documentation on http://docs.python.org/library/ftplib.html.) We gracefully end the connection to the FTP server by calling the quit() method and inform the user about the completion of the task by showing a message that mentions the destination filename as this might be different than expected if a similarly named file exists:

ftp.storbinary('STOR '+dstfilename,open(filename))
ftp.quit()
Draw.PupMenu('Render result stored as "%s"%s|Ok' %(dstfilename,'%t'))

The full code is available as ftp.py in ftp.blend. It may be run from the text editor but in this case it is certainly far more convenient to put ftp.py in Blender's scripts directory. The script is configured to make itself available in the File | Export menu.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.189.251