'''
Copyright (c) 2011, Stephen Hopkins
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
        
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
import pymel.core as pm
from pymel.core.general import MeshFace, MeshVertex
from pymel.util.arrays import cross, dot

#This function calculates the face normal and moves the point
def pointFunction(face):
    selection = pm.ls(selection = True)
    if len(selection) != 1 or type(selection[0]) != MeshVertex:
        pm.warning("Selection must be a single vertex")
        return
    
    pointToMove = selection[0].getPosition()
    points = face.getPoints()
    p0 = p1 = p2 = pointToMoveIndex = None
    for i in range(len(points)):
        p = points[i]
        if p.isEquivalent(pointToMove, 0.00001):
            pointToMoveIndex = i
            continue
        if p0  ==  None:
            p0 = p
        elif p1 == None:
            p1 = p
        elif p2 == None:
            p2 = p
            
    if pointToMoveIndex == None:
        pm.warning("The selected vertex must lie on the original face")
        return
    
    normal = cross(p2 - p1, p0 - p1)
    normal.normalize()

    #calculate the new point, using closest point on plane from the selected point
    vectorFromPlane = pointToMove - p0
    distanceFromPlane = dot(vectorFromPlane, normal)
    newPoint = pointToMove - normal * distanceFromPlane
    print "Result: ", pointToMove, "->", newPoint        
    face.setPoint(newPoint, pointToMoveIndex)
    face.updateSurface()

#start the tool    
def start(*args):
    selection = pm.ls(selection = True)
    #validate the selection
    if len(selection) != 1 or type(selection[0]) != MeshFace:
        pm.warning("Selection must be a single face")
        return
    face = selection[0]
    if face.polygonVertexCount() != 4:
        pm.warning("The face must be a quad")
        return
    pm.headsUpMessage('Select the vertex to move')
    #change selection mode to vertex, pass face to next function
    pm.selectMode(object = True) 
    pm.selectType(allObjects = True, objectComponent = True, vertex = True)
    pm.hilite(selection)
    pm.scriptJob(parent = window, event=["SelectionChanged", lambda *args: pointFunction(face)], runOnce = True)


#Set up the gui
window = pm.window( title="Planar Quad", iconName='pquad', widthHeight=(200, 55), resizeToFitChildren = True)  
pm.columnLayout( adjustableColumn=True, columnAlign="left")
pm.text(label="This tool forces one vertex onto the plane\nof the other 3 vertices of a quad\n")
pm.text(label="Select a face and press start")
pm.headsUpMessage('Select a face and press start')
pm.button(label="start", width=60, command=start)
pm.showWindow(window)
