'''SVG Write in TouchDesigner Class
Authors | matthew ragan
matthewragan.com

v2 Additional features/fixes added by:
kris northern - IG @krisnorthern

New Features:

	
 I added a viewBox parameter to the export to address issues stemming 
from how Inkscape and Illustrator differently handle the "User Units" 
built into the SVG spec (this would result in the same SVG displaying 
very differently in the two programs. The problem stems from Inkscape 
using 96DPI as its native pixel space and Illustrator using 72DPI).

 Issues: Currently the camera mode is only set to orthographic. 

'''

import svgwrite
import numpy as np
import math
import webbrowser
CanvasWidth	= float(parent.svg.par.Canvasmm1)
CanvasHeight	= float(parent.svg.par.Canvasmm2)
xOffset 		= float(parent.svg.par.Canvasmm1)/2
yOffset		= float(parent.svg.par.Canvasmm2)/2
strokeWidth	= float(op('strokeWidth')['width'])*.30
strokeR		= int(float(parent.svg.par.Rgbr*255))
strokeG		= int(float(parent.svg.par.Rgbg*255))
strokeB		= int(float(parent.svg.par.Rgbb*255))
Zoom			= 1
Scalar 		= float(parent.svg.par.Canvasmm2) * Zoom

class Soptosvg:
	'''
	This class is intended to handle writing SVGs from SOPs in TouchDesigner.
	
	'''

	def __init__( self ):
		''' This is the init method for the Soptosvg process
		'''
		self.Polylinesop 			= parent.svg.par.Polylinesop
		self.Polygonsop			= parent.svg.par.Polygonsop
		self.Camera 				= parent.svg.par.Camera
		self.UseCamera 			= parent.svg.par.Usecamera
		self.Aspect 				= (parent.svg.par.Aspect1, parent.svg.par.Aspect2)
		self.Svgtype 				= parent.svg.par.Svgtype
		self.Filepath 				= "{dir}/{file}.svg"
		self.Axidocumentation 		= "<http://wiki.evilmadscientist.com/AxiDraw>"
		self.Axipdf 				= "<http://cdn.evilmadscientist.com/wiki/axidraw/software/AxiDraw_V33.pdf>"
		self.Svgwritedocumentation  = "<http://svgwrite.readthedocs.io/en/latest/svgwrite.html>"
		print( "Sop to SVG Initialized" )
		return

	def WorldToCam(self, oldP):
		'''Method to convert worldspace coords to cameraspace coords.

		Args
		-------------
		oldP (tdu.Position) : the tdu.Position to convert to camera space.

		Returns
		-------------
		newP (tuple) : tuple of x,y coordinates after camera projection. 

		'''
		camera 	= op(self.Camera.eval())
		view 		= camera.transform()
		view.invert()
		pers 		= camera.projection( self.Aspect[0].eval(), self.Aspect[1].eval() )
		viewP 		= view * oldP
		adjusted 	= pers * viewP 
		newX 		= adjusted.x/adjusted.z
		newY 		= adjusted.y/adjusted.z
		camScale 	= .48
		if self.UseCamera:
			newP 		= ((newX *camScale*CanvasWidth)+yOffset, (-newY *camScale*CanvasHeight)+yOffset)
		else:
			newP 		= ((newX *camScale)+yOffset, (-newY *camScale)+yOffset)

		return newP

	def Canvas_size(self):
		''' This is a helper method to return the dimensions of the canvas.

		Returns
		---------------
		canvassize (tupple) : a tupple of width and height dimensions measured in millimeters
		'''

		canvassize 				= (f'{CanvasWidth}mm',f'{CanvasHeight}mm')

		return canvassize

	def viewBox_size(self):
		viewboxsize 			= (f'0, 0, {CanvasWidth}, {CanvasHeight}')

		return viewboxsize
	

	def SavePolyline(self, path, pline):
		''' Polyline is an unclosed list of points that will create a single unfilled line
		
		Returns
		---------------
		formatted_profile (str) : A formatted string populated with the with the supplied information
		'''
		prims 			= pline.prims
		Canvassize 	= self.Canvas_size()
		viewBoxDims 	= self.viewBox_size()
		dwg 			= svgwrite.Drawing(path, profile='tiny', size=Canvassize , viewBox=viewBoxDims)
		
		for item in prims:
			if self.UseCamera:
				newPoints 	= [self.WorldToCam(vert.point.P) for vert in item ]
			else:
				newPoints 	= [((vert.point.x*Scalar)+xOffset , (-vert.point.y*Scalar)+yOffset) for vert in item ]
			newPoly	= dwg.polyline(points=newPoints, stroke=svgwrite.rgb(strokeR, strokeG, strokeB), stroke_width=strokeWidth, fill='none')
			dwg.add(newPoly)
		
		dwg.save()

		return

	def SavePolygon(self, path, pgon):
		''' This is a sample method.
		Returns
		---------------
		formatted_profile (str) : A formatted string populated with the with the supplied information
		'''
		prims 			= pgon.prims
		Canvassize 	= self.Canvas_size()
		viewBoxDims 	= self.viewBox_size()
		dwg 			= svgwrite.Drawing(path, profile='tiny', size=Canvassize , viewBox=viewBoxDims)
		
		for item in prims:
			if self.UseCamera:
				newPoints 	= [self.WorldToCam(vert.point.P) for vert in item ]
			else:
				newPoints 	= [((vert.point.x*Scalar)+xOffset,(-vert.point.y*Scalar)+yOffset) for vert in item ]
				
			newPoly	= dwg.polyline(points=newPoints, stroke=svgwrite.rgb(strokeR, strokeG, strokeB), stroke_width=strokeWidth, fill='none')
			dwg.add(newPoly)

			dwg.save()

	def SavePolygonAndPolygon(self, path, pline, pgon):
		''' This is a sample method.

		This sample method is intended to help illustrate what method docstrings should look like.
						
		Notes
		---------------
		'self' does not need to be included in the Args section.

		Args
		---------------
		name (str): A string name, with spaces as underscores
		age (int): Age as full year measurements
		height (float): Height in meters to 2 significant digits, ex: 1.45

		Examples
		---------------

		Returns
		---------------
		formatted_profile (str) : A formatted string populated with the with the supplied information
		'''
		prims 			= pline.prims
		pgonPrims 		= pgon.prims
		plinePrims 	= pline.prims
		Canvassize 	= self.Canvas_size()
		viewBoxDims 	= self.viewBox_size()
		dwg 			= svgwrite.Drawing(path, profile='tiny', size=Canvassize , viewBox=viewBoxDims)
		
		for item in pgonPrims:
			if self.UseCamera:
				newPoints 	= [self.WorldToCam(vert.point.P) for vert in item ]
			else:
				newPoints 	= [((vert.point.x*Scalar)+xOffset,(-vert.point.y*Scalar)+yOffset) for vert in item ]
				
			newPoly	= dwg.polyline(points=newPoints, stroke=svgwrite.rgb(strokeR, strokeG, strokeB), stroke_width=strokeWidth, fill='none')
			dwg.add(newPoly)
			
		
		for item in plinePrims:
			if self.UseCamera:
				newPoints 	= [self.WorldToCam(vert.point.P) for vert in item ]
			else:
				newPoints 	= [((vert.point.x*Scalar)+xOffset,(-vert.point.y*Scalar)+yOffset) for vert in item ]
				
			newPoly	= dwg.polyline(points=newPoints, stroke=svgwrite.rgb(strokeR, strokeG, strokeB), stroke_width=strokeWidth, fill='none') 
			dwg.add(newPoly)

		dwg.save()
		return

	def Par_check(self, svg_type):
		''' Par_check() is an error handling method.

		Par_check aims to ensrue that all parameters are correctly set up so we can advance to the
		steps of creating our SVGs. This means checking to ensure that all needed fields are
		completed in the TOX. If we pass all of the par check tests then we can move on to 
		writing our SVG file to disk.
						
		Notes
		---------------
		'self' does not need to be included in the Args section.

		Args
		---------------
		svg_type (str): the string name for the inteded type of output - polygon, polyline, both

		Returns
		---------------
		ready (bool) : the results of a set of logical checks to that ensures all requisite 
						pars have been supplied for a sucessful write to disk for the file.
		'''		

		ready 				= False
		title 				= "We're off the RAILS!"
		message 			= '''Hey there, things don't look totally right.
Check on these parameters to make sure everything is in order:\\n{}'''
		buttons 			= ['okay']
		checklist 			= []

		# error handling for geometry permutations
		# handling polyline saving
		if self.Svgtype 	== 'pline':
			if self.Polylinesop != None and op(self.Polylinesop).isSOP:
				pass
			else:
				checklist.append( 'Missing Polygon SOP' )

		# handling polygon saving
		elif self.Svgtype 	== 'pgon':
			if self.Polygonsop != None and op(self.Polygonsop).isSOP:
				pass
			else:
				checklist.append( 'Missing Polyline SOP' )

		# handling combined objects - polyline and polygon saving
		elif self.Svgtype 	== 'both':
			polyline 		= self.Polylinesop != None and op(self.Polylinesop).isSOP
			polygon 		= self.Polygonsop != None and op(self.Polygonsop).isSOP

			# both sops are present
			if polyline and polygon:
				pass
			# missing polyline sop
			elif polygon and not polyline:
				checklist.append( 'Missing Polyline SOP' )
			# missing polygon sop
			elif polyline and not polygon:
				checklist.append( 'Missing Polygon SOP' )
			# missing both polyline and polygon sops
			elif not polyline and not polygon:
				checklist.append( 'Missing Polygon SOP')
				checklist.append( 'Missing Polyline SOP')

		# handling to check for a directory path
		if parent.svg.par.Dir == None or parent.svg.par.Dir.val == '':
			checklist.append( 'Missing Directory Path' )
		
		else:
			pass

		# handling to check for a file path
		if parent.svg.par.Filename == None or parent.svg.par.Filename.val == '':
			checklist.append( 'Missing File name' )

		else:
			pass		

		# we're in the clear, everything is ready to go
		if len(checklist) 			== 0:
			ready 					= True
		# correctly format message for ui.messageBox and warn user about missing elements
		else:
			ready 					= False	
			messageChecklist 		= '\\n'
			for item in checklist:
				messageChecklist 	+= '     * {}\\n'.format(item)
			
			message 				= message.format(messageChecklist)
			ui.messageBox(title, message, buttons=buttons)
		return ready

	def Save(self):
		''' This is the Save method, used to start the process of writing the svg to disk.

		Based on settings in the tox's parameters the Save() method will utilize other
		helper methods to correctly save out the file. Pragmatically, this means first
		ensuring that all pars are correctly set up (error prevention), then the 
		appropriate calling of other methods to ensure that geometry is correclty 
		written to file.
						
		Notes
		---------------
		none

		Args
		---------------
		none

		Returns
		---------------
		none
		'''
		# get the svg type
		svgtype 				= self.Svgtype

		# start with Par_check to see if we're ready to proced.
		readyToContinue 		= self.Par_check( svgtype )

		if readyToContinue:
			filepath 			= self.Filepath.format( dir=parent.svg.par.Dir, 
														file=parent.svg.par.Filename)
			
			if svgtype == 'pline':
				self.SavePolyline(	path=filepath, 
									pline=op(self.Polylinesop))

			elif svgtype == 'pgon':
				self.SavePolygon(	path=filepath,
									pgon=op(self.Polygonsop))

			elif svgtype == 'both':
				self.SavePolygonAndPolygon( path=filepath,
											pline=op(self.Polylinesop), 
											pgon=op(self.Polygonsop))
			else:
				print("Woah... something is very wrong")
				pass

			print(filepath)
			print(self.Polylinesop)
			print(self.Svgtype)
		
		else:
			pass

		return