The other day i needed an aim constraint (a look-at functionality) in Nuke but couldn't find anything out there so i had to come up with something.

Figured this may be interesting and for the Maya guys.
Here it goes:

import maya.cmds as mc
import maya.OpenMaya as om

def lookAt(base, target, up=[0,1,0]):

    # check if the "base" object has parent
    try: parent = mc.listRelatives(base, pa=True, p=True)[0]
    except: parent = None

    base = mc.getAttr(base+".worldMatrix")
    target= mc.getAttr(target+".worldMatrix")

    # build transformation matrix
    x = om.MVector(target[12]-base[12], target[13]-base[13], target[14]-base[14])
    z = x ^ om.MVector(-up[0], -up[1], -up[2])
    y = x ^ z
    m = om.MMatrix()
    l = [x.x, x.y, x.z, 0, y.x, y.y, y.z, 0, z.x, z.y, z.z, 0, 0, 0, 0, 1]
    om.MScriptUtil.createMatrixFromList(l, m)

    if parent:
       # transform the matrix in the local space of the parent object
        m2 = om.MMatrix()
        l = mc.getAttr(parent+".worldMatrix")
        om.MScriptUtil.createMatrixFromList(l, m2)
        m *= m2.inverse()

    # retrieve the desired rotation for "base" to aim at "target", in degrees
    r = om.MTransformationMatrix(m).eulerRotation() * 57.2958

    return r[0], r[1], r[2]

Quote 1 0
thank you @pshipkov, that is useful indeed.

I guess this is not the ideal place to ask, but could you share how you achieved that in nuke?

if you're the least noob in the room, you're in the wrong room
@mapofemergence on twitter
Quote 0 0
Forgot to answer this one.

Nuke has basic lookAt capability in its 3D nodes, but no up-vector and aim in place functionality.

import nuke

def nuke_aimConstraint(base, target, up=[0,1,0]):

  base = nuke.toNode(base)
  target = nuke.toNode(target)
  try: parent = base.input(0)
  except: parent = None

  m = nuke.math.Matrix4()
  l = base["matrix"].getValue()
  l2 = target["world_matrix"].getValue()

  if parent:
    # target's world matrix
    for i in range(16): m[i] = l2[i]
    # parent's inverse world matrix
    m2 = nuke.math.Matrix4()
    l2 = parent["world_matrix"].getValue()
    for i in range(16): m2[i] = l2[i]
    m2 = m2
    # target in the space of parent
    l2 = m * m2.inverse()
    # up vector in the space of parent
    up = m2.transform(nuke.math.Vector4(up[0], up[1], up[2], 1))

  # build transformation matrix
  x = nuke.math.Vector3(l[3]-l2[3], l[7]-l2[7], l[11]-l2[11])
  z = x.cross(nuke.math.Vector3(-up[0], -up[1], -up[2]))
  y = x.cross(z)
  m = nuke.math.Matrix4()
  m[0] = -x.x
  m[1] = -x.y
  m[2] = -x.z
  m[3] = 0
  m[4] = y.x
  m[5] = y.y
  m[6] = y.z
  m[7] = 0
  m[8] = z.x
  m[9] = z.y
  m[10] = z.z
  m[11] = 0
  m[12] = 0
  m[13] = 0
  m[14] = 0
  m[15] = 1

  # retrieve the desired rotation for "base" to aim at "target", in degrees
  r = m.rotationsZXY()
  base["rotate"].setValue([r[0]*57.2958, r[1]*57.2958, r[2]*57.2958])

Quote 0 0
Woow thanks @pshipkov !!!
Really useful to animate something flying/swimming!
I tweaked a bit the original code to set a rotation key with a value which aim to the object's position 2 frames in the futur. Basically, it's orienting the object to follow the animation.

I guess the code is quite dirty, I'm only an animator, not a coder :D
I'm creating a temp locator, getting object's xform 2 frames later, applying to locator translate, going back on current frame, applying  'lookAt()', with the locator as aim

import maya.cmds as mc
import maya.OpenMaya as om

def lookAt(base, target, up=[0,-1,0]):

# check if the "base" object has parent
parent = mc.listRelatives(base, pa=True, p=True)
if parent != None: parent = parent[0]

base = mc.getAttr(base+".worldMatrix")
target= mc.getAttr(target+".worldMatrix")

# build transformation matrix
z = om.MVector(target[12]-base[12], target[13]-base[13], target[14]-base[14])
x = z ^ om.MVector(-up[0], -up[1], -up[2])
y = x ^ z
m = om.MMatrix()
l = [x.x, x.y, x.z, 0, y.x, y.y, y.z, 0, z.x, z.y, z.z, 0, 0, 0, 0, 1]
om.MScriptUtil.createMatrixFromList(l, m)

if parent:
# transform the matrix in the local space of the parent object
m2 = om.MMatrix()
l = mc.getAttr(parent+".worldMatrix")
om.MScriptUtil.createMatrixFromList(l, m2)
m *= m2.inverse()
# retrieve the desired rotation for "base" to aim at "target", in degrees
r = om.MTransformationMatrix(m).eulerRotation() * 57.2958

return r[0], r[1], r[2]


def aimFuturPosition(inNode, offset):
tmpLoc = mc.spaceLocator(n=inNode+'_tmpLocator')
tmpLoc_translate = mc.xform( inNode, query=True, t=True, worldSpace=True)
mc.xform( tmpLoc[0], worldSpace=True, t=(tmpLoc_translate))

matrixRotation=lookAt( tmpLoc[0], inNode)
print matrixRotation
mc.xform(inNode, worldSpace=True, ro=(matrixRotation))
mc.filterCurve (inNode)
mc.currentTime ((mc.findKeyframe(timeSlider=True, which='next')),edit=True)
aimFuturPosition(inNode[0], 2)

PS: my controller was using Z as the front axis, so I tweaked a bit the up, an switched x and z

Hope it could be useful to some of you guys!

Quote 0 0
Thanks for the kind words and for sharing your code.
Quote 0 0