#Imports------------------------------------------------------------------------
import math
#Globals------------------------------------------------------------------------
null = None
false = 0
true = 1

oVec1 = XSIMath.CreateVector3()
oVec2 = XSIMath.CreateVector3()
oVec3 = XSIMath.CreateVector3()
#-------------------------------------------------------------------------------
def XSILoadPlugin( in_reg ):
	in_reg.Author = "Julian Johnson"
	in_reg.Name = "Catmull_RomPlugin"
	in_reg.Major = 1
	in_reg.Minor = 0
	in_reg.RegisterOperator("Catmull_Rom")
	in_reg.RegisterCommand("ApplyCatmull","ApplyCatmull")
	return true

def ApplyCatmull_Init( in_ctxt ):
	oCmd = in_ctxt.Source
	oCmd.Description = "Create an instance of Catmull_Rom operator"
	oCmd.SetFlag(8388608,false)
	return true

def ApplyCatmull_Execute():
	oSel = Application.Selection
	oFil = Application.Filters('null')
	#validity checks--------------------------------------------------------
	if oSel.Count < 2:
		s = 'You need to select at least two nulls' 
		oMessage = XSIUIToolkit.Msgbox(s, 0)
		return
	for x in oSel:
		if not oFil.Match(x):
			s = 'Your object must be a null' 
			oMessage = XSIUIToolkit.Msgbox(s, 0)
			return
		else:#make all the nulls' display properties local for later
			oDis = x.Properties('display')
			Application.MakeLocal(oDis, 0)
			
	#Add control objects----------------------------------------------------
	oRoo = Application.ActiveSceneRoot
	oCur = oRoo.AddNurbsCurve()
	oCu2 = oCur.AddNurbsCurve()
	oCu2.Properties('Visibility').Parameters('selectability').Value = 0
	oNul = [oCur.AddNull('Tangent_Start'),oCur.AddNull('Tangent_End')]
	oCur.Name = 'CatMull_Rom_Curve'
	oCu2.Name = 'Tangent_Display_Curve'
	
	#Make Display properties local------------------------------------------
	oDis = oCu2.Properties('display')
	Application.MakeLocal(oDis, 0)
	for x in oNul:
		oDis = x.Properties('display')
		oDis = Application.MakeLocal(oDis, 0)
		
	#-----------------------------------------------------------------------
	oOp = XSIFactory.CreateObject("Catmull_Rom")
	
	#AddGroups--------------------------------------------------------------
	curgrp = oOp.AddPortGroup("curgrp", 0, 1000)
	tangrp = oOp.AddPortGroup("tangrp", 0, 1000)
	kingrp = oOp.AddPortGroup("kingrp", 0, 1000)
	visgrp = oOp.AddPortGroup("visgrp", 0, 2)#tangent null icon visibility
	usdgrp = oOp.AddPortGroup("usdgrp", 0, 2)#user data in
	tacgrp = oOp.AddPortGroup("tacgrp", 0, 2)#tangent curve line prim
	cukgrp = oOp.AddPortGroup("cukgrp", 0, 2)#curve local kine
	nushap = oOp.AddPortGroup("nushap", 0, 1000)# null icon attributes
	ncolor = oOp.AddPortGroup("ncolor", 0, 1000)# null color
	tcolor = oOp.AddPortGroup("tcolor", 0, 1000)# tangent null color
	
	#Note: No connections can take place until all ports are added
	#AddPorts--------------------------------------------------------------
	oOp.AddIOPortByClassID(63, "tangent_kine",  tangrp.Index)
	oOp.AddInputPortByClassID(63, "null_kine", kingrp.Index)
	oOp.AddIOPortByClassID(44, "tangent_primary_icon", visgrp.Index)
	oOp.AddIOPort(oCur.ActivePrimitive, "curve", curgrp.Index)
	oOp.AddIOPort(oCu2.ActivePrimitive, "cu2", tacgrp.Index)
	oOp.AddInputPortByClassID(63, "curve_local_kine", cukgrp.Index)
	oOp.AddIOPortByClassID(44, "nushap", nushap.Index)
	oOp.AddIOPortByClassID(57, "ncolor", ncolor.Index)
	oOp.AddIOPortByClassID(57, "tcolor", tcolor.Index)
	
	#contruct userdatablob to store null order------------------------------
	oBlob = oCur.AddProperty("UserDataBlob",false,"CatmullDataStore")
	oOp.AddInputPort(oBlob, "ud", usdgrp.Index)
	udArray = []
	for idx,item in enumerate(Application.Selection):
		oOp.ConnectToGroup(kingrp.Index,item.Kinematics.Global)
		oOp.ConnectToGroup(nushap.Index,item.ActivePrimitive)
		oOp.ConnectToGroup(ncolor.Index,item.Properties('Display'))
		udArray.append([idx,str(item.FullName)])
	oBlob.Value = str(udArray)
	
	#connect remaining groups-----------------------------------------------
	oOp.ConnectToGroup(cukgrp.Index,oCur.Kinematics.Global)
	oOp.ConnectToGroup(usdgrp.Index,oBlob)
	oOp.ConnectToGroup(curgrp.Index,oCur.ActivePrimitive)
	oOp.ConnectToGroup(tangrp.Index,oNul[0].Kinematics.Global)
	oOp.ConnectToGroup(tangrp.Index,oNul[1].Kinematics.Global)
	oOp.ConnectToGroup(visgrp.Index,oNul[0].Parameters('primary_icon'))
	oOp.ConnectToGroup(visgrp.Index,oNul[1].Parameters('primary_icon'))
	oOp.ConnectToGroup(tacgrp.Index,oCu2.ActivePrimitive)
	for idx, item in enumerate(oNul):
		oOp.ConnectToGroup(tcolor.Index,item.Properties('Display'))
	Application.InspectObj(oOp)
	return oOp
	
def Catmull_Rom_Define( in_ctxt ):
	oCustomOperator = in_ctxt.Source
	
	paramlist = (("Step",3,10,1,100),
	("Handles",11,0),
	("Use_Handles",11,0),
	("Tangents_Initialised",11,0),
	("Dash_Size",4,.3,.01,1),
	("Null_Icon",3,4,0,10),
	("Null_Icon_Size",4,0.9,0,10),
	("R",4,.875,0,1),
	("G",4,.733,0,1),
	("B",4,.25,0,1),
	("A",4,1,0,1),
	("Even",11,0),
	("Segments",3,20,1,400),
	("ActualSize",11,0),
	("target_len",4,0.5,.01,1000),
	("Adjust",11,0),
	("tR",4,.076,0,1),
	("tG",4,.358,0,1),
	("tB",4,.6,0,1),
	("tA",4,1,0,1),
	("tNull_Icon",3,5,0,10),
	("tNull_Icon_Size",4,2,0,10))
	for x in paramlist:
		oPDef = XSIFactory.CreateParamDef2(*x)
		oCustomOperator.AddParameter(oPDef)
	
	for x in paramlist[12:16]:
		oCustomOperator.Parameters(x[0]).ReadOnly = 1
	return true

def vec_array(vec,p1,p2,p3,p4, step,n):
	for u in range(n,step+1):
		t = u/step
		pts_a = []
		for x in range(3):
			pts_a.append(0.5*((-p1[x]+3*p2[x]-3*p3[x]+p4[x])*t*t*t+\
			(2*p1[x]-5*p2[x]+4*p3[x]-p4[x])*t*t+(-p1[x]\
			+p3[x])*t+2*p2[x]))
		vec.append(pts_a)
	return(vec)

def dotted_curve(l,v1,v2,v3,dash):
	dash = max(.01,dash)
	d = []
	steps = max(1,l/dash)
	t = 1/steps
	c = 0
	for x in range(steps+1):
		oVec3.LinearlyInterpolate(v1,v3,c)
		d.append(oVec3.Get2())
		c += t
	nbcrvs = math.floor(len(d)/2)
	if len(d) % 2: d.pop()
	return(d,nbcrvs)

def sum_lengths(v_array):
	l = []
	for idx, item in enumerate(v_array):
		oVec1.Set(*item)
		try:
			oVec2.Set(*v_array[idx+1])
			oVec3.Sub(oVec1,oVec2)
			l.append(oVec3.Length())
		except IndexError:
			pass
	return(l)
	
def Catmull_Rom_Update( in_ctxt ):
	#Ports------------------------------------------------------------------
	output 		= in_ctxt.OutputTarget
	port 		= in_ctxt.OutputPort
	
	#Params-----------------------------------------------------------------
	handles 	= in_ctxt.GetParameterValue("Handles")
	uhandles 	= in_ctxt.GetParameterValue("Use_Handles")
	ti 		= in_ctxt.GetParameterValue("Tangents_Initialised")
	
	target_len 	= in_ctxt.GetParameterValue("target_len")
	actual_size 	= in_ctxt.GetParameterValue("ActualSize")
	adjust		= in_ctxt.GetParameterValue("Adjust")
	
	#Local Transform of Curve-----------------------------------------------
	oCl = in_ctxt.GetInputValue(0,6,0)
	
	#Tangents---------------------------------------------------------------
	oTan1 = in_ctxt.GetInputValue(0,1,0)
	oTan2 = in_ctxt.GetInputValue(0,1,1)
	
	oT1 = XSIMath.MapWorldPositionToObjectSpace\
	(oCl.Transform, oTan1.Transform.Translation)
	oT2 = XSIMath.MapWorldPositionToObjectSpace\
	(oCl.Transform, oTan2.Transform.Translation)
	
	#Data from Blob---------------------------------------------------------
	userD =  in_ctxt.GetInputValue(0,4,0)
	userD = eval(userD.Value)
	
	#-----------------------------------------------------------------------
	count = in_ctxt.Source.GetNumInstancesInGroup(2)
	pts = []
	#-----------------------------------------------------------------------
	for x in range(count):
		oK = in_ctxt.GetInputValue(0,2,x)
		oW = oK.Transform.Translation
		oV = XSIMath.MapWorldPositionToObjectSpace(oCl.Transform, oW) 
		pts.append(oV.Get2())
	#order points-----------------------------------------------------------	
	pts_ordered = [ [] for x in range(len(pts)) ]
	for idx,item in enumerate(userD):
		pts_ordered[item[0]] = pts[idx]
	#-----------------------------------------------------------------------
	if port.GroupIndex == 0:
		step = float(in_ctxt.GetParameterValue("Step"))
		segments = in_ctxt.GetParameterValue("Segments")
		even = in_ctxt.GetParameterValue("Even")
		
		vec = []
		for idx in range(0,len(pts)-1):
			try:
				p1 = pts_ordered[max(idx-1,0)]
			except IndexError:
				p1 = pts_ordered[idx]
			
			if uhandles == 1 and idx == 0:
				p1 = oT1.Get2()
				
			p2 = pts_ordered[idx]
			p3 = pts_ordered[idx+1]
			
			try:
				p4 = pts_ordered[idx+2]
			except IndexError:
				p4 = pts_ordered[idx+1]
			
			if uhandles == 1 and idx == len(pts)-2:
				p4 = oT2.Get2()
			if idx != 0: n = 1#take care of the 1st pnt on the curve
			else: n = 0
			vec = vec_array(vec,p1,p2,p3,p4,step,n)
		
		#---------------------------------------------------------------
		if even:
		#sum up the vector lengths
			lengths = sum_lengths(vec)
			accum = 0
			lengths_a = []
			for x in lengths:
				r = x+accum
				lengths_a.append(r)
				accum = r
			lengths_a.insert(0,0)
			
			#-------------------------------------------------------
			if actual_size:
				target_len = min(lengths_a[-1],target_len)
				segments = int(lengths_a[-1]/target_len)
				seglen = [target_len,lengths_a[-1]/segments][adjust]
			else:
				segments  = float(segments)
				seglen = lengths_a[-1]/segments
				
			seglenlist  = []
			for x in range(segments):
				seglenlist.append(seglen*x)
			vec_test = []
			for x in seglenlist:
				lengths_a.append(x)
				lengths_a.sort()
				index = lengths_a.index(x)
				oVec1.Set(*vec[max(0,index-1)])
				try:
					oVec2.Set(*vec[index])
				except IndexError:
					oVec2.Set(*vec[index-1])
					
				pre = lengths_a[max(0,index-1)]
							
				try:	
					post  = lengths_a[index+1]
				except IndexError:
					post = lengths_a[index]
					
				minus = lengths_a[index] - pre
				tspace = post - pre
				try:
					u = minus/tspace
				except ZeroDivisionError:
					u = 0
				oVec3.LinearlyInterpolate(oVec1,oVec2,u)
				
				vec_test.append(oVec3.Get2())
				lengths_a.pop(index)
			vec_test.append(vec[-1])
			vec = vec_test
		#---------------------------------------------------------------
		vec = zip(*vec)
		vec.append([1 for x in range(len(vec[0]))])
	
		#main curve-----------------------------------------------------
		output.Geometry.Set(1,vec,None,None,None,None,(1,),(1,))
	
	#tangent null kine------------------------------------------------------	
	if port.GroupIndex == 1:
		if not handles:#if handle display is off
			if port.GroupInstance == 0:#start tangent null
				oTrans = XSIMath.CreateTransform()
				if ti:
					oTrans.Translation = oT1
				else:
					oTrans.SetTranslationFromValues(*pts_ordered[0])
				output.Transform = oTrans
			else:#end tangent null
				oTrans = XSIMath.CreateTransform()
				if ti:
					oTrans.Translation = oT2
				else:
					oTrans.SetTranslationFromValues(*pts_ordered[-1])
				output.Transform = oTrans
	
	#check for icon display - note that ConnectToGroup is *not* connecting
	#to the parameter correctly but to the primitive
	#primary icon toggle----------------------------------------------------
	if port.GroupIndex == 3:
		tnsize = in_ctxt.GetParameterValue("tNull_Icon_Size")
		tnicon = in_ctxt.GetParameterValue("tNull_Icon")
		
		if handles:
			output.Parameters('primary_icon').Value = tnicon
			output.Parameters('size').Value = tnsize
		else:
			output.Parameters('primary_icon').Value = 0
		#use handles boolean to select from a list of icon values
	
	#-----------------------------------------------------------------------
	if port.GroupIndex == 7:
		nsize = in_ctxt.GetParameterValue("Null_Icon_Size")
		nicon = in_ctxt.GetParameterValue("Null_Icon")
		
		output.Parameters('primary_icon').Value = nicon
		output.Parameters('size').Value = nsize
	#-----------------------------------------------------------------------
	if port.GroupIndex == 8:
		red = in_ctxt.GetParameterValue("R")
		green = in_ctxt.GetParameterValue("G")
		blue = in_ctxt.GetParameterValue("B")
		
		output.Parameters('wirecolorr').Value = red
		output.Parameters('wirecolorg').Value = green
		output.Parameters('wirecolorb').Value = blue
	
	if port.GroupIndex == 9:
		tred = in_ctxt.GetParameterValue("tR")
		tgreen = in_ctxt.GetParameterValue("tG")
		tblue = in_ctxt.GetParameterValue("tB")
		
		output.Parameters('wirecolorr').Value = tred
		output.Parameters('wirecolorg').Value = tgreen
		output.Parameters('wirecolorb').Value = tblue
	#tangentcurvedrawlines--------------------------------------------------
	if port.GroupIndex == 5:
		dash = in_ctxt.GetParameterValue("Dash_Size")
		if handles:
			#-------------------------------------------------------
			d = []
			e = []
			nbcrvs = 0
			nbcrvs2 = 0
			l = [None,None]
			#Tan1---------------------------------------------------
			oVec1.Set(*pts_ordered[0])
			oVec2.Sub(oTan1.Transform.Translation,oVec1)
			l[0] = oVec2.Length()
			if l[0]:
				d, nbcrvs = dotted_curve(l[0],oVec1,oVec2,oT1,dash)
			#Tan2---------------------------------------------------
			oVec1.Set(*pts_ordered[-1])
			oVec2.Sub(oTan2.Transform.Translation,oVec1)	
			l[1] = oVec2.Length()	
			if l[1]:
				e, nbcrvs2 = dotted_curve(l[1],oVec1,oVec2,oT2,dash)
			#Accumulate data for both tangent lines-----------------
			if l[0] or l[1]:
				nbcrvs += nbcrvs2
				knotlens = [2 for x in range(nbcrvs)]
				misc     = [1 for x in range(nbcrvs)]
				d += e
				d = zip(*d)
				d.append([1 for x in range(len(d[0]))])
				if len(d[0]) > 1:
					output.Geometry.Set(nbcrvs,d,knotlens,None,None,None,misc,misc)
			else:
				output.Geometry.Set(1,[[0,0],[0,0],[0,0],[1,1]],None,None,None,None,(1,),(1,))
		else:
			output.Geometry.Set(1,[[0,0],[0,0],[0,0],[1,1]],None,None,None,None,(1,),(1,))

	return true

def Catmull_Rom_DefineLayout( in_ctxt ):
	
	a = ('None',0,'Null',1,'Rings',2,"Arrow Rings",3,"Box",4,"Circle",5,
	"Square",6,"Diamond",7,"Pyramid",8,"Pointed Box",9,"Arrow",10)
	
	oLayout = in_ctxt.Source
	oLayout.Clear()
	oLayout.AddTab('Main')
	oLayout.AddItem("Step","Step/Accuracy")
	
	oLayout.AddGroup()
	oLayout.AddItem("Use_Handles", "Use Tangent Handles")
	oLayout.AddItem("Handles", "Display Tangent Handles")
	oLayout.EndGroup()
	
	oLayout.AddGroup()
	oLayout.AddRow()
	oLayout.AddButton("PickAdditional", "Pick Additional Knot")
	oLayout.AddButton("Remove_Knot", "Remove Knot")
	oLayout.EndRow()
	oLayout.EndGroup()
	
	oLayout.AddGroup()
	oLayout.AddItem("Even", "Even Spacing")
	oLayout.AddItem("Segments", "Number of Segments")
	oLayout.AddItem("ActualSize", "Actual Size")
	oLayout.AddItem("target_len", "Length")
	oLayout.AddItem("Adjust", "Adjust")
	oLayout.EndGroup()
	
	oLayout.AddGroup('Clean Up')
	oLayout.AddButton("CleanUp", "Delete Operator")
	oLayout.EndGroup()
	
	oLayout.AddTab('Display')
	
	oLayout.AddGroup('Tangents')
	oLayout.AddColor('tR', 'Icon Colour')
	oLayout.AddItem('tNull_Icon_Size', "Null Size")
	oLayout.AddEnumControl('tNull_Icon', a , 'Null Icon')
	oLayout.AddItem("Dash_Size", "Dash Size")
	oLayout.EndGroup()
	
	oLayout.AddGroup('Control Nulls')
	oLayout.AddColor('R', 'Icon Colour')
	oLayout.AddItem('Null_Icon_Size', "Null Size")
	oLayout.AddEnumControl('Null_Icon', a, 'Null Icon')
	oLayout.EndGroup()
	
	return true

def Catmull_Rom_Even_OnChanged():
	oOp = PPG.Inspected(0)
	evenvalue = oOp.Parameters('Even').Value #0 is off, 1 is on
	for x in ("Segments","ActualSize"):
		oOp.Parameters(x).ReadOnly = (1,0)[evenvalue]
	if evenvalue and oOp.Parameters('ActualSize').Value:
		for x in ("target_len","Adjust"):
			oOp.Parameters(x).ReadOnly = 0
	else:
		for x in ("target_len","Adjust"):
			oOp.Parameters(x).ReadOnly = 1

def Catmull_Rom_ActualSize_OnChanged():
	oOp = PPG.Inspected(0)
	actsize = oOp.Parameters('ActualSize').Value
	for x in ("target_len","Adjust"):
		oOp.Parameters(x).ReadOnly = (1,0)[actsize]
	
def Catmull_Rom_CleanUp_OnClicked():
	oOp = PPG.Inspected(0)
	oParent = oOp.Parent3DObject
	Application.DeleteObj(oOp)
	Application.DeleteObj('B:'+str(oParent.FullName))
	
def Catmull_Rom_Use_Handles_OnChanged( ):
	#if you turn 'use_handles' on
	if PPG.Inspected(0).Parameters('Use_Handles').Value:
		PPG.Inspected(0).Parameters('Handles').Value = 1 #displays hands
	PPG.Inspected(0).Parameters('Tangents_Initialised').Value = 1

def Catmull_Rom_PickAdditional_OnClicked( ):
	ecount = 0
	pickar = []
	oOp = PPG.Inspected(0)
	oBlobPort = oOp.PortAt(0,4,0)
	UserData =  oBlobPort.Target2
	UD_array =  eval(UserData.Value)
	#check that the picked elements are a) nulls and b) the insertion
	#null is part of the curve.
	d = 0
	oFilter = Application.Filters('Null')
	while ecount < 2:
		if ecount < 1: 
			a = ['Pick Null To Insert After','Pick Null To Insert Before']
		else:
			a = ['Pick Null To Insert','Pick Null To Insert']
		
		oPicked = Application.PickObject(*a)
		if oPicked(0) == 0: return
		
		oNull = oPicked(2)
		if ecount < 1:
			if oFilter.Match(oNull):
				if oNull.FullName in zip(*UD_array)[1]:
					pickar.append(oPicked(2))
					d = oPicked(0)
				else:
					s = 'Null is not part of curve' 
					oMessage = XSIUIToolkit.Msgbox(s, 0)
					return
			else:
				s = 'You need to select a Null' 
				oMessage = XSIUIToolkit.Msgbox(s, 0)
				return
		else:
			if oFilter.Match(oNull):
				pickar.append(oPicked(2))
			else:
				s = 'You need to select a Null' 
				oMessage = XSIUIToolkit.Msgbox(s, 0)
				return
		ecount = len(pickar)	
		
	oInse = pickar[0]
	oNull = pickar[1]
	
	for idx,item in enumerate(UD_array):
		if item[1] == oInse.FullName:
			i_point = item[0] + (2-d)
			break
	for x in UD_array:
		if x[0] >= i_point: x[0] += 1
			
	UD_array.append([i_point,str(oNull.FullName)])
	UserData.Value = str(UD_array)
	oOp.ConnectToGroup(2,oNull.Kinematics.Global)
	oOp.ConnectToGroup(7,oNull.ActivePrimitive)
	Application.MakeLocal(oNull.Properties('Display'))
	oOp.ConnectToGroup(8,oNull.Properties('Display'))

def Catmull_Rom_Remove_Knot_OnClicked( ):
	oOp = PPG.Inspected(0)
	oBlobPort = oOp.PortAt(0,4,0)
	UserData =  oBlobPort.Target2
	UD_array =  eval(UserData.Value)
	#-----------------------------------------------------------------------
	#check that the picked elements are a) nulls and b) the insertion
	#null is part of the curve.
	oFilter = Application.Filters('Null')
	a = ['Pick Null To Remove','Pick Null To Remove']
	oPicked = Application.PickObject(*a)
	if oPicked(0) == 0: return
	oNull = oPicked(2)
	#-----------------------------------------------------------------------
	if oFilter.Match(oNull):
		if oNull.FullName not in zip(*UD_array)[1]:
			s = 'Null is not part of curve' 
			oMessage = XSIUIToolkit.Msgbox(s, 0)
			return
	else:
		s = 'You need to select a Null' 
		oMessage = XSIUIToolkit.Msgbox(s, 0)
		return
	#-----------------------------------------------------------------------
	for idx,item in enumerate(UD_array):
		if item[1] == oNull.FullName:
			removal_id = idx
			i_point = item[0]#
			break
	#-----------------------------------------------------------------------
	#everything above i-point has to come down an index by 1
	UD_array.pop(idx)
	for x in UD_array: 
		if x[0] >= i_point: x[0] -= 1
	#-----------------------------------------------------------------------
	UserData.Value = str(UD_array)
	oOp.DisconnectGroup(2, removal_id)
	oOp.DisconnectGroup(7, removal_id)
	oOp.DisconnectGroup(8, removal_id)
