Archive for the 'Tools' Category

Wed, Jan 18th, 2017
posted by jjburton 08:01 PM

The problem: storing a dag node component in a way that makes it easily callable and persistent.

As I’ve been both refactoring/optimizing our core libraries as well as updating locinator I came across this old issue. There are several ways of doing this, some better than others. Just been wrapping up rewrite of our attribute function library. A part of that was rolling out our msgList concept from cgmMeta to being outside meta as well as expanding on that with datList(more on that another day).

Short version

If you don’t care about the details and just wanna see code, grab the last master branch build of our tools and you can find the main functions here:

  • cgm.core.lib.attribute_utils.set_message/get_message
  • Walkthrough example of datList/msgList with new stuff —
  • Note — There may be a lot of script editor activity on the example stuff as I have DEBUG on in the module currently.

Long version

Let’s say we wanna store an object ‘null1’ to call and we’re storing on ‘storageNull’ How might we do that.

  • string attr – example: storageNull.stringAttr = null1
    • This works as long as there is only one object named ‘null1’ and as long as ‘null1’ is never renamed. So in short, it works rather poorly.
  • msgAttr – example storageNull.msgAttr >>connection>> null.msg
    • This works great and was my preferred method up to this point.

The conundrum on locinator was that I had some locator types that were created from a component say ‘geo.vtx[123]’ for example. My solution back in 2010ish when I wrote it was to just use a string for the whole thing and just hope there wasn’t a name conflict.

So, how might we store this in a persistent manner. Having learned a few things since back in twenty ought ten I said self, we can can better than that now.

The new implementation is as follows:

  1. We take our data to be stored and split out our base node from any component or attribute. Namely we split the first ‘.’ out and validate the bits to know what we have
  2. Store the main node as a standard message connection
  3. Store the extra bits to a json dict via Red9’s json string implementation. We also allow for a a specified dataAttr (our extra data attr) and dataKey (for the dict) for specific storage

So in this case our ‘geo.vtx[123]’ is split to the following:

  • storageNull.msgAttr >>connection>> geo.msg
  • sorageNull.dataAttr = {msgAttr/dataKey:vtx[123]}

We do this as a dict and not a simple string attr per stored object because we use lots of these and having two attrs for every stored message seemed overkill. Once I’d worked out the component store, attribute storing was pretty simple. If we wanted to also add ‘geo2.tx’, it would be added as:

  • storageNull.msgAttr2 >>connection>> geo2.msg
  • sorageNull.dataAttr = {msgAttr2/dataKey2:vtx[71], msgAttr/dataKey:tx}

The dataKey comes in particular use with our datList/msgList setup which is our solution to multi message attrs being rubbish for maintaining ordered data.

When the get_message call happens it first gets the msgAttr and then checks the default extra dat attr if none is specified. Whenever data is found it gets appended to the return.

Yes, you can do some of this stuff with objectSets or other avenues and sometimes those work great. This
is simply another way of storing data mainly for our rigging purposes.

Still refining this but happy so far. Thus ends this post.


Fri, Dec 23rd, 2016
posted by jjburton 09:12 PM

First of all, what is ray casting? Ray casting in maya is when one of several api functions is called which when given a vector, start point and shapes to hit – returns points of intersection.

Turns out you can use that information for all kinds of things. For several years now, we’ve been using it to place follicles, cast curves and shapes on other meshes and other functions. A few months ago, I took a quick pass at adding a snap to function to our implementation where a user selects objects to snap, activates the tool and then casts a ray in scene to get a point in space to snap to. It worked but penetrations were rampant and I planned on revisiting it when I had some time.

Recently I found I had small chunks of time and this was one of the things that seemed useful to use one of those chunks for.

The solution we ended up with is as follows:

  1. Objects are selected
  2. The tool is activated
  3. The user left clicks the screen to cast a ray given the options they’ve provided via the marking menu
  4. A locator is generated and continuously updated while the key is held down
  5. When the left click is released, the snap targets:
    1. Cast another ray either along their ‘down’ axis or casting back to the hit point depending or orient mode
    2. The first mesh hit is assumed to be the driven shape of the control or object and provides the offset distance to use
    3. The targets are snapped to a new point in space from the hit point out along the normal of the mesh or nurbs surface of that hit the offset distance detected or provided via the marking menu for fixed amount
    4. The objects are oriented (if required

The core of our functionality for this work on this pass is found:

  • cgm.core.lib.rayCaster — I simplified our call to a more generic rayCaster.cast rather than breaking down multi hit and other modes via separate calls. Also added normal returns from hit points as it was necessary for the offseting
  • cgm.core.classes.DraggerContextFactory.clickMesh — oh so much…
    • Added offsetting
    • Cast plane mode. Can create objects on a function generated cast plane of x,y,z
    • vectorLine — new create type for visualizing vectors and normals
    • data — new create type to just get data
    • object axis args — for orient stuff
    • Duplication — Selected objects are duplicated and snapped with each left click until the tool is dropped.
  • cgm.core.lib.math_utils.get_vector_of_two_points — Self evident.
  • cgm.core.lib.distance_utils.get_pos_by_vec_dist —  Get a point along a ray given a point, ray and distance along that ray

Lessons learned:

  • Not 100% satisfied on current orient mode and I think Bokser may take a stab at that
  • Maybe I was the only one still using it but zoo’s baseMel UI has some serious slowdown in 2016. Normal mc. calls are much much faster. I’m culling out our usage as I can for speedier ui’s.
  • Initially I was using a vector from the hit point to the snap object as the offset vector but it proved to be inconsistent – For example, if you cast to a far side of a mesh with a ‘far’ cast, the offset put it inside the shape that was hit. Ended up finding the normal of the mesh/nurbs shape hit point to be a much better offset vector to use.
  • There are some issues with Maya api 2.0 folks should be aware of if you should want to mess with this stuff yourselves. These were all found to be True in Maya 2016.
    • meshFn.allIntersections — When casting at poly edges, 2.0 fails. 1.0 does not
    • surfaceFn.intersect — Nurbs surface UV returns a different rawUV than 1.0’s. 1.0’s normalizes as expected, 2.0’s does not
    • surfaceFn.normal — Nurbs surface normal return is junk and broken with 2.0. 1.0’s is just fine.

More on all of this, a vid or two and a new tool to play with in a few weeks.

Wed, Sep 21st, 2016
posted by jjburton 11:09 AM

More vids on specific tool pages. See links inline

Sometimes you just gotta ship something.

For a LONG time now, I’ve been struggling to get Morpheus 2 where I wanted it. Having a small window to get something done because of personal stuff, I wanted to get something done. It’s also been way too long since we’ve released a ‘solid’ tool build so wanted to do that here.

I’ll keep this post updated with new builds as they become more stable until the next major release.

  • Build – 09.22.2016
    • Path fixes that may have been causing some folks issues
    • Soft selection evaluation base functions in
    •  math
      • Most math functions now work with soft select evaluation
      • Added Reset to Targets to Base section
      • Added CopyTo to Target Math section
  • Build – 09.21.2016

So, I made a new tool encompassing a chunk of the tech from Morpheus 2’s development into a manner that is more user friendly. An overview of some of the tech added:

  • Versions — Things should be working from Maya 2011 – 2017
    • 2017
      • Worked on resolving a host of issues. From gui hard crashing to zoo.path stuff mentioned in a blog post last month.
  • Help — cgmTools>Help
    • Added Report issue — link to bit bucket report issue form. Please use this to re port issues.
    • Get Builds — link to page to download wip builds
  • cgmMeshTools — cgmTools>rigging>cgm.meshTools.
    •  MeshMath
      • Symmetry evaluation implemented
      • Base to targed functions/selections
      • Lots of math functions for working with mesh targets – normally blendshape work.
    • Ray Casting
      • ClickMesh
        • Added Nurbs Support
        • Added Snap support – Select targets, activate and snap stuff to any geo you have loaded as targets or in the scene. This is something I wanted to do way back when I first started playing with rayCasting and I’m happy to check that box
        • Follicles on nurbs now work
      • Curve Slice — Lathe curves from objects within mesh objects
      • Curve Wrapping — More advanced curve lathing
      • Implemented multi surface casting to most functions
    • Utils
      • Proximity Mesh/Query — Create proximity mesh or selections from one mesh to another
  • Snap Making Menu — cgmTools>Hotkeys>Snap Tools
    • Added the rayCasting snap
  • cgmMeta
    • A lot of the optimization from last month is in the build.
  • Web documentation
    • Check the side bar here to find the new tool sections (meshTools, cgmMMSnap)
  • cgmHotkeyer
    • Back with Maya 2016, zoo’s hotkey setup no longer worked because of Maya changes. We wrote our own and all hotkey setup uses that now.
  • Other stuff – as the last post released build was years ago, there is a HUGE amount of tools and functions implemented.



Wed, Sep 7th, 2016
posted by jjburton 11:09 AM

Been struggling on this one. The problem at hand is one of trying to get transformed blendshape targets baked down from one mesh to another. This path happened to be a dead end but hope it is useful for other purposes.

There are times when it is useful to see the difference in two meshes, or add/subtract the difference between two. In general, mesh math (as we’ll call it).

There are a few new calls:

  • cgm.core.lib.geo_Utils
    • meshMath_values — this call does the math portion of mesh math
    • meshMath
  • modes
    • add : (target + source) * multiplier
    • subtract : (target – source) * multiplier
    • multiply : (target * source) * multiplier
    • average: ((target + source) /2 ) * multiplier
    • difference: delta
    • addDiff: target + (delta * multiplier)
    • subtractDiff: target + (delta * multiplier)
    • blend: pretty much blendshape result if you added as a target using multiplier as weight
    • copyTo: resets to target to the source
  • multiplier — Multiplier value to throw in the mix with the other math
  • space — object,world
  • resultMode
    • new: apply new duplicate of target
    • modify: modify the existing target
    • values: just get the values

I’ll be adding this to a gui with other controls down the road.

Wed, May 4th, 2016
posted by jjburton 09:05 PM

To anyone who’s worked with coding blendshape stuff it can be tedious especially when you bring in inbetweens.  Thankfully, Autodesk is fixing a lot of that with 2016 extension 2 if you missed that update but there are still folks using older versions and it doesn’t resolve everything. We have to deal with them a good bit on Morpheus 2 and so we wrote a metaclass to deal with them.

Initial features of the cgmBlendshape metaclass that you can’t easily do with normal api or mc/cmd calls:

  • Most functions work off of index/weight or shape/selection format
  • Easy alias naming
  • Replacing shapes — change out shapes in place keeping inbetweens and connections intact
  • Extract shapes — extract shapes from index/weight calls and supporting multipliers to the delta difference
  • Shape restoration — replace deleted shapes on the fly. Recreate a shape from delta and base information and plug it back in for further editing
  • Subclass to cgmNode to all those functions carry over as well
  • Tested in 2011 and 2016
  • NOTE – this is  wip metaclass and will undergo lots of changes

Before we get into the the specifics of the metaclass, here’s some general lessons learned on blendshapes working through this.

  • A blendshape target has several bits of important information
    • Index — this is it’s index in the blendshape node. Note – not necessarily sequential.
    • Weight — this is the value at which this shape is ‘on’. Usually it is 1.0. Inbetween shapes are between 0 and 1.0.
    • Shape — this is the shape that drives the blendshape channel
    • Dag — the dag node for the shape
    • Alias — the attribute corresponding to its index in the weight list. Typically it is the name of the dag node.
    • Plug — the actual raw attribute of the shape on the node. ‘BSNODE.w[index]’
    • Weight Index — follows a maya formula of index = wt * 1000 + 5000. So a 1.0 weight is a weight index of 6000.
  • The way maya stores info
    • Blendshape data is stored in these arrays in real time so that if you query the data and your base mesh isn’t zeroed out, the transformation happening is baked into that
    • The caveat to that is that targets that have their base geo deleted are ‘locked’ in to their respective data channels at the point they were when deleted. Their delta information is frozen.
    • BlendshapeNode.inputTarget[0].inputTargetGroup[index].inputTargetItem[weightIndex]
      • inputTarget — this is most often 0.
      • inputTargetGroup — information for a particular shape index
      • inputTargetItem — information for a particular weight index
    • Sub items at that index
      • inputPointsTarget — the is the differential data of the point positions being transformed by a given shape target. It is indexed to the inputComponentsTarget array
      • inputComponentsTarget — these are the compents that are being affected by a given shape
      • inputGeomTarget — this is the geo affecting a particular target shape
  • Replacing blendshapes – you can 1) use a copy geo function if the point count is exact to change the shape to what you want or 2) make a function to do it yourself. There’s not a great way to replace a shape except to rebuild that whole index or the node itself. We made a function to do that
  • Once a blendshape node is created with targets, the individual targets are no longer needed and just take up space. Especially when you have the easy ability to extract shapes.
  • Getting a base for calculating delta information. As the blendshapes are stored as delta off of the base, the best way I could find to get that delta was to turn off all the deformers on the base object, query that and then return on/connect the envelopes. I’m sure there’s more elegant solutions but I was unsuccessful in finding one.
    • Once you have that creating a new mesh from a an existing one is as simple as:
      • Taking base data
      • For components that are affected on a given index/weight: add the delta to base
      • duplicating the base and xform(t=vPos, absolute = True) each of the verts will give you a duplicate shape
  • Aliasing weight attributes – mc.aliasAttr(‘NEWNAME’, ‘BSNODE.w[index]’)

Here’s a dummy file I used for testing:

Here’s some code to play with the first iteration. You’ll need to grab the MorpheusDev branch on bitbucket if you wanna play with it till I push it to the main branch.

Author: Josh Burton

Website :

Help for learning the basis of cgmMeta.cgmBlendshape
from cgm.core import cgm_Meta as cgmMeta
cgm.core._reload()#...this is the core reloader

#>> cgmMeta.cgmBlendshape
import maya.cmds as mc

#You MUST have the demo file to work though this exercise though you could probably glean the gist without it with your own  setup

#>>Starting off =========================================================================
bs1 = cgmMeta.cgmBlendShape('pSphere1_bsNode')#...let's initialize our blendshape
bs1._MFN you'll find the api blendshape deformer call should you be inclined to use it

#>>bsShape Functions =========================================================================
#We're referring to the shapes that drive a blendshape nodeds base object here and the functions relating to them
#Doing this first will make the blendshape wide functions make more sense on the queries and what not.

bs1.bsShape_add('base1_add')#...we're gonna add a new shape to our node. Since no index is specified, it just chooses the next available
bs1.bsShape_add('base1_add', 8)#...let's  specify an index
#...hmm, our add throws an error because that name is taken. let's fix it
bs1.bsShape_add('base1_tween', 0, weight = .5)#...we're gonna add a new inbetween shape by it's geo, index, and weight

#Replace functions...
#...replacing is not something easily done in basic maya calls
bs1.bsShape_replace('base1_replace','base1_target')#...replace with a "from to"" call. 
bs1.bsShape_replace('base1_target','base1_replace')#...and back

#...Note - the inbetween is intact as is the driver connection
bs1.bsShape_replace('base1_replace',0)#...indice calls also work for most calls

#An index for use with working with blendshapes needs to have an index and weight in order to know what you're working with
bs1.bsShape_index('base1_target')#...this will return a list of the indices and weights which this target affects in [[index,weight],...] format
bs1.bsShape_index('base1_add')#...this will return a list of the indices and weights which this target affects in [[index,weight],...] format

bs1.bsShape_getTargetArgs('base1_target')#...this returns data for a target in the format excpected by mc.blendshape for easier use in nested list format

#>>Blendshape node wide functions =========================================================================
bs1.get_targetWeightsDict()#...this is a handy call for just getting the data on a blendshape in {index:{weight:{data}}} format
bs1.get_indices()#...get the indices in use on the blendshape from the api in a list format
bs1.bsShapes_get()#...get our blendshape shapes that drive our blendshape
bs1.get_baseObjects()#...get the base shapes of the blendshape or the object(s) the blendshape is driving
bs1.get_weight_attrs()#...get the attributes on the bsNode which drive our indices
bs1.bsShapes_get()#...get our shapes

#>>Arg validation =========================================================================
bs1.bsShape_validateShapeArg() target specified, error
bs1.bsShape_validateShapeArg(0)#...more than one entry, error
bs1.bsShape_validateShapeArg(0, .5)#...there we go

#Generating geo...
#Sometimes you wanna extract shapes from a blendShape node. Let's try some of that
bs1.bsShape_createGeoFromIndex(0)#...will create the a new piece of geo matching the 1.0 weight at 1.0
bs1.bsShape_createGeoFromIndex(0,.5)#...will get you the inbetween
bs1.bsShape_createGeoFromIndex(3)#...will get you squat because nothing is there
bs1.bsShape_createGeoFromIndex(0, multiplier = 2.0) can also generate factored targets
bs1.bsShape_createGeoFromIndex(0, multiplier = .5)#...

bs1.bsShapes_delete()#...delete all the targets for your blendshape.
#...ah geeze I didn't mean to do that. No worries!
bs1.bsShapes_restore()#...rebuilds the targets and plugs them back in
Mon, Oct 19th, 2015
posted by jjburton 12:10 PM

So for Morpheus 2, the customization asset uses a blendshape head that needed to be connected to a unified body for the setup to work right. To do that I wanted to do wrap on a blendshape going into the unified body geo blendshape setup. However, we only wanted the head verts affected. If you’re not doing a procedural setup, you can simply paint out the other verts on the blendshape’s channel. Looking into a procedural solution required finding a few code chunks.

Googling on this stuff produced few helpful results so hopefully this can help someone else should they find themselves looking into these things.

First chunk we needed was the setAttr command on setting specific blendshape target weights. This one was a hard one to track down for whatever reason but here it is:


The components of this chunk are as follows:

  • unifiedBridge_blendshape — this is the blendshape node
  • inputTarget[0] — this is the index of the shape on the blendshape node we want
  • inputTargetGroup[1] –As best I understand it this is the target information for 1, or when the shape is fully on
  • targetWeights[563] — this is the vertex id of the vertice in question
  • 1.0 — value to set

As I had a head mesh that was gonna deform that same head that had been unified with the body to have a single mesh for easier skinning/shaping use, I needed the verts I wanted to mask on (for the head verts) and off (for the rest). So first all the verts are turned off, then the verts we wanted turned back on are done so with something like this:

for vtx in range(0,mc.polyEvaluate('M1_Head_geo',v=True)):#...for each vertice of the head geo
    pos = distance.returnWorldSpacePosition('M1_Head_geo.vtx[{0}]'.format(vtx))#...grab the position with our use of mc.pointPosition is the cgm.lib.distance module
    val = distance.returnClosestPointOnMeshInfoFromPos(pos,'M1_UnifiedBase_GEO')['closestVertexIndex']#...get the closest vertice id from the unified mesh using our use of maya point on surface stuff. Again in cgm.lib.distance
    mc.setAttr('unifiedBridge_blendShape.inputTarget[0].inputTargetGroup[1].targetWeights[{0}]'.format(val),1.0)#...then our vertice value is plugged in to the set attr chain

This is utilized with some modification during the setup process for a customization asset.

Have a great day!


Sun, Feb 15th, 2015
posted by jjburton 01:02 PM

While doing some optimization on Morpheus 2 and incorporating some of Red9‘s latest stuff I noticed an odd bottleneck in the code.  So I decided to dig in to it.

For those short of time, the short of it:

  • Maya’s duplicate command get’s slower based on scene complexity – regardless of manually calling or doing it through the interface
  • Maya’s duplicate command (and perhaps others) get slower based on how long you’ve been doing stuff in maya

I noticed that a relatively simple step in one of my joint chain functions was oddly slow and delved into it. In a empty or lite scene it was pretty instantaneous but in a regular scene got really slow. Dug in and came to just being duplicate being slow. That was my theory at least , so I wrote series of tests to verify. The first of those being a test that given a number of times to iterate and a number of children joints, the test will 1) create a joint chain of y joints and 2) duplicate that root joint iterating on the provided number. 

The results showed a pretty linear line of increasing speed as the scene added more objects. The more objects, the longer things got. Interesting but not enough to go on.

The second series of tests I wrote my own simple joint duplicate function using mc.joint and matching positioning, rotateOrder etc. I also checked some other items to eliminate those as possible hindrances or see if they affected speeds:

  • Undo – No difference whether it is on or off
  • History – No history on joints
  • Connections — Only connection on any tested joints in inverseScale
  • Flags — Tried all combinations on the duplicate command I could think of to no avail
My rewrite is always the same speed regardless of complexity, mc. duplicate get’s progressively slower as it goes on. Here are some results:
 speedTest_duplicateInPlace >> Iterations: 200 | Breakpoint Step: 24
 speedTest_duplicateInPlace >> Method 1 | Start: 0.015 | End: 0.192 | Difference: 0.177 | Total: 20.455 
 speedTest_duplicateInPlace >> Method 2 | Start: 0.037 | End: 0.036 | Difference: -0.001 | Total: 7.192 
 speedTest_duplicateInPlace >> Compare  |   Dif: 0.022 | Dif: -0.155 |                    Total: -13.264 
Maya: 2011 x64 | OS: Microsoft Windows 7 Business Edition, 64-bit Windows 7 Service Pack 1 (Build 7601)

 speedTest_duplicateInPlace >> Iterations: 200 | Breakpoint Step: 55
 speedTest_duplicateInPlace >> Method 1 | Start: 0.010 | End: 0.120 | Difference: 0.110 | Total: 13.098 
 speedTest_duplicateInPlace >> Method 2 | Start: 0.042 | End: 0.040 | Difference: -0.002 | Total: 8.160 
 speedTest_duplicateInPlace >> Compare  |   Dif: 0.032 | Dif: -0.080 |                    Total: -4.938 
Maya: 2014 x64 | OS: Microsoft Windows 7 Business Edition, 64-bit Windows 7 Service Pack 1 (Build 7601)

Breakpoint is the iteration at which my rewrite is faster than mc.duplicate for that run. Method 1 is mc.duplicate and Method 2 is my own.

How about some code you can play with yourself with a simple locator duplication?

def speedTest_simpleLocator(iterations = 100):
    import maya.cmds as mc
    import maya.mel as mel
    import time

    _loc = mc.spaceLocator()[0]
    l_times = []

    for i in range(iterations):

	t1 = time.clock()		
	mc.duplicate(_loc, po = True, ic = False, un = False)
	t2 = time.clock()

    for i,t in enumerate(l_times):
	print("Step {0} |  {1}".format(i,"%0.3f"%t))

    _str_dif = l_times[-1] - l_times[0] 
    print(" CGM Simple loc duplication -- {0} | Start -- {1} | End -- {2} | [Diff] -- {3} ".format("%0.3f"%(sum(l_times)),"%0.3f"%l_times[0],"%0.3f"%l_times[-1],"%0.3f"%_str_dif))
    print(" Maya: {0} | OS: {1}".format(mel.eval( 'about -%s'%'version'), mel.eval( 'about -%s'%'operatingSystemVersion')))

Here are some results of this standalone for me at least:

 CGM Simple loc duplication -- 88.324 | Start -- 0.007 | End -- 0.334 | [Diff] -- 0.327 
 Maya: 2011 x64 | OS: Microsoft Windows 7 Business Edition, 64-bit Windows 7 Service Pack 1 (Build 7601)

 CGM Simple loc duplication -- 59.259 | Start -- 0.005 | End -- 0.216 | [Diff] -- 0.211 
 Maya: 2014 x64 | OS: Microsoft Windows 7 Business Edition, 64-bit Windows 7 Service Pack 1 (Build 7601)

 CGM Simple loc duplication -- 40.160 | Start -- 0.004 | End -- 0.151 | [Diff] -- 0.147 
 Maya: 2015 | OS: Microsoft Windows 7 Business Edition, 64-bit Windows 7 Service Pack 1 (Build 7601)

Note – the 2015 run was a fresh open of the software and my experience doing this testing would see that getting slower.

If you run the test you’ll see for yourself the slow down. Now, what do we do with this?Working through this I created my own duplicator for curves, joints and locators. For now, I’m only going to use the joint one for Morpheus building until I can do some more testing and maybe get a better handle on it but it’s certainly an oddity.

Odder yet is the longer you do stuff in Maya, duplicate gets slower still. This I tested after noticing that after being away a bit that Windows had rebooted and suddenly duplicate was posting much better results. After a while, that slowed down. So I rebooted myself and yup, it’s faster after a reboot.  I have no idea on this one other than maybe a memory leak or..I dunno, I’m a hack at this stuff and that’s the best I got:)

If you’re interested, let me know what you find – different conclusions?

For now, Bokser told me I have to move on:)

Tue, Sep 17th, 2013
posted by jjburton 10:09 AM

First off, sorry for the big break in posts here. We’ve just been swamped working on Morpheus and haven’t posted much. This sounded like a good topic.

As we’re getting close to finishing up Morpheus 2, I’ve been trying to do some optimization in the code to make the build processes faster and other things. One of the questions I had was if log.debug was causing speed hits and what other affects prints or log.infos were having on our main code speeds.

What does one do when trying to solve something? Well, we make some hypotheses and tests for those hypotheses. Go, go 8th grade science!

So I wrote a test this morning and the results are rather interesting. I wrote a series of timed functions that do a pow calculation on an enumerated range. I gave the tester a single kw arg of setting the maxTest which sets the range end. I’ll post the tester code below.

The results on a 3000 range test are as follows:

  • logger >> Time >> = 46.289 seconds
  • debug >> Time >> = 24.752 seconds
  • if debug >> Time >> = 0.000 seconds
  • print >> Time >> = 150.508 seconds
  • calc >> Time >> = 0.569 seconds

The raw calc is obviously the fastest.  What was curious to me was that debugs take a hit even when not printing. What was most crazy was how slow print statements are and concluded that you should pretty much never ever use them in maya tools.

For the purposes of our own debugging, using an if/debug gate on that info seemed like it was going to make the most sense. So with that in mind I tried a way to find the level of the logger. The only call I could initially find was log.getEffectiveLevel(). That seems to be the most intuitive method to get a if/debug check for free. Need to do more research to figure out if a level of 10 – which seems to be debug’s default integer value is a usable check.

On a whim I pulled all the log.debugs from our core code and the time savings was considerable on our unit tests. Blrgh…yet another item for the ever growing to do pile.

In the end these are my initial conclusions:

  • Never use ‘print’ calls in maya python. Use logger –
  • Only raw log.debugs in when no calculations have to happen to form that report. I.e – they’ve already been done.
  • I need to begin working to pull out as many debugs as possible and/or level-gate those calls for speed’s sake.
import time
import logging
log = logging.getLogger(__name__)

def testDebugCalls(maxTest = 500):
    ifDebug = False
    def logTest():
        _str_funcName = 'logger'
        time_start = time.clock()
        for i,n in enumerate(range(1,maxTest)):
  "log >>> %s"%(pow(n,i)))
        return "%s >> Time >> = %0.3f seconds " % (_str_funcName,(time.clock()-time_start))
    def debugTest():
        _str_funcName = 'debug'
        time_start = time.clock()
        for i,n in enumerate(range(1,maxTest)):
            log.debug("debug >>> %s"%(pow(n,i)))
        return "%s >> Time >> = %0.3f seconds " % (_str_funcName,(time.clock()-time_start))
    def ifDebugTest():
        _str_funcName = 'if debug'
        time_start = time.clock()
        for i,n in enumerate(range(1,maxTest)):
            if log.getEffectiveLevel() == 10:log.debug("debug EFFECTIVELEVEL >>> %s"%(pow(n,i)))
        return "%s >> Time >> = %0.3f seconds " % (_str_funcName,(time.clock()-time_start))
    def printTest():
        _str_funcName = 'print'
        time_start = time.clock()
        for i,n in enumerate(range(1,maxTest)):
            print("print >>> %s"%(pow(n,i)))
        return "%s >> Time >> = %0.3f seconds " % (_str_funcName,(time.clock()-time_start))  
    def calcTest():
        _str_funcName = 'calc'
        time_start = time.clock()
        for i,n in enumerate(range(1,maxTest)):
            x = (pow(n,i))
        return "%s >> Time >> = %0.3f seconds " % (_str_funcName,(time.clock()-time_start))

    time_log = logTest()
    time_debug = debugTest()
    time_ifDebug = ifDebugTest()    
    time_print = printTest()
    time_calc = calcTest()

    for l in [time_log,time_debug,time_ifDebug,time_print,time_calc]:
        print l
Thu, Nov 1st, 2012
posted by jjburton 03:11 PM

We’re trying out the name ProblemKicker. If anyone else had a better one, we’re all ears.

Tue, Oct 16th, 2012
posted by jjburton 02:10 PM

Finally got this little bit of R&D in a functioning tool.

After looking into that bug at the end, it was messing up because this version of the Morphy proxy geo had messed up UV’s. Follicles need decent UV’s to work. Will look at adding a check for decent UV’s.


  • Add target mesh objects with the ‘>>’ button
  • Modes:
    • Surface – places on the surface
    • Bisect – places at every contact of the cast ray on the object(s)
    • MidPoint – finds the midpoint of hit points
  • Drag – whether you want creation as you drag or not. Default is only on mouse release
  • Clamp – how many intersection are allowed, 0 means infinite
  • Create Modes – all but locator and joint will create when you drop the tool or press ‘q’ on most maya default setups
    • locator
    • joint
    • jointChain
    • curve
    • follicle
    • group — only a transform at that spot
  • Start – initializes tool
  • Drop – finalizes it (or ‘q’)

To do in the future:

  • mirror mode
  • skinDeep mode – for placing joints just within the surface for facial work
  • Add a check to make sure a mesh has suitable UV’s