r8 - 26 Feb 2014 - 01:19:32 - MattEstelaYou are here: TWiki >  Maya Web > WebLeftBar > MayaPython

Maya python

Realised I haven't updated this page in ages, and I've written loads of little python scripts in the iterim. I'm a total convert to pymel, I suggest you all do the same. As such, I'll make this a dumping ground of little scripts I write as I go, just so I have a place to find them, and examples are always a handy way to learn.

Random scale/rotate of lots of trees

I'd instance-duplicated out a bunch of vray proxy trees, needed a little randomising. This set of 3 list comprehensions gives a random rotation in y between 0 and 360, a random y scale between 0.8 and 1.2, and shuffles their translation 15 units in x and z.

from pymel.core import *
import random
[ x.rotate.set( 0, random.randrange(0,360),0)   for x in ls(selection=True) ]
[ x.scale.set( 1, random.uniform(0.8,1.2) ,1)   for x in ls(selection=True) ]
[ x.translate.set( x.translate.get() + (random.randrange(-15,15),0,random.randrange(-15,15) ))   for x in ls(selection=True) ]

Set decay region on spotlights, move each light back 200 units on its own axis

Was doing a searchlights style shot, the vray volume fog went crazy at the base of the lights. figured it was cos it was crazy high intensity at a single point, so if i could use the light decay region to offset the light intensity away from its center, then offset the translation of the light to compensate, it'd help. It did. smile

from pymel.core import *

lgts = ls(selection=True)    # I select all the spotlight shapes first
for lgt in lgts:
    lgt.useDecayRegions.set(1)    
    lgt.startDistance1.set(0)
    lgt.endDistance1.set(0)
    lgt.startDistance2.set(0)
    lgt.endDistance2.set(0)
    lgt.startDistance3.set(200)
    lgt.endDistance3.set(100000)
    
    g = group(empty=True, parent=lgt.getParent(), name='%s_offset'%lgt.name() )    # create group, parent it to the light transform
    g.translate.set(0,0,200)
    parent(lgt, g, shape=True, relative=True)     # trick that'll parent the light shape to the group, normally maya doesn't let you do this

A melscripters quickie guide to maya python

While you gain in terms of string manipulation, access to external libraries and the outside world, its not as pleasantly pythonic as you'd hope. Now I definitely see why there's such a push for pymel (an amazing open source effort to make python in maya behave properly). Enyhoo, if you're dabbling, even if you know some python, its not intuitive to start with. Here's all you need to know:

import maya.cmds as cmds
selection = cmds.ls(selection=True)
for each in selection:
   print selection

Note that for the most part you're kind of maintaining two things at once; your maya variables, and your python list of those maya variables. Whereas in mel you can interact with things directly, maya-python always imposes a level of misdirection. The 'ls' command is a good example; you'd expect something like ls(selection) or ls(scene) to work, but you get no such python cleverness; each mel command has been wrappd as a clunky python call, and you have to do all your communication with maya this way.

That said, once you start doing list comprehensions on maya selections, it becomes difficult to go back to mel. Eg:

# find all objects that have a 'tag' string attribute with value 'special stuff'

import maya.cmds as cmds
taglist = [ x for x in cmds.ls('*.tag') if 'special_stuff' in cmds.getAttr(x)]
print taglist

Getting a nice list of the script paths

Annoying that mel returns this as a huge single string, python to the rescue:
env = os.getenv('MAYA_SCRIPT_PATH')
import pprint
pprint.pprint(env.split(':'))

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: r8 < r7 < r6 < r5 < r4 | 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