cosmogonies.net Blog of cosmogonies.net

handbook
18Mar/130

How to handle the unexpected

Thanks to a french blog, I just discovered that python allow you to handle the unexecpected python errors.
It is called "excepthook" and it is a must have in a studio framework !
So I urged myself to put a routine into the userSetup.py of anyone in the studio that starts Maya or MotionBuilder:
 

import sys

def handleCrash(_ErrorType, _Value, _TraceBack):
  """ do your stuff """
  sys.__excepthook__(_ErrorType, _Value, _TraceBack) #as Sam says, be polite.

sys.excepthook = handleCrash
 
With that, you can send you an email with a HTML-formated text, with all the details, but be careful about the possible lag when sending email.
My best guest right now is to insert an sql request into a debug.db file using sqlite3.
With a little additional efforts (or a database reading app), you can track anything happening in the studio.
My advice is to get exact time, the user name, machine name and its opened-file's name to fill the logging.
Here is the final snippet:

import sys

def handleCrash(_ErrorType, _Value, _TraceBack):
  import traceback
  import sys
  import datetime
  import os

  UserName = os.getenv('USERNAME')
  MachineName = os.getenv('COMPUTERNAME')

  FileName = "" # could be, regarding your current environment,
                #maya.cmds.file(query=True,sceneName=True,shortName=False)
                #pyfbsdk.FBApplication().FBXFileName

  HtmlBuffer="<body>"
  HtmlBuffer+="UNEXPECTED:"+str(_ErrorType) +" ==> "+ str(_Value)+"<br>"
  HtmlBuffer+= UserName+" on "+MachineName+"<br>"
  HtmlBuffer+= "on File ="+FileName+"<br>"

  dt = datetime.datetime.now()
  DateNiceName = dt.strftime("%A, %d. %B %Y %I:%M%p")
  HtmlBuffer+= "At "+DateNiceName+"<br>"

  formatted_lines = traceback.format_tb(_TraceBack)
  for cur in  formatted_lines:
    HtmlBuffer+=str(cur)+"<br>"
  HtmlBuffer+="</body>"

  #do your stuff

  sys.__excepthook__(_ErrorType, _Value, _TraceBack)

sys.excepthook = handleCrash

By the way, in Maya you will find that some of your errors will not be caught by that system.
Cyrille Fauvel wrote about that in the awesome "Around the corner" dev blog.
http://around-the-corner.typepad.com/adn/2013/03/my-entry.html
import maya.utils

def handleCrash_Maya(etype, value, tb, detail=2):
handleCrash(etype, value, tb)
  return maya.utils._formatGuiException(etype, value, tb, detail)

maya.utils.formatGuiException = handleCrash_Maya

PS: Coming back from GDC2013, I attended to an awesome conference called "Spend Time Where it Matters: Friction Free Bug Reporting" by Raphael Saint-Pierre (Ubisoft) , talking about auto-BugReporting.
I learned here a very great tips to identify and index a bug into a database without creating some clones of similar ones everytime a new occurence pop. How ? Very easy: give an Id coming from a hash of the call stack ! Tested... and approved !

faq
rss
5Mar/133

How to batch Motionbuilder: FBX SDK to the rescue.

When I suddently discovered that MotionBuilder does not have a batch mode, I fainted, as I consider doing my job is half the time coding batch tools.

As a reminder, the Batch version of a software is the access of this one in command-line; means without interfaces, but more importantly it is also scriptable so you can process infinite auto-tasks management with it.
Maya comes with a mayabatch.exe, and xsi has its xsibatch.exe, but you can just start MotionBuilder.exe with a -console flag, which allow you having an external output, nothing really fancy.
This is a real issue here: How can building a python framework, multi-environment, and keep tools doing full-auto pipeline processes.
Thankful to Autodesk policy, they gave us a solution : FBX SDK.

  • Explanations:

FBX SDK is written in c++, but hopefully Autodesk managed to wrapped it with SWIG to propose a binding python (if the link is dead, try a search with fbx sdk python binding in Area)
The full doc is actually here, but it is the c++ one. Do not worry about it, python equivalent are found easily (remember dir statement).

So, Is this another environement to my framework: FBX ? Not quite.
We are still in standalone python, but we are using a library to PARSE a file format.
As Maya uses .MA as a 3d structure of succession of MEL Commands, and .MB as a binay, FBX provides both ascii and binary, but ascii fbx are very hard to parse.
FBX SDK help you to read, modify and save any file of FBX, but require only python, ANY python installed on your computer.
For example, I use FBX SDK to open, read, and manage a fbx file, running into a mayabatch.exe (and its mayapy python).
You can also used FBX SDK into MotionBuilder UI, but that is REALLY the snake eating its own tail !

  • Several snippets:

Here I share with you several usefull snippets:


def getHandle(_FilePath):
  import PyFBX
  import PyFBX.fbx

  lSdkManager = PyFBX.fbx.KFbxSdkManager.Create()
  ios = PyFBX.fbx.KFbxIOSettings.Create(lSdkManager, PyFBX.fbx.IOSROOT)
  lImporter = PyFBX.fbx.KFbxImporter.Create(lSdkManager, "")
  lImporter.Initialize(_FilePath, -1, lSdkManager.GetIOSettings())
  MobuFileHandle = PyFBX.fbx.KFbxScene.Create(lSdkManager, "myScene")
  lImporter.Import(MobuFileHandle)
  return MobuFileHandle

If you use that to parse a lot of files, be really sure to free the RAM from
.Import here will literally copy the file into the RAM of your computer, and as the reference is still alive, the garbage collector will not free the RAM.
My advice here is to explicitly calling lSdkManager.Destroy() and recreate a fileHandle from scratch every time you switch from a file to another.


def saveAs(_FBXHandle, _FilePath):
  import PyFBX
  import PyFBX.fbx
  import os.path
  lSdkManager = PyFBX.fbx.KFbxSdkManager.Create()
  ios = PyFBX.fbx.KFbxIOSettings.Create(lSdkManager,PyFBX.fbx.IOSROOT)
  lExporter = PyFBX.fbx.KFbxExporter.Create(lSdkManager,"")
  lExporter.Initialize(_FilePath,-1,lSdkManager.GetIOSettings())
  res = lExporter.Export(_FBXHandle)

  if res and os.path.exists(_FilePath):
    print("File was exported succesfully at ="+_FilePath)

Remember how Motionbuilder manage their types of objects in Navigator Models aside, and all objects ordered by types?
Same thing here in FBX, everything is stored by types first, and you have a Method of Scene for everyone:
Finally, notice how most of them are only accessible with an index, GetCharacter, GetControlSetPlug, GetCharacterPose, GetPose , GetMaterial, GetTexture, GetVideo
Here are some snippets for parsing:


def getVideoFiles(_Scene):
  VideoList=[]
  for currentIdx in range(_Scene.GetVideoCount()):
    VideoList.append( _Scene.GetVideo(currentIdx).GetFileName() )
  return VideoList

def getMaterials(_Scene):
  MaterialList=[]
  for currentIdx in range(_Scene.GetMaterialCount()):
    MaterialList.append( _Scene.GetMaterial(currentIdx) )
  return MaterialList

def getTextures(_Scene):
  TextureList=[]
  for currentIdx in range(_Scene.GetTextureCount()):
    TextureList.append( _Scene.GetTexture(currentIdx) )
  return TextureList

def getChildren(_Node):
  ChildrenList=[]
  for currentIdx in range(_Node.GetChildCount()):
    ChildrenList.append( _Node.GetChild(currentIdx) )
  return ChildrenList

So you can easily explore the graph of a top node:


def getAllNodes(_TopNode):
  import PyFBX
  import PyFBX.fbx
  theNodeList=[]

  def recurseExplore(currentNode):
    print currentNode.GetTypeName(), currentNode.GetName()

    for i in range(currentNode.GetChildCount()):
      cur = currentNode.GetChild(i)
      theNodeList.append(cur)
    recurseExplore(cur)

  recurseExplore(_TopNode)
  return theNodeList
fd = getHandle(r"\\...\Your\Path\FileName.fbx")
test = getAllNodes(fd.GetRootNode())

  • Avoiding traps and shortcuts:

Finally, you can of course look for a specific property.
You have in the doc all the methods for getting rotation or translation in Nodes.
Here is some snippets to get a value of a property by name, for example your own custom ones.


#currentNode= fd.GetRootNode()
value = currentNode.FindProperty(_PropertyName).Get()

But be very careful about not mixing both SDK in your mind!
FBX is NOT Motionbuilder, and experienced python coders in Mobu will find some similarities, but it differs a lot.
For example, forget about .Data member of a node to get its value, but use .Get() method.

Precisely, python in Mobu is called "Open Reality SDK" and is not FBX SDK, but the python package name in mobu, pyfbsdk, can be really confusing (FB prefix comes from FilmBox).
Here are some others examples:


if type(currentNode) == PyFBX.fbx.KFbxNode:
  value = currentNode.Translation.Get()
  print type(value), type(value[0]),value[0]
#<class 'fbx.fbxDouble3'> <type 'float'> 0.0
  value = currentNode.GetPreRotation(0)
  print type(value), value, value[0]
#<class 'fbx.KFbxVector4'> fbx.KFbxVector4(0.000000, 0.000000, 0.000000, 1.000000) 0.0

  • The bright and dark sides

You can open the scenes quickly, automatically.
You can safely open/close a lot of files like this.

I encountered some issues while saving cleaned file, so be sure to increment and not overwrite (as usual of course!)
You cannot blast, cannot plot, etc. ; well basically, you are not in mobu, nor using pyfbsdk module, but you can go very far (edit keys for example)

In my present experience with MotionBuilder, I used FBX SDK to:

- Scan all characters assets to parse a specific PreRotation joint value.
- Scan all scenes to list their content, sync it with asset management system.
- Done some stats about the artists worked, draw dependencies graph and find obsolete assets.

service
4Apr/120

APyTA_06: Dealing with objects collection.

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
advertise
   
advertise
suggest