def poleVectorMatrixConstraint(ik_handle, control): """ Creates a pole vector matrix constraint from the given ik_handle's start joint driven by the given pole vector ctrl. Thanks to Wasim Khan for this method. https://github.com/wasimk/maya-pole-vector-constaint Args: ik_handle (pm.nodetypes.IkHandle): IK Handle that will be driven by pole vector control. control (pm.nodetypes.Transform): Control that will drive the ik_handle rotations. Returns: (list): All nodes created. """ if ik_handle.nodeType() != 'ikHandle': pm.error(ik_handle.nodeName() + ' is not an IK Handle!') attribute.addSeparator(control) pm.addAttr(control, at='float', ln='poleVectorWeight', dv=1, min=0, max=1, k=True, w=True, r=True) joint = pm.PyNode(pm.ikHandle(ik_handle, q=True, startJoint=True)) # First create all necessary nodes needed for pole vector math world_point = pm.createNode('pointMatrixMult', name='{}_Position_PointMatrixMult'.format(joint)) compose_matrix = pm.createNode('composeMatrix', name='{}_Position_CompMatrix'.format(joint)) inverse_matrix = pm.createNode('inverseMatrix', name='{}_Position_InverseMatrix'.format(joint)) multiplication_matrix = pm.createNode('multMatrix', name='{}_PolePosition_MultMatrix'.format(ik_handle)) pole_matrix = pm.createNode('pointMatrixMult', name='{}_PolePosition_PointMatrixMult'.format(ik_handle)) blend_colors = pm.createNode('blendColors', name='{}_Blend_PoleWeight'.format(ik_handle)) blend_colors.color2.set(ik_handle.poleVector.get()) # Since ikHandle is setting rotation value on startJoint, we can't connect worldMatrix right away. # In order to avoid cycle, Compose world space position for start joint with pointMatrixMult node # Connecting position attribute and parentMatrix will give us worldSpace position joint.translate >> world_point.inPoint joint.parentMatrix >> world_point.inMatrix # Now composeMatrix from output, so we can inverse and find local position from startJoint to pole control world_point.output >> compose_matrix.inputTranslate compose_matrix.outputMatrix >> inverse_matrix.inputMatrix control.worldMatrix >> multiplication_matrix.matrixIn[0] inverse_matrix.outputMatrix >> multiplication_matrix.matrixIn[1] # Now connect outputs multiplication_matrix.matrixSum >> pole_matrix.inMatrix pole_matrix.output >> blend_colors.color1 blend_colors.output >> ik_handle.poleVector control.poleVectorWeight >> blend_colors.blender return [blend_colors, pole_matrix, multiplication_matrix, inverse_matrix, compose_matrix, world_point]
def create(driver, name=''): """ Creates the switcher control in charge of switching between FK and IK chains. Args: driver (pm.nodetypes.Transform): Transform where switcher will be created and will drive switcher's transform. name (string): Prefix name for switcher control. Returns: (pm.nodetypes.Transform): Switcher control created. """ name = name + pcfg.switcher_suffix switcher_control = control.create(driver, name=name, shape=curve.cube, scale=0.5) attribute.addSeparator(switcher_control) attribute.nonKeyableCompound(switcher_control) switcher_control.addAttr(pcfg.fk_ik_attribute, k=True, dv=0, hsx=True, hsn=True, smn=0, smx=1) switcher_control.addAttr(pcfg.switcher_transforms, dt='string', k=False, h=True, s=True) switcher_control.addAttr(pcfg.switcher_fk, dt='string', k=False, h=True, s=True) switcher_control.addAttr(pcfg.switcher_ik, dt='string', k=False, h=True, s=True) switcher_control.addAttr(pcfg.switcher_reverses, dt='string', k=False, h=True, s=True) driver.worldMatrix >> switcher_control.offsetParentMatrix return switcher_control
def createRig(name=''): """ Creates the node that houses all rig nodes. Args: name (string): If given, will use the given name as the name for the rig node. Returns: (pm.nodetypes.piperRig): Rig node created. """ name = name if name else 'piperRig' piper_rig = create('piperRig', 'burnt orange', name=name) piper_rig._.lock() attribute.nonKeyable(piper_rig.highPolyVisibility) attribute.lockAndHideCompound(piper_rig) attribute.addSeparator(piper_rig) return piper_rig
def create(spaces=None, transform=None, direct=False, warn=True): """ Creates the given spaces on the given transform. Args: spaces (iterator): A bunch of pm.nodetypes.Transform(s) that will drive the given transform. transform (pm.nodetypes.Transform): Transform to have ability to switch between given spaces. direct (boolean): If False, will plug output matrix into offsetParentMatrix, else direct connection. warn (boolean): If True, will warn about any existing spaces on given transform that clash with given spaces. Returns: (list): Name of space attribute(s) made. """ if not spaces and not transform: selected = pm.selected() spaces = selected[:-1] transform = selected[-1] if len(selected) < 2: pm.error('Not enough transforms selected!') orient_matrix = None space_attributes = [] parent = transform.getParent() transform_name = transform.name(stripNamespace=True) matrix_blend = attribute.getMessagedSpacesBlender(transform) if not matrix_blend: # create and hook up matrix blend matrix_blend = pm.createNode('blendMatrix', n=transform_name + pcfg.space_blend_matrix_suffix) attribute.addSpaceMessage(matrix_blend, transform) target = matrix_blend.attr('target[0]') offset_parent_matrix_plug = attribute.getSourcePlug( transform.offsetParentMatrix) if offset_parent_matrix_plug: offset_parent_matrix_plug >> matrix_blend.inputMatrix else: offset_matrix = transform.offsetParentMatrix.get() matrix_blend.inputMatrix.set(offset_matrix) if direct: multiply = pm.createNode('multMatrix', n=transform_name + '_blendOffset_MM') multiply.matrixIn[0].set(transform.matrix.get()) matrix_blend.outputMatrix >> multiply.matrixIn[1] decompose = pm.createNode('decomposeMatrix', n=transform_name + '_blend_DM') multiply.matrixSum >> decompose.inputMatrix decompose.outputTranslate >> transform.translate decompose.outputRotate >> transform.rotate decompose.outputScale >> transform.scale else: matrix_blend.outputMatrix >> transform.offsetParentMatrix # counter drive parent to create a world space if parent: parent.worldInverseMatrix >> target.targetMatrix # create attributes on transform and add world space by default attribute.addSeparator(transform) transform.addAttr(pcfg.space_use_translate, at='bool', dv=1, k=True) transform.addAttr(pcfg.space_use_rotate, at='bool', dv=1, k=True) transform.addAttr(pcfg.space_use_orient, at='bool', dv=0, k=True) transform.addAttr(pcfg.space_use_scale, at='bool', dv=1, k=True) transform.addAttr(pcfg.spaces_name, dt='string', k=False, h=True, s=True) transform.addAttr(pcfg.space_world_name, k=True, dv=0, hsx=True, hsn=True, smn=0, smx=1) _connect(transform, target, transform.attr(pcfg.space_world_name)) space_attributes.append(pcfg.space_world_name) # used for orient position = attribute.getSourcePlug(matrix_blend.inputMatrix) for space in spaces: space_name = space.name(stripNamespace=True) space_attribute = space_name + pcfg.space_suffix if transform.hasAttr(space_attribute): pm.warning(space_attribute + ' already exists on ' + transform_name) if warn else None continue transform.addAttr(space_attribute, k=True, dv=0, hsx=True, hsn=True, smn=0, smx=1) target = attribute.getNextAvailableTarget(matrix_blend, 1) target_plug = space.worldMatrix # make multiply matrix node and hook it up if direct or parent: multiply = pm.createNode('multMatrix', n='space_{}_To_{}_MM'.format( transform_name, space_name)) is_space_plugged = False inverse_matrix_plug = multiply.matrixIn[1] # direct connections require offset if direct: offset = transform.parentMatrix.get( ) * space.worldInverseMatrix.get() multiply.matrixIn[0].set(offset) target_plug >> multiply.matrixIn[1] inverse_matrix_plug = multiply.matrixIn[2] is_space_plugged = True # counter drive parent if parent: None if is_space_plugged else target_plug >> multiply.matrixIn[ 0] parent.worldInverseMatrix >> inverse_matrix_plug target_plug = multiply.matrixSum # if has input matrix, then create an orient space if position: orient_name = '{}_X_{}_OM'.format(transform_name, space_name) orient_matrix = pipernode.createOrientMatrix(position, target_plug, name=orient_name) target_plug = orient_matrix.output target_plug >> target.targetMatrix _connect(transform, target, transform.attr(space_attribute), orient_matrix) space_attributes.append(space_attribute) # update the spaces attribute old_spaces = getAll(transform) updated_spaces = old_spaces + space_attributes transform.attr(pcfg.spaces_name).set(', '.join(updated_spaces)) return space_attributes
def create(spaces, transform, direct=False): """ Creates the given spaces on the given transform. Args: spaces (iterator): A bunch of pm.nodetypes.Transform(s) that will drive the given transform. transform (pm.nodetypes.Transform): Transform to have ability to switch between given spaces. direct (boolean): If False, will plug output matrix into offsetParentMatrix, else direct connection. Returns: (list): Name of space attribute(s) made. """ space_attributes = [] parent = transform.getParent() transform_name = transform.name(stripNamespace=True) matrix_blend = transform.offsetParentMatrix.listConnections() has_spaces = matrix_blend and transform.hasAttr(pcfg.space_world_name) if has_spaces: # define matrix blend from the matrix plug matrix_blend = matrix_blend[0] if matrix_blend.nodeType() != 'blendMatrix': pm.error(transform.nodeName() + ' has wrong type: ' + matrix_blend.nodeName() + ' in offsetParentMatrix') else: # create and hook up matrix blend matrix_blend = pm.createNode('blendMatrix', n=transform_name + pcfg.space_blend_matrix_suffix) target = matrix_blend.attr('target[0]') offset_matrix = transform.offsetParentMatrix.get() matrix_blend.inputMatrix.set(offset_matrix) if direct: multiply = pm.createNode('multMatrix', n=transform_name + '_blendOffset') multiply.matrixIn[0].set(transform.matrix.get()) matrix_blend.outputMatrix >> multiply.matrixIn[1] decompose = pm.createNode('decomposeMatrix', n=transform_name + '_blendDecompose') multiply.matrixSum >> decompose.inputMatrix decompose.outputTranslate >> transform.translate decompose.outputRotate >> transform.rotate decompose.outputScale >> transform.scale else: matrix_blend.outputMatrix >> transform.offsetParentMatrix # counter drive parent to create a world space if parent: parent.worldInverseMatrix >> target.targetMatrix # create attributes on transform and add world space by default attribute.addSeparator(transform) transform.addAttr(pcfg.space_use_translate, at='bool', dv=1, k=True) transform.addAttr(pcfg.space_use_rotate, at='bool', dv=1, k=True) transform.addAttr(pcfg.space_use_scale, at='bool', dv=1, k=True) transform.addAttr(pcfg.spaces_name, dt='string', k=False, h=True, s=True) transform.addAttr(pcfg.space_world_name, k=True, dv=0, hsx=True, hsn=True, smn=0, smx=1) _connect(transform, target, transform.attr(pcfg.space_world_name)) space_attributes.append(pcfg.space_world_name) for space in spaces: space_name = space.name(stripNamespace=True) space_attribute = space_name + pcfg.space_suffix transform.addAttr(space_attribute, k=True, dv=0, hsx=True, hsn=True, smn=0, smx=1) target = attribute.getNextAvailableTarget(matrix_blend, 1) # make multiply matrix node and hook it up offset = transform.parentMatrix.get() * space.worldInverseMatrix.get() multiply = pm.createNode('multMatrix', n='space_{}_To_{}_multMatrix'.format(transform_name, space_name)) multiply.matrixIn[0].set(offset) space.worldMatrix >> multiply.matrixIn[1] # counter drive parent if parent: parent.worldInverseMatrix >> multiply.matrixIn[2] multiply.matrixSum >> target.targetMatrix _connect(transform, target, transform.attr(space_attribute)) space_attributes.append(space_attribute) # update the spaces attribute old_spaces = getAll(transform) updated_spaces = old_spaces + space_attributes transform.attr(pcfg.spaces_name).set(', '.join(updated_spaces)) return space_attributes
def reverse(driver, target, driven_negate=None, transform=None, switcher_ctrl=None, shape=curve.square, axis=None): """ Creates a control that offsets the given target through rotation (usually foot roll reverse rig). Args: driver (pm.nodetypes.Transform): The transform that drives the whole chain. Usually the IK handle. target (pm.nodetypes.Transform): Transform that will have control rotations added to. Usually end joint. driven_negate (pm.nodetypes.Transform): Transform that will have control rotations subtracted from. Usually any controls further down the chain/hierarchy of the given target. transform (pm.nodetypes.Transform): Transform for making the control. Useful for figuring out control size too. If None given, will try to use given driven_negate, if no driven_negate, will try to use given target. switcher_ctrl (pm.nodetypes.Transform): Transform that handles switching between FK and IK chains. shape (method): Creates the shape control that will drive reverse rig system. axis (string): Direction control will be facing when created. Returns: (pm.nodetypes.Transform): Control created. """ if not transform: transform = driven_negate if driven_negate else target # attempt to deduce axis if transform only has one child and axis is not given if not axis and transform.getChildren() and len(transform.getChildren()) == 1: axis_vector = pipermath.getOrientAxis(transform, transform.getChildren()[0]) axis = convert.axisToString(axis_vector) axis = convert.axisToTriAxis(axis)[1] # create control name = transform.name() + '_reverse' driver_parent = driver.getParent() ctrl = control.create(transform, shape, name, axis, 'burnt orange', 0.5, True, parent=driver_parent) name = ctrl.name() pm.parent(driver, ctrl) attribute.lockAndHideCompound(ctrl, ['t', 's']) target_source = target.rotate.connections(scn=True, plugs=True, destination=False) # add control's rotation to whatever is connected to target's rotate. if target_source: target_source = target_source[0] plus = pm.createNode('plusMinusAverage', n='_'.join([target.name(), 'plus', ctrl.name()])) target_source >> plus.input3D[0] ctrl.rotate >> plus.input3D[1] plus.output3D >> target.rotate else: ctrl.rotate >> target.rotate # if no driven negate given or driven negate is not being offset by the offsetParentMatrix, we are finished here if not driven_negate or not driven_negate.offsetParentMatrix.connections(scn=True, plugs=True, destination=False): return ctrl # decompose and compose matrices to get rotation value subtracted with control's rotation source_matrix = driven_negate.offsetParentMatrix.connections(scn=True, plugs=True, destination=False)[0] source_name = source_matrix.node().name() decomp_matrix = pm.createNode('decomposeMatrix', n=source_name + '_DM') compose_matrix = pm.createNode('composeMatrix', n=source_name + '_CM') source_matrix >> decomp_matrix.inputMatrix decomp_matrix.outputTranslate >> compose_matrix.inputTranslate decomp_matrix.outputScale >> compose_matrix.inputScale minus = pm.createNode('plusMinusAverage', n='_'.join([source_name, 'minus', name])) minus.operation.set(2) decomp_matrix.outputRotate >> minus.input3D[0] ctrl.rotate >> minus.input3D[1] minus.output3D >> compose_matrix.inputRotate compose_matrix.outputMatrix >> driven_negate.offsetParentMatrix attribute.addReverseMessage(ctrl, driven_negate) if switcher_ctrl: # add reverse control to switcher data and connect ik visibility onto reverse control switcher.addData(switcher_ctrl.attr(pcfg.switcher_reverses), [name]) switcher_attribute = switcher_ctrl.attr(pcfg.fk_ik_attribute) switcher_attribute >> ctrl.lodVisibility # add proxy fk_ik attribute to ctrl attribute.addSeparator(ctrl) ctrl.addAttr(pcfg.proxy_fk_ik, proxy=switcher_attribute, k=True, dv=0, hsx=True, hsn=True, smn=0, smx=1) # only make driven_negate by affected if IK is set to True blend = pm.createNode('blendMatrix', n=source_name + '_negateBlend') source_matrix >> blend.inputMatrix compose_matrix.outputMatrix >> blend.target[0].targetMatrix switcher_attribute >> blend.target[0].weight blend.outputMatrix >> driven_negate.offsetParentMatrix return ctrl
def banker(joint, ik_control, pivot_track=None, side='', use_track_shape=True): """ Creates a reverse foot control that changes pivot based on curve shape and control rotation input. Useful for banking. Args: joint (pm.nodetypes.Joint): Joint that will be driven by the reverse module and IK handle. ik_control (pm.nodetypes.Transform): Control that drives the IK Handle. pivot_track (pm.nodetypes.Transform): With a NurbsCurve shape as child that will act as the track for the pivot. side (string or None): Side to generate cross section use_track_shape (boolean): If True, will use the pivot track shape as the control shape Returns: (pm.nodetypes.Transform): Control that moves the reverse foot pivot. """ joint_name = joint.name() prefix = joint_name + '_' axis = pipermath.getOrientAxis(joint.getParent(), joint) axes = convert.axisToTriAxis(axis) if not side: side = pcu.getSide(joint_name) # get the IK handle and validate there is only one ik_handle = list(set(ik_control.connections(skipConversionNodes=True, type='ikHandle'))) if len(ik_handle) != 1: pm.error('Needed only ONE ik_handle. {} found.'.format(str(len(ik_handle)))) ik_handle = ik_handle[0] # create a pivot track (cross section curve) if no pivot track (curve) is given if not pivot_track: # if IK joint given, get the name of the regular joint by stripping the ik prefix if joint_name.startswith(pcfg.ik_prefix): stripped_name = pcu.removePrefixes(joint_name, pcfg.ik_prefix) namespace_name = pcfg.skeleton_namespace + ':' + stripped_name search_joint = pm.PyNode(stripped_name) if pm.objExists(stripped_name) else pm.PyNode(namespace_name) else: search_joint = joint # tries to get the meshes influenced by the skin cluster connected to the joint skins = search_joint.future(type='skinCluster') meshes = {mesh for skin in skins for mesh in pm.skinCluster(skin, q=True, g=True)} if skins else None # create the pivot track curve pm.select(cl=True) pivot_track = curve.originCrossSection(meshes, side=side, name=prefix + 'pivotTrack') # validate that only one is made if len(pivot_track) != 1: pm.error('Needed only ONE curve! {} curves made. Try to specify a side.'.format(str(len(pivot_track)))) pivot_track = pivot_track[0] # create the pivot and the normalized pivot, move the norm pivot to joint and then to floor pivot = pm.group(em=True, name=prefix + 'Pivot') normalized_pivot = pm.group(em=True, name=prefix + 'normalizedPivot') pm.matchTransform(normalized_pivot, joint, pos=True, rot=False, scale=False) normalized_pivot.ty.set(0) xform.toOffsetMatrix(normalized_pivot) # figure out control size, create control, lock and hide axis, translate, and scale if ik_control.hasAttr(pcfg.proxy_fk_ik): switcher_control = switcher.get(ik_control) transforms = switcher.getData(switcher_control.attr(pcfg.switcher_transforms), cast=True) size = control.calculateSize(transforms[-1]) else: size = None if use_track_shape: ctrl = pm.duplicate(pivot_track, n=prefix + 'bank')[0] curve.color(ctrl, 'burnt orange') else: ctrl = control.create(joint, shape=curve.plus, name=prefix + 'bank', axis=axes[0], color='burnt orange', matrix_offset=True, size=size, inner=.125, outer=1.25) attribute.lockAndHide(ctrl.attr('r' + axes[0])) attribute.lockAndHideCompound(ctrl, ['t', 's']) # node to add small number small_add = pm.createNode('plusMinusAverage', n=prefix + 'plusSmallNumber') small_add.input1D[0].set(0.001) normalize_node = pm.createNode('vectorProduct', n=prefix + 'pivotNormal') normalize_node.operation.set(0) normalize_node.normalizeOutput.set(True) # adding a small amount to avoid division by zero ctrl.attr('r' + axes[1]) >> small_add.input1D[1] small_add.output1D >> normalize_node.attr('input1' + axes[2].upper()) # need to multiply the rotation by -1 negative_mult = pm.createNode('multDoubleLinear', n=prefix + 'negative') ctrl.attr('r' + axes[2]) >> negative_mult.input1 negative_mult.input2.set(-1) normalize_input_attribute = negative_mult.output normalize_input_attribute >> normalize_node.attr('input1' + axes[1].upper()) normalize_node.output >> normalized_pivot.translate # creating the normalized (circle) version of the cross section positions = [] duplicate_curve = pm.duplicate(pivot_track)[0] pm.move(0, 0, 0, duplicate_curve, rpr=True) cvs = duplicate_curve.numCVs() for cv in range(0, cvs): position = duplicate_curve.cv[cv].getPosition(space='world') position.normalize() positions.append(position) # delete the duplicate and finally make the normalize track. Make sure to close the curve and center pivots pm.delete(duplicate_curve) normalized_track = pm.curve(d=1, p=positions, k=range(len(positions)), ws=True, n=prefix + 'normalizedTrack') normalized_track = pm.closeCurve(normalized_track, replaceOriginal=True)[0] pm.xform(normalized_track, centerPivots=True) # move normalized track to joint, then to floor, and freeze transforms pm.matchTransform(normalized_track, joint, pos=True, rot=False, scale=False) normalized_track.ty.set(0) myu.freezeTransformations(normalized_track) decomposed_matrix = pm.createNode('decomposeMatrix', n=normalize_node + '_decompose') normalized_pivot.worldMatrix >> decomposed_matrix.inputMatrix nearest_point = pm.createNode('nearestPointOnCurve', n=prefix + 'nearestPoint') decomposed_matrix.outputTranslate >> nearest_point.inPosition normalized_track.getShape().worldSpace >> nearest_point.inputCurve curve_info = pm.createNode('pointOnCurveInfo', n=prefix + 'curveInfo') nearest_point.parameter >> curve_info.parameter pivot_track.getShape().worldSpace >> curve_info.inputCurve reverse_group = pm.group(em=True, n=prefix + 'reverse_grp') xform.parentMatrixConstraint(ik_control, reverse_group, offset=True) pm.parent([pivot, ctrl], reverse_group) # curve_info position is where the pivot goes! Connect something to it if you want to visualize it ctrl.r >> pivot.r curve_info.result.position >> pivot.rotatePivot # connect ik handle by letting the pivot drive it pm.parent(ik_handle, pivot) # make the pivot drive the joint's rotations joint.r.disconnect() xform.parentMatrixConstraint(pivot, joint, t=False, r=True, s=False, offset=True) # clean up by hiding curves pivot_track.visibility.set(False) normalized_track.visibility.set(False) ik_control.addAttr(pcfg.banker_attribute, dt='string', k=False, h=True, s=True) ik_control.attr(pcfg.banker_attribute).set(ctrl.name()) # hook up pivot control with fk_ik attribute if ik has an fk-ik proxy if ik_control.hasAttr(pcfg.proxy_fk_ik): switcher_control = switcher.get(ik_control) switcher_attribute = switcher_control.attr(pcfg.fk_ik_attribute) switcher_attribute >> ctrl.lodVisibility attribute.addSeparator(ctrl) ctrl.addAttr(pcfg.proxy_fk_ik, proxy=switcher_attribute, k=True, dv=0, hsx=True, hsn=True, smn=0, smx=1) return ctrl
def FKIK(start, end, parent=None, axis=None, fk_shape=curve.circle, ik_shape=curve.ring, proxy=True): """ Creates a FK and IK controls that drive the chain from start to end. Args: start (pm.nodetypes.Joint): Start of the chain to be driven by FK controls. end (pm.nodetypes.Joint): End of the chain to be driven by FK controls. If none given, will only drive start parent (pm.nodetypes.Transform): If given, will drive the start control through parent matrix constraint. axis (string): Only used if no end joint given for shape's axis to match rotations. fk_shape (method): Used to create curve or visual representation for the FK controls. ik_shape (method): Used to create curve or visual representation for the IK controls. proxy (boolean): If True, adds a proxy FK_IK attribute to all controls. Returns: (list): Two lists, FK and IK controls created in order from start to end respectively. """ # create joint chains that is the same as the given start and end chain for FK and IK then create controls on those transforms = xform.getChain(start, end) sizes = [control.calculateSize(transform) for transform in transforms] fk_transforms, fk_controls = FK(start, end, parent=parent, axis=axis, shape=fk_shape, sizes=sizes, connect=False) ik_transforms, ik_controls = IK(start, end, parent=parent, shape=ik_shape, sizes=sizes, connect=False) controls = fk_controls + ik_controls # create the switcher control and add the transforms, fk, and iks to its attribute to store it switcher_control = switcher.create(end, end.name()) switcher_attribute = switcher_control.attr(pcfg.fk_ik_attribute) switcher.addData(switcher_control.attr(pcfg.switcher_transforms), transforms, names=True) switcher.addData(switcher_control.attr(pcfg.switcher_fk), fk_controls, names=True) switcher.addData(switcher_control.attr(pcfg.switcher_ik), ik_controls, names=True) controls.insert(0, switcher_control) # one minus the output of the fk ik attribute in order to drive visibility of ik/fk controls negative_one = pm.createNode('multDoubleLinear', n=switcher_control.name() + '_negativeOne') switcher_attribute >> negative_one.input1 negative_one.input2.set(-1) plus_one = pm.createNode('plusMinusAverage', n=switcher_control.name() + '_plusOne') negative_one.output >> plus_one.input1D[0] plus_one.input1D[1].set(1) [plus_one.output1D >> fk.lodVisibility for fk in fk_controls] [switcher_attribute >> ik.lodVisibility for ik in ik_controls] # use spaces to drive original chain with fk and ik transforms and hook up switcher attributes for original_transform, fk_transform, ik_transform in zip(transforms, fk_transforms, ik_transforms): world_space, fk_space, ik_space = space.create([fk_transform, ik_transform], original_transform, direct=True) original_transform.attr(fk_space).set(1) switcher_attribute >> original_transform.attr(ik_space) if not proxy: return fk_transforms, ik_transforms, controls # make proxy fk ik attribute on all the controls switcher_control.visibility.set(False) for ctrl in controls[1:]: attribute.addSeparator(ctrl) ctrl.addAttr(pcfg.proxy_fk_ik, proxy=switcher_attribute, k=True, dv=0, hsx=True, hsn=True, smn=0, smx=1) return fk_transforms, ik_transforms, controls
def IK(start, end, parent=None, shape=curve.ring, sizes=None, connect=True): """ Creates IK controls and IK RP solver and for the given start and end joints. Args: start (pm.nodetypes.Joint): Start of the joint chain. end (pm.nodetypes.Joint): End of the joint chain. parent (pm.nodetypes.Transform): Parent of start control. shape (method): Creates the shape control that will drive joints. sizes (list): Sizes to use for each control. connect (bool): If True, connects the duplicate FK chain to the given start/end transforms to be driven. Returns: (list): Controls created in order from start to end. """ axis = None mid_ctrl = None start_ctrl = None controls = [] transforms = xform.getChain(start, end) duplicates = xform.duplicateChain(transforms, prefix=pcfg.ik_prefix, color='purple', scale=0.5) mid = pcu.getMedian(transforms) mid_duplicate = pcu.getMedian(duplicates) if mid == start or mid == end: pm.error('Not enough joints given! {} is the mid joint?'.format(mid.name())) for i, (transform, duplicate) in enumerate(zip(transforms, duplicates)): name = duplicate.name(stripNamespace=True) size = sizes[i] if sizes else control.calculateSize(transform) ctrl_parent = parent if transform == transforms[0] else None if transform != transforms[-1]: next_transform = transforms[i + 1] axis_vector = pipermath.getOrientAxis(transform, next_transform) axis = convert.axisToString(axis_vector) # start if transform == transforms[0]: ctrl = control.create(duplicate, name=name, axis=axis, shape=shape, size=size) attribute.bindConnect(transform, ctrl, ctrl_parent) start_ctrl = ctrl # mid elif transform == mid: ctrl = control.create(duplicate, curve.orb, name, axis, scale=0.01, matrix_offset=False, size=size) translation, rotate, scale, _ = xform.calculatePoleVector(start, mid, end) pm.xform(ctrl, t=translation, ro=rotate, s=scale) mid_ctrl = ctrl # end elif transform == transforms[-1]: ctrl = control.create(duplicate, name=name, axis=axis, shape=pipernode.createIK, control_shape=shape, size=size) attribute.bindConnect(transform, ctrl) attribute.uniformScale(ctrl, axis) else: ctrl = control.create(duplicate, name=name, axis=axis, shape=shape, size=size) if connect: xform.parentMatrixConstraint(duplicate, transform) controls.append(ctrl) piper_ik = controls[-1] mid.attr(pcfg.length_attribute) >> piper_ik.startInitialLength transforms[-1].attr(pcfg.length_attribute) >> piper_ik.endInitialLength if axis.startswith('n'): piper_ik.direction.set(-1) axis = axis.lstrip('n') # connect controls to joints, and make ik handle decompose = xform.parentMatrixConstraint(start_ctrl, duplicates[0], t=True, r=False, s=True) xform.parentMatrixConstraint(piper_ik, duplicates[-1], t=False) ik_handle_name = duplicates[-1].name(stripNamespace=True) + '_handle' ik_handle, _ = pm.ikHandle(sj=duplicates[0], ee=duplicates[-1], sol='ikRPsolver', n=ik_handle_name, pw=1, w=1) pm.parent(ik_handle, piper_ik) pipermath.zeroOut(ik_handle) ik_handle.translate >> piper_ik.handleTranslate ik_handle.parentMatrix >> piper_ik.handleParentMatrix # xform.poleVectorMatrixConstraint(ik_handle, mid_ctrl) attribute.addSeparator(mid_ctrl) mid_ctrl.addAttr('poleVectorWeight', k=True, dv=1, min=0, max=1) constraint = pm.poleVectorConstraint(mid_ctrl, ik_handle) mid_ctrl.poleVectorWeight >> constraint.attr(mid_ctrl.name() + 'W0') # connect the rest start_ctrl.worldMatrix >> piper_ik.startMatrix mid_ctrl.worldMatrix >> piper_ik.poleVectorMatrix piper_ik.startOutput >> mid_duplicate.attr('t' + axis) piper_ik.endOutput >> duplicates[-1].attr('t' + axis) piper_ik.twist >> ik_handle.twist piper_ik._.lock() # scale ctrl connect pipernode.multiply(duplicates[0], decompose.attr('outputScale' + axis.upper()), inputs=[piper_ik.startOutputScale]) pipernode.multiply(mid_duplicate, mid_ctrl.attr('s' + axis), inputs=[piper_ik.endOutputScale]) attribute.uniformScale(mid_ctrl, axis) # parent pole vector to end control and create pm.parent(mid_ctrl, piper_ik) xform.toOffsetMatrix(mid_ctrl) space.create([start_ctrl], mid_ctrl) mid_ctrl.useScale.set(False) attribute.lockAndHideCompound(mid_ctrl, ['r']) # preferred angle connection mid_bind = convert.toBind(mid, pm.warning) if mid_bind: mid_bind.preferredAngle >> piper_ik.preferredAngleInput piper_ik.preferredAngleOutput >> mid_duplicate.preferredAngle return duplicates, controls