def build(target, spaceName, spaceContainer, rotateTarget, control, space): if not spaceName: spaceName = pdil.simpleName(target) + '_altRot' trueTarget = group(em=True, name=pdil.simpleName(target, '{0}_split'), p=spaceContainer) xform(trueTarget, ws=1, t=xform(control, q=True, ws=True, t=True)) trans = parentConstraint(target, trueTarget, mo=True, sr=['x', 'y', 'z']) # An explicit target can be given, otherwise it ends up being the main controller in effect. if rotateTarget: rot = parentConstraint(rotateTarget, trueTarget, mo=True, st=['x', 'y', 'z']) trans.addAttr('transTarget', at='bool', dv=True) rot.addAttr('rotTarget', at='bool', dv=True) return trueTarget, spaceName
def build(cls, target, spaceName, spaceContainer, constraintWeights, control, space): ''' In this mode, target is a list of targets and constraintWeights is an optional list of weights Args: target: List of targets, but the first is the "fallback" space use to move the whole system constraintWegiths: Optional dict of weights for the constraints {'points': [p1, p2, p3], 'orients': [o1, o2, o3]} ''' if not spaceName: spaceName = cls.DEFAULT_SPACENAME wrapper = group(em=True, name=pdil.simpleName(control, '{0}_wrapMultiPR'), p=spaceContainer) #pdil.dagObj.matchTo(wrapper, PyNode(target[0])) # Not sure if this actually matters parentConstraint(target[0], wrapper) trueTarget = group(em=True, name=pdil.simpleName(control, '{0}_multiPR'), p=wrapper) pdil.dagObj.matchTo( trueTarget, control) # It's actually important so this can follow correctly. if constraintWeights: points = constraintWeights.get('points', [1.0] * (len(target) - 1)) orients = constraintWeights.get('orients', [1.0] * (len(target) - 1)) else: points = [1.0] * (len(target) - 1) orients = [1.0] * (len(target) - 1) for t, p, o in zip(target[1:], points, orients): oc = orientConstraint(t, trueTarget, w=o, mo=True) pointConstraint(t, trueTarget, w=p, mo=True) oc.interpType.set(2) return trueTarget, spaceName
def build(target, spaceName, spaceContainer, rotateTarget, control, space): # In this mode, target is a list of targets and rotateTarget is an optional list of weights if not spaceName: #spaceName = pdil.simpleName(target) + '_' + pdil.simpleName(rotateTarget) + '_follow' spaceName = 'multi_parent' trueTarget = group(em=True, name=pdil.simpleName(target[0], '{0}_multiParent'), p=spaceContainer) pdil.dagObj.matchTo( trueTarget, control) # It's actually important so this can follow correctly. proxies = [] for t in target: d = duplicate(trueTarget)[0] d.rename(d.name() + '_' + pdil.simpleName(t)) parentConstraint(t, d, mo=True) proxies.append(d) constraint = parentConstraint(proxies, trueTarget, mo=True) constraint.interpType.set( 2) # Shorted rotation is probably the most stable if rotateTarget: attrs = constraint.getWeightAliasList() for v, attr in zip(rotateTarget, attrs): attr.set(v) return trueTarget, spaceName
def dupChain(start, end, nameFormat='{0}_dup'): ''' Creates a duplicate chain, pruned of all branches and children. Can handle same joints and start and end. :param string nameFormat: The str.format used on the duped chain ''' chain = getChain(start, end) assert chain, '{0} and {1} are not in the same hierarchy, dupChain() failed'.format( start, end) dup = duplicate(start)[0] if start != end: child = findChild(dup, pdil.simpleName(end)) assert child, 'dupChain failed to find duped child {0} in {1}'.format( end, start) prune(dup, child) else: child = dup dupedChain = getChain(dup, child) ends = dupedChain[-1].getChildren(type='transform') if ends: delete(ends) for src, d in zip(chain, dupedChain): dupName = pdil.simpleName(src, nameFormat) d.rename(dupName) return dupedChain
def getGroup(modeName, main=None, checkOnly=False): ''' Returns the group that contains the space's proxy target, i.e. the node the zero space is actually constrained to. Args: modeName: str = One of the target modes, ex: 'PARENT', 'POINT_ORIENT', etc. main: PyNode = The main controller in case the scene has multiple. checkOnly: bool = If True, won't build the space and returns None if not found. ''' modeName = toCamel(modeName) if not main: main = find.mainGroup() for child in main.listRelatives(): if pdil.simpleName(child) == '__spaces__': spaceContainer = child break else: spaceContainer = group(em=True, n='__spaces__', p=main) hide(spaceContainer) for child in spaceContainer.listRelatives(): if pdil.simpleName(child) == modeName: spaceGroup = child break else: if checkOnly: return None else: spaceGroup = group(em=True, name=modeName, p=spaceContainer) return spaceGroup
def pointer(parent, child): ''' Makes proxy joints for two TempJoints so the hierarchical relationship can be drawn. .. todo:: I think the actual connection logic might move to BPJoint.setParent ''' assert type(parent).__name__ == type( child).__name__ == 'BPJoint', 'Both must be TempJoints' if not parent.attr('children').isConnectedTo(child.attr('parent')): parent.attr('children') >> child.attr('parent') proxyRadius = _DEFAULT_PROXY_RADIUS try: proxyRadius = parent.parent.proxy.radius.get() except: pass grp = getProxyGroup() if not child.proxy: makeProxy(child, grp, child.radius.get() * _DEFAULT_PROXY_RADIUS) if not parent.proxy: makeProxy(parent, grp, parent.radius.get() * _DEFAULT_PROXY_RADIUS) # If card parentage is established, manage vis if parent.cardCon.node() != child.cardCon.node(): parentCardName = simpleName(parent.cardCon.node()) childCardName = simpleName(child.cardCon.node()) child.proxy.setParent(grp) linkStart = joint(grp) linkStart.radius.set(parent.radius.get() * proxyRadius) linkEnd = joint(linkStart) linkEnd.radius.set(child.radius.get() * proxyRadius) pointConstraint(parent, linkStart) pointConstraint(child, linkEnd) pdil.math.multiply(parent.cardCon.node().v, child.cardCon.node().v) >> linkStart.v if not child.cardCon.node().v.isConnectedTo(child.v): child.cardCon.node().v >> child.v child.v >> child.proxy.v _clearLink(child.proxy) _recordLink(child.proxy, linkStart) child.proxy.rename(childCardName + '_proxy') linkStart.rename(parentCardName + '_' + childCardName + '_link') else: child.proxy.setParent(parent.proxy)
def build(target, spaceName, spaceContainer, rotateTarget, control, space): if not spaceName: spaceName = pdil.simpleName(target) + '_' + pdil.simpleName( rotateTarget) + '_follow' trueTarget = group(em=True, name=pdil.simpleName(target, '{0}_dualFollow'), p=spaceContainer) pdil.dagObj.matchTo(trueTarget, control) pointConstraint(target, rotateTarget, trueTarget, mo=True) orientConstraint(target, rotateTarget, trueTarget, mo=True) return trueTarget, spaceName
def build(target, spaceName, spaceContainer, rotateTarget, control, space): if not spaceName: spaceName = pdil.simpleName(target) + '_pos' for child in spaceContainer.listRelatives(): if target in pointConstraint(child, q=True, tl=True): trueTarget = child break else: trueTarget = group(em=True, name=pdil.simpleName(target, '{0}_posOnly'), p=spaceContainer) pointConstraint(target, trueTarget) return trueTarget, spaceName
def rootBone(nodes=None): ''' Returns the root bone, trying to account for case and namespaces or None if not found. `make` should be either 'root' or 'weaponRoot', specifying which to make (always top level) if a root is not found. Can be given a list of nodes to search for the root, ''' names = [config._settings['root_name']] if not nodes: # Check if any exist top level first top = ls(assemblies=True) for name in names: if name in top: return PyNode('|' + name) # See if there is a top level obj in a namespace named b_root of any casing. searchNodes = nodes if nodes else ls(assemblies=True) nodes = [obj for obj in searchNodes if simpleName(obj).lower() in names] if len(nodes) == 1: return nodes[0] # Then check if any exist at all (which will fail if several exist in separate groups). for name in names: if objExists(name): return PyNode(name) return None
def build(target, spaceName, spaceContainer, rotateTarget, control, space): if not spaceName: spaceName = pdil.simpleName(target) + '_' + pdil.simpleName( rotateTarget) + '_parent' trueTarget = group(em=True, name=pdil.simpleName(target, '{0}_dualParent'), p=spaceContainer) pdil.dagObj.matchTo(trueTarget, control) # parentConstraint doesn't actually respect maintainOffset regarding rotation parentConstraint([target, rotateTarget], trueTarget, mo=True, sr=['x', 'y', 'z']) orientConstraint([target, rotateTarget], trueTarget, mo=True) return trueTarget, spaceName
def build(target, spaceName, spaceContainer, rotateTarget, control, space): if not spaceName: spaceName = pdil.simpleName(target) + '_rot' trueTarget = group(em=True, name=pdil.simpleName(target, '{0}_rotOnly'), p=spaceContainer) xform(trueTarget, ws=1, t=xform(control, q=True, ws=True, t=True)) # parentConstraint translate to it's parent so it remains in position and lets us do a full parentConstraint later parentConstraint(space.getParent(), trueTarget, mo=True, sr=['x', 'y', 'z']) parentConstraint(target, trueTarget, mo=True, st=['x', 'y', 'z']) return trueTarget, spaceName
def trimName(jnt): ''' Given an joint, return its simple name without b_ or rig_ if those prefixes exist. ''' name = pdil.simpleName(jnt) prefix = config._settings['joint_prefix'] if name.startswith(prefix): return name[len(prefix):] return name
def jointListerAddRow(self, ctr, tempJoint, name, card, parentCard): index = next(ctr) jointName = pdil.shortName(tempJoint) self.setItem(index, self.JOINT_LISTER_NAME, Cell(jointName)) cb = Cell(checked=tempJoint.isHelper) self.setItem(index, self.JOINT_LISTER_HELPER, cb) self.setItem(index, self.JOINT_LISTER_OUTPUT, Cell(name)) cb = Cell(checked=tempJoint.displayHandle.get()) self.setItem(index, self.JOINT_LISTER_HANDLES, cb) # --- Orient --- orientText = '' if tempJoint.customOrient: if tempJoint.customOrient == tempJoint.card: orientText = '-as card-' elif tempJoint.customOrient == tempJoint: orientText = '-as proxy-' else: orientText = 'custom:' + pdil.shortName(tempJoint.customOrient) elif isinstance(tempJoint.orientTarget, basestring): orientText = tempJoint.orientTarget elif tempJoint.orientTarget: orientText = pdil.shortName(tempJoint.orientTarget) self.setItem(index, self.JOINT_LISTER_ORIENT, Cell(orientText)) # --- parent --- if tempJoint.parent: # Technically this will fail if there is a helper also has a child (which is just fine, just not useful) outputMap = tempJoint.parent.card.getOutputMap(includeHelpers=True) if tempJoint.info.get('options', {}).get('mirroredSide'): parentName = outputMap[tempJoint.parent][1] else: parentName = outputMap[tempJoint.parent][0] if not parentName: # This being empty means the parent is a helper parentName = '!helper! ' + pdil.simpleName(tempJoint.parent) elif tempJoint.extraNode[0]: outputMap = tempJoint.extraNode[0].card.getOutputMap( includeHelpers=False) parentName = outputMap[tempJoint.extraNode[0]][1] else: parentName = '' self.setItem(index, self.JOINT_LISTER_CHILDOF, Cell(parentName))
def convertNodesToNames(self): ''' Returns an json serializable version of the current preset file. ''' convertedMaster = collections.OrderedDict() for presetName, preset in self.profiles.items(): converted = collections.OrderedDict() for ctrl, space in preset.items(): ctrlName = pdil.simpleName(ctrl) if not isinstance( ctrl, basestring) else ctrl converted[ctrlName] = space convertedMaster[presetName] = converted return convertedMaster
def addUserDriven(control, spaceName): targetName = pdil.simpleName(control) + '_' + spaceName userGroup = common.getGroup(common.Mode.USER) if targetName in userGroup.listRelatives(type='transform'): warning('This space/target already exists') return trueTarget = group(em=True, name=targetName) trueTarget.setParent(userGroup) pdil.dagObj.matchTo(trueTarget, control) pdil.dagObj.align(trueTarget, make=True) add(control, trueTarget, spaceName, mode=common.Mode.USER) return trueTarget
def addRow(self, row, ctrl, space): name = pdil.simpleName(ctrl) selectButton = QtWidgets.QPushButton(name) selectButton.clicked.connect(partial(select, ctrl)) spaceChooser = QtWidgets.QComboBox() spaceChooser.addItems(fossil.space.getNames(ctrl) + [ACTIVATE_KEY]) spaceChooser.setCurrentText(space) spaceChooser.currentTextChanged.connect(partial(self.setSpace, ctrl)) removeButton = QtWidgets.QPushButton('X') removeButton.clicked.connect(partial(self.removeControl, ctrl)) #self.ui.profileTable.setItem(row, 0, QtWidgets.QTableWidgetItem(ctrl.name) ) self.ui.profileTable.setCellWidget(row, 0, selectButton) self.ui.profileTable.setCellWidget(row, 1, spaceChooser) self.ui.profileTable.setCellWidget(row, 2, removeButton)
def build(target, spaceName, spaceContainer, rotateTarget, control, space): # In this mode, target is a list of targets and rotateTarget is an optional list of weights if not spaceName: spaceName = 'multi_orient' trueTarget = group(em=True, name=pdil.simpleName(control, '{0}_multiOrient'), p=spaceContainer) pdil.dagObj.matchTo( trueTarget, control) # It's actually important so this can follow correctly. proxies = [] for t in target: d = duplicate(trueTarget)[0] orientConstraint(t, d, mo=True) proxies.append(d) # Turn of inheritTransform so we can place it under the trueTarget to keep things tidy w/o cycle errors d.inheritsTransform.set(0) d.setParent(trueTarget) #constraint = orientConstraint( proxies, trueTarget, mo=True ) #constraint.interpType.set(2) # Shorted rotation is probably the most stable constraint = parentConstraint(proxies, trueTarget, mo=True, st=['x', 'y', 'z']) constraint.addAttr('rotTarget', at='bool', dv=True) constraint.interpType.set(2) if rotateTarget: attrs = constraint.getWeightAliasList() for v, attr in zip(rotateTarget, attrs): attr.set(v) # parentConstraint translate to it's parent so it remains in position and lets us do a full parentConstraint later parentConstraint(space.getParent(), trueTarget, mo=True, sr=['x', 'y', 'z']) return trueTarget, spaceName
def __init__(self, card): self.card = card rigData = card.rigData name = pdil.simpleName(card) #head, repeat, tail = util.parse(card.nameInfo.get()) names = rigData.get('nameInfo', {'head': [], 'repeat': '', 'tail': []}) head = ' '.join(names.get('head', [])) repeat = names.get('repeat', '') tail = ' '.join(names.get('tail', [])) side = rigData.get('mirrorCode', '') QtWidgets.QTreeWidgetItem.__init__( self, [name, '', '', head, repeat, tail, '', side]) self.setCheckState( 1, Qt.Checked if card.visibility.get() else Qt.Unchecked) self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable)
def setProfile(self, profileName): log.debug('set profile ' + profileName) self.clearControlSpaces() if not profileName: return self.curProfile = collections.OrderedDict() # Get controls from the character chooser, defaulting to all index = self.ui.characterChooser.currentIndex( ) - 1 # First item is blank so offset by 1 if index >= 0: allControls = fossil.find.controllers( main=self.mainControllers[index]) else: allControls = fossil.find.controllers() nameMap = {pdil.simpleName(ctrl): ctrl for ctrl in allControls} items = self.profiles[profileName].items() self.ui.profileTable.setRowCount(len(items)) for i, (ctrlName, space) in enumerate(items): if ctrlName in nameMap: self.curProfile[nameMap[ctrlName]] = space self.addRow(i, nameMap[ctrlName], space) elif isinstance(ctrlName, PyNode): self.curProfile[ctrlName] = space self.addRow(i, ctrlName, space) else: self.curProfile[ctrlName] = space self.addEmpty(i, ctrlName, space) self.profiles[profileName] = self.curProfile self.enableProfileGui(True)
def setOrientTarget(self, row, bpJoint, newTarget): if newTarget is None: bpJoint.orientTarget = None self.clearCustomOrient(bpJoint) self.item(row, self.JOINT_LISTER_ORIENT).setText('') elif newTarget == '-as card-': bpJoint.orientTarget = None bpJoint.customOrient = bpJoint.card self.item(row, self.JOINT_LISTER_ORIENT).setText('-as card-') elif newTarget == '-as proxy-': bpJoint.orientTarget = None bpJoint.customOrient = bpJoint self.item(row, self.JOINT_LISTER_ORIENT).setText('-as proxy-') elif newTarget == '-world-': ''' .. todo:: Make a pass to fix the old stuff then remove the code rebuilding the attr if needed. ''' # Since this used to be a message attr, rebuild it (as a string) if bpJoint.hasAttr( 'orientTargetJnt') and bpJoint.orientTargetJnt.type( ) == 'message': bpJoint.deleteAttr('orientTargetJnt') bpJoint.orientTarget = '-world-' #textFieldButtonGrp(field, e=True, tx='-world-') self.item(row, self.JOINT_LISTER_ORIENT).setText('-world-') self.clearCustomOrient(bpJoint) else: self.item(row, self.JOINT_LISTER_ORIENT).setText( pdil.simpleName(newTarget)) bpJoint.orientTarget = newTarget bpJoint.customOrient = None
def parentGroup(target): ''' Returns a group that is constrained to the parent of the target. Used to allow control hierarchies to live elsewhere. .. todo:: Get rid of parentProxy, which is dumb ''' name = pdil.simpleName(target, '{0}_Proxy') grp = group(em=True, name=name) info = jsonGet(target, 'fossilAccessoryInfo') if info.get('parent'): parentConstraint(info['parent'], grp, mo=False) # Don't constrain top level nodes since they need to follow main, not b_Root elif target.getParent() != node.getTrueRoot(): parentConstraint(target.getParent(), grp, mo=False) return grp
def customOrient(bpJoint): newNodes = importFile(os.path.dirname(__file__) + '/Axis.ma', rnn=True, renameAll=True) transform = ls(newNodes, type='transform')[0] masterGroup = proxyskel.masterGroup() for child in masterGroup.listRelatives(): if child.name() == 'customOrients': customGroup = child break else: customGroup = group(n='customOrients', p=masterGroup, em=True) transform.setParent(customGroup) transform.scale.set(3, 3, 3) transform.t.setKeyable(False) bpJoint.customOrient = transform pointConstraint(bpJoint, transform) transform.t.lock() transform.rename(pdil.simpleName(bpJoint, 'orient_{0}'))
def mainGroup(nodePool=None, fromControl=None): ''' Returns the main group, intended for building the rig where a single main group is assumed, unless nodePool is used. Args: nodePool: A list of possible nodes (like the results of importing/referencing) fromControl: Find the mainGroup in the hierarchy of this control TODO: At some point, (2023?) remove all the 'main' name assumptions and only use the attribute. ''' if fromControl: path = fromControl.fullPath().split('|')[1:] for i, name in enumerate(path): if attributeQuery(config.FOSSIL_MAIN_CONTROL, ex=True, node=name): return PyNode('|' + '|'.join(path[:i + 1])) if nodePool: for n in nodePool: if n.hasAttr( config.FOSSIL_MAIN_CONTROL) or simpleName(n) == 'main': return n if objExists('|main'): return PyNode('|main') else: # A pymel bug is sometimes returning duplicate nodes main = list( set([obj for obj in ls('main', r=True) if not obj.getParent()])) if len(main) == 1: return main[0] # No "main" ojbect was found, looking for tags plugs = ls('*.' + config.FOSSIL_MAIN_CONTROL) if plugs: return plugs[0].node() return None
def buildFreeform(joints, translatable=False, mirroredTranslate=False, scalable=False, groupName='', controlSpec={}): ''' Make an FK chain between the given joints. :param PyNode start: Start of joint chain :param PyNode end: End of chain :param bool translatable: Default=False :param bool mirroredTranslate: If true, translations will be flipped :param dict controlSpec: Override default control details here. Only has 'main'. .. todo:: I think I want to control spec housed elsewhere for easier accessibility. ''' # Make a top level section for each lead joint in the subHierarchy #topLevel = [j for j in joints if j.getParent() not in joints] topContainer = group(n=pdil.simpleName(joints[0], '{0}_Freeform'), p=node.mainGroup(), em=True) #top = container #leadOrient, leadPoint = None, None controls = [] done = {} for j in joints: done[j] = False for j in joints: ctrl = controllerShape.build( util.trimName(j) + "_ctrl", controlSpec['main'], type=controllerShape.ControlType.TRANSLATE if translatable else controllerShape.ControlType.ROTATE) controls.append(ctrl) pdil.dagObj.matchTo(ctrl, j) constraints = util.constrainTo(j, ctrl, includeScale=scalable) space = pdil.dagObj.zero(ctrl) if mirroredTranslate: space.s.set(-1, -1, -1) done[j] = (ctrl, space) # Lock unneeded transforms if not translatable: pdil.dagObj.lock(ctrl, 't') if not scalable: pdil.dagObj.lock(ctrl, 's') else: # Preserving scaling symmetry if translations are mirrored if mirroredTranslate: constraints[2].node().offsetZ.set(-1) # Parenting the controllers is challenging since they could occur in any order for jnt, (ctrl, space) in done.items(): if jnt.getParent() in done: space.setParent(done[jnt.getParent()][0]) else: container = util.parentGroup(jnt) container.setParent(topContainer) container.rename(util.trimName(jnt) + '_fkChain') space.setParent(container) # A leader must be choosen, so just use the order they were built in ctrl = pdil.nodeApi.RigController.convert(controls[0]) log.debug('The leader is {}'.format(ctrl)) for i, c in enumerate(controls[1:]): ctrl.subControl[str(i)] = c log.debug('Adding {} {}'.format(i, c)) ctrl.container = topContainer return ctrl, None # ConstraintResults(leadPoint, leadOrient )
def buildDogleg(hipJoint, end, pvLen=None, name='Dogleg', endOrientType=util.EndOrient.TRUE_ZERO_FOOT, groupName='', controlSpec={}): ''' .. todo:: * Specify toe joint instead to remove ambiguity in case of twist joints. * For some reason, sometimes, twist must be introduced because some flippin occurs. For some reason the poleVector doesn't come in straight on. * Need to determine if a 180 twist is needed as the minotaur did. * Need to figure out the best way to constrain the last joint to the controller ''' boundChain = util.getChain(hipJoint, end) container = group(n=name + '_dogHindleg', em=True, p=node.mainGroup()) # &&& I think I want to turn this into the container for all extra stuff related to a given control chainGrp = group(p=container, n=name + "_ikChain", em=True) parentConstraint(hipJoint.getParent(), chainGrp, mo=True) # Make the control to translate/offset the limb's socket. socketOffset = controllerShape.build( name + '_socket', controlSpec['socket'], type=controllerShape.ControlType.TRANSLATE) pdil.dagObj.lock(socketOffset, 'r s') pdil.dagObj.moveTo(socketOffset, hipJoint) socketZero = pdil.dagObj.zero(socketOffset) socketZero.setParent(chainGrp) footCtrl = controllerShape.build(name, controlSpec['main'], type=controllerShape.ControlType.IK) pdil.dagObj.lock(footCtrl, 's') footCtrl.addAttr('bend', at='double', k=True) pdil.dagObj.moveTo(footCtrl, end) if endOrientType == util.EndOrient.TRUE_ZERO: util.trueZeroSetup(end, footCtrl) elif endOrientType == util.EndOrient.TRUE_ZERO_FOOT: util.trueZeroFloorPlane(end, footCtrl) elif endOrientType == util.EndOrient.JOINT: pdil.dagObj.matchTo(footCtrl, end) footCtrl.rx.set(util.shortestAxis(footCtrl.rx.get())) footCtrl.ry.set(util.shortestAxis(footCtrl.ry.get())) footCtrl.rz.set(util.shortestAxis(footCtrl.rz.get())) pdil.dagObj.zero(footCtrl) elif endOrientType == util.EndOrient.WORLD: # Do nothing, it's built world oriented pass util.createMatcher(footCtrl, end).setParent(container) # Make the main ik chain which gives overall compression masterChain = util.dupChain(hipJoint, end) masterChain[0].rename(pdil.simpleName(hipJoint, '{0}_OverallCompression')) mainIk = util.ikRP('mainIk', masterChain[0], masterChain[-1]) PyNode('ikSpringSolver').message >> mainIk.ikSolver ''' mainIk = ikHandle( sol='ikRPsolver', sj=masterChain[0], ee=masterChain[-1] )[0] PyNode('ikSpringSolver').message >> mainIk.ikSolver mainIk.rename('mainIk') hide(mainIk) ''' springFixup = group(em=True, n='SprinkIkFix') springFixup.inheritsTransform.set(False) springFixup.inheritsTransform.lock() springFixup.setParent(socketOffset) pointConstraint(socketOffset, springFixup) masterChain[0].setParent(springFixup) #pointConstraint( socketOffset, hipJoint ) # Create the polevector. This needs to happen first so things don't flip out later out = util.calcOutVector(masterChain[0], masterChain[1], masterChain[-1]) if not pvLen or pvLen < 0: pvLen = util.chainLength(masterChain[1:]) * 0.5 pvPos = out * pvLen + dt.Vector( xform(boundChain[1], q=True, ws=True, t=True)) pvCtrl = controllerShape.build(name + '_pv', controlSpec['pv'], type=controllerShape.ControlType.POLEVECTOR) pdil.dagObj.lock(pvCtrl, 'r s') xform(pvCtrl, ws=True, t=pvPos) poleVectorConstraint(pvCtrl, mainIk) # Verify the knees are in the same place delta = boundChain[1].getTranslation( 'world') - masterChain[1].getTranslation('world') if delta.length() > 0.1: mainIk.twist.set(180) # Make sub IKs so the chain can be offset offsetChain = util.dupChain(hipJoint, end, '{0}_offset') hide(offsetChain[0]) #offsetChain[0].rename( 'OffsetChain' ) offsetChain[0].setParent(container) controllerShape.connectingLine(pvCtrl, offsetChain[1]) constraints = util.constrainAtoB(util.getChain(hipJoint, end), offsetChain, mo=False) pointConstraint(masterChain[0], offsetChain[0]) ankleIk = util.ikRP('hipToAnkle', offsetChain[0], offsetChain[-2]) offsetIk = util.ikRP('metatarsusIk', offsetChain[-2], offsetChain[-1]) #ankleIk = ikHandle( sol='ikRPsolver', sj=offsetChain[0], ee=offsetChain[-2])[0] #offsetIk = ikHandle( sol='ikRPsolver', sj=offsetChain[-2], ee=offsetChain[-1])[0] #offsetIk.rename('metatarsusIk') bend = controllerShape.build(name + '_bend', controlSpec['bend'], type=controllerShape.ControlType.ROTATE) pdil.dagObj.matchTo(bend, masterChain[-1]) if end.tx.get() < 0: pdil.anim.orientJoint(bend, boundChain[-2], upTarget=boundChain[-3], aim='-y', up='-x') else: pdil.anim.orientJoint(bend, boundChain[-2], upTarget=boundChain[-3], aim='y', up='x') bendZero = pdil.dagObj.zero(bend) bendZero.setParent(footCtrl) pdil.dagObj.lock(bend, 't s') orientConstraint(masterChain[-1], bendZero, mo=True) ## offsetControl = group(em=True, n='OffsetBend') ## offsetContainer = group(offsetControl, n='OffsetSpace') ## offsetContainer.setParent(footCtrl) # Setup the offsetContainer so it is properly aligned to bend on z ## offsetContainer.setParent( masterChain[-1] ) ## offsetContainer.t.set(0, 0, 0) #temp = aimConstraint( pvCtrl, offsetContainer, aim=[1, 0, 0], wut='object', wuo=hipJoint, u=[0, 1, 0]) #delete( temp ) ''' NEED TO CHANGE THE ORIENTATION Must perfectly align with ankle segment so the offset ikhandle can translate according to how much things are scaled ''' ## lib.anim.orientJoint(offsetContainer, boundChain[-2], upTarget=boundChain[-3], aim='y', up='x') #mimic old way lib.anim.orientJoint(offsetContainer, pvCtrl, upTarget=hipJoint, aim='x', up='y') #lib.anim.orientJoint(offsetContainer, pvCtrl, upTarget=hipJoint, aim='x', up='y') ## offsetControl.t.set(0, 0, 0) ## offsetControl.t.lock() ## offsetControl.r.set(0, 0, 0) ## footCtrl.bend >> offsetControl.rz ''' This is really dumb. Sometimes maya will rotate everything by 180 but I'm not sure how to calculate the proper offset, which normally results in one axis being off by 360, so account for that too. ''' temp = orientConstraint(footCtrl, offsetChain[-1], mo=True) if not pdil.math.isClose(offsetChain[-1].r.get(), [0, 0, 0]): badVals = offsetChain[-1].r.get() delete(temp) offsetChain[-1].r.set(-badVals) temp = orientConstraint(footCtrl, offsetChain[-1], mo=True) for a in 'xyz': val = offsetChain[-1].attr('r' + a).get() if abs(val - 360) < 0.00001: attr = temp.attr('offset' + a.upper()) attr.set(attr.get() - 360) elif abs(val + 360) < 0.00001: attr = temp.attr('offset' + a.upper()) attr.set(attr.get() + 360) # Hopefully the end of dumbness ankleIk.setParent(bend) # Adjust the offset ikHandle according to how long the final bone is. masterChain[-1].tx >> ankleIk.ty # if masterChain[-1].tx.get() > 0: # masterChain[-1].tx >> ankleIk.ty # else: # pdil.math.multiply(masterChain[-1].tx, -1.0) >> ankleIk.ty ankleIk.tx.lock() ankleIk.tz.lock() #ankleIk.t.lock() mainIk.setParent(footCtrl) offsetIk.setParent(footCtrl) pdil.dagObj.zero(footCtrl).setParent(container) hide(masterChain[0]) poleVectorConstraint(pvCtrl, ankleIk) poleVectorConstraint(pvCtrl, offsetIk) # Adding the pv constraint might require a counter rotation of the offsetIk counterTwist = offsetChain[-2].rx.get() * ( 1.0 if offsetChain[-2].tx.get() < 0 else -1.0) offsetIk.twist.set(counterTwist) pdil.dagObj.zero(pvCtrl).setParent(container) # Make stretchy ik, but the secondary chain needs the stretch hooked up too. util.makeStretchyNonSpline(footCtrl, mainIk) for src, dest in zip(masterChain[1:], offsetChain[1:]): src.tx >> dest.tx footCtrl = pdil.nodeApi.RigController.convert(footCtrl) footCtrl.container = container footCtrl.subControl['socket'] = socketOffset footCtrl.subControl['pv'] = pvCtrl footCtrl.subControl['bend'] = bend # Add default spaces space.addMain(pvCtrl) space.add(pvCtrl, footCtrl) space.add(pvCtrl, footCtrl, mode=space.Mode.TRANSLATE) if hipJoint.getParent(): space.add(pvCtrl, hipJoint.getParent()) space.addMain(footCtrl) space.add(footCtrl, hipJoint.getParent()) return footCtrl, constraints
def generateReposer(cards=None, placeholder=False, progress=None): ''' If no cards are specificed, a new reposer is build, otherwise it rebuilds/adds reposers for the specified cards. Args: cards placeholder progress: Optional `progressWindow` that will be `.update()`'d twice for each card, MUST be preconfigured (in case several things are updating) &&& TODO Verify the cards can be built in any order ''' global jointMapping # global'd for debugging suffix = '_placeholder' if placeholder else '' rJoints = [] rCards = [] unlock = {} # <repose joint or card>: <list of attrs to be re-locked> jointMapping = {} # Lazy "bi-directional mapping" of bpj <-> reposeJoint, both are added as keys to eachother # Build all the cards and joints if not cards: cards = find.blueprintCards() # Delete previous roots for oldRoot in getReposeRoots(): oldRoot.deleteAttr('reposeRoot') # Otherwise populate the containers with the existing reposer to build/add new stuff. else: #allExistingRCards = set( cmds.ls( '*.bpCard', o=True, r=True, l=True ) ) allExistingRJoints = set( cmds.ls( '*.bpj', o=True, r=True, l=True ) ) for oldRoot in getReposeRoots(): joints = cmds.listRelatives( str(oldRoot), f=True, ad=True, type='joint' ) joints = [PyNode(c) for c in allExistingRJoints.intersection(joints)] for rj in joints: bpj = rj.bpj.listConnections()[0] jointMapping[rj] = bpj jointMapping[bpj] = rj for card in cards: if progress: progress.update() rCard = duplicate(card, po=0)[0] showHidden(rCard) pdil.dagObj.unlock(rCard) stripReposerCard(rCard) targetName = simpleName(card, '{}_repose' + suffix) previous, attrs = reposeLink(card, rCard, 'bpCard') if not placeholder else (None, []) unlock[rCard] = attrs renameReposeObj(rCard, targetName, previous) for child in rCard.listRelatives(): if not child.type() == 'nurbsSurface': delete(child) rCards.append(rCard) makeIdentity(rCard, t=False, r=False, s=True, apply=True) pdil.dagObj.lock( rCard, 's' ) for jnt in card.joints: reposeJoint = joint(None) targetName = simpleName(jnt, '{}_repose' + suffix) previous, attrs = reposeLink(jnt, reposeJoint, 'bpj') if not placeholder else (None, []) unlock[reposeJoint] = attrs renameReposeObj(reposeJoint, targetName, previous) pdil.dagObj.matchTo(reposeJoint, jnt) #assert jnt.info.get('options', {}).get('mirroredSide', False) is False, 'parent to mirrored joints not supported yet' jointMapping[jnt] = reposeJoint jointMapping[reposeJoint] = jnt rJoints.append(reposeJoint) # Set their parents for reposeJoint in rJoints: parent = jointMapping[reposeJoint].parent if parent in jointMapping: # Check against joint mapping in case only a few selected cards a being tposed reposeJoint.setParent( jointMapping[parent] ) reposeContainer = getReposeContainer() # Put under cards, card pivot to lead joint for rCard, card in zip(rCards, cards): if progress: progress.update() bpj = card.parentCardJoint #print('BPJ - - - - ', bpj, bpj in jointMapping) if bpj in jointMapping: start = card.start() if card.joints else bpj #rCard.setParent( getRJoint(bpj) ) pdil.dagObj.unlock(rCard) #firstBpj = card.joints[0] #return isMirrored = card.isCardMirrored() mirroredSide = card.joints[0].info.get('options', {}).get('mirroredSide') #print('rCard.mirror', rCard.mirror, 'info:', mirroredSide) #if rCard.mirror is False and mirroredSide: if isMirrored is False and card.mirror is False and mirroredSide: #print('opposite mirror') rCard.setParent( makeMirrored( jointMapping[bpj] ) ) else: #print('regular side stuff') rCard.setParent( jointMapping[bpj] ) #cmds.parent(str(rCard), str(jointMapping[bpj])) xform(rCard, ws=True, piv=xform(start, q=True, t=True, ws=True) ) pdil.dagObj.lock(rCard, 't') else: if not placeholder: rCard.addAttr('reposeRoot', at='message') rCard.setParent( reposeContainer ) addVector(rCard, 'origRot', rCard.r.get()) addVector(rCard, 'origTrans', rCard.t.get()) #start = getRJoint(card.start()) start = jointMapping[card.start()] start.setParent( rCard ) pdil.dagObj.lock( start, 't s' ) if rCard in unlock: for attr in unlock[rCard]: rCard.attr(attr).unlock() rCard.attr(attr).showInChannelBox(True) for reposeJoint in rJoints: pdil.dagObj.lock(reposeJoint, 'ry rz') pdil.dagObj.lock(reposeJoint, 't s') # I can't see why I wasn't locking t/s already. Possible exception, `freeform` if reposeJoint in unlock: for attr in unlock[reposeJoint]: reposeJoint.attr(attr).unlock() reposeJoint.attr(attr).showInChannelBox(True) addVector(reposeJoint, 'origRot', reposeJoint.r.get()) addVector(reposeJoint, 'origTrans', reposeJoint.t.get()) '''
def twistSetup(control, twistJoints, startSegment, endSegment, jointLenMultiplier, twistLateralAxis=[0, 1, 0], driverLateralAxis=[0, 1, 0], defaultPower=0.5): ''' Given a list of twist joints, an anchoring startSegment and the endSegment :param twistJoints: The joints that will be twisted :param twistDriver: The end joint that will influence the twisting TwistJoints bone's aim axis = the lateral axis TwistJoints Up axis = points to the target (wrist) Assumption, all the twist joints and start segment are oriented the same World up = object rotation up obj = target (wrist) up axis = I think this is the target's lateral axis ''' #anchor = duplicate( twistJoints, po=True )[0] #anchor.rename( simpleName(jnt, '{0}Anchor') ) for jnt in twistJoints: aimer = duplicate(jnt, po=True)[0] space = duplicate(jnt, po=True)[0] saveRestLength(space) pdil.math.multiply(space.restLength, jointLenMultiplier) >> space.tx aimer.rename(simpleName(jnt, '{0}Aimer')) space.rename(simpleName(jnt, '{0}Space')) space.drawStyle.set(2) jnt.setParent(space) #hide(anchor, aimer) hide(aimer) constraint = orientConstraint(startSegment, aimer, space) constraint.interpType.set( 2) # Set to "shortest" because it will flip otherwise. aimConstraint( endSegment, aimer, wut='objectrotation', wuo=endSegment, mo=True, u=twistLateralAxis, # identifyAxis(jnt, asVector=True), # noqa e127 aimVector=[1, 0, 0], # identifyAxis(jnt, asVector=True), wu=driverLateralAxis, ) baseRotAttr, endRotAttr = constraint.getWeightAliasList() driver = drive(control, simpleName(jnt, '{0}_Auto'), endRotAttr, minVal=0, maxVal=1, dv=defaultPower) pdil.math.opposite(driver) >> baseRotAttr
def build(cls, targets, spaceName, spaceContainer, extraInfo, control, space): if not spaceName: spaceName = 'freeform' trueTarget = group(em=True, name=pdil.simpleName(control, '{0}_freeform'), p=spaceContainer) pdil.dagObj.matchTo( trueTarget, control ) # It's actually important so this can follow correctly. (line copied from MULTI_ORIENT) # Put proxies in a group that follows the "local" space. Honestly this math doesn't make # sense but it gives the results JH wants, which are sensible. proxyGrp = group(em=True, name=pdil.simpleName(control, '{0}_freeformProxies'), p=spaceContainer) pdil.dagObj.matchTo(proxyGrp, control) parentConstraint(cls.getProxy(control), proxyGrp, mo=True) rProxies = [] tProxies = [] for t, (mode, w) in zip(targets, extraInfo): d = duplicate(trueTarget)[0] d.rename(pdil.simpleName(t) + '_freeform') d.setParent(proxyGrp) if mode == cls.ORIENT: orientConstraint(t, d, mo=True) rProxies.append((d, w)) elif mode == cls.POINT: pointConstraint(t, d, mo=True) tProxies.append((d, w)) elif mode == cls.PARENT: parentConstraint(t, d, mo=True) rProxies.append((d, w)) tProxies.append((d, w)) elif mode == cls.POINT_ORIENT: orientConstraint(t, d, mo=True) pointConstraint(t, d, mo=True) rProxies.append((d, w)) tProxies.append((d, w)) elif mode == cls.PARENT_TRANS: parentConstraint(t, d, mo=True, sr=['x', 'y', 'z']) tProxies.append((d, w)) if rProxies: rConstraint = parentConstraint([p[0] for p in rProxies], trueTarget, mo=True, st=['x', 'y', 'z']) rConstraint.addAttr('rotTarget', at='bool', dv=True) rConstraint.interpType.set(2) attrs = rConstraint.getWeightAliasList() for v, attr in zip([p[1] for p in rProxies], attrs): attr.set(v) else: # If there were no rotate targets, use the parent joint or group proxy = cls.getProxy(control) if proxy: const = parentConstraint(proxy, trueTarget, mo=True, st=['x', 'y', 'z']) const.addAttr('mo_ignore', at='bool', dv=True) if tProxies: tConstraint = parentConstraint([p[0] for p in tProxies], trueTarget, mo=True, sr=['x', 'y', 'z']) attrs = tConstraint.getWeightAliasList() for v, attr in zip([p[1] for p in tProxies], attrs): attr.set(v) else: # If there were no translate targets, use the parent joint or group proxy = cls.getProxy(control) if proxy: const = parentConstraint(proxy, trueTarget, mo=True, sr=['x', 'y', 'z']) const.addAttr('mo_ignore', at='bool', dv=True) return trueTarget, spaceName
def add(control, target, spaceName='', modeName=common.Mode.ROTATE_TRANSLATE, enum=True, rotateTarget=None): ''' Add a space to the given control. Args: control: Add the space to it target: What to target modeName: String name for what type of space enum: DEPRECATED unused rotateTarget: Now a catch-all for lots of additional data depending on the mode ''' # Early outs if not target: print("No target specified") return False for targetInfo in getTargetInfoBD(control): if targetInfo.type == modeName and targetInfo.target == target: print("Target already exists", modeName, target) return False if spaceName in common.getNames(control): return False # End early outs rotateLocked = False translateLocked = False # If the control can't translate, make sure the mode is rotate-only. if control.tx.isLocked() and control.ty.isLocked() and control.tz.isLocked( ): if modeName != 'MULTI_ORIENT': modeName = 'ROTATE' translateLocked = True if control.rx.isLocked() and control.ry.isLocked() and control.rz.isLocked( ): rotateLocked = True with pdil.dagObj.TemporaryUnlock(control, trans=not translateLocked, rot=not rotateLocked): space = pdil.dagObj.zero(control, apply=False) # &&& Hack to not make two user groups, unsure which direction is the best. # The class probably should make the object but I can't remember if that causes some side effect. spaceContainer = common.getGroup( modeName, main=find.mainGroup( fromControl=control)) if modeName != 'USER' else None # ----------------------- # ACTUAL SPACE ADDED HERE # Call the appropriate sub function to build the particulars of the space trueTarget, spaceName = common.Mode.build(modeName, target, spaceName, spaceContainer, rotateTarget, control, space) # ----------------------- if not spaceName: spaceName = pdil.simpleName(target) existingNames = common.getNames(control) + [spaceName] if not control.hasAttr(common.ENUM_ATTR): control.addAttr(common.ENUM_ATTR, at='enum', enumName='FAKE', k=True) common.setNames(control, existingNames) choice = getChoiceNode(control) if not choice: choice = makeChoiceNode() decomp = createNode('decomposeMatrix') choice.output >> decomp.inputMatrix control.space >> choice.selector else: decomp = choice.output.listConnections()[0] offsetMatrix = addTarget(choice, trueTarget, control, space, choice.input.numElements()) offsetMatrix.addAttr(common.SPACE_TYPE_NAME, dt='string') offsetMatrix.attr(common.SPACE_TYPE_NAME).set(modeName) if len( existingNames ) == 1: # At this stage, this means this is the first space so decomp needs hookup. decomp.outputTranslate >> space.t decomp.outputRotate >> space.r return True
def buildSplineTwist(start, end, controlCountOrCrv=4, twistInfDist=0, simplifyCurve=True, tipBend=True, sourceBend=True, matchOrient=True, allowOffset=True, # noqa e128 useLeadOrient=False, # This is an backwards compatible option, mutually exclusive with matchOrient twistStyle=TwistStyle.ADVANCED, duplicateCurve=True, controlOrient=OrientMode.CLOSEST_JOINT, name='', groupName='', controlSpec={}): ''' Make a spline controller from `start` to `end`. :param int twistInfDist: Default twist controls to falloff before hitting eachother. Otherwise it is the number of joints on either side it will influence. :param bool simplifyCurve: Only used if # of cvs is specified. Turning it on will likely result it the curve not matching the existing joint position but will be more evenly spaced per control. :param bool tipBend: If True, an extra cv will be added at the second to last joint, controlled by the last controller to ease out. ##:param bool applyDirectly: If True, rig the given joints, do not make a duplicate chain :param bool useLeadOrient: If True, the controllers will be aligned the same as the first joint. **NOTE** I think this option only exists to preserve previous builds, this is pretty dumb :param bool matchOrient: Does trueZero on the start and end. I'm not sure this makes sense. .. todo:: * Add the same spline chain +X towards child that the neck has and test out advancedTwist() * See if I can identify the closest joint to a control and orient to that * The first joint has parent AND local, which are the same thing, keep this for convenience of selecting all the controls and editing attrs? * Test specifying your own curve * There is a float division error that can happen if there are too many control cvs. * Verify twists work right with unsimplified curves (hint, I don't think they do). ''' matchOrient = False useLeadOrient = False if isinstance( controlCountOrCrv, int ): assert controlCountOrCrv > 3, "controlCount must be at least 4" # The axis to twist and stretch on. jointAxis = util.identifyAxis( start.listRelatives(type='joint')[0] ) # Make a duplicate chain for the IK that will also stretch. stretchingChain = util.dupChain( start, end, '{0}_stretch' ) # &&& NOTE! This might affect advanced twist in some way. # If the chain is mirrored, we need to reorient to point down x so the # spline doesn't mess up when the main control rotates if stretchingChain[1].tx.get() < 0: # Despite aggresive zeroing of the source, the dup can still end up slightly # off zero so force it. for jnt in stretchingChain: jnt.r.set(0, 0, 0) joint( stretchingChain[0], e=True, oj='xyz', secondaryAxisOrient='yup', zso=True, ch=True) joint( stretchingChain[-1], e=True, oj='none') if isinstance( controlCountOrCrv, int ): mainIk, _effector, crv = ikHandle( sol='ikSplineSolver', sj=stretchingChain[0], ee=stretchingChain[-1], ns=controlCountOrCrv - 3, simplifyCurve=simplifyCurve) else: if duplicateCurve: crv = duplicate(controlCountOrCrv)[0] else: crv = controlCountOrCrv mainIk, _effector = ikHandle( sol='ikSplineSolver', sj=stretchingChain[0], ee=stretchingChain[-1], ccv=False, pcv=False) crv.getShape().worldSpace[0] >> mainIk.inCurve hide(mainIk) mainIk.rename( pdil.simpleName(start, "{0}_ikHandle") ) crv.rename( pdil.simpleName(start, "{0}_curve") ) if not name: name = util.trimName(start) if name.count(' '): name, endName = name.split() else: endName = '' # Only add a tipBend cv if number of cvs was specified. if tipBend and isinstance( controlCountOrCrv, int ): currentTrans = [ xform(cv, q=True, ws=True, t=True) for cv in crv.cv ] insertKnotCurve( crv.u[1], nk=1, add=False, ib=False, rpo=True, cos=True, ch=True) for pos, cv in zip(currentTrans, crv.cv[:-2]): xform( cv, ws=True, t=pos ) xform( crv.cv[-2], ws=True, t=xform(end.getParent(), q=True, ws=True, t=True) ) xform( crv.cv[-1], ws=True, t=currentTrans[-1] ) # Only add a sourceBend cv if number of cvs was specified. if sourceBend and isinstance( controlCountOrCrv, int ): currentTrans = [ xform(cv, q=True, ws=True, t=True) for cv in crv.cv ] insertKnotCurve( crv.u[1.2], nk=1, add=False, ib=False, rpo=True, cos=True, ch=True) # I honestly don't know why, but 1.2 must be different than 1.0 for pos, cv in zip(currentTrans[1:], crv.cv[2:]): xform( cv, ws=True, t=pos ) xform( crv.cv[0], ws=True, t=currentTrans[0] ) xform( crv.cv[1], ws=True, t=xform(stretchingChain[1], q=True, ws=True, t=True) ) grp = group(em=True, p=node.mainGroup(), n=start.name() + "_splineTwist") controls = util.addControlsToCurve(name + 'Ctrl', crv, controlSpec['main']) for ctrl in controls: pdil.dagObj.zero(ctrl).setParent( grp ) if controlOrient == OrientMode.CLOSEST_JOINT: # Use the real chain to match orientations since the stretching chain might reorient to compensate for mirroring. jointPos = {j: dt.Vector(xform(j, q=True, ws=True, t=True)) for j in util.getChain(start, end)} aveSpacing = util.chainLength(stretchingChain) / (len(stretchingChain) - 1) for ctrl in controls: cpos = dt.Vector(xform(ctrl, q=True, ws=True, t=True)) distances = [ ( (jpos - cpos).length() / aveSpacing, j) for j, jpos in jointPos.items() ] distances.sort() ''' Just use the closest joint if within 10% of the average spacing Possible future improvement, look at two joints, and determine if the control is between them and inbetween the orientation. ''' if True: # distances[0][0] < 100: r = xform(distances[0][1], q=True, ro=True, ws=True) with pdil.dagObj.Solo(ctrl): xform(ctrl, ro=r, ws=True) pdil.dagObj.zero(ctrl) if endName: controls[-1].rename(endName + 'Ctrl') if matchOrient: util.trueZeroSetup(start, controls[0]) util.trueZeroSetup(end, controls[-1]) if tipBend: if useLeadOrient and not matchOrient: controls[-1].setRotation( end.getRotation(space='world'), space='world' ) parent( controls[-2].getChildren(), controls[-1] ) name = controls[-2].name() delete( pdil.dagObj.zero(controls[-2]) ) if not endName: controls[-1].rename(name) controls[-2] = controls[-1] controls.pop() #core.dagObj.zero(controls[-2]).setParent(controls[-1]) #channels = [t + a for t in 'trs' for a in 'xyz'] #for channel in channels: # controls[-2].attr( channel ).setKeyable(False) # controls[-2].attr( channel ).lock() if sourceBend: names = [] for ctrl in controls[1:-1]: names.append( ctrl.name() ) ctrl.rename( '__temp' ) endNum = -1 if endName else None for name, cur in zip(names, controls[2:endNum] ): cur.rename(name) if useLeadOrient and not matchOrient: controls[0].setRotation( start.getRotation(space='world'), space='world' ) parent( controls[1].getChildren(), controls[0] ) delete( pdil.dagObj.zero(controls[1]) ) del controls[1] controls[0] = pdil.nodeApi.RigController.convert(controls[0]) controls[0].container = grp stretchAttr, jointLenMultiplier = util.makeStretchySpline(controls[0], mainIk) connectingCurve = addConnectingCurve(controls) controls[0].visibility >> connectingCurve.visibility # Make twist for everything but hide them all and drive the ones that overlap # with spline controllers by the spline control. if not twistInfDist: numJoints = countJoints(start, end) twistInfDist = int(math.ceil( numJoints - len(controls) ) / float(len(controls) - 1)) twistInfDist = max(1, twistInfDist) noInherit = group(em=True, p=grp, n='NoInheritTransform') pdil.dagObj.lock(noInherit) noInherit.inheritsTransform.set(False) noInherit.inheritsTransform.lock() # &&& If simplify curve is ON, the last joint gets constrained to the spinner? # Otherwise it gets constrained to the offset or stretch joint, which I think is correct. if allowOffset: # If allowOffset, make another chain to handle the difference in joint positions. offsetChain = util.dupChain( start, end, '{0}_offset' ) offsetChain[0].setParent(noInherit) hide(offsetChain[0]) twists, constraints = addTwistControls( offsetChain, start, end, twistInfDist) finalRigJoint = offsetChain[-1] else: twists, constraints = addTwistControls( stretchingChain, start, end, twistInfDist ) finalRigJoint = stretchingChain[-1] # Constrain the end to the last controller so it doesn't pop off at all, # but still respect the stretch attr. pointConstraint(finalRigJoint, end, e=True, rm=True) # Make a proxy that can allows respecting stretch being active or not. endProxy = duplicate(end, po=True)[0] endProxy.rename('endProxy') hide(endProxy) endProxy.setParent(grp) stretchAttr >> pdil.constraints.pointConst( controls[-1], endProxy, mo=True ) pdil.math.opposite(stretchAttr) >> pdil.constraints.pointConst( finalRigJoint, endProxy ) constraints.point >> pdil.constraints.pointConst( endProxy, end ) hide(twists) numControls = len(controls) numTwists = len(twists) for i, ctrl in enumerate(controls): index = int(round( i * ((numTwists - 1) / (numControls - 1)) )) util.drive( ctrl, 'twist', twists[index].attr('r' + jointAxis) ) space.add( ctrl, start.getParent(), 'local' ) parents = [start.getParent()] + controls[:-1] stretchingChain[0].setParent(noInherit) crv.setParent(noInherit) hide(crv, stretchingChain[0]) connectingCurve.setParent( noInherit ) mainIk.setParent(grp) # Do not want to scale but let rotate for "fk-like" space mode for ctrl, _parent in zip(controls, parents): pdil.dagObj.lock( ctrl, 's' ) if useLeadOrient: ctrl.setRotation( start.getRotation(space='world'), space='world' ) pdil.dagObj.zero(ctrl) space.addMain(ctrl) space.add( ctrl, _parent, 'parent') for i, ctrl in enumerate(controls[1:]): controls[0].subControl[str(i)] = ctrl # Must constrain AFTER controls (possibly) get orientd orientConstraint( controls[-1], finalRigJoint, mo=True ) # Setup advanced twist if twistStyle == TwistStyle.ADVANCED: # &&& Test using advancedTwist() to replace the code beloew util.advancedTwist(stretchingChain[0], stretchingChain[1], controls[0], controls[-1], mainIk) ''' startAxis = duplicate( start, po=True )[0] startAxis.rename( 'startAxis' ) startAxis.setParent( controls[0] ) endAxis = duplicate( start, po=True )[0] endAxis.rename( 'endAxis' ) endAxis.setParent( controls[-1] ) endAxis.t.set(0, 0, 0) mainIk.dTwistControlEnable.set(1) mainIk.dWorldUpType.set(4) startAxis.worldMatrix[0] >> mainIk.dWorldUpMatrix endAxis.worldMatrix[0] >> mainIk.dWorldUpMatrixEnd hide(startAxis, endAxis) ''' else: if twistStyle == TwistStyle.X: controls[-1].rx >> mainIk.twist elif twistStyle == TwistStyle.NEG_X: pdil.math.multiply(controls[-1].rx, -1.0) >> mainIk.twist elif twistStyle == TwistStyle.Y: controls[-1].ry >> mainIk.twist elif twistStyle == TwistStyle.NEG_Y: pdil.math.multiply(controls[-1].ry, -1.0) >> mainIk.twist elif twistStyle == TwistStyle.Z: controls[-1].rz >> mainIk.twist elif twistStyle == TwistStyle.NEG_Z: pdil.math.multiply(controls[-1].rz, -1.0) >> mainIk.twist # To make .twist work, the chain needs to follow parent joint follow = group(em=True, p=grp) target = start.getParent() pdil.dagObj.matchTo(follow, stretchingChain[0]) parentConstraint( target, follow, mo=1 ) follow.rename(target + '_follow') stretchingChain[0].setParent(follow) # Constraint the offset (if exists) to the stretch last to account for any adjustments. if allowOffset: util.constrainAtoB(offsetChain[:-1], stretchingChain[:-1]) pointConstraint(stretchingChain[-1], offsetChain[-1], mo=True) return controls[0], constraints