eduardo simioni
Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-263 post type-post status-publish format-standard hentry category-uncategorized">

understanding transformation matrices

by on Aug.28, 2016, under Uncategorized

After some children hiatus, I need to finish this post I’ve drafted a while ago. I’ll do another one when I get my hands on a Maya install, in the meantime copy this into the Maxscript editor and try it out:

pt = point axistripod:true size:1 name:"trMatrix"

spX = sphere wirecolor:red radius:0.05 name:"xVector"
spY = sphere wirecolor:green radius:0.05 name:"yVector"
spZ = sphere wirecolor:blue radius:0.05 name:"zVector"

ctrl = position_script()
ctrl.addNode "pt" pt 
spX.position.controller.script ="[pt.transform[1][1], pt.transform[1][2], pt.transform[1][3]]"

ctrl = position_script()
ctrl.addNode "pt" pt 
spY.position.controller.script ="[pt.transform[2][1], pt.transform[2][2], pt.transform[2][3]]"

ctrl = position_script()
ctrl.addNode "pt" pt 
spZ.position.controller.script ="[pt.transform[3][1], pt.transform[3][2], pt.transform[3][3]]"

This will create a point that we will call trMatrix, this is “the matrix we will be trying to understand”. Together with the point it will create three spheres, one for each axis. With a Script Controller, it will also constraint the position of each sphere to each row of the transformation matrix. So red spX will be constrained to the first row, the “x vector”, of trMatrix.transform, green spY to the second row, the “y vector”, and blue spZ to the third row, the “z vector”.

Now rotate trMatrix (don’t move it just yet, only rotate it), run this afterwards and try to understand what’s happening:

print pt.transform
print "Sphere X position: " + (spX.position as string)
print "Sphere Y position: " + (spX.position as string)
print "Sphere Z position: " + (spX.position as string)

Due to the script controller from before, each position in global space for each sphere is actually a row from trMatrix point’s transform matrix. So in this case, each row of the transform matrix is actually just “an offset from zero”. In other words, it’s simply a vector in 3d space whose origin is 0, 0, 0.

Now if you move trMatrix point the spheres will stay in the same place. They are constrained to each vector in the transform matrix, but those are not affected by the point position, the last row. If you want the spheres to follow the point, you simply need to add each value from the position vector on the script controller expression, with this:

spX.position.controller.script ="[pt.transform[1].x+pt.position.x, pt.transform[1].y+pt.position.y, pt.transform[1].z+pt.position.z]"--set the expression
spY.position.controller.script ="[pt.transform[2].x+pt.position.x, pt.transform[2].y+pt.position.y, pt.transform[2].z+pt.position.z]"--set the expression
spZ.position.controller.script ="[pt.transform[3].x+pt.position.x, pt.transform[3].y+pt.position.y, pt.transform[3].z+pt.position.z]"--set the expression

All animators already know this intuitively but here’s the thing: a local matrix position is simply an offset, a vector offset, from its parent, as if that parent was the origin (0,0,0). The global matrix is the object matrix in relation to the origin itself, but the local one is as if the origin “moved” to its parent. And so is with a hierarchical skeleton up until the root.

Lastly, here’s quickly two ways in Maxscript to get the local matrix:

$.transform * (inverse $.parent.transform)
Leave a Comment more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-325 post type-post status-publish format-standard hentry category-maya category-motionbuilder category-pyqt category-pyside category-python tag-howto tag-maya-2 tag-motionbuilder-2 tag-pyqt tag-pyside tag-python tag-tips">

right click signal in PyQt/PySide

by on Jun.10, 2014, under Maya, MotionBuilder, pyqt, pyside, python

It’s extremely easy, if you know it’s called customContextMenuRequested:

As for example:

btnLeft.customContextMenuRequested.connect( lambda: self.RMB(btnLeft, oModel) )

It also has QPoint as default value returning the pixel where right click/contextMenu was requested:

btnLeft.customContextMenuRequested.connect( self.asd )

def asd(self, pos)
    print pos
Leave a Comment :, , , , , , more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-271 post type-post status-publish format-standard hentry category-gui category-motionbuilder category-pipeline category-python tag-dynamic tag-fbgenericmenu tag-fbgenericmenuitem tag-fbmenumanager tag-howto tag-menu tag-motionbuilder-2 tag-pipeline tag-python tag-tutorial">

creating a dynamic menu system in Motionbuilder

by on Feb.16, 2014, under GUI, MotionBuilder, pipeline, python

There are two main tricky parts in doing this:

1) Organizing your tools
2) The event function

Prior to face this sort of task you need to be aware of the concepts explained on the previous post, python modules and import system and have a very very very good understanding of FBMenuManager.

Organizing your tools

In short, there are two ways of accomplishing this. One is having each menu entry as a simple .py script which is executed with execfile(), the other is to think of each tool as a module/folder. If you want to group stuff into different menus, it helps to think of each menu as a different empty module/folder as well.

So let’s suppose you want a menu structure inside Motionbuilder like this:

  • Scripts
    • Cinematics
      • Mocap Cleaner
      • Mocap Destroyer
    • Gameplay
      • Import Tool
      • Export Tool
    • Generic
      • Animate_Everything
      • Fix_Animation

Cinematics, Gameplay and Generic are the main menus, and they show on the right of “Help”. Mocap Cleaner, Mocap Destroyer, Import Tool, Export Tool are (imaginary) full featured tools, each one in its own folder with multiple files. Animate_Everything and Fix_Animation are simple .py scripts, and they are grouped in one single folder. Like this:

Files organization

The Generic scripts is quite simple to do and it’s covered down below. The way I did for full featured tools though is, obviously, creating one menu entry for each folder inside the Scripts folder. Each of these “menu folders” had a (see below in initialization) with some variables describing their content and would become a main entry besides the “Help” menu. Each folder would then be added to sys.path and create one of these data structures:

class MenuData():
    This stores all necessary information for one new menu to be constructed it`s sub menu items.
    def __init__(self, sMenuName, sFullPath, mMenuModule):
        # menu information
        self.sMenuName   = sMenuName
        self.mMenuModule = mMenuModule
        self.sFullPath   = sFullPath
        # actual FBGenericMenu object for the menu
        self.oMenu       = None
        # the key one, list of tools in tuples, 0 is name, 1 is module or full script path (for generics)
        self.lmTools     = []

So you end up with a list of MenuData’s, one for Cinematic, Gameplay and Generic. Now you need to create the actual FBGenericMenu for each folder. You could simplify this into one single for loop, if you have all tools into the Scripts root you could even do away with the MenuData (which is to create one menu per folder), but I wanted to do something generic and support many different outcomes so I was doing it in two loops described below.

Initializing Tools:

For each MenuData I look for each subfolder (Scripts/Gameplay/Import, Scripts/Gameplay/ExportTool for example) and save each module (present in this subfolder) into MenuData.lmTools. The tricky part is how to do this:

for oMenu in listOfMenuData:
    lDir = os.listdir( oMenu.sFullPath )
    for sPath in lDir:
        sMenuPathWithSubFolder = oMenu.sFullPath + "/" + sPath
        if os.path.isdir( sMenuPathWithSubFolder ) and os.path.isfile( sMenuPathWithSubFolder+"/" ):
            module = __import__( sPath )
            oMenu.lmTools.append( (sPath, module ) )

Line 6 works because each “Menu/Module” path was added to sys.path, so you can easily import each tool without its entire path. The important line though is the last one. It adds a list of tools to MenuData.lmTools with a tuple that contains (“string to the tool module”, “the tool module itself”). And here is why you need to add the menu folder to sys.path. You could still get the tool module with other means, but by having Gameplay in sys.path you can do import ImportTool on other tools and share code between them.

On the tool folder you need a so python can import that as a package. You can (or need) to put variables in there, so the menu system can create the proper menu entry for the tool (to use a custom name, for example). This is an example of what I’m using on /Scripts/Gameplay/Destroyer/

sToolFullName = "Mocap Destroyer"
sStartFileToImport = "destroyerDialog"
sCustomFunction = "mainUI()"

It is important that there’s no code running in this __init__. This file is loaded and ran during Motionbuilder startup, so if there’s something crashing in there the entire menu system won’t load (unless you don’t trust your colleagues and populate it with try/catches).

And here is where python magic happens. Once you loaded the module in MenuData you can access all the contents from that with for example:

print oMenu.lmTools[1].sToolFullName
print oMenu.lmTools[1].sTartFileToImport

And this feature you will need to use on the next two steps.

Create menu entries

You haven’t created anything yet because you need to know WHAT to create. That’s why I was importing each tool before actually creating FBGenericMenuItems. This way the title of the menu item can be set on the tool itself, instead of relying on a separate file altogether.

But when dealing with FBGenericMenu is best to just use code instead of describing it:

oMenuMngr = fb.FBMenuManager()
for oMenu in listOfMenuData:
    # FBGenericMenu
    oMenuMngr.InsertLast( None, oMenu.sMenuName )
    oFBMenu = oMenuMngr.GetMenu( oMenu.sMenuName )
    oFBMenu.OnMenuActivate.Add( EventMenu )
    oMenu.oFBMenu = oFBMenu

    # create menu contents (MenuItems) for tools
    print ('create tool: '+ oMenu.sMenuName)
    for i in range(len(oMenu.lmTools)):
        oTool = oMenu.lmTools[i][1]
        if hasattr( oTool, 'sToolFullName' ):
            sName = oTool.sToolFullName
            sName = oMenu.lmTools[i][0]
        oFBMenu.InsertLast( sName, i ) # you need the "i"ndex for the event function below (although you could use a string instead)

As you can see, a FBGenericMenu is stored in MenuData.oFBMenu. You will need it for the EventMenu function. After that we just iterate through each stored module, creating a menu entry for each one. If the module has a sToolFullName, we use that one for the menu entry, otherwise we use its folder name.


All menu entries call just one function. You don’t want to generate a new function for each menu entry, otherwise you will end up with some heavy meta-python coding. And you shouldn’t need to. EventMenu is called with two variables, control and event. Control is the actual FBGenericMenu that was clicked. Event.Id has the index which you used to add that menu entry, last line of the code snippet above. You also have a Event.Name, which is the actual string being displayed in the interface. With this cards in your pocket you can do this:

def EventMenu( control, event ):
    Runs the script selected from the menu. Each menu entry has a corresponding tool __init__ialized (but not loaded in memory)
    This is stored in MenuData.lmTools[1], and it should have variables/attributes point what should be imported and
    how the tool should be executed. Defaults to main() in /Scripts/MENU/TOOL/
    #print control, event.Id, event.Name

    # find which menu was clicked and get it's MenuData from loMenus
    oMenu = None
    for oTM in loMenus:
        if control == oTM.oFBMenu:
            oMenu = oTM

    sToolName = oMenu.lmTools[event.Id][0]
    oTool     = oMenu.lmTools[event.Id][1]

    # no sStartFileToImport, so we file to import is with same name as folder, as in: /Script/Tool/
    if not hasattr( oTool, 'sStartFileToImport' ):
        sModule = oMenu.sMenuName+"."+sToolName+"."+sToolName
    # with sStartFileToImport, so we have /User/Script/Tool/
        sModule = oMenu.sMenuName+"."+sToolName+'.'+oTool.sStartFileToImport

    #print "Importing:\n{0}".format(sModule)

    # if it doesn't have sCustomFunction we run main()
    if not hasattr( oTool, 'sCustomFunction' ):
        sModuleFunction = sModule + '.main()'
    # otherwise, we run the named function
        if oTool.sCustomFunction.find('(') == -1:
            sModuleFunction = sModule + '.' + oTool.sCustomFunction + '()'
            sModuleFunction = sModule + '.' + oTool.sCustomFunction

    #print "Evaluating:\n{0}".format( sModuleFunction )
    eval( sModuleFunction, sys.modules )

First, we find which main menu (the ones beside Help) the “click” happened, by comparing all MenuData.oFBMenu to control. Once we have the MenuData, we have a list of tools inside, which we can quickly fetch with event.Id, since they were added in order and each have its own unique id. Another option would be to compare strings, trying event.Name in a list of tool names, but since we allow tools to have sToolFullName and comparing string is lower than using an index directly, index is better. This is the shitty FBMenuManager part, if you don’t get how this is working you need more time playing with FBMenuManager, FBGenericMenu and FBGenericMenuItem (and/or I haven’t worded it very clearly).

Once we have the actual tool module (/Scripts/Gameplay/ImporterTool/ we can run the actual tool. I wanted to add some flexibility, so I allowed sStartFileToImport variable in the folder, which points to the actual python file that will be imported. If this variable doesn’t exists, it will try to import a python file with the same name as the folder.

Now though is when python import magic actually gets in the way.

Python “import” command runs the imported script just once. When you do import again, it’s not re-evaluating the imported code. Even if you changed the script locally, you need to do reload module instead, which can cause a series of other problems.

Instead of making self-running scripts, I wanted to also add the flexibility for a function to be called for the tool to start (say you want to run something based on selected objects, for example). That’s why it’s also checking for a sCustomFunction variable. If there’s none, it will use just run (sModule + ‘.main()’).

Lastly, it does eval. Which runs said function.

Generic menu

For the generic menu, instead of loading a module I just save the path for the script in MenuData.lmTools[(“script name”, “full path for script”)] and run them with:

execfile( oMenu.lmTools[event.Id][1], {} )

You will just need to add something in /Scripts/Generic/ (like isGenericMenu=True) so you know that that folder is a generic menu and should run another exec procedure. The empty dictionary {} at the end is to specify a new empty set of locals and globals, otherwise you get scope problems when running scripts without functions.

Leave a Comment :, , , , , , , , , more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-239 post type-post status-publish format-standard hentry category-python category-uncategorized tag-import tag-modules tag-packages tag-python tag-reference tag-tips">

Python, modules and static objects

by on Jan.17, 2014, under python, Uncategorized

There’s this little caveat about python I recently learned that would have helped me understand some of its concepts much more clearly if I was told this earlier on:

A module is a static object.

That means, and please someone correct me if I’m wrong, but maybe with the exception of global variables (which you should avoid anyway), you can treat a module as a static object. So instead of creating a static class for some tool functions you can just group all those functions into a separate .py.

One of the best things in python is how easy and robust is to work with modules, do take your time to get to know how they work. It will help tons to organize your tools and re-use code.

Another thing to remember: is used by the python interpreter to treat directories as packages. And a must read literature on the subject is Python documentation on Modules.

Leave a Comment :, , , , , more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-247 post type-post status-publish format-standard hentry category-3dsmax category-ini category-maxscript category-motionbuilder category-pipeline category-python category-xml tag-3ds-max tag-3dsmax-2 tag-maxscript tag-motionbuilder-2 tag-pipeline tag-python tag-tips">

sharing files between 3dsMax and Motionbuilder

by on Oct.28, 2013, under 3ds Max, ini, maxscript, MotionBuilder, pipeline, python, xml

Here’s a small tip on where to quickly put files (ini or xml for example) that you can easily fetch from both Max:


And Motionbuilder:

import os

Both will return “C:\Users\USER\AppData\Local”. And if you ever wondered what’s the difference between /AppData/Local and AppData/Roaming this might shed some light on it:

Leave a Comment :, , , , , , more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-219 post type-post status-publish format-standard hentry category-gui category-interface category-motionbuilder category-python tag-fbgenericmenu tag-fbgenericmenuitem tag-fbmenumanager tag-gui-2 tag-interface tag-menu tag-motionbuilder-2 tag-pyfbsdk tag-python">

FBMenuManager, FBGenericMenu and FBGenericMenuItem

by on Aug.21, 2013, under GUI, interface, MotionBuilder, python

If this was explained somewhere in Motionbuilder’s help it would have made my day yesterday so much easier. Anyway:

FBMenuManager(): the manager
FBGenericMenu(): a menu with FBGenericMenuItem()s inside
FBGenericMenuItem(): an item inside a menu, where something happens when you click on it

You can think of FBGenericMenu as a branch with leafs inside. The FBGenericMenuItems are leafs. And with FBMenuManager() you can construct, edit and destruct more branches and leafs.

You can create leafs with FBGenericMenu, but that’s all you should use it for (when creating menus).

All main menus are FBGenericMenu instances, the ones that are besides File, Edit, Help, etc.

But here’s the important trick. If you want a submenu inside a menu you need to use FBMenuManager and specify the full path (FBGenericMenu/FBGenericMenuItem) for the new subMenu. It will then create a FBGenericMenu for your FBGenericMenuItem. Confusing? Yes.

One practical example. Say you have a menu called MyTool, and you want a submenu Import with items Animation and Character, something like:

  • MyTool
    • Import
      • Animation
      • Character

You could use this code:

oMenuMngr = FBMenuManager()

oMenuMngr.InsertLast( None, "MyTool" )
oMenuMngr.InsertLast( "MyTool", "Import" )

oMenu = oMenuMngr.GetMenu( "MyTool" )
oMenuItem = oMenu.GetFirstItem()
print oMenuItem.Caption
# Import

oMenuMngr.InsertLast( "MyTool/Import", "Animation" )
# now Import is also a FBGenericMenu as well as a FBGenericMenuItem

temp = oMenu.GetFirstItem()
print temp
# Import FBGenericMenuItem

temp = oMenuMngr.GetMenu( "MyTool/Import")
print temp
# Import FBGenericMenu

Apparently, to get a FBGenericMenu object you need the full path. The FBGenericMenuItems you can get only from the parent FBGenericMenu where it belongs.

FBGenericMenu also holds two important things:

1) a list of Ids, one integer for each FBGenereicMenuItem inside, which doesn’t need to be in order
2) OnMenuActivate, which binds all items to only one function that in turn will run each time the user click on any of FBGenericMenuItems inside this FBGenericMenu.

But I will get more to that on another post about creating dynamic menus based on tools you have on disc.

Leave a Comment :, , , , , , , , more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-201 post type-post status-publish format-standard hentry category-python category-visualization tag-api tag-google tag-python tag-spreadsheet tag-tutorial tag-visualization">

google python api, review and sample code

by on Jan.04, 2013, under python, visualization

Looking around the web it seemed fairly simple to work with Google Python API to update a spreadsheet. But alas, it is not. Installation is super straightforward (with Python 2.7.3 at least), just download and run installer. Using it is another world.

Accessing and reading data from a spreadsheet seems to be fully working, but once you start to try other stuff you bump into many problems. I wanted to just create a new spreadsheet (or worksheet) and feed text data into it but had to adapt my way through to get it working. Although it’s in version 3, there are some serious limitations to it. Such as:

  • There’s no support yet to delete spreadsheets.
  • Error messages are quite cryptic, like “500 internal server error”.
  • Client.InsertRow when used only on a brand new worksheet gives a ‘400 bad request’ error, but works if the worksheet has a header row created by hand on the web UI.
  • Deleteworksheet, althought it exists, always gives an ‘Internal server’ error
  • InsertRow and update cell are called on the client, to update the spreadsheet online on each command, so if you are adding 2000 rows it might take a while. Making an instance of a cell or row feed, updating it locally and sending it to the server might work, but I haven’t tried it. I’m not sure there is a way to send a feed actually.
  • There are many authentication methods, and although all except OAuth2 are deprecated, only OAuth1 and ClientLogin works with the old spreadsheet service framework, the only one I was able to use.
  • Official documentation is scarce. Seems like there’s better support for Java and .NET API’s, while Python’s lagging behind.

Mind you, this is just a quick run to try out the API. Things could have been working better if I used OAuth2 instead of ClientLogin, but I didn’t have time right now to meddle more into this stuff. I’m not developing an app, just trying to quickly use spreadsheets with Python, so I’m not even sure if it’s worth going through all the bureocracy of registering a “client secret”. Anyway, without further ado, here’s the code (puns intended):

import gdata.spreadsheet.service

def getSpreadsheetKeys(sheetName="eventIDMap", worksheetName="Sheet1"):
    returns client, spreadsheet key and worksheet key from 
    given names with an empty worksheet
    # connects to the service
    client = gdata.spreadsheet.service.SpreadsheetsService()
    client.ssl = True = ''
    client.password = 'password'

    # goes through the list of spreadsheets available in google drive
    # looking for one named with sheetName
    spreadsheetFeed = client.GetSpreadsheetsFeed()
    sheet = None
    for spreadsheet in spreadsheetFeed.entry:
        if spreadsheet.title.text == sheetName:
            sheet = spreadsheet
    # if not found would create one. if client.CreateResource worked...
    #if sheet == None:
        #sheet ='spreadsheet', title=sheetName)
        #sheet = client.CreateResource(sheet)
    # gets sheet key. it's actually in the url if you open it on a browser.
    sheetKey ='/', 1)[1]

    # gets a list of worksheets present in the spreadsheet
    worksheetFeed = client.GetWorksheetsFeed(sheetKey)
    for worksheetEntry in worksheetFeed.entry:
        if worksheetEntry.title.text == worksheetName:
            #worksheetEntry.title.text = ( "deleteme_" + ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(3)) )
            #client.DeleteWorksheet(worksheetEntry) # this gives a 500 Internal Server Error
            #worksheet = client.AddWorksheet(worksheetName, 4000, 5, sheetKey)
            worksheet = worksheetEntry
            worksheet = client.AddWorksheet(worksheetName, 4000, 5, sheetKey)
    # gets worksheet key, also found in the url
    worksheetKey ='/', 1)[1]
    return client, sheetKey, worksheetKey


    # gather stuff you want to send to the spreadsheet into 
    # a list or something, this case eventTable

    client, sheetKey, worksheetKey = getEventIDSpreadsheetKeys()

    # what happens here is that for each entry in eventTable
    # a dictionary is created. each label in the dictionary 
    # correspond to the names in the first row on the worksheet
    # so A1 is eventid, B1 is eventname, and so on.
    for event in eventTable:
        eventDict = {'eventid':event[0],
        client.InsertRow(rowDict, sheetKey, worksheetKey)

The biggest hurdle with the example above is that the spreadsheet “eventIDMap” must exist in your Google Drive and the first row must have the exact same elements as the dictionary, and they need to be in lower case. There’s no way to add that first row, not with import gdata.spreadsheet.service anyway.

Conclusion is, with Python, if you want to add data to a spreadsheet, it might be easier to output a csv file from whatever source you have and import it into a spreadsheet. On the other hand, reading data from a spreadsheet can be a couple of lines code.

Leave a Comment :, , , , , more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-184 post type-post status-publish format-standard hentry category-3dsmax category-maxscript category-pipeline tag-3ds-max tag-max tag-maxscript tag-rig tag-scale tag-scene tag-trick">

scale all the things!

by on Aug.16, 2012, under 3ds Max, maxscript, pipeline

Here’s a nice trick my mate at Funcom, Endre Eikrem, told me. You can scale everything, scene, mesh, skin, rig, animation, with this simple code:

units.SystemScale = 0.5
units.SystemScale = 1.0
fetchMaxFile useFileUnits:false quiet:true

You just need to have “Respect System Unit in Files” activated, inside Customize/Unit Setup/System Unit Setup. That’s it!

Except! There’s always some exception. So far we found a couple of stuff that are not scaled with this trick:

  • Wire Parameters. They are strings, so it’s expected that Max doesn’t scale them.
  • Key Tangents. If tangents were manually edited they are not going to be scaled. On the other hand, animation keys are scaled correctly.
  • Float Limit Controller. It’s value is not scaled.

There might be other exceptions, but so far these are quite easy to workaround with Maxscript. Specially if your rig is simple. On Funcom rigs, we just needed to parse Orientation_Constraint controllers and edit their wire parameters to the FK/IK handle. Float_Limit controller is just a matter of finding it and setting a new value based on the units.SystemScale used. And key tangents are not to worry if you first bake/plot the animation on the handles. Keyframes being scaled, it works flawlessly.

If anyone trying this out find any more exceptions please let me know.

2 Comments :, , , , , , more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-135 post type-post status-publish format-standard hentry category-ide category-maya tag-autocompletion tag-debug tag-ide-2 tag-linux tag-maya-2 tag-openmaya tag-pymel tag-python tag-wing">

wing ide with maya on linux

by on Jul.21, 2011, under IDE, Maya

As Jason Parks noted, Wing is definitely a great IDE to work with Python. With OpenMaya and pymel auto-completion together with code debugging it’s everything you might want to script on Maya. Eric Pavey has a great tutorial on how to setup everything, I will just try to resume it with some tips on getting it working on Linux. Some considerations:

  • Sending code works as with Eclipse. You open a port on Maya and send it through a socket. Locally or remotely.
  • Autocompletion works and in Maya 2012 is quite easy to setup.
  • Debug works by importing wingdbstub in your script. Maya to Wing communication function basically the same way as sending code, but on a different port.

Each point is independent from one another and you have to setup each one differently.

Sending code from Wing to Maya.

  1. Open port on Maya
  2. Copy Eric Pavey’s scripts to their folders
  3. Setup hotkeys on Wing’s keymap.normal

To open a port on Maya create a shelf button or add to userSetup.mel either:

import maya.cmds as cmds
    if cmds.commandPort(':7720', q=True) !=1:
        cmds.commandPort(n=':7720', eo = False, nr = True)


// userSetup.mel
commandPort -name "" -echoOutput;
commandPort -name ":7720" -echoOutput;

You need then two .py files: – goes into your /.wingide4/scripts in your /home. This saves what you have on Wing on a temp file, opens a socket and communicates with Maya, calling: – which goes under your /home//maya/scripts. It’s called inside Maya by the previous script to run the temp file.

Lastly, you need to bind one or more wrappers into a hotkey in Wing, which calls the functions in You should do this at /usr/lib/wingide4.0/keymap.normal. Just change one key to ‘Ctrl-P’: ‘python_to_maya()’ or ‘Ctrl-P’: ‘all_python_to_maya()’ for example. The scripts provided above are slightly edited versions of Eric Pavey’s version to work on Linux. The only changes are the socket line, the temp file and the send all to python wrappers.


  1. Add pi files to Wing Source Analysis

On Wing, under Edit/Preferences/Source Analysis/Advanced/Interface File Path add the directory: /usr/autodesk/maya/devkit/other/pymel/extras/completion/pi. That’s it. For older versions of Maya you might have to find or download the pi files from somewhere.


  1. Copy from Wing to /maya/scripts
  2. Edit and set kEmbedded=1
  3. Enable Passive Listen and Kill Externally Launched on Wing
  4. When you want to debug, import wingdbstub in your script

To set this up copy /usr/lib/wingide4.0/ to /maya/scripts in your /home. Open it and search for kEmbedded and change from =0 to =1.

On Wing, under Edit/Preferences/Debugger/External/Remote activate ‘Enable Passive Listen’ and ‘Kill Externally Launched’. With this you are good to go, but when you want to debug something you need to add this to the beginning of the code you want to debug:

import wingdbstub

This procedure is also on Maya’s help.

On Windows the folders are different, but these procedures are exactly the same. Both and should also work on Windows, the location of the temp file and the socket line depends on the system you are running everything (left two different if’s there just for clarity.)

Leave a Comment :, , , , , , , , more...

Warning: Use of undefined constant topPost - assumed 'topPost' (this will throw an Error in a future version of PHP) in /home/public/wp-content/themes/pixel/index.php on line 11
class="topPost post-101 post type-post status-publish format-standard hentry category-3dsmax category-pipeline category-xml tag-3dsmax-2 tag-hierarchy tag-maxscript tag-recursion tag-tips tag-tree tag-tutorial tag-xml">

xml with dotnet on maxscript

by on Apr.25, 2011, under 3ds Max, pipeline, xml

Paul Neale has a very good tutorial about dotnet and maxscript, specially reading and writing xml, but I decided to add some more information from a slightly different approach. First, let’s translate an XML to dotnetesque:

    <element1 />
    <element2 attributeName="attribute content">
        <element2.1>innerXML of this element</element2.1>

And the entire document is a XmlDocument (instance of dotNetObject “System.Xml.XmlDocument“)

There are many ways to read and write XML with dotnet. If you need to read the whole document first, probably the most optimized is by using XmlParser, but it can be a bit of a pain. If you are using Maxscript you are not doing it in realtime, so a couple miliseconds won’t make that much of a difference.

Anyway, if you are looking for specific information inside tags you can easily do it using .GetElementsByTagName:

	XmlDoc = dotNetObject "System.Xml.XmlDocument"
	XmlDoc.load "D:\\Characters\\Rider\\exportEMFX.xml"

	-- I know <baseCharacter> has only one entry, so I get the first one
	tagBaseChar = XmlDoc.GetElementsByTagName "baseCharacter"
	baseCharacter = (tagBaseChar.item 0).InnerXML
	-- I don't know how many <file>s there is, so I get them into an array
	tagFiles = XmlDoc.GetElementsByTagName "file"
	fileList = (for i=0 to (tagFiles.count-1) collect (tagFiles.item i).InnerXML)

To read something like:


Note that GetElementsByTagName doesn’t care about hierarchy, it just finds the element you are looking for, no matter where in the tree it is or how many times it’s repeated. If you have a “file” under “filesToLoad” and also under “filesToSave” it won’t differentiate between them.

If you have a tree/graph structure and you don’t know each tag name one solution is to recourse through it all loading them on a dictionary or array.

Say you have an hierarchy structure you want to save, together with their layers, into an XML. You can store the data in two arrays with:

this two are recursive functions to return the hierarchy in a single array
fn GetHierarchyTree lParent =
	toRet = #(
	for o in lParent.children do
		append toRet (GetHierarchyTree o)
fn GetLayerTree lParent =
	toRet = #(
	for o in lParent.children do
		append toRet (GetLayerTree o)

this is just an example, you shouldn't use globals like this
global parentTree = #()
global layerTree = #()

gets the data into parentTree and layerTree
fn getData =
	for o in objects where o.parent == undefined do
		append parentTree (GetHierarchyTree o)
		append layerTree (GetLayerTree o)

To save this into an XML you also go with recursion:

fn recurseTreeToXML baseNode xmlDocNew pTree lTree =
    for i=1 to pTree.count do
		-- if it's a leaf, there's data, so we append
        if (isKindOf pTree[i] String) then
            curNode = xmlDocNew.CreateElement pTree[i]
            curNode.SetAttribute "layer" lTree[i]
            baseNode.appendChild curNode
            baseNode = curNode
        else -- it's a branch with more data underneath
            recurseTreeToXML baseNode xmlDocNew pTree[i] lTree[i]
fn saveDataOnXML =
	-- you cannot append elements to an xmldoc
	-- you have to append elements to a single root element, child of xmldoc
    xmlDocNew = dotNetObject "System.Xml.XmlDocument"
    xmlRoot = xmlDocNew.CreateElement (getFilenameFile maxfilename)
    xmlDocNew.appendChild xmlRoot
    recurseTreeToXML xmlRoot xmlDocNew parentTree layerTree ("C:\\temp\\test.xml")

And also to read an XML:

fn recurseXML docElement =
	tempPTree = #(
	tempLayerTree = #(docElement.getAttribute "layer")
	for i = 0 to (docElement.childNodes.count - 1) do
		tmp = (recurseXML docElement.childNodes.itemOf[i])
		append tempPTree tmp[1]
		append tempLayerTree tmp[2]
	return #(tempPTree, tempLayerTree)

global parentTreeXML = #()
global layerTreeXML = #()
fn readXML =
	xmlDoc = dotNetObject "System.Xml.XmlDocument"
	xmlDoc.load ("C:\\temp\\test.xml") 
	docElement = XmlDoc.documentElement
	-- if we don't do this here we end up with the root node on parentTreeXML
	-- which can be worked out in anoter way also, of course.
	for i = 0 to (docElement.childNodes.count - 1) do
		tmp = (recurseXML docElement.childNodes.itemOf[i])
		append parentTreeXML tmp[1]
		append layerTreeXML tmp[2]

Remember, besides dotnet help website, showproperties and showmethods are your best friends. You can also copy this code and paste on maxscript editor or jedit or somewhere with a syntax highlighter to better read it.

1 Comment :, , , , , , , more...

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!