#!/usr/bin/env python
# optimize.py : Optimizes a set of paths from a pcbGcode.
# [2011.12.25] Mendez written
# [2012.07.31] Mendez cleaned up handles drills

# [System]
import os
import re
from datetime import datetime

# [Installed]
from clint.textui import colored, puts, indent, progress

# [Package]
from pygrbl import argv, util
from pygrbl.util import deltaTime
from pygrbl.gcode import GCode
from pygrbl.tool import Tool


# [Constants]
DESC = 'Python GCode optimizations'
FILEENDING = '_opt' # file ending for optimized file.
Z_MOVE  =   20 # mil  [20] : Height above board for a move
Z_MILL  =   -7 # mil  [-7] : Mill depth into board for traces
Z_SPOT  =   -9 # mil  [-9] : Mill depth for spot drill holes
Z_DRILL =  -63 # mil [-63] : Mill depth for full drill holes


# The Optimize function
def opt(gfile):
  '''Optimization core function:
  Reads in gCode ascii file.
  Processes gcode into toolpath list
  figures out milling.
  Reorders milling to get optimal
  Writes out to new file.'''
  
  start = util.deltaTime()
  util.cprint('Optimizing file: {}\n  Started: {}'.format(gfile.name, start), color='blue')
  
  # Parse the gcode from the ascii to a list of command numbers and location
  gcode = GCode(gfile)
  gcode.parse()
  
  # Take the list and make a toolpath out of it. A toolpath is a list of locations
  # where the bit needs to be moved / milled : [ [x,y,z,t], ...]
  tool = Tool(gcode)
  tool.groupMills()
  util.cprint('Toolpath length: {:.2f} inches, (mill only: {:.2f})'.format(tool.length(),tool.millLength()), color='blue')
  
  # Standardize the mill hight and spot height
  tool.setMillHeight(Z_MILL,Z_SPOT)
  tool.uniqMills() # clean up the mills
  
  
  # This starts the optimization process:
  # start at here, and go to the next path which is closest is the overall plan
  util.cprint('Starting Optimization:', color='blue')
  
  here = [0.0]*3 # start at the origin
  newMills = []  # accumulate mills here
  while len(tool.mills) > 0:
    # No Optimization
    # mill = tool.mills.pop(0)

    # Basic optimization, find the next closest one and use it.
    # mill = tool.getNextMill(here)

    # Advanced Optimization:  Assumes that each mill path closed, so finds 
    #  the mill path which is close to the point and reorders it to be so
    mill = tool.getClosestMill(here)

    # you were here, now you are there
    # move mills and update location
    newMills.append(mill) 
    here = newMills[-1][-1]
  
  # update the tool path with the new mill path
  tool.mills.extend(newMills)
  tool.reTool(Z_MOVE)
  tool.uniq()
  util.cprint('Toolpath length: {:.2f} inches, (mill only: {:.2f})'.format(tool.length(),tool.millLength()), color='blue')

  # Save this with the _opt file ending.
  output = tool.buildGcode()
  outfile = FILEENDING.join(os.path.splitext(gfile.name))
  util.cprint('Writing: {}'.format(outfile), color='green')
  with open(outfile,'w') as f:
    f.write(output)
  
  # how long did this take?
  util.cprint('Time to completion: {}\n'.format(deltaTime(start)), color='green')





if __name__ == '__main__':
  # Initialize the args
  start = util.deltaTime()
  args = argv.arg(description=DESC,
                  getFile=True,       # get gcode to process
                  getMultiFiles=True, # accept any number of files
                  getDevice=False)    # Dont need a Device

  # optimize each file in the list
  for gfile in args.gcode:
    # only process things that end with:
    #  .drill.tap
    #  .etch.tap
    c = re.match(r'(.+)((?P<drill>\.drill\.tap)|(?P<etch>\.etch\.tap))', gfile.name)
    if c is not None:
      opt(gfile)
  
  util.cprint('{} finished in {}'.format(args.name, deltaTime(start)), color='green')












