"""
Speed is a script to set the feedrate, and flowrate.
 
The default 'Activate Speed' checkbox is on.  When it is on, the functions described below will work, when it is off, the
functions will not be called.  The speed script sets the feedrate, and flowrate.  To run speed, in a shell type:
> python speed.py
 
The 'Extrusion Diameter over Thickness is the ratio of the extrusion diameter over the layer thickness, the default is 1.25.  The
extrusion fill density ratio that is printed to the console, ( it is derived quantity not a parameter ) is the area of the extrusion
diameter over the extrusion width over the layer thickness.  Assuming the extrusion diameter is correct, a high value means the
filament will be packed tightly, and the object will be almost as dense as the filament.  If the value is too high, there could be too
little room for the filament, and the extruder will end up plowing through the extra filament.  A low value means the filaments will
be far away from each other, the object will be leaky and light.  The value with the default extrusion preferences is around 0.82.
 
The feedrate for the shape will be set to the 'Feedrate" preference.  The speed of the orbit compared to the operating extruder
speed will be set to the "Orbital Feedrate over Operating Feedrate" preference.  If you want the orbit to be very short, set the
"Orbital Feedrate over Operating Feedrate" preference to a low value like 0.1.
 
In the "Flowrate Choice" radio button group, if "Do Not Add Flowrate" is selected then speed will not add a flowrate to the gcode
output.  If "Metric" is selected, the flowrate in cubic millimeters per second will be added to the output.  If "PWM Setting" is
selected, the value in the "Flowrate PWM Setting" field will be added to the output.
 
The 'Perimeter Feedrate over Operating Feedrate' is the ratio of the feedrate of the perimeter over the feedrate of the infill.  With
the default of 1.0, the perimeter feedrate will be the same as the infill feedrate.  The 'Perimeter Flowrate over Operating Flowrate'
is the ratio of the flowrate of the perimeter over the flowrate of the infill.  With the default of 1.0, the perimeter flow rate will be
the same as the infill flow rate.  To have higher build quality on the outside at the expense of slower build speed, a typical
setting for the 'Perimeter Feedrate over Operating Feedrate' would be 0.5.  To go along with that, if you are using a speed
controlled extruder, the 'Perimeter Flowrate over Operating Flowrate' should also be 0.5.  If you are using Pulse Width Modulation
to control the speed, then you'll probably need a slightly higher ratio because there is a minimum voltage 'Flowrate PWM Setting'
required for the extruder motor to turn.  The flow rate PWM ratio would be determined by trial and error, with the first trial being:
Perimeter Flowrate over Operating Flowrate ~
Perimeter Feedrate over Operating Feedrate * ( Flowrate PWM Setting - Minimum Flowrate PWM Setting )
+ Minimum Flowrate PWM Setting
 
The following examples speed 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 speed.py.  The speed function will speed if "Activate
Speed" is true, which can be set in the dialog or by changing the preferences file 'speed.csv' with a text editor or a spreadsheet
program set to separate tabs.  The functions writeOutput and getSpeedChainGcode check to see if the text has been speeded,
if not they call getMultiplyChainGcode in multiply.py to get multiplied gcode; once they have the multiplied text, then they
speed.
 
 
> python speed.py
This brings up the dialog, after clicking 'Speed', the following is printed:
File Screw Holder Bottom.stl is being chain speeded.
The speeded file is saved as Screw Holder Bottom_speed.gcode
 
 
>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 speed
>>> speed.main()
This brings up the speed dialog.
 
 
>>> speed.writeOutput()
Screw Holder Bottom.stl
File Screw Holder Bottom.stl is being chain speeded.
The speeded file is saved as Screw Holder Bottom_speed.gcode
 
 
>>> speed.getSpeedGcode("
( GCode generated by May 8, 2008 carve.py )
( Extruder Initialization )
..
many lines of gcode
..
")
 
 
>>> speed.getSpeedChainGcode("
( 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.skeinforge_utilities import interpret
from skeinforge_tools import material
from skeinforge_tools import multiply
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 getSpeedChainGcode( fileName, gcodeText, speedPreferences = None ):
	"Speed a gcode linear move text.  Chain speed the gcode if it is not already speeded."
	gcodeText = gcodec.getGcodeFileText( fileName, gcodeText )
	if not gcodec.isProcedureDone( gcodeText, 'multiply' ):
		gcodeText = multiply.getMultiplyChainGcode( fileName, gcodeText )
	return getSpeedGcode( gcodeText, speedPreferences )
 
def getSpeedGcode( gcodeText, speedPreferences = None ):
	"Speed a gcode linear move text."
	if gcodeText == '':
		return ''
	if gcodec.isProcedureDone( gcodeText, 'speed' ):
		return gcodeText
	if speedPreferences == None:
		speedPreferences = SpeedPreferences()
		preferences.readPreferences( speedPreferences )
	if not speedPreferences.activateSpeed.value:
		return gcodeText
	skein = SpeedSkein()
	skein.parseGcode( gcodeText, speedPreferences )
	return skein.output.getvalue()
 
def writeOutput( fileName = '' ):
	"""Speed a gcode linear move file.  Chain speed the gcode if it is not already speeded.
	If no fileName is specified, speed 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 ]
	speedPreferences = SpeedPreferences()
	preferences.readPreferences( speedPreferences )
	startTime = time.time()
	print( 'File ' + gcodec.getSummarizedFilename( fileName ) + ' is being chain speeded.' )
	suffixFilename = fileName[ : fileName.rfind( '.' ) ] + '_speed.gcode'
	speedGcode = getSpeedChainGcode( fileName, '', speedPreferences )
	if speedGcode == '':
		return
	gcodec.writeFileText( suffixFilename, speedGcode )
	print( 'The speeded file is saved as ' + gcodec.getSummarizedFilename( suffixFilename ) )
	analyze.writeOutput( suffixFilename, speedGcode )
	print( 'It took ' + str( int( round( time.time() - startTime ) ) ) + ' seconds to speed the file.' )
 
 
class SpeedPreferences:
	"A class to handle the speed preferences."
	def __init__( self ):
		"Set the default preferences, execute title & preferences fileName."
		#Set the default preferences.
		self.archive = []
		self.activateSpeed = preferences.BooleanPreference().getFromValue( 'Activate Speed:', True )
		self.archive.append( self.activateSpeed )
		self.bridgeFeedrateMultiplier = preferences.FloatPreference().getFromValue( 'Bridge Feedrate Multiplier (ratio):', 1.0 )
		self.archive.append( self.bridgeFeedrateMultiplier )
		self.extrusionDiameterOverThickness = preferences.FloatPreference().getFromValue( 'Extrusion Diameter over Thickness (ratio):', 1.25 )
		self.archive.append( self.extrusionDiameterOverThickness )
		self.feedratePerSecond = preferences.FloatPreference().getFromValue( 'Feedrate (mm/s):', 16.0 )
		self.archive.append( self.feedratePerSecond )
		self.fileNameInput = preferences.Filename().getFromFilename( interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File to be Speeded', '' )
		self.archive.append( self.fileNameInput )
		flowrateRadio = []
		self.flowrateChoiceLabel = preferences.LabelDisplay().getFromName( 'Flowrate Choice: ' )
		self.archive.append( self.flowrateChoiceLabel )
		self.flowrateDoNotAddFlowratePreference = preferences.Radio().getFromRadio( 'Do Not Add Flowrate', flowrateRadio, False )
		self.archive.append( self.flowrateDoNotAddFlowratePreference )
		self.flowrateMetricPreference = preferences.Radio().getFromRadio( 'Metric', flowrateRadio, False )
		self.archive.append( self.flowrateMetricPreference )
		self.flowratePWMPreference = preferences.Radio().getFromRadio( 'PWM Setting', flowrateRadio, True )
		self.archive.append( self.flowratePWMPreference )
		self.flowratePWMSetting = preferences.FloatPreference().getFromValue( 'Flowrate PWM Setting (if PWM Setting is Chosen):', 210.0 )
		self.archive.append( self.flowratePWMSetting )
		self.orbitalFeedrateOverOperatingFeedrate = preferences.FloatPreference().getFromValue( 'Orbital Feedrate over Operating Feedrate (ratio):', 0.5 )
		self.archive.append( self.orbitalFeedrateOverOperatingFeedrate )
		self.perimeterFeedrateOverOperatingFeedrate = preferences.FloatPreference().getFromValue( 'Perimeter Feedrate over Operating Feedrate (ratio):', 1.0 )
		self.archive.append( self.perimeterFeedrateOverOperatingFeedrate )
		self.perimeterFlowrateOverOperatingFlowrate = preferences.FloatPreference().getFromValue( 'Perimeter Flowrate over Operating Flowrate (ratio):', 1.0 )
		self.archive.append( self.perimeterFlowrateOverOperatingFlowrate )
		self.supportFlowrateOverOperatingFlowrate = preferences.FloatPreference().getFromValue( 'Support Flowrate over Operating Flowrate (ratio):', 1.0 )
		self.archive.append( self.supportFlowrateOverOperatingFlowrate )
		self.travelFeedratePerSecond = preferences.FloatPreference().getFromValue( 'Travel Feedrate (mm/s):', 30.0 )
		self.archive.append( self.travelFeedratePerSecond )
		#Create the archive, title of the execute button, title of the dialog & preferences fileName.
		self.executeTitle = 'Speed'
		self.saveTitle = 'Save Preferences'
		preferences.setHelpPreferencesFileNameTitleWindowPosition( self, 'skeinforge_tools.speed.html' )
 
	def execute( self ):
		"Speed button has been clicked."
		fileNames = polyfile.getFileOrDirectoryTypesUnmodifiedGcode( self.fileNameInput.value, interpret.getImportPluginFilenames(), self.fileNameInput.wasCancelled )
		for fileName in fileNames:
			writeOutput( fileName )
 
 
class SpeedSkein:
	"A class to speed a skein of extrusions."
	def __init__( self ):
		self.decimalPlacesCarried = 3
		self.feedratePerSecond = 16.0
		self.isExtruderActive = False
		self.isBridgeLayer = False
		self.isSurroundingLoopBeginning = False
		self.lineIndex = 0
		self.lines = None
		self.oldFlowrateString = None
		self.oldLocation = None
		self.output = cStringIO.StringIO()
 
	def addFlowrateLineIfNecessary( self ):
		"Add flowrate line."
		flowrateString = self.getFlowrateString()
		if flowrateString != self.oldFlowrateString:
			self.addLine( 'M108 S' + flowrateString )
		self.oldFlowrateString = flowrateString
 
	def addLine( self, line ):
		"Add a line of text and a newline to the output."
		if len( line ) > 0:
			self.output.write( line + "\n" )
 
	def getFlowrateString( self ):
		"Get the flowrate string."
		if self.speedPreferences.flowrateDoNotAddFlowratePreference.value:
			return None
		flowrate = self.getOperatingFlowrate()
		if self.isSurroundingLoopBeginning:
			flowrate *= self.speedPreferences.perimeterFlowrateOverOperatingFlowrate.value
		return euclidean.getRoundedToThreePlaces( flowrate )
 
	def getGcodeFromFeedrateMovement( self, feedrateMinute, point ):
		"Get a gcode movement."
		return "G1 X%s Y%s Z%s F%s" % ( self.getRounded( point.x ), self.getRounded( point.y ), self.getRounded( point.z ), self.getRounded( feedrateMinute ) )
 
	def getOperatingFlowrate( self ):
		"Get the operating flowrate."
		if self.speedPreferences.flowratePWMPreference.value:
			return self.speedPreferences.flowratePWMSetting.value
		return self.flowrateCubicMillimetersPerSecond
 
	def getRounded( self, number ):
		"Get number rounded to the number of carried decimal places as a string."
		return euclidean.getRoundedToDecimalPlacesString( self.decimalPlacesCarried, number )
 
	def getSpeededLine( self, splitLine ):
		"Get gcode line with feedrate."
		location = gcodec.getLocationFromSplitLine( self.oldLocation, splitLine )
		self.oldLocation = location
		feedrate = 60.0 * self.feedratePerSecond
		if self.isSurroundingLoopBeginning:
			feedrate *= self.speedPreferences.perimeterFeedrateOverOperatingFeedrate.value
		self.addFlowrateLineIfNecessary()
		if self.isBridgeLayer:
			feedrate *= self.speedPreferences.bridgeFeedrateMultiplier.value
		if not self.isExtruderActive:
			feedrate = self.travelFeedratePerMinute
		return self.getGcodeFromFeedrateMovement( feedrate, location )
 
	def parseGcode( self, gcodeText, speedPreferences ):
		"Parse gcode text and store the speed gcode."
		self.speedPreferences = speedPreferences
		self.feedratePerSecond = speedPreferences.feedratePerSecond.value
		self.orbitalFeedratePerSecond = self.feedratePerSecond * speedPreferences.orbitalFeedrateOverOperatingFeedrate.value
		self.travelFeedratePerMinute = 60.0 * self.speedPreferences.travelFeedratePerSecond.value
		self.lines = gcodec.getTextLines( gcodeText )
		self.parseInitialization()
		for line in self.lines[ self.lineIndex : ]:
			self.parseLine( line )
		circleArea = self.extrusionDiameter * self.extrusionDiameter * math.pi / 4.0
		print( 'The extrusion fill density ratio is ' + euclidean.getRoundedToThreePlaces( circleArea / self.extrusionWidth / self.layerThickness ) )
 
	def parseInitialization( self ):
		"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 ] )
			elif firstWord == '(<layerThickness>':
				self.layerThickness = float( splitLine[ 1 ] )
				self.extrusionDiameter = self.speedPreferences.extrusionDiameterOverThickness.value * self.layerThickness
				self.flowrateCubicMillimetersPerSecond = math.pi * self.extrusionDiameter * self.extrusionDiameter / 4.0 * self.feedratePerSecond
				roundedFlowrate = euclidean.getRoundedToThreePlaces( self.flowrateCubicMillimetersPerSecond )
				self.addLine( '(<flowrateCubicMillimetersPerSecond> ' + roundedFlowrate + ' </flowrateCubicMillimetersPerSecond>)' )
			elif firstWord == '(<extrusionWidth>':
				self.extrusionWidth = float( splitLine[ 1 ] )
				self.addLine( '(<feedrateMinute> %s </feedrateMinute>)' % ( 60.0 * self.feedratePerSecond ) )
				self.addLine( '(<operatingFeedratePerSecond> %s </operatingFeedratePerSecond>)' % self.feedratePerSecond )
				self.addLine( '(<orbitalFeedratePerSecond> %s </orbitalFeedratePerSecond>)' % self.orbitalFeedratePerSecond )
				if not self.speedPreferences.flowrateDoNotAddFlowratePreference.value:
					supportFlowrate = self.speedPreferences.supportFlowrateOverOperatingFlowrate.value * self.getOperatingFlowrate()
					self.addLine( '(<supportFlowrate> %s </supportFlowrate)' % euclidean.getRoundedToThreePlaces( supportFlowrate ) )
				self.addLine( '(<travelFeedratePerSecond> %s </travelFeedratePerSecond>)' % self.speedPreferences.travelFeedratePerSecond.value )
			elif firstWord == '(</extruderInitialization>)':
				self.addLine( '(<procedureDone> speed </procedureDone>)' )
				self.addLine( line )
				self.lineIndex += 1
				return
			self.addLine( line )
 
	def parseLine( self, line ):
		"Parse a gcode line and add it to the speed skein."
		splitLine = line.split()
		if len( splitLine ) < 1:
			return
		firstWord = splitLine[ 0 ]
		if firstWord == 'G1':
			line = self.getSpeededLine( splitLine )
		elif firstWord == 'M101':
			self.isExtruderActive = True
		elif firstWord == 'M103':
			self.isSurroundingLoopBeginning = False
			self.isExtruderActive = False
		elif firstWord == '(<bridgeLayer>)':
			self.isBridgeLayer = True
		elif firstWord == '(<layer>':
			self.isBridgeLayer = False
			self.addFlowrateLineIfNecessary()
		elif firstWord == '(<surroundingLoop>)':
			self.isSurroundingLoopBeginning = True
		self.addLine( line )
 
 
def main():
	"Display the speed dialog."
	if len( sys.argv ) > 1:
		writeOutput( ' '.join( sys.argv[ 1 : ] ) )
	else:
		preferences.displayDialog( SpeedPreferences() )
 
if __name__ == "__main__":
	main()