Python

From cgwiki
Revision as of 23:27, 25 February 2014 by MattEstela (talk | contribs) (Created page with "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 su...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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. :)

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:

<verbatim> import maya.cmds as cmds selection = cmds.ls(selection=True) for each in selection:

  print selection

</verbatim>

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:

<verbatim>

  1. 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 </verbatim>


---++ Getting a nice list of the script paths Annoying that mel returns this as a huge single string, python to the rescue: <verbatim> env = os.getenv('MAYA_SCRIPT_PATH') import pprint pprint.pprint(env.split(':')) </verbatim>

---+ 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. [[%ATTACHURL%/tokeruscripts.zip][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

  1. FixFramebuffer

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

<verbatim>

  1. !/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()

</verbatim>


  1. ListDir

---+++ 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:

<verbatim> 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 </verbatim>

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 [[1][here]] if you prefer that sort of thing.

Some messy code towards the end, sorry 'bout that.

<verbatim>

  1. ! /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)
  1. one last sort because its so much fun

sortedList.sort()

for each in sortedList: print each </verbatim>

  1. FakeFolder

---+++ fake a folder full of image sequences quickly

I wrote this to test the script above... :)

<verbatim>

  1. ! /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)

</verbatim>


  1. FlipBook

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

<verbatim>

  1. ! /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)

</verbatim>

  1. ShakeFlipbook

---+++ 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

<verbatim>

  1. !/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)

</verbatim>

  1. FrameTorange

---+++ copy single frame to range

usage: copyframe.py thisframe.50.pic 1-49

<verbatim>

  1. !/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')

</verbatim>

  1. ChangePrefix

---+++ 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

<verbatim>

  1. !/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]

  1. 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'

</verbatim>

  1. BatchShake

---+++ batching shake scripts

<verbatim>

  1. !/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) </verbatim>

  1. SmartListdir

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

<verbatim>

  1. !/usr/bin/python

import os

def smartls():

   if (len(os.listdir('.')) > 50):
      os.system('seqls')
   else:
      os.system('ls')

smartls() </verbatim>


-- Maya.MattEstela - 17 Oct 2007


  1. PythThread

---+++ Threading in Python

Mel can be a pain in the ass to thread, user Rod Green has written some python to do it easily: [[2][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 [[3][site]]

<verbatim>

  1. imports

import sys import time import threading import maya.mel as mel import maya.utils as utils import maya.cmds as cmds import string

  1. 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))


  1. functions

def prepMelCommand(commandString): return cmds.encodeString(commandString).replace("\\\"","\"")

def startTimerObj(runTime, command): newTimerObj = TimerObj(runTime, command) newTimerObj.start() </verbatim>


Mel usage:

<verbatim> 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\") ;") ; </verbatim>

Drop a comment in Rod Green's blog if you're curious !

-- Maya.MattBernadat - 28 Aug 2008