Posts Tagged ‘ Python

Python: How can I replace text in a string when the case of the text differs?

I process a lot of paths. Some of these paths are entered by hand, by a human. Other paths are machine generated by some tool. Sometimes the tool will respect the case entered originally, other times it makes everything lowercase. Based on these combinations, you could be dealing with two paths that are the exact same (location on disk), but some have upper-case characters defining part of their name, and others, lower-case text. If you need to process these paths, and change them from one relative path to another, these case inconsistencies can become a real pain. Below is one solution around the issue. I’d be interested to see others :)

Using re.findall, we can search in a string. But what really helps is re.IGNORECASE. This gives us a matching string based on the case of the source string, which we can then use to replace with later using the .replace() string method:

import re

srcPath ="c:/my/path/wIth/mIxeD/case"
match = "/with/mixed/"
replace = "/normal/"
resultPath = ""

try:
    sourceCaseMatch = re.findall(match, srcPath, re.IGNORECASE)[0]
    resultPath = srcPath.replace(sourceCaseMatch, replace)
except:
    pass

print "Result: '" + resultPath + "'"
# Result: 'c:/my/path/normal/case'

This is also posted on my Python Wiki

Understading Python generators in Maya

Python has the ability to create generator functions. See Python docs here:
http://docs.python.org/tutorial/classes.html#generators
http://docs.python.org/reference/expressions.html#generator-expressions
http://docs.python.org/reference/expressions.html#yieldexpr
http://docs.python.org/reference/simple_stmts.html#yield

What are they? From the Python docs (above):

“Generators are a simple and powerful tool for creating iterators. They are written like regular functions but use the yield statement whenever they want to return data. Each time next() is called, the generator resumes where it left-off (it remembers all the data values and which statement was last executed)…”

You can imagine that they are like a normal function looping over a list of items, or doing some work on each of the items in the list. The difference is, rather than looping over the whole list when the function is executed, the function pauses after each loop, waiting for you to tell it to continue.

This allows you the ability to make tools that can execute over a list of items, only executing on the next item when you tell it too.

Example below. In this example, the user writes a generator function that will create a locator placed at each vert passed into the function. But the locators are created one by one, only when we call to the generator function:

# Python code
import maya.cmds as mc

# Define our generator function
def generateLocs(verts):
    for v in verts:
        pos = mc.pointPosition(v, world=True)
        loc = mc.spaceLocator(position=pos)
        # Use 'yield' instead of 'return':
        yield loc[0]

Now put the code to use:

# First, select a polygonal object.  Then convert to verts:
verts = mc.ls(mc.polyListComponentConversion(toVertex=True), flatten=True)

# create our generator object called "loc":
loc = generateLocs(verts)

# each time we call to loc.next(), a new locator is created, base on
#  the next vert in the list:
print loc.next()
# locator1
print loc.next()
# locator2
print loc.next()
# locator3

# etc...

This post is also over on my Mel Wiki

Casting variable data from Python to Maya

I’ve recently ran across a nasty… bug, in the Maya-Python integration.  It appears, that if you have a Python module return a list, Maya will convert it to an array. That’s as expected.  However, if the Python list is empty, Maya converts it to a string with two brackets inside of it: “[]”, rather than converting it to an empty array.

I should note, as an update, that this initial presumption (that this is a ‘bug’) is wrong:  Maya variables are always typed (string $foo = “word”;), while Python’s aren’t, they’re just pointers (foo = “word”).  Maya has to auto-detect what type of data (string, float, etc) exists in the passed-in Python object and make a determination for what the conversion should be.  If it just gets an ’empty list’, it really has no idea what type of Maya array (float, string, int, vector) it should be.

Obviously, this can cause some real headaches if you’re trying to capture expected data from Python in Maya.

I found a fix, that I’m… not…. very… happy with.  Explained below the example.

Example:  First, a Python module that will return different types of data, based on the passed in arg:

# Python code
def variableReturn(varType):
    if varType == "str":
        return "string"
    if varType == "listSingle":
        return ["value1"]
    if varType == "listMult":
        return ["value1", "value2"]
    if varType == "listNone":
        return [None]
    if varType == "listEmpty":
        return []

Now, some mel code to capture that data.  I’ve already setup the Maya variable types to be the correct ones based on what the Python module spits out… but you can see that one of them is clearly wrong:

// Mel code:
string $str = python("variableReturn('str')");
string $lstSingle[] = python("variableReturn('listSingle')");
string $listMult[] = python("variableReturn('listMult')");
string $listNone[] = python("variableReturn('listNone')");
string $listEmpty = python("variableReturn('listEmpty')");

print ("Type of 'str' : " + `whatIs "$str"` + "n");
print ("Type of 'listSingle' : " + `whatIs "$lstSingle"` + "n");
print ("Type or 'listMult' : " + `whatIs "$listMult"` + "n");
print ("Type of 'listNone' : " + `whatIs "$listNone"` +"n");
print ("Type of 'listEmpty' : " + `whatIs "$listEmpty"` + "  <--- aaaagh!!!n");

Prints:

Type of 'str' : string variable
Type of 'listSingle' : string[] variable
Type or 'listMult' : string[] variable
Type of 'listNone' : string[] variable
Type of 'listEmpty' : string variable  <--- aaaagh!!!

As you can see, the workaround is to have your Python code return a list with a ‘None’ object in it:

# Python
return [None]

Presumably, you’d do some sort of test before hand in your Python code to know to return things this way:

# Python
if len(myVal) == 0:
    myVal = [None]
return myVal

Of course, in Maya you’d then have to detect if the first item of the list was empty, if it’s size was 1:

// mel
string $result[] = python("myPyFunc()");
if(size($result) && $result[0] != ""){
    // start doing something...
}

Update #1:

After reading through the Maya docs on Python integration, this may make a bit of sense:

Python Return Value MEL Conversion
string string
unicode string
int int
float float
list containing numbers, including at least one float float[]
list containing only integers or longs int[]
list containing a non-number string[]
anything else string

I’m guessing an ’empty list’ falls into the ‘anything else’ category, and thus converts it to a “string”. Even though our above example returns an empty list, and we presume to have string data, it could actually contain float data, int data, etc. But how is Maya to know this if all it gets is an un-typed empty list? It doesn’t, so it turns it into a string.

Update #2:

Looks like (based on comment below) later version of Maya have fixed this issue:  The docs (in 2010) now append this new information:

| empty Python list | empty string array (string $array[]) |

How to subtract a list from a list inPython?

It dawned on me today:  I’ll add lists on a regular basis (listC = listA + listB), but I don’t think I’ve ever ‘subtracted’ listA form listB.  A quick search didn’t come up with much (but I know it’s out there).  I came up with this method using list comprehensions:

listA = ["a","b"]
listB = ["b", "c"]
listC = [item for item in listB if item not in listA]
print listC
# ['c']

But there’s got to be an easier method?  Maybe using sets?

Posted on my Python Wiki too.

Auto Joint Orientations in Maya

I have this pet peeve:  Whenever I create joint hierarchies in Maya, it will automatically setup the joint orientation value for you (based on option set in the ‘Joint Tool’ tool settings).  That, is handy.  However, the last joint in the chain isn’t auto-oriented:   A joint’s auto-orientation is based on the vector between it, and its child.  So obviously, the last joint in the chain has no child, so it can’t use this system.  The result is, the last joint is always oriented to the worldspace global axis.  Which is almost exactly never what I want.

The workaround is easy, just make a little script to process all the joints in a selected hierarchy.  But it sure would be nice if they had an option to do this automatically in the UI….

"""
Eric Pavey - 2008-12-19
For the selected hierarchies, zero the joint orient values
on all leaf joints
"""
# Python code
import maya.cmds as mc

kids = mc.listRelatives(mc.ls(selection=True), children=True, type="joint", allDescendents=True)
for k in kids:
    if mc.listRelatives(k, children=True, type="joint") is None:
        for attr in [".jointOrientX", ".jointOrientY", ".jointOrientZ"]:
            mc.setAttr(k+attr, 0)
        print "Zeroed '" + k + ".jointOrient'"