def addJntsOnSurfIntersection(surf1, surf2, jntsNum): ''' Places jnts along intersection curve between surf1 and surf2 naming convention based on surf1 ''' # intersect surfaces crvGrp, intNode = mc.intersect(surf1, surf2, fs=True, ch=True, o=True, cos=False)[:2] intNode = mc.rename(intNode, surf1+'_ints') crvGrp = mc.rename(crvGrp, surf1+'_ints_crv_grp') crv = mc.listRelatives(crvGrp, c=True)[0] crv = mc.rename(crv, surf1+'_ints_crv') # rebuild curve to jntNum spans rbdCrv, rbdNode = mc.rebuildCurve(crv, ch=True, o=True, rpo=False, spans=jntsNum, rt=0, kr=2, n=crv+'_rbd_crv') rbdNode = mc.rename(rbdNode, crv+'_rbd') # offset curve to control size of eye hole offsetCrv, offsetNode = mc.offsetCurve(rbdCrv, ch=True, distance=0, o=True, ugn=0, n=crv+'_offset_crv') offsetNode = mc.rename(offsetNode, crv+'_offset') locs = [] locName = '_'.join(surf1.split('_')[:2]) # attach locators to intersection curve for locId in range(jntsNum): loc = mc.spaceLocator(n=locName+'_loc_%d' % locId)[0] rt.attachToMotionPath(offsetCrv, locId, loc, fm=False) mc.setAttr(loc+'.localScale', 0.05, 0.05, 0.05) locs.append(loc) # normal constraint to surf1 for loc in locs: mc.normalConstraint(surf2, loc, aim=(1,0,0)) jnts = [] # add joints under locators for loc in locs: mc.select(cl=True) jnt = mc.joint(n=loc.replace('_loc_','_jnt_')) rt.parentSnap(jnt, loc) mc.setAttr(jnt+'.jointOrient', 0,0,0) jnts.append(jnt) # groups grp = mc.group(crvGrp, offsetCrv, rbdCrv, locs, n=surf1+'_intersect_loc_grp') # create offset attribute mc.addAttr(grp, ln='collideOffset', at='double', dv=0, k=True) offsetPlug = cn.create_multDoubleLinear(grp+'.collideOffset', -1) mc.connectAttr(offsetPlug, offsetNode+'.distance', f=True) # connect debug rt.connectVisibilityToggle(offsetCrv, grp, 'offsetCrv', False) rt.connectVisibilityToggle(rbdCrv, grp, 'rebuildCrv', False) rt.connectVisibilityToggle(crvGrp, grp, 'intersectCrv', False) rt.connectVisibilityToggle(locs, grp, 'crvLocs', False) rt.connectVisibilityToggle(jnts, grp, 'crvJnts', False)
def createPtDriverSys(nodeName, attachGeo=None): ''' Create driver system based on vertex selection in viewport Returns drvSysGrp, and a list of locators that can be used to drive offset controls ''' # get vertex selections selVerts = mc.ls(os=True, fl=True) # create control placement locators on each vert drvLocs = [] origLocs = [] popcNodes = [] ctlNum = len(selVerts) for ctlId in range(ctlNum): loc = mc.spaceLocator(n=nodeName+'_wireOffset_drvLoc%d'%ctlId)[0] targetVert = selVerts[ctlId] mc.select(targetVert, loc, r=True) meval('doCreatePointOnPolyConstraintArgList 2 { "0" ,"0" ,"0" ,"1" ,"" ,"1" ,"0" ,"0" ,"0" ,"0" };') meval('CBdeleteConnection "%s.rx";' % loc) meval('CBdeleteConnection "%s.ry";' % loc) meval('CBdeleteConnection "%s.rz";' % loc) mc.setAttr(loc+'.r', 0,0,0) drvLocs.append(loc) # if attachGeo is defined, use attachGeo to drive pointOnPolyConstraint popcNode = mc.listRelatives(loc, c=True, type='pointOnPolyConstraint')[0] if attachGeo: # make origLoc to preserve position origLoc = mc.group(n=loc.replace('_drvLoc', '_drvLocOrig'), em=True) rt.parentSnap(origLoc, loc) mc.parent(origLoc, w=True) # swap input mesh for popcNode -> this will move drvLoc mc.connectAttr(attachGeo, popcNode+'.target[0].targetMesh', f=True) # parent origLoc back under driverLoc, and use origLoc as driverLoc instead mc.parent(origLoc, loc) origLocs.append(origLoc) popcNodes.append(popcNode) drvLocGrp = mc.group(drvLocs, n=nodeName+'_wireOffset_drvLocs_grp') drvSysGrp = mc.group(drvLocGrp, n=nodeName+'_wireOffset_drvSys_grp') rt.connectVisibilityToggle(drvLocs, drvSysGrp, 'drvLocsVis', False) mc.addAttr(drvSysGrp, ln='enabled', at='bool', k=True, dv=True) for eachPopc in popcNodes: rt.connectSDK(drvSysGrp+'.enabled', eachPopc+'.nodeState', {1:0, 0:2}) if attachGeo: return drvSysGrp, origLocs return drvSysGrp, drvLocs
def addTangentMPTo(startMP, endMP, direction, default=0.5, reverse=False): ''' add a "tangent" attribute to startLoc, and a tangentLoc to startLoc at 0, tangentLoc would be exactly on startLoc at 1, tangentLoc would be projected on direction vector onto endLoc's plane direction (string): 'x', 'y', or 'z' reverse (bool): reverse direction return tangentLoc ''' # add tangentLoc tangentLoc = mc.spaceLocator(n=startMP+'_tangent_loc')[0] rt.parentSnap(tangentLoc, startMP) # add planeLoc - moves on plane of endMP, to help calculcate max distance planeLoc = mc.spaceLocator(n=endMP+'_plane_loc')[0] rt.parentSnap(planeLoc, endMP) # pointConstraint planeLoc to startMP # skip "direction" axis, to maintain sliding on endMP's plane mc.pointConstraint(startMP, planeLoc, skip=direction) # get distance between startMP and planeLoc # this is the maximum length for tangent maxDistance = cn.create_distanceBetween(startMP, planeLoc) # add "tangent" attribute to startMP mc.addAttr(startMP, ln='tangent', at='double', min=0, max=1, dv=default, k=True) # multiply tangent value by maxDistance tangentDistance = cn.create_multDoubleLinear(startMP+'.tangent', maxDistance) # reverse the direction if necessary if reverse: tangentDistance = cn.create_multDoubleLinear(tangentDistance, -1) # this will be used to drive the tangentLoc translatePlug = '%s.t%s' % (tangentLoc, direction) mc.connectAttr(tangentDistance, translatePlug, f=True) # connect locators for debug rt.connectVisibilityToggle((planeLoc, tangentLoc), startMP, 'debugTangentLocs', default=False) rt.connectVisibilityToggle(tangentLoc, startMP, 'tangentLoc', False) return tangentLoc
def createCtlSys(nodeName, drvLocs, size=1, addGrp=1): ''' Adds controls under drvLocs, to be used as offset controls size - [float] radius of nurbs sphere control if size=1, radius will be 0.2 of distance between first 2 locators addGrp - [int] number of offset grps above the control Returns ctlSysGrp, and a list of controls ''' # calculate size pos1 = pm.dt.Point(mc.xform(drvLocs[0], q=True, ws=True, t=True)) pos2 = pm.dt.Point(mc.xform(drvLocs[1], q=True, ws=True, t=True)) vec = pos2 - pos1 dist = vec.length() size = dist * 0.2 * size # create controls ctls = [] for eachLoc in drvLocs: grp = eachLoc # add offset grps for grpId in range(addGrp): grp = mc.group(em=True, n=eachLoc.replace('_wireOffset_drvLoc', '_wireOffset_offset%d_grp'%grpId).replace('Orig',''), p=grp) mc.xform(grp, os=True, a=True, t=(0,0,0), ro=(0,0,0)) # create control ctl = mc.sphere(r=size, n=eachLoc.replace('_wireOffset_drvLoc', '_wireOffset_ctl').replace('Orig',''))[0] mc.delete(ctl, ch=True) rt.parentSnap(ctl, grp) # assign color # first, break connection to shader ctlShape = mc.listRelatives(ctl, c=True, s=True)[0] shdConn = mc.listConnections(ctlShape+'.instObjGroups', p=True)[0] mc.disconnectAttr(ctlShape+'.instObjGroups', shdConn) # assign color override mc.setAttr(ctlShape+'.overrideEnabled', 1) mc.setAttr(ctlShape+'.overrideColor', 6) ctls.append(ctl) ctlSysGrp = mc.group(em=True, n=nodeName+'_wireOffset_ctlSys_grp') rt.connectVisibilityToggle(ctls, ctlSysGrp, 'ctlVis', True) return ctlSysGrp, ctls
def addProxyGeoToJnt(jnt, geo, child=None): ''' Parent-snaps geo under jnt If child=None, uses first child of jnt Distance between jnt & child drives scaleX (assuming X is downAxis) ''' geo = mc.duplicate(geo, n=jnt+'_proxy')[0] rt.parentSnap(geo, jnt) if not child: child = mc.listRelatives(jnt, c=True, type='joint')[0] # get distance between jnt and child dist = cn.create_distanceBetween(jnt, child) finalScale = cn.create_multiplyDivide(dist, jnt+'.scaleX', 2) mc.connectAttr(finalScale, geo+'.sx', f=True) return geo
def addProxyGeoToJnt(jnt, geo, child=None): """ Parent-snaps geo under jnt If child=None, uses first child of jnt Distance between jnt & child drives scaleX (assuming X is downAxis) """ geo = mc.duplicate(geo, n=jnt + "_proxy")[0] rt.parentSnap(geo, jnt) if not child: child = mc.listRelatives(jnt, c=True, type="joint")[0] # get distance between jnt and child dist = cn.create_distanceBetween(jnt, child) finalScale = cn.create_multiplyDivide(dist, jnt + ".scaleX", 2) mc.connectAttr(finalScale, geo + ".sx", f=True) return geo
def getMirroredPos(ctls, parent=None): ''' Mirror a list of ctls, in worldSpace if parent=None Assume to use X world axis Assume local mirror axis is Z Returns posList, rotList ''' # get transforms for all ctls in worldSpace first # (before anything gets moved, as one control may move another in FK) loc1 = mc.spaceLocator()[0] loc2 = mc.spaceLocator()[0] mc.parent(loc2, loc1) flipGrp = mc.group(n='tempFlipGrp', em=True, w=True) if parent: mc.parent(flipGrp, parent) mc.xform(flipGrp, os=True, t=(0,0,0), ro=(0,0,0)) mc.parent(flipGrp, w=True) posList = [] rotList = [] for eachCtl in ctls: rt.parentSnap(loc1, eachCtl) mc.setAttr(loc1+'.rotateOrder', mc.getAttr(eachCtl+'.rotateOrder')) mc.setAttr(loc2+'.rotateOrder', mc.getAttr(eachCtl+'.rotateOrder')) mc.setAttr(loc1+'.scaleZ', 1) mc.setAttr(flipGrp+'.scaleX', 1) mc.parent(loc1, flipGrp) mc.setAttr(flipGrp+'.scaleX', -1) mc.setAttr(loc1+'.scaleZ', -1) posList.append(mc.xform(loc2, q=True, ws=True, t=True)) rotList.append(mc.xform(loc2, q=True, ws=True, ro=True)) mc.delete(flipGrp) mc.select(ctls, r=True) return posList, rotList
def addMidMP(startMP, endMP, startAimMP, endAimMP, aimVector, upVector, name): ''' add a midLoc positioned between startMP and endMP alignment can be set to separate MPs return midLoc ''' # add midPosLoc to get mid position midPosLoc = mc.spaceLocator(n=name+'_midPosLoc')[0] # constraint midPosLoc between startMP and endMP to get mid position mc.pointConstraint(startMP, endMP, midPosLoc) # add aimUp/Down locs to get orientation aimUpLoc = mc.spaceLocator(n=name+'_aimUpLoc')[0] aimDownLoc = mc.spaceLocator(n=name+'_aimDownLoc')[0] rt.parentSnap(aimUpLoc, midPosLoc) rt.parentSnap(aimDownLoc, midPosLoc) # aim constraints to startAimMP and endAimMPs mc.aimConstraint(startAimMP, aimDownLoc, aim=aimVector, u=upVector, wut='objectrotation', wuo=startAimMP, wu=upVector) oppositeAimVector = [-c for c in aimVector] mc.aimConstraint(endAimMP, aimUpLoc, aim=oppositeAimVector, u=upVector, wut='objectrotation', wuo=endAimMP, wu=upVector) # create orientLoc to blend between the two aims orientLoc = mc.spaceLocator(n=name+'_orientLoc')[0] rt.parentSnap(orientLoc, midPosLoc) mc.orientConstraint(startAimMP, endAimMP, orientLoc) # create midLoc for outputMP midLoc = mc.spaceLocator(n=name)[0] rt.parentSnap(midLoc, orientLoc) # connect visibilities for debugging rt.connectVisibilityToggle(midPosLoc, midLoc, 'debugMidPosLoc', default=False) rt.connectVisibilityToggle((aimUpLoc, aimDownLoc), midLoc, 'debugAimLocs', default=False) rt.connectVisibilityToggle(orientLoc, midLoc, 'debugOrientLoc', default=False) return midLoc
def placePivotsForReverseRoll(baseJnt, bendPos=(0,0,0), leftPos=(0,0,0), rightPos=(0,0,0)): ''' creates bendPivot, leftPivot, rightPivot to be adjusted manually ''' bendPivot = mc.spaceLocator(n=baseJnt+'_bendPivot_loc')[0] rt.parentSnap(bendPivot, baseJnt) mc.setAttr(bendPivot+'.t', *bendPos) leftPivot = mc.spaceLocator(n=baseJnt+'_leftPivot_loc')[0] rt.parentSnap(leftPivot, baseJnt) mc.setAttr(leftPivot+'.t', *leftPos) rightPivot = mc.spaceLocator(n=baseJnt+'_rightPivot_loc')[0] rt.parentSnap(rightPivot, baseJnt) mc.setAttr(rightPivot+'.t', *rightPos) return bendPivot, leftPivot, rightPivot
def addIndividualOffsetRig(toothId, selVerts): """ returns top group node """ name = 'CT_toothOffset%d_' % toothId #=========================================================================== # ADD OFFSET CONTROLS #=========================================================================== # add offset controlA ctlA = rt.ctlCurve(name + 'ctlA', 'circle', 0, snap='CT_teethOffset_align_loc_%d' % toothId, size=1, ctlOffsets=['bend']) mc.aimConstraint('CT_teethOffset_aim_loc_%d' % toothId, ctlA.home, aim=(1, 0, 0), u=(0, 0, 1), wut='objectRotation', wuo='CT_teethOffset_align_loc_%d' % toothId, wu=(0, 0, 1)) mc.pointConstraint('CT_teethOffset_align_loc_%d' % toothId, ctlA.home) # add second offset control as child to first ctlB = rt.ctlCurve(name + 'ctlB', 'circle', 0, snap=ctlA.crv, size=1, ctlOffsets=['bend']) mc.parent(ctlB.home, ctlA.crv) mc.setAttr(ctlB.home + '.tx', 0.5) # local rig's bend should update CtlB mc.connectAttr('CT_teethLocal%d_bndB.ry' % (toothId + 1), ctlB.grp['bend'] + '.ry', f=True) #=============================================================================== # Connections from offsetCtls to localRig #=============================================================================== # since scale should always be local mc.connectAttr(ctlA.crv + '.sy', 'CT_teethLocal%d_bndA.sy' % (toothId + 1), f=True) mc.connectAttr(ctlA.crv + '.sz', 'CT_teethLocal%d_bndA.sz' % (toothId + 1), f=True) mc.connectAttr(ctlB.crv + '.sy', 'CT_teethLocal%d_bndB.sy' % (toothId + 1), f=True) mc.connectAttr(ctlB.crv + '.sz', 'CT_teethLocal%d_bndB.sz' % (toothId + 1), f=True) # twisting could also be local mc.connectAttr(ctlA.crv + '.rx', 'CT_teethLocal%d_bndA.rx' % (toothId + 1), f=True) mc.connectAttr(ctlB.crv + '.rx', 'CT_teethLocal%d_bndB.rx' % (toothId + 1), f=True) # slide tooth along path mc.addAttr(ctlA.crv, ln='slide', at='double', k=True) mc.connectAttr(ctlA.crv + '.slide', 'CT_teethLocal%d_bndA.ty' % (toothId + 1), f=True) #=============================================================================== # Complete CV point placement for curves #=============================================================================== # endLoc under ctlB endLoc = mc.spaceLocator(n=name + 'ctlB_endLoc')[0] rt.parentSnap(endLoc, ctlB.crv) mc.setAttr(endLoc + '.tx', 0.5) mc.setAttr(endLoc + '.localScale', 0.1, 0.1, 0.1) #=============================================================================== # second hierarchy for base curve #=============================================================================== ctlABase = mc.group(em=True, n=name + 'ctlABase_grp') mc.aimConstraint('CT_teethOffset_aim_loc_%d' % toothId, ctlABase, aim=(1, 0, 0), u=(0, 0, 1), wut='objectRotation', wuo='CT_teethOffset_align_loc_%d' % toothId, wu=(0, 0, 1)) mc.pointConstraint('CT_teethOffset_align_loc_%d' % toothId, ctlABase) ctlBBase = mc.group(em=True, n=name + 'ctlBBase_grp') rt.parentSnap(ctlBBase, ctlABase) mc.setAttr(ctlBBase + '.tx', 0.5) mc.connectAttr('CT_teethLocal%d_bndB.ry' % (toothId + 1), ctlBBase + '.ry', f=True) endBaseLoc = mc.spaceLocator(n=name + 'ctlB_endBaseLoc')[0] rt.parentSnap(endBaseLoc, ctlBBase) mc.setAttr(endBaseLoc + '.tx', 0.5) mc.setAttr(endBaseLoc + '.localScale', 0.1, 0.1, 0.1) # connect scale for base transforms mc.connectAttr(ctlA.crv + '.sy', ctlABase + '.sy', f=True) mc.connectAttr(ctlA.crv + '.sz', ctlABase + '.sz', f=True) mc.connectAttr(ctlB.crv + '.sy', ctlBBase + '.sy', f=True) mc.connectAttr(ctlB.crv + '.sz', ctlBBase + '.sz', f=True) #=============================================================================== # Make wire deformer #=============================================================================== # base curve baseCrv = rt.makeCrvThroughObjs([ctlABase, ctlBBase, endBaseLoc], name + 'baseCrv', True, 2) # wire curve wireCrv = rt.makeCrvThroughObjs([ctlA.crv, ctlB.crv, endLoc], name + 'wireCrv', True, 2) # make wire wireDfm, wireCrv = mc.wire(selVerts, wire=wireCrv, n=name + 'wire_dfm', dds=(0, 50)) wireBaseUnwanted = wireCrv + 'BaseWire' # replace base mc.connectAttr(baseCrv + '.worldSpace[0]', wireDfm + '.baseWire[0]', f=True) mc.delete(wireBaseUnwanted) #=========================================================================== # GROUPS & DEBUG UTILS #=========================================================================== dfmGrp = mc.group(baseCrv, wireCrv, n=name + 'dfg_0') ctlGrp = mc.group(ctlA.home, ctlABase, n=name + 'ctg_0') retGrp = mc.group(dfmGrp, ctlGrp, n=name + 'mod_0') rt.connectVisibilityToggle([endLoc, endBaseLoc], retGrp, 'endLocs', False) rt.connectVisibilityToggle(dfmGrp, retGrp, 'wires', False) return retGrp
def createCrvDriverSys(nodeName, ctlNum, form=0, attachGeo=None): ''' Create driver system based on edge loop selection in viewport nodeName [string] ctlNum [int] - number of controls to add along curve form - [int] 0 = open, 1 = periodic Returns drvSysGrp, and a list of locators that can be used to drive offset controls ''' # select edge loop in UI drvCrv, p2cNode = mc.polyToCurve(form=form, degree=1, n=nodeName+'_wireOffset_crv') p2cNode = mc.rename(p2cNode, nodeName+'_wireOffset_p2c') crvSpans = mc.getAttr(drvCrv+'.spans') # create control placement locators on drvCrv drvLocs = [] for ctlId in range(ctlNum): loc = mc.spaceLocator(n=nodeName+'_wireOffset_drvLoc%d'%ctlId)[0] param = float(ctlId) / ctlNum * crvSpans rt.attachToMotionPath(drvCrv, param, loc, False) drvLocs.append(loc) # if curve is open, we will create an extra ctl, where param = crvSpans if mc.getAttr(drvCrv+'.form') != 2: loc = mc.spaceLocator(n=nodeName+'_wireOffset_drvLoc%d'%ctlNum)[0] param = crvSpans rt.attachToMotionPath(drvCrv, param, loc, False) drvLocs.append(loc) drvLocGrp = mc.group(drvLocs, n=nodeName+'_wireOffset_drvLocs_grp') drvSysGrp = mc.group(drvCrv, drvLocGrp, n=nodeName+'_wireOffset_drvSys_grp') rt.connectVisibilityToggle(drvLocs, drvSysGrp, 'drvLocsVis', False) rt.connectVisibilityToggle(drvCrv, drvSysGrp, 'drvCrvVis', False) mc.addAttr(drvSysGrp, ln='enabled', at='bool', k=True, dv=True) rt.connectSDK(drvSysGrp+'.enabled', p2cNode+'.nodeState', {1:0, 0:2}) # if attachGeo is defined, use attachGeo to drive polyToCurve if attachGeo: # make an origLoc for each driverLoc to preserve orig positions origLocs = [] for eachLoc in drvLocs: origLoc = mc.group(n=eachLoc.replace('_drvLoc', '_drvLocOrig'), em=True) rt.parentSnap(origLoc, eachLoc) mc.parent(origLoc, w=True) origLocs.append(origLoc) # switch the input mesh for polyToCurve -> this will move drvLocs mc.connectAttr(attachGeo, p2cNode+'.inputPolymesh', f=True) # parent orig loc back under driver loc, preserving transforms for drv, orig in zip(drvLocs, origLocs): mc.parent(orig, drv) return drvSysGrp, origLocs return drvSysGrp, drvLocs
def addReverseRoll(jnts, bendPivot, leftPivot, rightPivot): ''' add reverse roll to hand or foot setups jnts - [base, [digitBase, digitEnd], ... ] digitBase joints will be parentConstrained to the new "stableDigitJoints" that sticks with the IK handles (so you should pass in an offset grp above the actual joint) *** ASSUME ONE SPLIT JOINT BETWEEN BASE AND DIGIT TO BE CUSTOMIZED *** returns rollGrp, baseJnt EXAMPLE USE ON HAND: rollGrp (TRS, and attributes Bend & Side) should be driven by Ik/FKHand rollLocs rotations are driven by attributes on the Hand baseStableJnt is a child of the rollLocs, and therefore rotate with pivots at the rollLocs baseStableJnt drives the child of Ik/FkHand ''' #=========================================================================== # BUILD DRIVER JOINT CHAIN #=========================================================================== baseJnt = jnts[0] digitJnts = jnts[1:] basePos = mc.xform(baseJnt, q=True, t=True, ws=True) # base joint mc.select(cl=True) baseStableJnt = mc.joint(n=baseJnt+'_stable') rt.parentSnap(baseStableJnt, baseJnt) mc.setAttr(baseStableJnt+'.jointOrient', 0,0,0) mc.parent(baseStableJnt, w=True) ikHs = [] # digit joints for base, tip in digitJnts: #======================================================================= # MAKE JOINTS #======================================================================= mc.select(cl=True) # split joint digitPos = mc.xform(base, q=True, t=True, ws=True) # get midPoint between base to digitBase midPoint = [(b + d)/2 for b, d in zip(basePos, digitPos)] splitJnt = mc.joint(p=midPoint, n=base+'_mid') # digit base jnt digitBaseJnt = mc.joint(p=digitPos, n=base+'_stable') # digit end jnt tipPos = mc.xform(tip, q=True, t=True, ws=True) digitEndJnt = mc.joint(p=tipPos, n=base+'_stableTip') # orient joint chain mc.joint(splitJnt, oj='xyz', ch=True, sao='yup', e=True) mc.setAttr(digitEndJnt+'.jointOrient', 0,0,0) mc.parent(splitJnt, baseStableJnt) #======================================================================= # MAKE IKHANDLE #======================================================================= ikH = mc.ikHandle(solver='ikSCsolver', n=base+'_ikH', sj=digitBaseJnt, ee=digitEndJnt)[0] ikHs.append(ikH) #======================================================================= # PARENT CONSTRAINT original joints to stable joints #======================================================================= mc.parentConstraint(digitBaseJnt, base, mo=True) ikHdlGrp = mc.group(ikHs, n=baseJnt+'reverseRoll_ikHdl_grp') # parent baseStableJnt under locators to make multiple pivots rollGrp = abRT.groupFreeze(baseStableJnt) mc.parent(bendPivot, rollGrp) mc.parent(leftPivot, bendPivot) mc.parent(rightPivot, leftPivot) mc.parent(baseStableJnt, rightPivot) mc.parent(ikHdlGrp, rollGrp) # hide locators for debugging rt.connectVisibilityToggle([bendPivot, leftPivot, rightPivot], rollGrp, 'debugPivotLocs', False) # add attributes for controlling bend and side-to-side mc.addAttr(rollGrp, ln='bend', at='double', min=-10, max=10, dv=0, k=True) mc.addAttr(rollGrp, ln='side', at='double', min=-10, max=10, dv=0, k=True) rt.connectSDK(rollGrp+'.bend', bendPivot+'.rz', {-10:90, 10:-90}) rt.connectSDK(rollGrp+'.side', leftPivot+'.rx', {0:0, 10:-90}) rt.connectSDK(rollGrp+'.side', rightPivot+'.rx', {0:0, -10:90}) return rollGrp, baseStableJnt
def addJntsOnSurfIntersection(surf1, surf2, jntsNum): ''' Places jnts along intersection curve between surf1 and surf2 naming convention based on surf1 ''' # intersect surfaces crvGrp, intNode = mc.intersect(surf1, surf2, fs=True, ch=True, o=True, cos=False)[:2] intNode = mc.rename(intNode, surf1 + '_ints') crvGrp = mc.rename(crvGrp, surf1 + '_ints_crv_grp') crv = mc.listRelatives(crvGrp, c=True)[0] crv = mc.rename(crv, surf1 + '_ints_crv') # rebuild curve to jntNum spans rbdCrv, rbdNode = mc.rebuildCurve(crv, ch=True, o=True, rpo=False, spans=jntsNum, rt=0, kr=2, n=crv + '_rbd_crv') rbdNode = mc.rename(rbdNode, crv + '_rbd') # offset curve to control size of eye hole offsetCrv, offsetNode = mc.offsetCurve(rbdCrv, ch=True, distance=0, o=True, ugn=0, n=crv + '_offset_crv') offsetNode = mc.rename(offsetNode, crv + '_offset') locs = [] locName = '_'.join(surf1.split('_')[:2]) # attach locators to intersection curve for locId in range(jntsNum): loc = mc.spaceLocator(n=locName + '_loc_%d' % locId)[0] rt.attachToMotionPath(offsetCrv, locId, loc, fm=False) mc.setAttr(loc + '.localScale', 0.05, 0.05, 0.05) locs.append(loc) # normal constraint to surf1 for loc in locs: mc.normalConstraint(surf2, loc, aim=(1, 0, 0)) jnts = [] # add joints under locators for loc in locs: mc.select(cl=True) jnt = mc.joint(n=loc.replace('_loc_', '_jnt_')) rt.parentSnap(jnt, loc) mc.setAttr(jnt + '.jointOrient', 0, 0, 0) jnts.append(jnt) # groups grp = mc.group(crvGrp, offsetCrv, rbdCrv, locs, n=surf1 + '_intersect_loc_grp') # create offset attribute mc.addAttr(grp, ln='collideOffset', at='double', dv=0, k=True) offsetPlug = cn.create_multDoubleLinear(grp + '.collideOffset', -1) mc.connectAttr(offsetPlug, offsetNode + '.distance', f=True) # connect debug rt.connectVisibilityToggle(offsetCrv, grp, 'offsetCrv', False) rt.connectVisibilityToggle(rbdCrv, grp, 'rebuildCrv', False) rt.connectVisibilityToggle(crvGrp, grp, 'intersectCrv', False) rt.connectVisibilityToggle(locs, grp, 'crvLocs', False) rt.connectVisibilityToggle(jnts, grp, 'crvJnts', False)
def rigEyes(): # eyeBall - eyeLids intersections surf1 = 'LT_eyeBallIntersect_srf_0' surf2 = 'CT_eyeBallHeadIntersecter_srf_0' jntsNum = 20 addJntsOnSurfIntersection(surf1, surf2, jntsNum) # eyeBall pop controls baseTangentMP = ms.addTangentMPTo('LT_eyeBase_mPt', 'LT_eyeTip_mPt', 'z', default=0.2, reverse=False) tipTangentMP = ms.addTangentMPTo('LT_eyeTip_mPt', 'LT_eyeBase_mPt', 'z', default=0.2, reverse=True) midMP = ms.addMidMP(baseTangentMP, tipTangentMP, 'LT_eyeBase_mPt', 'LT_eyeTip_mPt', (0, 0, 1), (0, 1, 0), 'LT_mid_mPt') crv = ms.createSplineMPs(('LT_eyeBase_mPt', baseTangentMP, midMP, tipTangentMP, 'LT_eyeTip_mPt'), 8, 'LT_eyeSpine', (0, 3, 0)) baseTangentMP = ms.addTangentMPTo('RT_eyeBase_mPt', 'RT_eyeTip_mPt', 'z', default=0.2, reverse=False) tipTangentMP = ms.addTangentMPTo('RT_eyeTip_mPt', 'RT_eyeBase_mPt', 'z', default=0.2, reverse=True) midMP = ms.addMidMP(baseTangentMP, tipTangentMP, 'RT_eyeBase_mPt', 'RT_eyeTip_mPt', (0, 0, 1), (0, 1, 0), 'RT_mid_mPt') crv = ms.createSplineMPs(('RT_eyeBase_mPt', baseTangentMP, midMP, tipTangentMP, 'RT_eyeTip_mPt'), 8, 'RT_eyeSpine', (0, 3, 0)) #=========================================================================== # add IK offset ctrls to eyeball #=========================================================================== lfMps = mc.ls(sl=True) ctls = [] # create left controls for ctlId in range(0, len(lfMps)): ctl = cs.ctlCurve(lfMps[ctlId].replace('_MPJnt_', '_ctl_'), 'circle', 0, size=6, snap=lfMps[ctlId]) ctl.setSpaces([lfMps[ctlId]], ['Eye']) ctls.append(ctl) rtMps = mc.ls(sl=True) ctls = [] # create right controls for ctlId in range(0, len(rtMps)): ctl = cs.ctlCurve(rtMps[ctlId].replace('_MPJnt_', '_ctl_'), 'circle', 0, size=6, snap=rtMps[ctlId]) ctl.setSpaces([rtMps[ctlId]], ['Eye']) ctls.append(ctl) #=========================================================================== # Add stretchy volume for eyeBall spine #=========================================================================== stretchAmts = { 'LT_eyeSpine_ctl_0_space': 10, 'LT_eyeSpine_ctl_1_space': 9, 'LT_eyeSpine_ctl_2_space': 8, 'LT_eyeSpine_ctl_3_space': 5, 'LT_eyeSpine_ctl_4_space': 3, 'LT_eyeSpine_ctl_5_space': 1.25, 'LT_eyeSpine_ctl_6_space': 0, 'LT_eyeSpine_ctl_7_space': -1 } ms.addVolume('LT_eyeSpine_uniform_crv_crv', stretchAmts) stretchAmts = { 'RT_eyeSpine_ctl_0_space': 10, 'RT_eyeSpine_ctl_1_space': 9, 'RT_eyeSpine_ctl_2_space': 8, 'RT_eyeSpine_ctl_3_space': 5, 'RT_eyeSpine_ctl_4_space': 3, 'RT_eyeSpine_ctl_5_space': 1.25, 'RT_eyeSpine_ctl_6_space': 0, 'RT_eyeSpine_ctl_7_space': -1 } ms.addVolume('RT_eyeSpine_uniform_crv_crv', stretchAmts) #=========================================================================== # Add control lattice to eyeBall nurbs #=========================================================================== # Create lattice - hard coded to 8 ctls in Z eyeSphere = 'LT_eyeBallIntersect_srf_0' prefix = 'LT_eyeBallIntersect_' ffd, lat, latBase = mc.lattice(eyeSphere, n=prefix + 'ffd', oc=True, dv=(4, 4, 8)) grp = abRT.groupFreeze(lat) rt.transferAttrValues(lat + '.s', grp + '.s', False) mc.setAttr(lat + '.s', 1, 1, 1) mc.parent(latBase, grp) # Create lattice - hard coded to 8 ctls in Z eyeSphere = 'RT_eyeBallIntersect_srf_0' prefix = 'RT_eyeBallIntersect_' ffd, lat, latBase = mc.lattice(eyeSphere, n=prefix + 'ffd', oc=True, dv=(4, 4, 8)) grp = abRT.groupFreeze(lat) rt.transferAttrValues(lat + '.s', grp + '.s', False) mc.setAttr(lat + '.s', 1, 1, 1) mc.parent(latBase, grp) # DO THIS FOR LEFT AND RIGHT SIDES # Create joints under each ctl ctls = mc.ls(os=True) jnts = [] for eachCtl in ctls: mc.select(cl=True) jnt = mc.joint(n=eachCtl.replace('_ctl', '_jnt')) rt.parentSnap(jnt, eachCtl) jnts.append(jnt) mc.setAttr(jnt + '.radius', 3) mc.setAttr(jnt + '.jointOrient', 0, 0, 0) # Weight joints to lattice skn = mc.skinCluster(jnts, lat, name=lat + '_skn')[0] for jnt in jnts: i = jnts.index(jnt) mc.skinPercent(skn, lat + '.pt[*][*][%d]' % i, tv=((jnt, 1)))
def addIndividualOffsetRig(toothId, selVerts): """ returns top group node """ name = 'CT_toothOffset%d_'%toothId #=========================================================================== # ADD OFFSET CONTROLS #=========================================================================== # add offset controlA ctlA = rt.ctlCurve(name+'ctlA', 'circle', 0, snap='CT_teethOffset_align_loc_%d'%toothId, size=1, ctlOffsets=['bend']) mc.aimConstraint('CT_teethOffset_aim_loc_%d'%toothId, ctlA.home, aim=(1,0,0), u=(0,0,1), wut='objectRotation', wuo='CT_teethOffset_align_loc_%d'%toothId, wu=(0,0,1)) mc.pointConstraint('CT_teethOffset_align_loc_%d'%toothId, ctlA.home) # add second offset control as child to first ctlB = rt.ctlCurve(name+'ctlB', 'circle', 0, snap=ctlA.crv, size=1, ctlOffsets=['bend']) mc.parent(ctlB.home, ctlA.crv) mc.setAttr(ctlB.home+'.tx', 0.5) # local rig's bend should update CtlB mc.connectAttr('CT_teethLocal%d_bndB.ry'%(toothId+1), ctlB.grp['bend']+'.ry', f=True) #=============================================================================== # Connections from offsetCtls to localRig #=============================================================================== # since scale should always be local mc.connectAttr(ctlA.crv+'.sy', 'CT_teethLocal%d_bndA.sy'%(toothId+1), f=True) mc.connectAttr(ctlA.crv+'.sz', 'CT_teethLocal%d_bndA.sz'%(toothId+1), f=True) mc.connectAttr(ctlB.crv+'.sy', 'CT_teethLocal%d_bndB.sy'%(toothId+1), f=True) mc.connectAttr(ctlB.crv+'.sz', 'CT_teethLocal%d_bndB.sz'%(toothId+1), f=True) # twisting could also be local mc.connectAttr(ctlA.crv+'.rx', 'CT_teethLocal%d_bndA.rx'%(toothId+1), f=True) mc.connectAttr(ctlB.crv+'.rx', 'CT_teethLocal%d_bndB.rx'%(toothId+1), f=True) # slide tooth along path mc.addAttr(ctlA.crv, ln='slide', at='double', k=True) mc.connectAttr(ctlA.crv+'.slide', 'CT_teethLocal%d_bndA.ty'%(toothId+1), f=True) #=============================================================================== # Complete CV point placement for curves #=============================================================================== # endLoc under ctlB endLoc = mc.spaceLocator(n=name+'ctlB_endLoc')[0] rt.parentSnap(endLoc, ctlB.crv) mc.setAttr(endLoc+'.tx', 0.5) mc.setAttr(endLoc+'.localScale', 0.1,0.1,0.1) #=============================================================================== # second hierarchy for base curve #=============================================================================== ctlABase = mc.group(em=True, n=name+'ctlABase_grp') mc.aimConstraint('CT_teethOffset_aim_loc_%d'%toothId, ctlABase, aim=(1,0,0), u=(0,0,1), wut='objectRotation', wuo='CT_teethOffset_align_loc_%d'%toothId, wu=(0,0,1)) mc.pointConstraint('CT_teethOffset_align_loc_%d'%toothId, ctlABase) ctlBBase = mc.group(em=True, n=name+'ctlBBase_grp') rt.parentSnap(ctlBBase, ctlABase) mc.setAttr(ctlBBase+'.tx', 0.5) mc.connectAttr('CT_teethLocal%d_bndB.ry'%(toothId+1), ctlBBase+'.ry', f=True) endBaseLoc = mc.spaceLocator(n=name+'ctlB_endBaseLoc')[0] rt.parentSnap(endBaseLoc, ctlBBase) mc.setAttr(endBaseLoc+'.tx', 0.5) mc.setAttr(endBaseLoc+'.localScale', 0.1,0.1,0.1) # connect scale for base transforms mc.connectAttr(ctlA.crv+'.sy', ctlABase+'.sy', f=True) mc.connectAttr(ctlA.crv+'.sz', ctlABase+'.sz', f=True) mc.connectAttr(ctlB.crv+'.sy', ctlBBase+'.sy', f=True) mc.connectAttr(ctlB.crv+'.sz', ctlBBase+'.sz', f=True) #=============================================================================== # Make wire deformer #=============================================================================== # base curve baseCrv = rt.makeCrvThroughObjs([ctlABase, ctlBBase, endBaseLoc], name+'baseCrv', True, 2) # wire curve wireCrv = rt.makeCrvThroughObjs([ctlA.crv, ctlB.crv, endLoc], name+'wireCrv', True, 2) # make wire wireDfm, wireCrv = mc.wire(selVerts, wire=wireCrv, n=name+'wire_dfm', dds=(0,50)) wireBaseUnwanted = wireCrv+'BaseWire' # replace base mc.connectAttr(baseCrv+'.worldSpace[0]', wireDfm+'.baseWire[0]', f=True) mc.delete(wireBaseUnwanted) #=========================================================================== # GROUPS & DEBUG UTILS #=========================================================================== dfmGrp = mc.group(baseCrv, wireCrv, n=name+'dfg_0') ctlGrp = mc.group(ctlA.home, ctlABase, n=name+'ctg_0') retGrp= mc.group(dfmGrp, ctlGrp, n=name+'mod_0') rt.connectVisibilityToggle([endLoc, endBaseLoc], retGrp, 'endLocs', False) rt.connectVisibilityToggle(dfmGrp, retGrp, 'wires', False) return retGrp
def addOffsetPt(pt, aims, upObj, name): ''' ''' # make master grp masterGrp = mc.group(n=name+'_offsetPt_grp', em=True) mc.addAttr(masterGrp, ln='rollDistance', at='double', k=True, dv=2) #=========================================================================== # BASE #=========================================================================== # create base locator (drive base surface) baseLoc = mc.spaceLocator(n=name+'_baseLoc')[0] # point constraint to pt mc.pointConstraint(pt, baseLoc) # aim constraint to aim(s), using upObj as up object orientLocs = [] # should have maximum of 2 items for eachAim in aims: oriLoc = mc.spaceLocator(n=name+'_oriLoc#')[0] mc.pointConstraint(pt, oriLoc) if aims.index(eachAim) == 1: aimVec = (-1,0,0) # for the second aim constraint, use -X to aim opposite direction else: aimVec = (1,0,0) mc.aimConstraint(eachAim, oriLoc, aim=aimVec, u=(0,1,0), wuo=upObj, wut='object') orientLocs.append(oriLoc) mc.orientConstraint(orientLocs, baseLoc) # create base bnd jnt, parent under base loc baseBndJnt = mc.joint(n=name+'_base_bndJnt') rt.parentSnap(baseBndJnt, baseLoc) #=========================================================================== # OFFSET #=========================================================================== # create offset locator (drive offset surface), parent snap to base loc offsetLoc = mc.spaceLocator(n=name+'_offsetLoc')[0] rt.parentSnap(offsetLoc, baseLoc) # create acs locator (for automation), parent snap to offset loc offsetAcsLoc = mc.spaceLocator(n=name+'_offset_acsLoc')[0] rt.parentSnap(offsetAcsLoc, offsetLoc) #=========================================================================== # ROLL #=========================================================================== # create roll loc, parent snap to base loc, translate Z by masterGrp.rollDistance rollLoc = mc.spaceLocator(n=name+'_rollLoc')[0] rt.parentSnap(rollLoc, baseLoc) mc.connectAttr(masterGrp+'.rollDistance', rollLoc+'.tz', f=True) # create offset roll loc offsetRollLoc = mc.spaceLocator(n=name+'_roll_offsetLoc')[0] rt.parentSnap(offsetRollLoc, rollLoc) # create acs roll loc (for automation), parent snap to roll offset loc acsRollLoc = mc.spaceLocator(n=name+'_roll_acsLoc')[0] rt.parentSnap(acsRollLoc, offsetRollLoc) #=========================================================================== # calculate Offset transform in Roll space #=========================================================================== # offset-acs-loc.worldMatrix X roll-loc.inverseWorldMatrix mm = mc.createNode('multMatrix', n=name+'_calcRollSpace_mm') mc.connectAttr(offsetAcsLoc+'.worldMatrix[0]', mm+'.matrixIn[0]', f=True) mc.connectAttr(rollLoc+'.worldInverseMatrix[0]', mm+'.matrixIn[1]', f=True) dm = mc.createNode('decomposeMatrix', n=name+'_calcRollSpace_dm') mc.connectAttr(mm+'.matrixSum', dm+'.inputMatrix', f=True) #=========================================================================== # offset bnd jnt #=========================================================================== # use calculated position in roll-space, but parent under acs-roll-loc, to inherit offsets # make a locked loc first (to receive transforms) lockedLoc = mc.spaceLocator(n=name+'_readTransforms_loc')[0] rt.parentSnap(lockedLoc, acsRollLoc) mc.connectAttr(dm+'.outputTranslate', lockedLoc+'.t', f=True) mc.connectAttr(dm+'.outputRotate', lockedLoc+'.r', f=True) mc.connectAttr(dm+'.outputScale', lockedLoc+'.s', f=True) # user-controllable locator ctl = mc.spaceLocator(n=name+'_ctl')[0] rt.parentSnap(ctl, lockedLoc) # bnd jnt for offset surface offsetBndJnt = mc.joint(n=name+'_offset_bndJnt') #=========================================================================== # HIDING #=========================================================================== mc.addAttr(masterGrp, ln='debugVis', at='bool', k=True) mc.setAttr(masterGrp+'.debugVis', l=True) rt.connectVisibilityToggle(offsetBndJnt, masterGrp, 'offsetJnt', False) rt.connectVisibilityToggle(baseBndJnt, masterGrp, 'baseJnt', False) rt.connectVisibilityToggle(ctl, masterGrp, 'ctl', True) rt.connectVisibilityToggle(offsetRollLoc, masterGrp, 'rollLoc', True) rt.connectVisibilityToggle(orientLocs, masterGrp, 'orientLocs', False) rt.connectVisibilityToggle([baseLoc, oriLoc, offsetLoc, offsetAcsLoc, rollLoc, acsRollLoc, lockedLoc], masterGrp, 'locs', False) mc.parent(baseLoc, orientLocs, masterGrp) mc.select(masterGrp, r=True) return masterGrp
def rigEyes(): # eyeBall - eyeLids intersections surf1 = 'LT_eyeBallIntersect_srf_0' surf2 = 'CT_eyeBallHeadIntersecter_srf_0' jntsNum = 20 addJntsOnSurfIntersection(surf1, surf2, jntsNum) # eyeBall pop controls baseTangentMP = ms.addTangentMPTo('LT_eyeBase_mPt', 'LT_eyeTip_mPt', 'z', default=0.2, reverse=False) tipTangentMP = ms.addTangentMPTo('LT_eyeTip_mPt', 'LT_eyeBase_mPt', 'z', default=0.2, reverse=True) midMP = ms.addMidMP(baseTangentMP, tipTangentMP, 'LT_eyeBase_mPt', 'LT_eyeTip_mPt', (0,0,1), (0,1,0), 'LT_mid_mPt') crv = ms.createSplineMPs(('LT_eyeBase_mPt', baseTangentMP, midMP, tipTangentMP, 'LT_eyeTip_mPt'), 8, 'LT_eyeSpine', (0,3,0)) baseTangentMP = ms.addTangentMPTo('RT_eyeBase_mPt', 'RT_eyeTip_mPt', 'z', default=0.2, reverse=False) tipTangentMP = ms.addTangentMPTo('RT_eyeTip_mPt', 'RT_eyeBase_mPt', 'z', default=0.2, reverse=True) midMP = ms.addMidMP(baseTangentMP, tipTangentMP, 'RT_eyeBase_mPt', 'RT_eyeTip_mPt', (0,0,1), (0,1,0), 'RT_mid_mPt') crv = ms.createSplineMPs(('RT_eyeBase_mPt', baseTangentMP, midMP, tipTangentMP, 'RT_eyeTip_mPt'), 8, 'RT_eyeSpine', (0,3,0)) #=========================================================================== # add IK offset ctrls to eyeball #=========================================================================== lfMps = mc.ls(sl=True) ctls = [] # create left controls for ctlId in range(0,len(lfMps)): ctl = cs.ctlCurve(lfMps[ctlId].replace('_MPJnt_', '_ctl_'), 'circle', 0, size=6, snap=lfMps[ctlId]) ctl.setSpaces([lfMps[ctlId]], ['Eye']) ctls.append(ctl) rtMps = mc.ls(sl=True) ctls = [] # create right controls for ctlId in range(0,len(rtMps)): ctl = cs.ctlCurve(rtMps[ctlId].replace('_MPJnt_', '_ctl_'), 'circle', 0, size=6, snap=rtMps[ctlId]) ctl.setSpaces([rtMps[ctlId]], ['Eye']) ctls.append(ctl) #=========================================================================== # Add stretchy volume for eyeBall spine #=========================================================================== stretchAmts = {'LT_eyeSpine_ctl_0_space':10, 'LT_eyeSpine_ctl_1_space':9, 'LT_eyeSpine_ctl_2_space':8, 'LT_eyeSpine_ctl_3_space':5, 'LT_eyeSpine_ctl_4_space':3, 'LT_eyeSpine_ctl_5_space':1.25, 'LT_eyeSpine_ctl_6_space':0, 'LT_eyeSpine_ctl_7_space':-1} ms.addVolume('LT_eyeSpine_uniform_crv_crv', stretchAmts) stretchAmts = {'RT_eyeSpine_ctl_0_space':10, 'RT_eyeSpine_ctl_1_space':9, 'RT_eyeSpine_ctl_2_space':8, 'RT_eyeSpine_ctl_3_space':5, 'RT_eyeSpine_ctl_4_space':3, 'RT_eyeSpine_ctl_5_space':1.25, 'RT_eyeSpine_ctl_6_space':0, 'RT_eyeSpine_ctl_7_space':-1} ms.addVolume('RT_eyeSpine_uniform_crv_crv', stretchAmts) #=========================================================================== # Add control lattice to eyeBall nurbs #=========================================================================== # Create lattice - hard coded to 8 ctls in Z eyeSphere = 'LT_eyeBallIntersect_srf_0' prefix = 'LT_eyeBallIntersect_' ffd, lat, latBase = mc.lattice(eyeSphere, n=prefix+'ffd', oc=True, dv=(4,4,8)) grp = abRT.groupFreeze(lat) rt.transferAttrValues(lat+'.s', grp+'.s', False) mc.setAttr(lat+'.s',1,1,1) mc.parent(latBase, grp) # Create lattice - hard coded to 8 ctls in Z eyeSphere = 'RT_eyeBallIntersect_srf_0' prefix = 'RT_eyeBallIntersect_' ffd, lat, latBase = mc.lattice(eyeSphere, n=prefix+'ffd', oc=True, dv=(4,4,8)) grp = abRT.groupFreeze(lat) rt.transferAttrValues(lat+'.s', grp+'.s', False) mc.setAttr(lat+'.s',1,1,1) mc.parent(latBase, grp) # DO THIS FOR LEFT AND RIGHT SIDES # Create joints under each ctl ctls = mc.ls(os=True) jnts = [] for eachCtl in ctls: mc.select(cl=True) jnt = mc.joint(n=eachCtl.replace('_ctl', '_jnt')) rt.parentSnap(jnt, eachCtl) jnts.append(jnt) mc.setAttr(jnt+'.radius', 3) mc.setAttr(jnt+'.jointOrient', 0,0,0) # Weight joints to lattice skn = mc.skinCluster(jnts, lat, name=lat+'_skn')[0] for jnt in jnts: i = jnts.index(jnt) mc.skinPercent(skn, lat+'.pt[*][*][%d]'%i, tv=((jnt, 1)))