def setPose(name, node=None, ns=""): """ Reads the attributes off all objects in the specified set (name) and sets those as the set values :param name str: name of the pose to set :param node str: if specified, only applies the pose on the specified node """ if name == 'default': raise NameError("Can't override the default pose!") setName = ns + names.nodeName( 'set', names.getDescriptor(name), side=names.getSide(name)) if node: node = node.replace(names.getNS(node), "") nodes = [ns + node] else: nodes = util.objectSet.getSetContents(setName) for node in nodes: if not cmds.objExists("{0}.{1}".format(node, name)): cmds.error("{0} is not part of the pose {1}".format(node, name)) data = json.loads(cmds.getAttr("{0}.{1}".format(node, name))) for attr in data: val = cmds.getAttr("{0}.{1}".format(node, attr)) data[attr] = val prettyData = json.dumps(data, sort_keys=True, indent=3, separators=(',', ': ')) cmds.setAttr("{0}.{1}".format(node, name), prettyData, type="string")
def loadPoses(nameList, path, ns=""): """ Tries to load all poses in specified location with specfied names :param nameList list : list of pose names to look for :param path str: folder to look in """ if not os.path.exists(path): raise NameError("The path '{0}' does not exist!".format(path)) if not cmds.objExists(ns + basePoseSet): raise ValueError("This scene does not have a base pose set!") for name in nameList: setName = ns + names.nodeName( 'set', names.getDescriptor(name), side=names.getSide(name)) if not cmds.objExists(setName): cmds.sets(setName, add=ns + basePoseSet) dataPath = os.path.join(path, name + "Pose.json") data = json.loads(open(dataPath).read()) for node in data: if not cmds.objExists("{0}.{1}".format(ns + node, name)): continue newData = {} for attr in data[node]: val = data[node][attr] newData[attr] = val prettyData = json.dumps(newData, sort_keys=True, indent=3, separators=(',', ': ')) cmds.setAttr("{0}.{1}".format(ns + node, name), prettyData, type="string")
def savePoses(nameList, path, ns=""): """ Saves out all poses in nameList to specified path. Creates a new file per pose :param nameList list: list of pose names to save out :param path str: path to save to, should be in the style "/test/path" and cannot be a file """ if not os.path.exists(path): raise NameError("The path '{0}' does not exist!".format(path)) for name in nameList: setName = names.nodeName('set', names.getDescriptor(name), side=names.getSide(name)) nodes = util.objectSet.getSetContents(setName) data = {} for node in nodes: data[node] = {} if not cmds.objExists("{0}.{1}".format(node, name)): cmds.error("{0} is not part of the pose {1}".format( node, name)) poseData = json.loads(cmds.getAttr("{0}.{1}".format(node, name))) for attr in poseData: val = cmds.getAttr("{0}.{1}".format(node, attr)) poseData[attr] = val data[node] = poseData prettyData = json.dumps(data, sort_keys=True, indent=3, separators=(',', ': ')) # Create a file name dataPath = os.path.join(path, name + "Pose.json") if os.path.exists(dataPath): # os.rename and shutil.move both throws errors, doing it the manual way... old = json.loads(open(dataPath).read()) f = open(dataPath + "OLD", 'w') prettyData = json.dumps(old, sort_keys=True, indent=3, separators=(',', ': ')) print >> f, prettyData f.close() os.remove(dataPath) # Save the data to disc. Running json.dumps and printing line by line returns an easily readable json f = open(dataPath, 'w') prettyData = json.dumps(data, sort_keys=True, indent=3, separators=(',', ': ')) print >> f, prettyData f.close()
def createPoses(node, nameList=[], extra=[], ns=""): """ Creates poses specified in the nameList on specified node. :param node str: node to add poses to :param nameList list: list of pose names :param extra list: appends the attributes specified in this list to the default list of attrs Example: from ooutdmaya.rigging.core.util import pose reload(pose) if not cmds.objExists('pose_set'): cmds.sets('pose_set') mySphere = cmds.polySphere(n="test1_geo", ch=0)[0] cmds.addAttr(mySphere, ln="testAttr", at="float", k=True, dv=10) pose.createPoses(mySphere, ['default', 'test'], ['testAttr]) """ if not node: return if not cmds.objExists(ns + node): return attrList = [ 'tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz', 'v', 'rotateOrder' ] if extra: attrList = attrList + extra data = {} for attr in attrList: val = cmds.getAttr("{0}.{1}".format(ns + node, attr)) data[attr] = val prettyData = json.dumps(data, sort_keys=True, indent=3, separators=(',', ': ')) for name in nameList: cmds.addAttr(ns + node, ln=name, dt="string", h=False) cmds.setAttr("{0}.{1}".format(ns + node, name), prettyData, type="string") setName = ns + names.nodeName( 'set', names.getDescriptor(name), side=names.getSide(name)) if cmds.objExists(setName): cmds.sets(ns + node, add=setName) else: newSet = util.lib.createNode('objectSet', name=ns + setName) cmds.sets(newSet, add=ns + basePoseSet) cmds.sets(ns + node, add=newSet)
def applyPose(name, node=None, ns=""): """ Applies the pose specified (name) to all objects in the relevant pose set :param name str: name of the pose to apply """ setName = ns + names.nodeName( 'set', names.getDescriptor(ns + name), side=names.getSide(ns + name)) print setName if node: node = node.replace(names.getNS(node), "") nodes = [ns + node] else: nodes = util.objectSet.getSetContents(setName) for node in nodes: if not cmds.objExists("{0}.{1}".format(node, name)): cmds.error("{0} is not part of the pose {1}".format(node, name)) data = json.loads(cmds.getAttr("{0}.{1}".format(node, name))) for attr in data: try: cmds.setAttr("{0}.{1}".format(node, attr), data[attr]) except: continue
def getDescriptor(*args, **kwargs): # cmds.warning('ooutdmaya.rigging.core.util.names.getDescriptor now DEPRECIATED... please use\n\tooutdmaya.common.util.names.getDescriptor instead') return names.getDescriptor(*args, **kwargs)
def run(self): """ """ # Run inherited functionality base.Base.run(self) # Create groups self.driverChain = self.chain #======================================================================= # self.driverChain = joint.duplicateSegment(self.chain, suffix='DRV', # searchReplaceSetList=[(names.getNS(self.chain[0]), '')]) #======================================================================= self.jawOffset = cmds.createNode('transform', n=names.nodeName('transform', '{0}{1}Offset'.format(self.prefix, self.name), self.side)) chainParent = cmds.listRelatives(self.chain[0], p=1)[0] if chainParent: easy.snapAlignScaleTo(chainParent, self.jawOffset) cmds.parent(self.jawOffset, self.grpSkeleton) cmds.parent(self.driverChain[0], self.jawOffset) key = 'jaw' self.buildCtl(key, lockAttrList=['scale'], movablePivot=False, keyAttrList=['rotateOrder']) ctlDict = self.ctls[key] easy.snapAlignTo(self.chain[-1], ctlDict['input']) self.sdkZeroGrp = cmds.createNode('transform', n=names.nodeName('transform', '{0}{1}SDKZero'.format(self.prefix, self.name), self.side)) if chainParent: cmds.parent(self.sdkZeroGrp, chainParent) cmds.parentConstraint(chainParent, self.ctls['jaw']['input'], mo=1, n=names.addModifier(self.ctls['jaw']['input'], 'parentConstraint')) cmds.delete(cmds.parentConstraint(self.chain[0], self.sdkZeroGrp)) cmds.delete(cmds.scaleConstraint(self.chain[0], self.sdkZeroGrp)) cmds.delete(cmds.aimConstraint(self.chain[-1], self.sdkZeroGrp, aimVector=[1, 0, 0], upVector=[0, 0, 1], worldUpType='objectrotation', worldUpVector=self.jawBaseUpVector, worldUpObject=chainParent)) # Create a rotation set driven key transform self.sdkRotGrp = cmds.duplicate(self.sdkZeroGrp, n=names.addModifier(self.sdkZeroGrp, 'transform', addSuffix="SetDrivenRot"))[0] # Create a position set driven key transform self.sdkPosGrp = cmds.duplicate(self.sdkZeroGrp, n=names.addModifier(self.sdkZeroGrp, 'transform', addSuffix="SetDrivenPos"))[0] # Parent all groups together cmds.parent(self.sdkRotGrp, self.sdkZeroGrp) cmds.parent(self.sdkPosGrp, self.sdkRotGrp) cmds.parent(self.jawOffset, self.sdkPosGrp) # Create a transform to drive our jaw transformations self.sdkDriverLoc = cmds.spaceLocator(n=names.nodeName('locator', "{0}{1}SdkDriver".format(self.prefix, self.name), ))[0] cmds.delete(cmds.pointConstraint(self.chain[-1], self.sdkDriverLoc)) cmds.hide(self.sdkDriverLoc) self.sdkDriverLocGrp = lib.createNode('transform', names.getDescriptor(self.sdkDriverLoc)) cmds.delete(cmds.pointConstraint(self.chain[-1], self.sdkDriverLocGrp)) cmds.parent(self.sdkDriverLoc, self.sdkDriverLocGrp) cmds.parent(self.sdkDriverLocGrp, self.grpMisc) # Set driven keys posTxAttrPlug = '{0}.translateZ'.format(self.sdkPosGrp) posTzAttrPlug = '{0}.translateX'.format(self.sdkPosGrp) rotTxAttrPlug = '{0}.translateZ'.format(self.sdkRotGrp) rotTzAttrPlug = '{0}.translateX'.format(self.sdkRotGrp) rotRyAttrPlug = '{0}.rotateY'.format(self.sdkRotGrp) rotRzAttrPlug = '{0}.rotateZ'.format(self.sdkRotGrp) # Protraction/Retraction driven keys cmds.setDrivenKeyframe(posTxAttrPlug, currentDriver='{0}.translateX'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(posTzAttrPlug, currentDriver='{0}.translateX'.format(self.sdkDriverLoc)) # rename sdk nodes posTxSdk = cmds.listConnections(posTxAttrPlug, s=1, d=0, type='animCurve')[0] posTxSdk = cmds.rename(posTxSdk, '{0}PosTx_SDK'.format(self.prefix)) posTzSdk = cmds.listConnections(posTzAttrPlug, s=1, d=0, type='animCurve')[0] posTzSdk = cmds.rename(posTzSdk, '{0}PosTz_SDK'.format(self.prefix)) # Protraction driven key cmds.setAttr('{0}.translateX'.format(self.sdkDriverLoc), self.sdkDict['protraction']['locValue']) cmds.setAttr(posTxAttrPlug, self.sdkDict['protraction']['posTx']) cmds.setAttr(posTzAttrPlug, self.sdkDict['protraction']['posTz']) cmds.setDrivenKeyframe(posTxAttrPlug, currentDriver='{0}.translateX'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(posTzAttrPlug, currentDriver='{0}.translateX'.format(self.sdkDriverLoc)) # Retraction driven key cmds.setAttr('{0}.translateX'.format(self.sdkDriverLoc), self.sdkDict['retraction']['locValue']) cmds.setAttr(posTxAttrPlug, self.sdkDict['retraction']['posTx']) cmds.setAttr(posTzAttrPlug, self.sdkDict['retraction']['posTz']) cmds.setDrivenKeyframe(posTxAttrPlug, currentDriver='{0}.translateX'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(posTzAttrPlug, currentDriver='{0}.translateX'.format(self.sdkDriverLoc)) cmds.setAttr('{0}.translateX'.format(self.sdkDriverLoc), 0) # Sideways driven keys cmds.setDrivenKeyframe(rotRzAttrPlug, currentDriver='{0}.translateZ'.format(self.sdkDriverLoc)) # rename sdk node rotRzSdk = cmds.listConnections(rotRzAttrPlug, s=1, d=0, type='animCurve')[0] rotRzSdk = cmds.rename(rotRzSdk, '{0}RotRz_SDK'.format(self.prefix)) # Sideways left driven key cmds.setAttr('{0}.translateZ'.format(self.sdkDriverLoc), self.sdkDict['sideLeft']['locValue']) cmds.setAttr(rotRzAttrPlug, self.sdkDict['sideLeft']['rotRz']) cmds.setDrivenKeyframe(rotRzAttrPlug, currentDriver='{0}.translateZ'.format(self.sdkDriverLoc)) # Sideways right driven key cmds.setAttr('{0}.translateZ'.format(self.sdkDriverLoc), self.sdkDict['sideRight']['locValue']) cmds.setAttr(rotRzAttrPlug, self.sdkDict['sideRight']['rotRz']) cmds.setDrivenKeyframe(rotRzAttrPlug, currentDriver='{0}.translateZ'.format(self.sdkDriverLoc)) cmds.setAttr('{0}.translateZ'.format(self.sdkDriverLoc), 0) # Open/Close driven keys cmds.setDrivenKeyframe(rotRyAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(rotTxAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(rotTzAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) # rename sdk node rotRySdk = cmds.listConnections(rotRyAttrPlug, s=1, d=0, type='animCurve')[0] rotRySdk = cmds.rename(rotRySdk, '{0}RotRy_SDK'.format(self.prefix)) rotTxSdk = cmds.listConnections(rotTxAttrPlug, s=1, d=0, type='animCurve')[0] rotTxSdk = cmds.rename(rotTxSdk, '{0}RotTx_SDK'.format(self.prefix)) rotTzSdk = cmds.listConnections(rotTzAttrPlug, s=1, d=0, type='animCurve')[0] rotTzSdk = cmds.rename(rotTzSdk, '{0}RotTz_SDK'.format(self.prefix)) # Open driven key cmds.setAttr('{0}.translateY'.format(self.sdkDriverLoc), self.sdkDict['open']['locValue']) cmds.setAttr(rotRyAttrPlug, self.sdkDict['open']['rotRy']) cmds.setAttr(rotTxAttrPlug, self.sdkDict['open']['rotTx']) cmds.setAttr(rotTzAttrPlug, self.sdkDict['open']['rotTz']) cmds.setDrivenKeyframe(rotRyAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(rotTxAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(rotTzAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) # Close driven key cmds.setAttr('{0}.translateY'.format(self.sdkDriverLoc), self.sdkDict['close']['locValue']) cmds.setAttr(rotRyAttrPlug, self.sdkDict['close']['rotRy']) cmds.setAttr(rotTxAttrPlug, self.sdkDict['close']['rotTx']) cmds.setAttr(rotTzAttrPlug, self.sdkDict['close']['rotTz']) cmds.setDrivenKeyframe(rotRyAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(rotTxAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) cmds.setDrivenKeyframe(rotTzAttrPlug, currentDriver='{0}.translateY'.format(self.sdkDriverLoc)) cmds.setAttr('{0}.translateY'.format(self.sdkDriverLoc), 0) # Set tangents to linear cmds.keyTangent(posTxSdk, itt='linear', ott='linear', e=1) cmds.keyTangent(posTzSdk, itt='linear', ott='linear', e=1) cmds.keyTangent(rotRzSdk, itt='linear', ott='linear', e=1) cmds.keyTangent(rotRySdk, itt='linear', ott='linear', e=1) cmds.keyTangent(rotTxSdk, itt='linear', ott='linear', e=1) cmds.keyTangent(rotTzSdk, itt='linear', ott='linear', e=1) cmds.connectAttr("{0}.{1}".format(ctlDict['ctl'][0], "translate"), "{0}.{1}".format(self.sdkDriverLoc, "translate")) # Set transform limits cmds.transformLimits(ctlDict['ctl'][0], tx=self.sdkDict['limits']['tx'], etx=[1, 1]) cmds.transformLimits(ctlDict['ctl'][0], ty=self.sdkDict['limits']['ty'], ety=[1, 1]) cmds.transformLimits(ctlDict['ctl'][0], tz=self.sdkDict['limits']['tz'], etz=[1, 1]) # ==================== # Curve shape data # ==================== if self.createCurveShapeDataSet: for k in self.ctls: if 'ctl' in self.ctls[k]: cmds.sets(self.ctls[k]['ctl'], add=self.curveShapeDataSet) self.importCurveShapeData()
def buildInterp(self, bias=0.0): """Build interpolation joint Note: Uses MayaMuscle cSmartConstraint to reliably interpolate a joints rotation between two other joints. Useful for skinning tweaks in areas where you need a softer bend, or maintain volume. Changing bias defines how the rotation is interpolated between the parent and target joint. Aim can be x, y, z, -x, -y, -z Args: bias (float): -1 follows parent, 1 follows target """ # Prep variables and check for prerequisites self.side = names.getSide(self.target) self.descriptor = names.getDescriptor(self.target) self.basename = "{0}_{1}_{2}".format(self.side, self.descriptor, "interp") if self.target == "": raise ValueError( "Tried building interpolation joint setup without a target.") if not cmds.pluginInfo('MayaMuscle', q=1, l=1): try: cmds.loadPlugin('MayaMuscle') except: raise RuntimeError('Unable to load the MayaMuscle plugin!') # Setup interp grp if not cmds.objExists("rigInterp_grp"): rigInterpGrp = cmds.group(n="rigInterp_grp", em=1, w=1) if self.parent: cmds.parent(rigInterpGrp, self.parent) elif cmds.objExists("misc_grp"): cmds.parent(rigInterpGrp, "misc_grp") # Create hierarchy masterGrp = cmds.group(em=1, n=names.addModifier(self.basename, 'grp'), p="rigInterp_grp") cmds.delete(cmds.parentConstraint(self.target, masterGrp, mo=0)) # Create driver joints cmds.select(d=1) cDrv = cmds.joint( n=names.addModifier(self.basename, 'joint', addSuffix='child')) cmds.select(d=1) pDrv = cmds.joint( n=names.addModifier(self.basename, 'joint', addSuffix='parent')) cmds.parent(cDrv, pDrv, masterGrp) # Identify parent joint pJnt = cmds.listRelatives(self.target, p=1, typ="joint")[0] # Match dummy joint placements and move rotation to jointOrientation cmds.delete(cmds.parentConstraint(pJnt, pDrv, mo=0)) cmds.delete(cmds.parentConstraint(self.target, cDrv, mo=0)) cmds.makeIdentity(pDrv, r=1, a=1) cmds.makeIdentity(cDrv, r=1, a=1) # Create parent constraints pParCon = cmds.parentConstraint(pJnt, pDrv, mo=0, n=names.addModifier( pDrv, 'parentConstraint')) cParCon = cmds.parentConstraint(self.target, cDrv, mo=0, n=names.addModifier( cDrv, 'parentConstraint')) # Create the interpolation joint, add helper group structure. cmds.select(d=1) self.intJnt = cmds.joint(n=names.addModifier(self.basename, 'joint')) intOffGrp = cmds.group(em=1, w=1, n=names.addModifier(self.intJnt, 'grp', addSuffix="offset")) intConGrp = cmds.group(intOffGrp, w=1, n=names.addModifier(self.intJnt, 'grp', addSuffix="constraint")) cmds.delete(cmds.parentConstraint(self.intJnt, intConGrp, mo=0)) cmds.xform(intOffGrp, ro=(0, 0, 0), t=(0, 0, 0), a=1, ws=1) cmds.parent(self.intJnt, intOffGrp) # Pretty it up a bit cmds.setAttr("{0}.overrideEnabled".format(self.intJnt), 1) cmds.setAttr("{0}.radius".format(self.intJnt), 1.5) cmds.setAttr("{0}.overrideColor".format(self.intJnt), 17) # Create muscle smart constraint and connect it up # Note on the cMuscleSmartConstraint: # It has a build in trigger with the ability to adjust the bias. So, say you want it to follow the shoulder more # at values above 90 degrees, but want it to interpolate 50/50 for all rotation values under that, you can do it # by setting a trigger rotation value of 90, and once it hits 90 degrees of rotation, it'll smoothly move bias more # towards the shoulder rather than the elbow. smartCon = cmds.createNode("cMuscleSmartConstraint", n=names.addModifier( self.basename, 'cMuscleSmartConstraint', addSuffix='constraint')) cmds.connectAttr("{0}.worldMatrix".format(pDrv), "{0}.worldMatrixA".format(smartCon)) cmds.connectAttr("{0}.worldMatrix".format(cDrv), "{0}.worldMatrixB".format(smartCon)) cmds.connectAttr("{0}.outTranslate".format(smartCon), "{0}.translate".format(self.intJnt)) cmds.connectAttr("{0}.outRotate".format(smartCon), "{0}.rotate".format(self.intJnt)) # Set axis based on the aim attribute axisList = ["x", "y", "z", "-x", "-y", "-z"] axisSet = 0 for z, i in enumerate(axisList): if self.aimAxis == i: cmds.setAttr("{0}.axis".format(smartCon), z) axisSet = 1 if not axisSet: cmds.setAttr("{0}.axis".format(smartCon), 0) # Promote bias attributes etc. to master node for manual adjustments cmds.addAttr(masterGrp, ln="customAttributes", nn="__Custom Attributes__", k=1) cmds.setAttr("{0}.customAttributes".format(masterGrp), l=1) cmds.addAttr( masterGrp, ln="axis", ) cmds.addAttr(masterGrp, ln="trigger", k=1, min=0, max=180) cmds.addAttr(masterGrp, ln="bias", k=1, min=-1, max=1) cmds.addAttr(masterGrp, ln="biasAdjust", k=1, min=-2, max=2) cmds.connectAttr("{0}.trigger".format(masterGrp), "{0}.triggerMin".format(smartCon)) cmds.connectAttr("{0}.bias".format(masterGrp), "{0}.bias".format(smartCon)) cmds.connectAttr("{0}.biasAdjust".format(masterGrp), "{0}.biasAdjust".format(smartCon)) # Clean up cmds.parent(intConGrp, masterGrp) cmds.setAttr("{0}.bias".format(masterGrp), bias) cmds.setAttr("{0}.inheritsTransform".format(masterGrp), 0) for i in [cDrv, pDrv]: cmds.setAttr("{0}.v".format(i), 0) attrList = ["sx", "sy", "sz"] for attr in attrList: cmds.setAttr("{0}.{1}".format(self.intJnt, attr), l=1)
def buildTwist(self, reverse=0, noflip=0, stretch=0): """Builds a twist joint setup. Note: Requires at least 3 joints to build (ie. shoulder - elbow - wrist). Example: test = HelperJoints() test.target = "joint2" test._buildTwist(noflip=1) Args: reverse (bool): Defines whether we're extracting the twist down the chain or up the chain. In a general sense, for a shoulder, reverse should be 0, for an ankle, reverse should be 1. noflip (bool): Sets the constraints to noflip mode, allowing 360 degree rotation stretch (bool): Toggles whether or not the joint length should be continuesly sampled to "stretch" twist joints along with it whatever it's build inbetween. """ # Default args if self.target == "": raise ValueError( "Tried building twist joint setup without a target (self.target)." ) if self.val == 0: raise ValueError( "Tried building twist joint with specified amount (self.val) being 0 (no twist joints)" ) self.side = names.getSide(self.target) self.descriptor = names.getDescriptor(self.target) if self.side: self.basename = "{0}_{1}_{2}".format(self.side, self.descriptor, "twist") else: self.basename = "{0}_{1}".format(self.descriptor, "twist") # Setup twist grp if not cmds.objExists("rigTwist_grp"): rigTwistGrp = cmds.group(n="rigTwist_grp", em=1, w=1) if self.parent: cmds.parent(rigTwistGrp, self.parent) elif cmds.objExists("misc_grp"): cmds.parent(rigTwistGrp, "misc_grp") # Check if matrix nodes are loaded if not cmds.pluginInfo('matrixNodes', q=1, l=1): try: cmds.loadPlugin('matrixNodes') except: raise RuntimeError('Unable to load the matrixNodes plugin!') # Could potentially remove type limitation from this section if needed in the future. if self.childTarget == "": self.childTarget = cmds.listRelatives(self.target, c=1, typ="joint")[0] if self.parentTarget == "": self.parentTarget = cmds.listRelatives(self.target, p=1, typ="joint")[0] if self.jntLen == 0: lenFound = 0 for i in ["x", "y", "z"]: if self.aimAxis == i: self.jntLen = cmds.getAttr("{0}.t{1}".format( self.target, i)) lenFound = 1 if not lenFound and not self.jntLen: raise ValueError( "Joint length was not specified and the length could not be retrieved automatically" ) masterGrp = cmds.group(n=names.addModifier(self.basename, 'grp', addSuffix='Master'), em=1, p="rigTwist_grp") cmds.setAttr("{0}.inheritsTransform".format(masterGrp), 0) # Create dummy setup dummyTarget = cmds.duplicate(self.target, po=1, n=names.addModifier( self.basename, 'joint', addSuffix='DummyTarget'))[0] dummyParent = cmds.duplicate(self.parentTarget, po=1, n=names.addModifier( self.basename, 'joint', addSuffix='DummyParent'))[0] dummyChild = cmds.duplicate(self.childTarget, po=1, n=names.addModifier( self.basename, 'joint', addSuffix='DummyChild'))[0] cmds.parent(dummyChild, dummyTarget) cmds.parent(dummyTarget, dummyParent) cmds.parent(dummyParent, masterGrp) matchList = [self.target, self.parentTarget, self.childTarget] for z, i in enumerate([dummyTarget, dummyParent, dummyChild]): cmds.parentConstraint(matchList[z], i, n=names.addModifier(i, 'parentConstraint'), mo=0) # Create slave joints slaveA = cmds.duplicate(dummyTarget, po=1, n=names.addModifier(self.basename, 'joint', addSuffix='SlaveA'))[0] if reverse: slaveB = cmds.duplicate(dummyParent, po=1, n=names.addModifier(self.basename, 'joint', addSuffix='SlaveB'))[0] else: slaveB = cmds.duplicate(dummyChild, po=1, n=names.addModifier(self.basename, 'joint', addSuffix='SlaveB'))[0] cmds.parent(slaveB, slaveA) # Redundant parenting, just to be sure if not cmds.listRelatives(slaveA, p=1, typ="joint")[0] == dummyParent: cmds.parent(slaveA, dummyParent) # Create IK to remove the main axis rpIK, rpEE = cmds.ikHandle(n=names.addModifier(self.basename, 'ikHandle', addSuffix='Ext'), sj=slaveA, ee=slaveB) rpEE = cmds.rename( rpEE, names.addModifier(self.basename, 'endEffector', addSuffix='Ext')) cmds.parent(rpIK, dummyTarget) cmds.setAttr("{0}.poleVectorX".format(rpIK), 0) cmds.setAttr("{0}.poleVectorY".format(rpIK), 0) cmds.setAttr("{0}.poleVectorZ".format(rpIK), 0) # Create space locators that'll help store the current twist value (and visualize for debugging) twistValA = cmds.spaceLocator( n=names.addModifier(self.basename, 'locator', addSuffix='A'))[0] twistValB = cmds.spaceLocator( n=names.addModifier(self.basename, 'locator', addSuffix='B'))[0] cmds.parent(twistValA, twistValB, dummyTarget) attrList = [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"] for attr in attrList: cmds.setAttr(twistValA + attr, 0) cmds.setAttr(twistValB + attr, 0) valAOri = cmds.orientConstraint(slaveA, twistValA, n=names.addModifier(self.basename, 'orientConstraint', addSuffix='A'))[0] valBOri = cmds.orientConstraint(slaveA, dummyTarget, twistValB, n=names.addModifier(self.basename, 'orientConstraint', addSuffix='B'))[0] if noflip: cmds.setAttr("{0}.interpType".format(valAOri), 0) cmds.setAttr("{0}.interpType".format(valBOri), 0) # Create transform for easy attribute access and prep twistGrp = cmds.group(n=names.addModifier(self.basename, 'grp', addSuffix='Ext'), em=1, w=1) cmds.parent(twistGrp, masterGrp) attrList = [ ".tx", ".ty", ".tz", ".rx", ".ry", ".rz", ".sx", ".sy", ".sz", ".v" ] for attr in attrList: cmds.setAttr(twistGrp + attr, l=1, k=0) cmds.addAttr(twistGrp, ln="twistValA", k=1) cmds.addAttr(twistGrp, ln="twistValB", k=1) cmds.connectAttr("{0}.rx".format(twistValA), "{0}.twistValA".format(twistGrp)) valBmdl = cmds.createNode("multDoubleLinear", n=names.addModifier(self.basename, 'multDoubleLinear', addSuffix='ValueB')) cmds.setAttr("{0}.input2".format(valBmdl), 2) cmds.connectAttr("{0}.rx".format(twistValB), "{0}.input1".format(valBmdl)) cmds.connectAttr("{0}.output".format(valBmdl), "{0}.twistValB".format(twistGrp)) # Create a group for the twist joints to get a better ui result jntGrp = cmds.group(n=names.addModifier(self.basename, 'grp', addSuffix='Joints'), em=1, p=masterGrp) cmds.parentConstraint(dummyParent, jntGrp, mo=0, n=names.addModifier(self.basename, 'parentConstraint', addSuffix='Joints')) # Get the placement values for the twist joints val = self.val + 1 jntPlacement = [] if self.aimAxis.lower() == "y": tr = "ty" elif self.aimAxis.lower() == "z": tr = "tz" else: tr = "tx" self.twistJoints = list() for i in range(val): i += 1 if not i == val: div = float(i) / float(val) jntPlacement = float("{0:.2f}".format(self.jntLen * div)) curJoint = str(i) # Create, place and connect twist joints based on extracted information jnt = cmds.duplicate(dummyParent, po=1, n=names.addModifier( self.basename, 'joint', addSuffix=curJoint))[0] cmds.parent(jnt, jntGrp) for i in ["jointOrientX", "jointOrientY", "jointOrientZ"]: cmds.setAttr("{0}.{1}".format(jnt, i), 0) if stretch: stretchMdl = cmds.createNode( "multDoubleLinear", n=names.addModifier(self.basename, 'multDoubleLinear', addSuffix=curJoint + "Stretch")) cmds.setAttr("{0}.input2".format(stretchMdl), div) cmds.connectAttr("{0}.{1}".format(dummyTarget, tr), "{0}.input1".format(stretchMdl)) cmds.connectAttr("{0}.output".format(stretchMdl), "{0}.{1}".format(jnt, tr)) else: cmds.setAttr("{0}.{1}".format(jnt, tr), jntPlacement) twistValmdl = cmds.createNode("multDoubleLinear", n=names.addModifier( self.basename, 'multDoubleLinear', addSuffix=curJoint)) cmds.setAttr("{0}.input2".format(twistValmdl), (div * -1)) if noflip: cmds.connectAttr("{0}.twistValB".format(twistGrp), "{0}.input1".format(twistValmdl)) else: cmds.connectAttr("{0}.twistValA".format(twistGrp), "{0}.input1".format(twistValmdl)) cmds.connectAttr("{0}.output".format(twistValmdl), "{0}.rx".format(jnt)) # Pretty it up a bit cmds.setAttr("{0}.overrideEnabled".format(jnt), 1) cmds.setAttr("{0}.radius".format(jnt), 0.5) cmds.setAttr("{0}.overrideColor".format(jnt), 12) # Lock values attrList = ["tx", "ty", "tz", "rx", "ry", "rz"] for attr in attrList: cmds.setAttr("{0}.{1}".format(jnt, attr), l=1) # Assign joints to variable self.twistJoints = self.twistJoints + [jnt] # Clean up cmds.setAttr("{0}.v".format(dummyParent), 0)