"""
Cool is a script to cool the shape.
 
The default 'Activate Cool' checkbox is on.  When it is on, the functions described below will work, when it is off, the functions
will not be called.
 
The important value for the cool preferences is "Minimum Layer Time (seconds)" which is the minimum amount of time the
extruder will spend on a layer.  If it takes less time to extrude the layer than the minimum layer time, cool adds orbits with the
extruder off to give the layer time to cool, so that the next layer is not extruded on a molten base.
 
If the 'Turn Fan On at Beginning' preference is true, cool will turn the fan on at the beginning of the fabrication.  If the
'Turn Fan Off at Ending' preference is true, cool will turn the fan off at the ending of the fabrication.
 
To run cool, in a shell which cool is in type:
> python cool.py
 
The following examples cool the files Screw Holder Bottom.gcode & Screw Holder Bottom.stl.  The examples are run in a terminal in the
folder which contains Screw Holder Bottom.gcode, Screw Holder Bottom.stl and cool.py.  The cool function will cool if the 'Activate Cool'
checkbox is on.  The functions writeOutput and getCoolChainGcode check to see if the text has been cooled, if not they
call the getClipChainGcode in clip.py to clip the text; once they have the clipped text, then they cool.
 
 
> python cool.py
This brings up the dialog, after clicking 'Cool', the following is printed:
File Screw Holder Bottom.stl is being chain cooled.
The extrusion fill density ratio is 0.853
The cooled file is saved as Screw Holder Bottom.gcode
The scalable vector graphics file is saved as Hollow_Square_cool.svg
It took 34 seconds to cool the file.
 
 
> python cool.py Screw Holder Bottom.stl
File Screw Holder Bottom.stl is being chain cooled.
The extrusion fill density ratio is 0.853
The cooled file is saved as Screw Holder Bottom.gcode
The scalable vector graphics file is saved as Hollow_Square_cool.svg
It took 34 seconds to cool the file.
 
 
> python
Python 2.5.1 (r251:54863, Sep 22 2007, 01:43:31)
[GCC 4.2.1 (SUSE Linux)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import cool
>>> cool.main()
This brings up the cool dialog.
 
 
>>> cool.writeOutput()
File Screw Holder Bottom.stl is being chain cooled.
The extrusion fill density ratio is 0.853
The cooled file is saved as Screw Holder Bottom.gcode
The scalable vector graphics file is saved as Hollow_Square_cool.svg
It took 34 seconds to cool the file.
 
 
>>> cool.getCoolGcode("
( GCode generated by May 8, 2008 carve.py )
( Extruder Initialization )
..
many lines of gcode
..
")
 
 
>>> cool.getCoolChainGcode("
( GCode generated by May 8, 2008 carve.py )
( Extruder Initialization )
..
many lines of gcode
..
")
 
"""
 
from __future__ import absolute_import
#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
import __init__
 
from skeinforge_tools.skeinforge_utilities import euclidean
from skeinforge_tools.skeinforge_utilities import gcodec
from skeinforge_tools.skeinforge_utilities import intercircle
from skeinforge_tools.skeinforge_utilities import preferences
from skeinforge_tools import analyze
from skeinforge_tools import clip
from skeinforge_tools.skeinforge_utilities import interpret
from skeinforge_tools import polyfile
import cStringIO
import math
import sys
import time
 
 
__author__ = "Enrique Perez (perez_enrique@yahoo.com)"
__date__ = "$Date: 2008/21/04 $"
__license__ = "GPL 3.0"
 
 
def getCoolChainGcode( fileName, gcodeText, coolPreferences = None ):
	"Cool a gcode linear move text.  Chain cool the gcode if it is not already cooled."
	gcodeText = gcodec.getGcodeFileText( fileName, gcodeText )
	if not gcodec.isProcedureDone( gcodeText, 'clip' ):
		gcodeText = clip.getClipChainGcode( fileName, gcodeText )
	return getCoolGcode( gcodeText, coolPreferences )
 
def getCoolGcode( gcodeText, coolPreferences = None ):
	"Cool a gcode linear move text."
	if gcodeText == '':
		return ''
	if gcodec.isProcedureDone( gcodeText, 'cool' ):
		return gcodeText
	if coolPreferences == None:
		coolPreferences = CoolPreferences()
		preferences.readPreferences( coolPreferences )
	if not coolPreferences.activateCool.value:
		return gcodeText
	skein = CoolSkein()
	skein.parseGcode( gcodeText, coolPreferences )
	return skein.output.getvalue()
 
def writeOutput( fileName = '' ):
	"Cool a gcode linear move file.  Chain cool the gcode if it is not already cooled. If no fileName is specified, cool the first unmodified gcode file in this folder."
	if fileName == '':
		unmodified = interpret.getGNUTranslatorFilesUnmodified()
		if len( unmodified ) == 0:
			print( "There are no unmodified gcode files in this folder." )
			return
		fileName = unmodified[ 0 ]
	coolPreferences = CoolPreferences()
	preferences.readPreferences( coolPreferences )
	startTime = time.time()
	print( 'File ' + gcodec.getSummarizedFilename( fileName ) + ' is being chain cooled.' )
	suffixFilename = fileName[ : fileName.rfind( '.' ) ] + '_cool.gcode'
	coolGcode = getCoolChainGcode( fileName, '', coolPreferences )
	if coolGcode == '':
		return
	gcodec.writeFileText( suffixFilename, coolGcode )
	print( 'The cooled file is saved as ' + gcodec.getSummarizedFilename( suffixFilename ) )
	analyze.writeOutput( suffixFilename, coolGcode )
	print( 'It took ' + str( int( round( time.time() - startTime ) ) ) + ' seconds to cool the file.' )
 
 
class CoolPreferences:
	"A class to handle the cool preferences."
	def __init__( self ):
		"Set the default preferences, execute title & preferences fileName."
		#Set the default preferences.
		self.archive = []
		self.activateCool = preferences.BooleanPreference().getFromValue( 'Activate Cool', True )
		self.archive.append( self.activateCool )
		self.fileNameInput = preferences.Filename().getFromFilename( interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File to be Cooled', '' )
		self.archive.append( self.fileNameInput )
		self.minimumLayerTime = preferences.FloatPreference().getFromValue( 'Minimum Layer Time (seconds):', 60.0 )
		self.archive.append( self.minimumLayerTime )
		self.turnFanOnAtBeginning = preferences.BooleanPreference().getFromValue( 'Turn Fan On at Beginning', True )
		self.archive.append( self.turnFanOnAtBeginning )
		self.turnFanOffAtEnding = preferences.BooleanPreference().getFromValue( 'Turn Fan Off at Ending', True )
		self.archive.append( self.turnFanOffAtEnding )
		#Create the archive, title of the execute button, title of the dialog & preferences fileName.
		self.executeTitle = 'Cool'
		self.saveTitle = 'Save Preferences'
		preferences.setHelpPreferencesFileNameTitleWindowPosition( self, 'skeinforge_tools.cool.html' )
 
	def execute( self ):
		"Cool button has been clicked."
		fileNames = polyfile.getFileOrDirectoryTypesUnmodifiedGcode( self.fileNameInput.value, interpret.getImportPluginFilenames(), self.fileNameInput.wasCancelled )
		for fileName in fileNames:
			writeOutput( fileName )
 
 
class CoolSkein:
	"A class to cool a skein of extrusions."
	def __init__( self ):
		self.boundaryLayer = None
		self.decimalPlacesCarried = 3
		self.feedrateMinute = 960.0
		self.highestZ = - 99999999.9
		self.layerTime = 0.0
		self.lineIndex = 0
		self.lines = None
		self.oldLocation = None
		self.output = cStringIO.StringIO()
 
	def addGcodeFromFeedrateMovementZ( self, feedrateMinute, point, z ):
		"Add a movement to the output."
		self.addLine( "G1 X%s Y%s Z%s F%s" % ( self.getRounded( point.real ), self.getRounded( point.imag ), self.getRounded( z ), self.getRounded( feedrateMinute ) ) )
 
	def addLine( self, line ):
		"Add a line of text and a newline to the output."
		self.output.write( line + "\n" )
 
	def getRounded( self, number ):
		"Get number rounded to the number of carried decimal places as a string."
		return euclidean.getRoundedToDecimalPlacesString( self.decimalPlacesCarried, number )
 
	def linearMove( self, splitLine ):
		"Add line to time spent on layer."
		self.feedrateMinute = gcodec.getFeedrateMinute( self.feedrateMinute, splitLine )
		location = gcodec.getLocationFromSplitLine( self.oldLocation, splitLine )
		if self.oldLocation != None:
			feedrateSecond = self.feedrateMinute / 60.0
			self.layerTime += location.distance( self.oldLocation ) / feedrateSecond
		self.highestZ = max( location.z, self.highestZ )
		self.oldLocation = location
 
	def parseGcode( self, gcodeText, coolPreferences ):
		"Parse gcode text and store the cool gcode."
		self.lines = gcodec.getTextLines( gcodeText )
		self.parseInitialization( coolPreferences )
		for self.lineIndex in xrange( self.lineIndex, len( self.lines ) ):
			line = self.lines[ self.lineIndex ]
			self.parseLine( coolPreferences, line )
		if coolPreferences.turnFanOffAtEnding.value:
			self.addLine( 'M107' )
 
	def parseInitialization( self, coolPreferences ):
		"Parse gcode initialization and store the parameters."
		for self.lineIndex in xrange( len( self.lines ) ):
			line = self.lines[ self.lineIndex ]
			splitLine = line.split()
			firstWord = gcodec.getFirstWord( splitLine )
			if firstWord == '(<decimalPlacesCarried>':
				self.decimalPlacesCarried = int( splitLine[ 1 ] )
				if coolPreferences.turnFanOnAtBeginning.value:
					self.addLine( 'M106' )
			elif firstWord == '(<extrusionPerimeterWidth>':
				self.extrusionPerimeterWidth = float( splitLine[ 1 ] )
			elif firstWord == '(</extruderInitialization>)':
				self.addLine( '(<procedureDone> cool </procedureDone>)' )
				return
			elif firstWord == '(<orbitalFeedratePerSecond>':
				self.orbitalFeedratePerSecond = float( splitLine[ 1 ] )
			self.addLine( line )
 
	def parseLine( self, coolPreferences, line ):
		"Parse a gcode line and add it to the cool skein."
		splitLine = line.split()
		if len( splitLine ) < 1:
			return
		firstWord = splitLine[ 0 ]
		if firstWord == 'G1':
			self.linearMove( splitLine )
		elif firstWord == '(<boundaryPoint>':
			self.boundaryLoop.append( gcodec.getLocationFromSplitLine( None, splitLine ).dropAxis( 2 ) )
		elif firstWord == '(<layer>':
			remainingOrbitTime = coolPreferences.minimumLayerTime.value - self.layerTime
			if remainingOrbitTime > 0.0 and self.boundaryLayer != None:
				intercircle.addOperatingOrbits( self.boundaryLayer.loops, euclidean.getXYComplexFromVector3( self.oldLocation ), self, remainingOrbitTime, self.highestZ )
			z = float( splitLine[ 1 ] )
			self.boundaryLayer = euclidean.LoopLayer( z )
			self.highestZ = z
			self.layerTime = 0.0
		elif firstWord == '(<surroundingLoop>)':
			self.boundaryLoop = []
			self.boundaryLayer.loops.append( self.boundaryLoop )
		self.addLine( line )
 
 
def main( hashtable = None ):
	"Display the cool dialog."
	if len( sys.argv ) > 1:
		writeOutput( ' '.join( sys.argv[ 1 : ] ) )
	else:
		preferences.displayDialog( CoolPreferences() )
 
if __name__ == "__main__":
	main()