# jj_SnapToPlane
# Version 1.0
# 5th October 2007
# http://www.exch.demon.co.uk/jj_SnapToPlane.htm
# Author: Julian Johnson - julian@exch.demon.co.uk
# Language: Python
# Self-Installing Plugin
# Description: tool to snap points to an infinite plane

import win32com.client
from win32com.client import constants as c
import math
import sys
null = None
false = 0
true = 1
epsilon = 0.0000001
oFilter = Application.Selection.Filter

def disp(ob):
	return win32com.client.Dispatch(ob)
	
def XSILoadPlugin( in_reg ):
	in_reg.Author = "Julian Johnson"
	in_reg.Name = "jj_SnapToPlane"
	in_reg.Email = ""
	in_reg.URL = ""
	in_reg.Major = 1
	in_reg.Minor = 0

	in_reg.RegisterCommand("jj_SnapToPlane","jj_SnapToPlane")
	#RegistrationInsertionPoint - do not remove this line
	return true

def XSIUnloadPlugin( in_reg ):
	strPluginName = in_reg.Name
	Application.LogMessage(str(strPluginName) + str(" has been unloaded."))
	return true

def jj_SnapToPlane_Init( ctxt ):
	oCmd = ctxt.Source
	oCmd.Description = ""
	oCmd.ReturnValue = false
	oArgs = oCmd.Arguments
	oArgs.Add('Arg0',c.siArgumentInput,0)
	return true
	
def selection(Arg0):
	#-----------------------------------------------------------------------
	#Collect All The Affected Elements from the Selection Object
	#-----------------------------------------------------------------------
	oPolys = XSIFactory.CreateObject('XSI.Collection')
	oVerts = XSIFactory.CreateObject('XSI.Collection')
	oEdges = XSIFactory.CreateObject('XSI.Collection')
	
	
	Application.SetSelFilter("Polygon")
	oPolys.AddItems(Application.Selection)
	
	Application.SetSelFilter("Vertex")
	oVerts.AddItems(Application.Selection)
		
	Application.SetSelFilter("Edge")
	oEdges.AddItems(Application.Selection)
	#-----------------------------------------------------------------------
	#Make sure there is only one polygon selected
	#-----------------------------------------------------------------------
	polycount = 0
	for x in oPolys:
		polycount += x.SubComponent.ComponentCollection.Count
	s = 'Select a single target polygon. Currently, you have %s polygons selected.' % polycount
	if oPolys.Count == 0 or polycount > 1:
			oMessage = XSIUIToolkit.Msgbox(s, c.siMsgOkOnly)
	else:
		table = {0:plane,1:edges,2:edge,None:plane}
		table[Arg0](oPolys,oVerts,oEdges)

def jj_SnapToPlane_Execute( Arg0 ):
	selection(Arg0)
	
def plane3D(oPolys):
	#-----------------------------------------------------------------------
	#Establish Plane from Poly Normal
	#-----------------------------------------------------------------------
	oActivePoly = oPolys(0).SubComponent.ComponentCollection(0)
	oPosition = oActivePoly.Points(0).Position
	oNormalArray = oPolys(0).SubComponent.ComponentCollection.NormalArray
	oNormal = XSIMath.CreateVector3(oNormalArray[0][0], oNormalArray[1][0], oNormalArray[2][0])
	#-----------------------------------------------------------------------
	#You need to express the normal in terms of global space
	#-----------------------------------------------------------------------
	ParObjSpace = oActivePoly.SubComponent.Parent3DObject.Kinematics.Global.Transform
	oRot = XSIMath.CreateRotation()
	ParObjSpace.GetRotation(oRot)
	oNormal.MulByRotationInPlace(oRot)
	#-----------------------------------------------------------------------
	#Dot the position of the poly with the plane normal to get dist
	#-----------------------------------------------------------------------
	oPosition = XSIMath.MapObjectPositionToWorldSpace(ParObjSpace , oPosition)
	dist = oNormal.Dot(oPosition)
	return oNormal,dist

def plane(oPolys,oVerts,oEdges):
	#-----------------------------------------------------------------------
	#Convert edges to vertices and put into vert list
	#-----------------------------------------------------------------------
	for x in oEdges:
		nv = disp(x.SubComponent.ComponentCollection.NeighborVertices())
		oVerts.AddItems(nv.SubComponent)
	#-----------------------------------------------------------------------
	oNormal, dist = plane3D(oPolys)
	#-----------------------------------------------------------------------
	#q + (d - q . n) n
	#for each point get global position
	#-----------------------------------------------------------------------
	for x in oVerts:
		ParObjSpace2 = x.SubComponent.Parent3DObject.Kinematics.Global.Transform
		for y in x.SubComponent.ComponentCollection:
			oVpos = XSIMath.MapObjectPositionToWorldSpace(ParObjSpace2 , y.Position)
			dot2 = oVpos.Dot(oNormal)
			scale = dist - dot2
			oScaledNorm = XSIMath.CreateVector3()
			oScaledNorm.Scale(scale,oNormal)
			oVpos.AddInPlace(oScaledNorm)
			Application.Translate(y.SubComponent.Parent3DObject.FullName + '.pnt[%i]' % y.Index, oVpos.X,oVpos.Y,oVpos.Z, 0)
	Application.SetSelFilter(oFilter.Name)

def edges(oPolys,oVerts,oEdges):
	vert_vector = {}
	#-----------------------------------------------------------------------
	#Get the verts that have a selected edge
	#-----------------------------------------------------------------------
	oVertRemove = XSIFactory.CreateObject('XSI.Collection')
	for x in oVerts:
		oVertRemove.items = x.SubComponent.ComponentCollection
	for x in oEdges:#iterate through each edgesubcomponent object
		parspace = x.SubComponent.Parent3DObject.Kinematics.Global.Transform
		for i in x.SubComponent.ComponentCollection:
			nv = disp(i.SubComponent.ComponentCollection.NeighborVertices())
			for y in nv:
				a = oVertRemove.Remove(y)                           
				if a: 
					oposb = a.SubComponent.ComponentCollection(0)#vertex object
					#direction vector of this edge
					if oposb.IsEqualTo(nv(0)):
						oposa = nv(1)
					else:
						oposa = nv(0)
					vec = XSIMath.CreateVector3()
					vecb = XSIMath.CreateVector3()
					veca = XSIMath.CreateVector3()
					vecb = XSIMath.MapObjectPositionToWorldSpace(parspace,oposb.Position)
					veca = XSIMath.MapObjectPositionToWorldSpace(parspace,oposa.Position)
					vec.Sub(vecb,veca)
					vert_vector[oposb.Index] = [oposb,vec,vecb,i] 
	oNormal, dist = plane3D(oPolys)
	for x in vert_vector.keys():
		oVert = vert_vector[x][0]
		oVpos = vert_vector[x][2]
		oVec = vert_vector[x][1]
		dot = oVpos.Dot(oNormal)
		dot2 = oVec.Dot(oNormal)
		if abs(dot2) < epsilon:
			Application.SetSelFilter("Edge")
			Application.DeselectAll()
			Application.SelectObj(vert_vector[x][3])
			s = "There's no intersection with the plane using the selected edge's vector"
			oMessage = XSIUIToolkit.Msgbox(s, c.siMsgOkOnly)
			break
		else:
			t = (dist - dot)/dot2
			oVec.ScaleInPlace(t)
			oVpos.AddInPlace(oVec)
			Application.Translate(oVert.SubComponent.Parent3DObject.FullName + '.pnt[%i]' % oVert.Index, oVpos.X,oVpos.Y,oVpos.Z, 0)
			Application.SetSelFilter("Vertex")
	Application.SetSelFilter(oFilter.Name)
	
def edge(oPolys,oVerts,oEdges):
	#iterate through each edgesubcomponent object 
	edgecount = 0
	for x in oEdges:
		edgecount += x.SubComponent.ComponentCollection.Count
	s = 'Select a single edge. Currently, you have %s edges selected.' % edgecount
	if oEdges.Count == 0 or edgecount > 1:
			oMessage = XSIUIToolkit.Msgbox(s, c.siMsgOkOnly)
	else:
	#-----------------------------------------------------------------------
		for x in oEdges:
			for i in x.SubComponent.ComponentCollection:
				nv = disp(i.SubComponent.ComponentCollection.NeighborVertices()) 

		vec = XSIMath.CreateVector3()
		veca = XSIMath.CreateVector3()
		vecb = XSIMath.CreateVector3()
		ParObjSpace3 = nv(0).SubComponent.Parent3DObject.Kinematics.Global.Transform
		veca = XSIMath.MapObjectPositionToWorldSpace(ParObjSpace3,nv(0).Position)
		vecb = XSIMath.MapObjectPositionToWorldSpace(ParObjSpace3,nv(1).Position)
		vec.Sub(veca,vecb)
		
		oNormal, dist = plane3D(oPolys)
		for x in oVerts:
			for y in x.SubComponent.ComponentCollection:
				oVert = y
				oVpos = y.Position
				oVec = vec
				ParObjSpace2 = oVert.SubComponent.Parent3DObject.Kinematics.Global.Transform
				oVpos = XSIMath.MapObjectPositionToWorldSpace(ParObjSpace2 , oVpos)
				dot = oVpos.Dot(oNormal)
				dot2 = oVec.Dot(oNormal)
				if abs(dot2) < epsilon:
					Application.SetSelFilter("Edge")
					s = "There's no intersection with the plane using the selected edge's vector"
					oMessage = XSIUIToolkit.Msgbox(s, c.siMsgOkOnly)
					break
				else:
					t = (dist - dot)/dot2
					oVec2 = XSIMath.CreateVector3()
					oVec2.Scale(t,oVec)
					oVpos.AddInPlace(oVec2)
					Application.Translate(oVert.SubComponent.Parent3DObject.FullName + '.pnt[%i]' % oVert.Index, oVpos.X,oVpos.Y,oVpos.Z, 0)
	Application.SetSelFilter(oFilter.Name)
