r4 - 28 Aug 2008 - 10:49:13 - MattBernadatYou are here: TWiki >  Maya Web > WebLeftBar > MayaPython

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... smile

#! /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

Topic attachments
I Attachment Action Size Date Who Comment
zipzip tokeruscripts.zip manage 4.0 K 17 Oct 2007 - 07:03 MattEstela  
Edit | WYSIWYG | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r4 < r3 < r2 < r1 | More topic actions
 
Powered by TWiki
This site is powered by the TWiki collaboration platformCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback