Esempio n. 1
0
def createControl(shapeData,
                  name=None,
                  targetNode=None,
                  link=False,
                  parent=None):
    """
    Create a control at the given target with the given shape data.

    Args:
        shapeData: A dict containing control shape data
        name: A string name of the control created
        targetNode: An optional transform node to position the
            control at upon creation
        link (bool): If true, link the control to the targetNode
        parent: An optional transform node to parent the control to
    """
    if targetNode and not isinstance(targetNode, pm.nt.Transform):
        raise TypeError('targetNode must be a Transform node')
    if name is None:
        name = 'ctl1'
    # create control transform
    ctl = pm.group(em=True, n=name)
    addShapes(ctl, shapeData)
    if targetNode:
        # match target node transform settings
        ctl.setAttr('rotateOrder', targetNode.getAttr('rotateOrder'))
        pulse.nodes.matchWorldMatrix(targetNode, ctl)
        if link:
            pulse.links.link(targetNode, ctl)
    # group to main ctls group
    if parent:
        ctl.setParent(parent)
    # apply meta data to keep track of control shapes
    meta.setMetaData(ctl, CONTROLSHAPE_METACLASS, {})
    return ctl
    def run(self):
        # add meta class to the control, making it
        # easy to search for by anim tools, etc
        meta.setMetaData(self.controlNode, self.config['controlMetaClass'], {})

        if self.zeroOutMethod == 1:
            # freeze offset matrix
            pulse.nodes.freezeOffsetMatrix(self.controlNode)
        elif self.zeroOutMethod == 2:
            # create an offset transform
            pulse.nodes.createOffsetTransform(self.controlNode)

        # lockup attributes
        keyableAttrs = pulse.nodes.getExpandedAttrNames(self.keyableAttrs)
        lockedAttrs = pulse.nodes.getExpandedAttrNames(
            ['t', 'r', 'rp', 's', 'sp', 'ra', 'sh', 'v'])
        lockedAttrs = list(set(lockedAttrs) - set(keyableAttrs))

        for attrName in keyableAttrs:
            attr = self.controlNode.attr(attrName)
            attr.setKeyable(True)

        for attrName in lockedAttrs:
            attr = self.controlNode.attr(attrName)
            attr.setKeyable(False)
            attr.showInChannelBox(False)
            attr.setLocked(True)

        # show rotate order in channel box
        self.controlNode.rotateOrder.setLocked(True)
        self.controlNode.rotateOrder.showInChannelBox(True)

        # update rig meta data
        self.extendRigMetaDataList('animControls', [self.controlNode])
Esempio n. 3
0
 def test_removeLockedData(self):
     meta.setMetaData(self.node, 'myMetaClass', 'myTestData')
     self.node.attr(meta.core.METADATA_ATTR).setLocked(True)
     result = meta.removeMetaData(self.node)
     self.assertFalse(result)
     data = meta.getMetaData(self.node)
     self.assertEqual(data, {'myMetaClass': 'myTestData'})
    def run(self):
        # add meta class to the control, making it
        # easy to search for by anim tools, etc
        meta.setMetaData(self.controlNode, self.config['controlMetaClass'], {})

        if self.createOffset:
            offsetNode = pulse.nodes.createOffsetGroup(self.controlNode)

        # lockup attributes
        keyableAttrs = pulse.nodes.getExpandedAttrNames(self.keyableAttrs)
        lockedAttrs = pulse.nodes.getExpandedAttrNames(
            ['t', 'r', 'rp', 's', 'sp', 'ra', 'sh', 'v'])
        lockedAttrs = list(set(lockedAttrs) - set(keyableAttrs))

        for attrName in keyableAttrs:
            attr = self.controlNode.attr(attrName)
            attr.setKeyable(True)

        for attrName in lockedAttrs:
            attr = self.controlNode.attr(attrName)
            attr.setKeyable(False)
            attr.showInChannelBox(False)
            attr.setLocked(True)

        # show rotate order in channel box
        self.controlNode.rotateOrder.setLocked(True)
        self.controlNode.rotateOrder.showInChannelBox(True)
Esempio n. 5
0
 def test_removeClassData(self):
     meta.setMetaData(self.node, 'myMetaClass', None)
     meta.setMetaData(self.node, 'mySecondMetaClass', None)
     result = meta.removeMetaData(self.node, 'myMetaClass')
     self.assertTrue(result)
     self.assertTrue(meta.isMetaNode(self.node))
     self.assertFalse(meta.hasMetaClass(self.node, 'myMetaClass'))
     self.assertTrue(meta.hasMetaClass(self.node, 'mySecondMetaClass'))
Esempio n. 6
0
 def save(self):
     # TODO: handle locked nodes
     data = {
         'sets': [s.asDict() for s in self.sets]
     }
     node = self.getOrCreateNode()
     # update name to resolve node creation differences
     self.name = getCollectionNameFromNode(node)
     meta.setMetaData(node, META_CLASSNAME, data)
Esempio n. 7
0
 def setUp(self):
     self.nodeA = pm.group(em=True)
     meta.setMetaData(self.nodeA, 'ClassA', 'A')
     self.nodeB = pm.group(em=True)
     meta.setMetaData(self.nodeB, 'ClassB', 'B')
     meta.setMetaData(self.nodeB, 'ClassD', 'D')
     self.nodeC = pm.group(em=True)
     meta.setMetaData(self.nodeC, 'ClassC', 'C')
     meta.setMetaData(self.nodeC, 'ClassD', 'D')
Esempio n. 8
0
    def saveToNode(self, node, create=False):
        """
        Save this Blueprint to a node, creating a new node if desired.

        Args:
            node: A PyNode or node name
            create: A bool, whether to create the node if it doesn't exist
        """
        if create and not pm.cmds.objExists(node):
            node = pm.cmds.createNode('network', n=node)
        data = self.serialize()
        meta.setMetaData(node, BLUEPRINT_METACLASS, data)
Esempio n. 9
0
def createSpace(node, name):
    """
    Create a new space

    Args:
        node: A PyNode or string node name
        name: A string name of the space to create
    """
    data = {
        'name': name,
    }
    meta.setMetaData(node, SPACE_METACLASS, data)
Esempio n. 10
0
def setMirroringData(node, otherNode):
    """
    Set the mirroring data for a node

    Args:
        node: A node on which to set the mirroring data
        otherNode: The counterpart node to be stored in the mirroring data
    """
    data = {
        'otherNode': otherNode,
    }
    meta.setMetaData(node, MIRROR_METACLASS, data, undoable=True)
Esempio n. 11
0
def createRigNode(name):
    """
    Create and return a new Rig node

    Args:
        name: A str name of the rig
    """
    if cmds.objExists(name):
        raise ValueError(
            "Cannot create rig, node already exists: {0}".format(name))
    node = pm.group(name=name, em=True)
    for a in ('tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz'):
        node.attr(a).setLocked(True)
        node.attr(a).setKeyable(False)
    # set initial meta data for the rig
    meta.setMetaData(node, RIG_METACLASS, {'name': name})
    return node
Esempio n. 12
0
    def saveToNode(self, node, create=False):
        """
        Save this Blueprint to a node, creating a new node if desired.

        Args:
            node (PyNode or str): A node or node name
            create (bool): If true, create the node if necessary

        Returns:
            The node on which the blueprint was saved
        """
        if create and not cmds.objExists(node):
            node = cmds.createNode('network', n=node)
        data = self.serialize()
        st = time.time()
        meta.setMetaData(node, BLUEPRINT_METACLASS, data, replace=True)
        et = time.time()
        LOG.debug('blueprint save time: {0}s'.format(et - st))
        return node
Esempio n. 13
0
def setLinkMetaData(node, linkData):
    """
    Set the metadata for a linked node
    """
    meta.setMetaData(node, className=LINK_METACLASS, data=linkData)
Esempio n. 14
0
 def setLinkMetaData(self, node, linkData):
     """
     Set the metadata for a linked node
     """
     linkData['type'] = self.linkType
     meta.setMetaData(node, className=LINK_METACLASS, data=linkData)
Esempio n. 15
0
 def test_removeData(self):
     meta.setMetaData(self.node, 'myMetaClass', None)
     self.assertTrue(meta.isMetaNode(self.node))
     result = meta.removeMetaData(self.node)
     self.assertTrue(result)
     self.assertFalse(meta.isMetaNode(self.node))
Esempio n. 16
0
 def test_multiClassData(self):
     cls1 = 'myMetaClass1'
     cls2 = 'myMetaClass2'
     meta.setMetaData(self.node, cls1, None)
     meta.setMetaData(self.node, cls2, None)
     self.assertEqual(meta.getMetaData(self.node), {cls1: None, cls2: None})
Esempio n. 17
0
 def test_setAndGetData(self):
     setData = ['myData', {'a': 1, 'b': 2}, ('x', 'y', 'z')]
     className = 'myMetaClass'
     meta.setMetaData(self.node, className, setData)
     self.assertEqual(meta.getMetaData(self.node, className), setData)
Esempio n. 18
0
    def run(self):
        # retrieve mid and root joints
        midJoint = self.endJoint.getParent()
        rootJoint = midJoint.getParent()

        # duplicate joints for ik chain
        ikJointNameFmt = '{0}_ik'
        ikjnts = pulse.nodes.duplicateBranch(rootJoint,
                                             self.endJoint,
                                             nameFmt=ikJointNameFmt)
        for j in ikjnts:
            # TODO: debug settings for build actions
            j.v.set(True)
        rootIkJoint = ikjnts[0]
        midIkJoint = ikjnts[1]
        endIkJoint = ikjnts[2]

        # parent ik joints to root control
        rootIkJoint.setParent(self.rootCtl)

        # create ik and hook up pole object and controls
        handle, effector = pm.ikHandle(name="{0}_ikHandle".format(endIkJoint),
                                       startJoint=rootIkJoint,
                                       endEffector=endIkJoint,
                                       solver="ikRPsolver")

        # add twist attr to end control
        self.endCtlIk.addAttr('twist', at='double', k=1)
        self.endCtlIk.twist >> handle.twist

        # connect mid ik ctl (pole vector)
        pm.poleVectorConstraint(self.midCtlIk, handle)

        # parent ik handle to end control
        handle.setParent(self.endCtlIk)

        # TODO: use pick matrix and mult matrix to combine location from ik system with rotation/scale of ctl
        # constraint end joint scale and rotation to end control
        pm.orientConstraint(self.endCtlIk, endIkJoint, mo=True)
        pm.scaleConstraint(self.endCtlIk, endIkJoint, mo=True)

        # setup ikfk switch attr (integer, not blend)
        self.rootCtl.addAttr("ik",
                             min=0,
                             max=1,
                             at='short',
                             defaultValue=1,
                             keyable=1)
        ikAttr = self.rootCtl.attr("ik")

        # create choices for world matrix from ik and fk targets
        rootChoice = pulse.utilnodes.choice(ikAttr, self.rootCtl.wm,
                                            rootIkJoint.wm)
        rootChoice.node().rename(f"{rootJoint.nodeName()}_ikfk_choice")
        midChoice = pulse.utilnodes.choice(ikAttr, self.midCtlFk.wm,
                                           midIkJoint.wm)
        midChoice.node().rename(f"{midJoint.nodeName()}_ikfk_choice")
        endChoice = pulse.utilnodes.choice(ikAttr, self.endCtlFk.wm,
                                           endIkJoint.wm)
        endChoice.node().rename(f"{self.endJoint.nodeName()}_ikfk_choice")

        # connect the target matrices to the joints
        pulse.nodes.connectMatrix(rootChoice, rootJoint,
                                  pulse.nodes.ConnectMatrixMethod.SNAP)
        pulse.nodes.connectMatrix(midChoice, midJoint,
                                  pulse.nodes.ConnectMatrixMethod.SNAP)
        pulse.nodes.connectMatrix(endChoice, self.endJoint,
                                  pulse.nodes.ConnectMatrixMethod.SNAP)

        # connect visibility
        self.midCtlIk.v.setLocked(False)
        self.endCtlIk.v.setLocked(False)
        ikAttr >> self.midCtlIk.v
        ikAttr >> self.endCtlIk.v

        fkAttr = pulse.utilnodes.reverse(ikAttr)
        self.midCtlFk.v.setLocked(False)
        self.endCtlFk.v.setLocked(False)
        fkAttr >> self.midCtlFk.v
        fkAttr >> self.endCtlFk.v

        # add connecting line shape
        if self.addPoleLine:
            # keep consistent color overrides for the mid ctl
            color = pulse.nodes.getOverrideColor(self.midCtlIk)
            pulse.controlshapes.createLineShape(midIkJoint, self.midCtlIk,
                                                self.midCtlIk)
            if color:
                pulse.nodes.setOverrideColor(self.midCtlIk, color)

        # cleanup
        handle.v.set(False)
        for jnt in ikjnts:
            # TODO: lock attrs
            jnt.v.set(False)

        # add metadata to controls
        ikfk_ctl_data = {
            'root_fk_ctl': self.rootCtl,
            'mid_fk_ctl': self.midCtlFk,
            'end_fk_ctl': self.endCtlFk,
            'root_ik_ctl': self.rootCtl,
            'mid_ik_ctl': self.midCtlIk,
            'end_ik_ctl': self.endCtlIk,
            'end_joint': self.endJoint,
        }

        ikfk_ctls = {
            self.rootCtl, self.midCtlIk, self.endCtlIk, self.midCtlFk,
            self.endCtlFk
        }
        if self.extraControls:
            ikfk_ctls.update(self.extraControls)
        for ctl in ikfk_ctls:
            meta.setMetaData(ctl, IKFK_CONTROL_METACLASS, ikfk_ctl_data)
Esempio n. 19
0
def link(leader, follower):
    """
    Link the follower to a leader
    """
    meta.setMetaData(follower, className=LINK_METACLASS, data=leader)
Esempio n. 20
0
def setupSpaceConstraint(node,
                         spaceNames,
                         follower=None,
                         useOffsetMatrix=True):
    """
    Set up a node to be constrained for a space switch, but do not
    actually connect it to the desired spaces until `connectSpaceConstraint` is called.
    This is necessary because the transforms that represent each space may not
    have been defined yet, but the desire to constrain to them by space name can be expressed
    ahead of time.

    Args:
        node (PyNode): The node that will contain space switching attrs
        spaceNames (str list): The names of all spaces to be applied
        follower (PyNode): If given, the node that will be constrained, otherwise
            `node` will be used. Useful when wanting to create the space constrain attributes
            on an animation control, but connect the actual constraint to a parent transform
        useOffsetMatrix (bool): When true, will connect to the offsetParentMatrix
            of the follower node, instead of directly into the translate, rotate, and scale.
            This also eliminates the necessity for a decompose matrix node.
    """
    if useOffsetMatrix and cmds.about(api=True) < 20200000:
        # not supported before Maya 2020
        useOffsetMatrix = False

    if not follower:
        follower = node

    # setup space switching attr
    if not node.hasAttr(SPACESWITCH_ATTR):
        enumNames = ':'.join(spaceNames)
        node.addAttr(SPACESWITCH_ATTR, at='enum', en=enumNames)
        spaceAttr = node.attr(SPACESWITCH_ATTR)
        spaceAttr.setKeyable(True)
    else:
        spaceAttr = node.attr(SPACESWITCH_ATTR)

    nodeName = node.nodeName()

    offsetChoiceName = nodeName + '_spaceOffset_choice'
    spaceChoiceName = nodeName + '_space_choice'
    multMatrixName = nodeName + '_space_mmtx'
    decompName = nodeName + '_space_decomp'

    # create utility nodes
    offsetChoice = pm.shadingNode('choice', n=offsetChoiceName, asUtility=True)
    spaceChoice = pm.shadingNode('choice', n=spaceChoiceName, asUtility=True)
    utilnodes.loadMatrixPlugin()
    multMatrix = pm.shadingNode('multMatrix', n=multMatrixName, asUtility=True)
    if not useOffsetMatrix:
        decomp = pm.shadingNode('decomposeMatrix',
                                n=decompName,
                                asUtility=True)

    # setup connections
    spaceAttr >> offsetChoice.selector
    spaceAttr >> spaceChoice.selector
    offsetChoice.output >> multMatrix.matrixIn[0]
    spaceChoice.output >> multMatrix.matrixIn[1]
    # follower.pim >> multMatrix.matrixIn[2]
    if not useOffsetMatrix:
        multMatrix.matrixSum >> decomp.inputMatrix
    # final connection to the follower occurs
    # during connectSpaceConstraint.

    spaceData = []
    # native space indeces always take priority,
    # which means dynamic spaces may be adversely affected
    # if the native spaces change on a published rig
    # TODO: ability to reindex dynamic spaces
    for i, spaceName in enumerate(spaceNames):
        spaceData.append({
            'name': spaceName,
            # TODO: is `switch` needed anymore?
            'switch': None,
            'index': i,
        })

    data = {
        # native spaces in this constraint
        'spaces': spaceData,
        # dynamic spaces (added during animation), which may be
        # from the native rig, or from an external one
        'dynamicSpaces': [],
        # transform that is actually driven by the space constraint
        'follower': follower,
        # the utility nodes that make up the space constraint
        'offsetChoice': offsetChoice,
        'spaceChoice': spaceChoice,
        'multMatrix': multMatrix,
        'useOffsetMatrix': useOffsetMatrix,
    }
    if not useOffsetMatrix:
        # decomp only exists when not using offset matrix
        data['decompose'] = decomp

    meta.setMetaData(node, SPACECONSTRAINT_METACLASS, data)
Esempio n. 21
0
    def run(self):
        shouldCreateOffset = False
        if self.createFollowerOffset == 0:
            # Always
            shouldCreateOffset = True
        elif self.createFollowerOffset == 1 and self.follower.nodeType(
        ) != 'joint':
            # Exclude Joints and the follower is not a joint
            shouldCreateOffset = True

        _follower = self.follower
        _toeFollower = self.toeFollower
        if shouldCreateOffset:
            _follower = pulse.nodes.createOffsetTransform(self.follower)
            _toeFollower = pulse.nodes.createOffsetTransform(self.toeFollower)

        # TODO(bsayre): expose as option
        self.useCustomAttrs = False

        if self.useCustomAttrs:
            # create 'lift' and 'ballToe' blend attrs
            self.control.addAttr("ballToe",
                                 min=0,
                                 max=1,
                                 at='double',
                                 defaultValue=0,
                                 keyable=1)
            ballToeAttr = self.control.attr('ballToe')
            self.control.addAttr("lift",
                                 min=0,
                                 max=1,
                                 at='double',
                                 defaultValue=0,
                                 keyable=1)
            liftAttr = self.control.attr('lift')

            lockedAttrs = ['tx', 'ty', 'tz', 'sx', 'sy', 'sz']
        else:
            # use tx and tz to control ball toe and lift blend
            ballToeAttr = self.control.tx
            liftAttr = self.control.tz
            # configure magic control
            # limit translate attrs and use them to drive blends
            pm.transformLimits(self.control,
                               tx=(0, 1),
                               tz=(0, 1),
                               etz=(True, True),
                               etx=(True, True))
            lockedAttrs = ['ty', 'sx', 'sy', 'sz']

        # lockup attributes on the magic control
        for attrName in lockedAttrs:
            attr = self.control.attr(attrName)
            attr.setKeyable(False)
            attr.showInChannelBox(False)
            attr.setLocked(True)

        # transform that will contain the final results of planted targets
        if self.plantedTarget:
            planted_tgt = self.plantedTarget
        else:
            planted_tgt = pm.group(em=True,
                                   p=self.control,
                                   n='{0}_mf_anklePlanted_tgt'.format(
                                       self.follower.nodeName()))
        # use liftControl directly as target
        lifted_tgt = self.liftControl

        # toe tgt is only used for the toe follower, not the main ankle follower
        # (keeps toe control fully locked when using ball pivot)
        toeDown_tgt = pm.group(em=True,
                               p=self.toePivot,
                               n='{0}_mf_toeDown_tgt'.format(
                                   self.toeFollower.nodeName()))
        toeUp_tgt = pm.group(em=True,
                             p=_toeFollower.getParent(),
                             n='{0}_mf_toeUp_tgt'.format(
                                 self.toeFollower.nodeName()))
        # ball pivot will contain result of both toe and ball pivot
        ballToe_tgt = pm.group(em=True,
                               p=self.ballPivot,
                               n='{0}_mf_ballToe_tgt'.format(
                                   self.follower.nodeName()))
        heel_tgt = pm.group(em=True,
                            p=self.heelPivot,
                            n='{0}_mf_heel_tgt'.format(
                                self.follower.nodeName()))

        followerMtx = pulse.nodes.getWorldMatrix(self.follower)
        toeFollowerMtx = pulse.nodes.getWorldMatrix(self.toeFollower)

        # update pivots to match world rotation of control and create
        # offset so that direct connect rotations will match up
        for node in (self.toePivot, self.ballPivot, self.heelPivot):
            followerMtx.translate = (0, 0, 0)
            followerMtx.scale = (1, 1, 1)
            pulse.nodes.setWorldMatrix(node, followerMtx)
            pulse.nodes.createOffsetTransform(node)
            if node == self.toePivot:
                # after orienting toe pivot, re-parent ballPivot
                self.ballPivot.setParent(self.toePivot)

        # update toe target transforms to match toe follower transform
        for node in (toeDown_tgt, toeUp_tgt):
            pulse.nodes.setWorldMatrix(node, toeFollowerMtx)

        # update target transforms to match follower transform
        # (basically preserves offsets on the follower)
        for node in (ballToe_tgt, heel_tgt):  # , ankle_tgt):
            pulse.nodes.setWorldMatrix(node, followerMtx)

        # connect direct rotations to heel pivot done after creating targets so that
        # the targets WILL move to reflect magic control non-zero rotations (if any)
        self.control.r >> self.heelPivot.r

        # connect blended rotation to toe / ball pivots
        # use ballToe attr to drive the blend (0 == ball, 1 == toe)
        toeRotBlendAttr = pulse.utilnodes.blend2(self.control.r, (0, 0, 0),
                                                 ballToeAttr)
        ballRotBlendAttr = pulse.utilnodes.blend2((0, 0, 0), self.control.r,
                                                  ballToeAttr)
        toeRotBlendAttr >> self.toePivot.r
        ballRotBlendAttr >> self.ballPivot.r

        # hide and lock the now-connected pivots
        for node in (self.toePivot, self.ballPivot, self.heelPivot):
            node.t.lock()
            node.r.lock()
            node.s.lock()
            node.v.set(False)

        # create condition to switch between ball/toe and heel pivots
        # TODO(bsayre): use dot-product towards up to determine toe vs heel
        isToeRollAttr = pulse.utilnodes.condition(self.control.ry, 0, [1], [0],
                                                  2)
        plantedMtxAttr = pulse.utilnodes.choice(isToeRollAttr, heel_tgt.wm,
                                                ballToe_tgt.wm)

        # connect final planted ankle matrix to ankle target transform
        pulse.nodes.connectMatrix(plantedMtxAttr, planted_tgt,
                                  pulse.nodes.ConnectMatrixMethod.SNAP)
        planted_tgt.t.lock()
        planted_tgt.r.lock()
        planted_tgt.s.lock()
        planted_tgt.v.setKeyable(False)
        # create matrix blend between planted and lifted targets
        # use lift attr to drive the blend (0 == planted, 1 == lifted)
        plantedLiftedBlendAttr = self.createMatrixBlend(
            planted_tgt.wm, lifted_tgt.wm, liftAttr,
            '{0}_mf_plantedLiftedBlend'.format(self.follower.nodeName()))

        # connect final matrix to follower
        # TODO(bsayre): this connect eliminates all transform inheritance, is
        #   world space control what we want? or do we need to inject offsets and
        #   allow parent transforms to come through
        pulse.nodes.connectMatrix(plantedLiftedBlendAttr, _follower,
                                  pulse.nodes.ConnectMatrixMethod.SNAP)

        # create toe up/down matrix blend, (0 == toe-up, 1 == toe-down/ball pivot)
        # in order to do this, reverse ballToe attr, then multiply by isToeRoll
        # to ensure toe-down is not active when not using toe pivots
        # reverse ballToeAttr, so that 1 == toe-down/ball
        ballToeReverseAttr = pulse.utilnodes.reverse(ballToeAttr)
        # multiply by isToe to ensure ball not active while using heel pivot
        isToeAndBallAttr = pulse.utilnodes.multiply(ballToeReverseAttr,
                                                    isToeRollAttr)
        # multiply by 1-liftAttr to ensure ball not active while lifting
        liftReverseAttr = pulse.utilnodes.reverse(liftAttr)
        toeUpDownBlendAttr = pulse.utilnodes.multiply(isToeAndBallAttr,
                                                      liftReverseAttr)
        ballToeMtxBlendAttr = self.createMatrixBlend(
            toeUp_tgt.wm, toeDown_tgt.wm, toeUpDownBlendAttr,
            '{0}_mf_toeUpDownBlend'.format(self.toeFollower.nodeName()))

        # connect final toe rotations to toeFollower
        # TODO(bsayre): parent both tgts to ankle somehow to prevent locking
        pulse.nodes.connectMatrix(ballToeMtxBlendAttr, _toeFollower,
                                  pulse.nodes.ConnectMatrixMethod.SNAP)

        # add meta data to controls
        ctlData = {
            'plantedTarget': planted_tgt,
            'liftControl': self.liftControl,
        }
        meta.setMetaData(self.control, MAGIC_FEET_CTL_METACLASSNAME, ctlData,
                         False)

        liftCtlData = {'control': self.control}
        meta.setMetaData(self.liftControl, MAGIC_FEET_LIFT_CTL_METACLASSNAME,
                         liftCtlData, False)
Esempio n. 22
0
def prepareSpaceConstraint(node, follower, spaceNames):
    """
    Prepare a new space constraint. This sets up the constrained
    node, but does not connect it to the desired spaces until
    `createSpaceConstraint` is called.

    Args:
        node (PyNode): The node that will contain space switching attrs
        follower (PyNode): The node that will be constrained, can be `node`,
            or more commonly, a parent (offset) of `node`.
        spaceNames (str list): The names of all spaces to be applied
    """

    # setup space switching attr
    if not node.hasAttr(SPACESWITCH_ATTR):
        enumNames = ':'.join(spaceNames)
        node.addAttr(SPACESWITCH_ATTR, at='enum', en=enumNames)
        spaceAttr = node.attr(SPACESWITCH_ATTR)
        spaceAttr.setKeyable(True)
    else:
        spaceAttr = node.attr(SPACESWITCH_ATTR)

    nodeName = node.nodeName()

    offsetChoiceName = nodeName + '_space_offset_choice'
    spaceChoiceName = nodeName + '_space_choice'
    multMatrixName = nodeName + '_space_mmtx'
    decompName = nodeName + '_space_decomp'

    # create utility nodes
    offsetChoice = pm.shadingNode('choice', n=offsetChoiceName, asUtility=True)
    spaceChoice = pm.shadingNode('choice', n=spaceChoiceName, asUtility=True)
    pulse.utilnodes.loadMatrixPlugin()
    multMatrix = pm.shadingNode('multMatrix', n=multMatrixName, asUtility=True)
    decomp = pm.shadingNode('decomposeMatrix', n=decompName, asUtility=True)

    # setup connections
    spaceAttr >> offsetChoice.selector
    spaceAttr >> spaceChoice.selector
    offsetChoice.output >> multMatrix.matrixIn[0]
    spaceChoice.output >> multMatrix.matrixIn[1]
    follower.pim >> multMatrix.matrixIn[2]
    multMatrix.matrixSum >> decomp.inputMatrix
    # connection from decomp output to the follower trs
    # occurs after the actual space nodes are connected

    spaceData = []
    # native space indeces always take priority,
    # which means dynamic spaces may be adversely affected
    # if the native spaces change on a published rig
    # TODO: ability to reindex dynamic spaces
    for i, spaceName in enumerate(spaceNames):
        spaceData.append({
            'name': spaceName,
            # TODO: is `switch` needed anymore?
            'switch': None,
            'index': i,
        })

    data = {
        # native spaces in this constraint
        'spaces': spaceData,
        # dynamic spaces (added during animation), which may be
        # from the native rig, or from an external one
        'dynamicSpaces': [],
        # transform that is actually driven by the space constraint
        'follower': follower,
        # the utility nodes that make up the space constraint
        'offsetChoice': offsetChoice,
        'spaceChoice': spaceChoice,
        'multMatrix': multMatrix,
        'decompose': decomp,
    }

    meta.setMetaData(node, SPACECONSTRAINT_METACLASS, data)
Esempio n. 23
0
def prepareSpaceConstraint(node, follower, spaceNames):
    """
    Prepare a new space constraint. This sets up the constrained
    node, but does not connect it to the desired spaces until
    `createSpaceConstraint` is called.

    Args:
        node (PyNode): The node that will contain space switching attrs
        follower (PyNode): The node that will be constrained, can be
            different than the control.
        spaceNames (str list): The names of all spaces to be applied
    """

    data = {
        # native spaces in this constraint
        'spaces': [],
        # dynamic spaces (added during animation), which may be
        # from the native rig, or from an external one
        'dynamicSpaces': [],
        # transform that is actually constrained
        'constrainedNode': follower,
        # the addition utility nodes internal to the constraint
        'translateAdd': None,
        'rotateAdd': None,
        'scaleAdd': None,
    }

    # native space indeces always take priority,
    # which means dynamic spaces may be adversely affected
    # if the native spaces change on a published rig
    # TODO: ability to reindex dynamic spaces
    for i, spaceName in enumerate(spaceNames):
        data['spaces'].append({
            'name': spaceName,
            'switch': None,
            'index': i,
        })

    # create addition utilities for transform attrs
    addUtilities = [
        ('t', 'translateAdd'),
        ('r', 'rotateAdd'),
        ('s', 'scaleAdd'),
    ]

    for attrName, metaKey in addUtilities:
        # create addition utility
        defaultVal = 1 if (attrName == 's') else 0
        # create add node with first item set to defaults
        addNode = pulse.utilnodes.add(
            (defaultVal, defaultVal, defaultVal)).node()
        addNode.rename('{0}_add{1}'.format(
            node.nodeName(), attrName.upper()))

        # connect to the follower node
        addNode.output3D >> follower.attr(attrName)

        # store reference to utility in meta data
        data[metaKey] = addNode

    meta.setMetaData(node, SPACECONSTRAINT_METACLASS, data)

    # setup space switching attr
    if not node.hasAttr(SPACESWITCH_ATTR):
        enumNames = ':'.join(spaceNames)
        node.addAttr(SPACESWITCH_ATTR, at='enum', en=enumNames)
        sattr = node.attr(SPACESWITCH_ATTR)
        sattr.setKeyable(True)
    def run(self):
        # add attrs
        # ---------

        self.control.addAttr('roll', at='double', keyable=True)
        roll = self.control.attr('roll')

        self.control.addAttr('tilt', at='double', keyable=True)
        tilt = self.control.attr('tilt')

        self.control.addAttr('toeSwivel', at='double', keyable=True)
        toeSwivel = self.control.attr('toeSwivel')

        self.control.addAttr('heelSwivel', at='double', keyable=True)
        heelSwivel = self.control.attr('heelSwivel')

        self.control.addAttr('bendLimit',
                             at='double',
                             keyable=True,
                             defaultValue=self.bendLimitDefault,
                             minValue=0)
        bend_limit = self.control.attr('bendLimit')

        self.control.addAttr('straightAngle',
                             at='double',
                             keyable=True,
                             defaultValue=self.straightAngleDefault,
                             minValue=0)
        straight_angle = self.control.attr('straightAngle')

        # keep evaluated Bend Limit below Straight Angle to avoid zero division and flipping problems
        clamped_bend_limit = utilnodes.min_float(
            bend_limit, utilnodes.subtract(straight_angle, 0.001))

        # setup hierarchy
        # ---------------

        # control > heel > outerTilt > innerTilt > toe > ball
        offset_connect_method = pulse.nodes.ConnectMatrixMethod.CREATE_OFFSET
        pulse.nodes.connectMatrix(self.control.wm, self.heelPivot,
                                  offset_connect_method)
        pulse.nodes.connectMatrix(self.heelPivot.wm, self.outerTiltPivot,
                                  offset_connect_method)
        pulse.nodes.connectMatrix(self.outerTiltPivot.wm, self.innerTiltPivot,
                                  offset_connect_method)
        pulse.nodes.connectMatrix(self.innerTiltPivot.wm, self.toePivot,
                                  offset_connect_method)
        pulse.nodes.connectMatrix(self.toePivot.wm, self.ballPivot,
                                  offset_connect_method)

        # ballPivot > ankleFollower
        pulse.nodes.connectMatrix(self.ballPivot.wm, self.ankleFollower,
                                  offset_connect_method)

        # toePivot > toeFollower
        pulse.nodes.connectMatrix(self.toePivot.wm, self.toeFollower,
                                  offset_connect_method)

        # calculate custom foot attrs
        # ---------------------------

        # drive heel rotation with negative footRoll
        heel_roll = utilnodes.clamp(roll, -180, 0)
        heel_roll >> self.heelPivot.rotateX

        # get percentage blend between 0..BendLimit and BendLimit..StraightAngle
        zero_to_bend_pct = utilnodes.setRange(roll, 0, 1, 0,
                                              clamped_bend_limit)
        bend_to_toe_pct = utilnodes.setRange(roll, 0, 1, clamped_bend_limit,
                                             straight_angle)

        # multiply pcts to get ball rotation curve that goes up towards BendLimit,
        # then back down towards StraightAngle
        neg_bend_to_toe_pct = utilnodes.reverse(bend_to_toe_pct)
        ball_roll_factor = utilnodes.multiply(zero_to_bend_pct,
                                              neg_bend_to_toe_pct)
        ball_roll = utilnodes.multiply(ball_roll_factor, clamped_bend_limit)
        ball_roll >> self.ballPivot.rotateX

        # multiply bend pct to get toe rotation coming from bend, then
        # add in extra rotation to add after straight angle is reached, so that foot roll isn't clamped
        toe_roll_from_blend = utilnodes.multiply(bend_to_toe_pct,
                                                 straight_angle)
        toe_roll_excess = utilnodes.clamp(
            utilnodes.subtract(roll, straight_angle), 0, 180)
        toe_roll = utilnodes.add(toe_roll_from_blend, toe_roll_excess)
        toe_roll >> self.toePivot.rotateX

        # TODO: mirror swivel values (since roll cannot be flipped, we can't just re-orient the pivot nodes)

        # swivels are mostly direct, toe swivel positive values should move the heel outwards
        utilnodes.multiply(toeSwivel, -1) >> self.toePivot.rotateZ
        heelSwivel >> self.heelPivot.rotateZ

        # inner and outer tilt simply need clamped connections
        outer_tilt = utilnodes.clamp(tilt, 0, 180)
        outer_tilt >> self.outerTiltPivot.rotateY
        inner_tilt = utilnodes.clamp(tilt, -180, 0)
        inner_tilt >> self.innerTiltPivot.rotateY

        # lock up nodes
        # -------------

        for pivot in [
                self.toePivot, self.ballPivot, self.outerTiltPivot,
                self.innerTiltPivot, self.heelPivot
        ]:
            pivot.v.set(False)
            for a in ('tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz'):
                attr = pivot.attr(a)
                attr.setLocked(True)
                attr.setKeyable(False)

        # setup meta data
        # ---------------

        foot_ctl_data = {
            'foot_ctl': self.control,
            'ball_ctl': self.ballControl,
            'ankle_follower': self.ankleFollower,
            'toe_follower': self.toeFollower,
        }

        meta_nodes = {self.control, self.ballControl}
        meta_nodes.update(self.extraControls)
        for node in meta_nodes:
            meta.setMetaData(node, FOOT_CTL_METACLASSNAME, foot_ctl_data)