Particle-Based Overlapping Action



This one was a doozy.

There’s a way in Maya to get overlapping action on an object by using nParticles. I first came about it on a LesterBanks article when doing research for how to add dynamics to my car rig. Check out that link, it’s really cool!

So what this does is it uses a particle that is driven by some control and adds overlapping action through some direct-connection relationships and some workaround fixes… If you’re not familiar with the term, overlapping action is an animation principle for the reaction to an event. You can also think of it as Newton’s third law (action/reaction).

So check out the script that I wrote that automates this. The trickiest part was getting the same control functionality as you had before but with the dynamics layered on over it. That required me to do some fancy stuff that you’ll see described in the script comments…

import maya.cmds as cmds

def createSpring():
    # creates spring phsyics on the selected object
    ctrl =, o=1)[0]
    geo =, o=1)[1]
    ctrlLoc = cmds.xform(ctrl + ".rotatePivot", q=1, t=1, ws=1)
    # we have to make a duplicate of our ctrl to be able to still move our control as expected
    ctrlDuplicate = cmds.duplicate(ctrl)
    # make that duplicate a child of our original ctrl. It's technically doing the legwork for the physics.
    cmds.parent(ctrlDuplicate, ctrl)
    # create our particle at the location of our ctrl
    particle = cmds.particle(p=ctrlLoc, c=1)
    # make our spring physics on the duplicate object to avoid double transforms
    spring = cmds.spring(particle[0], ctrlDuplicate, name=ctrl + "_spring#", damping=2.5, stiffness=15)
    cmds.addAttr(ctrl, attributeType="float", ln="Damping", min=0, max=100, dv=2, keyable=1)
    cmds.connectAttr(ctrl + ".Damping", spring[0] + ".damping")
    cmds.addAttr(ctrl, attributeType="float", ln="Stiffness", min=0, max=100, dv=25, keyable=1)
    cmds.connectAttr(ctrl + ".Stiffness", spring[0] + ".stiffness")

    # this group will track the position of our particle and will be used for world position calculations
    springXform =, w=1, n=spring[0] + "_xform")
    cmds.connectAttr(particle[1] + ".worldCentroid", springXform + ".translate")

    # make our group that will be used to control our duplicate control
    ctrlXform =, w=1, n=ctrl + "_xform")
    cmds.xform(ctrlXform, t=ctrlLoc)
    cmds.makeIdentity(ctrlXform, apply=1, t=1)
    cmds.parent(ctrlXform, ctrlDuplicate)
    cmds.pointConstraint(springXform, ctrlXform, mo=1)

    pointA =, w=1, n=spring[0] + "_ptA")
    cmds.pointConstraint(ctrlDuplicate, pointA)
    pointB =, w=1, n=spring[0] + "_ptB")
    cmds.pointConstraint(ctrlXform, pointB)

    dist = cmds.createNode("plusMinusAverage", n=spring[0] + "_dist#")
    cmds.setAttr(dist + ".op", 2)

    cmds.connectAttr(pointA + ".translate", dist + ".input3D[0]")
    cmds.connectAttr(pointB + ".translate", dist + ".input3D[1]")

    mult = cmds.createNode("multiplyDivide", n=spring[0] + "_mult#")
    # make a variable so that the user can amp up the rotation if they want
    cmds.addAttr(ctrl, at="float", dv=15, min=0, ln="Rot_Multiplier", k=1)
    # create a node that inverts this for our i1x attribute
    inverse = cmds.createNode("multiplyDivide", n="inverse#")
    cmds.setAttr(inverse + ".i2x", -1)
    cmds.connectAttr(ctrl + ".Rot_Multiplier", inverse + ".i1x")
    cmds.connectAttr(inverse + ".ox", mult + ".i2z")
    cmds.connectAttr(ctrl + ".Rot_Multiplier", mult + ".i2y")
    cmds.connectAttr(ctrl + ".Rot_Multiplier", mult + ".i2x")

    cmds.connectAttr(dist + ".output3Dx", mult + ".i1x")
    cmds.connectAttr(dist + ".output3Dy", mult + ".i1y")
    cmds.connectAttr(dist + ".output3Dz", mult + ".i1z")

    cmds.connectAttr(mult + ".ox", ctrlXform + ".rz")
    cmds.connectAttr(mult + ".oz", ctrlXform + ".rx")

    pConst = cmds.parentConstraint(ctrlXform, ctrl, geo, mo=1)

    particleGrp =, pointA, pointB, n="particle_Grp#")
    cmds.parent(particleGrp, ctrl)

    cmds.addAttr(ctrl, at="float", dv=1, min=0, max=1, ln="Physics", k=1)
    remap = cmds.createNode("remapValue", n="physicsRemap#")
    cmds.setAttr(remap + ".outputMax", .25)
    cmds.connectAttr(ctrl + ".Physics", remap + ".value[0].value_FloatValue")

    # find the weight number where our new ctrlXform node is in
    weightList = cmds.parentConstraint(pConst, q=1, weightAliasList=1)
    weightValue = ""
    for i in weightList:
        if ctrlXform in i:
            weightValue = i

    cmds.connectAttr(remap + ".outValue", pConst[0] + "." + weightValue),spring,n=spring[0] + "_GRP")

1. Double transformations. The object is parented to both our ctrl and the group that both are doing the same
   transformations. Therefore it's double transforming...

    Ctrl Parent (what you animate)
        Ctrl You run the script on
            ctrl xform
                point constraint to particle xform
        spring pointA
            pointConst to Ctrl
        spring pointB
            pointConst to spring_xform
    parentConst to Ctrl (1)
    parentConst to ctrl_xform (.25)

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s