0

I'm creating code to create a motion path of a controller based on it's keyframed positions in Maya. I've run into a problem when trying to use this code to create a motion path of a parented controller. If I rotate and translate the parent the generated motion path does not reflect the actual path of motion. Instead it creates the motion path as if it was not affected by the parent. I've looked around and found information for applying rotation using matrix transformations to the current position but it seems to be rotating it way too much. I've included the function for creating the motion path, it's a little long but the part that isn't working is within the else statement when dealing with the upper torso controller.

Old Code

#
# This function creates an animation curve within the scene that follows the path of motion
# of the selected controller. It requires keyframe information in order to genereate the curve
# and uses the range of frames given by the user.
#
def createAnimCurve( bodyField, startField, endField, firstColor ):
    # Takes the value of the text field to select the controller
    obj = cmds.textField(bodyField, query=True, text=True)
    print obj
    # Takes in the string input of the paramter values and turns them into integer values
    startFrame = cmds.intField(startField, query=True, value=True)
    print startFrame
    endFrame = cmds.intField(endField, query=True, value=True)
    print endFrame
    color = cmds.colorIndexSliderGrp( firstColor, query=True, value=True ) - 1
    print color

    if obj == "":
        cmds.warning( "WARNING: Need to Select Body Part from Diagram" )
        return
    if cmds.objExists(obj[:-3]+'Path'):
        # Creates a warning pop up that double checks if the user wants to remove the curve
        delRes = cmds.confirmDialog( title='Delete Path Warning', message='Recreation will delete current path. Are you sure?', button=['Yes','No'], defaultButton='Yes', cancelButton='No', dismissString='No' )
        # If yes then the curve is deleted
        if delRes == 'Yes':
            #cmds.delete(obj[:-3]+'ScalePath')  
            #cmds.delete(obj[:-3]+'ScalePath_LOC')  
            cmds.delete(obj[:-3]+'Path')     
            cmds.delete(obj[:-3]+'Path_LOC')
        else:
            return
    # Sorts through the list of keyframes of the selected obj from the selected time line
    global keyframes
    keyframes = sorted(list(set(cmds.keyframe(obj, q=True, time=(startFrame,endFrame), timeChange=True))))
    # Creates the arrays for the required point positions
    points = []
    centerPoints = []
    centerRotates = []
    combinedPoints = []

    # Special cases for controllers that are named differently than their joints
    if obj == "L_foot_CTL" or obj == "R_foot_CTL":
        loc = obj[:-4] + "Ankle_LOC"
    elif obj == "M_upTorso_CTL":
        loc = "M_spineTip_LOC"
    else:    
        loc = obj[:-3] + "LOC"
    # Grabs the original world space position to calculate the approraite motion points
    locPos = cmds.getAttr(loc+".translate")
    centerLocPos = cmds.getAttr("M_centerMass_LOC.translate")

    #for step in range( startFrame, endFrame+2, int(curveCVstep)):
    for step in range(len(keyframes)):
        # Moves throughout the specified timeline to find point results
        cmds.currentTime( keyframes[step] )
        if obj != "M_upTorso_CTL":
            # Queries the position of the controller to draw the curve
            # Adds the position of the controller in world space to draw it relative to the control
            pos = cmds.xform( obj,q=True,ws=True,t=True )
            pos[0] = pos[0] + locPos[0][0]
            pos[1] = pos[1] + locPos[0][1] 
            pos[2] = pos[2] + locPos[0][2]
            # convert the tuple (vector) to a string
            points.append(pos)
            print pos
        else:
            spineLength = cmds.getAttr('spineCurveInfo.arcLength')

            # Queries the position of the controller to draw the curve
            # Adds the position of the controller in world space to draw it relative to the control
            # adds in the spine length to the y position to take into consideration the offset of the centerMass controller
            pos = cmds.xform( obj,q=True,ws=True,t=True )
            pos[0] = pos[0] + locPos[0][0]
            pos[1] = pos[1] + locPos[0][1]
            pos[2] = pos[2] + locPos[0][2]
            # convert the tuple (vector) to a string
            print "Printing out points"
            points.append(pos)
            print pos

            # Queries the position of the center of mass controller 
            centerPos = cmds.xform( "M_centerMass_CTL",q=1,os=1,t=1 )
            centerPos[0] = centerPos[0] #+ centerLocPos[0][0]
            centerPos[1] = centerPos[1] #+ centerLocPos[0][1]
            centerPos[2] = centerPos[2] #+ centerLocPos[0][2]
            # convert the tuple (vector) to a string
            print "Printing out center Points"
            centerPoints.append(centerPos)
            print centerPos

            # Combine the two point positions to find the relative position 
            combinedPos = []
            combinedPos1 = pos[0] + centerPos[0]
            combinedPos.append(combinedPos1)
            combinedPos2 = pos[1] + centerPos[1]
            combinedPos.append(combinedPos2)
            combinedPos3 = pos[2] + centerPos[2]
            combinedPos.append(combinedPos3)
            print "Printing out combined Points"
            print combinedPos

            # Queries the rotation of the center of mass controller
            #centerRot = cmds.xform( "M_centerMass_CTL",q=1,ws=1,ro=1 )
            #centerRotates.append(centerRot)
            #print "Printing out rotations"
            #print centerRot
            # applies rotation of the center of mass controller to the upper torso controller
            # rotation around the Z axis
            #tempX = combinedPos[0]*math.cos(math.radians(centerRot[2])) - combinedPos[1]*math.sin(math.radians(centerRot[2]))
            #tempY = combinedPos[0]*math.sin(math.radians(centerRot[2])) + combinedPos[1]*math.cos(math.radians(centerRot[2]))
            # rotation around the Y axis
            #tempX2 = tempX*math.cos(math.radians(centerRot[1])) + combinedPos[2]*math.sin(math.radians(centerRot[1]))
            #tempZ = combinedPos[2]*math.cos(math.radians(centerRot[1])) - tempX*math.sin(math.radians(centerRot[1]))
            # rotation around the X axis
            #tempY2 = tempY*math.cos(math.radians(centerRot[0])) - tempZ*math.sin(math.radians(centerRot[0]))
            #tempZ2 = tempY*math.sin(math.radians(centerRot[0])) + tempZ*math.cos(math.radians(centerRot[0]))

            #combinedPos[0] = tempX2
            #combinedPos[1] = tempY2
            #combinedPos[2] = tempZ2
            #print "Printing out rotated Points"
            combinedPoints.append(combinedPos)
            print combinedPos

    # if the obj is the upper torso controller we need to take into consideration the center of mass controller
    # Creates the motion curve with the required cvs
    if obj == "M_upTorso_CTL":
        cur = cmds.curve(d=2, ws=True, p=combinedPoints, n=obj[:-3]+'Path')
        cmds.setAttr(cur + '.overrideEnabled', 1)
        cmds.setAttr(cur + '.overrideColor', color)
        print cur
        cmds.move(points[0][0], points[0][1], points[0][2], cur+".scalePivot", cur+".rotatePivot", absolute=True)
    else:
        cur = cmds.curve(d=2, ws=True, p=points, n=obj[:-3]+'Path')
        cmds.setAttr(cur + '.overrideEnabled', 1)
        cmds.setAttr(cur + '.overrideColor', color) 
        print cur
        cmds.move(points[0][0], points[0][1], points[0][2], cur+".scalePivot", cur+".rotatePivot", absolute=True)
    # command that runs through each cv of the curve and returns their position within a list.
    cvs = cmds.getAttr( obj[:-3]+'Path.cv[*]' )
    print cvs

    global initCVS
    initCVS = cvs
    # Create a locator for the motion path that the controller will now follow
    locate = cmds.spaceLocator( n=obj[:-3]+"Path_LOC" )
    #for step in range( startFrame, endFrame+2, int(curveCVstep)):
    for step in range(len(keyframes)):
        # Moves throughout the specified timeline to find point results
        cmds.currentTime( keyframes[step] )
        # Moves the locator to match the position of the controller
        cmds.move( cvs[step][0], cvs[step][1], cvs[step][2], locate)
        # Keyframes the locator
        cmds.setKeyframe( locate )
    # Position obj at the location of locate.
    cmds.pointConstraint( locate, obj, n=obj[:-3]+"LOC1_PNT" )
    cmds.setAttr( loc+'.visibility', 0)
    # keys the weight of the point constraint to 0 before and after time frame (set to 1 during time frame)
    #Before startFrame
    cmds.currentTime( startFrame - 1 )
    cmds.setAttr(obj+'.blendPoint1', 0 )
    cmds.setKeyframe(obj+'.blendPoint1' )
    #After startframe
    cmds.currentTime( startFrame )
    cmds.setAttr(obj+'.blendPoint1', 1 )
    cmds.setKeyframe(obj+'.blendPoint1' )
    #Before endframe
    cmds.currentTime( endFrame )
    cmds.setAttr(obj+'.blendPoint1', 1 )
    cmds.setKeyframe(obj+'.blendPoint1' )
    #After endframe
    cmds.currentTime( endFrame + 1 )
    cmds.setAttr(obj+'.blendPoint1', 0 )
    cmds.setKeyframe(obj+'.blendPoint1' )
    cmds.select(obj)

The issue with the code was that I froze the transformations on my controllers setting the pivot to (0,0,0) in world space. The best way to fix this is to create a temporary locator and have it follow the controller. Use the positions of the temp locator to create the motion path of the controller. Once created delete the temp locator.

New Code

#
# This function creates an animation curve within the scene that follows the path of motion
# of the selected controller. It requires keyframe information in order to genereate the curve
# and uses the range of frames given by the user.
#
def createAnimCurve( bodyField, startField, endField, firstColor ):
    # Takes the value of the text field to select the controller
    obj = cmds.textField(bodyField, query=True, text=True)
    print obj
    # Takes in the string input of the paramter values and turns them into integer values
    startFrame = cmds.intField(startField, query=True, value=True)
    print startFrame
    endFrame = cmds.intField(endField, query=True, value=True)
    print endFrame
    color = cmds.colorIndexSliderGrp( firstColor, query=True, value=True ) - 1
    print color

    if obj == "":
        cmds.warning( "WARNING: Need to Select Body Part from Diagram" )
        return
    if cmds.objExists(obj[:-3]+'Path'):
        # Creates a warning pop up that double checks if the user wants to remove the curve
        delRes = cmds.confirmDialog( title='Delete Path Warning', message='Recreation will delete current path. Are you sure?', button=['Yes','No'], defaultButton='Yes', cancelButton='No', dismissString='No' )
        # If yes then the curve is deleted
        if delRes == 'Yes':
            cmds.delete(obj[:-3]+'Path')     
            cmds.delete(obj[:-3]+'Path_LOC')
        else:
            return
    # Sorts through the list of keyframes of the selected obj from the selected time line
    global keyframes
    keyframes = sorted(list(set(cmds.keyframe(obj, q=True, time=(startFrame,endFrame), timeChange=True))))
    # Creates the arrays for the required point positions
    points = []

    # Creates a temporary locator to find the world space values of the controller
    cmds.spaceLocator( n="tempLoc" )
    cmds.parentConstraint( obj, 'tempLoc', n='temp_PRT_CST' )

    #for step in range( startFrame, endFrame+2, int(curveCVstep)):
    for step in range(len(keyframes)):
        # Moves throughout the specified timeline to find point results
        cmds.currentTime( keyframes[step] )
        # Queries the position of the controller to draw the curve
        # Adds the position of the controller in world space to draw it relative to the control
        pos = cmds.xform( "tempLoc",q=True,ws=True,t=True )
        pos[0] = pos[0] 
        pos[1] = pos[1] 
        pos[2] = pos[2] 
        # convert the tuple (vector) to a string
        points.append(pos)
        print pos

    print "Creating the basic motion curve"
    cur = cmds.curve(d=2, ws=True, p=points, n=obj[:-3]+'Path')
    cmds.setAttr(cur + '.overrideEnabled', 1)
    cmds.setAttr(cur + '.overrideColor', color) 
    print cur
    cmds.move(points[0][0], points[0][1], points[0][2], cur+".scalePivot", cur+".rotatePivot", absolute=True)
    # command that runs through each cv of the curve and returns their position within a list.
    cvs = cmds.getAttr( obj[:-3]+'Path.cv[*]' )
    print cvs

    # Deletes the temp locator
    cmds.select("temp_PRT_CST")
    cmds.delete()
    cmds.select("tempLoc")
    cmds.delete()

    global initCVS
    initCVS = cvs
    # Create a locator for the motion path that the controller will now follow
    locate = cmds.spaceLocator( n=obj[:-3]+"Path_LOC" )
    #for step in range( startFrame, endFrame+2, int(curveCVstep)):
    for step in range(len(keyframes)):
        # Moves throughout the specified timeline to find point results
        cmds.currentTime( keyframes[step] )
        # Moves the locator to match the position of the controller
        cmds.move( cvs[step][0], cvs[step][1], cvs[step][2], locate)
        # Keyframes the locator
        cmds.setKeyframe( locate )
    # Position obj at the location of locate.
    cmds.pointConstraint( locate, obj, n=obj[:-3]+"LOC1_PNT" )
    # keys the weight of the point constraint to 0 before and after time frame (set to 1 during time frame)
    #Before startFrame
    cmds.currentTime( startFrame - 1 )
    cmds.setAttr(obj+'.blendPoint1', 0 )
    cmds.setKeyframe(obj+'.blendPoint1' )
    #After startframe
    cmds.currentTime( startFrame )
    cmds.setAttr(obj+'.blendPoint1', 1 )
    cmds.setKeyframe(obj+'.blendPoint1' )
    #Before endframe
    cmds.currentTime( endFrame )
    cmds.setAttr(obj+'.blendPoint1', 1 )
    cmds.setKeyframe(obj+'.blendPoint1' )
    #After endframe
    cmds.currentTime( endFrame + 1 )
    cmds.setAttr(obj+'.blendPoint1', 0 )
    cmds.setKeyframe(obj+'.blendPoint1' )
    cmds.select(obj)

Here's what the resulting motion path looks like enter image description here

Here's what the correct arc should be following enter image description here

Here's the new curve based on the new code. It seems to be following along the path of motion but it's compressed. enter image description here

zaidi92
  • 355
  • 1
  • 3
  • 11
  • just wanted to let you know I've updated my answer with a few more ideas in case they actually answer your question... – silent_sight Sep 15 '17 at 01:43
  • Managed to make a little progress. I was using object space to create the motion path but it still needs to be rotated by the parent controller to follow the correct path. If that's not done it just assumes nothings happening and creates a path without the parent influence. – zaidi92 Sep 15 '17 at 02:37

2 Answers2

1

if you're just trying to get the world space position of something, you can use:

position = cmds.xform(item, q=True, ws=True, t=True)

and then use that position data as needed - it should work regardless of how any parents are rotated... The xform command can be used to set the position of things in world space as well:

cmds.xform(item, ws=True, t=position)

I'm not sure if this actually answers your question - it's a little unclear what you're trying to achieve in your code; I can't quite follow the reason for the locator and point constraint that's being turned on/off. Are you trying to create an editable motion trail?

If so, have you tried using Maya's built in editable motion trail tool?

If you're trying to make your own for some reason, I suppose you could follow a '2 step' approach where you first create a curve where each cv is at the world space position of a control through your designated start/end time. Then edit the curve cv positions to clean up the arc and run a separate function that queries the world space position of each cv, then applies them to the control and sets a keyframe at the appropriate time. Since you're doing all this in world space positions, you shouldn't need to worry about any parent positions/rotations (fortunately, that should make the code relatively simple).

silent_sight
  • 492
  • 1
  • 8
  • 16
  • The purpose of the project is to create my own motion path because I'll be creating different versions that can be edited by the animator. Maya's motion path was my first choice but it doesn't have the flexibility I need so I'm making my own. The locators are made to allow the controller to follow the motion path. In the final version when more than one path is made (such as this default path and one of the editable paths) the animator can adjust the influences of the locators so the controller can switch between paths. – zaidi92 Sep 15 '17 at 02:11
  • I'll replace the 1s with True but they should have the same effect. For some strange reason it isn't giving me the actual world space coordinates so I add the locators that was used to create the joint structure of the rig (I'm generating joints along locator positions so that the rig can be adjusted for different models). – zaidi92 Sep 15 '17 at 02:13
  • that seems odd the xform command isn't giving you the true world space position... I guess you could try using getAttr on the worldMatrix[0] attr and extracting the xyz translation values from that list of values... – silent_sight Sep 15 '17 at 03:14
  • How would I extract the xyz translation values? I am able to get the attr but I don't really understand the matrix that's returned. – zaidi92 Sep 16 '17 at 03:02
  • You should check youtube for maya matrix explanation videos for a more in-depth overview (there should be a lot of really good ones there), but to put it very simply, you should get a list of 16 values. A matrix is 16 values (4 columns/4 rows) used to describe a transform. The 1st row is for x rotation, 2nd for y rotation, 3rd for z rotation, and the 4th is for position. So, indices 12-14 of the list will be your xyz position values (ie: matrix[12] = x, matrix[13] = y, matrix[14] = z) – silent_sight Sep 16 '17 at 04:27
  • Would freezing the transforms of the controllers have any affect on the world space position? I also found that tutorial on youtube and was able to replicate the matrix multiplication but it just creates the path of motion while ignoring the parented controls rotation. I can edit this post and include the new code if that helps. – zaidi92 Sep 17 '17 at 22:25
  • hmm... possibly - that would move the pivot away from the "origin" of the node, so you may have to factor that in; however, it may be a non issue since that offset would be constant, so a curve will still follow the correct path even if slightly offset from the "center" of the control - I guess it really just needs to be tested... I haven't really looked into it very far, but there should be some attributes somewhere on the node with that offset value which you can use to construct a matrix to multiply with the worldMatrix values. – silent_sight Sep 18 '17 at 14:41
  • the pivot location attribute is the `rotatePivot` (X,Y, and Z). There is also a scalePivot, but if you freeze transforms, both of these will have the same values. Constructing a matrix with these would involve using the default (0,0,0) rotation vectors with the rotatePivot values to construct a 4x4 matrix [1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,z,1]... – silent_sight Sep 20 '17 at 19:00
  • I see, so I'll need to save these original pivot matrices before performing my calculation. I have been applying the locator position to the matrix values to adjust for the offset but the rotation may be the problem. I'll work on it some more this week and let you know what I find out. – zaidi92 Sep 26 '17 at 01:17
  • I hope you can get it to work... I've also posted another idea that you could try if computing it just doesn't seem to get the right results – silent_sight Sep 26 '17 at 01:25
1

Another thought I just had would be to parent a temp locator to your controls (positioned at the pivot of the control). Then you can use the worldPosition xyz attr values from the locator to plot your curve points. You can delete the locator after you have the curve.

By positioning the locator at the control's pivot to plot the curve, it should allow you to do your constraining thing without much headache (at least, in theory). It's also less math intensive since the locator already has an "easy to query" world space position value.

Sure it's not as cool as doing all the math computations, but if it works...

silent_sight
  • 492
  • 1
  • 8
  • 16
  • Wouldn't parenting the controllers break the hierarchy created? I'm assuming I'd have to set the locator under the parent of the current controller to keep it consistent. Also I do keep the original locators that are used to build the joint structure and place the controllers in space. Should I use these locators or use a new set of locators? Honestly anything that works right now is fine with me. The math isn't necessary but just the first thing I thought of when making the tool. – zaidi92 Sep 26 '17 at 17:06
  • Sorry, I don't quite understand what would break the hierarchy... I'm talking about creating a new temporary locator (it wouldn't control anything) and parenting that locator to the control you're creating a path for (position that locator at the control's rotation pivot). You can then use the `worldPosition` xyz values of the locator to plot your cvs for the path. Once you have the path, the locator is no longer needed and can be deleted. The locator being placed at the pivot should ensure the resulting curve goes right through the control's position allowing your constraint system to work – silent_sight Sep 26 '17 at 21:17
  • if parenting the locator still doesn't give you the true world space locations for your path's cvs, then leave the locator unparented in world space and parent constrain it to the control... then you could just use the translate xyz values... – silent_sight Sep 26 '17 at 21:18
  • Sorry, that was my misunderstanding. I was under the impression of having the controller following the locator rather than the locator following the controller. I was just concerned about how removing a controller from it's parent so often would affect it. – zaidi92 Sep 27 '17 at 15:34
  • Man thank you for all your help, that worked! I didn't even have to use any of that complicated math. I'll post the new code for anyone who needs the help. – zaidi92 Sep 27 '17 at 16:08
  • 1
    alright! Glad we got something to work! Parent constrained locator for the win!!! – silent_sight Sep 28 '17 at 02:11