Archive for the 'Problem Solving' Category

Mon, Jan 23rd, 2017
posted by jjburton 09:01 PM

Problem: Storing a shape (not a transform) as a messaged connection.

Quick addendum to last week’s post. Ran into this on some rayCasting stuff (needed specific shape connection for updatable loc via stored uv data in dicts – more on that soon). My own googling and maya fiddling came up short with successfully connecting a shapes.message plug to a message attribute. It always connects to the transform. So, I pulled out my old method of attribute storing and it seems to be working okay.

If I wanted to store shape3 to messageHolder on an attribute of testMessage. It would look like this.

The connection works like this: shape3.viewName —-> messageHolder.testMessage.

The logic plays out as follows in our needed functions:

  • Set message
    • When it checks for attribute, component stuff from previous post, it now also checks for shape
    • Instead of a message attribute, I use my copy_to function to copy an unassuming attribute found on shapes (‘viewName’) which then creates a new attribute on my messageHolder matching type and all so it’s connectable. That call also wires that connection
    • Extra data is then stored as before
  • Get message
    • It checks a passed message attribute name for type. If it’s a string, it checks the plug flag on list connections for our above wiring
    • Otherwise, it goes as before

Still testing but seems to be working for our purposes. We’ll see how it plays out.

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, Sep 2nd, 2016
posted by jjburton 09:09 PM

So I got a message from a user on Thursday saying that cgmToolbox didn’t work in Maya 2017. Got around to installing 2017 and yup – borked. Spent the evening on Thursday identifying this issue and Friday was fix day.

If you don’t care about what was wrong and just want the bottom line — cgmToolbox should be working in 2017 Maya with the new build I’ll be pushing to the repository shortly.

If you do care…

NOTE – If you use use zooToolbox and specifically zooPy.path.Path (or zoo.Path as I’ll call it), this post would behoove you to look at unless you like stumbling down the same rambling trail others have tread.

Been using zoo stuff for well over 5 years now and Hamish(creator of zooTools) is out of the game last I knew so I decided I had best fix the problem as googling the topic got jack squat and my usual sounding boards hadn’t come across it yet.

Initially I thought Autodesk had gone and changed something and blew up my stuff but at the end of the day it turned out to be the fact that the python that 2017 is running updated the python str class. It just so happens that zoo.Path runs that as a subclass and was overloading some built in calls (find and replace specifically). Anyway, there is a walk generator for path stuff that pushing an instance of the zoo.Path into it rather than a ‘native’ string. Part of that (new to 2017) walker calls on ‘replace’ and so breaks because it needs to replace the path separator which zoo.Path specifically avoids in it’s overload.  zoo.Path’s replace is ONLY for replacing tokens between the separators.

Long story short, that raises an error of ‘/’ cannot be indexed because the find call (in zoo.Path) is specifically removing in it’s searching.

Interesting tidbits:

  • With 2017, os.path.sep is now ‘\\’ up till 2017, it’s been ‘\’ at least all the way back to Maya 2011. On windows at least
  • Something changed with the os.walk generator to make it not work as it did before 2017. Maybe it used to str(arg) stuff in the process and now just passes through the string. Whatever the reason, it broke.
  • import sys || sys.version — gives you your python version. If you’re curious 2017’s is 2.7.11

Some code changes

  • zooPy.path.Path — If you have old versions of zoo installed and trying to run stuff in 2017. It’s gonna break on you if it hasn’t already. You can use this or do your own patch:)
    • osPath — call to return a os.path.sep joined version of the path. Path natively works with ‘/’ and the new double ‘//’ messes with stuff
    • _list_filesystem_items — changed the walk creator to use a osPath string to stop the failing
  • Cleaned out a bunch of stuff from __init__ files. — I’d had some built in calls for listing files and getting other info back before I knew the right way to do it or at least a better one.
  • cgmToolbox
    • clean_scriptPaths/clean_pluginPaths — The call that was breaking stuff were my path setup stuff. As such, the env for these guys got a little borked during the troubleshooting. This was a quick attempt at fixing stuff. As an experiment, this may or may not be reworked.
      • Check all paths for valid paths (will add to the env without failing)
      • Removed a bunch of .git stuff that some other scripts I’d used from someone else apparently added.
      • Acts as a report for what’s there if you didn’t know as it reports all good ones
  • core.cgmPy.os_Utils
    • get_lsFromPath — reworked from the __init__ cleanup. Accepts file paths now and just gets the director they’re in for searching

Now I can get back to cgmBlendshape for Morphy 2. Wrote some fun mesh math stuff toward that end earlier in the week as well but that’s a post for another day…:)


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.


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!