#jj_camresize.py
#julian johnson
#julian@exch.demon.co.uk
#tool to underscan/overscan a scene camera
#12/07/2009
#Version 1.0.0
#To Do:
#Tidy and conslidate callbacks into smaller functions
#Remove duplication of similar callbacks
#Reset overscan
#-------------------------------------------------------------------------------


from win32com.client import constants as c
import math
null = None
false = 0
true = 1

def XSILoadPlugin( in_reg ):
	in_reg.Author = "Administrator"
	in_reg.Name = "jj_camresize"
	in_reg.Major = 1
	in_reg.Minor = 1
	
	in_reg.RegisterOperator("jj_camresize")
	in_reg.RegisterCommand("Apply_jj_camresize")
	
	return true
	
def Apply_jj_camtools( ctxt ):
	oCmd = ctxt.Source
	oCmd.SetFlag(8388608,false)
	return true
	
def getcamera():
	oCams = Application.ActiveSceneRoot.FindChildren("" ,c.siCameraFilter )
	a = []
	
	for x in oCams: a.extend([x.FullName,x.FullName])
	
	oProp = XSIFactory.CreateObject( "CustomProperty" )
	oProp.Name = "Select Camera"
	oProp.AddParameter3("selecter",c.siString, a[1],c.siString )
	
	oLayout = oProp.PPGLayout
	oLayout.Clear()
	oLayout.AddEnumControl( "selecter", a,"",c.siControlCombo )
	
	cancel = Application.InspectObj(oProp,"","",c.siModal,0)
	if not cancel:
		r = oProp.Parameters("selecter").Value
		Application.DeleteObj(oProp)
		return r
	else:	
		return ""
		
	
def Apply_jj_camresize_Execute():
	camstring = getcamera()
	if camstring:
		oCamera = Application.Dictionary.GetObject(camstring)
	else:
		return
	newOp = XSIFactory.CreateObject("jj_camresize")
	newOp.AddIOPort(oCamera.ActivePrimitive)
	
	camera = newOp.AddParameter( XSIFactory.CreateParamDef2("camera", c.siString))
	camera.Value = oCamera.FullName
	
	selecter = newOp.AddParameter( XSIFactory.CreateParamDef2("selecter", c.siInt4,1))
	
	oxres =  newOp.AddParameter( XSIFactory.CreateParamDef2("oxres", c.siInt4))
	oyres = newOp.AddParameter( XSIFactory.CreateParamDef2("oyres", c.siInt4))
	
	opixratio = newOp.AddParameter( XSIFactory.CreateParamDef2("opixratio", c.siDouble))
	oaspect = newOp.AddParameter( XSIFactory.CreateParamDef2("oaspect", c.siDouble))
	ofov = newOp.AddParameter( XSIFactory.CreateParamDef2("ofov", c.siDouble,1))
	ofovtype = newOp.AddParameter( XSIFactory.CreateParamDef2("ofovtype", c.siInt4))
	
	ofovtype.Value = oCamera.ActivePrimitive.Parameters( "fovtype" ).Value
	oaspect.Value = oCamera.ActivePrimitive.Parameters( "aspect" ).Value
	ofov.Value = oCamera.ActivePrimitive.Parameters( "fov" ).Value
	
	oCurrentPass = Application.ActiveProject.ActiveScene.ActivePass.Parameters
	
	if oCurrentPass("ImageFormatOverride").Value:
		opixratio.Value =  oCurrentPass("ImagePixelRatio").Value
		oxres.Value = oCurrentPass("ImageWidth").Value
		oyres.Value = oCurrentPass("ImageHeight").Value
	else:
		oScene = Application.ActiveProject.ActiveScene.PassContainer.Properties("Scene Render Options").Parameters
		opixratio.Value = oScene("ImagePixelRatio").Value
		oxres.Value = oScene("ImageWidth").Value
		oyres.Value = oScene("ImageHeight").Value
	
	tr_x = newOp.AddParameter( XSIFactory.CreateParamDef2("tr_x", c.siDouble,0,0,oxres.Value -1))
	tr_y = newOp.AddParameter( XSIFactory.CreateParamDef2("tr_y", c.siDouble,0,0,oyres.Value -1))
	
	bl_x = newOp.AddParameter( XSIFactory.CreateParamDef2("bl_x", c.siDouble,0,1,oxres.Value))
	bl_y = newOp.AddParameter( XSIFactory.CreateParamDef2("bl_y", c.siDouble,0,1,oyres.Value))
	
	tr_x.Value = 0
	tr_y.Value = 0
	
	bl_x.Value = oxres.Value
	bl_y.Value = oyres.Value
	
	overx =  newOp.AddParameter( XSIFactory.CreateParamDef2("overx", c.siInt4,0))
	overy = newOp.AddParameter( XSIFactory.CreateParamDef2("overy", c.siInt4,0))
	
	newOp.Connect()
	Application.InspectObj(newOp)
	
	return newOp
	
def calcfov2(fovtype, fov, ndx, dx, ndy, dy):
	"""input the new 'length' of the desired screen area. The ratio
	between this and the existing screen size is used to multiply tan theta.
	The resized tan theta converts back to a new angle"""
	if fovtype == 1:
		fov = fov * math.pi/180.0
		degrees = math.atan( (ndx/float(dx)) * math.tan(fov/2.0))  * 180.0/math.pi
	else:
		fov = fov * math.pi/180.0
		degrees = math.atan( (ndy/float(dy)) * math.tan(fov/2.0))  * 180.0/math.pi
	return degrees	

def normalise(v,fov,aspect,fovtype):
	"""converts camera coordinates on the z plane at camera z -1 to 
	normalised coordinates on the screen between -1 and 1"""
	tan = math.tan((fov*math.pi/180) * 0.5)
	if fovtype == 1:
		nx = v.X * 1.0/ tan
		ny = v.Y * 1.0/(tan / aspect)
	else:
		nx = v.X * 1.0/(tan * aspect)
		ny = v.Y * 1.0/ tan
	return nx, ny

def underscan_layout(oLayout):
	oLayout.Clear()
	a = ("Underscan",1, "Overscan",2)
	oLayout.AddEnumControl( "selecter", a, "Select Tool")
	oLayout.AddGroup()
	oLayout.AddItem("tr_x","Top Left X")
	oLayout.AddItem("tr_y","Top Left Y")
	oLayout.AddItem("bl_x","Bot Left X")
	oLayout.AddItem("bl_y","Bot Left Y")
	oLayout.AddRow()
	oLayout.AddButton("PickPositions")
	oLayout.AddButton("PickObject")
	oLayout.AddButton("Reset")
	oLayout.EndRow()
	oLayout.EndGroup()
	oLayout.AddGroup("Pass/Scene Resolution")
	oLayout.AddRow()
	oLayout.AddButton("ApplyRes", "Apply Resolution")
	oLayout.AddButton("RestoreRes", "Reset Resolution")
	oLayout.EndRow()
	oLayout.EndGroup()

def applyres(xpix,ypix):
	oCurrentPass = Application.ActiveProject.ActiveScene.ActivePass.Parameters

	if oCurrentPass("ImageFormatOverride").Value:
		oCurrentPass("ImageFormatPreset").Value = 0
		oCurrentPass("ImageLockAspectRatio").Value = 0
		oCurrentPass("ImageWidth").Value = xpix
		oCurrentPass("ImageHeight").Value = ypix
	else:
		oScene = Application.ActiveProject.ActiveScene.PassContainer.\
		Properties("Scene Render Options").Parameters
		oScene("ImageFormatPreset").Value = 0
		oScene("ImageLockAspectRatio").Value = 0
		oScene("ImageWidth").Value = xpix
		oScene("ImageHeight").Value = ypix


def jj_camresize_overApplyRes_OnClicked( ):
	overx = PPG.Inspected(0).Parameters("overx").Value
	overy = PPG.Inspected(0).Parameters("overy").Value
	xres = PPG.Inspected(0).Parameters("oxres").Value
	yres = PPG.Inspected(0).Parameters("oyres").Value
	
	xpix = overx + xres
	ypix = overy + yres
	
	applyres(xpix,ypix)	
	
def jj_camresize_ApplyRes_OnClicked( ):
	
	tr_x = PPG.Inspected(0).Parameters("tr_x").Value
	tr_y = PPG.Inspected(0).Parameters("tr_y").Value
	bl_x = PPG.Inspected(0).Parameters("bl_x").Value
	bl_y = PPG.Inspected(0).Parameters("bl_y").Value
	
	xpix = abs(tr_x - bl_x)
	ypix = abs(tr_y - bl_y)
	
	applyres(xpix,ypix)	
	
def restoreres(PPG):
	xres = PPG.Inspected(0).Parameters("oxres").Value
	yres = PPG.Inspected(0).Parameters("oyres").Value
	oCurrentPass = Application.ActiveProject.ActiveScene.ActivePass.Parameters
	if oCurrentPass("ImageFormatOverride").Value:
		oCurrentPass("ImageFormatPreset").Value = 0
		oCurrentPass("ImageLockAspectRatio").Value = 0
		oCurrentPass("ImageWidth").Value = xres
		oCurrentPass("ImageHeight").Value = yres
	else:
		oScene = Application.ActiveProject.ActiveScene.PassContainer.\
		Properties("Scene Render Options").Parameters
		oScene("ImageFormatPreset").Value = 0
		oScene("ImageLockAspectRatio").Value = 0
		oScene("ImageWidth").Value = xres
		oScene("ImageHeight").Value = yres
		
def jj_camresize_RestoreRes_OnClicked( ):
	restoreres(PPG)

def jj_camresize_overRestoreRes_OnClicked( ):
	restoreres(PPG)
	
def pickobject():
	obj = Application.PickObject("Pick an object, Pick an object")
	if obj(2) == 0:
		XSIUIToolkit.Msgbox( "Pick Session Aborted", c.siMsgOkOnly)
		return 0
	else:
		return obj(2)
			
def overscan_layout(oLayout):
	oLayout.Clear()
	a = ("Underscan",1, "Overscan",2)
	oLayout.AddEnumControl( "selecter", a, "Select Tool" )
	oLayout.AddGroup( "OverScan Pixels" )
	oLayout.AddItem("overx", "Additional X Pixels")
	oLayout.AddItem("overy", "Additional Y Pixels")
	oLayout.AddGroup("Resolution")
	oLayout.AddRow()
	oLayout.AddButton("overApplyRes", "Apply Resolution")
	oLayout.AddButton("overRestoreRes", "Reset Resolution")
	oLayout.EndRow()
	oLayout.EndGroup()
	
def screenxy(a,res): 
	return (a * res * 0.5) + res * 0.5

def jj_camresize_selecter_OnChanged():
	oLayout = PPG.PPGLayout
	if PPG.Inspected(0).Parameters("selecter").Value == 1:
		underscan_layout(oLayout)
	elif PPG.Inspected(0).Parameters("selecter").Value == 2:
		#reset underscan
		xres = PPG.Inspected(0).Parameters("oxres").Value
		yres = PPG.Inspected(0).Parameters("oyres").Value
	
		PPG.Inspected(0).Parameters("tr_x").Value = 0
		PPG.Inspected(0).Parameters("tr_y").Value = 0
		PPG.Inspected(0).Parameters("bl_x").Value = xres
		PPG.Inspected(0).Parameters("bl_y").Value = yres
		
		#reset any resolution change
		restoreres(PPG)
		overscan_layout(oLayout)
	PPG.Refresh()

def jj_camresize_PickObject_OnClicked( ):
	obj = pickobject()
	if obj and ClassName(obj) == "X3DObject":
		oCamera = Application.Dictionary.GetObject(PPG.camera.Value)
		
		oCamTrans = oCamera.Kinematics.Global.Transform
		objTrans = obj.Kinematics.Global.Transform
		m1 = XSIMath.CreateMatrix4()
		oCamTrans.GetMatrix4(m1)
		m1.InvertInPlace()
		m2 = XSIMath.CreateMatrix4()
		
		pa = obj.ActivePrimitive.Geometry.Points.PositionArray
		pazip = zip(*pa)
		pazip_out = []
		v = XSIMath.CreateVector3()
		objTrans.GetMatrix4(m2)
		
		for x in pazip:
			v.Set(x[0],x[1],x[2])
			v.MulByMatrix4InPlace(m2)
			v.MulByMatrix4InPlace(m1)
			v.Set(v.X/float(v.Z) * -1, v.Y/float(v.Z) * -1, -1)
			pazip_out.append((v.Get2()))
		
		pazip_out = zip(*pazip_out)
		maxx = max(pazip_out[0])
		minx = min(pazip_out[0])
		maxy = max(pazip_out[1])
		miny = min(pazip_out[1])
		maxz = max(pazip_out[2])
		minz = min(pazip_out[2])
		
		fov = PPG.Inspected(0).Parameters("ofov").Value
		fovtype = PPG.Inspected(0).Parameters("ofovtype").Value
		aspect = PPG.Inspected(0).Parameters("oaspect").Value
		
		v.Set(minx,maxy,-1)
		minx, maxy = normalise(v,fov,aspect,fovtype)
		
		v.Set(maxx,miny,-1)
		maxx, miny = normalise(v,fov,aspect,fovtype)
		
		xres = PPG.Inspected(0).Parameters("oxres").Value
		yres = PPG.Inspected(0).Parameters("oyres").Value
		
		PPG.Inspected(0).Parameters("tr_x").Value = screenxy(minx,xres)
		PPG.Inspected(0).Parameters("tr_y").Value = screenxy(maxy,yres)
		PPG.Inspected(0).Parameters("bl_x").Value = screenxy(maxx,xres)
		PPG.Inspected(0).Parameters("bl_y").Value = screenxy(miny,yres)
	else:
		return
	
def jj_camresize_PickPositions_OnClicked( ):
	oCamera = Application.Dictionary.GetObject(PPG.camera.Value)
	
	oCamTrans = oCamera.Kinematics.Global.Transform
	m1 = XSIMath.CreateMatrix4()
	oCamTrans.GetMatrix4(m1)
	m1.InvertInPlace()
	
	fov = PPG.Inspected(0).Parameters("ofov").Value
	aspect = PPG.Inspected(0).Parameters("oaspect").Value
	fovtype = PPG.Inspected(0).Parameters("ofovtype").Value
	xres = PPG.Inspected(0).Parameters("oxres").Value
	yres = PPG.Inspected(0).Parameters("oyres").Value
	pixratio = PPG.Inspected(0).Parameters("opixratio").Value
	l=[]
	pickcount = 0
	
	while pickcount < 2:
		oPos = Application.PickPosition()
		if oPos(0) == 0: break
		
		v0 = XSIMath.CreateVector3()
		v0.Set(oPos[1],oPos[2],oPos[3])
		v0.MulByMatrix4InPlace(m1)
		v0.ScaleInPlace(1.0/-v0.Z)
		
		l.extend(normalise(v0,fov,aspect,fovtype))
		pickcount += 1
	if pickcount == 2:
		PPG.Inspected(0).Parameters("tr_x").Value = screenxy(l[0],xres)
		PPG.Inspected(0).Parameters("tr_y").Value = screenxy(l[1],yres)
		PPG.Inspected(0).Parameters("bl_x").Value = screenxy(l[2],xres)
		PPG.Inspected(0).Parameters("bl_y").Value = screenxy(l[3],yres)
	else: 
		XSIUIToolkit.Msgbox( "Pick Session Aborted", c.siMsgOkOnly)

def jj_camresize_Reset_OnClicked( ):
	
	xres = PPG.Inspected(0).Parameters("oxres").Value
	yres = PPG.Inspected(0).Parameters("oyres").Value
	
	PPG.Inspected(0).Parameters("tr_x").Value = 0
	PPG.Inspected(0).Parameters("tr_y").Value = 0
	PPG.Inspected(0).Parameters("bl_x").Value = xres
	PPG.Inspected(0).Parameters("bl_y").Value = yres
	
	restoreres(PPG)

def jj_camresize_DefineLayout( in_ctxt ):
	oLayout = in_ctxt.Source
	underscan_layout(oLayout)
	return true
	
def jj_camresize_Update( ctxt ):
	output = ctxt.OutputTarget
	oCamera =  ctxt.GetInputValue(0)
	
	selecter = ctxt.GetParameterValue("selecter")
	fov = ctxt.GetParameterValue("ofov")
	aspect = ctxt.GetParameterValue("oaspect")
	fovtype = ctxt.GetParameterValue("ofovtype")
	pixratio  = ctxt.GetParameterValue("opixratio")
	
	xres = ctxt.GetParameterValue("oxres")
	yres = ctxt.GetParameterValue("oyres")
	
	if selecter == 1:
		#calculate pixel resolution
		tr_x = ctxt.GetParameterValue("tr_x")
		tr_y = ctxt.GetParameterValue("tr_y")
		
		bl_x = ctxt.GetParameterValue("bl_x")
		bl_y = ctxt.GetParameterValue("bl_y")
		
		#LogMessage("%s, %s, %s, %s" % (tr_x, tr_y, bl_x, bl_y))
		xpix = abs(tr_x - bl_x)
		ypix = abs(tr_y - bl_y)
		
		#calculate aspect
		naspect = xpix/(ypix/pixratio)
		
		#calculate fov
		degrees = calcfov2(fovtype, fov, xpix, xres, ypix, yres)
		
		#calculate offset to projection plane
		xcen = ((tr_x + bl_x)/2.0)
		ycen = ((tr_y + bl_y)/2.0)
		
		xcen /= xres
		ycen /= yres
		
		xcen -= 0.5
		ycen -= 0.5
		
		output.Parameters( "fov" ).Value = degrees * 2
		output.Parameters( "aspect" ).Value = naspect
		
		#calculate projectionplane offset
		output.Parameters("projplane").Value = 1
		width = oCamera.Parameters("projplanewidth").Value
		height = oCamera.Parameters("projplaneheight").Value
		
		xprop = xcen * width
		yprop = ycen * height
		
		xprop = xprop * (xres/float(xpix))
		yprop = yprop * (yres/float(ypix))
		
		output.Parameters( "projplaneoffx" ).Value = xprop 
		output.Parameters( "projplaneoffy" ).Value = yprop
	elif selecter == 2:
		
		#reset projplan if switch from underscan
		output.Parameters("projplane").Value = 1
		output.Parameters("projplaneoffx").Value = 0.0
		output.Parameters("projplaneoffy").Value = 0.0
		#-----------------------------------------------------------
		overx = ctxt.GetParameterValue("overx")
		overy = ctxt.GetParameterValue("overy")
		newaspect = (xres + overx)/ ((yres + overy) * 1.0/pixratio)
		degrees = calcfov2(fovtype, fov, xres + overx, xres, yres + overy, yres)
	
		output.Parameters( "fov" ).Value = degrees * 2
		output.Parameters( "aspect" ).Value = newaspect
	








 