#Version 1.02
#-------------------------------------------------------------------------------
#01.Trapped bug in 6.x where pointlocators fails with ClosestLocations
#-------------------------------------------------------------------------------
#Dated:10th March 2008

import win32com.client
from win32com.client import constants as c
import math
import sys

null = None
false = 0
true = 1

s1 = """Your selection forms a complete loop
i.e. if you look at the structure of the
underlying vertices you will see that it
could be interpreted as a closed loop
not an L-shaped start and an open end"""

def XSILoadPlugin( in_reg ):
	in_reg.Author = "Julian Johnson"
	in_reg.Name = "jj_smartquadPlugin"
	in_reg.Major = 1
	in_reg.Minor = 0
	in_reg.RegisterCommand("jj_SmartQuad","jj_SmartQuad")
	in_reg.RegisterCommand("jj_SmartQuadC","jj_SmartQuadC")
	return true
	
def jj_SmartQuad_Init( ctxt ):
	oCmd = ctxt.Source
	oCmd.Description = ""
	oCmd.ReturnValue = true
	return true

def jj_SmartQuad_Execute( ):
	main(0)
	return true
	
def jj_SmartQuadC_Init( ctxt ):
	oCmd = ctxt.Source
	oCmd.Description = ""
	oCmd.ReturnValue = true
	return true

def jj_SmartQuadC_Execute( ):
	main(1)
	return true
	
def msgbox(s):
	oMessage = XSIUIToolkit.Msgbox(s, c.siMsgOkOnly)
	
def contigcheck(verts):
	"""This iterates over the verts and outputs an array which contains
	neighb verts collection, nbvert count, nb edges, nb edges count,
	the vert object itself, per vertex"""
	oTempColl = XSIFactory.CreateObject('XSI.Collection')
	table = {}
	loop = []
	for x in verts:
		oTempColl.AddItems(verts)
		oResult = oTempColl.RemoveItems(x.NeighborVertices())
		if oResult.Count == 0:
			s = 'All points need to be connected by a shared edge'
			msgbox(s)
			return
			break
		else:
			loop.append(oResult.Count)
			table[x.Index] = [oResult, oResult.Count, x.NeighborEdges(), x.NeighborEdges().Count, x]
	if 1 not in loop:
		msgbox(s1)
		return
	return table
		
def sortarray(x,y):
	"""This just sorts a list of lists"""
	return cmp(x[1],y[1])
	
def selectionhandler():
	"""Filters out bad selections: more than one mesh; no mesh;
	less than three verts"""
	oSel = Application.Selection
	if oSel.Count > 1:
		s = 'You have geometry selected on more than one object'
		msgbox(s)
		return
	elif oSel.Count == 0:
		s = 'You must select something'
		msgbox(s)
		return
		
	oSubCmp = oSel(0).SubComponent
	 
	if oSubCmp.Type == 'edgeSubComponent':
		oVerts = oSubCmp.ComponentCollection.NeighborVertices()
	else:	
		oVerts = oSubCmp.ComponentCollection
		
	if oVerts.Count < 3:
		s = 'You need to select at least 3 verts'
		msgbox(s)
		return
	
	oParent = oSubCmp.Parent3DObject
	return oVerts, oParent

def sequence(pre,active,table):
	"""Makes sure the sequence is ordered correctly"""	
	ordered = []
	preVert = pre
	activeVert = active
	ordered.extend((preVert,activeVert))
	count = 0
	while activeVert:
		if table[activeVert.Index][0].Count > 1:
			for x in table[activeVert.Index][0]:
				if x.SubComponent.ComponentCollection(0).Index != preVert.Index:
					ordered.append(x.SubComponent.ComponentCollection(0))
					preVert = activeVert
					activeVert = x.SubComponent.ComponentCollection(0)
					break
		else: activeVert = None
	return ordered	

def anglevecs(seq):
	pyanglevecs = []
	for index, item in enumerate(seq[:-1]):
		vec = XSIMath.CreateVector3()
		vec.Sub(item.Position,seq[index+1].Position)
		pyanglevecs.append(vec)
	return pyanglevecs

def ptorder(b,mid):
	for x in b.NeighborPolygons():
		for y in mid.NeighborPolygons():
			if x.IsEqualTo(y):
				poly = x
				break
	
	array = list(poly.Points.IndexArray)
	bpos = array.index(b.Index)
	mpos = array.index(mid.Index)
	
	if abs(mpos-bpos) > 1:
		if cmp(mpos,bpos) == 1:
			return 1
		else:
			return 0
	elif array.index(b.Index) < array.index(mid.Index):
		return 0
	else:
		return 1

def build(A,B,oMid,nuVec,ptord,oParent):
	Application.ApplyOp("CreatePolygon", oParent)
	ptAindex = Application.AddPointToNewPolygon(oParent, nuVec.X, nuVec.Y, nuVec.Z)
	l = {1:(B,oMid,A),0:(A,oMid,B)}
	for x in l[ptord]:
		Application.ConnectNewPolygonToPoint( oParent, x.Index, 0 )
	return ptAindex
	
def buildconnect(A,B,oMid,vert,ptord,oParent):
	Application.ApplyOp("CreatePolygon", oParent)
	l = {1:(vert,B,oMid,A),0:(vert,A,oMid,B)}
	for x in l[ptord]:
		Application.ConnectNewPolygonToPoint( oParent, x.Index, 0 )

def build3pt_new(A,B,M,oParent,connect):
	pyanglevecs2 = anglevecs([A,M,B])
	
	dispVec = pyanglevecs2[-1]
	dispVec.NegateInPlace()
	
	nuVec = XSIMath.CreateVector3()
	nuVec.Add(A.Position,dispVec)
	
	ptord = ptorder(B,M)
	
	if connect:
		oPTrans = oParent.Kinematics.Local.Transform
		oGeo = oParent.ActivePrimitive.Geometry
		
		veclist = ((nuVec.X,), (nuVec.Y,), (nuVec.Z,))
		radius = max(oGeo.GetBoundingBox()[3:])
		
		oGeo.SetupClosestLocationQueries(c.siClosestVertexOrKnot,oPTrans)
		
		if Application.Version()[0] == '5':
			PointLocators = oGeo.GetClosestLocations(veclist)
		else:
			PointLocators = oGeo.GetClosestLocationsWithinRadius(nuVec,radius,1)
		vertid = oGeo.GetTriangleVertexIndexArray(PointLocators, [0])
		vert = oGeo.Points(vertid[0][0])
		buildconnect(A,B,M,vert,ptord,oParent)
	
	else: build(A,B,M,nuVec,ptord,oParent)	
	
def build2(pyanglevecs,seq,oParent,Arg0):
	v1D = XSIMath.CreateVector3()
	v2D = XSIMath.CreateVector3()
	v3D = XSIMath.CreateVector3()
	v4D = XSIMath.CreateVector3()
	v5D = XSIMath.CreateVector3()
	v8D = XSIMath.CreateVector3()
	
	pyptA = [seq[0]]
	ptA = seq[0]
	
	for index, item in enumerate(seq[1:]):
		ptApos = ptA.Position
		oMidPos = item.Position
		ptBpos = seq[index+2].Position

		a = pyanglevecs[index+1]
		
		try:
			b = pyanglevecs[index+2]
		except IndexError:
			build3pt_new(ptA,seq[index+2],item,oParent,Arg0)
			return
		
		a.NegateInPlace()
		a.NormalizeInPlace()
		b.NormalizeInPlace()
	
		dot1 = a.Dot(b)
		if dot1 < -0.9999999999:
			v1D.Sub(oMidPos,ptBpos)
			v2D.Sub(ptApos,ptBpos)
			v3D.Cross(v1D,v2D)
			v3D.NormalizeInPlace()
			v4D.Cross(v3D,v1D)
			v4D.NormalizeInPlace()
			dot = v2D.Dot(v4D)
			v4D.ScaleInPlace(dot)
			v8D.Add(ptBpos,v4D)
		else:
			v1D.LinearlyInterpolate(a,b,0.5)
			v1D.NormalizeInPlace()
			v3D.Sub(oMidPos,ptBpos)
			v3D.NormalizeInPlace()
			v4D.Cross(v3D,v1D)
			v5D.Cross(v4D,v1D)
			v5D.NormalizeInPlace()
			d = ptBpos.Dot(v5D)
			dist = ptApos.Dot(v5D)
			dist2 = v3D.Dot(v5D)
			t = (d - dist)/dist2
			v3D.ScaleInPlace(t)
			v8D.Add(ptApos,v3D)
		
		ptord = ptorder(seq[index+2],item)
		ptAindex = build(ptA,seq[index+2],item,v8D,ptord,oParent)
		
		ptA = Application.GetValue(oParent.FullName+".pnt[%s]" % ptAindex) 
		ptA = ptA.SubComponent.ComponentCollection(0)
		pyptA.append(ptA)

def midpoint(table,oVerts):
	"""Finds midpoints with four edges (possibly more than one)
	and two nb points in oVerts"""
	oTempColl = XSIFactory.CreateObject('XSI.Collection')
	midlst = []
	for x in table.values():
		mid = x[4]
		if x[3] == 4:
			oTempColl.AddItems(oVerts)
			oResult = oTempColl.RemoveItems(x[0])
			if oResult.Count == 2:
				midlst.append(x)
	return midlst
		
def main(Arg0):
	oVerts, oParent = selectionhandler()
	if not oVerts: return
	table = contigcheck(oVerts)
	if not table: return
	
	if oVerts.Count == 3:
		values = table.values()
		values.sort(sortarray)
		oMid = values[2][4]
		A = values[1][4]
		B = values[0][4]
		build3pt_new(A,B,oMid,oParent,Arg0)
		return
		
	midlst = midpoint(table,oVerts)
	
	if (len(midlst)) > 1:
		s = "You have a double 'L' Shape"
		msgbox(s)
		return
	
	if not midlst:
		values = table.values()
		values.sort(sortarray)
		
		startpoint = values[0][4]
		nbpt = values[0][0]
		nbpoint = nbpt(0).SubComponent.ComponentCollection(0)
		
		seq =  sequence(startpoint,nbpoint,table)
		pyanglevecs = anglevecs(seq)
		
		dots = []
		for idx, item  in enumerate(pyanglevecs[:-1]):
			item.NormalizeInPlace()
			pyanglevecs[idx+1].NormalizeInPlace()
			dot = item.Dot(pyanglevecs[idx+1])
			dots.append(dot)
		epsilon = 0.0000001
		if 1 - dots[0] < epsilon and 1 - dots[-1] < epsilon:
			s = "You have no discernible \nL Shape"
			msgbox(s)
			return
		if not cmp(dots[0],dots[-1]) < 1:
			seq.reverse()
			pyanglevecs.reverse()
		build2(pyanglevecs,seq,oParent,Arg0)
		return
		
	for x in midlst:
		for y in x[0]:
			if table[y.SubComponent.ElementArray[0]][1] == 1:
				A = y.SubComponent.ComponentCollection[0]
		try: 
			A
		except UnboundLocalError:
			s = "Your L Shape is Malformed!"
			msgbox(s)
			return
	
	oMid = midlst[0][4]
	seq = sequence(A,oMid,table)
			
	pyanglevecs = anglevecs(seq)
	build2(pyanglevecs,seq,oParent,Arg0)
		

