Monday, March 28, 2016

Tutorial - Cloning the shape of an object through UV coordinates (mid)

In this tutorial we will create a maya python script that takes two meshes A and B and makes B look like A using the UVs to create a correspondence across the two meshes.
The more the two UV maps are overlapping, the better results will be achieved.

This is the second tutorial that should lead to creating your own UV blendshape deformer in Maya. I am doing this as many people asked me for my UV blendshape deformer.
The first tutorial of this series is available here.

Let´s start with the basics: import maya cmds and OpenMaya:
import maya.cmds as cmds
import maya.OpenMaya as om


Now we can create some geometry (a sphere and a plane) in our scene:
cmds.polySphere(r= 1.0, ch=True)
cmds.polyPlane(w=1.0, sw=10, sh=10, ch=True)


We now need to select the objects and get their Dag path:
cmds.select("pSphere1", "pPlane1")
sel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(sel)
myOriginalDagPath = om.MDagPath()
myTargetDagPath = om.MDagPath()
sel.getDagPath(0, myOriginalDagPath)
sel.getDagPath(1, myTargetDagPath)


We instantiate a function set for the original mesh that will be used to query the point at a certain UV:
originalMeshFn = om.MFnMesh(myOriginalDagPath)


Time to iterate over all vertices of the target mesh (with MItMeshVertex), get their UV coordinates, query the original mesh for the 3d position of the UV coordinate on its mesh and finally set this position as the new position for the target vertex:
... create the vertex iterator
targetMeshVtxIt = om.MItMeshVertex(myTargetDagPath)
... then we iterate using a while condition until the iterator has not reached the last vertex (isDone())

targetMeshVtxIt = om.MItMeshVertex(myTargetDagPath)
while not targetMeshVtxIt.isDone():
    targetMeshVtxIt.next()
... we read the u and v coordinate of each iterated vertex

targetMeshVtxIt = om.MItMeshVertex(myTargetDagPath)
while not targetMeshVtxIt.isDone():
    uCoords = om.MFloatArray()
    vCoords = om.MFloatArray()
    faceIDs = om.MIntArray()
    targetMeshVtxIt.getUVs(uCoords, vCoords, faceIDs)
    targetMeshVtxIt.next()
... we query the 3d position on the original mesh at these UV coordinates. This can be done by calling the getPointAtUV function on originalMeshFn. The function requires the user to provide the index of the face on which the UV coordinate lays. As we don´t know the face index, we will try to call the function over all the faces of the original mesh. If the function call succeeds we will exit (break) the for loop. In case no Point can be found, the target vertex will be moved at position <<0.0, 0.0, 0.0>>.

targetMeshVtxIt = om.MItMeshVertex(myTargetDagPath)
while not targetMeshVtxIt.isDone():
    uCoords = om.MFloatArray()
    vCoords = om.MFloatArray()
    faceIDs = om.MIntArray()
    targetMeshVtxIt.getUVs(uCoords, vCoords, faceIDs)
    newVertexPosition = om.MPoint(0.0, 0.0, 0.0)
    for faceIdx in range(originalMeshFn.numPolygons()):
        try:    
            originalMeshFn.getPointAtUV(faceIdx, newVertexPosition, uvCoord, om.MSpace.kObject)
            break
        except:
            continue
    targetMeshVtxIt.next()
... getPointAtUV expects the UV coordinates (uvCoord) to be passed as float2 pointer (&). This can be done using the Open Maya MScriptUtil class:

targetMeshVtxIt = om.MItMeshVertex(myTargetDagPath)
while not targetMeshVtxIt.isDone():
    uCoords = om.MFloatArray()
    vCoords = om.MFloatArray()
    faceIDs = om.MIntArray()
    targetMeshVtxIt.getUVs(uCoords, vCoords, faceIDs)
    newVertexPosition = om.MPoint(0.0, 0.0, 0.0)

    util = om.MScriptUtil()
    util.createFromDouble(uCoords[0], vCoords[0])
    uvCoord = util.asFloat2Ptr()

    for faceIdx in range(originalMeshFn.numPolygons()):
        try:    
            originalMeshFn.getPointAtUV(faceIdx, newVertexPosition, uvCoord, om.MSpace.kObject)
            break
        except:
            continue
    targetMeshVtxIt.next()
... Now that we have the source position for the target vertex we can set it:

targetMeshVtxIt = om.MItMeshVertex(myTargetDagPath)
while not targetMeshVtxIt.isDone():
    uCoords = om.MFloatArray()
    vCoords = om.MFloatArray()
    faceIDs = om.MIntArray()
    targetMeshVtxIt.getUVs(uCoords, vCoords, faceIDs)
    newVertexPosition = om.MPoint(0.0, 0.0, 0.0)

    util = om.MScriptUtil()
    util.createFromDouble(uCoords[0], vCoords[0])
    uvCoord = util.asFloat2Ptr()

    for faceIdx in range(originalMeshFn.numPolygons()):
        try:    
            originalMeshFn.getPointAtUV(faceIdx, newVertexPosition, uvCoord, om.MSpace.kObject)
            break
        except:
            continue
    targetMeshVtxIt.setPosition(newVertexPosition, om.MSpace.kObject)
    targetMeshVtxIt.next()


Finally here the complete code:
import maya.cmds as cmds
import maya.OpenMaya as om

cmds.polySphere(r= 1.0, ch=True)
cmds.polyPlane(w=1.0, sw=10, sh=10, ch=True)
cmds.select("pSphere1", "pPlane1")

sel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(sel)
myOriginalDagPath = om.MDagPath()
myTargetDagPath = om.MDagPath()
sel.getDagPath(0, myOriginalDagPath)
sel.getDagPath(1, myTargetDagPath)

originalMeshFn = om.MFnMesh(myOriginalDagPath)

targetMeshVtxIt = om.MItMeshVertex(myTargetDagPath)
while not targetMeshVtxIt.isDone():
    uCoords = om.MFloatArray()
    vCoords = om.MFloatArray()
    faceIDs = om.MIntArray()
    targetMeshVtxIt.getUVs(uCoords, vCoords, faceIDs)
    newVertexPosition = om.MPoint(0.0, 0.0, 0.0)

    util = om.MScriptUtil()
    util.createFromDouble(uCoords[0], vCoords[0])
    uvCoord = util.asFloat2Ptr()

    for faceIdx in range(originalMeshFn.numPolygons()):
        try:    
            originalMeshFn.getPointAtUV(faceIdx, newVertexPosition, uvCoord, om.MSpace.kObject)
            break
        except:
            continue
    targetMeshVtxIt.setPosition(newVertexPosition, om.MSpace.kObject)
    targetMeshVtxIt.next()


Now you can take out the lines that create the sphere and the plane. Also remove the line that selects the two meshes. You create your own geometries and select them so that the original mesh is selected first and the target is selected second.
You should now add some check to this code, the most basic being checking if the selected object is a mesh!

No comments:

Post a Comment