Пример #1
0
def calcOutVector(start, middle, end):
    '''
    Given the lead joint of 3, determine the vector pointing directly away along the xz plane.
    
    ..  todo::
        Gracefully handle if the ik is on the xz plane already.
    '''

    s = dt.Vector(xform(start, q=1, ws=1, t=1))
    m = dt.Vector(xform(middle, q=1, ws=1, t=1))
    e = dt.Vector(xform(end, q=1, ws=1, t=1))

    up = s - e

    if upAxis(q=True, ax=True) == 'y':
        kneeScale = (m.y - e.y) / up.y if up.y else 0.0
    else:
        kneeScale = (m.z - e.z) / up.z if up.z else 0.0

    modifiedUp = kneeScale * up
    newPos = modifiedUp + e

    outFromKnee = m - newPos

    angleBetween = (m - s).angle(e - m)

    log.TooStraight.check(angleBetween)

    outFromKnee.normalize()

    return outFromKnee
Пример #2
0
def distanceBetween(a, b):
    '''
    Returns the world space distance between two objects
    '''
    dist = dt.Vector(xform(a, q=True, ws=True, t=True)) - dt.Vector(
        xform(b, q=True, ws=True, t=True))
    return dist.length()
Пример #3
0
def calcOutVector(start, middle, end):
    '''
    Given the lead joint of 3, determine the vector pointing directly away along the xz plane.
    
    ..  todo::
        Gracefully handle if the ik is on the xz plane already.
    '''

    s = dt.Vector(xform(start, q=1, ws=1, t=1))
    m = dt.Vector(xform(middle, q=1, ws=1, t=1))
    e = dt.Vector(xform(end, q=1, ws=1, t=1))

    up = s - e

    if upAxis(q=True, ax=True) == 'y':
        kneeScale = (m.y - e.y) / up.y if up.y else 0.0
    else:
        kneeScale = (m.z - e.z) / up.z if up.z else 0.0

    modifiedUp = kneeScale * up
    newPos = modifiedUp + e

    outFromKnee = m - newPos
    outFromKnee.normalize()

    # If we are halfway to the x/z plane, lerp between the old formula and a new one
    testUp = dt.Vector(up)
    if testUp.y < 0:
        testUp.y *= -1.0

    angleToVertical = dt.Vector(0, 1, 0).angle(testUp)

    if angleToVertical > _45_DEGREES:
        # Calculate a point perpendicular to the line created by the start and end
        # going through the middle
        theta = up.angle(m - e)

        distToMidpoint = math.cos(theta) * (m - e).length()

        midPoint = distToMidpoint * up.normal() + e

        altOutFromKnee = m - midPoint

        altOutFromKnee.normalize()

        # lerp between the vectors
        percent = (
            angleToVertical - _45_DEGREES
        ) / _45_DEGREES  # 45 to up axis will favor old, on y axis favors new
        outFromKnee = slerp(outFromKnee, altOutFromKnee, percent)

    angleBetween = (m - s).angle(e - m)

    log.TooStraight.check(angleBetween)

    outFromKnee.normalize()

    return outFromKnee
Пример #4
0
def distanceBetween(a, b):
    '''
    Returns the world space distance between two objects
    
    .. todo:: Should this be combined with `measure`?
    '''
    dist = dt.Vector(xform(a, q=True, ws=True, t=True)) - dt.Vector(
        xform(b, q=True, ws=True, t=True))
    return dist.length()
Пример #5
0
    def apply(cls, objects, values, ctrl):
        pos, rot = cls.split(values)
        out = util.calcOutVector(pos['hip'], pos['knee'], pos['ankle'])
        out *= values['length']

        pvPos = values['knee'][0] + out

        util.applyWorldInfo(ctrl, values['matcher'])

        xform(ctrl.subControl['pv'], ws=True, t=pvPos)

        # Aim Y at ball
        matrix = values['ankleMatrix']
        bendNormal = dt.Vector(matrix[4:7]) * -1.0

        ybasis = (pos['ankle'] - pos['ball']).normal()
        xbasis = ybasis.cross(bendNormal)
        zbasis = xbasis.cross(ybasis)

        if objects['ball'].tx.get() < 0:
            ybasis *= -1
            xbasis *= -1

        r = pdil.math.eulerFromMatrix([xbasis, ybasis, zbasis], degrees=True)
        xform(ctrl.subControl['bend'], ws=True, ro=r)
Пример #6
0
 def split(cls, values):
     ''' Turns all the `worldInfo` into separate dictionaries. '''
     pos, rot = {}, {}
     for key in cls.WORLD_INFO:
         pos[key] = dt.Vector(values[key][0])
         rot[key] = values[key][1]
     return pos, rot
Пример #7
0
def angleBetween(a, mid, c):
    # Give 3 points, return the angle and axis between the vectors
    aPos = dt.Vector(xform(a, q=True, ws=True, t=True))
    midPos = dt.Vector(xform(mid, q=True, ws=True, t=True))
    cPos = dt.Vector(xform(c, q=True, ws=True, t=True))

    aLine = midPos - aPos
    bLine = midPos - cPos

    aLine.normalize()
    bLine.normalize()

    axis = aLine.cross(bLine)

    if axis.length() > 0.01:
        return math.degrees(math.acos(aLine.dot(bLine))), axis
    else:
        return 0, axis
Пример #8
0
def findClosest(obj, targets):
    '''
    Given an object or position, finds which of the given targets it is closest to.
    '''
    if isinstance(obj, (PyNode, basestring)):
        pos = xform(obj, q=True, ws=True, t=True)
    else:
        pos = obj
    
    dists = [((dt.Vector(xform(t, q=1, ws=1, t=1)) - pos).length(), t) for t in targets]
    dists.sort()
    return dists[0][1]
Пример #9
0
    def apply(data, values, ikControl):

        #_matchIkToChain( ikControl, ikEndJoint, ikControl.subControl['pv'], ikControl.subControl['socket'], endJnt)
        #_matchIkToChain(ikCtrl, ikJnt, pv, socket, chainEndTarget)

        # Draw a line from the start to end using the lengths to calc the elbow's projected midpoint
        startPos = dt.Vector(values['base'][0])
        midPos = dt.Vector(values['mid'][0])
        endPos = dt.Vector(values['end'][0])

        toEndDir = endPos - startPos
        a = (midPos - startPos).length()
        b = (endPos - midPos).length()
        midPoint = startPos + (toEndDir * (a / (a + b)))

        # The pv direction is from the above projected midpoint to the elbow
        pvDir = midPos - midPoint
        pvDir.normalize()

        armLength = values['armLength']
        newPvPos = midPos + pvDir * armLength

        xform(ikControl.subControl['socket'], ws=True, t=startPos)
        xform(ikControl, ws=True, t=endPos)
        #xform( ikControl.subControl['pv'], ws=True, t=newPvPos )

        # Not sure how to do the math but this works to properly align the ik
        tempAligner = group(em=True)
        tempAligner.t.set(endPos)
        tempAligner.r.set(xform(ikControl, q=True, ws=True, ro=True))
        tempAligner.setParent(data['ikEndJoint'])
        tempAligner.r.lock()
        tempAligner.setParent(data['end'])

        xform(ikControl, ws=True, ro=pdil.dagObj.getRot(tempAligner))
        delete(tempAligner)

        # In case the PV is spaced to the controller, put it back
        xform(ikControl.subControl['pv'], ws=True, t=newPvPos)
Пример #10
0
def trueWorldFloorAngle(obj):
    '''
    Only true for Y up, returns the smallest Y axis worldspace angle needed to
    rotate to be axis aligned.
    
    To rotate the object run `rotate([0, a, 0], r=1, ws=1, fo=True)`
    '''
    m = xform(obj, q=True, ws=True, m=True)

    # The valid axes to check
    rows = (0, 1, 2)
    cols = (2, )

    hirow = rows[0]
    hicol = cols[0]
    highest = m[hirow * 4 + hicol]

    for col in cols:
        for row in rows:
            if abs(m[row * 4 + col]) > abs(highest):
                highest = m[row * 4 + col]
                hirow = row
                hicol = col
    #print 'col: {}    row: {}   h: {}'.format(hicol, hirow, highest)
    # The `col` determines the world axis
    if hicol == 0:
        worldAxis = dt.Vector([1.0, 0, 0])
    elif hicol == 1:
        worldAxis = dt.Vector([0, 1.0, 0])
    elif hicol == 2:
        worldAxis = dt.Vector([0, 0, 1.0])

    # If the highest is negative, flip it; i.e a local axis closely matched -z
    if highest < 0:
        worldAxis *= -1.0

    # The `row` determins the local axis
    if hirow == 0:
        localAxis = dt.Vector(m[0], 0, m[2]).normal()
    elif hirow == 1:
        localAxis = dt.Vector(m[4], 0, m[6]).normal()
    elif hirow == 2:
        localAxis = dt.Vector(m[8], 0, m[10]).normal()

    a = math.degrees(localAxis.angle(worldAxis))

    # If the cross in negative, flip the angle
    if localAxis.cross(worldAxis).y < 0:
        a *= -1

    return a
Пример #11
0
def spineAlign(spineCard, rotation, threshold=6):
    ''' Rotates the joints of the card cumulatively to `rotation`, which is spread out proportionally.
    
    Joints less than `threshold` from the up axis are not considered.
    '''
    up = dt.Vector(0, 1, 0)

    spineEnd = spineCard.joints[
        -1]  # &&& This should validate the end joint is real and not a helper
    childrenOfSpineCards = [
        getRJoint(bpj).getParent() for bpj in spineEnd.proxyChildren
    ]
    preserve = {
        card: pdil.dagObj.getRot(card)
        for card in childrenOfSpineCards
    }

    # Get the positions of the spine joints AND the next joint, since that determines the rotation of the final spine
    reposeJoints = [getRJoint(j) for j in spineCard.joints]
    pos = [pdil.dagObj.getPos(rj) for rj in reposeJoints]

    nextJoint = spineEnd.getOrientState()
    if nextJoint.joint:
        pos.append(pdil.dagObj.getPos(getRJoint(nextJoint.joint)))

    angles = [
        math.degrees((child - cur).angle(up))
        for cur, child in zip(pos, pos[1:])
    ]

    currentRotations = [pdil.dagObj.getRot(rj) for rj in reposeJoints]

    adjust = [(i, angle) for i, angle in enumerate(angles)
              if angle > threshold]
    total = sum((angle for _, angle in adjust))

    for i, angle in adjust:
        currentRotations[i].x -= rotation * (angle / total)
        xform(reposeJoints[i], ws=True, ro=currentRotations[i])

    for bpj, rot in preserve.items():
        xform(bpj, ws=True, ro=rot)
Пример #12
0
def __load(targetMesh, targetSkeletonRoot):
    data = _loadTempWeight()
    
    if not data:
        return
    
    skiningData = data.values()[0]
    
    # Map all the joints in the file to the joints in the targetSkeletonRoot
    
    targetJoints = {j: [] for j in listRelatives(targetSkeletonRoot, ad=True, type='joint')}
    targetJoints[PyNode(targetSkeletonRoot)] = []
    
    #print(skiningData['joints'].values()[0])
    sourceTrans = {j: dt.Vector( info[1:4] ) for j, info in skiningData['joints'].items()}
    targetTrans = {target: target.getTranslation(space='world') for target in targetJoints}
    
    #for target in targetJoints:
    #    trans = target.getTranslation(space='world')
    for srcJ, srcPos in sourceTrans.items():
        dists = [((srcPos - tgtPos).length(), tgtJ) for tgtJ, tgtPos in targetTrans.items()]
        dists.sort()
        print(srcJ, list((dists))[-3:])
Пример #13
0
def midAimer(start, end, midCtrl, name='aimer', upVector=None):
    '''
    Creates an object point contrained to two others, aiming at the second.  Up
    vector defaults to the control's Y.
    '''
    aimer = group(em=True, name=name)
    #aimer.setParent(container)
    #aimer = polyCone(axis=[1, 0, 0])[0]
    core.dagObj.moveTo(aimer, midCtrl)
    pointConstraint(end, start, aimer, mo=True)

    aimV = dt.Vector(xform(end, q=True, ws=True, t=True)) - dt.Vector(
        xform(aimer, q=1, ws=1, t=1))
    aimV.normalize()

    if upVector:
        midCtrlYUp = upVector
    else:
        temp = xform(midCtrl, q=True, ws=True, m=True)
        midCtrlYUp = dt.Vector(temp[4:7])
    """
    # Generally the X axis is a good default up since things are normally  on that plane
    if abs(aimV[0]) < 0.0001 or min([abs(v) for v in aimV]) == abs(aimV[0]):
        upV = dt.Vector([-1, 0, 0])
        forwardV = aimV.cross(upV)
        recalcUp = forwardV.cross(aimV)
        
        # Reference
        #xrow = aimV
        #yrow = recalcUp
        #zrow = forwardV
        midCtrlYUp = recalcUp
        print( 'midCtrlYUp', midCtrlYUp )
    else:
        # Choose Y up as the up (hopefully this works)
        if abs(aimV[1]) < abs(aimV[0]) and abs(aimV[1]) < abs(aimV[2]):
            upV = dt.Vector([0, 1, 0])
            forwardV = aimV.cross(upV)
            recalcUp = forwardV.cross(aimV)
            
            # Reference
            #xrow = aimV
            #yrow = recalcUp
            #zrow = forwardV
            midCtrlYUp = recalcUp
            pass
    #
    """

    # Determine which axis of the end is closest to the midControl's Y axis.
    endMatrix = xform(end, q=True, ws=True, m=True)
    #midMatrix = xform(aimer, q=True, ws=True, m=True)
    #midCtrlYUp = dt.Vector(midMatrix[4:7])

    choices = [
        (endMatrix[:3], [1, 0, 0]),
        ([-x for x in endMatrix[:3]], [-1, 0, 0]),
        (endMatrix[4:7], [0, 1, 0]),
        ([-x for x in endMatrix[4:7]], [0, -1, 0]),
        (endMatrix[8:11], [0, 0, -1]),
        ([-x for x in endMatrix[8:11]], [0, 0, 1]),
    ]

    # Seed with the first choice as the best...
    low = midCtrlYUp.angle(dt.Vector(choices[0][0]))
    axis = choices[0][1]
    # ... and see if any others are better
    for vector, destAxis in choices[1:]:
        vector = dt.Vector(
            vector)  # Just passing 3 numbers sometimes gets a math error.

        if midCtrlYUp.angle(vector) < low:
            low = midCtrlYUp.angle(vector)
            axis = destAxis

    aimConstraint(end,
                  aimer,
                  wut='objectrotation',
                  aim=[1, 0, 0],
                  wuo=end,
                  upVector=[0, 1, 0],
                  wu=axis,
                  mo=False)

    return aimer
Пример #14
0
    @classmethod
    def check(cls, jnt):
        if cls.zero < abs(cmds.xform(str(jnt), q=True, ws=True, t=True)[0]) < cls.tolerance:
            cls.offcenter.append(jnt)
            
    @classmethod
    def results(cls):
        
        if not cls.offcenter:
            return ''

        return ('These joints are really close to the center, are they supposed to be offcenter?\n    ' +
                '\n    '.join( [str(j) for j in cls.offcenter] ) )


ZERO_VECTOR = dt.Vector(0, 0, 0)


class Rotation(Reporter):
    '''
    Controls should only be made on joints that are not rotated, so make sure
    the are all not rotated.
    '''
    
    rotatedJoints = []
    
    @classmethod
    def clear(cls):
        cls.rotatedJoints = []
    
    @classmethod
Пример #15
0
def buildIkChain(start,
                 end,
                 pvLen=None,
                 stretchDefault=1,
                 endOrientType=util.EndOrient.TRUE_ZERO,
                 twists={},
                 makeBendable=False,
                 name='',
                 groupName='',
                 controlSpec={}):
    '''
    
    :param int pvLen: How far from the center joint to be, defaults to half the length of the chain.
    ..  todo::
        * Have fk build as rotate only if not stretchy
    
    :param dict twists: Indicates how many twists each section has, ex {1: 2} means
        joint[1] has 2 twists, which means a 3 joint arm chain becomes
        shoulder, elbow, twist1, twist2, wrist

    '''

    chain = util.getChain(start, end)

    # Simplify the names
    controlChain = util.dupChain(start, end)
    for j, orig in zip(controlChain, chain):
        j.rename(util.trimName(orig) + '_proxy')

    mainJointCount = len(chain) - sum(twists.values())

    # Take the linear chain and figure out what are the "main ik", and which
    # are the twist joints.  Also parent the mainArmature as a solo chain for ik application.
    mainArmature = []
    subTwists = {}
    cur = 0
    for i in range(mainJointCount):
        mainArmature.append(controlChain[cur])

        if len(
                mainArmature
        ) > 1:  # Need to reparent so the 'pivot' joints are independent of the twists

            if mainArmature[-1].getParent() != mainArmature[
                    -2]:  # ... unless this section has no twists and is already parented.
                mainArmature[-1].setParent(mainArmature[-2])

        cur += 1
        if i in twists:
            subTwists[mainArmature[-1]] = []

            for ti in range(twists[i]):
                subTwists[mainArmature[-1]].append(controlChain[cur])
                controlChain[cur].setParent(
                    w=True
                )  # This ends up being temporary so the ik is applied properly
                cur += 1

    # actual ik node
    mainIk = ikHandle(sol='ikRPsolver',
                      sj=mainArmature[0],
                      ee=mainArmature[-1])[0]
    # NOT using Spring because it acts odd.  If the pelvis turns, the poleVectors follow it.
    # Make as RP first so the ik doesn't flip around
    #PyNode('ikSpringSolver').message >> mainIk.ikSolver

    # Build the main ik control

    hide(mainIk)
    hide(controlChain)

    if not name:
        name = util.trimName(start)

    ctrl = controllerShape.build(name + '_Ik',
                                 controlSpec['main'],
                                 type=controllerShape.ControlType.IK)

    container = group(n=name + '_grp')
    container.setParent(node.mainGroup())

    pdil.dagObj.moveTo(ctrl, end)
    pdil.dagObj.zero(ctrl).setParent(container)

    # Orient the main ik control
    if endOrientType == util.EndOrient.TRUE_ZERO:
        util.trueZeroSetup(end, ctrl)
    elif endOrientType == util.EndOrient.TRUE_ZERO_FOOT:
        util.trueZeroFloorPlane(end, ctrl)
    elif endOrientType == util.EndOrient.JOINT:
        pdil.dagObj.matchTo(ctrl, end)

        ctrl.rx.set(util.shortestAxis(ctrl.rx.get()))
        ctrl.ry.set(util.shortestAxis(ctrl.ry.get()))
        ctrl.rz.set(util.shortestAxis(ctrl.rz.get()))

        pdil.dagObj.zero(ctrl)
    elif endOrientType == util.EndOrient.WORLD:
        # Do nothing, it's built world oriented
        pass

    pdil.dagObj.lock(ctrl, 's')

    mainIk.setParent(ctrl)

    # I think orientTarget is for matching fk to ik
    orientTarget = duplicate(end, po=True)[0]
    orientTarget.setParent(ctrl)
    pdil.dagObj.lock(orientTarget)
    orientConstraint(orientTarget, mainArmature[-1])
    hide(orientTarget)

    pdil.dagObj.lock(mainIk)

    attr, jointLenMultiplier, nodes = util.makeStretchyNonSpline(
        ctrl, mainIk, stretchDefault)
    # &&& Need to do the math for all the

    # Make the offset joints and setup all the parenting of twists
    subArmature = []
    rotationOffsetCtrls = []
    bendCtrls = []
    for i, j in enumerate(
            mainArmature[:-1]
    ):  # [:-1] Since last joint can't logically have twists
        if makeBendable:
            j.drawStyle.set(
                2
            )  # Probably should make groups but not drawing bones works for now.
        offset = duplicate(j, po=True)[0]
        offset.setParent(j)
        offset.rename(pdil.simpleName(j, '{}_Twist'))

        #subArmature.append(offset)  ### OLD
        if True:  ### NEW
            if not makeBendable:
                subArmature.append(offset)
            else:
                if i == 0:
                    subArmature.append(offset)
                else:
                    offsetCtrl = controllerShape.build(
                        'Bend%i' % (len(bendCtrls) + 1), {
                            'shape': 'band',
                            'size': 10,
                            'color': 'green 0.22',
                            'align': 'x'
                        })
                    pdil.dagObj.matchTo(offsetCtrl, offset)
                    offsetCtrl.setParent(offset)
                    showHidden(offsetCtrl, a=True)
                    subArmature.append(offsetCtrl)
                    bendCtrls.append(offsetCtrl)

        rotationOffsetCtrls.append(offset)  # &&& Deprectated?

        attrName = pdil.simpleName(j, '{}_Twist')
        ctrl.addAttr(attrName, at='double', k=True)
        ctrl.attr(attrName) >> offset.rx

        if i in twists:
            for subTwist in subTwists[j]:
                subTwist.setParent(j)
                #subArmature.append(subTwist) ### NEW comment out

                attrName = pdil.simpleName(subTwist)
                ctrl.addAttr(attrName, at='double', k=True)
                ctrl.attr(attrName) >> subTwist.rx

                if not makeBendable:
                    subArmature.append(subTwist)
                else:
                    if True:  ### NEW
                        offsetCtrl = controllerShape.build(
                            'Bend%i' % (len(bendCtrls) + 1), {
                                'shape': 'band',
                                'size': 10,
                                'color': 'green 0.22',
                                'align': 'x'
                            })
                        pdil.dagObj.matchTo(offsetCtrl, subTwist)
                        offsetCtrl.setParent(subTwist)
                        subTwist.drawStyle.set(
                            2
                        )  # Probably should make groups but not drawing bones works fine for now.
                        showHidden(offsetCtrl, a=True)
                        subArmature.append(offsetCtrl)
                        bendCtrls.append(offsetCtrl)

                #offset.rename( simpleName(j, '{0}_0ffset') )

    #for mainJoint, (startSegment, endSegment) in zip( mainArmature, zip( rotationOffsetCtrls, rotationOffsetCtrls[1:] + [mainArmature[-1]] )):
    #    if mainJoint in subTwists:
    #        twistSetup(subTwists[mainJoint], startSegment, endSegment)

    # Since we don't want twists affecting eachother, base them off the mainArmature
    if False:  ### SKipping this new stuff and resurrecting the old twists
        for startSegment, endSegment in zip(mainArmature, mainArmature[1:]):
            #print( 'HAS SUB TWISTS', startSegment in subTwists )
            if startSegment in subTwists:
                twistSetup(ctrl, subTwists[startSegment], startSegment,
                           endSegment, jointLenMultiplier)
    '''
    # Build the groups to hold the twist controls
    groups = []
    for i, (j, nextJ) in enumerate(zip(mainArmature[:-1], mainArmature[1:])):
        g = group(em=True)
        parentConstraint(j, g)
        g.rename( pdil.dagObj.simpleName(g, '{0}_grp') )
        groups.append(g)

        g.setParent(container)
        
        if j in subTwists:
            
            #totalDist = pdil.dagObj.distanceBetween(j, nextJ)
            
            for subTwist in subTwists[j]:
                
                dist = pdil.dagObj.distanceBetween(j, subTwist)
                
                #disc = 'disc'()
                disc = controllerShape.build('Twist', {'shape': 'disc', 'align': 'x', 'size': 3})
                disc.setParent(g)
                disc.t.set( 0, 0, 0 )
                disc.r.set( 0, 0, 0 )
                
                pdil.dagObj.lock(disc)
                disc.rx.unlock()
                disc.tx.unlock()
                
                # Manage the lengths of the twist joints and their controls
                mult = pdil.math.multiply( dist, jointLenMultiplier)
                mult >> disc.tx
                mult >> subTwist.tx
                
                disc.rx >> subTwist.rx
    '''

    constraints = util.constrainAtoB(chain, subArmature + [mainArmature[-1]])

    # PoleVector
    if not pvLen or pvLen < 0:
        pvLen = util.chainLength(mainArmature) * 0.5
    out = util.calcOutVector(mainArmature[0], mainArmature[1],
                             mainArmature[-1])
    pvPos = out * pvLen + dt.Vector(
        xform(mainArmature[1], q=True, ws=True, t=True))
    pvCtrl = controllerShape.build(name + '_pv',
                                   controlSpec['pv'],
                                   type=controllerShape.ControlType.POLEVECTOR)

    pdil.dagObj.lock(pvCtrl, 'r s')
    xform(pvCtrl, ws=True, t=pvPos)
    controllerShape.connectingLine(pvCtrl, mainArmature[1])
    poleVectorConstraint(pvCtrl, mainIk)
    pdil.dagObj.zero(pvCtrl).setParent(container)

    # Socket offset control
    socketOffset = controllerShape.build(
        name + '_socket',
        controlSpec['socket'],
        type=controllerShape.ControlType.TRANSLATE)
    socketContainer = util.parentGroup(start)
    socketContainer.setParent(container)

    pdil.dagObj.moveTo(socketOffset, start)
    pdil.dagObj.zero(socketOffset).setParent(socketContainer)
    pdil.dagObj.lock(socketOffset, 'r s')
    pointConstraint(socketOffset, mainArmature[0])

    # Reuse the socketOffset container for the controlling chain
    mainArmature[0].setParent(socketContainer)
    #    hide( mainArmature[0] )
    ''' Currently unable to get this to update, maybe order of operations needs to be enforced?
    # Add switch to reverse the direction of the bend
    reverseAngle = controlChain[1].jointOrient.get()[1] * -1.1
    ctrl.addAttr( 'reverse', at='short', min=0, max=1, dv=0, k=True )
    preferredAngle = pdil.math.condition( ctrl.reverse, '=', 0, 0, reverseAngle )
    twist = pdil.math.condition( ctrl.reverse, '=', 0, 0, -180)
    preferredAngle >> controlChain[1].preferredAngleY
    twist >> mainIk.twist
    pdil.math.condition( mainIk.twist, '!=', 0, 0, 1 ) >> mainIk.twistType # Force updating??
    '''

    if True:  # &&& LOCKABLE
        endToMidDist, g1 = pdil.dagObj.measure(ctrl, pvCtrl, 'end_to_mid')
        startToMidDist, g2 = pdil.dagObj.measure(socketOffset, pvCtrl,
                                                 'start_to_mid')
        parent(endToMidDist, g1, startToMidDist, g2, container)

        #ctrl.addAttr( 'lockPV', at='double', min=0.0, dv=0.0, max=1.0, k=True )

        #switcher.input[0].set(1)

        #print('--'* 20)
        #print(mainArmature)

        for jnt, dist in zip(mainArmature[1:], [startToMidDist, endToMidDist]):
            axis = util.identifyAxis(jnt)
            lockSwitch = jnt.attr('t' + axis).listConnections(s=True,
                                                              d=False)[0]
            if jnt.attr('t' + axis).get() < 0:
                pdil.math.multiply(dist.distance, -1) >> lockSwitch.input[1]
            else:
                dist.distance >> lockSwitch.input[1]

            util.drive(ctrl, 'lockPV', lockSwitch.attributesBlender, 0, 1)
        """
        axis = identifyAxis(mainArmature[-1])
        lockSwitchA = mainArmature[-1].attr('t' + axis).listConnections(s=True, d=False)[0]
        if mainArmature[-1].attr('t' + axis).get() < 0:
            pdil.math.multiply( endToMidDist.distance, -1) >> lockSwitchA.input[1]
        else:
            endToMidDist.distance, -1 >> lockSwitchA.input[1]
        
        lockSwitchB = mainArmature[-2].attr('t' + axis).listConnections(s=True, d=False)[0]
        startToMidDist.distance >> lockSwitchB.input[1]
        #print(lockSwitchA, lockSwitchB, '-'* 20)
        drive(ctrl, 'lockPV', lockSwitchA.attributesBlender, 0, 1)
        drive(ctrl, 'lockPV', lockSwitchB.attributesBlender, 0, 1)
        """

    # Register all the parts of the control for easy identification at other times.
    ctrl = pdil.nodeApi.RigController.convert(ctrl)
    ctrl.container = container

    ctrl.subControl['socket'] = socketOffset
    for i, bend in enumerate(bendCtrls):
        ctrl.subControl['bend%i' % i] = bend
    ctrl.subControl['pv'] = pvCtrl
    # Add default spaces
    space.addMain(pvCtrl)
    #space.add( pvCtrl, ctrl, spaceName=shortName(ctrl, '{0}_posOnly') )
    #space.add( pvCtrl, ctrl, spaceName=shortName(ctrl, '{0}_posOnly'), mode=space.TRANSLATE)
    space.add(pvCtrl, ctrl)
    space.add(pvCtrl, ctrl, mode=space.Mode.TRANSLATE)

    return ctrl, constraints
Пример #16
0
def toObjByCenter(card, otherJoint):
    pos = xform(card, q=True, ws=True, t=True)
    dest = xform(otherJoint, q=True, ws=True, t=True)

    xform(card, ws=True, r=True, t=(dt.Vector(dest) - dt.Vector(pos)))
Пример #17
0
def to(card, otherJoint):
    pos = xform(card.start(), q=True, ws=True, t=True)
    dest = xform(otherJoint, q=True, ws=True, t=True)

    xform(card, ws=True, r=True, t=(dt.Vector(dest) - dt.Vector(pos)))
Пример #18
0
def splitCard(tempJoint):
    '''
    Everything after and including the given joint will become a new card.
    '''
    oldCard = tempJoint.cardCon.node()
    if oldCard.start() == tempJoint:
        warning('Cannot split at the first joint')
        return

    card = makeCard(jointCount=0, size=(1, 1))
    newCvs = list(card.cv)
    newCvs = [newCvs[0], newCvs[2], newCvs[1], newCvs[3]]

    if oldCard.listRelatives(type='mesh'):
        points = [
            dt.Vector(xform(v, q=True, ws=True, t=True)) for v in oldCard.vtx
        ]
        vtx = list(oldCard.vtx)
    else:
        points = [
            dt.Vector(xform(v, q=True, ws=True, t=True)) for v in oldCard.cv
        ]
        points = [points[0], points[2], points[1], points[3]
                  ]  # vtx and points must be rearranged in the same way
        vtx = list(oldCard.cv)
        vtx = [vtx[0], vtx[2], vtx[1], vtx[3]]

    midA = (points[0] - points[2]) / 2.0 + points[2]
    midB = (points[1] - points[3]) / 2.0 + points[3]

    xform(vtx[0], ws=True, t=midA)
    xform(vtx[1], ws=True, t=midB)

    card.setParent(oldCard.getParent())
    card.t.set(oldCard.t.get())
    card.r.set(oldCard.r.get())
    card.s.set(oldCard.s.get())

    xform(newCvs[0], ws=True, t=points[0])
    xform(newCvs[1], ws=True, t=points[1])
    xform(newCvs[2], ws=True, t=midA)
    xform(newCvs[3], ws=True, t=midB)

    start, repeat, end = util.parse(oldCard.nameInfo.get())

    index = oldCard.joints.index(tempJoint)

    if index == len(start):
        # New card is repeat + end
        oldCard.nameInfo.set(' '.join(start))
        card.nameInfo.set(repeat + '* ' + ' '.join(end))

    elif index == len(oldCard.joints) - len(end):
        oldCard.nameInfo.set(' '.join(start) + ' ' + repeat + '*')
        card.nameInfo.set(' '.join(end))
    else:
        # Terrible split!
        oldCard.nameInfo.set(' '.join(start) + ' ' + repeat + '*')
        card.nameInfo.set(repeat + 'X* ' + ' '.join(end))
        confirmDialog(
            m="You are splitting in the repeating Zone, you'll want to fix up names\nAn 'X' has been added to the new cards repeating section"
        )

    card.rename(card.nameInfo.get())
    oldCard.rename(oldCard.nameInfo.get())

    for j in oldCard.joints[index:]:

        prevConnection = j.message.listConnections(type=card.__class__, p=1)
        j.message.disconnect(prevConnection[0])

        # Not sure why position is lost but I'm not sure it really matters
        pos = xform(j, q=True, ws=True, t=True)
        card.addJoint(j)
        xform(j, ws=True, t=pos)
Пример #19
0
def buildDogleg(hipJoint,
                end,
                pvLen=None,
                name='Dogleg',
                endOrientType=util.EndOrient.TRUE_ZERO_FOOT,
                groupName='',
                controlSpec={}):
    '''
    ..  todo::
        * Specify toe joint instead to remove ambiguity in case of twist joints.
        * For some reason, sometimes, twist must be introduced because some flippin
            occurs.  For some reason the poleVector doesn't come in straight on.
            * Need to determine if a 180 twist is needed as the minotaur did.
        * Need to figure out the best way to constrain the last joint to the controller
    '''

    boundChain = util.getChain(hipJoint, end)

    container = group(n=name + '_dogHindleg',
                      em=True,
                      p=lib.getNodes.mainGroup())

    # &&& I think I want to turn this into the container for all extra stuff related to a given control
    chainGrp = group(p=container, n=name + "_ikChain", em=True)
    parentConstraint(hipJoint.getParent(), chainGrp, mo=True)

    # Make the control to translate/offset the limb's socket.
    socketOffset = controllerShape.build(
        name + '_socket',
        controlSpec['socket'],
        type=controllerShape.ControlType.TRANSLATE)
    core.dagObj.lockScale(socketOffset)
    core.dagObj.lockRot(socketOffset)
    core.dagObj.moveTo(socketOffset, hipJoint)
    socketZero = core.dagObj.zero(socketOffset)
    socketZero.setParent(chainGrp)

    footCtrl = controllerShape.build(name,
                                     controlSpec['main'],
                                     type=controllerShape.ControlType.IK)
    core.dagObj.lockScale(footCtrl)
    footCtrl.addAttr('bend', at='double', k=True)
    core.dagObj.moveTo(footCtrl, end)

    if endOrientType == util.EndOrient.TRUE_ZERO:
        util.trueZeroSetup(end, footCtrl)
    elif endOrientType == util.EndOrient.TRUE_ZERO_FOOT:
        util.trueZeroFloorPlane(end, footCtrl)
    elif endOrientType == util.EndOrient.JOINT:
        core.dagObj.matchTo(footCtrl, end)

        footCtrl.rx.set(util.shortestAxis(footCtrl.rx.get()))
        footCtrl.ry.set(util.shortestAxis(footCtrl.ry.get()))
        footCtrl.rz.set(util.shortestAxis(footCtrl.rz.get()))

        core.dagObj.zero(footCtrl)
    elif endOrientType == util.EndOrient.WORLD:
        # Do nothing, it's built world oriented
        pass

    util.createMatcher(footCtrl, end).setParent(container)

    # Make the main ik chain which gives overall compression
    masterChain = util.dupChain(hipJoint, end)
    masterChain[0].rename(simpleName(hipJoint, '{0}_OverallCompression'))

    mainIk = ikHandle(sol='ikRPsolver', sj=masterChain[0],
                      ee=masterChain[-1])[0]
    PyNode('ikSpringSolver').message >> mainIk.ikSolver

    mainIk.rename('mainIk')
    hide(mainIk)

    springFixup = group(em=True, n='SprinkIkFix')
    springFixup.inheritsTransform.set(False)
    springFixup.inheritsTransform.lock()
    springFixup.setParent(socketOffset)
    pointConstraint(socketOffset, springFixup)
    masterChain[0].setParent(springFixup)

    #pointConstraint( socketOffset, hipJoint )

    # Create the polevector.  This needs to happen first so things don't flip out later
    out = util.calcOutVector(masterChain[0], masterChain[1], masterChain[-1])
    if not pvLen or pvLen < 0:
        pvLen = util.chainLength(masterChain[1:]) * 0.5
    pvPos = out * pvLen + dt.Vector(
        xform(boundChain[1], q=True, ws=True, t=True))

    pvCtrl = controllerShape.build(name + '_pv',
                                   controlSpec['pv'],
                                   type=controllerShape.ControlType.POLEVECTOR)
    core.dagObj.lockScale(pvCtrl)
    core.dagObj.lockRot(pvCtrl)
    xform(pvCtrl, ws=True, t=pvPos)
    poleVectorConstraint(pvCtrl, mainIk)

    # Verify the knees are in the same place
    delta = boundChain[1].getTranslation(
        'world') - masterChain[1].getTranslation('world')
    if delta.length() > 0.1:
        mainIk.twist.set(180)

    # Make sub IKs so the chain can be offset
    offsetChain = util.dupChain(hipJoint, end)
    hide(offsetChain[0])
    offsetChain[0].rename('OffsetChain')
    offsetChain[0].setParent(container)
    controllerShape.connectingLine(pvCtrl, offsetChain[1])
    constraints = util.constrainAtoB(util.getChain(hipJoint, end),
                                     offsetChain,
                                     mo=False)

    pointConstraint(masterChain[0], offsetChain[0])
    ankleIk = ikHandle(sol='ikRPsolver', sj=offsetChain[0],
                       ee=offsetChain[-2])[0]
    offsetIk = ikHandle(sol='ikRPsolver',
                        sj=offsetChain[-2],
                        ee=offsetChain[-1])[0]
    offsetIk.rename('metatarsusIk')

    offsetControl = group(em=True, n='OffsetBend')
    offsetContainer = group(offsetControl, n='OffsetSpace')
    offsetContainer.setParent(footCtrl)

    # Setup the offsetContainer so it is properly aligned to bend on z
    offsetContainer.setParent(masterChain[-1])
    offsetContainer.t.set(0, 0, 0)
    #temp = aimConstraint( pvCtrl, offsetContainer, aim=[1, 0, 0], wut='object', wuo=hipJoint, u=[0, 1, 0])
    #delete( temp )
    '''
    NEED TO CHANGE THE ORIENTATION
    
    Must perfectly align with ankle segment so the offset ikhandle can translate
    according to how much things are scaled
    
    '''
    lib.anim.orientJoint(offsetContainer,
                         boundChain[-2],
                         upTarget=boundChain[-3],
                         aim='y',
                         up='x')
    #mimic old way lib.anim.orientJoint(offsetContainer, pvCtrl, upTarget=hipJoint, aim='x', up='y')
    #lib.anim.orientJoint(offsetContainer, pvCtrl, upTarget=hipJoint, aim='x', up='y')

    offsetControl.t.set(0, 0, 0)
    offsetControl.t.lock()
    offsetControl.r.set(0, 0, 0)
    footCtrl.bend >> offsetControl.rz
    '''
    This is really dumb.
    Sometimes maya will rotate everything by 180 but I'm not sure how to
    calculate the proper offset, which normally results in one axis being off
    by 360, so account for that too.
    '''
    temp = orientConstraint(footCtrl, offsetChain[-1], mo=True)

    if not core.math.isClose(offsetChain[-1].r.get(), [0, 0, 0]):

        badVals = offsetChain[-1].r.get()
        delete(temp)
        offsetChain[-1].r.set(-badVals)
        temp = orientConstraint(footCtrl, offsetChain[-1], mo=True)

        for a in 'xyz':
            val = offsetChain[-1].attr('r' + a).get()
            if abs(val - 360) < 0.00001:
                attr = temp.attr('offset' + a.upper())
                attr.set(attr.get() - 360)

            elif abs(val + 360) < 0.00001:
                attr = temp.attr('offset' + a.upper())
                attr.set(attr.get() + 360)
    # Hopefully the end of dumbness

    ankleIk.setParent(offsetControl)

    # Adjust the offset ikHandle according to how long the final bone is.

    if masterChain[-1].tx.get() > 0:
        masterChain[-1].tx >> ankleIk.ty
    else:
        core.math.multiply(masterChain[-1].tx, -1.0) >> ankleIk.ty

    ankleIk.tx.lock()
    ankleIk.tz.lock()

    #ankleIk.t.lock()

    mainIk.setParent(footCtrl)
    offsetIk.setParent(footCtrl)

    core.dagObj.zero(footCtrl).setParent(container)

    hide(masterChain[0], ankleIk, offsetIk)
    poleVectorConstraint(pvCtrl, ankleIk)
    poleVectorConstraint(pvCtrl, offsetIk)

    # Adding the pv constraint might require a counter rotation of the offsetIk
    counterTwist = offsetChain[-2].rx.get() * (
        1.0 if offsetChain[-2].tx.get() < 0 else -1.0)
    offsetIk.twist.set(counterTwist)

    core.dagObj.zero(pvCtrl).setParent(container)

    # Make stretchy ik, but the secondary chain needs the stretch hooked up too.
    rig.makeStretchyNonSpline(footCtrl, mainIk)
    #for src, dest in zip( util.getChain(masterChain, masterEnd)[1:], util.getChain( hipJoint, getDepth(hipJoint, 4) )[1:] ):
    #    src.tx >> dest.tx

    for src, dest in zip(masterChain[1:], offsetChain[1:]):
        src.tx >> dest.tx

    footCtrl = nodeApi.RigController.convert(footCtrl)
    footCtrl.container = container
    footCtrl.subControl['pv'] = pvCtrl
    footCtrl.subControl['socket'] = socketOffset

    # Add default spaces
    space.addWorld(pvCtrl)
    space.add(pvCtrl, footCtrl)
    space.add(pvCtrl, footCtrl, mode=space.Mode.TRANSLATE)
    if hipJoint.getParent():
        space.add(pvCtrl, hipJoint.getParent())

        space.addWorld(footCtrl)
        space.add(footCtrl, hipJoint.getParent())

    return footCtrl, constraints
Пример #20
0
def buildSquashAndStretch(joints, squashCenter, orientAsParent=True, rangeMin=-5, rangeMax=5, scaleMin=0.5, scaleMax=2, controlSpec={}):
    '''
    :param joints: List of joints that will scale
    :param squashCenter: The worldspace center point to place the master squash control.
    :param orientAsParent: Whether the control should be oriented ?? Does this make sense?... Probably not
    '''
    
    squashCenter = dt.Vector(squashCenter)
    container = util.parentGroup(joints[0])
    container.setParent( node.mainGroup() )
    
    mainCtrl = controllerShape.build(   util.trimName(joints[0].getParent()) + "SquashMain_ctrl",
                                controlSpec['main'],
                                type=controllerShape.ControlType.TRANSLATE )
    mainCtrl = pdil.nodeApi.RigController.convert(mainCtrl)
    mainCtrl.setParent(container)
    
    mainCtrl.addAttr( 'size', at='double', min=rangeMin, max=rangeMax, dv=0.0, k=True )
    
    pdil.dagObj.lock(mainCtrl, 's')
    
    if orientAsParent:
        pdil.dagObj.matchTo( mainCtrl, joints[0].getParent() )
                            
    xform(mainCtrl, ws=True, t=squashCenter)
    
    pdil.dagObj.zero(mainCtrl)
    
    subControls = []
    for i, j in enumerate(joints):
        subCtrl = controllerShape.build(util.trimName(j) + "_ctrl",
                                controlSpec['manual'],
                                type=controllerShape.ControlType.TRANSLATE )
        subControls.append(subCtrl)
        pdil.dagObj.matchTo(subCtrl, j)
        subCtrl.setParent(container)
        pdil.dagObj.zero(subCtrl)
        pdil.dagObj.lock(subCtrl, 'r s')
        
        scalingLoc = spaceLocator()
        scalingLoc.rename( util.trimName(j) + '_squasher' )
        pdil.dagObj.matchTo(scalingLoc, j)
        hide(scalingLoc)
        scalingLoc.setParent(mainCtrl)
        
        space.add(subCtrl, scalingLoc, 'standard')
                
        ctrlPos = dt.Vector(xform(subCtrl, q=True, ws=True, t=True))
        
        setDrivenKeyframe( scalingLoc, at=['tx', 'ty', 'tz'], cd=mainCtrl.size )
        
        mainCtrl.size.set(rangeMin)
        lower = (ctrlPos - squashCenter) * scaleMin + squashCenter
        xform(scalingLoc, ws=True, t=lower)
        setDrivenKeyframe( scalingLoc, at=['tx', 'ty', 'tz'], cd=mainCtrl.size )
        
        mainCtrl.size.set(rangeMax)
        upper = (ctrlPos - squashCenter) * scaleMax + squashCenter
        xform(scalingLoc, ws=True, t=upper)
        setDrivenKeyframe( scalingLoc, at=['tx', 'ty', 'tz'], cd=mainCtrl.size )
        
        mainCtrl.size.set(0.0)
        xform(scalingLoc, ws=True, t=(ctrlPos))
        
        mainCtrl.subControl[str(i)] = subCtrl
        
    constraints = util.constrainAtoB(joints, subControls)
    
    mainCtrl.container = container
    
    return mainCtrl, constraints
Пример #21
0
def getPos(obj):
    ''' Get worldspace position '''
    return dt.Vector(xform(obj, q=True, ws=True, t=True))
Пример #22
0
def buildRibbon(start,
                end,
                normal,
                numControls=3,
                name='Ribbon',
                groupName='',
                controlSpec={}):

    chain = util.getChain(start, end)
    controlChain = util.dupChain(start, end)

    if not name:
        name = 'Ribbon'

    container = util.parentGroup(chain[0])
    container.setParent(lib.getNodes.mainGroup())

    world = group(em=True)
    hide(world)
    world.setParent(container)

    #controlChain[0].setParent( container )

    crv = curve(d=1, p=[(0, 0, 0)] * len(chain))

    for i, j in enumerate(chain):
        xform(crv.cv[i], t=xform(j, q=True, ws=True, t=True), ws=True)

    dup = duplicate(crv)[0]

    # &&& Obviously need to calc the offset somehow, maybe I can use the mirror state?  Maybe not, due to asymmetry
    #offset = dt.Vector(5, 0, 0)
    offset = normal

    crv.t.set(offset)
    dup.t.set(offset * -1)

    surfaceTrans = loft(crv,
                        dup,
                        uniform=True,
                        polygon=0,
                        sectionSpans=1,
                        degree=3,
                        autoReverse=True)[0]
    surfaceTrans.setParent(world)

    delete(crv, dup)

    rebuildSurface(surfaceTrans,
                   rebuildType=0,
                   replaceOriginal=True,
                   spansU=1,
                   spansV=(len(chain) - 1) * 2,
                   dir=2,
                   degreeV=3,
                   degreeU=1)
    hide(surfaceTrans)

    surfaceShape = surfaceTrans.getShape()

    closest = createNode('closestPointOnSurface')
    surfaceShape.worldSpace >> closest.inputSurface

    vScalar = surfaceShape.minMaxRangeV.get()[1]

    #constraints = []

    for jnt, hairJoint in zip(chain, controlChain):
        #jnt.setParent(world)

        follicle = createNode('follicle')
        hide(follicle)
        trans = follicle.getParent()

        #hairJoints.append(trans)
        trans.setParent(world)

        pos = jnt.getTranslation(space='world')
        closest.inPosition.set(pos)

        surfaceShape.local >> follicle.inputSurface

        u = closest.parameterU.get()
        # closestPointOnSurface returns val in relation to the maxV but the follicle needs it normalized.
        v = closest.parameterV.get() / vScalar

        follicle.parameterU.set(u)
        follicle.parameterV.set(v)

        follicle.outTranslate >> trans.translate
        follicle.outRotate >> trans.rotate
        trans.translate.lock()
        trans.rotate.lock()
        hairJoint.setParent(trans)

    constraints = util.constrainAtoB(chain, controlChain)

    temp = core.capi.asMObject(surfaceShape)
    nurbsObj = OpenMaya.MFnNurbsSurface(temp.object())

    controls = []
    controlJoints = []
    for i in range(numControls):
        percent = i / float(numControls - 1) * vScalar
        p = pointOnSurface(surfaceShape, u=.5, v=percent, p=True)

        ctrl = controllerShape.build(
            name + '%i' % (i + 1),
            controlSpec['main'],
            type=controllerShape.ControlType.TRANSLATE)

        ctrl.t.set(p)
        j = joint(ctrl)
        hide(j)
        controlJoints.append(j)

        ctrl.setParent(container)

        # Aim the control at the next joint with it's up following the surface
        if i < numControls - 1:
            target = chain[i + 1]
            normal = nurbsObj.normal(0.5, percent)
            lib.anim.orientJoint(ctrl,
                                 target,
                                 upVector=dt.Vector(normal.x, normal.y,
                                                    normal.z))
            core.dagObj.zero(ctrl)

        controls.append(ctrl)

    # Orient the final control to the final joint
    core.dagObj.matchTo(controls[-1], chain[-1])
    core.dagObj.zero(controls[-1])

    skinCluster(surfaceShape, controlJoints, tsb=True)

    mainCtrl = nodeApi.RigController.convert(controls[0])
    mainCtrl.container = container
    for i, ctrl in enumerate(controls[1:], 1):
        mainCtrl.subControl[str(i)] = ctrl

    return mainCtrl, constraints
Пример #23
0
def orientJoint(jnt, target, upTarget=None, aim='x', up='y', upVector=None):
    '''
    Orient an object (doesn't have to be a joint) to the target.  Basically a
    code only aiming.
    
    :param PyNode jnt: The joint to orient
    :param PyNode target: The object to orient to.
    :param PyNode/upTarget pos: A PyNode or position [x,y,z] for the up vector
    :param upVector: If specified, upTarget is not needed
    :param chr aim:
    
    This works for aim=x, up=y and negative versions
    
    Things to investigate:
        * Sometimes the rotations are different but that turns out to be -0.0 in
        the matrix.  AFAIK, everything still ends up fine.
            # Adept ends up with the same JOs!
            # Minotaur is the same
        
        * It looks like rotate order doesn't matter
    
    It's (almost) an all code version of:

        if isinstance(pos, PyNode):
            upObj = pos
        else:
            upObj = spaceLocator()
            upObj.t.set( pos )
        
        aim = axisConvert(aim)
        up = axisConvert(up)
        
        # Temporarily unparent children and clear orientation.
        with lib.core.dagObj.Solo( jnt ):
            jnt.r.set(0, 0, 0)
            jnt.jo.set(0, 0, 0)
            
            const = aimConstraint( target, jnt, aim=aim, u=up, wut='object', wuo=upObj )
            jnt.jo.set( jnt.r.get() )
            delete( const )
            jnt.r.set(0, 0, 0)

        def axisConvert( axisChar ):
            """
            Turn a character representing an axis into 3 numbers, ex x = [1,0,0], -y = [0,-1,0]
            
            :param char axisChar: Either "x", "y" or "z", possibly negated, eg: "-x"
            """
            axis = [0, 0, 0]
            c = axisChar[-1]
            axis[ ord(c) - ord('x') ] = -1 if axisChar.startswith('-') else 1
            return axis
    '''

    #print jnt, target, pos, aim, up
    jPos = dt.Vector(cmds.xform(str(jnt), q=True, ws=True, t=True))

    if not isinstance(target, dt.Vector):
        tPos = dt.Vector(cmds.xform(str(target), q=True, ws=True, t=True))
    else:
        tPos = target

    if not upVector:
        if isinstance(upTarget, PyNode):
            uPos = dt.Vector(cmds.xform(str(upTarget), q=True, ws=True,
                                        t=True))
        else:
            uPos = dt.Vector(upTarget)

        upV = uPos - jPos
        if up[0] == '-':
            upV *= -1.0
        upV.normalize()
    else:
        upV = dt.Vector(upVector)
        upV.normalize()

    aimV = tPos - jPos
    if aim[0] == '-':
        aimV *= -1.0
    aimV.normalize()

    # The aim/up order determines if it's aim.cross(up) or up.cross(aim) for the final axis
    if aim[-1] == 'x' and up[-1] == 'y':
        mainCross = _forwardCross
        finalCross = _forwardCross
    elif aim[-1] == 'x' and up[-1] == 'z':
        mainCross = _reverseCross
        finalCross = _reverseCross

    elif aim[-1] == 'y' and up[-1] == 'z':
        mainCross = _forwardCross
        finalCross = _forwardCross
    elif aim[-1] == 'y' and up[-1] == 'x':
        mainCross = _reverseCross
        finalCross = _reverseCross

    elif aim[-1] == 'z' and up[-1] == 'x':
        mainCross = _forwardCross
        finalCross = _forwardCross
    elif aim[-1] == 'z' and up[-1] == 'y':
        mainCross = _reverseCross
        finalCross = _reverseCross

    finalAxis = mainCross(aimV, upV)
    finalAxis.normalize()

    # aimV and upV are probably not perpendicular, but finalAxis was built
    # perpendicular to both so rebuild upV from aimV and finalAxis
    #newUp = finalAxis.cross(aimV)
    newUp = finalCross(finalAxis, aimV)
    newUp.normalize()

    axes = [None, None, None]

    if aim[-1] == 'x':
        axes[0] = list(aimV) + [0.0]
    elif aim[-1] == 'y':
        axes[1] = list(aimV) + [0.0]
    else:
        axes[2] = list(aimV) + [0.0]

    if up[-1] == 'x':
        axes[0] = list(newUp) + [0.0]
    elif up[-1] == 'y':
        axes[1] = list(newUp) + [0.0]
    else:
        axes[2] = list(newUp) + [0.0]

    for i, v in enumerate(axes):
        if not v:
            axes[i] = list(finalAxis) + [0.0]

    axes.append(list(jnt.t.get()) + [1.0])

    r = core.math.eulerFromMatrix(axes, degrees=True)

    # Temporarily unparent children and clear orientation.
    with core.dagObj.TempWorld(jnt):
        with core.dagObj.Solo(jnt):
            if jnt.type() == 'joint':
                jnt.r.set(0, 0, 0)
                jnt.jo.set(r)
            else:
                jnt.r.set(r)
Пример #24
0
def getRot(obj):
    ''' Get worldspace rotation '''
    return dt.Vector(xform(obj, q=True, ws=True, ro=True))
Пример #25
0
def determineClosestWorldOrient(obj):
    '''
    Given an object, returns the shortest rotation that aligns the object with
    the world.  This is used to allow IK elements to have world alignment but
    easily return to the bind pose.
    '''
    ''' # This is essentially a math version of the following:
        x = spaceLocator()
        y = spaceLocator()
        core.dagObj.moveTo( x, obj )
        core.dagObj.moveTo( y, obj )
        x.tx.set( 1 + x.tx.get() )
        y.ty.set( 1 + y.ty.get() )
        x.setParent(obj)
        y.setParent(obj)
        
        def zeroSmaller(loc):
            vals = [abs(v) for v in loc.t.get() ]
            largetVal = max(vals)
            index = vals.index(largetVal)
            for i, attr in enumerate('xyz'):
                if i == index:
                    continue
                loc.attr( 't' + attr ).set(0)
        
        zeroSmaller( x )
        zeroSmaller( y )
        
        ref = spaceLocator()
        core.dagObj.moveTo( ref, obj )
        aimConstraint( x, ref, wut='object', wuo=y )
        
        rot = ref.r.get()
        delete( x, y, ref )
        return rot
    '''

    # Make 2 world spaced points one unit along x and y
    x = dt.Matrix([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (1, 0, 0, 0)])
    y = dt.Matrix([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 0)])
    #z = dt.Matrix( [ (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 1, 0,) ] )

    world = obj.worldMatrix.get()
    inv = world.inverse()

    # Find the local matrices respective of the obj
    localX = x * inv
    localY = y * inv

    # For X, zero out the two smaller axes for each, ex t=.2, .3, .8 -> t=0, 0, .8
    def useX(matrix):
        return dt.Matrix([
            matrix[0], matrix[1], matrix[2],
            [matrix[3][0], 0, 0, matrix[3][3]]
        ])

    def useY(matrix):
        return dt.Matrix([
            matrix[0], matrix[1], matrix[2],
            [0, matrix[3][1], 0, matrix[3][3]]
        ])

    def useZ(matrix):
        return dt.Matrix([
            matrix[0], matrix[1], matrix[2],
            [0, 0, matrix[3][2], matrix[3][3]]
        ])

    xUsed, yUsed, zUsed = [False] * 3
    if abs(localX[3][0]) > abs(localX[3][1]) and abs(localX[3][0]) > abs(
            localX[3][2]):
        localX = useX(localX)
        xUsed = True
    elif abs(localX[3][1]) > abs(localX[3][0]) and abs(localX[3][1]) > abs(
            localX[3][2]):
        localX = useY(localX)
        yUsed = True
    else:
        localX = useZ(localX)
        zUsed = True

    # Do the same for Y
    if xUsed:
        if abs(localY[3][1]) > abs(localY[3][2]):
            localY = useY(localY)
            yUsed = True
        else:
            localY = useZ(localY)
            zUsed = True

    elif yUsed:
        if abs(localY[3][0]) > abs(localY[3][2]):
            localY = useX(localY)
            xUsed = True
        else:
            localY = useZ(localY)
            zUsed = True

    elif zUsed:
        if abs(localY[3][0]) > abs(localY[3][1]):
            localY = useX(localX)
            xUsed = True
        else:
            localY = useY(localY)
            yUsed = True

    # Find the 'world' (but treating the obj's pos as the origin) positions.
    worldX = localX * world
    worldY = localY * world

    # Convert this into a rotation matrix by mimicing an aim constraint
    x = dt.Vector(worldX[-1][:-1])
    y = dt.Vector(worldY[-1][:-1])

    x.normalize()
    y.normalize()
    z = x.cross(y)
    y = z.cross(x)

    msutil = maya.OpenMaya.MScriptUtil()
    mat = maya.OpenMaya.MMatrix()
    msutil.createMatrixFromList([
        x[0], x[1], x[2], 0.0, y[0], y[1], y[2], 0.0, z[0], z[1], z[2], 0.0,
        0.0, 0.0, 0.0, 1.0
    ], mat)  # noqa e123
    rot = maya.OpenMaya.MEulerRotation.decompose(
        mat, maya.OpenMaya.MEulerRotation.kXYZ)

    return dt.Vector(math.degrees(rot.x), math.degrees(rot.y),
                     math.degrees(rot.z))
Пример #26
0
def getUpVectors(j):
    # HACK!!!!  Needs to work in surfaceFollow
    return dt.Vector(0, 0, 1), dt.Vector(0, 0, 1)
Пример #27
0
    def buildBones(cls):
        # Might need to wrap as single undo()
        sel = set(util.selectedCards())
        if not sel:
            confirmDialog( m='No cards selected' )
            return

        issues = cls.validateBoneNames(sel)
        if issues:
            confirmDialog(m='\n'.join(issues), t='Fix these')
            return

        if tpose.reposerExists():
            realJoints = []
            bindPoseJoints = []

            with tpose.matchReposer(cardlister.cardJointBuildOrder()):
                for card in cardlister.cardJointBuildOrder():
                    if card in sel:
                        realJoints += card.buildJoints_core(nodeApi.fossilNodes.JointMode.tpose)

            for card in cardlister.cardJointBuildOrder():
                if card in sel:
                    bindPoseJoints += card.buildJoints_core(nodeApi.fossilNodes.JointMode.bind)

            constraints = []
            for bind, real in zip(bindPoseJoints, realJoints):
                constraints.append( orientConstraint( bind, real ) )
                real.addAttr( 'bindZero', at='double3' )
                
                real.addAttr( 'bindZeroX', at='double', p='bindZero' )
                real.addAttr( 'bindZeroY', at='double', p='bindZero' )
                real.addAttr( 'bindZeroZ', at='double', p='bindZero' )
                real.bindZero.set( real.r.get() )
            
            for constraint, real in zip(constraints, realJoints):
                delete(constraint)
                real.r.set(0, 0, 0)

            root = core.findNode.getRoot()
            topJoints = root.listRelatives(type='joint')

            for jnt in topJoints:
                try:
                    index = realJoints.index(jnt)
                    real = jnt
                    bind = bindPoseJoints[index]

                    real.addAttr( 'bindZeroTr', at='double3' )
                    real.addAttr( 'bindZeroTrX', at='double', p='bindZeroTr' )
                    real.addAttr( 'bindZeroTrY', at='double', p='bindZeroTr' )
                    real.addAttr( 'bindZeroTrZ', at='double', p='bindZeroTr' )

                    real.bindZeroTr.set(
                        dt.Vector(xform(bind, q=True, ws=True, t=True)) - dt.Vector(xform(real, q=True, ws=True, t=True))
                    )

                except ValueError:
                    pass

        else:
            # Only build the selected cards, but always do it in the right order.
            for card in cardlister.cardJointBuildOrder():
                if card in sel:
                    card.buildJoints_core(nodeApi.fossilNodes.JointMode.default)

        select(sel)
Пример #28
0
def buildSurfaceFollow(joints, groupOrigin, surface=None, controlSpec={}):

    groupOrigin = dt.Vector(groupOrigin)
    container = util.parentGroup(joints[0])
    container.setParent(lib.getNodes.mainGroup())

    mainCtrl = controllerShape.build(
        util.trimName(joints[0].getParent()) + 'Surface_ctrl',
        controlSpec['main'],
        type=controllerShape.ControlType.TRANSLATE)

    mainCtrl = nodeApi.RigController.convert(mainCtrl)

    mainCtrl.setParent(container)
    xform(mainCtrl, ws=True, t=groupOrigin)

    core.dagObj.lockScale(mainCtrl)

    core.dagObj.zero(mainCtrl)

    subControls = []
    locs = []
    offsets = []
    for i, j in enumerate(joints):
        loc = spaceLocator()
        locs.append(loc)
        core.dagObj.matchTo(loc, j)

        geometryConstraint(surface, loc)

        objUp, worldObjUp = getUpVectors(j)

        normalConstraint(surface,
                         loc,
                         wuo=mainCtrl,
                         wut='objectrotation',
                         upVector=objUp,
                         worldUpVector=worldObjUp)

        offsetCtrl = controllerShape.build(
            util.trimName(j) + 'Offset_ctrl',
            controlSpec['offset'],
            type=controllerShape.ControlType.TRANSLATE)

        core.dagObj.matchTo(offsetCtrl, loc)

        offsets.append(offsetCtrl)
        offsetCtrl.setParent(loc)
        core.dagObj.zero(offsetCtrl)

        subCtrl = controllerShape.build(
            util.trimName(j) + '_ctrl',
            controlSpec['manual'],
            type=controllerShape.ControlType.TRANSLATE)

        subControls.append(subCtrl)

        core.dagObj.matchTo(subCtrl, loc)

        subCtrl.setParent(mainCtrl)

        core.dagObj.zero(subCtrl)

        pointConstraint(subCtrl, loc)

        core.dagObj.lockRot(subCtrl)
        core.dagObj.lockScale(subCtrl)
        core.dagObj.lockScale(offsetCtrl)

        loc.setParent(subCtrl)

        space.add(offsetCtrl, loc, spaceName='surface')

        mainCtrl.subControl[str(i)] = subCtrl
        mainCtrl.subControl[str(i) + '_offset'] = offsetCtrl

    constraints = util.constrainAtoB(joints, offsets)

    mainCtrl.container = container

    return mainCtrl, constraints
Пример #29
0
def buildSplineChest(start,
                     end,
                     name='Chest',
                     indexOfRibCage=-1,
                     useTrueZero=True,
                     groupName='',
                     controlSpec={}):
    '''
    Makes a spline from the start to the `indexOfRibCage` joint, and
    TODO
        - the remaining joints get fk controllers (so you can make the spine and neck all one card, I guess)
    '''
    srcChain = util.getChain(start, end)

    chain = util.dupChain(start, end, '{0}_spline')

    chestBase = chain[indexOfRibCage]
    chestIndex = chain.index(chestBase)

    if chestIndex % 2 == 0:
        # Due to `division`, have to cast to int
        midPos = xform(chain[int(chestIndex / 2)], q=True, ws=True, t=True)
        midRot = xform(chain[int(chestIndex / 2)], q=True, ws=True, ro=True)

    else:
        tempIndex = int(math.floor(chestIndex / 2))
        low = chain[tempIndex]
        high = chain[tempIndex + 1]

        midPos = dt.Vector(xform(low, q=True, ws=True, t=True))
        midPos += dt.Vector(xform(high, q=True, ws=True, t=True))
        midPos = dt.Vector(midPos) * .5
        '''&&&
        To be safe, find the closest axis on the second obj
        Get average z basis, forward
        then average y basis, up
        calc x, side
        recalc y, up
        This is the world matrix of the average rotation'''
        midRot = xform(low, q=True, ws=True, ro=True)
        #raise Exception('Need to implement even number of stomach joints')

    container = group(em=True, p=node.mainGroup(), n=name + "_controls")
    container.inheritsTransform.set(False)
    container.inheritsTransform.lock()
    chain[0].setParent(container)

    mainIk, _effector, crv = ikHandle(sol='ikSplineSolver',
                                      sj=chain[0],
                                      ee=chestBase,
                                      ns=3,
                                      simplifyCurve=False)

    crvShape = crv.getShape()
    crvShape.overrideEnabled.set(True)
    crvShape.overrideDisplayType.set(2)

    parent(mainIk, crv, container)

    # -- Base --  # I don't think there is any benefit to controlling this, but it might just be my weighting.
    base = joint(None, n='Base')
    pdil.dagObj.moveTo(base, chain[0])
    base.setParent(container)
    parentConstraint(start.getParent(), base, mo=True)
    hide(base)

    # -- Chest control --
    chestCtrl = controllerShape.build(name + '_main', controlSpec['main'],
                                      controllerShape.ControlType.SPLINE)
    chestCtrl.setParent(container)
    util.makeStretchySpline(chestCtrl, mainIk)
    chestCtrl.stretch.set(1)
    chestCtrl.stretch.lock()
    chestCtrl.stretch.setKeyable(False)
    pdil.dagObj.lock(chestCtrl, 's')

    # Put pivot point at the bottom
    chestCtrl.ty.set(chestCtrl.boundingBox()[1][1])

    pdil.sharedShape.remove(chestCtrl, visNode.VIS_NODE_TYPE)
    chestCtrl.setPivots([0, 0, 0], worldSpace=True)
    makeIdentity(chestCtrl, a=True, t=True)
    pdil.sharedShape.use(chestCtrl, visNode.get())

    move(chestCtrl, xform(chestBase, q=True, ws=True, t=True), rpr=True)
    pdil.dagObj.zero(chestCtrl)

    if useTrueZero:
        rot = util.determineClosestWorldOrient(chestBase)

        util.storeTrueZero(chestCtrl, rot)
        pdil.dagObj.rezero(
            chestCtrl
        )  # Not sure why this is needed but otherwise the translate isn't zeroed
        chestCtrl.r.set(rot)

    chest = joint(None, n='Chest')
    chest.setParent(chestCtrl)
    pdil.dagObj.moveTo(chest, chestBase)
    pdil.dagObj.lock(chest)
    hide(chest)

    chestMatcher = util.createMatcher(chestCtrl, srcChain[chestIndex])
    chestMatcher.setParent(container)

    # Chest spaces need to happen after it's done being manipulated into place
    space.add(chestCtrl, start.getParent(), 'local')
    space.add(chestCtrl,
              start.getParent(),
              'local_posOnly',
              mode=space.Mode.TRANSLATE)
    space.addMain(chestCtrl)  # Not sure this space is useful...
    space.addTrueWorld(chestCtrl)
    space.add(chestCtrl,
              start.getParent(),
              'worldRotate',
              mode=space.Mode.ALT_ROTATE,
              rotateTarget=find.mainGroup())

    # -- Chest Offset -- &&& Currently hard coded to make a single offset joint
    chestOffsetCtrl = None
    if chestIndex < (len(chain) - 1):
        chestOffsetCtrl = controllerShape.build(
            name + '_bend', controlSpec['offset'],
            controllerShape.ControlType.SPLINE)
        chestOffsetCtrl.setParent(chestCtrl)
        pdil.dagObj.matchTo(chestOffsetCtrl, chain[-1])
        #move(chestOffsetCtrl, [0, 0.7, 3], r=True)
        pdil.dagObj.zero(chestOffsetCtrl)
        pdil.dagObj.lock(chestOffsetCtrl, 's')
        parentConstraint(chestOffsetCtrl, chain[-1], mo=True)

    # -- Mid --
    midCtrl = controllerShape.build(name + '_mid', controlSpec['middle'],
                                    controllerShape.ControlType.SPLINE)
    #pdil.dagObj.matchTo( midCtrl, midPoint )
    xform(midCtrl, ws=True, t=midPos)

    pdil.dagObj.lock(midCtrl, 's')
    midCtrl.setParent(container)

    mid = joint(None, n='Mid')
    #pdil.dagObj.moveTo( mid, midPoint )
    xform(mid, ws=True, t=midPos)
    mid.setParent(midCtrl)
    pdil.dagObj.lock(mid)
    hide(mid)

    # Mid control's rotation aims at the chest
    pdil.dagObj.zero(midCtrl)

    aimer = util.midAimer(base, chestCtrl, midCtrl)
    aimer.setParent(container)
    hide(aimer)

    space.add(midCtrl, aimer, spaceName='default')
    userDriven = space.addUserDriven(
        midCtrl, 'extreme')  # Best name I got, extreme poses!
    parentConstraint(base,
                     chestCtrl,
                     userDriven,
                     mo=True,
                     skipRotate=('x', 'y', 'z'))
    orientConstraint(base, chestCtrl, userDriven, mo=True)
    """
    # -- Shoulders --
    if numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints
        shoulderCtrl = controllerShape.build( name + '_shoulders', controlSpec['end'], controllerShape.ControlType.SPLINE )
        pdil.dagObj.matchTo( shoulderCtrl, srcChain[-2])  # We want to use the penultimate joint orientation
        pdil.dagObj.moveTo( shoulderCtrl, end)
        controllerShape.scaleAllCVs( shoulderCtrl, x=0.15 )
        shoulderZero = pdil.dagObj.zero(shoulderCtrl)
        shoulderZero.setParent(chestCtrl)
        pdil.dagObj.lock(shoulderCtrl, 't s')
    
        neck = joint(None, n='Neck')
        neck.setParent( shoulderCtrl )
        pdil.dagObj.moveTo( neck, end )
        pdil.dagObj.lock(neck)
        hide(neck)
    
    # -- Neck --
    neckCtrl = controllerShape.build( name + '_neck', controlSpec['neck'], controllerShape.ControlType.ROTATE )
    pdil.dagObj.matchTo( neckCtrl, end)
    if numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints
        pdil.dagObj.zero(neckCtrl).setParent( shoulderCtrl )
        pdil.dagObj.lock(neckCtrl, 's t')
        space.add( neckCtrl, srcChain[-2], 'chest' )
        
    else:
        pdil.dagObj.zero(neckCtrl).setParent( chestCtrl )
        pdil.dagObj.lock(neckCtrl, 't s')
        space.add( neckCtrl, chestCtrl, 'chest' )
        
    space.addMain(neckCtrl)
    """
    # Constrain to spline proxy, up to the chest...
    constraints = []
    for src, dest in list(zip(chain, srcChain))[:chestIndex]:
        constraints.append(pdil.constraints.pointConst(src, dest))
        constraints.append(pdil.constraints.orientConst(src, dest))

    # ... including the chest
    src = chain[chestIndex]
    dest = srcChain[chestIndex]

    # &&& Gotta remove/figure out what is going on here, why can't I just constrain entirely the srcChain to it's dup'd chain?
    if False:  # numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints
        constraints.append(pdil.constraints.pointConst(src, dest))
        constraints.append(pdil.constraints.orientConst(src, dest))
    # ... not including the chest
    else:
        chestProxy = duplicate(src, po=True)[0]
        chestProxy.setParent(chestCtrl)
        constraints.append(pdil.constraints.pointConst(chestProxy, dest))
        constraints.append(pdil.constraints.orientConst(chestProxy, dest))
        hide(chestProxy)

    if chestOffsetCtrl:
        constraints.append(pdil.constraints.pointConst(chain[-1],
                                                       srcChain[-1]))
        constraints.append(
            pdil.constraints.orientConst(chain[-1], srcChain[-1]))

    #constraints.append( pdil.constraints.pointConst( neckCtrl, srcChain[-1] ) )
    #constraints.append( pdil.constraints.orientConst( neckCtrl, srcChain[-1] ) )
    """
    if numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints
        # Make a proxy since we can't constrain with maintainOffset=True if we're making fk too.
        proxy = duplicate(srcChain[-2], po=True)[0]
        proxy.setParent(neck)
        pdil.dagObj.lock(proxy)
        
        constraints.append( pdil.constraints.pointConst( proxy, srcChain[-2] ) )
        constraints.append( pdil.constraints.orientConst( proxy, srcChain[-2] ) )
    """

    hide(chain, mainIk)

    # Bind joints to the curve
    if False:  # numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints
        skinCluster(crv, base, mid, chest, neck, tsb=True)
    else:
        skinCluster(crv, base, mid, chest, tsb=True)

    chestCtrl = pdil.nodeApi.RigController.convert(chestCtrl)
    chestCtrl.container = container
    chestCtrl.subControl['mid'] = midCtrl
    if chestOffsetCtrl:
        chestCtrl.subControl['offset'] = chestOffsetCtrl
    #if numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints
    #    chestCtrl.subControl['offset'] = shoulderCtrl
    #chestCtrl.subControl['neck'] = neckCtrl

    # Setup advanced twist
    startAxis = duplicate(start, po=True)[0]
    startAxis.rename('startAxis')
    startAxis.setParent(base)
    pdil.dagObj.lock(startAxis)

    endAxis = duplicate(start, po=True)[0]
    endAxis.rename('endAxis')
    endAxis.setParent(chestCtrl)
    endAxis.t.set(0, 0, 0)
    pdil.dagObj.lock(endAxis)

    hide(startAxis, endAxis)

    mainIk.dTwistControlEnable.set(1)
    mainIk.dWorldUpType.set(4)
    startAxis.worldMatrix[0] >> mainIk.dWorldUpMatrix
    endAxis.worldMatrix[0] >> mainIk.dWorldUpMatrixEnd

    hide(startAxis, endAxis)

    return chestCtrl, constraints
    '''
Пример #30
0
def buildSplineTwist(start, end, controlCountOrCrv=4, twistInfDist=0, simplifyCurve=True,
    tipBend=True, sourceBend=True, matchOrient=True, allowOffset=True,  # noqa e128
    useLeadOrient=False,  # This is an backwards compatible option, mutually exclusive with matchOrient
    twistStyle=TwistStyle.ADVANCED, duplicateCurve=True,
    controlOrient=OrientMode.CLOSEST_JOINT,
    name='', groupName='', controlSpec={}):
    '''
    Make a spline controller from `start` to `end`.
    
    :param int twistInfDist: Default twist controls to falloff before hitting eachother.
        Otherwise it is the number of joints on either side it will influence.
    :param bool simplifyCurve:  Only used if # of cvs is specified.  Turning it
        on will likely result it the curve not matching the existing joint position
        but will be more evenly spaced per control.
    :param bool tipBend:  If True, an extra cv will be added at the second to
        last joint, controlled by the last controller to ease out.
        
    ##:param bool applyDirectly: If True, rig the given joints, do not make a duplicate chain
        
    :param bool useLeadOrient: If True, the controllers will be aligned the same
        as the first joint.
        **NOTE** I think this option only exists to preserve previous builds, this is pretty dumb
        
    :param bool matchOrient: Does trueZero on the start and end.  I'm not sure this makes sense.
        
    
    
    ..  todo::
        * Add the same spline chain +X towards child that the neck has and test out advancedTwist()
    
        * See if I can identify the closest joint to a control and orient to that
        * The first joint has parent AND local, which are the same thing, keep this for convenience of selecting all the controls and editing attrs?
        * Test specifying your own curve
        * There is a float division error that can happen if there are too many control cvs.
        * Verify twists work right with unsimplified curves (hint, I don't think they do).
    '''
    
    matchOrient = False
    useLeadOrient = False
    
    if isinstance( controlCountOrCrv, int ):
        assert controlCountOrCrv > 3, "controlCount must be at least 4"
    
    # The axis to twist and stretch on.
    jointAxis = util.identifyAxis( start.listRelatives(type='joint')[0] )
    
    # Make a duplicate chain for the IK that will also stretch.
    stretchingChain = util.dupChain( start, end, '{0}_stretch' )
    
    # &&& NOTE!  This might affect advanced twist in some way.
    # If the chain is mirrored, we need to reorient to point down x so the
    # spline doesn't mess up when the main control rotates
    if stretchingChain[1].tx.get() < 0:
        # Despite aggresive zeroing of the source, the dup can still end up slightly
        # off zero so force it.
        for jnt in stretchingChain:
            jnt.r.set(0, 0, 0)
        joint( stretchingChain[0], e=True, oj='xyz', secondaryAxisOrient='yup', zso=True, ch=True)
        joint( stretchingChain[-1], e=True, oj='none')
    
    if isinstance( controlCountOrCrv, int ):
        mainIk, _effector, crv = ikHandle( sol='ikSplineSolver',
            sj=stretchingChain[0],
            ee=stretchingChain[-1],
            ns=controlCountOrCrv - 3,
            simplifyCurve=simplifyCurve)
    else:
        if duplicateCurve:
            crv = duplicate(controlCountOrCrv)[0]
        else:
            crv = controlCountOrCrv
            
        mainIk, _effector = ikHandle( sol='ikSplineSolver',
            sj=stretchingChain[0],
            ee=stretchingChain[-1],
            ccv=False,
            pcv=False)
        crv.getShape().worldSpace[0] >> mainIk.inCurve
    
    hide(mainIk)
    mainIk.rename( pdil.simpleName(start, "{0}_ikHandle") )
    crv.rename( pdil.simpleName(start, "{0}_curve") )
        
    if not name:
        name = util.trimName(start)

    if name.count(' '):
        name, endName = name.split()
    else:
        endName = ''
    
    # Only add a tipBend cv if number of cvs was specified.
    if tipBend and isinstance( controlCountOrCrv, int ):
        currentTrans = [ xform(cv, q=True, ws=True, t=True) for cv in crv.cv ]
        insertKnotCurve( crv.u[1], nk=1, add=False, ib=False, rpo=True, cos=True, ch=True)
        for pos, cv in zip(currentTrans, crv.cv[:-2]):
            xform( cv, ws=True, t=pos )
    
        xform( crv.cv[-2], ws=True, t=xform(end.getParent(), q=True, ws=True, t=True) )
        xform( crv.cv[-1], ws=True, t=currentTrans[-1] )
        
    # Only add a sourceBend cv if number of cvs was specified.
    if sourceBend and isinstance( controlCountOrCrv, int ):
        currentTrans = [ xform(cv, q=True, ws=True, t=True) for cv in crv.cv ]
        insertKnotCurve( crv.u[1.2], nk=1, add=False, ib=False, rpo=True, cos=True, ch=True)  # I honestly don't know why, but 1.2 must be different than 1.0
        for pos, cv in zip(currentTrans[1:], crv.cv[2:]):
            xform( cv, ws=True, t=pos )
    
        xform( crv.cv[0], ws=True, t=currentTrans[0] )
        xform( crv.cv[1], ws=True, t=xform(stretchingChain[1], q=True, ws=True, t=True) )
    
    grp = group(em=True, p=node.mainGroup(), n=start.name() + "_splineTwist")
    
    controls = util.addControlsToCurve(name + 'Ctrl', crv, controlSpec['main'])
    for ctrl in controls:
        pdil.dagObj.zero(ctrl).setParent( grp )


    if controlOrient == OrientMode.CLOSEST_JOINT:
        # Use the real chain to match orientations since the stretching chain might reorient to compensate for mirroring.
        jointPos = {j: dt.Vector(xform(j, q=True, ws=True, t=True)) for j in util.getChain(start, end)}
        
        aveSpacing = util.chainLength(stretchingChain) / (len(stretchingChain) - 1)
        
        for ctrl in controls:
            cpos = dt.Vector(xform(ctrl, q=True, ws=True, t=True))
            distances = [ ( (jpos - cpos).length() / aveSpacing, j) for j, jpos in jointPos.items()  ]
            distances.sort()
            
            
            ''' Just use the closest joint if within 10% of the average spacing
            Possible future improvement, look at two joints, and determine if
            the control is between them and inbetween the orientation.
            '''
            if True:  # distances[0][0] < 100:
                r = xform(distances[0][1], q=True, ro=True, ws=True)

                with pdil.dagObj.Solo(ctrl):
                    xform(ctrl, ro=r, ws=True)
                    pdil.dagObj.zero(ctrl)

    if endName:
        controls[-1].rename(endName + 'Ctrl')

    if matchOrient:
        util.trueZeroSetup(start, controls[0])
        util.trueZeroSetup(end, controls[-1])

    if tipBend:
        if useLeadOrient and not matchOrient:
            controls[-1].setRotation( end.getRotation(space='world'), space='world' )
        
        parent( controls[-2].getChildren(), controls[-1] )
        name = controls[-2].name()
        delete( pdil.dagObj.zero(controls[-2]) )

        if not endName:
            controls[-1].rename(name)
        controls[-2] = controls[-1]
        controls.pop()
        #core.dagObj.zero(controls[-2]).setParent(controls[-1])
        #channels = [t + a for t in 'trs' for a in 'xyz']
        #for channel in channels:
        #    controls[-2].attr( channel ).setKeyable(False)
        #    controls[-2].attr( channel ).lock()
           
    if sourceBend:
        names = []
        
        for ctrl in controls[1:-1]:
            names.append( ctrl.name() )
            ctrl.rename( '__temp' )
        
        endNum = -1 if endName else None
        for name, cur in zip(names, controls[2:endNum] ):
            cur.rename(name)
            
        if useLeadOrient and not matchOrient:
            controls[0].setRotation( start.getRotation(space='world'), space='world' )
            
        parent( controls[1].getChildren(), controls[0] )
        delete( pdil.dagObj.zero(controls[1]) )
        
        del controls[1]
        
    controls[0] = pdil.nodeApi.RigController.convert(controls[0])
    controls[0].container = grp
    
    stretchAttr, jointLenMultiplier = util.makeStretchySpline(controls[0], mainIk)
        
    connectingCurve = addConnectingCurve(controls)
    controls[0].visibility >> connectingCurve.visibility
    
    # Make twist for everything but hide them all and drive the ones that overlap
    # with spline controllers by the spline control.
    if not twistInfDist:
        numJoints = countJoints(start, end)
        twistInfDist = int(math.ceil( numJoints - len(controls) ) / float(len(controls) - 1))
        twistInfDist = max(1, twistInfDist)
    
    noInherit = group(em=True, p=grp, n='NoInheritTransform')
    pdil.dagObj.lock(noInherit)
    noInherit.inheritsTransform.set(False)
    noInherit.inheritsTransform.lock()

    # &&& If simplify curve is ON, the last joint gets constrained to the spinner?
    # Otherwise it gets constrained to the offset or stretch joint, which I think is correct.
    
    if allowOffset:
        # If allowOffset, make another chain to handle the difference in joint positions.
        offsetChain = util.dupChain( start, end, '{0}_offset' )

        offsetChain[0].setParent(noInherit)
        hide(offsetChain[0])
        twists, constraints = addTwistControls( offsetChain, start, end, twistInfDist)
        finalRigJoint = offsetChain[-1]
    else:
        twists, constraints = addTwistControls( stretchingChain, start, end, twistInfDist )
        finalRigJoint = stretchingChain[-1]
    
    # Constrain the end to the last controller so it doesn't pop off at all,
    # but still respect the stretch attr.
    pointConstraint(finalRigJoint, end, e=True, rm=True)
    
    # Make a proxy that can allows respecting stretch being active or not.
    endProxy = duplicate(end, po=True)[0]
    endProxy.rename('endProxy')
    hide(endProxy)
    endProxy.setParent(grp)
    
    stretchAttr >> pdil.constraints.pointConst( controls[-1], endProxy, mo=True )
    pdil.math.opposite(stretchAttr) >> pdil.constraints.pointConst( finalRigJoint, endProxy )
    constraints.point >> pdil.constraints.pointConst( endProxy, end )
    
    hide(twists)
    
    numControls = len(controls)
    numTwists = len(twists)
    for i, ctrl in enumerate(controls):
        index = int(round( i * ((numTwists - 1) / (numControls - 1)) ))
        util.drive( ctrl, 'twist', twists[index].attr('r' + jointAxis) )
        space.add( ctrl, start.getParent(), 'local' )
    
    parents = [start.getParent()] + controls[:-1]
    
    stretchingChain[0].setParent(noInherit)
    crv.setParent(noInherit)
    hide(crv, stretchingChain[0])
    connectingCurve.setParent( noInherit )
    
    mainIk.setParent(grp)
    
    # Do not want to scale but let rotate for "fk-like" space mode
    for ctrl, _parent in zip(controls, parents):
        pdil.dagObj.lock( ctrl, 's' )
        
        if useLeadOrient:
            ctrl.setRotation( start.getRotation(space='world'), space='world' )
            pdil.dagObj.zero(ctrl)
        
        space.addMain(ctrl)
        space.add( ctrl, _parent, 'parent')
    
    for i, ctrl in enumerate(controls[1:]):
        controls[0].subControl[str(i)] = ctrl
    
    # Must constrain AFTER controls (possibly) get orientd
    orientConstraint( controls[-1], finalRigJoint, mo=True )

    # Setup advanced twist
    if twistStyle == TwistStyle.ADVANCED:
        # &&& Test using advancedTwist() to replace the code beloew
        util.advancedTwist(stretchingChain[0], stretchingChain[1], controls[0], controls[-1], mainIk)
        '''
        startAxis = duplicate( start, po=True )[0]
        startAxis.rename( 'startAxis' )
        startAxis.setParent( controls[0] )
        
        endAxis = duplicate( start, po=True )[0]
        endAxis.rename( 'endAxis' )
        endAxis.setParent( controls[-1] )
        endAxis.t.set(0, 0, 0)
        
        mainIk.dTwistControlEnable.set(1)
        mainIk.dWorldUpType.set(4)
        startAxis.worldMatrix[0] >> mainIk.dWorldUpMatrix
        endAxis.worldMatrix[0] >> mainIk.dWorldUpMatrixEnd
        
        hide(startAxis, endAxis)
        '''
    else:
        if twistStyle == TwistStyle.X:
            controls[-1].rx >> mainIk.twist
        elif twistStyle == TwistStyle.NEG_X:
            pdil.math.multiply(controls[-1].rx, -1.0) >> mainIk.twist
            
        elif twistStyle == TwistStyle.Y:
            controls[-1].ry >> mainIk.twist
        elif twistStyle == TwistStyle.NEG_Y:
            pdil.math.multiply(controls[-1].ry, -1.0) >> mainIk.twist
            
        elif twistStyle == TwistStyle.Z:
            controls[-1].rz >> mainIk.twist
        elif twistStyle == TwistStyle.NEG_Z:
            pdil.math.multiply(controls[-1].rz, -1.0) >> mainIk.twist
        
        # To make .twist work, the chain needs to follow parent joint
        follow = group(em=True, p=grp)
        target = start.getParent()
        pdil.dagObj.matchTo(follow, stretchingChain[0])
        parentConstraint( target, follow, mo=1 )
        follow.rename(target + '_follow')
        stretchingChain[0].setParent(follow)
        
    # Constraint the offset (if exists) to the stretch last to account for any adjustments.
    if allowOffset:
        util.constrainAtoB(offsetChain[:-1], stretchingChain[:-1])
        pointConstraint(stretchingChain[-1], offsetChain[-1], mo=True)

    return controls[0], constraints