This page is a static version of posts hosted in my dev-blog.
It compiles all about the APyTA category (Applied Python for Technical Artists), and are writen in no other purpose that to help you to embrace python philosophy in CGI world.

APyTA_01: Why Python ?
APyTA_02: Let's Draw, not print !
APyTA_03: Python syntax and conventions.
APyTA_04: Object Introduction, getting the selection.
APyTA_05: Self learning: Exploring the RAM.
APyTA_06: Dealing with objects collections.
APyTA_07: Smart collections, advanced features.
APyTA_08: The devil as a string.
APyTA_09: Looping and forking, logic’s basics.
APyTA_10: Function mechanics, arguments and scope notions.
APyTA_11: Referencing your work.
APyTA_12: : Handling exceptions, just in case.
APyTA_13: Most used modules, Diving into the system.
APyTA_14: Most used modules, Exp Files management.
APyTA_15: OBJECT Part I, Everything is an OBJECT.
APyTA_16: OBJECT Part II, How to inherit a skeleton.
APyTA_17: OBJECT Part III = Static tactics.

APyTA_01: Why Python ?

Actually in our CGI world, there is python scripting embedded in MotionBuilder, Maya, XSI(Softimage), Blender, Cinema 4D, Houdini, Nuke, GIMP, Modo, etc..
For me, this success is closely linked to only one very pythonic specificity: modularity.

Just think about python like a kernell, a powerfull core able to accept easily a lot of additional layers. From several sources of any universes (not CGI-restrictive).
Forget about your MEL dedicated proc: Python is born outside CGI, and is worn by a whole community with a full bunch of very different approaches. Python will enrich you.
So you can incorporate foreign solutions:great. But more important, you can easily build some flexible code for self-reusing (context-adaptative).

To resume, let's imagine your big CGI-Scene file, produced by numerous departments, without external references. What a nightmare, right ?
For me, this whole Python-revolution is about building crossed pattern, reusable libraries and meaningful objects.

Of course, there is some other advantages, like flexibility, readability, agile-friendly, but let's discover it by examples. That's our point.

print("Hello World!")
#Nothing scary until now !
(Link to:Original post on dev-blog)


APyTA_02: Let's Draw, not print !

Just forget this damn script editor, and let's create a text in Maya: Create>Text
Let's look at our MEL feedback in the Script Editor:

textCurves -ch 0 -f "Times New Roman|h-13|w400|c0" -t "Hello world";

Well, first good news: you can use these MEL-formatted lines with your python code !
Let's Type this:

import maya.mel
maya.mel.eval('textCurves -ch 0 -f "Times New Roman|h-13|w400|c0" -t "Hello world";')

(Please notice how I avoid breaking the string chain with a global simple quote.)
Well, so you have no reason to be afraid: all your MEL-knowledge, favorite procs are not obsolete !

To be honest, when you'll become more at ease with python, (and this would be quicker than you actually think)
in practice this method will be the very last of your choices. But now you know it.

Well, this MEL-line can be very easily converted into a python function:

import maya.cmds
pythonReturn = maya.cmds.textCurves(text="Hello world")

First, notice in the first line the call to maya commands libraries.
This is where all MEL commands are stored in python.
They are stored in a form like any other function, with parenthesis and arguments.
So, to respect the MEL-Style, you have to write the parameter (shortName or longName).
Non-specified parameters will be set at their default values.

So, we have to way of doing thing, MEL-Style and Python-Style.
I'm sorry but it is a bit more complicated. As Python is object-oriented (a whole post on that aspect will come later),
some people (Hi Olivier!) decided to respect a meaningful structure of commands, and created PyMEL.
Let's have a look at it:

import pymel.core
pymelReturn= pymel.core.modeling.textCurves(t="Hello world" )

Quite similar right ? But you can observe the command is "namespaced", like grouped by categories.
So when calling which one ? Are there others surprises ?

Yes of course, please wellcoooooome OpenMaya !
I'll explain it later, but let say it is the python equivalent to C++ API of Maya !
You will be able to do, with only python, (almost) reach all the possibilities of Maya Plug-Ins (but slower).

import maya.OpenMaya
maya.OpenMaya.MGlobal.displayInfo("Hello World")
maya.OpenMaya.MGlobal.displayWarning("Hello World?")
maya.OpenMaya.MGlobal.displayError("Hello World!")

And we're done with that presentation ! Ouch !

Original post: Let's Draw, not print !


APyTA_03 : Python syntax and conventions

Ok, let's start from a typical function to apprehend the syntax :

def doSomethingSpecial(_GivenThis,_GivenThat):
    """ This function adds given parameters if they have the same type """
    ListOfArgument = [_GivenThis,_GivenThat]
    for currentArgument in ListOfArgument:
        #Printing each argument to be sure:
        print(currentArgument)

    if type(_GivenThis) == type(_GivenThat):
        myResult = _GivenThis+_GivenThat
        return myResult
    else:
        return "ERROR, conflicting types"

print doSomethingSpecial(1,2)
#1 2 3
print doSomethingSpecial("a","b")
#a b ab
print doSomethingSpecial("a",2)
#a 2 ERROR, conflicting types

So, let's analyze these points:

1) NO BRACES. All python blocks of instructions are created and closed by indentation.
Do not forget the first ':', which will force you to create an additional indent.
Be really sure to setup your current editor with a rule like: Tabulations ARE four spaces.
Forget about Maya script editor and install Eclipse, or NotePad++.

2) def allow you to define a function.
In my Naming conventions, a function start always by a VERB.
Because a function is always an ACTION, it does something.
If you can't nameit that way, you are probally in the wrong path.

3) NO TYPES ??? Yes, python is strongly typed, we will see that later.
But you don't need to tell him what kind of data is your variable.
Python will know that. So you don't need that.
Quite useful ! you can also re-use a variable with a different content.
Please notice that is the case for the return value.
It is a string, but other contexts the function will return an int or a float !

4) A list of variables in python are written with [], comma separated,
and can be iterate very lightly with a for in statement.
Notice also the name of the variable, the first letter in Majuscule
to better isolate different parts in one name.

5) Document your code ! # is the commenting tag to tell python
that the next characters on the line are NOT code.
Usefull to comment, but notice the triple-double quote at the start of our function.
This is a DOCSTRING. On header of a function or a class, I advice you to always describe
your function goal and return behaviour, with algorithm and tweaks if any.

Note: This capacity of python to cast on the fly the type of your variable
will give you a very powerful tool: polymorphism.
You can make a function working with integers as well as float, or manage multi-types of return values.
But (there is always a but), forbid yourself to have one same variable
and re-use its name to store several things at different time.
The name of the variable must be as precise as you can, and stay genuine.
Of course you can have a variable "mySelectedObject" (who can be a Mesh, a Camera, etc...)

[Original post] APyTA_03 : Python syntax and conventions


APyTA_04: Object Introduction, getting the selection.

If you code in MEL, you already know this three "flags" of MEL Commands:
Create, Query & Edit. So how to use them in python ?

import maya.cmds
maya.cmds.polyCube( subdivisionsX=5, subdivisionsY=5, subdivisionsZ=5, width=1.3, height=3.1)
currentWidth = maya.cmds.polyCube( 'polyCube1', q=True, width=True )
print(currentWidth) #1.3
currentWidth = maya.cmds.polyCube( 'polyCube1', e=True, height=1.1 )

But wait, I have good news, this is totally obsolete, since python is Objet-Oriented.
Let's find it out with a major snipplet : Getting the selection.

currentSelection = maya.cmds.ls(selection=True)
print(type(currentSelection)) #<type 'list'>
print(len(currentSelection))#1 <=This is the size of the list, allow us to access to the first value:
print(currentSelection[0], type(currentSelection[0]))
#(u'pCube1', <type 'unicode'>)

#Well, I have the name of the selected object... And now what ?

In this example, we use the return  value of the ls command to get the selection.
Its type is a list (of one element). This element is a string (a special one, unicode).
This is a selection of basic types in python:

</pre>
print(type(1))    #<type 'int'>
print(type(1.0)) #<type 'float'>
print(type("1")) #<type 'str'>
print(type(u"1")) #<type 'unicode'>
print(type([1,1.0,"1"])) #<type 'list'>

These are the most common types you deal with. But you need a lot more, believe me.
First of all, your scripts are working in a 3D Viewport. You're not dealing with numbers, characters and stuff, right ?
So let's see it in real actions:

import pymel.core
currentSelection = pymel.core.general.ls(selection=True)
print type(currentSelection[0]) #<class 'pymel.core.nodetypes.Transform'>
print currentSelection[0]._name #|pCube1
print currentSelection[0].getTranslation() #[0.0, 0.0, 0.0]
print currentSelection[0].getRotation() #[0.0, -0.0, 0.0]
print currentSelection[0].hide()
print currentSelection[0].show()

Ok, now currentSelection variable is a real object.
A context-aware piece of RAM representing my "piece of data in focus".
So Maya is able to return a real representation of a Higher level of abstraction.
In this example, the Transform is a maya-defined custom type.
It is a structure of data, called a CLASS.
A class can contains parameters (called MEMBERS), which can be of any types or classes.
It can contains also functions (called METHODS).

To resume be very aware of the dangers of hard-coded string (explicitly written into your code) I often choose a bright color in my syntax-highlighting to be able to see them well and reduce them. There is a lot of events, interactions, callbacks that can modify your object name or members.

Object-oriented coding allow you to have some kind of handle aiming to your objects of focus. If later the object have changed its name, its shape, almost everything, you can still access it and manipulate it if your variable is the object himself.

[Original post] APyTA_04:


APyTA_05: Self learning:Exploring the RAM

Now you apprehend objects, I will show you the pythonic holy trinity: print, help, dir
Personally, I use them aaaall the time, and we can call that section: forget the doc !

import pymel.core
polyCubeReturnValue  = pymel.core.modeling.polyCube()
help(polyCubeReturnValue) #Display all the embeded doc of your object (DOCSTRING)
print type(polyCubeReturnValue) , len(polyCubeReturnValue) #<type 'list'> 2

You can use print statement without parenthesis, and with several arguments (comma separated).
Help will display directly the help of your object (when it is written of course...)

print type(polyCubeReturnValue[0]) #<class 'pymel.core.nodetypes.Transform'>
print type(polyCubeReturnValue[1]) #<class 'pymel.core.nodetypes.PolyCube'>
myCube = polyCubeReturnValue[1]
print myCube #polyCube1 (Notice that the print of a class just return object name.
print dir(myCube) #['MAttrClass', 'MdgTimerMetric', '__add__',... ,'setSubdivisionsHeight',..., 'type', 'unlock', 'upper']
myCube.setSubdivisionsHeight(5)

See how we can be really sure about what's our variables with type statement, and then explore the object capabilities with dir ?
Sometimes the doc are wrong, Sometimes documentation is unclear, unfinished, unprecise.
So keep in mind a saving-ass maxim: RAM DO NOT LIE.

myTransform = polyCubeReturnValue[0] #Let's explore the first return class of polyCube, the Transform
print dir(myTransform)  #['LimitType', ..., 'getShape' ,..., 'upper', 'zeroTransformPivots']
print myTransform.getShape() #pCubeShape1
print dir( myTransform.getShape() )
myTransform.getShape().createUVSet("SecondUVSet")

Notice that in the last line, I could make another variable myShape = myTransform.getShape()
But if I don't really need to reuse it, you can call methods and members in cascade like this.

A lot of time, you will have to verify the content or the type of your variable.
Print can display it into the Maya script editor, giving you the best feedback.
Tips : Script Editor could be a pain in the as* when printing a lot of stuff.
Performance are very bad, so for huge logging (like all vertices of your mesh).
Why not print into the Output window of Maya ?

print('Hello world 1')
import sys
sys.stdout.write('Hello world 2')
sys.__stdout__.write('Hello world 3') #This way, the print display your string in output editor of Maya.
[Original post] APyTA_05: Self learning:Exploring the RAM


APyTA_06: Dealing with objects collection.

A lot of time, you will have to iterate through a collection of objects, (Transform, meshes, joints, vertices, etc.).
We already see basic types, and custom-types (class) from Maya. Let's dig into list management:

import pymel.core
for i in range(7): #range return a list of increasing numbers # Result: [0, 1, 2, 3, 4, 5, 6] #
 pymel.core.modeling.polyCube()
theCubeList = pymel.core.general.ls( geometry=True)
print len(theCubeList) #7
print theCubeList[3]._name #|pCube4|pCubeShape4
print theCubeList[-1]._name #|pCube7|pCubeShape7
print theCubeList[9]._name # Error: IndexError: list index out of range #
print theCubeList[2:4] #[nt.Mesh(u'pCubeShape3'), nt.Mesh(u'pCubeShape4')]

So, be really aware of methods that returns a list of objects like ls, you will have to treat them as a list in any cases, emptyList [], or also a singe-item list.
You can access the list items with an index, and use -1 for the last of them.
You also can do a range inside, with this format [startIndex:endIndex:step], and all are optional!

Let's now modify the list:

newCube = pymel.core.modeling.polyCube() #
print len(theCubeList) #7
theCubeList.append(newCube)
print len(theCubeList) #8
del theCubeList[7]
print len(theCubeList) #7

So adding an item its the append() method of the object List, and del to remove an item.
del is a generic statement that allow you to remove from RAM any variables, if you want to free memory yourself.

Sometimes you will want to merge two List (if you use append you will nest your lists).
This can do with the method extend, (or the + operator) :

l1= range(2)
l2= range(2,5)
l1.append(l2)
print l1 #[0, 1, [2, 3, 4]]
l1= range(2)
l2= range(2,5)
l1.extend(l2) #Same as l1 = l1 + l2
print l1 #[0, 1, 2, 3, 4]

If you want to keep an indexed-iterator, you can use 'enumerate' from your list to return both values(your current object and its index)

for i,currentCube in enumerate(pymel.core.general.ls( selection=True)):
  print i, currentCube._name
  if i%2==0: #modulo operator helps here to delete each even index in your list.
    print "Even Index"

Other useful methods are available for list, like sort(), min() or max(), refer to the main doc for the full list.

With Maya 2013 Python API2.0 and also in MotionBuilder, a lot of c++arrays are managed in python as list.
So be very careful, when you iterate through a collection of objects, be REALLY SURE to avoid modifying your list into your loop.
A very famous mistake is to iterate into an object children and into the loop unparent or modify the hierarchy, like this one (in MotionBuilder):


import pyfbsdk
for i in range(10):
  pyfbsdk.FBCreateObject( "Browsing/Templates/Elements", "Cube", "MyCube" )
myGroup = pyfbsdk.FBCreateObject( 'Browsing/Templates/Elements', 'Null', 'GROUP' )

for currentElement in FBSystem().Scene.RootModel.Children:
  print currentElement.Name
  currentElement.Parent = myGroup #This will provoque less iterations than expected !
#Notice here that just even objects are parented...

#Storing the objects into a temp list (objectToParent) will solve this problem:
objectToParent=[]
for currentElement in FBSystem().Scene.RootModel.Children:
  objectToParent.append(currentElement )
for currentElement in objectToParent:
  currentElement.Parent = myGroup
[Original post] APyTA_06: Dealing with objects collection.


APyTA_07: Smart collections, advanced features (tuples and dictionaries)

In Python you can deal with an another variant of list. Please welcome TUPLES.
Tuples are size-fixed lists, with no possibility to assign items individually, and they are constructed with parenthesis():

myTuple= ("theName",12.34)
print type(myTuple), myTuple #<type 'tuple'> ('theName', 12.34)
myTuple[1]=42 # Error: TypeError: 'tuple' object does not support item assignment #
myTuple = ("otherName",2)
print myTuple #myTuple = ("otherName",2)

So when to use it ? As they are less flexible than lists, tuples have better performance for a find loop for example.
Use them when you don't need to append/del (so always a same length of items), and when every change needs to update the whole collection.

Basically, I use tuples (not very often) when I have to return not an object but a two/three fixed elements related together.
Sometimes you can use tuples like a very small class (like a struct in c++),with a useful usage to give arguments set
or to give a return value (a single object). But be very careful at the order and the length when parsing it.

In the following example, I use tuple to store the mousePosition better than a list:

import pymel.core
# Procedure called on mousse left-button pressed
def SampleContextPress():
  pressPosition = pymel.core.draggerContext( 'sampleContext', query=True, anchorPoint=True)
  print type(pressPosition) , pressPosition #<type 'list'> [290.0, 650.0, 0.0]
  myTuple = ( pressPosition[0],pressPosition[1] )
  print myTuple #(290.0, 650.0)
  return myTuple

#Commands to link left-clic to our function
pymel.core.draggerContext( 'sampleContext', pressCommand='SampleContextPress()', cursor='hand' );
pymel.core.setToolTo('sampleContext')

Next are the DICTIONARIES
They are unordered list of key:value pairs, where a key is an immutable object (often a string, or a number, but can be a complex object).

Unlike Tuples, I use dictionnaries aaaaaall the time. They are constructed with braces {}.
Think about them as a fashion to tag objects, and make really smart (and powerful) collections and relations.
You can swap a lot of iteration time, parsing your list at the research of your "suitable" object when computing.

For a real-world example, when dealing with graphic interfaces, users selects widgets by their names right ?
But into your code, you have to store the full object (the transform node for example), for better flexibility and readability.
So in my logic function I will convert from the ObjectName #<type 'str'> into the Object itself #<class 'pymel.core.nodetypes.Transform'> !

import maya.cmds
#Scene Creation
myCubeTransform = pymel.core.modeling.polyCube()[0] #Remember APyTA05 ? first item of polyCube's return value
myTorusTransform = pymel.core.modeling.polyTorus()[0]
myPlaneTransform = pymel.core.modeling.polyPlane()[0]

myDict={}
myDict[myCubeTransform._name] = myCubeTransform #Link: name => object
myDict[myTorusTransform._name] = myTorusTransform
myDict[myPlaneTransform._name] = myPlaneTransform
print myDict #{u'pCube1': nt.Transform(u'pCube1'), u'pTorus1': nt.Transform(u'pTorus1'), u'pPlane1': nt.Transform(u'pPlane1')}

def toggleVisibility(_Name):
  if myDict[_Name].isVisible():
    myDict[_Name].hide()
  else:
    myDict[_Name].show()

def UI_Prompt():
  cmds.window( width=150 )
  cmds.columnLayout( adjustableColumn=True )
  t = maya.cmds.text(l='Toggle Visibility of:')
  for currentKey in myDict.keys(): #Note here how I can easily acess to a list of keys
    maya.cmds.button(l=currentKey, c="toggleVisibility( '"+currentKey+"' )" ) #Notice the dual usage of simple/double quote (avoiding crappy escaping)
  cmds.showWindow()

UI_Prompt()

To finish with the dictionaries, you can also use list methods like len(), in, to valid your future access.
You also can have a MyDict.keys(), which is a list of string, containing the list of all the dictionaries keys.
Or you can also prompt the dictionary to valid your potential key with the MyDict.has_key method.

A complete understanding on how dictionaries work needs additional notions of HashTables.
To understand the hidden power behind that, I can refer you to this well written post: at http://www.laurentluce.com/posts/python-dictionary-implementation

At last, a third type of collections exist, the SET.
Just remember that a set is a list with NO DUPLICATES.
A very direct way to convert is by doing that:

myList=["aCube","anotherCube",42,"aCube",False,0,42]
mySet = set(myList)
print type(mySet), mySet #<type 'set'> set([False, 'anotherCube', 42, 'aCube'])
[Original post] APyTA_07: Smart collections, advanced features (tuples and dicionnaries)


APyTA_08: The devil as a string.

I may have had warned you about the danger of dealing with the name of the object instead of the object itself but...
Let's talk a little about strings, as you will have to often manipulate them (renaming and finding stuff mostly).
First rule : a string is.... a list of string (of one single letter) !

mySalutation="Hello_World"
print mySalutation, type(mySalutation) #Hello_World <type 'str'>
print mySalutation[6] #W
print type(mySalutation[6])#<type 'str'>

Unlike other languages, string is a basic class in Python and have a lot of useful methods.
Let's see examples of the very powerful renaming tools including in python which comes very handy:

mySalutationFriendly=mySalutation.replace("ello","i")+"_!"
print mySalutationFriendly #Hi_World_!
if mySalutation.startswith("Hello"): #Testing a start (you can also do a endswith)
  print mySalutation+"_"+str(42) #Hello_World_42
  print mySalutation+"_"+str(42).zfill(5) #Hello_World_00042

Please notice this replace methods does NOT change your variable content, so if you want to store it,
you will have to re-affect your variable with the result (And remember you can cascade the methods as in any classes)
Python is capable of polymorphism, but is STRONGLY typed. That mainly means for you that you can't concat numbers (int,float) and strings,
for example when printing a variable , so you have to explicitly convert your number as a string as follow: print("MyVarAsNumber="+str(MyVarAsNumber))

If you want to search if a substring is into your string, 'in' will do that perfectly, and split allow you to return a list of cut string, defining a separator.

if "_" in mySalutationFriendly:
  myList = mySalutationFriendly.split("_") #You can split on any character(s).
  print myList #['Hi', 'World', '!']

If you want to add special characters into your string, you will have to enter a specific code with backslash: \n or newline, \t for tabulation.
For including a backslash into a string, you have to escape it with another backslash: \\  and at last, for including a quote, you also have to escape it : \" .
But Python have anticipated that, and as he have three different string constructor: quote ', double-quote " and triple-double quote """, you can trick your string with that easily:
"""  Hello, currently my pseudo is "JohnDoe", but some of my friends call me "Mister '<tag>'  "  """

Finally, if you want more power to match a pattern into a string, you will need the 're' module, like that:

<pre>cameraName ="SHOT-Aa_C001_V01"
import re
print re.findall(r'\d+', cameraName) #['001', '01']
pattern = "SHOT-[A-Z][a-z]_C[0-9][0-9][0-9]_V[0-9][0-9]"
print re.match(pattern,"SHOT-aA_C001_V01")!=None   #False
print re.match(pattern,"SHOT_AA_C001_V01")!=None  #False
print re.match(pattern,"SHOT_AA_C01_V01")!=None  #False
print re.match(pattern,"SHOT_AA_C01_V0")!=None  #False
print re.match(pattern,cameraName,)!=None  #True

So, for pattern matching, use the re module and search a little bit more about REGEXP (Regular Expression). I will make a post on that module later anyway.
About special characters, be warn that string will handle ONLY ASCII letters.
So if you need to be able to manage special characters, like accentuation, you will need to use unicode (A next APyTA will deal with that too)

Also, dealing with path could be really painful.
AFAIK, you can write a path in three major styles:

<pre>MayaPath = "C:\\Program Files\\Autodesk\\Maya2012\\bin\\maya.exe"
MayaPath = r"C:\Program Files\Autodesk\Maya2012\bin\maya.exe" # r is for RAW, tels python to take your string "ASITIS"
MayaPath = "C:/Program Files/Autodesk/Maya2012/bin/maya.exe"

The last one is linux-friendly, do not care about escaping  backslash,
BUT I shamefully use the first one... because I mostly work on windows,
and because path are often computed from parts and pieces coming from your app, other tools, etc... and you have to be really sure they are formated the same to concat them...

[Original post] APyTA_08: The devil as a string.


APyTA_09: Looping and forking, logic's basics.

Keywords are special statement and are strictly forbidden to overwrite (as a variable or function/method), and represent the Kernel of Python mechanics.
In the following APyTAs I will try to explain them all, regrouped by categories. Your can have dynamically the list like that:

import keyword
for currentKeyword in keyword.kwlist:
  print(currentKeyword)

First let's have a quick look at the boolean keyword operator:
and #  (False and True) ==False (and because the first term is False, and' will not eval the second term)
or # (True or False) ==True (same mechanics here, 'or' will ignore second term if the first term is True)
not #Boolean unary operator  not True  False
is #Identity comparison (== is the equality comparison)

The most common snippet I can give to you here is a validation test to a user selection:

import pymel.core.general
MySelection = pymel.core.general.ls(transforms=True)
if( len(MySelection) >0 and type(MySelection[0]) == pymel.core.nodetypes.Transform) :
  #Manage here your logic safely, do all operations available on a Transform object.
else:
  print("Please select a transform object")

Looping is the basics for the logic building in script, so let's enumerate all their keyword related:
if #Open a scope if the condition is True
else #Coupled with if, open a scope if the condition is False
elif #Syntaxic contraction of else if
for #Create an iterative loop
in #Create a temp variable (the current element) to loop into a list
while #Create a conditional loop
break #Exit the current scope
continue #Go to next scope (or iteration in loop)

import pymel.core
Mode = 1
MAX_NUMBER=25

if(Mode==0):
  for i in range(MAX_NUMBER):
    pymel.core.modeling.polyCube()
elif(Mode==1):
  while(len(pymel.core.general.ls(geometry=True))<=MAX_NUMBER):  #The loop will stop when your condition is True. So be REALLY SURE to reach it!
    obj = pymel.core.modeling.polyCube()
    if obj[0]._name =="pCube6":
      continue
    elif obj[0]._name =="pCube10": #When reaching this test, the while exit, and you have just 10 cube created.
      break
    print("Creating:"+obj[0]._name) #This sentence will not be printed for pCube6 due to the continue statement.
else:
  print("Unsupported mode")

My advice is to avoid the 'while' looping when it is possible.
In the most common cases, believe me, you can enumerate the range of your objects-to-evaluate (It is often your scene objects, or part of it, filtered on some criteria).
The danger of the 'while' looping is a misunderstanding of your object manipulation (or about Maya self-refresh computation), and not anticipate a context-change (like Maya auto-renaming to avoid names conflict).

[Original post] APyTA_09: Looping and forking, logic’s basics.


APyTA_10: Function mechanics, arguments and scope notions.

Main keywords for defining functions logic are :
def #Define a function (or method)
print #Display a value in current output (sys.stdout)
del #Remove the variable from the RAM
pass #Exit the scope, doing nothing (useful to tag a scope into a TODO status, because python wants always something if you create a scope (by ':')
return #Allow the function to return a computed result, the best way to communicate a value through a scope.
class #Create an object (a future APyTA will explain that)
assert #Can authenticate your function arguments

As we already seen some function snippets, let's dig into assert keyword: http://wiki.python.org/moin/UsingAssertionsEffectively
Its purpose is to help you validate your given argument in a scope of a function.
Python polymorphism could be quite scary, and when you have data from the 'outside world', you may want to be really sure of the type of the objects you will have to deal with:

import pymel.core.nodetypes, pymel.core.modeling, pymel.core.datatypes

def raiseObject(_OffsetZ, _theObject, _TypeChecking=False): #Putting a value like that MyArg=Value are called Optional Argument (Syntax law force optionnal arguments to be ther LAST ones).
  if(_TypeChecking):
    assert type(_OffsetZ) is float, "_OffsetZ is not an float: %s" % `_OffsetZ` #I kept these string formating (usable also with print) to show you them but I'm not a big fan of it.
    assert type(_theObject) is pymel.core.nodetypes.Transform, "_theObject is not a Transform: %s" % `_theObject`

  _theObject.setTranslation( pymel.core.datatypes.Vector(y=_OffsetZ) ) #See how here I need _theObject of being a Transform ? .setTranslation( will be meaningless otherwise.

myCubeTranform  = pymel.core.modeling.polyCube()[0]
liftObj = raiseObject #Function are vars to. Usefull to create aliases.

raiseObject(5.0, "pCube1",True) # AssertionError: _theObject is not a Transform: 'pCube1' #
raiseObject(5.0, "pCube1")# AttributeError: 'str' object has no attribute 'setTranslation' #   Excepected, we skip the assert part, but the core of the function compute with a wrong type
liftObj(5, myCubeTranform,True) # AssertionError: _OffsetZ is not an float: 5 #   but liftObj(5, myPolyCube)  could have worked
raiseObject(5.0, myCubeTranform,True) #OK#

For the validation of function arguments as a process in development, we can really modelize an Axis, from devs that check everything: every variables every time;  into those who delegates these tests to dev/user intelligence and API knowledge.
My advice is to be nearly at the middle of it. Python scripts are clear text file, readable, and easy understandable, so personally I don't not check often my arguments integrity.
Especially when you build a framework, with a lot of packages/modules navigation, your variable could be checked in chains through its living time if you implement an assert in every function/methods...
But, I always check my data when they are coming from a different context, I mean by that a user graphical interface (prompt, comboBox, etc.), when reading a file or accessing your selection (Does the user REALLY selected a joint ?), or any scene content (therefore, a "context stranger").

Well, in the last example, we will see how a function could be flexible in python, and dig into the notion of scope.
A SCOPE is the meaning of an new indentation (mandatory after a ':') , and represent a "living context" for all objects (variables) created in it.
The golden rule to remember is this tagging mechanic: when escaping a scope (therefore reaching a dedented line), the tags will be discarded (by the engine called Garbage collector).
When you create a variable in a scope (aka a level of indentation), your variable will 'live' through it, and 'die' when the scope die, so at its dedentation (there are exceptions, but the collector will always destroy your variables at the exit of a function/method).
We saw in looping lesson the 'for' and the 'while', but let's have a look of another loop type, without keyword this time, just a play with a function that can call itself:

def createCubes(DefaultName="Qube",MaxNumber=25):
  VariableScopeRelated_tocreateCubes=123

  def createCubeAndGoOn():
    pymel.core.modeling.polyCube(name=DefaultName)
    if len(pymel.core.general.ls(geometry=True))<MaxNumber : #Like the while condition be REALLY SURE to reach your test unless it is infinite loop!
      createCubeAndGoOn()
      #Here, VariableScopeRelated_tocreateCubesis still alive, as in any scope  'under' the first one (createCubes one)

  createCubeAndGoOn()
createCubes(MaxNumber=10,DefaultName="Kube") #Creates 10 cubes

print VariableScopeRelated_tocreateCubes# Error: NameError: name 'VariableScopeRelated_tocreateCubes' is not defined #

Several comments on that one:
Please notice that you are allowed by python to create a function (and its scope) anywhere, and also in a class definition.
It is quite useful in the resistivity mode, which need often an starting function, and a recursive one.
Notice also that if you specify a keyword argument, you are no longer tied to the order of the argument.

Last, but not least, learn that you can define functions without specify arguments,  like that:

def doIt (*args):
  print type(args), args
doIt(1,"anything",1.0) #<type 'tuple'> (1, 'anything', 1.0)
def doIt2 (**Kwargs):
  print type(Kwargs),Kwargs
doIt2(arg1=2,arg2="anything") #<type 'dict'> {'arg1': 2, 'arg2': 'anything'}

*args: Tells python to pack all arguments into a TUPLE.
**Kwargs: Tells python to pack arguments into a DICT

Of course, you will to start the core of your function by analyzing the given tuple or dict.
... and that's all folks !

[Original post] APyTA_10: Function mechanics, arguments and scope notions.


APyTA_11 : Referencing your work

Referencing is crucial to fully express python usability and re-usability.
import #Record external reference (will execute rooted-code)
as #Allow you to overwrite a namespace(module) path
from #Allow import to access specific classes

Python allow you to create references to scripts, the same way you create external files in reference in Maya.
To do that, the notion of class and objects are extended to:
Modules : The file .py , which contains your code.
Packages : The folder containing the modules (therefore python files), but only if it have a specific file called __init__.py
Theses notions will helps you to setup a full framework hierarchy very easily ! (cf notions of Namespaces in other languages).

When referencing to your external script file, you have to specify the relative path of all your packages (folder), concatenated by '.'
Python gives you four main ways of doing that, here for example creating a polyCube:

import pymel.core.modeling as pmc
pmc.polyCube()
from pymel.core.modeling import polyCube
polyCube()
from pymel.core.modeling import *
polyCube()
polyTorus()
import pymel.core.modeling
pymel.core.modeling.polyCube()

Personally, I recommend the last one, (the uglier I know), for more explanations please refer to my post on Acronyms are evil.
Be precise. And anyway, do NOT use import *, as it really is evil (collapse all paths and is a great risk of overwriting functions or misunderstanding).
Remember the notion of scope in the previous lesson ?
Well, you can also consider a module like a scope too, and the same for the package (containing in the __init__.py, often empty)
__init__.py can store also piece of codes, more often used for putting recurrent import in the modules of your package.

If you want to import again a module (for example after modifying your module), you can use the reload() statement.
But, your old objects, variables, will not be updated (be aware of retro-compatibility glitches).
Sadly I must confess that I advice you to quit Maya (or any context that embed python) when you change your code.
Could be boring, but believe me, you will avoid some issues like that !
When I was alpha-testing videogames, the "teleporting" cheat-tools to avoid hours of gameplay while testing a end of level was strictly forbidden.
I kept this experience in mind, and my advice is to test a feature in REAL condition.
It's boring, it's often a long process, but you will avoid so many bugs and unexpected context if you do not "play the game"
or re-generate each time a user roleplay (and interface interaction).

With the previous snippet, you may have noticed that the path to references scripts are not OS-formatted path, like the windows "C:\MyFolder".
This is because python have some preferences, as starting points to do its searches.
You can print them, and add yours with that lines:

import sys
print sys.path
sys.path.append("YourPackageFullPath")
#Now you can type "import YourModule" (at condition that file was in  YourPackageFullPath\YourModule.py

print sys.modules #Show all modules loaded in RAM (import)

In windows, you can also change that value before python starts using Environment Variable
(From StartMenu, or typing the command "SETX PYTHONPATH YourPackageFullPath")
In Maya, if a module called "usersetup.py" are in sys.path, Maya will execute the code inside
(unless there is also a script called userSetup.mel, which always have priority).
Also, if there is a module called "sitecustomize.py", python will execute its code at startup.

Notice that if you want to execute a module outside of Maya (just with a python commandline), for batching process for example,
You have to execute this line (still in windows): C:\Program Files\Autodesk\Maya2012\bin\mayapy.exe "C:\...\yourFile.py"
But, in yourFile.py, you have to but a specific block of code to tells python what to execute inside:

if __name__ == "__main__":
  print("Launching application")
  #INSERT here what you want to execute when running your command.

Notice that mayapy.exe is the python executable embedded with maya, but can be run separately.
But you can't do the same thing in MotionBuilder for example, and sometimes you will need to have your own independent python on an "empty" client machine.
So you will have to install it from downloading in the website. But be sure (avoid issues) to have the same version (in your Maya for example).
You can know its version by printing the sys.version

[Original post] APyTA_11 : Referencing your work


APyTA_12: Handling exceptions, just in case.

Let's see now, how python will help you to manage errors and failure! Yes it can do that too!
try #Test if the next instruction is valid
except #Coupled with try, create a scope in case of try failure
finally #Coupled with try, open a scope independent of try success
raise
#Throw a custom-exception, defined as a class

No full explanation needed here, just see the magic by yourself:

aList=[]
try:
  Doomed = aList[56]  # Error: IndexError: list index out of range #
except :
  print("ERROR")
else:
  print("OK")
finally:
  print("Anyway...")

try:
  Doomed = aList[56]
except IndexError:
  print("ERROR On Index")

try:
  print("toto="+45)
except TypeError: # Error: TypeError: cannot concatenate 'str' and 'int' objects #
  print("ERROR On Type")

Because you sometimes (often?) can't manage all the cases combination that will define your context when computing,
these tools helps you to handle an unexpected and behave accordingly.
You can just let the except keyword alone, or differentiate your scopes to execute, depending on your error type
(You can find all errors types at the address : http://docs.python.org/tutorial/errors.html)

By the way , with raise keyword, you will be able to create your own type of errors, using classes.
(cf further about classes and object creation).

import pymel.core.general
class UnitarySelectionError(BaseException):
  pass

def validSelection():
  currentSelection = pymel.core.general.ls(selection=True, transforms=True)
  if(len(currentSelection)>1):
    raise UnitarySelectionError()
  else:
    print currentSelection[0].name()

try:
  validSelection()
except UnitarySelectionError:
  print("The user have selected more than one transform")
except:
  print("The user have selected nothing!!") #Reached because we do not handle an empty selection, so it is an IndexError at line17
else:
  print("It's ok, you can go on") #Reached if your selection is one transform object)
[Original post] APyTA_12: Handling exceptions, just in case


APyTA_13: Most used modules, Diving into the system.

Let's talk about the most famous modules.
Theses are not external, they comes as built-in into your python, all you have to do to start playing with them is an import.

pprint (pretty print) is a very handy module to replace the standard print. http://docs.python.org/library/pprint.html
Use it when you need a visual feedback on a variable that is a dictionary, or some nested lists.

The module sys (for system) is a module very helpful to prompt python about itself and its mechanics. http://docs.python.org/library/sys.html
We already view the sys.path which return a string list of all the filesystem directory where python will look for when importing.
You can nicely browse all the modules in ram with .modules (example given with pprint):

import sys
import pprint
pprint.pprint(sys.modules)

Another cool feature of this module is the getsizeof function.
Managing HUGE collections of datas ? Big batch loops ? Well then, keep track of what you are really storing.

import sys
sys.getsizeof("a") #41
sys.getsizeof("") #40
sys.getsizeof(None) #16  So None is 2.5 lighter than an empty list, see what I mean ?
sys.getsizeof(False) #24

Sys help also to manipulate its own buffer stream. Python have a three level of buffer : standard In, Out, and Error (coming from linux world).
For us, what is matter is just standard output (error is the same thing), and that we can redirect it:

userInput = sys.stdin.read() #This just open a prompt
print userInput
sys.stdout.write("This is standard output") #See ? Like a print, but without the newline
sys.stderr.write("This is standard error")

sys.stdout = open("F:\PythonCourses\MayaBuffer.txt",'w') #Insert here an already exsisting file
sys.stdout.write("This is standard output") #Nothing is printed in the Maya script editor !

Notice that the file will not be readable instantaneously, but will be flushed when Maya is closed (or if you close it intentionally with .close() ).
We will see above a better way to log your infos. Eventually, know that you also can access to the starting buffer, initialized at python launch.
But now let's have a look at sys.__stdout__ which is a recorded value of default output for printing at python startup.
This helps that a lot because by default it is the output window of Maya ! (Which have better performance, and can isolate your verbosity from usual commands feedback):

sys.__stdout__.write("I am written in Output window, and I like that.")

I use the module time when I need to name my unique files, or when I upload tracking into the Asset Manager.
http://docs.python.org/library/time.html

import time
print time.strftime("%Y-%m-%d") #2012-05-26
print time.strftime("%H:%M:%S") #12:45:47
print time.strftime("%YY-%mM-%dD_%Hh%Mm%Ss") #2012Y-05M-26D_12h46m07s  This is how I format the time personally

The module logging is dedicated to help you keeping trace of your outputs, and I recommend heavily to do that.
CGI Applications crashes. It happens. You need to keep a log of what the user was doing, that is on top of the application. In a file.
It help you a lot, in AGILE development, when frequently users ask you to come to their desk for an "error".
What error ? They do not read it, or they forgot. But the log know.
And you will not have to reproduce the manipulation (which often requires loading a huge file).

import time
#Setuping of the logger:
import logging
logger = logging.getLogger('ToolName')
logger.setLevel(logging.DEBUG)
LogFileFullPath = LOG_FOLDER+"\\"+time.strftime("%YY-%mM-%dD")+'.log' #I record a log in a daily file
logFileHandle = logging.FileHandler(LogFileFullPath)
logFileHandle.setLevel(logging.DEBUG) #This Level could be change in a menu of yours, if the users wants to be in "quiet mode" for example (speed up huge ierations).
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') #This is how you setup the prefix of all your lines.
logFileHandle.setFormatter(formatter)
logger.addHandler(logFileHandle)
#Example of usage:
logger.info("This is a normal message") #Use that for classic feedback, resolved variables.
logger.warn("This is a warning message") #Use when something is wrong but the process can continue. Show must go on.
logger.error("This is an ERROR !") #Ok, this is Unexpected context, a non-sense argument. You can not continue. Function will stop and user must know it.
logger.critical("This is the end of the WORLD!!!") #Without the feature in scope (connection to the DB, internet acess for examples), the tool is USELESS. Please contact your administrator and give him a coffee.
[Original post] APyTA_13: Most used modules, Diving into the system


APyTA_14: Most used modules, Exp Files management.

The os module, for Operating System, is a module that helps you deal with your filesystem.
A sub-module, called os.path, will be dedicated to the path resolving and manipulation.
http://docs.python.org/library/os.html and http://docs.python.org/library/os.path.html

You will see in the os doc a lot of low-level functions, because python handle very well the kernel of the file system.
But we do not need that depth, often so let's have a look first about how dealing with files easily:

FILEPATH_ASSTRING=r"F:\PythonCourses\PYTHON_TEST01.txt" #For this test please enter here a path where you have the right to create files.
myFile = open(FILEPATH_ASSTRING,'w')
print type(myFile)#<type 'file'>  Another python object, called a File Descriptor
myFile.write("line1\n")
myFile.writelines(["line2\n","line3\n"])
myFile.close()
print open(FILEPATH_ASSTRING,'r').readlines() #returned as a list of string (lines)
#['line1\n', 'line2\n', 'line3\n'] (to check use REAL notepad, like notepad++, forget the crappy one in windows)

Manage a file with the open built-in function, giving a path (remember the string format for path).
Your variable will not be the string stream, it is an handle, a file - descriptor. Take that object like a cursor into your text editor.
Notice also the second argument. You can open a file in ONE MODE ONLY. 'w' is for write, and if anything in the path already exists, it will purely vanish into the warp !
So be very careful in which mode you have to deal with your file ('w' is write, 'r' for read, 'a' for append).

I hardly show you a full example here, because it will require involving your filesystem, so let's enumerate the most useful functions:
os.mkdir(path) #Create the directory at path (but the sub-path must exist)
os.makedirs(path)#Create the directory at path, and create all parent folder if the sub-path does not exist !
os.rename(OldNamePath, NewNamePath) #Rename the file (but can not change its path).
os.listdir(path) #Get all direct children of current Folder. Could be files or sub-folders !
os.remove(path) #Delete a file, do not work if path is a directory!
os.rmdir(path) #This remove a directory, but only if it is empty
os.chmod(path,mode) #Change the attribute of the file at path (use to put a file in read-only)
os.chown(path, uid, gid) #Change the owner of the file at path (give it to root for example).

import os,os.path
print os.environ #Will display all you need to know about your current machine (USERNAME,COMPUTERNAME,USERDOMAIN, etc.)
myTempFolder = os.environ['TEMP'] #For me it is : 'C:\\Users\\Username\\AppData\\Local\\Temp'
if not os.path.exists(myTempFolder):
  os.makedirs(myTempFolder)
for currentItem in os.listdir(myTempFolder):
  if os.path.isfile(myTempFolder+"\\"+currentItem): #Warning currentItem is JUST the file name.
    print currentItem

The os.path module is a tool to analyze the string as a path one, and the exists function is really mandatory to safely consider the file or dir.
FILEPATH_ASSTRING=r"F:\PythonCourses\PYTHON_TEST01.txt" #remember the r to express the string as RAW ?
os.path.exists(FILEPATH_ASSTRING) # Result: True #
os.path.basename(FILEPATH_ASSTRING) # Result: PYTHON_TEST01.txt #
os.path.dirname(FILEPATH_ASSTRING) # Result: F:\PythonCourses #Returned value is full path of the directory!
os.path.getsize(FILEPATH_ASSTRING) # Result: 18L #  In Bytes
os.path.isdir(FILEPATH_ASSTRING) # Result: False #
os.path.splitdrive(FILEPATH_ASSTRING) # Result: ('F:', '\\PythonCourses\\PYTHON_TEST01.txt') #
os.path.splitext(FILEPATH_ASSTRING) # Result: ('F:\\PythonCourses\\PYTHON_TEST01', '.txt') #
os.path.splitunc(FILEPATH_ASSTRING) # Result: ('', 'F:\\PythonCourses\\PYTHON_TEST01.txt') #   The first tuple will be the start of a UNC path like \\ServerName\...

If you want to copy a file very easily, use the shutil module which can do that for you:
http://docs.python.org/library/shutil.html

import shutil
shutil.copy(SourcePath, DestinationPath) #Copy the file from source to destination
shutil.copy2(SourcePath, DestinationPath) #Idem, but also copy the stat ! (owner, creation date, etc.) A perfect clone!
shutil.copytree(SourcePath, DestinationPath) #Can duplicate recursively a whole filesystem hierarchy.

At last, but not least, os.walk is a very powerful function to explore recursively your filesystem.
Here is the snippet:

fileList=[]
rootdir = os.environ['TEMP']
for root, subFolders, files in os.walk(rootdir):
  for currentFile in files:
    fileList.append(os.path.join(root,currentFile)) #You can use os.path.join instead of the operator +"/"+
import pprint
pprint.pprint(fileList) #Lots of crap right ?
[Original post] APyTA_14: Most used modules, Exp Files management.


APyTA_15 : OBJECT Part I, Everything is an OBJECT is Everything...

The golden rule in python is this. Everything is an object, so without fully understand it, you already use it a lot.
You may also already known that python is an object-oriented language.
It is time to define this notion right now.

A class is a definition of a template, describing a structure. A type.
An object is an instance of this class, its application into a 'real' case

MyVar = "lalo"
type(MyVar) # Result: <type 'str'> #
MyVar.__len__() # Result: 4 #
id(MyVar)# Result: 875904576L #
MyVar.replace("la","ri") # Result: 'rilo' #

Looking at this little piece of code, you have to understand that the type statement will return the name of the class, given the object.
Every time you create a variable, using the = operator, python will store somewhere in RAM (indexed by id() ) a copy of the class definition, with your given values.
This is the key concept here, the instantiation. And forget about Maya Instances, the concept here is different.
To take a more accurate example, Instantiate a class into a variable is like copy/paste a template folder, where you already put some subFolder/subFiles to avoid to do that every time.
Storing a named variable with a typed-value is precisely instantiate a class, given its template as a type, to create a new object.

The next notion, and sorry again, some vocabulary to learn, is to know what classes are made of.
Basically, they are a list of Members and Methods.
Members are local variable, created in the scope of the instance of the class, and refers to a value (could be a built-in, as int or string, but could be any other classes too).
Methods are like members, created locally in the object-scope, but they are functions defined with a def statement, they have a scope,  and can return value.

Python does not comes with built-in variables type to manage Vectors. Maya does, but forget that and focus on how to do some calculus with vectors.
First, you can create a variable, setting it with a tuple of three floats, like MyVector = (1.2,3.4,5.6). But you will very soon have readability issues to access the Y axis by MyVector[1] ...
Secondly, you will have to create a lot of calculus functions, and every time you will need to assert your arguments, because they will work only for your specific tuple.

import math
class APYTA_Vector():

  def __init__(self,_x=0.0,_y=0.0,_z=0.0):
    self.x=_x
    self.y=_y
    self.z=_z

  def getLength(self):
    return math.sqrt( self.x*self.x + self.y*self.y + self.z*self.z)

  def Normalize(self):
    InvLength = 1 / self.getLength()
    self.x *= InvLength
    self.y *= InvLength
    self.z *= InvLength

print APYTA_Vector() #<__main__.APYTA_Vector instance at 0x000000000CF23B88>
MyVector = APYTA_Vector(1,2,3)
print MyVector.y #2
print MyVector.getLength()#3.74165738677
MyVector.Normalize()
print MyVector.x, MyVector.y, MyVector.z #0.267261241912 0.534522483825 0.801783725737
print MyVector.getLength()#1.0

First, we can see here the class is defined by the keyword class. I usually had a prefix to my classes, because clashes can occurs, and that signed them explicitly.
The members of the class are defined into a very specific function (a Method) __init__(self). Here I setup three members, initialized at 0.0
Remember well the self , which is mandatory to be the first argument, and it explicitly refers to the Instance of the class (the object) .
This __init__ function is not really a constructor like in other languages (if you omit it, your object still exist), but it is used for initialization, declaration of members.

Like __init__, you can define any functions, with def statement, into the scope of the class. It is exactly like a function, living in the scope class, and having a first mandatory argument self.
Here, the method getLength can return an evaluation of all the object members, and see how referring to it is so neat : MyVector.getLength() .
This is the "object revolution" in coding. You will start to forget about processes that take "that and that" and return its calculus.
You will begin to manage object of meaning. Define functions (Methods) that just make sense.

Finally, see how I can modify the members of my object into a method, like in Normalize() ?
Nothing is returned, but the values (members) of my current instance are updated according to the processes of Normalization.
It is the very same thing of a list.sort() for example.

[Original post] APyTA_15 : OBJECT Part I, Everything is an object, and Object is everything, and…


APyTA_16 : OBJECT Part II, How to inherit a skeleton

I promise you that object-oriented coding will be meaningful.
For a more accurate example, I have a biggest snippet to show you , dedicaced to rig team.

First we have to introduce a new concept between objects : the inheritance.
You can also create a hierarchy between objects, indicating explicitely in a class a super-class.
Not like parenting, telling a class is issued from another object (could be your classes or another built-in type) is about describing an object which is a more complex version of this class.
The super-class is a generalisation of your class.
Here is a quick example of hierarchy into classes:

class Animal:
  pass
class Mamal(Animal):
  pass
class Cat(Mamal):
  pass

Well, so you can create objects with inheritance of other classes
and you can create members (self-owned variables, which are objects too).
Sometimes it is very hard to DESIGN when contain, and when inherit.
This will help you in most of the case:
"inherits" means " is a sort of "
"contains (members)" means "have some "

Not as before, I will comment the code directly inside the snippet, as there is a lot to say!
So, as I want to create a skeleton, how object-orientation can change my way of coding ?
This is my design:
I propose to create an Avatar object. It contains Limbs.
There are two sort of limbs, arms and legs.
Each of those are basically a list of joints.
Here's the snippet (do not care for lame-algorithms, a more completed version will follow in next post)

import math
import maya.OpenMaya
import pymel.core


class Avatar(object):  # Also when desribing your mother-of-all class, always inherit from the python class-object (you will benefits of some built-ins)
    """ This object is a rigged-skeleton representation of a character """

    def __init__(self, _Size=1.70, _NbArm=2, _NbLeg=2, _hasTail=False):
        self.Size = _Size  # How tall it is. Float,  in meters

        self.LimbList = []   # Limb Lists
        self.ArticulationList = []   # All Joints Lists
        self.Hips = None  # Quick Access shortcut
        self.NeckBase = None  # Quick Access shortcut

        self._createTrunk()  # We can call an method into the inialisation process of our Avatar instance.

        RelativePosition = maya.OpenMaya.MVector(math.cos(0), 0, math.sin(0))
        currentArm = Arm("Left", self.NeckBase,  RelativePosition * 0.2)  # Here we create a new instance of class Arm, and Arm-Object.
        self.LimbList.append(currentArm)

        RelativePosition = maya.OpenMaya.MVector(math.cos(math.pi), 0, math.sin(math.pi))
        currentArm = Arm("Right", self.NeckBase,  RelativePosition * 0.2)
        self.LimbList.append(currentArm)

        RelativePosition = maya.OpenMaya.MVector(math.cos(0), 0, math.sin(0))
        currentLeg = Leg("Left", self.Hips,  RelativePosition * 0.2)
        self.LimbList.append(currentLeg)

        RelativePosition = maya.OpenMaya.MVector(math.cos(math.pi), 0, math.sin(math.pi))
        currentLeg = Leg("Right", self.Hips,  RelativePosition * 0.2)
        self.LimbList.append(currentLeg)

    def _createTrunk(self):
        """ Will create the trunk of the body """
        HipsPos = maya.OpenMaya.MVector(0, self.Size * 0.4, 0)
        SpineOffset = (self.Size - HipsPos.y) / float(8)

        for increment in range(8):
            pymel.core.general.select(clear=True)   # We do not want Maya to create fuzzy stuff with active selection. So before each creation we clear the selection.
            currrentArticulation = pymel.core.nodetypes.Joint(name="Spine_" + str(increment + 1).zfill(2))
            currrentArticulation.setTranslation(maya.OpenMaya.MVector(0,  HipsPos.y + increment * SpineOffset, 0))

            if self.ArticulationList:  # Not the first joint, we have to connect it to the previous on...
                pymel.core.connectJoint(currrentArticulation,  self.ArticulationList[-1],  parentMode=True)  # Remember,  -1 is the index of the last member of a list.
            else:
                self.Hips = currrentArticulation

            if increment == 6:  # The last two bones are Neck and Head, so Arms takes their root from bellow.
                self.NeckBase = currrentArticulation

            self.ArticulationList.append(currrentArticulation)


class Limb(object):
    def __init__(self, _Name):
        self.ArticulationList = []  # Same name as in Avatar member, but 'self.' indicates the ownership of this class instance. We're deadling with two very different variables so.
        self.Name = _Name


class Arm(Limb):
    def __init__(self, _Name, _NeckBase,  _Offset):
        super(Arm, self).__init__("Arm_" + _Name)  # Inheritance in python is not automatic. Super (Mother) class of Arm is Limb, and its methods are callable form Arm instance, but it needs the keywords super to do that.
        BasePosition = _NeckBase.getTranslation(space='world')

        for i in range(3):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))
            currentArticulation.setTranslation(BasePosition + _Offset * (i + 1))
            if not self.ArticulationList:  # The first arm joint must be connected to the Base.
                pymel.core.connectJoint(currentArticulation,  _NeckBase,  parentMode=True)
            else:
                pymel.core.connectJoint(currentArticulation,  self.ArticulationList[-1],  parentMode=True)
            self.ArticulationList.append(currentArticulation)


class Leg(Limb):
    NbArticulation = 3

    def __init__(self, _Name, _HipsBase,  _Offset):
        super(Leg, self).__init__("Leg_" + _Name)
        BasePosition = _HipsBase.getTranslation(space='world')
        previouslyCreated = _HipsBase
        for i in range(3):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))

            if(previouslyCreated == _HipsBase):  # First joint
                currentArticulation.setTranslation(BasePosition + _Offset, space='world')
            else:
                InverseLerp = (1 - (i / float(2)))   # Lerp is the transpose of a range FROM [min = >max] TO [0 = >1]   # Arm.NbArticulation-1 because we do not care about the first Articulation
                currentArticulation.setTranslation(maya.OpenMaya.MVector(previouslyCreated.getTranslation(space='world').x, previouslyCreated.getTranslation(space='world').y * InverseLerp, previouslyCreated.getTranslation(space='world').z), space='world')

            pymel.core.connectJoint(currentArticulation,  previouslyCreated,  parentMode=True)
            previouslyCreated = currentArticulation
            self.ArticulationList.append(currentArticulation)

I hope you had catch it. Now let's manipulate that:


JohnDoe = Avatar()  # This variable aim to a newly created object, typed from class Avatar.

#As in pymel, you no longer need to find Hips by name and fit a naming-convention.

JohnDoe.Hips.setTranslation(maya.OpenMaya.MVector(1,2,3)) #Move hips

#Now, let's select both hands:
pymel.core.general.select(clear=True)
for currentLimb in JohnDoe.LimbList:
    if type(currentLimb) is Arm:
        pymel.core.general.select( currentLimb.ArticulationList[-1],add=True )

See how Object-Orientation is more about giving sense to a bunch of integers and strings ?
But this backside of the medal is this design thing. Sometimes you don't need to deeply create a very precise hierarchy of objects.
Worse, you can have a big change in your framework (like a new Software in the pipeline), which needs you to re-think your all classes...
As this concise example lacks of real interest, the next post will go (a little) further into Avatar creation.

[Original post] APyTA_16 : OBJECT Part II, How to inherit a skeleton


APyTA_17 : OBJECT Part III = Static tactics

Cosmogonies_APyTA_17

Illustation of Lesson APYTA#17:
http://cosmogonies.net/Blog/category/apyta/

Well, this snippet is just a more elaborate version of the latest post, about creating an object-oriented skeleton:
Because my credo here is to illustrate how objects is all about meaning, giving sense (and some reality) to our lines of code, I propose here to add some arguments to give some consistence to our skeleton, a little how we create a character in role playing game^^. So, this is my prototype of my Avatar creation:

#
def __init__(self, _Name="DefaultName", _Age=30, _Size=1.70, _Sex=eSexe.kFemale, _NbArm=2, _NbLeg=2, _hasTail=False):
#

See how we are dealing with real stuff, and also giving more flexibility than previous snippet.

By the way, you have now to discover another concept, static members and methods.
I talked previously how a class is instantiated into a variable: myInstance = myClass().
The instance will benefits to all members and methods (functions) bound to the self keyword in class definition.

Well sometimes you don't want to bind members to instances. You want a variable to be linked to the scope of the class itself !
These are called static members and static methods, and examples are explained bellow.
Again, rather than cutting the long snippet, I placed my comments through the lines inside the code itself:

import math
import maya.OpenMaya
import pymel.core
import maya.cmds

maya.cmds.currentUnit(linear='m')  # Unless you're a gridsnap-lover, I prefer working with meter units that make more sense for me.
maya.cmds.jointDisplayScale(5)

class eSexe():
    """ An enumeration of all sex status """
    (kMale, kFemale) = ("Male",  "Female")  # This snippet is useful when you want to manage an very simple-object with several enumeration (as struct in c)

class Avatar(object):  # Also when desribing your mother-of-all class, always inherit from the python class-object (you will benefits of some built-ins)
    """ This object is a rigged-skeleton representation of a character """
    NbSpine = 8  # Arbitrary, we decide that all spinal chain will have 8 joints. So it is in a static Member of Avatar class.
    HipsRatio = 0.4  # Percentage of hips height. Arbitrary also !

    def __init__(self, _Name="DefaultName", _Age=30, _Size=1.70, _Sex=eSexe.kFemale, _NbArm=2, _NbLeg=2, _hasTail=False):
        self.Age = _Age  # With age,  bones are stacked and incurved,  int ,  in years
        self.Size = _Size  # How tall it is. Float,  in meters
        self.Sex = _Sex  # Sexe influence Hips Width and Shoulder width

        self.LimbList = []   # Limb Lists
        self.ArticulationList = []   # All Joints Lists
        self.Hips = None  # Quick Access shortcut
        self.NeckBase = None  # Quick Access shortcut

        self._createTrunk()  # We can call an method into the inialisation process of our Avatar instance.

        ArmLength = self.NeckBase.getTranslation(space='world').y - self.Hips.getTranslation(space='world').y
        for i in range(_NbArm):
            angle = (float(i) / float(_NbArm)) * 2 * math.pi
            RelativePosition = maya.OpenMaya.MVector(math.cos(angle), 0, math.sin(angle))
            newArm = Arm(chr(65 + i), self.NeckBase, RelativePosition * Limb.getGenderOffset(Arm, self.Sex), ArmLength)  # Here we create a new instance of class Arm, and Arm-Object.
            newArm.createIK()  # Notice here we call the method from the newly created instance. For Leg it is done differently.
            self.LimbList.append(newArm)

        for i in range(_NbLeg):
            angle = (float(i) / float(_NbLeg)) * 2 * math.pi
            RelativePosition = maya.OpenMaya.MVector(math.cos(angle),  0,  math.sin(angle))
            newLeg = Leg(chr(65 + i), self.Hips,  RelativePosition * Limb.getGenderOffset(Leg, self.Sex))  # Notice also the call of a static-method of class Limb.
            self.LimbList.append(newLeg)

        if _hasTail:
            self.LimbList.append(Tail(self.Hips, 10, 2))

        RootLocator = pymel.core.circle(normal=(0, 1, 0), center=(0, 0, 0), radius=0.5)[0]
        pymel.core.annotate(RootLocator, text=str(_Name), point=(0, 0, 0.5))
        pymel.core.parent(self.Hips, RootLocator)

    def _createTrunk(self):
        """ Will create the trunk of the body """
        HipsPos = maya.OpenMaya.MVector(0, self.Size * Avatar.HipsRatio, 0)  # Notice here the use of static member of Avatar, treat that like a constant value.
        SpineOffset = (self.Size - HipsPos.y) / float(Avatar.NbSpine)
        SenescenceOffset = float(self.Age) / 100.0  # Factor of spine curve due to age (Cheating because Height is conserved)
        SenescenceOffset *= 0.01

        for increment in range(Avatar.NbSpine):
            pymel.core.general.select(clear=True)   # We do not want Maya to create fuzzy stuff with active selection. So before each creation we clear the selection.
            currrentArticulation = pymel.core.nodetypes.Joint(name="Spine_" + str(increment + 1).zfill(2))
            currrentArticulation.setTranslation(maya.OpenMaya.MVector(0,  HipsPos.y + increment * SpineOffset, (increment ** 2) * SenescenceOffset))

            if self.ArticulationList:  # Not the first joint, we have to connect it to the previous one...
                pymel.core.connectJoint(currrentArticulation,  self.ArticulationList[-1],  parentMode=True)  # Remember,  -1 is the index of the last member of a list.
            else:
                self.Hips = currrentArticulation

            if increment == Avatar.NbSpine - 2:  # The last two bones are Neck and Head, so Arms takes their root from bellow.
                self.NeckBase = currrentArticulation

            self.ArticulationList.append(currrentArticulation)

class Limb(object):
    def __init__(self, _Name):
        self.ArticulationList = []  # Same name as in Avatar member, but 'self.' indicates the ownership of this class instance. We're deadling with two very different variables so.
        self.Name = _Name
        self.Effector = None  # Every Limb MUST have an effector. But right now it is purely virtual. The member will be set when Ik will be constructed.

    @staticmethod
    def getGenderOffset(_Limb, _Sex):  # Notive there is no 'self' first argument.
        """ Static Method that return offset of Base position for given Limb """
        if _Limb is Arm:  # Notice also the first expected argument here is the class itself, not one of their instances: That's where is operator fit better than ==
            if _Sex == eSexe.kFemale:
                return 0.25
            elif _Sex == eSexe.kMale:  # Male have larger shoulder...
                return 0.3
        elif _Limb is Leg:
            if _Sex == eSexe.kFemale:  # Male have larger hips...
                return 0.3
            elif _Sex == eSexe.kMale:
                return 0.25
        else:
            raise  # Unknown Limb ! (Not the purpose here but why not raise a custom Exception... as a new class of course)

class Arm(Limb):
    NbArticulation = 3  # We iterate through a max joint define in the class member, not the instance member. It is call a static member. Useful for storing scoped-constants and convert factor.

    def __init__(self, _Name, _NeckBase,  _Offset, _Length):
        super(Arm, self).__init__("Arm_" + _Name)  # Inheritance in python is not automatic. Super (Mother) class of Arm is Limb, and its methods are callable form Arm instance, but it needs the keywords super to do that.
        LastPosition = _NeckBase.getTranslation(space='world')
        _Offset.normalize()

        for i in range(Arm.NbArticulation):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))
            NewPosition = LastPosition + _Offset * (_Length / float(Arm.NbArticulation))
            currentArticulation.setTranslation(NewPosition)
            if not self.ArticulationList:  # The first arm joint must be connected to the Base.
                pymel.core.connectJoint(currentArticulation,  _NeckBase,  parentMode=True)
            else:
                pymel.core.connectJoint(currentArticulation,  self.ArticulationList[-1],  parentMode=True)
            self.ArticulationList.append(currentArticulation)
            LastPosition = NewPosition

    def createIK(self):
        pymel.core.general.select(clear=True)
        result = pymel.core.ikHandle(name=self.Name + "_IKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSCsolver)
        self.Effector = result[0]  # Filling Limb inherited member with the newly created effector.

class Leg(Limb):
    NbArticulation = 3

    def __init__(self, _Name, _HipsBase,  _Offset):
        super(Leg, self).__init__("Leg_" + _Name)
        BasePosition = _HipsBase.getTranslation(space='world')
        previouslyCreated = _HipsBase
        for i in range(0, Leg.NbArticulation):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))

            if(previouslyCreated == _HipsBase):  # First joint
                currentArticulation.setTranslation(BasePosition + _Offset, space='world')
            else:
                InverseLerp = (1 - (i / float(Arm.NbArticulation - 1)))   # Lerp is the transpose of a range FROM [min = >max] TO [0 = >1]   # Arm.NbArticulation-1 because we do not care about the first Articulation
                currentArticulation.setTranslation(maya.OpenMaya.MVector(previouslyCreated.getTranslation(space='world').x, previouslyCreated.getTranslation(space='world').y * InverseLerp, previouslyCreated.getTranslation(space='world').z), space='world')

            pymel.core.connectJoint(currentArticulation,  previouslyCreated,  parentMode=True)
            previouslyCreated = currentArticulation
            self.ArticulationList.append(currentArticulation)
        self.createIK()  # Instead of Arm, here we decided that it is into the __init__ process to create IK, so we call the methods from here.

    def createIK(self):
        pymel.core.general.select(clear=True)
        result = pymel.core.ikHandle(name=self.Name + "_IKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSCsolver)  # IkRPsolver TOASK
        self.Effector = result[0]
        return result[0]

class Tail(Limb):
    def __init__(self,  _HipsBase,  _NbSubdiv, _Size=1):
        super(Tail, self).__init__("Tail")
        BasePosition = _HipsBase.getTranslation(space='world')
        for i in range(1, _NbSubdiv):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name="Tail_" + str(i + 1).zfill(2))
            offSet = i / float(_NbSubdiv)
            currentArticulation.setTranslation(maya.OpenMaya.MVector(BasePosition.x, BasePosition.y, BasePosition.z - (offSet * _Size)),  space='world')
            if not self.ArticulationList:  # The first arm joint must be connected to the Base.
                pymel.core.connectJoint(currentArticulation, _HipsBase,  parentMode=True)
            else:
                pymel.core.connectJoint(currentArticulation, self.ArticulationList[-1],  parentMode=True)
            self.ArticulationList.append(currentArticulation)
        self.createSplineIK()

    def createSplineIK(self):
        pymel.core.ikHandle(name="TailIKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSplineSolver)

JohnDoe = Avatar("John Doe", _Sex=eSexe.kMale)
TentacleMonster = Avatar("Weirdo", _Size=2.5, _NbArm=5, _NbLeg=3)
GrandMaMouse = Avatar("Grand'Ma Mouse", _Size=1.2, _Age=86, _hasTail=True)

[Original post] APyTA_17 : OBJECT Part III = Static tactics