6 min read

(Read more interesting articles on Blender 2.49 Scripting here.)

Revisiting mesh—making an impression

The following illustration gives some impression of what is possible. The tracks are created by animating a rolling car tire on a subdivided plane:

In the following part, we will refer to the object mesh being deformed as the source and the object, or objects, doing the deforming as targets. In a sense, this is much like a constraint and we might have implemented these deformations as pycontraints. However, that wouldn’t be feasible because constraints get evaluated each time the source or targets move; thereby causing the user interface to come to a grinding halt as calculating the intersections and the resulting deformation of meshes is computationally intensive. Therefore, we choose an approach where we calculate and cache the results each time the frame is changed.

Our script will have to serve several functions, it must:

  • Calculate and cache the deformations on each frame change
  • Change vertex coordinates when cached information is present

And when run standalone, the script should:

  • Save or restore the original mesh
  • Prompt the user for targets
  • Associate itself as a script link with the source object
  • Possibly remove itself as a script link

An important consideration in designing the script is how we will store or cache the original mesh and the intermediate, deformed meshes. Because we will not change the topology of the mesh (that is, the way vertices are connected to each other), but just the vertex coordinates, it will be sufficient to store just those coordinates. That leaves us with the question: where to store this information.

If we do not want to write our own persistent storage solution, we have two options:

  • Use Blender’s registry
  • Associate the data with the source object as a property

Blender’s registry is easy to use but we must have some method of associating the data with an object because it is possible that the user might want to associate more than one object with an impression calculation. We could use the name of the object as a key, but if the user would change that name, we would lose the reference with the stored information while the script link functionality would still be there. This would leave the user responsible for removing the stored data if the name of the object was changed.

Associating all data as a property would not suffer from any renaming and the data would be cleared when the object is deleted, but the types of data that may be stored in a property are limited to an integer, a floating point value, or a string. There are ways to convert arbitrary data to strings by using Python’s standard pickle module, but, unfortunately, this scenario is thwarted by two problems:

  • Vertex coordinates in Blender are Vector instances and these do not support the pickle protocol
  • The size of string properties is limited to 127 characters and that is far too small to store even a single frame of vertex coordinates for a moderately sized mesh

Despite the drawbacks of using the registry, we will use it to devise two functions—one to store vertex coordinates for a given frame number and one to retrieve that data and apply it to the vertices of the mesh. First, we define a utility function ckey() that will return a key to use with the registry functions based on the name of the object whose mesh data we want to cache(download full code from here):

def ckey(ob):
return meshcache+ob.name

Not all registries are the same
Do not confuse Blender’s registry with the Windows registry. Both serve the similar purpose of providing a persistent storage for all sorts of data, but both are distinct entities. The actual data for Blender registry items that are written to disk resides in .blender/scripts/bpydata/config/ by default and this location may be altered by setting the datadir property with Blender.Set().

Our storemesh() function will take an object and a frame number as arguments. Its first action is to extract just the vertex coordinates from the mesh data associated with the object. Next, it retrieves any data stored in Blender’s registry for the object that we are dealing with and we pass the extra True parameter to indicate that if there is no data present in memory, GetKey() should check for it on disk. If there is no data stored for our object whatsoever, GetKey() will return None, in which case we initialize our cache to an empty dictionary.

Subsequently, we store our mesh coordinates in this dictionary indexed by the frame number (highlighted in the next code snippet). We convert this integer frame number to a string to be used as the actual key because Blender’s SetKey() function assumes all of the keys to be strings when saving registry data to disk, and will raise an exception if it encounters an integer. The final line calls SetKey() again with an extra True argument to indicate that we want the data to be stored to disk as well.

def storemesh(ob,frame):
coords = [(v.co.x,v.co.y,v.co.z) for v in ob.getData().verts]
d=Blender.Registry.GetKey(ckey(ob),True)
if d == None: d={}
d[str(frame)]=coords
Blender.Registry.SetKey(ckey(ob),d,True)

The retrievemesh() function will take an object and a frame number as arguments. If it finds cached data for the given object and frame, it will assign the stored vertex coordinates to vertices in the mesh. We first define two new exceptions to indicate some specific error conditions retrievemesh() may encounter:

class NoSuchProperty(RuntimeError): pass;
class NoFrameCached(RuntimeError): pass;

retrievemesh() will raise the NoSuchProperty exception if the object has no associated cached mesh data and a NoFrameCached exception if the data is present but not for the indicated frame. The highlighted line in the next code deserves some attention. We fetch the associated mesh data of the object with mesh=True. This will yield a wrapped mesh, not a copy, so any vertex data we access or alter will refer to the actual data. Also, we encounter Python’s built-in zip() function that will take two lists and returns a list consisting of tuples of two elements, one from each list. It effectively lets us traverse two lists in parallel. In our case, these lists are a list of vertices and a list of coordinates and we simply convert these coordinates to vectors and assign them to the co-attribute of each vertex:

def retrievemesh(ob,frame):
d=Blender.Registry.GetKey(ckey(ob),True)
if d == None:
raise NoSuchProperty("no property %s for object %s"
%(meshcache,ob.name))
try:
coords = d[str(frame)]
except KeyError:
raise NoFrameCached("frame %d not cached on object %s"
%(frame,ob.name))
for v,c in zip(ob.getData(mesh=True).verts,coords):
v.co = Blender.Mathutils.Vector(c)

To complete our set of cache functions we define a function clearcache() that will attempt to remove the registry data associated with our object. The try … except … clause will ensure that the absence of stored data is silently ignored:

def clearcache(ob):
try:
Blender.Registry.RemoveKey(ckey(ob))
except:
pass

LEAVE A REPLY

Please enter your comment!
Please enter your name here