Archive for April, 2016

Fri, Apr 22nd, 2016
posted by jjburton 12:04 PM



So, finally wrapped up my work for Morpheus 2 in regards to the wrap setups. As you can see from the last two trips on the rabbit trail(Step 1, Step 2), this wasn’t exactly a simple process.

The point of all of this is to be able to bake blendshapes reliably to nonconforming geo while affecting only the regions we want without having to go in and tweak things by hand. This will prove more and more useful as customization option expand. Why bother with this? Wrap deformers are exceedingly slow. Being able to replace them with skinning data and copying blendshapes between meshes will make your animations play faster and feel more interactive. The final solution was to create proximity geo which is localized to the area I want to affect the nonconforming target mesh. The proximesh is wrapped to the base driver and the target is wrapped to the proximesh.

target –[wraps to]—>> proximesh –[wraps to]–>> base

Here’s a general breakdown of the baking function:

  1. Given a source mesh that has the blendshapes and a nonconforming target mesh we want them on…
  2. Go through all blendshape channels on the source mesh and…
    1. Get their connections/values
    2. Break all connections/zero values so we have clean channels to push our specific shapes to our target mesh
  3. Generate a proximesh of the source with the area we want influencing our nonconforming mesh
  4. Generate a duplicate target mesh so we’re not messing with that mesh
  5. Wrap the proximesh to the source mesh
  6. Wrap the duplicate target mesh to the base mesh
  7. Go through all the blendshape channels on the source mesh and…
    1. Turn a channel on
    2. Duplicate our wrapped target mesh to create a new mesh with the blendshape data on it pushed through by the wrap
    3. If we’re going to cull no change shapes – check each generated shape against the target mesh to figure out when are not moving any verts and delete those offenders
  8. Go through all the original blendshape channels again and rewire them as they were before our function
  9. Delete the wraps and temporary geo
  10. If desired, create a new blendshape node with our final list of baked targets on our nonconforming base mesh
  11. If desired wire the new blendshape node to match the original one we baked from so the channels follow one another.

Easy peasy:)

Functions created while working through it:

  •  cgm.lib.deformers
    • proximityWrapObject — This was the solution in the end to getting rid of movement in the mesh in areas I didn’t want affected.
    • influenceWrapObject — See step one above. Dead end but might prove useful in the future
    • bakeBlendShapeNodesToTargetObject — Greatly expanded this during this little journey
      • Added wrapMethod — influence wrap and proximity wrap and associated flags
      • Added cullNoChangeGeo — removes baked targets that don’t move the base mesh within the given tolerance
  •  cgm.core.lib.geo_Utils
    • is_equivalent — Function comparing points of to pieces of geometry to see if their components match in object space. Useful for culling out empty blendshape targets that have been baked. Supports tolerance in checking as well
    • get_proximityGeo — In depth function for returning geo within range of a source/target object setup. Search by boundingbox and raycasting to find geo within the source. Can return objects,faces,edges,verts or proximity geo which is new geo from the targets that corresponds to the search return

Lessons Learned for wraps in general

  • The maya command call to create a node is mc.CreateWrap (in 2011 at least). I hope later versions made it easier as
  • The object you wrap your target two gets two attributes (dropoff and smoothness) that dicatate how the wrap on your target is affected. No idea why it took me this long in maya to notice that in the docs.
  • Simply using Maya wrapDeformer to wrap an object to another when the object to be wrapped doesn’t conform to the target geo is a bad idea. You’ll get movement in your wrap geo where you don’t want it.

Long story short. The wrap problem is resolved for Morpheus 2.0.

For now. 🙂

Wed, Apr 20th, 2016
posted by jjburton 02:04 PM

So the rabbit trail from over the weekend proved to not be the answer to my original problem as hoped. Namely I was still getting geo movement from far off regions when baking blendshapes to non-similar geo (think a sphere placed on a body).

As such, my next plan to resolve this was to create a specific conforming geo piece to wrap to then wrap my nonconforming object to. To do this, I need a way to  find the geo closest to my geo I wanted to transfer the blendshapes too and so wrote a new function for this that would:

  • Search target geo against a source geo piece to find geo from each target within the source by two methods:
    • boundingBox.contains – by vert
    • rayCasting  – by the compass vectors to make sure it is completely within the sourceObject
  • Translate that data to verts,edges, faces
  • Have a method to expand that data:
    • selection traversing
    • softSelection radius

Lessons learned:

  • bounding box checking is much much faster so use that mode unless you just have to have a more precise idea of what verts are inside the mesh.
  • Not a lot of specific info I could find on some of these concepts and so wanted to share to save someone else time and deadends

Here’s part one of this issue which is housed at cgm.core.lib.geo_Utils.get_contained. There are too many dependencies to include them all but you can get this gist from the code.

def get_contained(sourceObj= None, targets = None, mode = 0, returnMode = 0, selectReturn = True,
                  expandBy = None, expandAmount = 0):
    Method for checking targets componeents or entirty are within a source object.

        sourceObj(str): Object to check against
        targets(list): list of objects to check
        mode(int):search by
           0: rayCast interior
           1: bounding box -- THIS IS MUCH FASTER
        returnMode(int):Data to return 
        selectReturn(bool): whether to select the return or not
        expandBy(str): None
                       expandSelection: uses polyTraverse to grow selection
                       softSelect: use softSelection with linear falloff by the expandAmount Distance
        expandAmount(float/int): amount to expand

        list items matching conditions
    :acknowledgements -- idea of raycasting to resolve if in interior of object
    Only works with mesh currently
    __l_returnModes = ['obj/transform','face','edge/span','vert/cv']
    __l_modes = ['raycast interior','bounding box']
    __l_expandBy = [None, 'expandSelection','softSelect']
    result = []
    _mode = cgmValid.valueArg(mode, inRange=[0,1], noneValid=False, calledFrom = 'get_contained')"mode: {0}".format(_mode))
    _returnMode = cgmValid.valueArg(returnMode, inRange=[0,3], noneValid=False, calledFrom = 'get_contained')"returnMode: {0}".format(_returnMode))
    _selectReturn = cgmValid.boolArg(selectReturn, calledFrom='get_contained')
    _expandBy = None
    if expandBy is not None:
        if expandBy in __l_expandBy:
            _expandBy = expandBy
            raise ValueError,"'{0}' expandBy arg not found in : {1}".format(expandBy, __l_expandBy)
    #Get our objects if we don't have them
    if sourceObj is None and targets is None:
        _sel =
        sourceObj = _sel[0]
        targets = _sel[1:]

    targets = cgmValid.listArg(targets)#...Validate our targets as a list
    l_targetCounts = []
    for o in targets:
        _d = cgmValid.MeshDict(o)
    sel = OM.MSelectionList()#..make selection list
    for i,o in enumerate([sourceObj] + targets):
            sel.add(o)#...add objs
        except Exception,err:   
            raise Exception,"{0} fail. {1}".format(o,err)

    _dagPath = OM.MDagPath()#...mesh path holder
    matching = []#...our match holder
    _l_found = OM.MSelectionList() list for found matches
    if _mode is 1:'bounding box mode...')        
        try:#Get our source bb info
            fnMesh_source = OM.MFnMesh(_dagPath)
            matrix_source = OM.MMatrix(_dagPath.inclusiveMatrix())    
            bb_source = fnMesh_source.boundingBox()
            sel.remove(0)#...remove the source
        except Exception,err:
            raise Exception,"Source validation fail | {0}".format(err)
        for i in xrange(sel.length()):
            _tar = targets[i]
            _vtxCount = l_targetCounts[i]
  "Checking '{0}'".format(_tar))
            guiFactory.doUpdateProgressWindow("Checking {0}".format(_tar), i, 
            sel.getDagPath(i, _dagPath)#...get the target 
            fnMesh_target = OM.MFnMesh(_dagPath)#...get the FnMesh for the target
            pArray_target = OM.MPointArray() array  
            fnMesh_target.getPoints(pArray_target)#...get comparing data
            matrix_target = OM.MMatrix(_dagPath.inclusiveMatrix())    
            fnMesh_target = OM.MFnMesh(_dagPath)
            bb_target = fnMesh_source.boundingBox()
            if bb_source.contains( cgmOM.Point(mc.xform(_tar, q=True, ws=True, t=True))) or bb_source.intersects(bb_target):
                if _returnMode is 0:#...object
            iter = OM.MItGeometry(_dagPath)
            while not iter.isDone():
                vert = iter.position(OM.MSpace.kWorld)
                if bb_source.contains(vert):
                    _l_found.add(_dagPath, iter.currentItem())
    elif _mode is 0:'Ray cast Mode...')
        sel.remove(0)#...remove the source        
        for i in xrange(sel.length()):
            _tar = targets[i]
            _vtxCount = l_targetCounts[i]            
  "Checking '{0}'".format(_tar))
            sel.getDagPath(i, _dagPath)#...get the target 
            fnMesh_target = OM.MFnMesh(_dagPath)#...get the FnMesh for the target
            guiFactory.doUpdateProgressWindow("Checking {0}".format(_tar), i, 
            iter = OM.MItGeometry(_dagPath)
            _cnt = 0            
            if _returnMode is 0:#...if the object intersects
                _found = False
                while not iter.isDone():
                    guiFactory.doUpdateProgressWindow("Checking vtx[{0}]".format(_cnt), _cnt, 
                    _cnt +=1
                    vert = iter.position(OM.MSpace.kWorld)
                    _inside = True                           
                    for v in cgmValid.d_stringToVector.itervalues():
                        d_return = cgmRAYS.findMeshIntersection(sourceObj, vert, 
                        if not d_return.get('hit'):#...if we miss once, it's not inside
                            _inside = False
                    if _inside:
                        _found = True
            else:#...vert/edge/face mode...
                while not iter.isDone():
                    guiFactory.doUpdateProgressWindow("Checking vtx[{0}]".format(_cnt), _cnt,  
                    _cnt +=1                    
                    vert = iter.position(OM.MSpace.kWorld)
                    _good = True                           
                    p = cgmOM.Point(vert)
                    for v in cgmValid.d_stringToVector.itervalues():
                        d_return = cgmRAYS.findMeshIntersection(sourceObj, vert, 
                        if not d_return.get('hit'):#...if we miss once, it's not inside
                            _good = False
                    if _good:
                        _l_found.add(_dagPath, iter.currentItem())
    #Post processing =============================================================================
    _l_found.getSelectionStrings(matching)#...push data to strings"Found {0} vers contained...".format(len(matching)))
    #Expand ========================================================================================
    if _expandBy is not None and returnMode > 0:"Expanding result by '{0}'...".format(_expandBy))   
        _sel = or []        
        _expandFactor = int(expandAmount)         
        if _expandBy is 'expandSelection':
            for i in range(_expandFactor):
                mel.eval("PolySelectTraverse 1;")
            matching = = True)
        if _expandBy is 'softSelect':
            mc.softSelect(softSelectEnabled=True,ssc='1,0,0,0,1,2', softSelectFalloff = 1, softSelectDistance = _expandFactor)
            matching = cgmSELECT.get_softSelectionItems()   

    if _returnMode > 0 and _returnMode is not 3 and matching:#...need to convert"Return conversion necessary")
        if _returnMode is 1:#...face
            matching = mc.polyListComponentConversion(matching, fv=True, tf=True, internal = True)
        elif _returnMode is 2:#...edge
            matching = mc.polyListComponentConversion(matching, fv=True, te=True, internal = True )

    if _selectReturn and matching:
    return matching

Up next is setting up a new wrap setup with this data. Will post when that’s done.


Sun, Apr 17th, 2016
posted by jjburton 07:04 PM

On yet another rabbit trail of problem solving on Morpheus 2.0, I came across an issue where wrap deformers weren’t working as needed. Namely transferring blendshapes from one mesh to another when the shape of the target mesh wasn’t like the original. Even geo changes in regions no where near the ‘to bake’ geo were affecting it.

So did some googling and came across a concept I’d not used before – namely using a mesh to deform another with a skinCluster.

Neat, so how do we do it?

  1. Get your target and source mesh ready
  2. Create a joint and skinCluster your target mesh to it
  3. Add the driving mesh to the skinCluster with the useGeometry flag ( sample code for this line below).
    1. polySmoothness flag. This controls the smoothness of the mesh deformation of the target mesh.
    2. A polySmoothness of 0 is the closest to a standard wrap deformer
    3. In my initial testing I found that this flag could only be set consistently on creation. Editing the flag could push the smoothness up but not down (in 2011 at least).
  4. Make sure the useComponents attribute on the skinCluster is set to True. If you don’t see the deformation doing anything this is the likely culprit.
mc.skinCluster(_cluster, e = True, addInfluence = sourceObject, wt = 100.0, useGeometry = 1, polySmoothness = 10)

I wrote a script to set this up but it’s still wip. It’s a function found here: cgm.lib.deformers.influenceWrapObject. Because of the issue noted in step 3.2, I added the polySmoothness as a creation flag.

This method of wrapping is much more localized than wrap deformers when the mesh isn’t close AND provides an easy way to paint weights for the deformation.


Thu, Apr 14th, 2016
posted by jjburton 08:04 AM

This post is to remind us how to do this. J had a computer issue and had to reinstall Windows:)

  1. Follow this tutorial –
  2. Make some coffee, you earned it.

If you get the “server’s host key is not cached…” error when attempting to push from SourceTree:

  1. Command prompt
  2. Go to Program Files (x86)/PuTTY
  3. “plink -agent”
  4. Hit ‘y’