[Maya Python] Space Switching. 3 inputs are okay. How about 4?

python
maya

#1

Hi,

I have a script for space switching that works for 3 inputs: 2 parents spaces and 1 child space.
However, I was wondering how to revise the code to accommodate the 4 inputs automatically (i.e. with 3 parent spaces).

I initially thought just having a conditional statement such as

If input>3:
Do this:
If input>4:
Do this:
If input>5
Do this:

But this would mean having several duplicate commands within the do this block.

Is there a better way of doing this? Thank you

The code is the following

import maya.cmds as cmds


objSel = cmds.ls(sl=True)

spaceSwitchCon = 'locator1'

# This will be affected when the the number of inputs increased
spaceSwitchConstraint = cmds.orientConstraint(objSel[0],objSel[1],objSel[2],mo=True)

# This will be affected when the the number of inputs increased
name1 = objSel[0].split('_')[-1]
name2 = objSel[1].split('_')[-1]

cmds.addAttr(spaceSwitchCon, ln = "spaceSwitch", at = "enum",en = name1 + ':' + name2 + ':')
cmds.setAttr(spaceSwitchCon + '.spaceSwitch', keyable=True)

# This will affected when the the number of inputs increased
reverse_spaceSwitch = cmds.createNode('reverse', n="reverse_" + objSel[0])
cmds.connectAttr(spaceSwitchCon + '.spaceSwitch', reverse_spaceSwitch + '.input.inputX')
cmds.connectAttr(reverse_spaceSwitch + '.output.outputX', spaceSwitchConstraint[0] + '.' + objSel[0] + 'W0')
cmds.connectAttr(spaceSwitchCon + '.spaceSwitch', spaceSwitchConstraint[0] + '.' + objSel[1] + 'W1')

#2

The way I approach this type of problem is to first identify what can be abstracted. Essentially, you’re going to want the space switch created by this script to behave in the same way whether you have 2 orient targets, 3, or even as many as 9 or 10. You definitely won’t want to have to write that by hand every time.

So rather than thinking in terms of object1, object2, object3, you should store these as a list so the number of targets can be whatever you need. (check out list slicing and joining in python)

The next thing you’re going to want to do is modify your code to operate on lists of variable length. It’s not very clear in the documentation, but cmds.orientConstraint will actually accept a list as the first argument, leaving you with

targetList = objSel[:-2]
child = objSel[-1]
cmds.orientConstraint(targetList, child, mo=True)

So the next step would be processing the names. You could wrap this step in a function to make it more abstract

def processNames(objectList):
    newNames = []
    for obj in objectList:
        newName = obj.split('_')[-1]
        newNames.append(newName)
    return newNames

So now you have

renamedObjects = processNames(objectList)

Lastly, (probably the most difficult part) we want to tackle setting up the connections to the space switch. A reverse node is not the best choice for this. It’s going to turn 0 to 1 (and vice versa), but if you have more than 2 objects you can end up with an enum value of 2, which will get reversed to -1. Not too useful. Instead take a look at the condition node. You can use it quite effectively for any number of items. Just check if the enum is equal to an expected value, and if it is output a 1 (using colorIfTrue), otherwise output a 0 (using colorIfFalse).

Now for the code… we’re going to use some python magic via enumerate to get the index of each object as we iterate through the list

def connectSpaceSwitch(sourceAttr, objectList, constraint):
    for idx, obj in enumerate(objectList):
        # create the condition node
        condition = cmds.createNode('condition', n='condition_' + obj)
        cmds.setAttr(condition + '.operation', 0) # 0 is 'Equal'
        cmds.setAttr(condition + '.secondTerm', idx)
        cmds.setAttr(condition + '.colorIfTrueR', 1)
        cmds.setAttr(condition + '.colorIfFalseR', 0)

        # connect the attributes
        cmds.connectAttr(sourceAttr, condition + '.firstTerm')
        cmds.connectAttr(condition + '.outColorR', constraint + '.' + obj + 'W' + str(idx))

So now that last section of code becomes…

connectSpaceSwitch(spaceSwitchCon + '.spaceSwitch', objectList, spaceSwitchConstraint[0])

Writing your code this way is going to save a lot of headaches down the road when you have to come back and modify it. Plus, those functions are reusable, which is a huge plus. Work smarter not harder

Hope this helps!


#3

Hi @JoDaRober

Thanks for the detailed input, and might take me some time to fully digest the concepts (being a beginner). For instance, it’s my first time encountering having two variables in a for loop (i.e. for idx, obj rather than just for obj)

However, off the top of my head, I do get where you are coming from.

I’ll get back to you with (hopefully) a working script.

Thank you again and have a great day ahead!


#4

Ah, I understand. I remember being there.

Hopefully as a bit of clarification, enumerate is a function that is built in to python to give you the number position of every object in the list as well as the object itself.

Let’s say you have a list of objects ['pCube1', 'pCube2', 'pCube3', 'pCube4']
If you loop through that list with an enumerate, here’s what you get:

objectList = ['pCube1', 'pCube2', 'pCube3', 'pCube4']

for idx, obj in enumerate(objectList):
    print idx
    print obj

# ----- output -----
0
pCube1
1
pCube2
2
pCube3
3
pCube4

I was only using it to make setting all of the ‘W1’, ‘W2’, ‘W3’ … parts of the constraint easier since you have to know the number of the object for that. (Same as the last two lines of your original example)

IMO the best way to learn this stuff is to ask yourself a bunch of specific questions, like “how do I combine a list of names into a single string with the names separated by a semicolon?” or “how do I take a list of selected things and split it into two lists?” then use google to find the answer and type it out yourself in the script editor. Once you get comfortable with this type of stuff in python, your code becomes much more powerful. You’re already on the right track by trying to expand your script to work with varying numbers of objects. It just takes time and practice.


#5

@bentraje - Take a look at dictionary comprehension in python , to get a handle on this mechanic. A dictionary is basically a mutable list of key value pairs - E.g.

myDict = {"a":1, "b": 2, "c":3}

When you iterate over this dictionary you can iterate in multiple ways:

for i in myDict # Iterate over the dictionaries keys - a, b, c
for k, v in myDict.items() # Iterate over the key and value -  a 1, b 2, c 3
for v in myDict.values() # Iterate over the values of the dictionary - 1, 2, 3

Now there is a caveat - dictionaries (in there default form are unordered) - so what the enumerate method essentially is doing is creating a list of index, value pairs - I’m using tuples for this (non-mutable list):

# Essentially what enumerate is doing

def enumify(array):

  """Returns a list of tuples in a index, value paring
  
     E.g. [(0, "a"), (1, "b"), (2, "c")] 

  """

  return [(i, array[i]) for i in range(len(array))]

When you iterate over this list passing 2 variables e.g.


myList = ["a", "b", "c"]

for index, value in enumify(myList):
    print index, value

# >>> 0 a
# >>> 1 b
# >>> 2 c

You actually unpacking each item in the list and passing each part to the variables your assigning i.e. index => list[0][0] and value => list[0][1]

Unpacking is a awesome mechanic of Python - and you can unpacking any amount of variables if they correspond correctly. This is unpacking 3 variables :slight_smile: :


def enumify_foo(array):
  return [(i, array[i], "foo_{0}".format(i)) for i in range(len(array))]


for i, v, k in enumify_foo(["a", "b", "c"]):
  print i, v, k


#6

Hi @JoDarober Thanks for the response. The script works as expected.
I just modify some codes such as from objSel[:-2] to objSel[:-1]
And adding the “Add Attr” block Here is the code. Thanks again!

# Declaring Variables

objSel = ['parentSpace01_chest','parentSpace02_COG', 'parentSpace03_world', 'childSpace01_jnt']
spaceSwitchCon = 'spaceSwitch'

targetList = objSel[:-1]
child = objSel[-1]

# Creating the constraint

spaceSwitchConstraint = cmds.orientConstraint(targetList, child, mo=True)

# Strip name

def processNames(objectList):
    newNames = []
    for obj in objectList:
        newName = obj.split('_')[-1]
        newNames.append(newName)
    return newNames

renamedObjects = processNames(targetList)


# Add Attr

enumList = ''

for enum in renamedObjects:     
    enumList += enum + ':'
    
cmds.addAttr(spaceSwitchCon, ln = "spaceSwitch", at = "enum",en = enumList)
cmds.setAttr(spaceSwitchCon + '.spaceSwitch', keyable=True)

# Connect Space Switch

def connectSpaceSwitch(sourceAttr, objectList, constraint):
    for idx, obj in enumerate(objectList):
        # create the condition node
        condition = cmds.createNode('condition', n='condition_' + obj)
        cmds.setAttr(condition + '.operation', 0) # 0 is 'Equal'
        cmds.setAttr(condition + '.secondTerm', idx)
        cmds.setAttr(condition + '.colorIfTrueR', 1)
        cmds.setAttr(condition + '.colorIfFalseR', 0)

        # connect the attributes
        cmds.connectAttr(sourceAttr, condition + '.firstTerm')
        cmds.connectAttr(condition + '.outColorR', constraint + '.' + obj + 'W' + str(idx))
                
connectSpaceSwitch(spaceSwitchCon + '.spaceSwitch', targetList, spaceSwitchConstraint[0])

#7

@chalk

Thanks for the explanation. I knew about the list comprehension but didn’t know there is also a dictionary comprehension.

Seems like a handy way to execute code but to be honest I found the comprehension a bit hard to read, but I’ll keep those in mind in the future.

Also thanks for pointing about the “unpacking”. Seems really a handy way to manipulate data.