Python scripts
Picked up a macbook pro recently, really handy to have a proper unix backend wrapped in a nice interface. A side effect of this has been getting back into python. Pleased to find just about all my old scripts worked without change, so here they are. I've also made a little zip archive of all these scripts for your downloading pleasure.
tokeruscripts.zip
Note that this is python as it pertains to sequence wrangling, or batching image conversions, not python in maya. Haven't got into that yet (too comfortable with MEL now to bother relearning), but must make an effort to do so soon, if only for access to the API for prototyping. A google group has been setup for python-in-maya, take a look:
http://groups.google.com/group/python_inside_maya
Fix framebuffer pass-after-number naming
I've mentioned elsewhere that mentalray framebuffers will name stuff in the format image.0001_yourpass.tif, ie the pass comes after the numbering. This messed with my sense of neatness, but to my surprise shake, nuke, fcheck, after effects loaded it fine. However flame kindasorta broke; its compact sequence lister only shows the prefix, so all your passes are named the same. Thats justification for posting this script. To my surprise it runs fine from the maya python interpreter too, so thats nice. Eventually it should be made into a post-render mel to fix things on the fly, but enyhoo, this'll do for now. Run it from the folder of your image files. There's some options so that you can preview the renaming, and to force-rename if the target filename already exists.
#!/usr/bin/python
def correctBufferName(f='me014_sh010_leonFB.0001_exampleBuffer.iff'):
try:
d = {}
# grab the prefix, frame+buffer, suffix by splitting on the dot
d["prefix"], d["framebuffer"], d["suffix"] = f.split(".")
# now split the framenumber and buffer by splitting on the underscore
d["frame"], d["buffer"] = d["framebuffer"].split("_")
# join it back together in the right order, using underscores first
p = "_".join( (d["prefix"], d["buffer"]) )
# finally append the suffix with a dot
result = ".".join( (p, d["frame"], d["suffix"]) )
except ValueError:
# file isn't a framebuffer format, just return the name as is.
result = f
return result
def fixBuffer(renderdir='/Users/mattestela/Projects/fixbuffers/single', preview = True, force=False, maxnum=0):
import os
os.chdir(renderdir)
print 'processing dir: ' + renderdir
files = os.listdir(renderdir)
if (maxnum > 0): files = files[0:maxnum]
for f in files:
newname = correctBufferName(f)
if (f == newname):
if (preview and maxnum):
print f
else:
pass
else:
print 'mv: ' + f + ' >> ' + newname,
#check if file exists first
if ((os.path.exists(renderdir+'/'+newname) == True) and (force==False)):
print ' --- target filename exists, skipping.'
else:
if (preview == False):
os.rename(f, newname)
else:
print ' (preview) ',
print '...done'
def usage():
print "usage: fixBuffer [-p|--preview] [-f|--force] [-m maxfiles]"
print "-p,--preview do a dry run, don't rename anything"
print "-f,--force rename even if target file exists"
print "-m,--maxfiles <maxfiles> list a total of <maxfiles>"
def main():
import getopt, sys
# get options
try:
opts, args = getopt.getopt(sys.argv[1:], "hpfm:", ["help", "preview", "force","maxfiles="])
except getopt.GetoptError, err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
usage()
sys.exit(2)
preview = False
force = False
allmode = False
for o, a in opts:
if o in ("-h", "--help"):
usage()
sys.exit()
elif o in ("-p", "--preview"):
preview = True
elif o in ("-f","--force"):
force = True
elif o in ("-a","--all"):
allmode = True
elif o in ("-m","--maxnum"):
maxnum = a
else:
assert False, "unhandled option"
try:
dir = args[-1]
except IndexError:
dir = '.'
fixBuffer(dir, preview, force, maxnum=0)
if __name__ == '__main__':
print 'current _name is: ' + __name__
main()
list a directory of sequences sanely (aka seqls or lseq)
Most places have a really fast c or perl version of this, here's a python take. Given a folder full of renders, it does this:
C:\projects\foo\images > seqls.py
3pad.000-049.jpg
ambocc.0000-0499.tif
beauty.0000-0204,0206-0266,0268-0499.tif
createDummyFiles.py
diffuse.0000-0499.tif
nopad.0-99.tif
seqls.py
shadow.0000-0259,0265-0499.tif
spec.0000-0384,0386-0499.tif
Fancier versions like james studdart's uberlister have switches to pre-build command lines for flipping in shake and whatnot. I'll add that eventually.
The slightly tricky part was recognising missing frames. Was all ready to do some fancy list comprehension/map/lambda silliness, when I read that python 2.3 added a 'set' class. That made the problem easy to solve. Ideal frame range 'A', your real frame range 'B', missing frames are 'A - B'. Isn't python neat? Found a perl version
here if you prefer that sort of thing.
Some messy code towards the end, sorry 'bout that.
#! /usr/bin/python
import os
from sets import Set
files = os.listdir('.')
result = {}
sortedList = []
def padFrame(frame,pad):
return '0' * (pad - len(str(frame))) + str(frame)
for file in files:
try:
prefix, frame, suffix = file.split('.')
# build a dictionary of the sequences as {name: frames, suffix}
#
# eg beauty.01.tif ... beauty.99.tif will convert to
# { beauty : [01,02,...,98,99], tif }
try:
result[prefix][0].append(frame)
except KeyError:
# we have a new file sequence, so create a new key:value pair
result[prefix] = [[frame],suffix]
except ValueError:
# the file isn't in a sequence, add a dummy key:value pair
result[file] = file
for prefix in result:
if result[prefix] != prefix:
frames = result[prefix][0]
frames.sort()
# find gaps in sequence
startFrame = int(frames[0])
endFrame = int(frames[-1])
pad = len(frames[0])
idealRange = Set(range(startFrame,endFrame))
realFrames = Set([int(x) for x in frames])
# sets can't be sorted, so cast to a list here
missingFrames = list(idealRange - realFrames)
missingFrames.sort()
#calculate fancy ranges
subRanges = []
for gap in missingFrames:
if startFrame != gap:
rangeStart = padFrame(startFrame,pad)
rangeEnd = padFrame(gap-1,pad)
subRanges.append('-'.join([rangeStart, rangeEnd]))
startFrame = gap+1
subRanges.append('-'.join([padFrame(startFrame,pad), padFrame(endFrame,pad) ]))
frameRanges = ','.join(subRanges)
suffix = result[prefix][1]
sortedList.append('.'.join([prefix, frameRanges ,suffix]))
else: sortedList.append(prefix)
# one last sort because its so much fun
sortedList.sort()
for each in sortedList: print each
fake a folder full of image sequences quickly
I wrote this to test the script above...
#! /usr/bin/python
import os
passes = ['beauty','spec','diffuse','shadow','ambocc']
sequenceLength = 952
suffix = 'tif'
pad = 3
createFiles = 1
deleteFiles = 0
showFiles = 0
for prefix in passes:
for i in range(sequenceLength):
num = '0' * (pad - len(str(i))) + str(i)
filename = '.'.join([prefix, num ,suffix])
if showFiles: print filename
if createFiles:
fs = os.open(filename, os.O_WRONLY | os.O_CREAT, 0666)
os.close(fs)
if deleteFiles: os.remove(filename)
flipbook given a single frame in a sequence (aka fchecker)
This started as some
horrendous code many years ago, slowly tidying it up for public re-release.
Things left to do is make it fall back to shake if it can't find fcheck (or should that be the other way round?), find the path to fcheck/shake rather than be hardcoded, and as an external task, find the best way to integrate this with the window right click menu.
#! /usr/bin/python
import re
import os
def fchecker(fname):
try:
prefix, number, suffix = fname.split('.')
except AttributeError:
return 'Could not find sequence number and/or prefix and/or suffix'
# finding matching files
files = os.listdir(os.getcwd())
sequence = []
for file in files:
try:
fileprefix, filenumber, filesuffix = file.split('.')
if fileprefix == prefix and filesuffix == suffix:
sequence.append(filenumber)
except:
pass
# find startframe, endframe and by-frames
if len(sequence) > 1:
sequence.sort()
startFrame = int(sequence[0])
endFrame = int(sequence[-1])
byFrame = int(sequence[1]) - int(sequence[0])
# create command line string
hash = '#' * len(number)
filename = '.'.join([prefix,hash,suffix])
fcheck = 'fcheck -n'
command = ' '.join([fcheck,str(startFrame),str(endFrame),str(byFrame),filename])
return command
else:
return 'no match found'
if __name__ == '__main__':
import sys
img = sys.argv[1]
fname = os.path.split(img)[1]
command = fchecker(fname)
print command
os.system(command)
start shake flipbook
checks if the sequence is padded or not, runs shake. handy if you have another script that lists sequences in the format 'mysequence.0001-0353.tif'
usage: flip.py mysequence.0001-0353.tif
#!/usr/bin/python
import os
import sys
def getPad(range):
try:
start,end = range.split('-')
except ValueError:
start = end = range
if len(start) == len(end) and start[0] == '0':
padsign = '#'
else:
padsign = '@'
return padsign,start,end
def createCommand(img):
name,range,suffix = img.split('.')
padsign,start,end = getPad(range)
newname = '.'.join([name,padsign,suffix])
framerange = '-'.join([start,end])
command = ' '.join(['shake', newname,'-t',framerange])
return command
if __name__ == "__main__":
try:
img = sys.argv[1]
command = createCommand(img)
except:
print 'usage: r2 name.1-100.pic'
sys.exit()
print command
os.system(command)
copy single frame to range
usage: copyframe.py thisframe.50.pic 1-49
#!/usr/bin/python
import os
import sys
import shutil
img = sys.argv[1]
startframe = int(sys.argv[2])
endframe = int(sys.argv[3])
name = img.split('.')[0]
suffix = img.split('.')[2]
for i in range(startframe,endframe+1):
newfile = '.'.join([name,str(i),suffix])
print ' '.join([img,'=>',newfile,'...']),
shutil.copyfile(img ,newfile )
print('done')
change sequence prefix
This one handles pads (cos I just needed it, so I wrote it), will copy to the other scripts later.
usage: chprefix.py oldname.1-60.pic newprefix
#!/usr/bin/python
import os
import sys
import shutil
if len(sys.argv) != 3:
print 'usage: chprefix oldName.1-100.pic newName'
sys.exit()
img = sys.argv[1]
newprefix = sys.argv[2]
startframe = int(img.split('.')[1].split('-')[0])
endframe = int(img.split('.')[1].split('-')[1])
oldprefix = img.split('.')[0]
suffix = img.split('.')[2]
# check if we're padded
pad = 0
startpadlength = len(img.split('.')[1].split('-')[0])
endpadlength = len(img.split('.')[1].split('-')[1])
if startpadlength == endpadlength:
if img.split('.')[1].split('-')[0][0] == '0':
pad = startpadlength
for i in range(startframe,endframe+1):
number = str(i)
if pad:
number = '0'*(pad-len(str(i)))+str(i)
oldfile = '.'.join([oldprefix,number,suffix])
newfile = '.'.join([newprefix,number,suffix])
print ' '.join([oldfile ,'=>',newfile,'...'] ),
shutil.move(oldfile ,newfile )
print 'done'
batching shake scripts
#!/usr/bin/python
import os
import sys
cmd = '-reorder rrrr -setalpha 1 -fo'
filein = sys.argv[1]
outdir = sys.argv[2]
name = filein.split('.')[0]
startframe = int(filein.split('.')[1].split('-')[0])
endframe = int(filein.split('.')[1].split('-')[1])
suffix = filein.split('.')[2]
name = '.'.join([name,'@',suffix])
filein = '-fi ' + name
timerange = '-t '+str(startframe)+'-'+str(endframe)
fileout = '/'.join([outdir,name])
try:
os.mkdir(outdir)
except OSError:
print outdir,' exists!'
command = ' '.join(['shake',filein,timerange,cmd,fileout])
print command
os.system(command)
smart ls
Just a little experiment. Ultimately the plan would be to bind this to just 'ls', so that if a directory is under 50 files, call regular ls, otherwise use one of the cleverererer sequence listers like seqls or lseq that float around most post houses. The len(os.listdir()) call is very quick, way faster than I expected it to be, so this may actually prove useful in the long run...
#!/usr/bin/python
import os
def smartls():
if (len(os.listdir('.')) > 50):
os.system('seqls')
else:
os.system('ls')
smartls()
--
MattEstela - 17 Oct 2007
Threading in Python
Mel can be a pain in the ass to thread, user Rod Green has written some python to do it easily:
You've got Threads in my Mel! (Timer Event Objects in Maya)
Here's a shameless copy - because we all know what happened to Bryan Ewert's
site
# imports
import sys
import time
import threading
import maya.mel as mel
import maya.utils as utils
import maya.cmds as cmds
import string
# classes
class TimerObj(threading.Thread):
def __init__(self, runTime, command):
self.runTime = runTime
self.command = command
threading.Thread.__init__(self)
def run(self):
time.sleep(self.runTime)
utils.executeDeferred(mel.eval, prepMelCommand(self.command))
# functions
def prepMelCommand(commandString):
return cmds.encodeString(commandString).replace("\\\"","\"")
def startTimerObj(runTime, command):
newTimerObj = TimerObj(runTime, command)
newTimerObj.start()
Mel usage:
global proc startTimer(int $runTime, string $command)
{
python("import timer;"
+"timer.startTimerObj( " + $runTime + ", \"" + encodeString($command) + "\")") ;
}
startTimer(10, "print(\"Times Up!\n\") ;print(\"Hello World!\n\") ;") ;
Drop a comment in Rod Green's blog if you're curious !
--
MattBernadat - 28 Aug 2008