Home Website Youtube GitHub

I have a question about Lips Rigger jitter, very urgent

When I created a lip rig for a character with less than one unit, I found that when moving a few small green controllers such as lips_L_upInner_ctl, lips_L_upOuter_ctl, the model would shake.
Then I find the previously bound characters, scale them down to less than one unit, and they do the same thing. I don’t know how to fix this, Any help would be appreciated.

Hello @shawn ,
Can provide a sample data and/or video showing the issue?

thanks

HI, @Miquel ,I recorded a youtube video

Thank you for your reply, looking forward to your feedback

@Miquel ,Here is the supplementary video

I see the issue is very clear. But Hard to tell without having the scene? Can you send me just the rig without the geometry?
btw: really cool model!

The file has been emailed to you, thank you for your help

I didn’t get any email. where did you send it? can you provide just a download link?

I can’t see your videos for some reason but:

  • Find all wire deformers in your scene. Turn the falloff high to something like 100. Search the forum for “wireDeformer” for a script to do this if you need help.
  • Find all parent constraints on the lip nodes that have more then one target. Set the interpType to “shortest” or “no flip”.

If you try those two things, my guess is that it will help.

2 Likes

Email sending failed, I used gumroad for the first time, I don’t know if it was successful or not.
this is the url:
mgear rigging (gumroad.com)

Hi @chrislesage ,Thanks for the feedback, I will try it right away

I’ve tried two ways and it doesn’t seem to work.
I have uploaded the file, you can download it

please use a service like https://wetransfer.com/ or similar and post the link to download it. Thanks

2 Likes

Already sent the file using wetransfer

I am at a computer now. I can now see your videos.

What is the unit of measurement your Maya is set to? Are you using centimetres?

And what is the scale of your character? For example, in centimeters, how tall is your character? Is it very very tiny? In your original post you said “with less than one unit”.

If it is that tiny, perhaps you might be hitting some limits of Maya’s accuracy. You might have to consider scaling your entire world/production up. There might be a workaround, but I’m not sure.

Yes,I am using centimeters, this character is less than one centimeter

Took a peek at this scene and I also assume it is a scale issue. The output of motionPath nodes seems to be what causes the jumping. The jitter in the video is at a scale of about .001 units. You can also get jitter if you tweak the uparam by a little bit, movement along the curves is not even.

2 Likes
  1. Maybe a hacky workaround could be leaving the rig bigger (say 10 or 100x bigger) and then scaling your final geometry down to size at the end of your history stack. Bake it out as Alembic, or just scale it down with a cluster or something, and render the scaled/baked geo.

  2. You could also change the motionPath to a nurbs ribbon. I talked about that here where I use the curves to loft into a nurbs: Can mGear create a vanilla Maya Rig that doesn't need mGear? - #6 by Intuition but here is a revised script that should run on its own:

You might find a ribbon is more stable at tiny scales. But I haven’t tested that.

import pymel.core as pm


def skin_geometry(oJoints, oGeo, pName):
    """A simple skinCluster command with my preferred prefs."""
    return pm.skinCluster(
            oJoints,
            oGeo,
            bindMethod=0, # closest distance
            dropoffRate=1.0,
            maximumInfluences=1,
            normalizeWeights=1, # interactive
            obeyMaxInfluences=False,
            skinMethod=0, # classic linear
            removeUnusedInfluence=0,
            weightDistribution=1, # neighbors
            name=pName,
        )


def pin_to_surface(oNurbs, sourceObj=None, uPos=0.5, vPos=0.5):
    """
    This function replaces what I used to use follicles for.
    It pins an object to a surface's UV coordinates.
    In rare circumstances follicles can flip and jitter. This seems to solve that.
    
    1. oNurbs is the surface you want to pin to.
    Pass a PyNode transform, NurbsSurface or valid string name.
    2. sourceObj is an optional reference transform. If specified the UV coordinates
    will be placed as close as possible. Otherwise, specify U and V coordinates.
    Pass a PyNode transform, shape node or valid string name.
    3. uPos and vPos can be specified, and default to 0.5
    """
    
    #TODO: Can I support polygons?
    # Parse whether it is a nurbsSurface shape or transform
    if type(oNurbs) == str and pm.objExists(oNurbs):
        oNurbs = pm.PyNode(oNurbs)
    if type(oNurbs) == pm.nodetypes.Transform:
        pass
    elif type(oNurbs) == pm.nodetypes.NurbsSurface:
        oNurbs = oNurbs.getTransform()
    elif type(oNurbs) == list:
        pm.warning('Specify a NurbsSurface, not a list.')
        return False
    else:
        pm.warning('Invalid surface object specified.')
        return False
    
    pointOnSurface = pm.createNode('pointOnSurfaceInfo')
    oNurbs.getShape().worldSpace.connect(pointOnSurface.inputSurface)
    # follicles remap from 0-1, but closestPointOnSurface must take minMaxRangeV into account
    paramLengthU = oNurbs.getShape().minMaxRangeU.get()
    paramLengthV = oNurbs.getShape().minMaxRangeV.get()

    if sourceObj:
        # Place the follicle at the position of the sourceObj
        # Otherwise use the UV coordinates passed in the function
        if isinstance(sourceObj, str) and pm.objExists(sourceObj):
            sourceObj = pm.PyNode(sourceObj)
        if isinstance(sourceObj, pm.nodetypes.Transform):
            pass
        elif isinstance(sourceObj, pm.nodetypes.Shape):
            sourceObj = sourceObj.getTransform()
        elif type(sourceObj) == list:
            pm.warning('sourceObj should be a transform, not a list.')
            return False
        else:
            pm.warning('Invalid sourceObj specified.')
            return False        
        oNode = pm.createNode('closestPointOnSurface', n='ZZZTEMP')
        oNurbs.worldSpace.connect(oNode.inputSurface, force=True)
        oNode.inPosition.set(sourceObj.getTranslation(space='world'))
        uPos = oNode.parameterU.get()
        vPos = oNode.parameterV.get()
        pm.delete(oNode)

    pName = '{}_foll#'.format(oNurbs.name())
    result = pm.spaceLocator(n=pName).getShape()
    result.addAttr('parameterU', at='double', keyable=True, dv=uPos)
    result.addAttr('parameterV', at='double', keyable=True, dv=vPos)
    # set min and max ranges for the follicle along the UV limits.
    result.parameterU.setMin(paramLengthU[0])
    result.parameterV.setMin(paramLengthV[0])
    result.parameterU.setMax(paramLengthU[1])
    result.parameterV.setMax(paramLengthV[1])
    result.parameterU.connect(pointOnSurface.parameterU)
    result.parameterV.connect(pointOnSurface.parameterV)
    
    # Compose a 4x4 matrix
    mtx = pm.createNode('fourByFourMatrix')
    outMatrix = pm.createNode('decomposeMatrix')
    mtx.output.connect(outMatrix.inputMatrix)
    outMatrix.outputTranslate.connect(result.getTransform().translate)
    outMatrix.outputRotate.connect(result.getTransform().rotate)

    '''
    Thanks to kiaran at https://forums.cgsociety.org/t/rotations-by-surface-normal/1228039/4
    # Normalize these vectors
    [tanu.x, tanu.y, tanu.z, 0]
    [norm.x, norm.y, norm.z, 0]
    [tanv.x, tanv.y, tanv.z, 0]
    # World space position
    [pos.x, pos.y, pos.z, 1]
    '''

    pointOnSurface.normalizedTangentUX.connect(mtx.in00)
    pointOnSurface.normalizedTangentUY.connect(mtx.in01)
    pointOnSurface.normalizedTangentUZ.connect(mtx.in02)
    mtx.in03.set(0)

    pointOnSurface.normalizedNormalX.connect(mtx.in10)
    pointOnSurface.normalizedNormalY.connect(mtx.in11)
    pointOnSurface.normalizedNormalZ.connect(mtx.in12)
    mtx.in13.set(0)

    pointOnSurface.normalizedTangentVX.connect(mtx.in20)
    pointOnSurface.normalizedTangentVY.connect(mtx.in21)
    pointOnSurface.normalizedTangentVZ.connect(mtx.in22)
    mtx.in23.set(0)

    pointOnSurface.positionX.connect(mtx.in30)
    pointOnSurface.positionY.connect(mtx.in31)
    pointOnSurface.positionZ.connect(mtx.in32)
    mtx.in33.set(1)
    
    return result


def turn_lips_to_ribbon():
    ''' Turn the lips motion paths into ribbons. '''

    # Create some intial groups
    org = pm.group(em=True, n='lips_ribbon_org')
    face_org = pm.PyNode('face_org')
    joint_org = pm.group(em=True, n='lips_control_joints_org')
    pm.parent(org, face_org)
    pm.parent(joint_org, org)
    org.visibility.set(0)

    upLipCurve = pm.PyNode('lips_C_upCtl_crv')
    lowLipCurve = pm.PyNode('lips_C_lowCtl_crv')

    # Create loft curves
    up1 = pm.duplicate(upLipCurve, n='lips_C_up_crvA')[0]
    up2 = pm.duplicate(upLipCurve, n='lips_C_up_crvA')[0]
    low1 = pm.duplicate(lowLipCurve, n='lips_C_up_crvA')[0]
    low2 = pm.duplicate(lowLipCurve, n='lips_C_up_crvA')[0]
    up2.tz.set(-2)
    low2.tz.set(-2)
    pm.parent(up1, up2, low1, low2, None)

    # Loft them into ribbons
    upLoft = pm.loft(up1, up2, ch=False, range=True, n='lips_C_up_ribbon')[0]
    lowLoft = pm.loft(low1, low2, ch=False, range=True, n='lips_C_low_ribbon')[0]

    pm.delete(up1, up2, low1, low2)
    pm.parent(upLoft, lowLoft, org)

    # Get the lipRopes for all the remaining joints.
    lipRopes = []
    for each in pm.ls('lips_C0*_*LipRope_jnt', type='joint'):
        if 'mgear_matrixConstraint' in str(each.inputs()):
            ropeNode = list(set(each.inputs(type='mgear_matrixConstraint')))[0].inputs()[0]
        else:
            ropeNode = each.inputs(type='decomposeMatrix')[0].inputs()[0].inputs()[0]
        lipRopes.append(ropeNode)
    upRopes = [rope for rope in lipRopes if 'up' in rope.name()]
    lowRopes = [rope for rope in lipRopes if 'low' in rope.name()]

    # Delete any existing constraints on the ropes
    badConstraints = pm.PyNode('lips_C_rope').getChildren(ad=True, type='constraint')
    pm.delete(badConstraints)

    # Plot a matrix follicle at the cloestPoint for every lipRope transform.
    # Disconnect and constrain or parent the ropes under the follicles.
    def plot_folls(org, ribbon, coll, scaleMeasure):
        oRoot = pm.group(em=True, n=ribbon.name() + '_folls')
        pm.parent(oRoot, org)

        for eachJoint in coll:
            oFoll = pin_to_surface(ribbon, eachJoint, 5, 5)
            oLoc = pm.group(em=True, n=oFoll.getTransform().name() + '_ROOT#')
            oLoc.setTranslation(oFoll.getTransform().getTranslation(space='world'), space='world')
            pm.parent(oLoc, oFoll.getTransform())
            pm.parent(oFoll.getTransform(), oRoot)
            oLoc.r.set([0,0,0])
            for eachAttr in ['t', 'r', 's', 'tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz']:
                eachJoint.attr(eachAttr).disconnect()
            #pm.parentConstraint(oFoll.getTransform(), eachJoint, mo=True)
            #pm.scaleConstraint(oFoll.getTransform(), eachJoint, mo=True)
            pm.parent(eachJoint, oLoc)
            scaleMeasure.sx.connect(oFoll.getTransform().sx)
            scaleMeasure.sy.connect(oFoll.getTransform().sy)
            scaleMeasure.sz.connect(oFoll.getTransform().sz)

    # rig parent, ribbon, list of nodes to plot closest point to.
    plot_folls(org, upLoft, upRopes, pm.PyNode('head_scale_measure'))
    plot_folls(org, lowLoft, lowRopes, pm.PyNode('head_scale_measure'))

    # Create control joints to skin the ribbons to.
    upperLipControls = pm.ls(['lips_R_corner_ctl', 'lips_R_upOuter_ctl', 'lips_R_upInner_ctl', 'lips_C_upper_ctl', 'lips_L_upInner_ctl', 'lips_L_upOuter_ctl', 'lips_L_corner_ctl'])
    lowerLipControls = pm.ls(['lips_R_corner_ctl', 'lips_R_lowOuter_ctl', 'lips_R_lowInner_ctl', 'lips_C_lower_ctl', 'lips_L_lowInner_ctl', 'lips_L_lowOuter_ctl', 'lips_L_corner_ctl'])

    upJoints = []
    for each in upperLipControls:
        pm.select(None)
        if pm.objExists(each.name().replace('_ctl','_ribbon_ctl_jnt')):
            oJoint = pm.PyNode(each.name().replace('_ctl','_ribbon_ctl_jnt'))
        else:
            oJoint = pm.joint(n=each.name().replace('_ctl','_ribbon_ctl_jnt'))
            pm.parentConstraint(each, oJoint, mo=False)
            pm.scaleConstraint(each, oJoint, mo=True)
        upJoints.append(oJoint)
    lowJoints = []
    for each in lowerLipControls:
        pm.select(None)
        if pm.objExists(each.name().replace('_ctl','_ribbon_ctl_jnt')):
            oJoint = pm.PyNode(each.name().replace('_ctl','_ribbon_ctl_jnt'))
        else:
            oJoint = pm.joint(n=each.name().replace('_ctl','_ribbon_ctl_jnt'))
            pm.parentConstraint(each, oJoint, mo=False)
            pm.scaleConstraint(each, oJoint, mo=True)
        lowJoints.append(oJoint)
    pm.parent(upJoints, lowJoints, joint_org)

    oSkin = skin_geometry(upJoints, upLoft, 'upper_lip_ribbon_skinCluster')
    oSkin = skin_geometry(lowJoints, lowLoft, 'lower_lip_ribbon_skinCluster')

    # Delete all the remaining lip curves, wires and unused lip rope transforms.
    pm.delete(pm.PyNode('lips_C_rope'), pm.PyNode('lips_C_crvs'))

    #TAG: ZZZ Improve Face Rig Project
    # Constrain the 2 end joints to the corner lips.
    # fix_corner_rotation
    lipRopeNodesUp = sorted(pm.ls('lips_C*_upLipRope', type='transform'))
    lipRopeNodesLow = sorted(pm.ls('lips_C*_lowLipRope', type='transform'))

    rightLips = lipRopeNodesUp[0:2] + lipRopeNodesLow[0:2]
    leftLips = lipRopeNodesUp[-2:] + lipRopeNodesLow[-2:]

    midLipNumber = (len(lipRopeNodesUp))/2
    midLipsUp = [lipRopeNodesUp[midLipNumber-1], lipRopeNodesUp[midLipNumber], lipRopeNodesUp[midLipNumber+1]]
    midLipNumber = (len(lipRopeNodesLow))/2
    midLipsLow = [lipRopeNodesLow[midLipNumber-1], lipRopeNodesLow[midLipNumber], lipRopeNodesLow[midLipNumber+1]]

    rightLipControl = pm.PyNode('lips_R_corner_ctl')
    leftLipControl = pm.PyNode('lips_L_corner_ctl')
    topLipControl = pm.PyNode('lips_C_upper_ctl')
    bottomLipControl = pm.PyNode('lips_C_lower_ctl')
    for each in rightLips:
        pm.orientConstraint(rightLipControl, each.getParent(), mo=True)
    for each in leftLips:
        pm.orientConstraint(leftLipControl, each.getParent(), mo=True)
    for each in midLipsUp:
        pm.orientConstraint(topLipControl, each.getParent(), mo=True)
    for each in midLipsLow:
        pm.orientConstraint(bottomLipControl, each.getParent(), mo=True)

    # Constrain the 3rd and 4th joints to the outer lip controls.
    # fix_corner_rotation
    rightLipsUp = lipRopeNodesUp[2:5]
    rightLipsLow = lipRopeNodesLow[2:5]
    leftLipsUp = lipRopeNodesUp[-5:-2]
    leftLipsLow = lipRopeNodesLow[-5:-2]

    rightLipControlUp = pm.PyNode('lips_R_upOuter_ctl')
    leftLipControlUp = pm.PyNode('lips_L_upOuter_ctl')
    rightLipControlLow = pm.PyNode('lips_R_lowOuter_ctl')
    leftLipControlLow = pm.PyNode('lips_L_lowOuter_ctl')
    for each in rightLipsUp:
        pm.orientConstraint(rightLipControlUp, each.getParent(), mo=True)
    for each in leftLipsUp:
        pm.orientConstraint(leftLipControlUp, each.getParent(), mo=True)
    for each in rightLipsLow:
        pm.orientConstraint(rightLipControlLow, each.getParent(), mo=True)
    for each in leftLipsLow:
        pm.orientConstraint(leftLipControlLow, each.getParent(), mo=True)

turn_lips_to_ribbon()
1 Like

For now, I can only magnify it by a factor of ten to avoid this problem. Thank you again.