예제 #1
0
def createAnimation():
    """
    Creates the node that houses a rig. Used to export animation.

    Returns:
        (pm.nodetypes.piperAnimation): Animation node created.
    """
    scene_name = pm.sceneName().namebase
    name = scene_name if scene_name else 'piperAnimation'
    piper_animation = create('piperAnimation',
                             'dark green',
                             name=pcfg.animation_prefix + name)
    attribute.lockAndHideCompound(piper_animation)
    rigs = get('piperRig', ignore='piperAnimation')
    pm.parent(rigs[0], piper_animation) if len(rigs) == 1 else pm.warning(
        '{} rigs found!'.format(str(len(rigs))))
    return piper_animation
예제 #2
0
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
예제 #3
0
def createHigh(rigs=None):
    """
    References in the high poly version of the mesh. Naming convention is important! See piper configs "high_poly"
    to correctly suffix high poly file and high poly geometry.

    Args:
        rigs (list): Rigs to create high-poly version of mesh.

    Returns:
         (list): Absolute path of references made.
    """
    warnings = []
    piper_rigs = []
    references = {}
    high_references = []
    skeleton_meshes = rig.getSkeletonMeshes(rigs=rigs)

    # get all the data more organized based on what file reference each mesh belongs to
    for mesh, data in skeleton_meshes.items():
        reference = pm.FileReference(namespace=mesh.namespace())

        # no need to operate on meshes that are already high poly
        if reference.namespace.startswith(pcfg.high_poly_namespace):
            continue

        if reference in references:
            references[reference].update({mesh: data})
        else:
            references[reference] = {mesh: data}

    for reference, meshes in references.items():

        deformers = []
        skinned_mesh = None
        should_continue = False
        path, extension = os.path.splitext(reference.path)
        high_absolute = path + pcfg.high_poly_file_suffix + extension
        high_references.append(high_absolute)

        # cannot reference in path that does not exist
        if not os.path.exists(high_absolute):
            text = 'High poly path does not exist! ' + high_absolute
            warnings.append(text)
            pm.warning(text)
            continue

        for i, mesh in enumerate(meshes):

            if i == 0:
                # all meshes in reference should share same skinned mesh
                skinned_mesh = meshes[mesh]['skinned_mesh']
                should_continue = skinned_mesh.wraps.get(
                )  # if there is data in wraps attribute, then high_poly exists

                if should_continue:
                    text = skinned_mesh.name() + ' already has high poly!'
                    warnings.append(text)
                    pm.warning(text)
                    break

                # getting relative path
                root_path = reference.path.split(reference.unresolvedPath())[0]
                high_relative = high_absolute.split(root_path)[-1]

                # reference in high poly, filter by transform, create group, group deformers, and parent group
                nodes = pm.createReference(high_relative,
                                           namespace=pcfg.high_poly_namespace,
                                           returnNewNodes=True)
                nodes = pm.ls(nodes, type='transform')
                group_name = os.path.basename(
                    path) + pcfg.high_poly_file_suffix + pcfg.group_suffix
                group = pm.PyNode(group_name) if pm.objExists(
                    group_name) else pm.group(name=group_name, em=True)
                deformers.append(
                    group.name()
                )  # deformers will be serialized with json so using group name
                pm.parent(nodes, group)
                pm.parent(group, skinned_mesh)
                attribute.lockAndHideCompound(group)

            # finding the accompanying high poly mesh with a suffix swap
            high_name = pcfg.high_poly_namespace + ':'
            high_name += mesh.name(stripNamespace=True).replace(
                pcfg.low_poly_suffix, pcfg.high_poly_suffix)

            if not pm.objExists(high_name):
                text = mesh.name(
                ) + ' has no high poly equivalent! ' + high_name
                warnings.append(text)
                pm.warning(text)
                continue

            # create proximity wrap
            piper_rig = meshes[mesh]['rig']
            piper_rigs.append(piper_rig)
            high_poly = pm.PyNode(high_name)
            deformer = skin.createProximityWrap(mesh, high_poly)
            deformers.append(deformer)

            piper_rig.highPolyVisibility >> high_poly.visibility

        if should_continue:
            continue

        # write the deformers and group onto the skinned mesh wraps attribute
        deformers_data = json.dumps(deformers)
        skinned_mesh.wraps.set(deformers_data)

    pm.select(piper_rigs)

    # finished referencing high poly, display warnings if any found
    warning_length = len(warnings)
    if warning_length == 0:
        pm.displayInfo(
            'Finished referencing and proximity wrapping high-poly geometry')
    elif warning_length == 1:
        pm.warning('Finished with ONE warning: ' + warnings[0])
    else:
        pm.warning(
            'Finished with MULTIPLE warnings. Please see Script Editor for details.'
        )

    return high_references
예제 #4
0
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
예제 #5
0
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
예제 #6
0
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